diff --git a/orcinus/GeoIP2/README.md b/orcinus/GeoIP2/README.md deleted file mode 100644 index d44db02..0000000 --- a/orcinus/GeoIP2/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Maxmind GeoIP2 Geolocation - -To enable the Geolocation service for items in the Query Log, follow the steps below: - -1. Download the latest Maxmind GeoIP2 .phar file from the Github, and place it in the same directory as this README file: https://github.com/maxmind/GeoIP2-php/releases -2. Login at the Maxmind website; account registration is free: https://www.maxmind.com/en/account/login -3. Navigate to the "Downloads" area of your Maxmind account, and download the GeoLite Country (not CSV) GZIP package. -4. Unzip the 'GeoLite2-Country.mmdb' file and place it in the same directory as this README.txt file. diff --git a/orcinus/Mustache/Autoloader.php b/orcinus/Mustache/Autoloader.php deleted file mode 100644 index e8ea3f4..0000000 --- a/orcinus/Mustache/Autoloader.php +++ /dev/null @@ -1,88 +0,0 @@ -baseDir = $realDir; - } else { - $this->baseDir = $baseDir; - } - } - - /** - * Register a new instance as an SPL autoloader. - * - * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..') - * - * @return Mustache_Autoloader Registered Autoloader instance - */ - public static function register($baseDir = null) - { - $key = $baseDir ? $baseDir : 0; - - if (!isset(self::$instances[$key])) { - self::$instances[$key] = new self($baseDir); - } - - $loader = self::$instances[$key]; - spl_autoload_register(array($loader, 'autoload')); - - return $loader; - } - - /** - * Autoload Mustache classes. - * - * @param string $class - */ - public function autoload($class) - { - if ($class[0] === '\\') { - $class = substr($class, 1); - } - - if (strpos($class, 'Mustache') !== 0) { - return; - } - - $file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class)); - if (is_file($file)) { - require $file; - } - } -} diff --git a/orcinus/Mustache/Cache.php b/orcinus/Mustache/Cache.php deleted file mode 100644 index 3292efa..0000000 --- a/orcinus/Mustache/Cache.php +++ /dev/null @@ -1,43 +0,0 @@ -logger; - } - - /** - * Set a logger instance. - * - * @param Mustache_Logger|Psr\Log\LoggerInterface $logger - */ - public function setLogger($logger = null) - { - if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) { - throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.'); - } - - $this->logger = $logger; - } - - /** - * Add a log record if logging is enabled. - * - * @param string $level The logging level - * @param string $message The log message - * @param array $context The log context - */ - protected function log($level, $message, array $context = array()) - { - if (isset($this->logger)) { - $this->logger->log($level, $message, $context); - } - } -} diff --git a/orcinus/Mustache/Cache/FilesystemCache.php b/orcinus/Mustache/Cache/FilesystemCache.php deleted file mode 100644 index 3e742b7..0000000 --- a/orcinus/Mustache/Cache/FilesystemCache.php +++ /dev/null @@ -1,161 +0,0 @@ -cache($className, $compiledSource); - * - * The FilesystemCache benefits from any opcode caching that may be setup in your environment. So do that, k? - */ -class Mustache_Cache_FilesystemCache extends Mustache_Cache_AbstractCache -{ - private $baseDir; - private $fileMode; - - /** - * Filesystem cache constructor. - * - * @param string $baseDir Directory for compiled templates - * @param int $fileMode Override default permissions for cache files. Defaults to using the system-defined umask - */ - public function __construct($baseDir, $fileMode = null) - { - $this->baseDir = $baseDir; - $this->fileMode = $fileMode; - } - - /** - * Load the class from cache using `require_once`. - * - * @param string $key - * - * @return bool - */ - public function load($key) - { - $fileName = $this->getCacheFilename($key); - if (!is_file($fileName)) { - return false; - } - - require_once $fileName; - - return true; - } - - /** - * Cache and load the compiled class. - * - * @param string $key - * @param string $value - */ - public function cache($key, $value) - { - $fileName = $this->getCacheFilename($key); - - $this->log( - Mustache_Logger::DEBUG, - 'Writing to template cache: "{fileName}"', - array('fileName' => $fileName) - ); - - $this->writeFile($fileName, $value); - $this->load($key); - } - - /** - * Build the cache filename. - * Subclasses should override for custom cache directory structures. - * - * @param string $name - * - * @return string - */ - protected function getCacheFilename($name) - { - return sprintf('%s/%s.php', $this->baseDir, $name); - } - - /** - * Create cache directory. - * - * @throws Mustache_Exception_RuntimeException If unable to create directory - * - * @param string $fileName - * - * @return string - */ - private function buildDirectoryForFilename($fileName) - { - $dirName = dirname($fileName); - if (!is_dir($dirName)) { - $this->log( - Mustache_Logger::INFO, - 'Creating Mustache template cache directory: "{dirName}"', - array('dirName' => $dirName) - ); - - @mkdir($dirName, 0777, true); - // @codeCoverageIgnoreStart - if (!is_dir($dirName)) { - throw new Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName)); - } - // @codeCoverageIgnoreEnd - } - - return $dirName; - } - - /** - * Write cache file. - * - * @throws Mustache_Exception_RuntimeException If unable to write file - * - * @param string $fileName - * @param string $value - */ - private function writeFile($fileName, $value) - { - $dirName = $this->buildDirectoryForFilename($fileName); - - $this->log( - Mustache_Logger::DEBUG, - 'Caching compiled template to "{fileName}"', - array('fileName' => $fileName) - ); - - $tempFile = tempnam($dirName, basename($fileName)); - if (false !== @file_put_contents($tempFile, $value)) { - if (@rename($tempFile, $fileName)) { - $mode = isset($this->fileMode) ? $this->fileMode : (0666 & ~umask()); - @chmod($fileName, $mode); - - return; - } - - // @codeCoverageIgnoreStart - $this->log( - Mustache_Logger::ERROR, - 'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"', - array('tempName' => $tempFile, 'fileName' => $fileName) - ); - // @codeCoverageIgnoreEnd - } - - // @codeCoverageIgnoreStart - throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".', $fileName)); - // @codeCoverageIgnoreEnd - } -} diff --git a/orcinus/Mustache/Cache/NoopCache.php b/orcinus/Mustache/Cache/NoopCache.php deleted file mode 100644 index ed9eec9..0000000 --- a/orcinus/Mustache/Cache/NoopCache.php +++ /dev/null @@ -1,47 +0,0 @@ -log( - Mustache_Logger::WARNING, - 'Template cache disabled, evaluating "{className}" class at runtime', - array('className' => $key) - ); - eval('?>' . $value); - } -} diff --git a/orcinus/Mustache/Compiler.php b/orcinus/Mustache/Compiler.php deleted file mode 100644 index 2b0d1f9..0000000 --- a/orcinus/Mustache/Compiler.php +++ /dev/null @@ -1,718 +0,0 @@ -pragmas = $this->defaultPragmas; - $this->sections = array(); - $this->blocks = array(); - $this->source = $source; - $this->indentNextLine = true; - $this->customEscape = $customEscape; - $this->entityFlags = $entityFlags; - $this->charset = $charset; - $this->strictCallables = $strictCallables; - - return $this->writeCode($tree, $name); - } - - /** - * Enable pragmas across all templates, regardless of the presence of pragma - * tags in the individual templates. - * - * @internal Users should set global pragmas in Mustache_Engine, not here :) - * - * @param string[] $pragmas - */ - public function setPragmas(array $pragmas) - { - $this->pragmas = array(); - foreach ($pragmas as $pragma) { - $this->pragmas[$pragma] = true; - } - $this->defaultPragmas = $this->pragmas; - } - - /** - * Helper function for walking the Mustache token parse tree. - * - * @throws Mustache_Exception_SyntaxException upon encountering unknown token types - * - * @param array $tree Parse tree of Mustache tokens - * @param int $level (default: 0) - * - * @return string Generated PHP source code - */ - private function walk(array $tree, $level = 0) - { - $code = ''; - $level++; - foreach ($tree as $node) { - switch ($node[Mustache_Tokenizer::TYPE]) { - case Mustache_Tokenizer::T_PRAGMA: - $this->pragmas[$node[Mustache_Tokenizer::NAME]] = true; - break; - - case Mustache_Tokenizer::T_SECTION: - $code .= $this->section( - $node[Mustache_Tokenizer::NODES], - $node[Mustache_Tokenizer::NAME], - isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), - $node[Mustache_Tokenizer::INDEX], - $node[Mustache_Tokenizer::END], - $node[Mustache_Tokenizer::OTAG], - $node[Mustache_Tokenizer::CTAG], - $level - ); - break; - - case Mustache_Tokenizer::T_INVERTED: - $code .= $this->invertedSection( - $node[Mustache_Tokenizer::NODES], - $node[Mustache_Tokenizer::NAME], - isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), - $level - ); - break; - - case Mustache_Tokenizer::T_PARTIAL: - $code .= $this->partial( - $node[Mustache_Tokenizer::NAME], - isset($node[Mustache_Tokenizer::DYNAMIC]) ? $node[Mustache_Tokenizer::DYNAMIC] : false, - isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', - $level - ); - break; - - case Mustache_Tokenizer::T_PARENT: - $code .= $this->parent( - $node[Mustache_Tokenizer::NAME], - isset($node[Mustache_Tokenizer::DYNAMIC]) ? $node[Mustache_Tokenizer::DYNAMIC] : false, - isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', - $node[Mustache_Tokenizer::NODES], - $level - ); - break; - - case Mustache_Tokenizer::T_BLOCK_ARG: - $code .= $this->blockArg( - $node[Mustache_Tokenizer::NODES], - $node[Mustache_Tokenizer::NAME], - $node[Mustache_Tokenizer::INDEX], - $node[Mustache_Tokenizer::END], - $node[Mustache_Tokenizer::OTAG], - $node[Mustache_Tokenizer::CTAG], - $level - ); - break; - - case Mustache_Tokenizer::T_BLOCK_VAR: - $code .= $this->blockVar( - $node[Mustache_Tokenizer::NODES], - $node[Mustache_Tokenizer::NAME], - $node[Mustache_Tokenizer::INDEX], - $node[Mustache_Tokenizer::END], - $node[Mustache_Tokenizer::OTAG], - $node[Mustache_Tokenizer::CTAG], - $level - ); - break; - - case Mustache_Tokenizer::T_COMMENT: - break; - - case Mustache_Tokenizer::T_ESCAPED: - case Mustache_Tokenizer::T_UNESCAPED: - case Mustache_Tokenizer::T_UNESCAPED_2: - $code .= $this->variable( - $node[Mustache_Tokenizer::NAME], - isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(), - $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED, - $level - ); - break; - - case Mustache_Tokenizer::T_TEXT: - $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level); - break; - - default: - throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node); - } - } - - return $code; - } - - const KLASS = 'lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context); - $buffer = \'\'; - %s - - return $buffer; - } - %s - %s - }'; - - const KLASS_NO_LAMBDAS = 'walk($tree); - $sections = implode("\n", $this->sections); - $blocks = implode("\n", $this->blocks); - $klass = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS; - - $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : ''; - - return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections, $blocks); - } - - const BLOCK_VAR = ' - $blockFunction = $context->findInBlock(%s); - if (is_callable($blockFunction)) { - $buffer .= call_user_func($blockFunction, $context); - %s} - '; - - const BLOCK_VAR_ELSE = '} else {%s'; - - /** - * Generate Mustache Template inheritance block variable PHP source. - * - * @param array $nodes Array of child tokens - * @param string $id Section name - * @param int $start Section start offset - * @param int $end Section end offset - * @param string $otag Current Mustache opening tag - * @param string $ctag Current Mustache closing tag - * @param int $level - * - * @return string Generated PHP source code - */ - private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level) - { - $id = var_export($id, true); - - $else = $this->walk($nodes, $level); - if ($else !== '') { - $else = sprintf($this->prepare(self::BLOCK_VAR_ELSE, $level + 1, false, true), $else); - } - - return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $else); - } - - const BLOCK_ARG = '%s => array($this, \'block%s\'),'; - - /** - * Generate Mustache Template inheritance block argument PHP source. - * - * @param array $nodes Array of child tokens - * @param string $id Section name - * @param int $start Section start offset - * @param int $end Section end offset - * @param string $otag Current Mustache opening tag - * @param string $ctag Current Mustache closing tag - * @param int $level - * - * @return string Generated PHP source code - */ - private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level) - { - $key = $this->block($nodes); - $id = var_export($id, true); - - return sprintf($this->prepare(self::BLOCK_ARG, $level), $id, $key); - } - - const BLOCK_FUNCTION = ' - public function block%s($context) - { - $indent = $buffer = \'\';%s - - return $buffer; - } - '; - - /** - * Generate Mustache Template inheritance block function PHP source. - * - * @param array $nodes Array of child tokens - * - * @return string key of new block function - */ - private function block($nodes) - { - $code = $this->walk($nodes, 0); - $key = ucfirst(md5($code)); - - if (!isset($this->blocks[$key])) { - $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code); - } - - return $key; - } - - const SECTION_CALL = ' - $value = $context->%s(%s);%s - $buffer .= $this->section%s($context, $indent, $value); - '; - - const SECTION = ' - private function section%s(Mustache_Context $context, $indent, $value) - { - $buffer = \'\'; - - if (%s) { - $source = %s; - $result = (string) call_user_func($value, $source, %s); - if (strpos($result, \'{{\') === false) { - $buffer .= $result; - } else { - $buffer .= $this->mustache - ->loadLambda($result%s) - ->renderInternal($context); - } - } elseif (!empty($value)) { - $values = $this->isIterable($value) ? $value : array($value); - foreach ($values as $value) { - $context->push($value); - %s - $context->pop(); - } - } - - return $buffer; - } - '; - - /** - * Generate Mustache Template section PHP source. - * - * @param array $nodes Array of child tokens - * @param string $id Section name - * @param string[] $filters Array of filters - * @param int $start Section start offset - * @param int $end Section end offset - * @param string $otag Current Mustache opening tag - * @param string $ctag Current Mustache closing tag - * @param int $level - * - * @return string Generated section PHP source code - */ - private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level) - { - $source = var_export(substr($this->source, $start, $end - $start), true); - $callable = $this->getCallable(); - - if ($otag !== '{{' || $ctag !== '}}') { - $delimTag = var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true); - $helper = sprintf('$this->lambdaHelper->withDelimiters(%s)', $delimTag); - $delims = ', ' . $delimTag; - } else { - $helper = '$this->lambdaHelper'; - $delims = ''; - } - - $key = ucfirst(md5($delims . "\n" . $source)); - - if (!isset($this->sections[$key])) { - $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $delims, $this->walk($nodes, 2)); - } - - $method = $this->getFindMethod($id); - $id = var_export($id, true); - $filters = $this->getFilters($filters, $level); - - return sprintf($this->prepare(self::SECTION_CALL, $level), $method, $id, $filters, $key); - } - - const INVERTED_SECTION = ' - $value = $context->%s(%s);%s - if (empty($value)) { - %s - } - '; - - /** - * Generate Mustache Template inverted section PHP source. - * - * @param array $nodes Array of child tokens - * @param string $id Section name - * @param string[] $filters Array of filters - * @param int $level - * - * @return string Generated inverted section PHP source code - */ - private function invertedSection($nodes, $id, $filters, $level) - { - $method = $this->getFindMethod($id); - $id = var_export($id, true); - $filters = $this->getFilters($filters, $level); - - return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $filters, $this->walk($nodes, $level)); - } - - const DYNAMIC_NAME = '$this->resolveValue($context->%s(%s), $context)'; - - /** - * Generate Mustache Template dynamic name resolution PHP source. - * - * @param string $id Tag name - * @param bool $dynamic True if the name is dynamic - * - * @return string Dynamic name resolution PHP source code - */ - private function resolveDynamicName($id, $dynamic) - { - if (!$dynamic) { - return var_export($id, true); - } - - $method = $this->getFindMethod($id); - $id = ($method !== 'last') ? var_export($id, true) : ''; - - // TODO: filters? - - return sprintf(self::DYNAMIC_NAME, $method, $id); - } - - const PARTIAL_INDENT = ', $indent . %s'; - const PARTIAL = ' - if ($partial = $this->mustache->loadPartial(%s)) { - $buffer .= $partial->renderInternal($context%s); - } - '; - - /** - * Generate Mustache Template partial call PHP source. - * - * @param string $id Partial name - * @param bool $dynamic Partial name is dynamic - * @param string $indent Whitespace indent to apply to partial - * @param int $level - * - * @return string Generated partial call PHP source code - */ - private function partial($id, $dynamic, $indent, $level) - { - if ($indent !== '') { - $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true)); - } else { - $indentParam = ''; - } - - return sprintf( - $this->prepare(self::PARTIAL, $level), - $this->resolveDynamicName($id, $dynamic), - $indentParam - ); - } - - const PARENT = ' - if ($parent = $this->mustache->loadPartial(%s)) { - $context->pushBlockContext(array(%s - )); - $buffer .= $parent->renderInternal($context, $indent); - $context->popBlockContext(); - } - '; - - const PARENT_NO_CONTEXT = ' - if ($parent = $this->mustache->loadPartial(%s)) { - $buffer .= $parent->renderInternal($context, $indent); - } - '; - - /** - * Generate Mustache Template inheritance parent call PHP source. - * - * @param string $id Parent tag name - * @param bool $dynamic Tag name is dynamic - * @param string $indent Whitespace indent to apply to parent - * @param array $children Child nodes - * @param int $level - * - * @return string Generated PHP source code - */ - private function parent($id, $dynamic, $indent, array $children, $level) - { - $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs')); - $partialName = $this->resolveDynamicName($id, $dynamic); - - if (empty($realChildren)) { - return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), $partialName); - } - - return sprintf( - $this->prepare(self::PARENT, $level), - $partialName, - $this->walk($realChildren, $level + 1) - ); - } - - /** - * Helper method for filtering out non-block-arg tokens. - * - * @param array $node - * - * @return bool True if $node is a block arg token - */ - private static function onlyBlockArgs(array $node) - { - return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG; - } - - const VARIABLE = ' - $value = $this->resolveValue($context->%s(%s), $context);%s - $buffer .= %s($value === null ? \'\' : %s); - '; - - /** - * Generate Mustache Template variable interpolation PHP source. - * - * @param string $id Variable name - * @param string[] $filters Array of filters - * @param bool $escape Escape the variable value for output? - * @param int $level - * - * @return string Generated variable interpolation PHP source - */ - private function variable($id, $filters, $escape, $level) - { - $method = $this->getFindMethod($id); - $id = ($method !== 'last') ? var_export($id, true) : ''; - $filters = $this->getFilters($filters, $level); - $value = $escape ? $this->getEscape() : '$value'; - - return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value); - } - - const FILTER = ' - $filter = $context->%s(%s); - if (!(%s)) { - throw new Mustache_Exception_UnknownFilterException(%s); - } - $value = call_user_func($filter, $value);%s - '; - - /** - * Generate Mustache Template variable filtering PHP source. - * - * @param string[] $filters Array of filters - * @param int $level - * - * @return string Generated filter PHP source - */ - private function getFilters(array $filters, $level) - { - if (empty($filters)) { - return ''; - } - - $name = array_shift($filters); - $method = $this->getFindMethod($name); - $filter = ($method !== 'last') ? var_export($name, true) : ''; - $callable = $this->getCallable('$filter'); - $msg = var_export($name, true); - - return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level)); - } - - const LINE = '$buffer .= "\n";'; - const TEXT = '$buffer .= %s%s;'; - - /** - * Generate Mustache Template output Buffer call PHP source. - * - * @param string $text - * @param int $level - * - * @return string Generated output Buffer call PHP source - */ - private function text($text, $level) - { - $indentNextLine = (substr($text, -1) === "\n"); - $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true)); - $this->indentNextLine = $indentNextLine; - - return $code; - } - - /** - * Prepare PHP source code snippet for output. - * - * @param string $text - * @param int $bonus Additional indent level (default: 0) - * @param bool $prependNewline Prepend a newline to the snippet? (default: true) - * @param bool $appendNewline Append a newline to the snippet? (default: false) - * - * @return string PHP source code snippet - */ - private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false) - { - $text = ($prependNewline ? "\n" : '') . trim($text); - if ($prependNewline) { - $bonus++; - } - if ($appendNewline) { - $text .= "\n"; - } - - return preg_replace("/\n( {8})?/", "\n" . str_repeat(' ', $bonus * 4), $text); - } - - const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)'; - const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)'; - - /** - * Get the current escaper. - * - * @param string $value (default: '$value') - * - * @return string Either a custom callback, or an inline call to `htmlspecialchars` - */ - private function getEscape($value = '$value') - { - if ($this->customEscape) { - return sprintf(self::CUSTOM_ESCAPE, $value); - } - - return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true)); - } - - /** - * Select the appropriate Context `find` method for a given $id. - * - * The return value will be one of `find`, `findDot`, `findAnchoredDot` or `last`. - * - * @see Mustache_Context::find - * @see Mustache_Context::findDot - * @see Mustache_Context::last - * - * @param string $id Variable name - * - * @return string `find` method name - */ - private function getFindMethod($id) - { - if ($id === '.') { - return 'last'; - } - - if (isset($this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) { - if (substr($id, 0, 1) === '.') { - return 'findAnchoredDot'; - } - } - - if (strpos($id, '.') === false) { - return 'find'; - } - - return 'findDot'; - } - - const IS_CALLABLE = '!is_string(%s) && is_callable(%s)'; - const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)'; - - /** - * Helper function to compile strict vs lax "is callable" logic. - * - * @param string $variable (default: '$value') - * - * @return string "is callable" logic - */ - private function getCallable($variable = '$value') - { - $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE; - - return sprintf($tpl, $variable, $variable); - } - - const LINE_INDENT = '$indent . '; - - /** - * Get the current $indent prefix to write to the buffer. - * - * @return string "$indent . " or "" - */ - private function flushIndent() - { - if (!$this->indentNextLine) { - return ''; - } - - $this->indentNextLine = false; - - return self::LINE_INDENT; - } -} diff --git a/orcinus/Mustache/Context.php b/orcinus/Mustache/Context.php deleted file mode 100644 index 69c02e0..0000000 --- a/orcinus/Mustache/Context.php +++ /dev/null @@ -1,242 +0,0 @@ -stack = array($context); - } - } - - /** - * Push a new Context frame onto the stack. - * - * @param mixed $value Object or array to use for context - */ - public function push($value) - { - array_push($this->stack, $value); - } - - /** - * Push a new Context frame onto the block context stack. - * - * @param mixed $value Object or array to use for block context - */ - public function pushBlockContext($value) - { - array_push($this->blockStack, $value); - } - - /** - * Pop the last Context frame from the stack. - * - * @return mixed Last Context frame (object or array) - */ - public function pop() - { - return array_pop($this->stack); - } - - /** - * Pop the last block Context frame from the stack. - * - * @return mixed Last block Context frame (object or array) - */ - public function popBlockContext() - { - return array_pop($this->blockStack); - } - - /** - * Get the last Context frame. - * - * @return mixed Last Context frame (object or array) - */ - public function last() - { - return end($this->stack); - } - - /** - * Find a variable in the Context stack. - * - * Starting with the last Context frame (the context of the innermost section), and working back to the top-level - * rendering context, look for a variable with the given name: - * - * * If the Context frame is an associative array which contains the key $id, returns the value of that element. - * * If the Context frame is an object, this will check first for a public method, then a public property named - * $id. Failing both of these, it will try `__isset` and `__get` magic methods. - * * If a value named $id is not found in any Context frame, returns an empty string. - * - * @param string $id Variable name - * - * @return mixed Variable value, or '' if not found - */ - public function find($id) - { - return $this->findVariableInStack($id, $this->stack); - } - - /** - * Find a 'dot notation' variable in the Context stack. - * - * Note that dot notation traversal bubbles through scope differently than the regular find method. After finding - * the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous - * result. For example, given the following context stack: - * - * $data = array( - * 'name' => 'Fred', - * 'child' => array( - * 'name' => 'Bob' - * ), - * ); - * - * ... and the Mustache following template: - * - * {{ child.name }} - * - * ... the `name` value is only searched for within the `child` value of the global Context, not within parent - * Context frames. - * - * @param string $id Dotted variable selector - * - * @return mixed Variable value, or '' if not found - */ - public function findDot($id) - { - $chunks = explode('.', $id); - $first = array_shift($chunks); - $value = $this->findVariableInStack($first, $this->stack); - - foreach ($chunks as $chunk) { - if ($value === '') { - return $value; - } - - $value = $this->findVariableInStack($chunk, array($value)); - } - - return $value; - } - - /** - * Find an 'anchored dot notation' variable in the Context stack. - * - * This is the same as findDot(), except it looks in the top of the context - * stack for the first value, rather than searching the whole context stack - * and starting from there. - * - * @see Mustache_Context::findDot - * - * @throws Mustache_Exception_InvalidArgumentException if given an invalid anchored dot $id - * - * @param string $id Dotted variable selector - * - * @return mixed Variable value, or '' if not found - */ - public function findAnchoredDot($id) - { - $chunks = explode('.', $id); - $first = array_shift($chunks); - if ($first !== '') { - throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected id for findAnchoredDot: %s', $id)); - } - - $value = $this->last(); - - foreach ($chunks as $chunk) { - if ($value === '') { - return $value; - } - - $value = $this->findVariableInStack($chunk, array($value)); - } - - return $value; - } - - /** - * Find an argument in the block context stack. - * - * @param string $id - * - * @return mixed Variable value, or '' if not found - */ - public function findInBlock($id) - { - foreach ($this->blockStack as $context) { - if (array_key_exists($id, $context)) { - return $context[$id]; - } - } - - return ''; - } - - /** - * Helper function to find a variable in the Context stack. - * - * @see Mustache_Context::find - * - * @param string $id Variable name - * @param array $stack Context stack - * - * @return mixed Variable value, or '' if not found - */ - private function findVariableInStack($id, array $stack) - { - for ($i = count($stack) - 1; $i >= 0; $i--) { - $frame = &$stack[$i]; - - switch (gettype($frame)) { - case 'object': - if (!($frame instanceof Closure)) { - // Note that is_callable() *will not work here* - // See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods - if (method_exists($frame, $id)) { - return $frame->$id(); - } - - if (isset($frame->$id)) { - return $frame->$id; - } - - if ($frame instanceof ArrayAccess && isset($frame[$id])) { - return $frame[$id]; - } - } - break; - - case 'array': - if (array_key_exists($id, $frame)) { - return $frame[$id]; - } - break; - } - } - - return ''; - } -} diff --git a/orcinus/Mustache/Engine.php b/orcinus/Mustache/Engine.php deleted file mode 100644 index 7a31ac0..0000000 --- a/orcinus/Mustache/Engine.php +++ /dev/null @@ -1,831 +0,0 @@ - true, - self::PRAGMA_BLOCKS => true, - self::PRAGMA_ANCHORED_DOT => true, - self::PRAGMA_DYNAMIC_NAMES => true, - ); - - // Template cache - private $templates = array(); - - // Environment - private $templateClassPrefix = '__Mustache_'; - private $cache; - private $lambdaCache; - private $cacheLambdaTemplates = false; - private $loader; - private $partialsLoader; - private $helpers; - private $escape; - private $entityFlags = ENT_COMPAT; - private $charset = 'UTF-8'; - private $logger; - private $strictCallables = false; - private $pragmas = array(); - private $delimiters; - - // Services - private $tokenizer; - private $parser; - private $compiler; - - /** - * Mustache class constructor. - * - * Passing an $options array allows overriding certain Mustache options during instantiation: - * - * $options = array( - * // The class prefix for compiled templates. Defaults to '__Mustache_'. - * 'template_class_prefix' => '__MyTemplates_', - * - * // A Mustache cache instance or a cache directory string for compiled templates. - * // Mustache will not cache templates unless this is set. - * 'cache' => dirname(__FILE__).'/tmp/cache/mustache', - * - * // Override default permissions for cache files. Defaults to using the system-defined umask. It is - * // *strongly* recommended that you configure your umask properly rather than overriding permissions here. - * 'cache_file_mode' => 0666, - * - * // Optionally, enable caching for lambda section templates. This is generally not recommended, as lambda - * // sections are often too dynamic to benefit from caching. - * 'cache_lambda_templates' => true, - * - * // Customize the tag delimiters used by this engine instance. Note that overriding here changes the - * // delimiters used to parse all templates and partials loaded by this instance. To override just for a - * // single template, use an inline "change delimiters" tag at the start of the template file: - * // - * // {{=<% %>=}} - * // - * 'delimiters' => '<% %>', - * - * // A Mustache template loader instance. Uses a StringLoader if not specified. - * 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'), - * - * // A Mustache loader instance for partials. - * 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'), - * - * // An array of Mustache partials. Useful for quick-and-dirty string template loading, but not as - * // efficient or lazy as a Filesystem (or database) loader. - * 'partials' => array('foo' => file_get_contents(dirname(__FILE__).'/views/partials/foo.mustache')), - * - * // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order - * // sections), or any other valid Mustache context value. They will be prepended to the context stack, - * // so they will be available in any template loaded by this Mustache instance. - * 'helpers' => array('i18n' => function ($text) { - * // do something translatey here... - * }), - * - * // An 'escape' callback, responsible for escaping double-mustache variables. - * 'escape' => function ($value) { - * return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8'); - * }, - * - * // Type argument for `htmlspecialchars`. Defaults to ENT_COMPAT. You may prefer ENT_QUOTES. - * 'entity_flags' => ENT_QUOTES, - * - * // Character set for `htmlspecialchars`. Defaults to 'UTF-8'. Use 'UTF-8'. - * 'charset' => 'ISO-8859-1', - * - * // A Mustache Logger instance. No logging will occur unless this is set. Using a PSR-3 compatible - * // logging library -- such as Monolog -- is highly recommended. A simple stream logger implementation is - * // available as well: - * 'logger' => new Mustache_Logger_StreamLogger('php://stderr'), - * - * // Only treat Closure instances and invokable classes as callable. If true, values like - * // `array('ClassName', 'methodName')` and `array($classInstance, 'methodName')`, which are traditionally - * // "callable" in PHP, are not called to resolve variables for interpolation or section contexts. This - * // helps protect against arbitrary code execution when user input is passed directly into the template. - * // This currently defaults to false, but will default to true in v3.0. - * 'strict_callables' => true, - * - * // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual - * // templates. - * 'pragmas' => [Mustache_Engine::PRAGMA_FILTERS], - * ); - * - * @throws Mustache_Exception_InvalidArgumentException If `escape` option is not callable - * - * @param array $options (default: array()) - */ - public function __construct(array $options = array()) - { - if (isset($options['template_class_prefix'])) { - if ((string) $options['template_class_prefix'] === '') { - throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "template_class_prefix" must not be empty'); - } - - $this->templateClassPrefix = $options['template_class_prefix']; - } - - if (isset($options['cache'])) { - $cache = $options['cache']; - - if (is_string($cache)) { - $mode = isset($options['cache_file_mode']) ? $options['cache_file_mode'] : null; - $cache = new Mustache_Cache_FilesystemCache($cache, $mode); - } - - $this->setCache($cache); - } - - if (isset($options['cache_lambda_templates'])) { - $this->cacheLambdaTemplates = (bool) $options['cache_lambda_templates']; - } - - if (isset($options['loader'])) { - $this->setLoader($options['loader']); - } - - if (isset($options['partials_loader'])) { - $this->setPartialsLoader($options['partials_loader']); - } - - if (isset($options['partials'])) { - $this->setPartials($options['partials']); - } - - if (isset($options['helpers'])) { - $this->setHelpers($options['helpers']); - } - - if (isset($options['escape'])) { - if (!is_callable($options['escape'])) { - throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "escape" option must be callable'); - } - - $this->escape = $options['escape']; - } - - if (isset($options['entity_flags'])) { - $this->entityFlags = $options['entity_flags']; - } - - if (isset($options['charset'])) { - $this->charset = $options['charset']; - } - - if (isset($options['logger'])) { - $this->setLogger($options['logger']); - } - - if (isset($options['strict_callables'])) { - $this->strictCallables = $options['strict_callables']; - } - - if (isset($options['delimiters'])) { - $this->delimiters = $options['delimiters']; - } - - if (isset($options['pragmas'])) { - foreach ($options['pragmas'] as $pragma) { - if (!isset(self::$knownPragmas[$pragma])) { - throw new Mustache_Exception_InvalidArgumentException(sprintf('Unknown pragma: "%s".', $pragma)); - } - $this->pragmas[$pragma] = true; - } - } - } - - /** - * Shortcut 'render' invocation. - * - * Equivalent to calling `$mustache->loadTemplate($template)->render($context);` - * - * @see Mustache_Engine::loadTemplate - * @see Mustache_Template::render - * - * @param string $template - * @param mixed $context (default: array()) - * - * @return string Rendered template - */ - public function render($template, $context = array()) - { - return $this->loadTemplate($template)->render($context); - } - - /** - * Get the current Mustache escape callback. - * - * @return callable|null - */ - public function getEscape() - { - return $this->escape; - } - - /** - * Get the current Mustache entitity type to escape. - * - * @return int - */ - public function getEntityFlags() - { - return $this->entityFlags; - } - - /** - * Get the current Mustache character set. - * - * @return string - */ - public function getCharset() - { - return $this->charset; - } - - /** - * Get the current globally enabled pragmas. - * - * @return array - */ - public function getPragmas() - { - return array_keys($this->pragmas); - } - - /** - * Set the Mustache template Loader instance. - * - * @param Mustache_Loader $loader - */ - public function setLoader(Mustache_Loader $loader) - { - $this->loader = $loader; - } - - /** - * Get the current Mustache template Loader instance. - * - * If no Loader instance has been explicitly specified, this method will instantiate and return - * a StringLoader instance. - * - * @return Mustache_Loader - */ - public function getLoader() - { - if (!isset($this->loader)) { - $this->loader = new Mustache_Loader_StringLoader(); - } - - return $this->loader; - } - - /** - * Set the Mustache partials Loader instance. - * - * @param Mustache_Loader $partialsLoader - */ - public function setPartialsLoader(Mustache_Loader $partialsLoader) - { - $this->partialsLoader = $partialsLoader; - } - - /** - * Get the current Mustache partials Loader instance. - * - * If no Loader instance has been explicitly specified, this method will instantiate and return - * an ArrayLoader instance. - * - * @return Mustache_Loader - */ - public function getPartialsLoader() - { - if (!isset($this->partialsLoader)) { - $this->partialsLoader = new Mustache_Loader_ArrayLoader(); - } - - return $this->partialsLoader; - } - - /** - * Set partials for the current partials Loader instance. - * - * @throws Mustache_Exception_RuntimeException If the current Loader instance is immutable - * - * @param array $partials (default: array()) - */ - public function setPartials(array $partials = array()) - { - if (!isset($this->partialsLoader)) { - $this->partialsLoader = new Mustache_Loader_ArrayLoader(); - } - - if (!$this->partialsLoader instanceof Mustache_Loader_MutableLoader) { - throw new Mustache_Exception_RuntimeException('Unable to set partials on an immutable Mustache Loader instance'); - } - - $this->partialsLoader->setTemplates($partials); - } - - /** - * Set an array of Mustache helpers. - * - * An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order sections), or - * any other valid Mustache context value. They will be prepended to the context stack, so they will be available in - * any template loaded by this Mustache instance. - * - * @throws Mustache_Exception_InvalidArgumentException if $helpers is not an array or Traversable - * - * @param array|Traversable $helpers - */ - public function setHelpers($helpers) - { - if (!is_array($helpers) && !$helpers instanceof Traversable) { - throw new Mustache_Exception_InvalidArgumentException('setHelpers expects an array of helpers'); - } - - $this->getHelpers()->clear(); - - foreach ($helpers as $name => $helper) { - $this->addHelper($name, $helper); - } - } - - /** - * Get the current set of Mustache helpers. - * - * @see Mustache_Engine::setHelpers - * - * @return Mustache_HelperCollection - */ - public function getHelpers() - { - if (!isset($this->helpers)) { - $this->helpers = new Mustache_HelperCollection(); - } - - return $this->helpers; - } - - /** - * Add a new Mustache helper. - * - * @see Mustache_Engine::setHelpers - * - * @param string $name - * @param mixed $helper - */ - public function addHelper($name, $helper) - { - $this->getHelpers()->add($name, $helper); - } - - /** - * Get a Mustache helper by name. - * - * @see Mustache_Engine::setHelpers - * - * @param string $name - * - * @return mixed Helper - */ - public function getHelper($name) - { - return $this->getHelpers()->get($name); - } - - /** - * Check whether this Mustache instance has a helper. - * - * @see Mustache_Engine::setHelpers - * - * @param string $name - * - * @return bool True if the helper is present - */ - public function hasHelper($name) - { - return $this->getHelpers()->has($name); - } - - /** - * Remove a helper by name. - * - * @see Mustache_Engine::setHelpers - * - * @param string $name - */ - public function removeHelper($name) - { - $this->getHelpers()->remove($name); - } - - /** - * Set the Mustache Logger instance. - * - * @throws Mustache_Exception_InvalidArgumentException If logger is not an instance of Mustache_Logger or Psr\Log\LoggerInterface - * - * @param Mustache_Logger|Psr\Log\LoggerInterface $logger - */ - public function setLogger($logger = null) - { - if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) { - throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.'); - } - - if ($this->getCache()->getLogger() === null) { - $this->getCache()->setLogger($logger); - } - - $this->logger = $logger; - } - - /** - * Get the current Mustache Logger instance. - * - * @return Mustache_Logger|Psr\Log\LoggerInterface - */ - public function getLogger() - { - return $this->logger; - } - - /** - * Set the Mustache Tokenizer instance. - * - * @param Mustache_Tokenizer $tokenizer - */ - public function setTokenizer(Mustache_Tokenizer $tokenizer) - { - $this->tokenizer = $tokenizer; - } - - /** - * Get the current Mustache Tokenizer instance. - * - * If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one. - * - * @return Mustache_Tokenizer - */ - public function getTokenizer() - { - if (!isset($this->tokenizer)) { - $this->tokenizer = new Mustache_Tokenizer(); - } - - return $this->tokenizer; - } - - /** - * Set the Mustache Parser instance. - * - * @param Mustache_Parser $parser - */ - public function setParser(Mustache_Parser $parser) - { - $this->parser = $parser; - } - - /** - * Get the current Mustache Parser instance. - * - * If no Parser instance has been explicitly specified, this method will instantiate and return a new one. - * - * @return Mustache_Parser - */ - public function getParser() - { - if (!isset($this->parser)) { - $this->parser = new Mustache_Parser(); - } - - return $this->parser; - } - - /** - * Set the Mustache Compiler instance. - * - * @param Mustache_Compiler $compiler - */ - public function setCompiler(Mustache_Compiler $compiler) - { - $this->compiler = $compiler; - } - - /** - * Get the current Mustache Compiler instance. - * - * If no Compiler instance has been explicitly specified, this method will instantiate and return a new one. - * - * @return Mustache_Compiler - */ - public function getCompiler() - { - if (!isset($this->compiler)) { - $this->compiler = new Mustache_Compiler(); - } - - return $this->compiler; - } - - /** - * Set the Mustache Cache instance. - * - * @param Mustache_Cache $cache - */ - public function setCache(Mustache_Cache $cache) - { - if (isset($this->logger) && $cache->getLogger() === null) { - $cache->setLogger($this->getLogger()); - } - - $this->cache = $cache; - } - - /** - * Get the current Mustache Cache instance. - * - * If no Cache instance has been explicitly specified, this method will instantiate and return a new one. - * - * @return Mustache_Cache - */ - public function getCache() - { - if (!isset($this->cache)) { - $this->setCache(new Mustache_Cache_NoopCache()); - } - - return $this->cache; - } - - /** - * Get the current Lambda Cache instance. - * - * If 'cache_lambda_templates' is enabled, this is the default cache instance. Otherwise, it is a NoopCache. - * - * @see Mustache_Engine::getCache - * - * @return Mustache_Cache - */ - protected function getLambdaCache() - { - if ($this->cacheLambdaTemplates) { - return $this->getCache(); - } - - if (!isset($this->lambdaCache)) { - $this->lambdaCache = new Mustache_Cache_NoopCache(); - } - - return $this->lambdaCache; - } - - /** - * Helper method to generate a Mustache template class. - * - * This method must be updated any time options are added which make it so - * the same template could be parsed and compiled multiple different ways. - * - * @param string|Mustache_Source $source - * - * @return string Mustache Template class name - */ - public function getTemplateClassName($source) - { - // For the most part, adding a new option here should do the trick. - // - // Pick a value here which is unique for each possible way the template - // could be compiled... but not necessarily unique per option value. See - // escape below, which only needs to differentiate between 'custom' and - // 'default' escapes. - // - // Keep this list in alphabetical order :) - $chunks = array( - 'charset' => $this->charset, - 'delimiters' => $this->delimiters ? $this->delimiters : '{{ }}', - 'entityFlags' => $this->entityFlags, - 'escape' => isset($this->escape) ? 'custom' : 'default', - 'key' => ($source instanceof Mustache_Source) ? $source->getKey() : 'source', - 'pragmas' => $this->getPragmas(), - 'strictCallables' => $this->strictCallables, - 'version' => self::VERSION, - ); - - $key = json_encode($chunks); - - // Template Source instances have already provided their own source key. For strings, just include the whole - // source string in the md5 hash. - if (!$source instanceof Mustache_Source) { - $key .= "\n" . $source; - } - - return $this->templateClassPrefix . md5($key); - } - - /** - * Load a Mustache Template by name. - * - * @param string $name - * - * @return Mustache_Template - */ - public function loadTemplate($name) - { - return $this->loadSource($this->getLoader()->load($name)); - } - - /** - * Load a Mustache partial Template by name. - * - * This is a helper method used internally by Template instances for loading partial templates. You can most likely - * ignore it completely. - * - * @param string $name - * - * @return Mustache_Template - */ - public function loadPartial($name) - { - try { - if (isset($this->partialsLoader)) { - $loader = $this->partialsLoader; - } elseif (isset($this->loader) && !$this->loader instanceof Mustache_Loader_StringLoader) { - $loader = $this->loader; - } else { - throw new Mustache_Exception_UnknownTemplateException($name); - } - - return $this->loadSource($loader->load($name)); - } catch (Mustache_Exception_UnknownTemplateException $e) { - // If the named partial cannot be found, log then return null. - $this->log( - Mustache_Logger::WARNING, - 'Partial not found: "{name}"', - array('name' => $e->getTemplateName()) - ); - } - } - - /** - * Load a Mustache lambda Template by source. - * - * This is a helper method used by Template instances to generate subtemplates for Lambda sections. You can most - * likely ignore it completely. - * - * @param string $source - * @param string $delims (default: null) - * - * @return Mustache_Template - */ - public function loadLambda($source, $delims = null) - { - if ($delims !== null) { - $source = $delims . "\n" . $source; - } - - return $this->loadSource($source, $this->getLambdaCache()); - } - - /** - * Instantiate and return a Mustache Template instance by source. - * - * Optionally provide a Mustache_Cache instance. This is used internally by Mustache_Engine::loadLambda to respect - * the 'cache_lambda_templates' configuration option. - * - * @see Mustache_Engine::loadTemplate - * @see Mustache_Engine::loadPartial - * @see Mustache_Engine::loadLambda - * - * @param string|Mustache_Source $source - * @param Mustache_Cache $cache (default: null) - * - * @return Mustache_Template - */ - private function loadSource($source, Mustache_Cache $cache = null) - { - $className = $this->getTemplateClassName($source); - - if (!isset($this->templates[$className])) { - if ($cache === null) { - $cache = $this->getCache(); - } - - if (!class_exists($className, false)) { - if (!$cache->load($className)) { - $compiled = $this->compile($source); - $cache->cache($className, $compiled); - } - } - - $this->log( - Mustache_Logger::DEBUG, - 'Instantiating template: "{className}"', - array('className' => $className) - ); - - $this->templates[$className] = new $className($this); - } - - return $this->templates[$className]; - } - - /** - * Helper method to tokenize a Mustache template. - * - * @see Mustache_Tokenizer::scan - * - * @param string $source - * - * @return array Tokens - */ - private function tokenize($source) - { - return $this->getTokenizer()->scan($source, $this->delimiters); - } - - /** - * Helper method to parse a Mustache template. - * - * @see Mustache_Parser::parse - * - * @param string $source - * - * @return array Token tree - */ - private function parse($source) - { - $parser = $this->getParser(); - $parser->setPragmas($this->getPragmas()); - - return $parser->parse($this->tokenize($source)); - } - - /** - * Helper method to compile a Mustache template. - * - * @see Mustache_Compiler::compile - * - * @param string|Mustache_Source $source - * - * @return string generated Mustache template class code - */ - private function compile($source) - { - $name = $this->getTemplateClassName($source); - - $this->log( - Mustache_Logger::INFO, - 'Compiling template to "{className}" class', - array('className' => $name) - ); - - if ($source instanceof Mustache_Source) { - $source = $source->getSource(); - } - $tree = $this->parse($source); - - $compiler = $this->getCompiler(); - $compiler->setPragmas($this->getPragmas()); - - return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags); - } - - /** - * Add a log record if logging is enabled. - * - * @param int $level The logging level - * @param string $message The log message - * @param array $context The log context - */ - private function log($level, $message, array $context = array()) - { - if (isset($this->logger)) { - $this->logger->log($level, $message, $context); - } - } -} diff --git a/orcinus/Mustache/Exception.php b/orcinus/Mustache/Exception.php deleted file mode 100644 index d4001a9..0000000 --- a/orcinus/Mustache/Exception.php +++ /dev/null @@ -1,18 +0,0 @@ -token = $token; - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - parent::__construct($msg, 0, $previous); - } else { - parent::__construct($msg); // @codeCoverageIgnore - } - } - - /** - * @return array - */ - public function getToken() - { - return $this->token; - } -} diff --git a/orcinus/Mustache/Exception/UnknownFilterException.php b/orcinus/Mustache/Exception/UnknownFilterException.php deleted file mode 100644 index 0651c17..0000000 --- a/orcinus/Mustache/Exception/UnknownFilterException.php +++ /dev/null @@ -1,38 +0,0 @@ -filterName = $filterName; - $message = sprintf('Unknown filter: %s', $filterName); - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - parent::__construct($message, 0, $previous); - } else { - parent::__construct($message); // @codeCoverageIgnore - } - } - - public function getFilterName() - { - return $this->filterName; - } -} diff --git a/orcinus/Mustache/Exception/UnknownHelperException.php b/orcinus/Mustache/Exception/UnknownHelperException.php deleted file mode 100644 index 193be78..0000000 --- a/orcinus/Mustache/Exception/UnknownHelperException.php +++ /dev/null @@ -1,38 +0,0 @@ -helperName = $helperName; - $message = sprintf('Unknown helper: %s', $helperName); - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - parent::__construct($message, 0, $previous); - } else { - parent::__construct($message); // @codeCoverageIgnore - } - } - - public function getHelperName() - { - return $this->helperName; - } -} diff --git a/orcinus/Mustache/Exception/UnknownTemplateException.php b/orcinus/Mustache/Exception/UnknownTemplateException.php deleted file mode 100644 index 32a778a..0000000 --- a/orcinus/Mustache/Exception/UnknownTemplateException.php +++ /dev/null @@ -1,38 +0,0 @@ -templateName = $templateName; - $message = sprintf('Unknown template: %s', $templateName); - if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - parent::__construct($message, 0, $previous); - } else { - parent::__construct($message); // @codeCoverageIgnore - } - } - - public function getTemplateName() - { - return $this->templateName; - } -} diff --git a/orcinus/Mustache/HelperCollection.php b/orcinus/Mustache/HelperCollection.php deleted file mode 100644 index 5d8f73c..0000000 --- a/orcinus/Mustache/HelperCollection.php +++ /dev/null @@ -1,172 +0,0 @@ - $helper` pairs. - * - * @throws Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable - * - * @param array|Traversable $helpers (default: null) - */ - public function __construct($helpers = null) - { - if ($helpers === null) { - return; - } - - if (!is_array($helpers) && !$helpers instanceof Traversable) { - throw new Mustache_Exception_InvalidArgumentException('HelperCollection constructor expects an array of helpers'); - } - - foreach ($helpers as $name => $helper) { - $this->add($name, $helper); - } - } - - /** - * Magic mutator. - * - * @see Mustache_HelperCollection::add - * - * @param string $name - * @param mixed $helper - */ - public function __set($name, $helper) - { - $this->add($name, $helper); - } - - /** - * Add a helper to this collection. - * - * @param string $name - * @param mixed $helper - */ - public function add($name, $helper) - { - $this->helpers[$name] = $helper; - } - - /** - * Magic accessor. - * - * @see Mustache_HelperCollection::get - * - * @param string $name - * - * @return mixed Helper - */ - public function __get($name) - { - return $this->get($name); - } - - /** - * Get a helper by name. - * - * @throws Mustache_Exception_UnknownHelperException If helper does not exist - * - * @param string $name - * - * @return mixed Helper - */ - public function get($name) - { - if (!$this->has($name)) { - throw new Mustache_Exception_UnknownHelperException($name); - } - - return $this->helpers[$name]; - } - - /** - * Magic isset(). - * - * @see Mustache_HelperCollection::has - * - * @param string $name - * - * @return bool True if helper is present - */ - public function __isset($name) - { - return $this->has($name); - } - - /** - * Check whether a given helper is present in the collection. - * - * @param string $name - * - * @return bool True if helper is present - */ - public function has($name) - { - return array_key_exists($name, $this->helpers); - } - - /** - * Magic unset(). - * - * @see Mustache_HelperCollection::remove - * - * @param string $name - */ - public function __unset($name) - { - $this->remove($name); - } - - /** - * Check whether a given helper is present in the collection. - * - * @throws Mustache_Exception_UnknownHelperException if the requested helper is not present - * - * @param string $name - */ - public function remove($name) - { - if (!$this->has($name)) { - throw new Mustache_Exception_UnknownHelperException($name); - } - - unset($this->helpers[$name]); - } - - /** - * Clear the helper collection. - * - * Removes all helpers from this collection - */ - public function clear() - { - $this->helpers = array(); - } - - /** - * Check whether the helper collection is empty. - * - * @return bool True if the collection is empty - */ - public function isEmpty() - { - return empty($this->helpers); - } -} diff --git a/orcinus/Mustache/LICENSE b/orcinus/Mustache/LICENSE deleted file mode 100644 index e0aecc9..0000000 --- a/orcinus/Mustache/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2010-2015 Justin Hileman - -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. diff --git a/orcinus/Mustache/LambdaHelper.php b/orcinus/Mustache/LambdaHelper.php deleted file mode 100644 index e93dbfa..0000000 --- a/orcinus/Mustache/LambdaHelper.php +++ /dev/null @@ -1,76 +0,0 @@ - =}}`. (default: null) - */ - public function __construct(Mustache_Engine $mustache, Mustache_Context $context, $delims = null) - { - $this->mustache = $mustache; - $this->context = $context; - $this->delims = $delims; - } - - /** - * Render a string as a Mustache template with the current rendering context. - * - * @param string $string - * - * @return string Rendered template - */ - public function render($string) - { - return $this->mustache - ->loadLambda((string) $string, $this->delims) - ->renderInternal($this->context); - } - - /** - * Render a string as a Mustache template with the current rendering context. - * - * @param string $string - * - * @return string Rendered template - */ - public function __invoke($string) - { - return $this->render($string); - } - - /** - * Get a Lambda Helper with custom delimiters. - * - * @param string $delims Custom delimiters, in the format `{{= <% %> =}}` - * - * @return Mustache_LambdaHelper - */ - public function withDelimiters($delims) - { - return new self($this->mustache, $this->context, $delims); - } -} diff --git a/orcinus/Mustache/Loader.php b/orcinus/Mustache/Loader.php deleted file mode 100644 index 23adba1..0000000 --- a/orcinus/Mustache/Loader.php +++ /dev/null @@ -1,27 +0,0 @@ - '{{ bar }}', - * 'baz' => 'Hey {{ qux }}!' - * ); - * - * $tpl = $loader->load('foo'); // '{{ bar }}' - * - * The ArrayLoader is used internally as a partials loader by Mustache_Engine instance when an array of partials - * is set. It can also be used as a quick-and-dirty Template loader. - */ -class Mustache_Loader_ArrayLoader implements Mustache_Loader, Mustache_Loader_MutableLoader -{ - private $templates; - - /** - * ArrayLoader constructor. - * - * @param array $templates Associative array of Template source (default: array()) - */ - public function __construct(array $templates = array()) - { - $this->templates = $templates; - } - - /** - * Load a Template. - * - * @throws Mustache_Exception_UnknownTemplateException If a template file is not found - * - * @param string $name - * - * @return string Mustache Template source - */ - public function load($name) - { - if (!isset($this->templates[$name])) { - throw new Mustache_Exception_UnknownTemplateException($name); - } - - return $this->templates[$name]; - } - - /** - * Set an associative array of Template sources for this loader. - * - * @param array $templates - */ - public function setTemplates(array $templates) - { - $this->templates = $templates; - } - - /** - * Set a Template source by name. - * - * @param string $name - * @param string $template Mustache Template source - */ - public function setTemplate($name, $template) - { - $this->templates[$name] = $template; - } -} diff --git a/orcinus/Mustache/Loader/CascadingLoader.php b/orcinus/Mustache/Loader/CascadingLoader.php deleted file mode 100644 index 3fb6353..0000000 --- a/orcinus/Mustache/Loader/CascadingLoader.php +++ /dev/null @@ -1,69 +0,0 @@ -loaders = array(); - foreach ($loaders as $loader) { - $this->addLoader($loader); - } - } - - /** - * Add a Loader instance. - * - * @param Mustache_Loader $loader - */ - public function addLoader(Mustache_Loader $loader) - { - $this->loaders[] = $loader; - } - - /** - * Load a Template by name. - * - * @throws Mustache_Exception_UnknownTemplateException If a template file is not found - * - * @param string $name - * - * @return string Mustache Template source - */ - public function load($name) - { - foreach ($this->loaders as $loader) { - try { - return $loader->load($name); - } catch (Mustache_Exception_UnknownTemplateException $e) { - // do nothing, check the next loader. - } - } - - throw new Mustache_Exception_UnknownTemplateException($name); - } -} diff --git a/orcinus/Mustache/Loader/FilesystemLoader.php b/orcinus/Mustache/Loader/FilesystemLoader.php deleted file mode 100644 index e366df7..0000000 --- a/orcinus/Mustache/Loader/FilesystemLoader.php +++ /dev/null @@ -1,135 +0,0 @@ -load('foo'); // equivalent to `file_get_contents(dirname(__FILE__).'/views/foo.mustache'); - * - * This is probably the most useful Mustache Loader implementation. It can be used for partials and normal Templates: - * - * $m = new Mustache(array( - * 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'), - * 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'), - * )); - */ -class Mustache_Loader_FilesystemLoader implements Mustache_Loader -{ - private $baseDir; - private $extension = '.mustache'; - private $templates = array(); - - /** - * Mustache filesystem Loader constructor. - * - * Passing an $options array allows overriding certain Loader options during instantiation: - * - * $options = array( - * // The filename extension used for Mustache templates. Defaults to '.mustache' - * 'extension' => '.ms', - * ); - * - * @throws Mustache_Exception_RuntimeException if $baseDir does not exist - * - * @param string $baseDir Base directory containing Mustache template files - * @param array $options Array of Loader options (default: array()) - */ - public function __construct($baseDir, array $options = array()) - { - $this->baseDir = $baseDir; - - if (strpos($this->baseDir, '://') === false) { - $this->baseDir = realpath($this->baseDir); - } - - if ($this->shouldCheckPath() && !is_dir($this->baseDir)) { - throw new Mustache_Exception_RuntimeException(sprintf('FilesystemLoader baseDir must be a directory: %s', $baseDir)); - } - - if (array_key_exists('extension', $options)) { - if (empty($options['extension'])) { - $this->extension = ''; - } else { - $this->extension = '.' . ltrim($options['extension'], '.'); - } - } - } - - /** - * Load a Template by name. - * - * $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'); - * $loader->load('admin/dashboard'); // loads "./views/admin/dashboard.mustache"; - * - * @param string $name - * - * @return string Mustache Template source - */ - public function load($name) - { - if (!isset($this->templates[$name])) { - $this->templates[$name] = $this->loadFile($name); - } - - return $this->templates[$name]; - } - - /** - * Helper function for loading a Mustache file by name. - * - * @throws Mustache_Exception_UnknownTemplateException If a template file is not found - * - * @param string $name - * - * @return string Mustache Template source - */ - protected function loadFile($name) - { - $fileName = $this->getFileName($name); - - if ($this->shouldCheckPath() && !file_exists($fileName)) { - throw new Mustache_Exception_UnknownTemplateException($name); - } - - return file_get_contents($fileName); - } - - /** - * Helper function for getting a Mustache template file name. - * - * @param string $name - * - * @return string Template file name - */ - protected function getFileName($name) - { - $fileName = $this->baseDir . '/' . $name; - if (substr($fileName, 0 - strlen($this->extension)) !== $this->extension) { - $fileName .= $this->extension; - } - - return $fileName; - } - - /** - * Only check if baseDir is a directory and requested templates are files if - * baseDir is using the filesystem stream wrapper. - * - * @return bool Whether to check `is_dir` and `file_exists` - */ - protected function shouldCheckPath() - { - return strpos($this->baseDir, '://') === false || strpos($this->baseDir, 'file://') === 0; - } -} diff --git a/orcinus/Mustache/Loader/InlineLoader.php b/orcinus/Mustache/Loader/InlineLoader.php deleted file mode 100644 index ae297fe..0000000 --- a/orcinus/Mustache/Loader/InlineLoader.php +++ /dev/null @@ -1,123 +0,0 @@ -load('hello'); - * $goodbye = $loader->load('goodbye'); - * - * __halt_compiler(); - * - * @@ hello - * Hello, {{ planet }}! - * - * @@ goodbye - * Goodbye, cruel {{ planet }} - * - * Templates are deliniated by lines containing only `@@ name`. - * - * The InlineLoader is well-suited to micro-frameworks such as Silex: - * - * $app->register(new MustacheServiceProvider, array( - * 'mustache.loader' => new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__) - * )); - * - * $app->get('/{name}', function ($name) use ($app) { - * return $app['mustache']->render('hello', compact('name')); - * }) - * ->value('name', 'world'); - * - * // ... - * - * __halt_compiler(); - * - * @@ hello - * Hello, {{ name }}! - */ -class Mustache_Loader_InlineLoader implements Mustache_Loader -{ - protected $fileName; - protected $offset; - protected $templates; - - /** - * The InlineLoader requires a filename and offset to process templates. - * - * The magic constants `__FILE__` and `__COMPILER_HALT_OFFSET__` are usually - * perfectly suited to the job: - * - * $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__); - * - * Note that this only works if the loader is instantiated inside the same - * file as the inline templates. If the templates are located in another - * file, it would be necessary to manually specify the filename and offset. - * - * @param string $fileName The file to parse for inline templates - * @param int $offset A string offset for the start of the templates. - * This usually coincides with the `__halt_compiler` - * call, and the `__COMPILER_HALT_OFFSET__` - */ - public function __construct($fileName, $offset) - { - if (!is_file($fileName)) { - throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid filename.'); - } - - if (!is_int($offset) || $offset < 0) { - throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid file offset.'); - } - - $this->fileName = $fileName; - $this->offset = $offset; - } - - /** - * Load a Template by name. - * - * @throws Mustache_Exception_UnknownTemplateException If a template file is not found - * - * @param string $name - * - * @return string Mustache Template source - */ - public function load($name) - { - $this->loadTemplates(); - - if (!array_key_exists($name, $this->templates)) { - throw new Mustache_Exception_UnknownTemplateException($name); - } - - return $this->templates[$name]; - } - - /** - * Parse and load templates from the end of a source file. - */ - protected function loadTemplates() - { - if ($this->templates === null) { - $this->templates = array(); - $data = file_get_contents($this->fileName, false, null, $this->offset); - foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) { - if (trim($chunk)) { - list($name, $content) = explode("\n", $chunk, 2); - $this->templates[trim($name)] = trim($content); - } - } - } - } -} diff --git a/orcinus/Mustache/Loader/MutableLoader.php b/orcinus/Mustache/Loader/MutableLoader.php deleted file mode 100644 index 57fe5be..0000000 --- a/orcinus/Mustache/Loader/MutableLoader.php +++ /dev/null @@ -1,31 +0,0 @@ - '.ms', - * 'stat_props' => array('size', 'mtime'), - * ); - * - * Specifying 'stat_props' overrides the stat properties used to invalidate the template cache. By default, this - * uses 'mtime' and 'size', but this can be set to any of the properties supported by stat(): - * - * http://php.net/manual/en/function.stat.php - * - * You can also disable filesystem stat entirely: - * - * $options = array('stat_props' => null); - * - * But with great power comes great responsibility. Namely, if you disable stat-based cache invalidation, - * YOU MUST CLEAR THE TEMPLATE CACHE YOURSELF when your templates change. Make it part of your build or deploy - * process so you don't forget! - * - * @throws Mustache_Exception_RuntimeException if $baseDir does not exist. - * - * @param string $baseDir Base directory containing Mustache template files. - * @param array $options Array of Loader options (default: array()) - */ - public function __construct($baseDir, array $options = array()) - { - parent::__construct($baseDir, $options); - - if (array_key_exists('stat_props', $options)) { - if (empty($options['stat_props'])) { - $this->statProps = array(); - } else { - $this->statProps = $options['stat_props']; - } - } else { - $this->statProps = array('size', 'mtime'); - } - } - - /** - * Helper function for loading a Mustache file by name. - * - * @throws Mustache_Exception_UnknownTemplateException If a template file is not found. - * - * @param string $name - * - * @return Mustache_Source Mustache Template source - */ - protected function loadFile($name) - { - $fileName = $this->getFileName($name); - - if (!file_exists($fileName)) { - throw new Mustache_Exception_UnknownTemplateException($name); - } - - return new Mustache_Source_FilesystemSource($fileName, $this->statProps); - } -} diff --git a/orcinus/Mustache/Loader/StringLoader.php b/orcinus/Mustache/Loader/StringLoader.php deleted file mode 100644 index 7012c03..0000000 --- a/orcinus/Mustache/Loader/StringLoader.php +++ /dev/null @@ -1,39 +0,0 @@ -load('{{ foo }}'); // '{{ foo }}' - * - * This is the default Template Loader instance used by Mustache: - * - * $m = new Mustache; - * $tpl = $m->loadTemplate('{{ foo }}'); - * echo $tpl->render(array('foo' => 'bar')); // "bar" - */ -class Mustache_Loader_StringLoader implements Mustache_Loader -{ - /** - * Load a Template by source. - * - * @param string $name Mustache Template source - * - * @return string Mustache Template source - */ - public function load($name) - { - return $name; - } -} diff --git a/orcinus/Mustache/Logger.php b/orcinus/Mustache/Logger.php deleted file mode 100644 index cb4037a..0000000 --- a/orcinus/Mustache/Logger.php +++ /dev/null @@ -1,126 +0,0 @@ -log(Mustache_Logger::EMERGENCY, $message, $context); - } - - /** - * Action must be taken immediately. - * - * Example: Entire website down, database unavailable, etc. This should - * trigger the SMS alerts and wake you up. - * - * @param string $message - * @param array $context - */ - public function alert($message, array $context = array()) - { - $this->log(Mustache_Logger::ALERT, $message, $context); - } - - /** - * Critical conditions. - * - * Example: Application component unavailable, unexpected exception. - * - * @param string $message - * @param array $context - */ - public function critical($message, array $context = array()) - { - $this->log(Mustache_Logger::CRITICAL, $message, $context); - } - - /** - * Runtime errors that do not require immediate action but should typically - * be logged and monitored. - * - * @param string $message - * @param array $context - */ - public function error($message, array $context = array()) - { - $this->log(Mustache_Logger::ERROR, $message, $context); - } - - /** - * Exceptional occurrences that are not errors. - * - * Example: Use of deprecated APIs, poor use of an API, undesirable things - * that are not necessarily wrong. - * - * @param string $message - * @param array $context - */ - public function warning($message, array $context = array()) - { - $this->log(Mustache_Logger::WARNING, $message, $context); - } - - /** - * Normal but significant events. - * - * @param string $message - * @param array $context - */ - public function notice($message, array $context = array()) - { - $this->log(Mustache_Logger::NOTICE, $message, $context); - } - - /** - * Interesting events. - * - * Example: User logs in, SQL logs. - * - * @param string $message - * @param array $context - */ - public function info($message, array $context = array()) - { - $this->log(Mustache_Logger::INFO, $message, $context); - } - - /** - * Detailed debug information. - * - * @param string $message - * @param array $context - */ - public function debug($message, array $context = array()) - { - $this->log(Mustache_Logger::DEBUG, $message, $context); - } -} diff --git a/orcinus/Mustache/Logger/StreamLogger.php b/orcinus/Mustache/Logger/StreamLogger.php deleted file mode 100644 index 402a148..0000000 --- a/orcinus/Mustache/Logger/StreamLogger.php +++ /dev/null @@ -1,194 +0,0 @@ - 100, - self::INFO => 200, - self::NOTICE => 250, - self::WARNING => 300, - self::ERROR => 400, - self::CRITICAL => 500, - self::ALERT => 550, - self::EMERGENCY => 600, - ); - - protected $level; - protected $stream = null; - protected $url = null; - - /** - * @throws InvalidArgumentException if the logging level is unknown - * - * @param resource|string $stream Resource instance or URL - * @param int $level The minimum logging level at which this handler will be triggered - */ - public function __construct($stream, $level = Mustache_Logger::ERROR) - { - $this->setLevel($level); - - if (is_resource($stream)) { - $this->stream = $stream; - } else { - $this->url = $stream; - } - } - - /** - * Close stream resources. - */ - public function __destruct() - { - if (is_resource($this->stream)) { - fclose($this->stream); - } - } - - /** - * Set the minimum logging level. - * - * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown - * - * @param int $level The minimum logging level which will be written - */ - public function setLevel($level) - { - if (!array_key_exists($level, self::$levels)) { - throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level)); - } - - $this->level = $level; - } - - /** - * Get the current minimum logging level. - * - * @return int - */ - public function getLevel() - { - return $this->level; - } - - /** - * Logs with an arbitrary level. - * - * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown - * - * @param mixed $level - * @param string $message - * @param array $context - */ - public function log($level, $message, array $context = array()) - { - if (!array_key_exists($level, self::$levels)) { - throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level)); - } - - if (self::$levels[$level] >= self::$levels[$this->level]) { - $this->writeLog($level, $message, $context); - } - } - - /** - * Write a record to the log. - * - * @throws Mustache_Exception_LogicException If neither a stream resource nor url is present - * @throws Mustache_Exception_RuntimeException If the stream url cannot be opened - * - * @param int $level The logging level - * @param string $message The log message - * @param array $context The log context - */ - protected function writeLog($level, $message, array $context = array()) - { - if (!is_resource($this->stream)) { - if (!isset($this->url)) { - throw new Mustache_Exception_LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); - } - - $this->stream = fopen($this->url, 'a'); - if (!is_resource($this->stream)) { - // @codeCoverageIgnoreStart - throw new Mustache_Exception_RuntimeException(sprintf('The stream or file "%s" could not be opened.', $this->url)); - // @codeCoverageIgnoreEnd - } - } - - fwrite($this->stream, self::formatLine($level, $message, $context)); - } - - /** - * Gets the name of the logging level. - * - * @throws InvalidArgumentException if the logging level is unknown - * - * @param int $level - * - * @return string - */ - protected static function getLevelName($level) - { - return strtoupper($level); - } - - /** - * Format a log line for output. - * - * @param int $level The logging level - * @param string $message The log message - * @param array $context The log context - * - * @return string - */ - protected static function formatLine($level, $message, array $context = array()) - { - return sprintf( - "%s: %s\n", - self::getLevelName($level), - self::interpolateMessage($message, $context) - ); - } - - /** - * Interpolate context values into the message placeholders. - * - * @param string $message - * @param array $context - * - * @return string - */ - protected static function interpolateMessage($message, array $context = array()) - { - if (strpos($message, '{') === false) { - return $message; - } - - // build a replacement array with braces around the context keys - $replace = array(); - foreach ($context as $key => $val) { - $replace['{' . $key . '}'] = $val; - } - - // interpolate replacement values into the the message and return - return strtr($message, $replace); - } -} diff --git a/orcinus/Mustache/Parser.php b/orcinus/Mustache/Parser.php deleted file mode 100644 index 02e3d89..0000000 --- a/orcinus/Mustache/Parser.php +++ /dev/null @@ -1,383 +0,0 @@ -lineNum = -1; - $this->lineTokens = 0; - $this->pragmas = $this->defaultPragmas; - - $this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]); - $this->pragmaBlocks = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]); - $this->pragmaDynamicNames = isset($this->pragmas[Mustache_Engine::PRAGMA_DYNAMIC_NAMES]); - - return $this->buildTree($tokens); - } - - /** - * Enable pragmas across all templates, regardless of the presence of pragma - * tags in the individual templates. - * - * @internal Users should set global pragmas in Mustache_Engine, not here :) - * - * @param string[] $pragmas - */ - public function setPragmas(array $pragmas) - { - $this->pragmas = array(); - foreach ($pragmas as $pragma) { - $this->enablePragma($pragma); - } - $this->defaultPragmas = $this->pragmas; - } - - /** - * Helper method for recursively building a parse tree. - * - * @throws Mustache_Exception_SyntaxException when nesting errors or mismatched section tags are encountered - * - * @param array &$tokens Set of Mustache tokens - * @param array $parent Parent token (default: null) - * - * @return array Mustache Token parse tree - */ - private function buildTree(array &$tokens, array $parent = null) - { - $nodes = array(); - - while (!empty($tokens)) { - $token = array_shift($tokens); - - if ($token[Mustache_Tokenizer::LINE] === $this->lineNum) { - $this->lineTokens++; - } else { - $this->lineNum = $token[Mustache_Tokenizer::LINE]; - $this->lineTokens = 0; - } - - if ($token[Mustache_Tokenizer::TYPE] !== Mustache_Tokenizer::T_COMMENT) { - if ($this->pragmaDynamicNames && isset($token[Mustache_Tokenizer::NAME])) { - list($name, $isDynamic) = $this->getDynamicName($token); - if ($isDynamic) { - $token[Mustache_Tokenizer::NAME] = $name; - $token[Mustache_Tokenizer::DYNAMIC] = true; - } - } - - if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) { - list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]); - if (!empty($filters)) { - $token[Mustache_Tokenizer::NAME] = $name; - $token[Mustache_Tokenizer::FILTERS] = $filters; - } - } - } - - switch ($token[Mustache_Tokenizer::TYPE]) { - case Mustache_Tokenizer::T_DELIM_CHANGE: - $this->checkIfTokenIsAllowedInParent($parent, $token); - $this->clearStandaloneLines($nodes, $tokens); - break; - - case Mustache_Tokenizer::T_SECTION: - case Mustache_Tokenizer::T_INVERTED: - $this->checkIfTokenIsAllowedInParent($parent, $token); - $this->clearStandaloneLines($nodes, $tokens); - $nodes[] = $this->buildTree($tokens, $token); - break; - - case Mustache_Tokenizer::T_END_SECTION: - if (!isset($parent)) { - $msg = sprintf( - 'Unexpected closing tag: /%s on line %d', - $token[Mustache_Tokenizer::NAME], - $token[Mustache_Tokenizer::LINE] - ); - throw new Mustache_Exception_SyntaxException($msg, $token); - } - - $sameName = $token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]; - $tokenDynamic = isset($token[Mustache_Tokenizer::DYNAMIC]) && $token[Mustache_Tokenizer::DYNAMIC]; - $parentDynamic = isset($parent[Mustache_Tokenizer::DYNAMIC]) && $parent[Mustache_Tokenizer::DYNAMIC]; - - if ($sameName || ($tokenDynamic !== $parentDynamic)) { - $msg = sprintf( - 'Nesting error: %s (on line %d) vs. %s (on line %d)', - $parent[Mustache_Tokenizer::NAME], - $parent[Mustache_Tokenizer::LINE], - $token[Mustache_Tokenizer::NAME], - $token[Mustache_Tokenizer::LINE] - ); - throw new Mustache_Exception_SyntaxException($msg, $token); - } - - $this->clearStandaloneLines($nodes, $tokens); - $parent[Mustache_Tokenizer::END] = $token[Mustache_Tokenizer::INDEX]; - $parent[Mustache_Tokenizer::NODES] = $nodes; - - return $parent; - - case Mustache_Tokenizer::T_PARTIAL: - $this->checkIfTokenIsAllowedInParent($parent, $token); - //store the whitespace prefix for laters! - if ($indent = $this->clearStandaloneLines($nodes, $tokens)) { - $token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE]; - } - $nodes[] = $token; - break; - - case Mustache_Tokenizer::T_PARENT: - $this->checkIfTokenIsAllowedInParent($parent, $token); - $nodes[] = $this->buildTree($tokens, $token); - break; - - case Mustache_Tokenizer::T_BLOCK_VAR: - if ($this->pragmaBlocks) { - // BLOCKS pragma is enabled, let's do this! - if (isset($parent) && $parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) { - $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_BLOCK_ARG; - } - $this->clearStandaloneLines($nodes, $tokens); - $nodes[] = $this->buildTree($tokens, $token); - } else { - // pretend this was just a normal "escaped" token... - $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_ESCAPED; - // TODO: figure out how to figure out if there was a space after this dollar: - $token[Mustache_Tokenizer::NAME] = '$' . $token[Mustache_Tokenizer::NAME]; - $nodes[] = $token; - } - break; - - case Mustache_Tokenizer::T_PRAGMA: - $this->enablePragma($token[Mustache_Tokenizer::NAME]); - // no break - - case Mustache_Tokenizer::T_COMMENT: - $this->clearStandaloneLines($nodes, $tokens); - $nodes[] = $token; - break; - - default: - $nodes[] = $token; - break; - } - } - - if (isset($parent)) { - $msg = sprintf( - 'Missing closing tag: %s opened on line %d', - $parent[Mustache_Tokenizer::NAME], - $parent[Mustache_Tokenizer::LINE] - ); - throw new Mustache_Exception_SyntaxException($msg, $parent); - } - - return $nodes; - } - - /** - * Clear standalone line tokens. - * - * Returns a whitespace token for indenting partials, if applicable. - * - * @param array $nodes Parsed nodes - * @param array $tokens Tokens to be parsed - * - * @return array|null Resulting indent token, if any - */ - private function clearStandaloneLines(array &$nodes, array &$tokens) - { - if ($this->lineTokens > 1) { - // this is the third or later node on this line, so it can't be standalone - return; - } - - $prev = null; - if ($this->lineTokens === 1) { - // this is the second node on this line, so it can't be standalone - // unless the previous node is whitespace. - if ($prev = end($nodes)) { - if (!$this->tokenIsWhitespace($prev)) { - return; - } - } - } - - if ($next = reset($tokens)) { - // If we're on a new line, bail. - if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) { - return; - } - - // If the next token isn't whitespace, bail. - if (!$this->tokenIsWhitespace($next)) { - return; - } - - if (count($tokens) !== 1) { - // Unless it's the last token in the template, the next token - // must end in newline for this to be standalone. - if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") { - return; - } - } - - // Discard the whitespace suffix - array_shift($tokens); - } - - if ($prev) { - // Return the whitespace prefix, if any - return array_pop($nodes); - } - } - - /** - * Check whether token is a whitespace token. - * - * True if token type is T_TEXT and value is all whitespace characters. - * - * @param array $token - * - * @return bool True if token is a whitespace token - */ - private function tokenIsWhitespace(array $token) - { - if ($token[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_TEXT) { - return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]); - } - - return false; - } - - /** - * Check whether a token is allowed inside a parent tag. - * - * @throws Mustache_Exception_SyntaxException if an invalid token is found inside a parent tag - * - * @param array|null $parent - * @param array $token - */ - private function checkIfTokenIsAllowedInParent($parent, array $token) - { - if (isset($parent) && $parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) { - throw new Mustache_Exception_SyntaxException('Illegal content in < parent tag', $token); - } - } - - /** - * Parse dynamic names. - * - * @throws Mustache_Exception_SyntaxException when a tag does not allow * - * @throws Mustache_Exception_SyntaxException on multiple *s, or dots or filters with * - */ - private function getDynamicName(array $token) - { - $name = $token[Mustache_Tokenizer::NAME]; - $isDynamic = false; - - if (preg_match('/^\s*\*\s*/', $name)) { - $this->ensureTagAllowsDynamicNames($token); - $name = preg_replace('/^\s*\*\s*/', '', $name); - $isDynamic = true; - } - - return array($name, $isDynamic); - } - - /** - * Check whether the given token supports dynamic tag names. - * - * @throws Mustache_Exception_SyntaxException when a tag does not allow * - * - * @param array $token - */ - private function ensureTagAllowsDynamicNames(array $token) - { - switch ($token[Mustache_Tokenizer::TYPE]) { - case Mustache_Tokenizer::T_PARTIAL: - case Mustache_Tokenizer::T_PARENT: - case Mustache_Tokenizer::T_END_SECTION: - return; - } - - $msg = sprintf( - 'Invalid dynamic name: %s in %s tag', - $token[Mustache_Tokenizer::NAME], - Mustache_Tokenizer::getTagName($token[Mustache_Tokenizer::TYPE]) - ); - - throw new Mustache_Exception_SyntaxException($msg, $token); - } - - - /** - * Split a tag name into name and filters. - * - * @param string $name - * - * @return array [Tag name, Array of filters] - */ - private function getNameAndFilters($name) - { - $filters = array_map('trim', explode('|', $name)); - $name = array_shift($filters); - - return array($name, $filters); - } - - /** - * Enable a pragma. - * - * @param string $name - */ - private function enablePragma($name) - { - $this->pragmas[$name] = true; - - switch ($name) { - case Mustache_Engine::PRAGMA_BLOCKS: - $this->pragmaBlocks = true; - break; - - case Mustache_Engine::PRAGMA_FILTERS: - $this->pragmaFilters = true; - break; - - case Mustache_Engine::PRAGMA_DYNAMIC_NAMES: - $this->pragmaDynamicNames = true; - break; - } - } -} diff --git a/orcinus/Mustache/Source.php b/orcinus/Mustache/Source.php deleted file mode 100644 index 278c2cb..0000000 --- a/orcinus/Mustache/Source.php +++ /dev/null @@ -1,40 +0,0 @@ -fileName = $fileName; - $this->statProps = $statProps; - } - - /** - * Get the Source key (used to generate the compiled class name). - * - * @throws Mustache_Exception_RuntimeException when a source file cannot be read - * - * @return string - */ - public function getKey() - { - $chunks = array( - 'fileName' => $this->fileName, - ); - - if (!empty($this->statProps)) { - if (!isset($this->stat)) { - $this->stat = @stat($this->fileName); - } - - if ($this->stat === false) { - throw new Mustache_Exception_RuntimeException(sprintf('Failed to read source file "%s".', $this->fileName)); - } - - foreach ($this->statProps as $prop) { - $chunks[$prop] = $this->stat[$prop]; - } - } - - return json_encode($chunks); - } - - /** - * Get the template Source. - * - * @return string - */ - public function getSource() - { - return file_get_contents($this->fileName); - } -} diff --git a/orcinus/Mustache/Template.php b/orcinus/Mustache/Template.php deleted file mode 100644 index 4de8239..0000000 --- a/orcinus/Mustache/Template.php +++ /dev/null @@ -1,180 +0,0 @@ -mustache = $mustache; - } - - /** - * Mustache Template instances can be treated as a function and rendered by simply calling them. - * - * $m = new Mustache_Engine; - * $tpl = $m->loadTemplate('Hello, {{ name }}!'); - * echo $tpl(array('name' => 'World')); // "Hello, World!" - * - * @see Mustache_Template::render - * - * @param mixed $context Array or object rendering context (default: array()) - * - * @return string Rendered template - */ - public function __invoke($context = array()) - { - return $this->render($context); - } - - /** - * Render this template given the rendering context. - * - * @param mixed $context Array or object rendering context (default: array()) - * - * @return string Rendered template - */ - public function render($context = array()) - { - return $this->renderInternal( - $this->prepareContextStack($context) - ); - } - - /** - * Internal rendering method implemented by Mustache Template concrete subclasses. - * - * This is where the magic happens :) - * - * NOTE: This method is not part of the Mustache.php public API. - * - * @param Mustache_Context $context - * @param string $indent (default: '') - * - * @return string Rendered template - */ - abstract public function renderInternal(Mustache_Context $context, $indent = ''); - - /** - * Tests whether a value should be iterated over (e.g. in a section context). - * - * In most languages there are two distinct array types: list and hash (or whatever you want to call them). Lists - * should be iterated, hashes should be treated as objects. Mustache follows this paradigm for Ruby, Javascript, - * Java, Python, etc. - * - * PHP, however, treats lists and hashes as one primitive type: array. So Mustache.php needs a way to distinguish - * between between a list of things (numeric, normalized array) and a set of variables to be used as section context - * (associative array). In other words, this will be iterated over: - * - * $items = array( - * array('name' => 'foo'), - * array('name' => 'bar'), - * array('name' => 'baz'), - * ); - * - * ... but this will be used as a section context block: - * - * $items = array( - * 1 => array('name' => 'foo'), - * 'banana' => array('name' => 'bar'), - * 42 => array('name' => 'baz'), - * ); - * - * @param mixed $value - * - * @return bool True if the value is 'iterable' - */ - protected function isIterable($value) - { - switch (gettype($value)) { - case 'object': - return $value instanceof Traversable; - - case 'array': - $i = 0; - foreach ($value as $k => $v) { - if ($k !== $i++) { - return false; - } - } - - return true; - - default: - return false; - } - } - - /** - * Helper method to prepare the Context stack. - * - * Adds the Mustache HelperCollection to the stack's top context frame if helpers are present. - * - * @param mixed $context Optional first context frame (default: null) - * - * @return Mustache_Context - */ - protected function prepareContextStack($context = null) - { - $stack = new Mustache_Context(); - - $helpers = $this->mustache->getHelpers(); - if (!$helpers->isEmpty()) { - $stack->push($helpers); - } - - if (!empty($context)) { - $stack->push($context); - } - - return $stack; - } - - /** - * Resolve a context value. - * - * Invoke the value if it is callable, otherwise return the value. - * - * @param mixed $value - * @param Mustache_Context $context - * - * @return string - */ - protected function resolveValue($value, Mustache_Context $context) - { - if (($this->strictCallables ? is_object($value) : !is_string($value)) && is_callable($value)) { - return $this->mustache - ->loadLambda((string) call_user_func($value)) - ->renderInternal($context); - } - - return $value; - } -} diff --git a/orcinus/Mustache/Tokenizer.php b/orcinus/Mustache/Tokenizer.php deleted file mode 100644 index d96f129..0000000 --- a/orcinus/Mustache/Tokenizer.php +++ /dev/null @@ -1,408 +0,0 @@ -'; - const T_PARENT = '<'; - const T_DELIM_CHANGE = '='; - const T_ESCAPED = '_v'; - const T_UNESCAPED = '{'; - const T_UNESCAPED_2 = '&'; - const T_TEXT = '_t'; - const T_PRAGMA = '%'; - const T_BLOCK_VAR = '$'; - const T_BLOCK_ARG = '$arg'; - - // Valid token types - private static $tagTypes = array( - self::T_SECTION => true, - self::T_INVERTED => true, - self::T_END_SECTION => true, - self::T_COMMENT => true, - self::T_PARTIAL => true, - self::T_PARENT => true, - self::T_DELIM_CHANGE => true, - self::T_ESCAPED => true, - self::T_UNESCAPED => true, - self::T_UNESCAPED_2 => true, - self::T_PRAGMA => true, - self::T_BLOCK_VAR => true, - ); - - private static $tagNames = array( - self::T_SECTION => 'section', - self::T_INVERTED => 'inverted section', - self::T_END_SECTION => 'section end', - self::T_COMMENT => 'comment', - self::T_PARTIAL => 'partial', - self::T_PARENT => 'parent', - self::T_DELIM_CHANGE => 'set delimiter', - self::T_ESCAPED => 'variable', - self::T_UNESCAPED => 'unescaped variable', - self::T_UNESCAPED_2 => 'unescaped variable', - self::T_PRAGMA => 'pragma', - self::T_BLOCK_VAR => 'block variable', - self::T_BLOCK_ARG => 'block variable', - ); - - // Token properties - const TYPE = 'type'; - const NAME = 'name'; - const DYNAMIC = 'dynamic'; - const OTAG = 'otag'; - const CTAG = 'ctag'; - const LINE = 'line'; - const INDEX = 'index'; - const END = 'end'; - const INDENT = 'indent'; - const NODES = 'nodes'; - const VALUE = 'value'; - const FILTERS = 'filters'; - - private $state; - private $tagType; - private $buffer; - private $tokens; - private $seenTag; - private $line; - - private $otag; - private $otagChar; - private $otagLen; - - private $ctag; - private $ctagChar; - private $ctagLen; - - /** - * Scan and tokenize template source. - * - * @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered - * @throws Mustache_Exception_InvalidArgumentException when $delimiters string is invalid - * - * @param string $text Mustache template source to tokenize - * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: empty string) - * - * @return array Set of Mustache tokens - */ - public function scan($text, $delimiters = '') - { - // Setting mbstring.func_overload makes things *really* slow. - // Let's do everyone a favor and scan this string as ASCII instead. - // - // The INI directive was removed in PHP 8.0 so we don't need to check there (and can drop it - // when we remove support for older versions of PHP). - // - // @codeCoverageIgnoreStart - $encoding = null; - if (version_compare(PHP_VERSION, '8.0.0', '<')) { - if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) { - $encoding = mb_internal_encoding(); - mb_internal_encoding('ASCII'); - } - } - // @codeCoverageIgnoreEnd - - $this->reset(); - - if (is_string($delimiters) && $delimiters = trim($delimiters)) { - $this->setDelimiters($delimiters); - } - - $len = strlen($text); - for ($i = 0; $i < $len; $i++) { - switch ($this->state) { - case self::IN_TEXT: - $char = $text[$i]; - // Test whether it's time to change tags. - if ($char === $this->otagChar && substr($text, $i, $this->otagLen) === $this->otag) { - $i--; - $this->flushBuffer(); - $this->state = self::IN_TAG_TYPE; - } else { - $this->buffer .= $char; - if ($char === "\n") { - $this->flushBuffer(); - $this->line++; - } - } - break; - - case self::IN_TAG_TYPE: - $i += $this->otagLen - 1; - $char = $text[$i + 1]; - if (isset(self::$tagTypes[$char])) { - $tag = $char; - $this->tagType = $tag; - } else { - $tag = null; - $this->tagType = self::T_ESCAPED; - } - - if ($this->tagType === self::T_DELIM_CHANGE) { - $i = $this->changeDelimiters($text, $i); - $this->state = self::IN_TEXT; - } elseif ($this->tagType === self::T_PRAGMA) { - $i = $this->addPragma($text, $i); - $this->state = self::IN_TEXT; - } else { - if ($tag !== null) { - $i++; - } - $this->state = self::IN_TAG; - } - $this->seenTag = $i; - break; - - default: - $char = $text[$i]; - // Test whether it's time to change tags. - if ($char === $this->ctagChar && substr($text, $i, $this->ctagLen) === $this->ctag) { - $token = array( - self::TYPE => $this->tagType, - self::NAME => trim($this->buffer), - self::OTAG => $this->otag, - self::CTAG => $this->ctag, - self::LINE => $this->line, - self::INDEX => ($this->tagType === self::T_END_SECTION) ? $this->seenTag - $this->otagLen : $i + $this->ctagLen, - ); - - if ($this->tagType === self::T_UNESCAPED) { - // Clean up `{{{ tripleStache }}}` style tokens. - if ($this->ctag === '}}') { - if (($i + 2 < $len) && $text[$i + 2] === '}') { - $i++; - } else { - $msg = sprintf( - 'Mismatched tag delimiters: %s on line %d', - $token[self::NAME], - $token[self::LINE] - ); - - throw new Mustache_Exception_SyntaxException($msg, $token); - } - } else { - $lastName = $token[self::NAME]; - if (substr($lastName, -1) === '}') { - $token[self::NAME] = trim(substr($lastName, 0, -1)); - } else { - $msg = sprintf( - 'Mismatched tag delimiters: %s on line %d', - $token[self::NAME], - $token[self::LINE] - ); - - throw new Mustache_Exception_SyntaxException($msg, $token); - } - } - } - - $this->buffer = ''; - $i += $this->ctagLen - 1; - $this->state = self::IN_TEXT; - $this->tokens[] = $token; - } else { - $this->buffer .= $char; - } - break; - } - } - - if ($this->state !== self::IN_TEXT) { - $this->throwUnclosedTagException(); - } - - $this->flushBuffer(); - - // Restore the user's encoding... - // @codeCoverageIgnoreStart - if ($encoding) { - mb_internal_encoding($encoding); - } - // @codeCoverageIgnoreEnd - - return $this->tokens; - } - - /** - * Helper function to reset tokenizer internal state. - */ - private function reset() - { - $this->state = self::IN_TEXT; - $this->tagType = null; - $this->buffer = ''; - $this->tokens = array(); - $this->seenTag = false; - $this->line = 0; - - $this->otag = '{{'; - $this->otagChar = '{'; - $this->otagLen = 2; - - $this->ctag = '}}'; - $this->ctagChar = '}'; - $this->ctagLen = 2; - } - - /** - * Flush the current buffer to a token. - */ - private function flushBuffer() - { - if (strlen($this->buffer) > 0) { - $this->tokens[] = array( - self::TYPE => self::T_TEXT, - self::LINE => $this->line, - self::VALUE => $this->buffer, - ); - $this->buffer = ''; - } - } - - /** - * Change the current Mustache delimiters. Set new `otag` and `ctag` values. - * - * @throws Mustache_Exception_SyntaxException when delimiter string is invalid - * - * @param string $text Mustache template source - * @param int $index Current tokenizer index - * - * @return int New index value - */ - private function changeDelimiters($text, $index) - { - $startIndex = strpos($text, '=', $index) + 1; - $close = '=' . $this->ctag; - $closeIndex = strpos($text, $close, $index); - - if ($closeIndex === false) { - $this->throwUnclosedTagException(); - } - - $token = array( - self::TYPE => self::T_DELIM_CHANGE, - self::LINE => $this->line, - ); - - try { - $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex))); - } catch (Mustache_Exception_InvalidArgumentException $e) { - throw new Mustache_Exception_SyntaxException($e->getMessage(), $token); - } - - $this->tokens[] = $token; - - return $closeIndex + strlen($close) - 1; - } - - /** - * Set the current Mustache `otag` and `ctag` delimiters. - * - * @throws Mustache_Exception_InvalidArgumentException when delimiter string is invalid - * - * @param string $delimiters - */ - private function setDelimiters($delimiters) - { - if (!preg_match('/^\s*(\S+)\s+(\S+)\s*$/', $delimiters, $matches)) { - throw new Mustache_Exception_InvalidArgumentException(sprintf('Invalid delimiters: %s', $delimiters)); - } - - list($_, $otag, $ctag) = $matches; - - $this->otag = $otag; - $this->otagChar = $otag[0]; - $this->otagLen = strlen($otag); - - $this->ctag = $ctag; - $this->ctagChar = $ctag[0]; - $this->ctagLen = strlen($ctag); - } - - /** - * Add pragma token. - * - * Pragmas are hoisted to the front of the template, so all pragma tokens - * will appear at the front of the token list. - * - * @param string $text - * @param int $index - * - * @return int New index value - */ - private function addPragma($text, $index) - { - $end = strpos($text, $this->ctag, $index); - if ($end === false) { - $this->throwUnclosedTagException(); - } - - $pragma = trim(substr($text, $index + 2, $end - $index - 2)); - - // Pragmas are hoisted to the front of the template. - array_unshift($this->tokens, array( - self::TYPE => self::T_PRAGMA, - self::NAME => $pragma, - self::LINE => 0, - )); - - return $end + $this->ctagLen - 1; - } - - - private function throwUnclosedTagException() - { - $name = trim($this->buffer); - if ($name !== '') { - $msg = sprintf('Unclosed tag: %s on line %d', $name, $this->line); - } else { - $msg = sprintf('Unclosed tag on line %d', $this->line); - } - - throw new Mustache_Exception_SyntaxException($msg, array( - self::TYPE => $this->tagType, - self::NAME => $name, - self::OTAG => $this->otag, - self::CTAG => $this->ctag, - self::LINE => $this->line, - self::INDEX => $this->seenTag - $this->otagLen, - )); - } - - /** - * Get the human readable name for a tag type. - * - * @param string $tagType One of the tokenizer T_* constants - * - * @return string - */ - static function getTagName($tagType) - { - return isset(self::$tagNames[$tagType]) ? self::$tagNames[$tagType] : 'unknown'; - } -} diff --git a/orcinus/PHPMailer/Exception.php b/orcinus/PHPMailer/Exception.php deleted file mode 100644 index 52eaf95..0000000 --- a/orcinus/PHPMailer/Exception.php +++ /dev/null @@ -1,40 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2020 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -namespace PHPMailer\PHPMailer; - -/** - * PHPMailer exception handler. - * - * @author Marcus Bointon - */ -class Exception extends \Exception -{ - /** - * Prettify error message output. - * - * @return string - */ - public function errorMessage() - { - return '' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "
\n"; - } -} diff --git a/orcinus/PHPMailer/LICENSE b/orcinus/PHPMailer/LICENSE deleted file mode 100644 index f166cc5..0000000 --- a/orcinus/PHPMailer/LICENSE +++ /dev/null @@ -1,502 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! \ No newline at end of file diff --git a/orcinus/PHPMailer/PHPMailer.php b/orcinus/PHPMailer/PHPMailer.php deleted file mode 100644 index a644d2c..0000000 --- a/orcinus/PHPMailer/PHPMailer.php +++ /dev/null @@ -1,5126 +0,0 @@ - - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2020 Marcus Bointon - * @copyright 2010 - 2012 Jim Jagielski - * @copyright 2004 - 2009 Andy Prevost - * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License - * @note This program is distributed in the hope that it will be useful - WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. - */ - -namespace PHPMailer\PHPMailer; - -/** - * PHPMailer - PHP email creation and transport class. - * - * @author Marcus Bointon (Synchro/coolbru) - * @author Jim Jagielski (jimjag) - * @author Andy Prevost (codeworxtech) - * @author Brent R. Matzelle (original founder) - */ -class PHPMailer -{ - const CHARSET_ASCII = 'us-ascii'; - const CHARSET_ISO88591 = 'iso-8859-1'; - const CHARSET_UTF8 = 'utf-8'; - - const CONTENT_TYPE_PLAINTEXT = 'text/plain'; - const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar'; - const CONTENT_TYPE_TEXT_HTML = 'text/html'; - const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative'; - const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed'; - const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related'; - - const ENCODING_7BIT = '7bit'; - const ENCODING_8BIT = '8bit'; - const ENCODING_BASE64 = 'base64'; - const ENCODING_BINARY = 'binary'; - const ENCODING_QUOTED_PRINTABLE = 'quoted-printable'; - - const ENCRYPTION_STARTTLS = 'tls'; - const ENCRYPTION_SMTPS = 'ssl'; - - const ICAL_METHOD_REQUEST = 'REQUEST'; - const ICAL_METHOD_PUBLISH = 'PUBLISH'; - const ICAL_METHOD_REPLY = 'REPLY'; - const ICAL_METHOD_ADD = 'ADD'; - const ICAL_METHOD_CANCEL = 'CANCEL'; - const ICAL_METHOD_REFRESH = 'REFRESH'; - const ICAL_METHOD_COUNTER = 'COUNTER'; - const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER'; - - /** - * Email priority. - * Options: null (default), 1 = High, 3 = Normal, 5 = low. - * When null, the header is not set at all. - * - * @var int|null - */ - public $Priority; - - /** - * The character set of the message. - * - * @var string - */ - public $CharSet = self::CHARSET_ISO88591; - - /** - * The MIME Content-type of the message. - * - * @var string - */ - public $ContentType = self::CONTENT_TYPE_PLAINTEXT; - - /** - * The message encoding. - * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable". - * - * @var string - */ - public $Encoding = self::ENCODING_8BIT; - - /** - * Holds the most recent mailer error message. - * - * @var string - */ - public $ErrorInfo = ''; - - /** - * The From email address for the message. - * - * @var string - */ - public $From = ''; - - /** - * The From name of the message. - * - * @var string - */ - public $FromName = ''; - - /** - * The envelope sender of the message. - * This will usually be turned into a Return-Path header by the receiver, - * and is the address that bounces will be sent to. - * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP. - * - * @var string - */ - public $Sender = ''; - - /** - * The Subject of the message. - * - * @var string - */ - public $Subject = ''; - - /** - * An HTML or plain text message body. - * If HTML then call isHTML(true). - * - * @var string - */ - public $Body = ''; - - /** - * The plain-text message body. - * This body can be read by mail clients that do not have HTML email - * capability such as mutt & Eudora. - * Clients that can read HTML will view the normal Body. - * - * @var string - */ - public $AltBody = ''; - - /** - * An iCal message part body. - * Only supported in simple alt or alt_inline message types - * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator. - * - * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/ - * @see http://kigkonsult.se/iCalcreator/ - * - * @var string - */ - public $Ical = ''; - - /** - * Value-array of "method" in Contenttype header "text/calendar" - * - * @var string[] - */ - protected static $IcalMethods = [ - self::ICAL_METHOD_REQUEST, - self::ICAL_METHOD_PUBLISH, - self::ICAL_METHOD_REPLY, - self::ICAL_METHOD_ADD, - self::ICAL_METHOD_CANCEL, - self::ICAL_METHOD_REFRESH, - self::ICAL_METHOD_COUNTER, - self::ICAL_METHOD_DECLINECOUNTER, - ]; - - /** - * The complete compiled MIME message body. - * - * @var string - */ - protected $MIMEBody = ''; - - /** - * The complete compiled MIME message headers. - * - * @var string - */ - protected $MIMEHeader = ''; - - /** - * Extra headers that createHeader() doesn't fold in. - * - * @var string - */ - protected $mailHeader = ''; - - /** - * Word-wrap the message body to this number of chars. - * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance. - * - * @see static::STD_LINE_LENGTH - * - * @var int - */ - public $WordWrap = 0; - - /** - * Which method to use to send mail. - * Options: "mail", "sendmail", or "smtp". - * - * @var string - */ - public $Mailer = 'mail'; - - /** - * The path to the sendmail program. - * - * @var string - */ - public $Sendmail = '/usr/sbin/sendmail'; - - /** - * Whether mail() uses a fully sendmail-compatible MTA. - * One which supports sendmail's "-oi -f" options. - * - * @var bool - */ - public $UseSendmailOptions = true; - - /** - * The email address that a reading confirmation should be sent to, also known as read receipt. - * - * @var string - */ - public $ConfirmReadingTo = ''; - - /** - * The hostname to use in the Message-ID header and as default HELO string. - * If empty, PHPMailer attempts to find one with, in order, - * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value - * 'localhost.localdomain'. - * - * @see PHPMailer::$Helo - * - * @var string - */ - public $Hostname = ''; - - /** - * An ID to be used in the Message-ID header. - * If empty, a unique id will be generated. - * You can set your own, but it must be in the format "", - * as defined in RFC5322 section 3.6.4 or it will be ignored. - * - * @see https://tools.ietf.org/html/rfc5322#section-3.6.4 - * - * @var string - */ - public $MessageID = ''; - - /** - * The message Date to be used in the Date header. - * If empty, the current date will be added. - * - * @var string - */ - public $MessageDate = ''; - - /** - * SMTP hosts. - * Either a single hostname or multiple semicolon-delimited hostnames. - * You can also specify a different port - * for each host by using this format: [hostname:port] - * (e.g. "smtp1.example.com:25;smtp2.example.com"). - * You can also specify encryption type, for example: - * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). - * Hosts will be tried in order. - * - * @var string - */ - public $Host = 'localhost'; - - /** - * The default SMTP server port. - * - * @var int - */ - public $Port = 25; - - /** - * The SMTP HELO/EHLO name used for the SMTP connection. - * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find - * one with the same method described above for $Hostname. - * - * @see PHPMailer::$Hostname - * - * @var string - */ - public $Helo = ''; - - /** - * What kind of encryption to use on the SMTP connection. - * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS. - * - * @var string - */ - public $SMTPSecure = ''; - - /** - * Whether to enable TLS encryption automatically if a server supports it, - * even if `SMTPSecure` is not set to 'tls'. - * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid. - * - * @var bool - */ - public $SMTPAutoTLS = true; - - /** - * Whether to use SMTP authentication. - * Uses the Username and Password properties. - * - * @see PHPMailer::$Username - * @see PHPMailer::$Password - * - * @var bool - */ - public $SMTPAuth = false; - - /** - * Options array passed to stream_context_create when connecting via SMTP. - * - * @var array - */ - public $SMTPOptions = []; - - /** - * SMTP username. - * - * @var string - */ - public $Username = ''; - - /** - * SMTP password. - * - * @var string - */ - public $Password = ''; - - /** - * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. - * If not specified, the first one from that list that the server supports will be selected. - * - * @var string - */ - public $AuthType = ''; - - /** - * An implementation of the PHPMailer OAuthTokenProvider interface. - * - * @var OAuthTokenProvider - */ - protected $oauth; - - /** - * The SMTP server timeout in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2. - * - * @var int - */ - public $Timeout = 300; - - /** - * Comma separated list of DSN notifications - * 'NEVER' under no circumstances a DSN must be returned to the sender. - * If you use NEVER all other notifications will be ignored. - * 'SUCCESS' will notify you when your mail has arrived at its destination. - * 'FAILURE' will arrive if an error occurred during delivery. - * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual - * delivery's outcome (success or failure) is not yet decided. - * - * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY - */ - public $dsn = ''; - - /** - * SMTP class debug output mode. - * Debug output level. - * Options: - * @see SMTP::DEBUG_OFF: No output - * @see SMTP::DEBUG_CLIENT: Client messages - * @see SMTP::DEBUG_SERVER: Client and server messages - * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status - * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed - * - * @see SMTP::$do_debug - * - * @var int - */ - public $SMTPDebug = 0; - - /** - * How to handle debug output. - * Options: - * * `echo` Output plain-text as-is, appropriate for CLI - * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output - * * `error_log` Output to error log as configured in php.ini - * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise. - * Alternatively, you can provide a callable expecting two params: a message string and the debug level: - * - * ```php - * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; - * ``` - * - * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug` - * level output is used: - * - * ```php - * $mail->Debugoutput = new myPsr3Logger; - * ``` - * - * @see SMTP::$Debugoutput - * - * @var string|callable|\Psr\Log\LoggerInterface - */ - public $Debugoutput = 'echo'; - - /** - * Whether to keep the SMTP connection open after each message. - * If this is set to true then the connection will remain open after a send, - * and closing the connection will require an explicit call to smtpClose(). - * It's a good idea to use this if you are sending multiple messages as it reduces overhead. - * See the mailing list example for how to use it. - * - * @var bool - */ - public $SMTPKeepAlive = false; - - /** - * Whether to split multiple to addresses into multiple messages - * or send them all in one message. - * Only supported in `mail` and `sendmail` transports, not in SMTP. - * - * @var bool - * - * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! - */ - public $SingleTo = false; - - /** - * Storage for addresses when SingleTo is enabled. - * - * @var array - */ - protected $SingleToArray = []; - - /** - * Whether to generate VERP addresses on send. - * Only applicable when sending via SMTP. - * - * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path - * @see http://www.postfix.org/VERP_README.html Postfix VERP info - * - * @var bool - */ - public $do_verp = false; - - /** - * Whether to allow sending messages with an empty body. - * - * @var bool - */ - public $AllowEmpty = false; - - /** - * DKIM selector. - * - * @var string - */ - public $DKIM_selector = ''; - - /** - * DKIM Identity. - * Usually the email address used as the source of the email. - * - * @var string - */ - public $DKIM_identity = ''; - - /** - * DKIM passphrase. - * Used if your key is encrypted. - * - * @var string - */ - public $DKIM_passphrase = ''; - - /** - * DKIM signing domain name. - * - * @example 'example.com' - * - * @var string - */ - public $DKIM_domain = ''; - - /** - * DKIM Copy header field values for diagnostic use. - * - * @var bool - */ - public $DKIM_copyHeaderFields = true; - - /** - * DKIM Extra signing headers. - * - * @example ['List-Unsubscribe', 'List-Help'] - * - * @var array - */ - public $DKIM_extraHeaders = []; - - /** - * DKIM private key file path. - * - * @var string - */ - public $DKIM_private = ''; - - /** - * DKIM private key string. - * - * If set, takes precedence over `$DKIM_private`. - * - * @var string - */ - public $DKIM_private_string = ''; - - /** - * Callback Action function name. - * - * The function that handles the result of the send email action. - * It is called out by send() for each email sent. - * - * Value can be any php callable: http://www.php.net/is_callable - * - * Parameters: - * bool $result result of the send action - * array $to email addresses of the recipients - * array $cc cc email addresses - * array $bcc bcc email addresses - * string $subject the subject - * string $body the email body - * string $from email address of sender - * string $extra extra information of possible use - * "smtp_transaction_id' => last smtp transaction id - * - * @var string - */ - public $action_function = ''; - - /** - * What to put in the X-Mailer header. - * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use. - * - * @var string|null - */ - public $XMailer = ''; - - /** - * Which validator to use by default when validating email addresses. - * May be a callable to inject your own validator, but there are several built-in validators. - * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option. - * - * @see PHPMailer::validateAddress() - * - * @var string|callable - */ - public static $validator = 'php'; - - /** - * An instance of the SMTP sender class. - * - * @var SMTP - */ - protected $smtp; - - /** - * The array of 'to' names and addresses. - * - * @var array - */ - protected $to = []; - - /** - * The array of 'cc' names and addresses. - * - * @var array - */ - protected $cc = []; - - /** - * The array of 'bcc' names and addresses. - * - * @var array - */ - protected $bcc = []; - - /** - * The array of reply-to names and addresses. - * - * @var array - */ - protected $ReplyTo = []; - - /** - * An array of all kinds of addresses. - * Includes all of $to, $cc, $bcc. - * - * @see PHPMailer::$to - * @see PHPMailer::$cc - * @see PHPMailer::$bcc - * - * @var array - */ - protected $all_recipients = []; - - /** - * An array of names and addresses queued for validation. - * In send(), valid and non duplicate entries are moved to $all_recipients - * and one of $to, $cc, or $bcc. - * This array is used only for addresses with IDN. - * - * @see PHPMailer::$to - * @see PHPMailer::$cc - * @see PHPMailer::$bcc - * @see PHPMailer::$all_recipients - * - * @var array - */ - protected $RecipientsQueue = []; - - /** - * An array of reply-to names and addresses queued for validation. - * In send(), valid and non duplicate entries are moved to $ReplyTo. - * This array is used only for addresses with IDN. - * - * @see PHPMailer::$ReplyTo - * - * @var array - */ - protected $ReplyToQueue = []; - - /** - * The array of attachments. - * - * @var array - */ - protected $attachment = []; - - /** - * The array of custom headers. - * - * @var array - */ - protected $CustomHeader = []; - - /** - * The most recent Message-ID (including angular brackets). - * - * @var string - */ - protected $lastMessageID = ''; - - /** - * The message's MIME type. - * - * @var string - */ - protected $message_type = ''; - - /** - * The array of MIME boundary strings. - * - * @var array - */ - protected $boundary = []; - - /** - * The array of available text strings for the current language. - * - * @var array - */ - protected $language = []; - - /** - * The number of errors encountered. - * - * @var int - */ - protected $error_count = 0; - - /** - * The S/MIME certificate file path. - * - * @var string - */ - protected $sign_cert_file = ''; - - /** - * The S/MIME key file path. - * - * @var string - */ - protected $sign_key_file = ''; - - /** - * The optional S/MIME extra certificates ("CA Chain") file path. - * - * @var string - */ - protected $sign_extracerts_file = ''; - - /** - * The S/MIME password for the key. - * Used only if the key is encrypted. - * - * @var string - */ - protected $sign_key_pass = ''; - - /** - * Whether to throw exceptions for errors. - * - * @var bool - */ - protected $exceptions = false; - - /** - * Unique ID used for message ID and boundaries. - * - * @var string - */ - protected $uniqueid = ''; - - /** - * The PHPMailer Version number. - * - * @var string - */ - const VERSION = '6.8.0'; - - /** - * Error severity: message only, continue processing. - * - * @var int - */ - const STOP_MESSAGE = 0; - - /** - * Error severity: message, likely ok to continue processing. - * - * @var int - */ - const STOP_CONTINUE = 1; - - /** - * Error severity: message, plus full stop, critical error reached. - * - * @var int - */ - const STOP_CRITICAL = 2; - - /** - * The SMTP standard CRLF line break. - * If you want to change line break format, change static::$LE, not this. - */ - const CRLF = "\r\n"; - - /** - * "Folding White Space" a white space string used for line folding. - */ - const FWS = ' '; - - /** - * SMTP RFC standard line ending; Carriage Return, Line Feed. - * - * @var string - */ - protected static $LE = self::CRLF; - - /** - * The maximum line length supported by mail(). - * - * Background: mail() will sometimes corrupt messages - * with headers headers longer than 65 chars, see #818. - * - * @var int - */ - const MAIL_MAX_LINE_LENGTH = 63; - - /** - * The maximum line length allowed by RFC 2822 section 2.1.1. - * - * @var int - */ - const MAX_LINE_LENGTH = 998; - - /** - * The lower maximum line length allowed by RFC 2822 section 2.1.1. - * This length does NOT include the line break - * 76 means that lines will be 77 or 78 chars depending on whether - * the line break format is LF or CRLF; both are valid. - * - * @var int - */ - const STD_LINE_LENGTH = 76; - - /** - * Constructor. - * - * @param bool $exceptions Should we throw external exceptions? - */ - public function __construct($exceptions = null) - { - if (null !== $exceptions) { - $this->exceptions = (bool) $exceptions; - } - //Pick an appropriate debug output format automatically - $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html'); - } - - /** - * Destructor. - */ - public function __destruct() - { - //Close any open SMTP connection nicely - $this->smtpClose(); - } - - /** - * Call mail() in a safe_mode-aware fashion. - * Also, unless sendmail_path points to sendmail (or something that - * claims to be sendmail), don't pass params (not a perfect fix, - * but it will do). - * - * @param string $to To - * @param string $subject Subject - * @param string $body Message Body - * @param string $header Additional Header(s) - * @param string|null $params Params - * - * @return bool - */ - private function mailPassthru($to, $subject, $body, $header, $params) - { - //Check overloading of mail function to avoid double-encoding - if ((int)ini_get('mbstring.func_overload') & 1) { - $subject = $this->secureHeader($subject); - } else { - $subject = $this->encodeHeader($this->secureHeader($subject)); - } - //Calling mail() with null params breaks - $this->edebug('Sending with mail()'); - $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); - $this->edebug("Envelope sender: {$this->Sender}"); - $this->edebug("To: {$to}"); - $this->edebug("Subject: {$subject}"); - $this->edebug("Headers: {$header}"); - if (!$this->UseSendmailOptions || null === $params) { - $result = @mail($to, $subject, $body, $header); - } else { - $this->edebug("Additional params: {$params}"); - $result = @mail($to, $subject, $body, $header, $params); - } - $this->edebug('Result: ' . ($result ? 'true' : 'false')); - return $result; - } - - /** - * Output debugging info via a user-defined method. - * Only generates output if debug output is enabled. - * - * @see PHPMailer::$Debugoutput - * @see PHPMailer::$SMTPDebug - * - * @param string $str - */ - protected function edebug($str) - { - if ($this->SMTPDebug <= 0) { - return; - } - //Is this a PSR-3 logger? - if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) { - $this->Debugoutput->debug($str); - - return; - } - //Avoid clash with built-in function names - if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) { - call_user_func($this->Debugoutput, $str, $this->SMTPDebug); - - return; - } - switch ($this->Debugoutput) { - case 'error_log': - //Don't output, just log - /** @noinspection ForgottenDebugOutputInspection */ - error_log($str); - break; - case 'html': - //Cleans up output a bit for a better looking, HTML-safe output - echo htmlentities( - preg_replace('/[\r\n]+/', '', $str), - ENT_QUOTES, - 'UTF-8' - ), "
\n"; - break; - case 'echo': - default: - //Normalize line breaks - $str = preg_replace('/\r\n|\r/m', "\n", $str); - echo gmdate('Y-m-d H:i:s'), - "\t", - //Trim trailing space - trim( - //Indent for readability, except for trailing break - str_replace( - "\n", - "\n \t ", - trim($str) - ) - ), - "\n"; - } - } - - /** - * Sets message type to HTML or plain. - * - * @param bool $isHtml True for HTML mode - */ - public function isHTML($isHtml = true) - { - if ($isHtml) { - $this->ContentType = static::CONTENT_TYPE_TEXT_HTML; - } else { - $this->ContentType = static::CONTENT_TYPE_PLAINTEXT; - } - } - - /** - * Send messages using SMTP. - */ - public function isSMTP() - { - $this->Mailer = 'smtp'; - } - - /** - * Send messages using PHP's mail() function. - */ - public function isMail() - { - $this->Mailer = 'mail'; - } - - /** - * Send messages using $Sendmail. - */ - public function isSendmail() - { - $ini_sendmail_path = ini_get('sendmail_path'); - - if (false === stripos($ini_sendmail_path, 'sendmail')) { - $this->Sendmail = '/usr/sbin/sendmail'; - } else { - $this->Sendmail = $ini_sendmail_path; - } - $this->Mailer = 'sendmail'; - } - - /** - * Send messages using qmail. - */ - public function isQmail() - { - $ini_sendmail_path = ini_get('sendmail_path'); - - if (false === stripos($ini_sendmail_path, 'qmail')) { - $this->Sendmail = '/var/qmail/bin/qmail-inject'; - } else { - $this->Sendmail = $ini_sendmail_path; - } - $this->Mailer = 'qmail'; - } - - /** - * Add a "To" address. - * - * @param string $address The email address to send to - * @param string $name - * - * @throws Exception - * - * @return bool true on success, false if address already used or invalid in some way - */ - public function addAddress($address, $name = '') - { - return $this->addOrEnqueueAnAddress('to', $address, $name); - } - - /** - * Add a "CC" address. - * - * @param string $address The email address to send to - * @param string $name - * - * @throws Exception - * - * @return bool true on success, false if address already used or invalid in some way - */ - public function addCC($address, $name = '') - { - return $this->addOrEnqueueAnAddress('cc', $address, $name); - } - - /** - * Add a "BCC" address. - * - * @param string $address The email address to send to - * @param string $name - * - * @throws Exception - * - * @return bool true on success, false if address already used or invalid in some way - */ - public function addBCC($address, $name = '') - { - return $this->addOrEnqueueAnAddress('bcc', $address, $name); - } - - /** - * Add a "Reply-To" address. - * - * @param string $address The email address to reply to - * @param string $name - * - * @throws Exception - * - * @return bool true on success, false if address already used or invalid in some way - */ - public function addReplyTo($address, $name = '') - { - return $this->addOrEnqueueAnAddress('Reply-To', $address, $name); - } - - /** - * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer - * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still - * be modified after calling this function), addition of such addresses is delayed until send(). - * Addresses that have been added already return false, but do not throw exceptions. - * - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' - * @param string $address The email address - * @param string $name An optional username associated with the address - * - * @throws Exception - * - * @return bool true on success, false if address already used or invalid in some way - */ - protected function addOrEnqueueAnAddress($kind, $address, $name) - { - $pos = false; - if ($address !== null) { - $address = trim($address); - $pos = strrpos($address, '@'); - } - if (false === $pos) { - //At-sign is missing. - $error_message = sprintf( - '%s (%s): %s', - $this->lang('invalid_address'), - $kind, - $address - ); - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new Exception($error_message); - } - - return false; - } - if ($name !== null && is_string($name)) { - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - } else { - $name = ''; - } - $params = [$kind, $address, $name]; - //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. - //Domain is assumed to be whatever is after the last @ symbol in the address - if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { - if ('Reply-To' !== $kind) { - if (!array_key_exists($address, $this->RecipientsQueue)) { - $this->RecipientsQueue[$address] = $params; - - return true; - } - } elseif (!array_key_exists($address, $this->ReplyToQueue)) { - $this->ReplyToQueue[$address] = $params; - - return true; - } - - return false; - } - - //Immediately add standard addresses without IDN. - return call_user_func_array([$this, 'addAnAddress'], $params); - } - - /** - * Set the boundaries to use for delimiting MIME parts. - * If you override this, ensure you set all 3 boundaries to unique values. - * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, - * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 - * - * @return void - */ - public function setBoundaries() - { - $this->uniqueid = $this->generateId(); - $this->boundary[1] = 'b1=_' . $this->uniqueid; - $this->boundary[2] = 'b2=_' . $this->uniqueid; - $this->boundary[3] = 'b3=_' . $this->uniqueid; - } - - /** - * Add an address to one of the recipient arrays or to the ReplyTo array. - * Addresses that have been added already return false, but do not throw exceptions. - * - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' - * @param string $address The email address to send, resp. to reply to - * @param string $name - * - * @throws Exception - * - * @return bool true on success, false if address already used or invalid in some way - */ - protected function addAnAddress($kind, $address, $name = '') - { - if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) { - $error_message = sprintf( - '%s: %s', - $this->lang('Invalid recipient kind'), - $kind - ); - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new Exception($error_message); - } - - return false; - } - if (!static::validateAddress($address)) { - $error_message = sprintf( - '%s (%s): %s', - $this->lang('invalid_address'), - $kind, - $address - ); - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new Exception($error_message); - } - - return false; - } - if ('Reply-To' !== $kind) { - if (!array_key_exists(strtolower($address), $this->all_recipients)) { - $this->{$kind}[] = [$address, $name]; - $this->all_recipients[strtolower($address)] = true; - - return true; - } - } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) { - $this->ReplyTo[strtolower($address)] = [$address, $name]; - - return true; - } - - return false; - } - - /** - * Parse and validate a string containing one or more RFC822-style comma-separated email addresses - * of the form "display name
" into an array of name/address pairs. - * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available. - * Note that quotes in the name part are removed. - * - * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation - * - * @param string $addrstr The address list string - * @param bool $useimap Whether to use the IMAP extension to parse the list - * @param string $charset The charset to use when decoding the address list string. - * - * @return array - */ - public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) - { - $addresses = []; - if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { - //Use this built-in parser if it's available - $list = imap_rfc822_parse_adrlist($addrstr, ''); - // Clear any potential IMAP errors to get rid of notices being thrown at end of script. - imap_errors(); - foreach ($list as $address) { - if ( - '.SYNTAX-ERROR.' !== $address->host && - static::validateAddress($address->mailbox . '@' . $address->host) - ) { - //Decode the name part if it's present and encoded - if ( - property_exists($address, 'personal') && - //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled - defined('MB_CASE_UPPER') && - preg_match('/^=\?.*\?=$/s', $address->personal) - ) { - $origCharset = mb_internal_encoding(); - mb_internal_encoding($charset); - //Undo any RFC2047-encoded spaces-as-underscores - $address->personal = str_replace('_', '=20', $address->personal); - //Decode the name - $address->personal = mb_decode_mimeheader($address->personal); - mb_internal_encoding($origCharset); - } - - $addresses[] = [ - 'name' => (property_exists($address, 'personal') ? $address->personal : ''), - 'address' => $address->mailbox . '@' . $address->host, - ]; - } - } - } else { - //Use this simpler parser - $list = explode(',', $addrstr); - foreach ($list as $address) { - $address = trim($address); - //Is there a separate name part? - if (strpos($address, '<') === false) { - //No separate name, just use the whole thing - if (static::validateAddress($address)) { - $addresses[] = [ - 'name' => '', - 'address' => $address, - ]; - } - } else { - list($name, $email) = explode('<', $address); - $email = trim(str_replace('>', '', $email)); - $name = trim($name); - if (static::validateAddress($email)) { - //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled - //If this name is encoded, decode it - if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) { - $origCharset = mb_internal_encoding(); - mb_internal_encoding($charset); - //Undo any RFC2047-encoded spaces-as-underscores - $name = str_replace('_', '=20', $name); - //Decode the name - $name = mb_decode_mimeheader($name); - mb_internal_encoding($origCharset); - } - $addresses[] = [ - //Remove any surrounding quotes and spaces from the name - 'name' => trim($name, '\'" '), - 'address' => $email, - ]; - } - } - } - } - - return $addresses; - } - - /** - * Set the From and FromName properties. - * - * @param string $address - * @param string $name - * @param bool $auto Whether to also set the Sender address, defaults to true - * - * @throws Exception - * - * @return bool - */ - public function setFrom($address, $name = '', $auto = true) - { - $address = trim((string)$address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - //Don't validate now addresses with IDN. Will be done in send(). - $pos = strrpos($address, '@'); - if ( - (false === $pos) - || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) - && !static::validateAddress($address)) - ) { - $error_message = sprintf( - '%s (From): %s', - $this->lang('invalid_address'), - $address - ); - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new Exception($error_message); - } - - return false; - } - $this->From = $address; - $this->FromName = $name; - if ($auto && empty($this->Sender)) { - $this->Sender = $address; - } - - return true; - } - - /** - * Return the Message-ID header of the last email. - * Technically this is the value from the last time the headers were created, - * but it's also the message ID of the last sent message except in - * pathological cases. - * - * @return string - */ - public function getLastMessageID() - { - return $this->lastMessageID; - } - - /** - * Check that a string looks like an email address. - * Validation patterns supported: - * * `auto` Pick best pattern automatically; - * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0; - * * `pcre` Use old PCRE implementation; - * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; - * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements. - * * `noregex` Don't use a regex: super fast, really dumb. - * Alternatively you may pass in a callable to inject your own validator, for example: - * - * ```php - * PHPMailer::validateAddress('user@example.com', function($address) { - * return (strpos($address, '@') !== false); - * }); - * ``` - * - * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator. - * - * @param string $address The email address to check - * @param string|callable $patternselect Which pattern to use - * - * @return bool - */ - public static function validateAddress($address, $patternselect = null) - { - if (null === $patternselect) { - $patternselect = static::$validator; - } - //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 - if (is_callable($patternselect) && !is_string($patternselect)) { - return call_user_func($patternselect, $address); - } - //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 - if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { - return false; - } - switch ($patternselect) { - case 'pcre': //Kept for BC - case 'pcre8': - /* - * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL - * is based. - * In addition to the addresses allowed by filter_var, also permits: - * * dotless domains: `a@b` - * * comments: `1234 @ local(blah) .machine .example` - * * quoted elements: `'"test blah"@example.org'` - * * numeric TLDs: `a@b.123` - * * unbracketed IPv4 literals: `a@192.168.0.1` - * * IPv6 literals: 'first.last@[IPv6:a1::]' - * Not all of these will necessarily work for sending! - * - * @see http://squiloople.com/2009/12/20/email-address-validation/ - * @copyright 2009-2010 Michael Rushton - * Feel free to use and redistribute this code. But please keep this copyright notice. - */ - return (bool) preg_match( - '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . - '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . - '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . - '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' . - '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' . - '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' . - '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' . - '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' . - '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', - $address - ); - case 'html5': - /* - * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. - * - * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) - */ - return (bool) preg_match( - '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . - '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', - $address - ); - case 'php': - default: - return filter_var($address, FILTER_VALIDATE_EMAIL) !== false; - } - } - - /** - * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the - * `intl` and `mbstring` PHP extensions. - * - * @return bool `true` if required functions for IDN support are present - */ - public static function idnSupported() - { - return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding'); - } - - /** - * Converts IDN in given email address to its ASCII form, also known as punycode, if possible. - * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet. - * This function silently returns unmodified address if: - * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form) - * - Conversion to punycode is impossible (e.g. required PHP functions are not available) - * or fails for any reason (e.g. domain contains characters not allowed in an IDN). - * - * @see PHPMailer::$CharSet - * - * @param string $address The email address to convert - * - * @return string The encoded address in ASCII form - */ - public function punyencodeAddress($address) - { - //Verify we have required functions, CharSet, and at-sign. - $pos = strrpos($address, '@'); - if ( - !empty($this->CharSet) && - false !== $pos && - static::idnSupported() - ) { - $domain = substr($address, ++$pos); - //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. - if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { - //Convert the domain from whatever charset it's in to UTF-8 - $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); - //Ignore IDE complaints about this line - method signature changed in PHP 5.4 - $errorcode = 0; - if (defined('INTL_IDNA_VARIANT_UTS46')) { - //Use the current punycode standard (appeared in PHP 7.2) - $punycode = idn_to_ascii( - $domain, - \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | - \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, - \INTL_IDNA_VARIANT_UTS46 - ); - } elseif (defined('INTL_IDNA_VARIANT_2003')) { - //Fall back to this old, deprecated/removed encoding - $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); - } else { - //Fall back to a default we don't know about - $punycode = idn_to_ascii($domain, $errorcode); - } - if (false !== $punycode) { - return substr($address, 0, $pos) . $punycode; - } - } - } - - return $address; - } - - /** - * Create a message and send it. - * Uses the sending method specified by $Mailer. - * - * @throws Exception - * - * @return bool false on error - See the ErrorInfo property for details of the error - */ - public function send() - { - try { - if (!$this->preSend()) { - return false; - } - - return $this->postSend(); - } catch (Exception $exc) { - $this->mailHeader = ''; - $this->setError($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - - return false; - } - } - - /** - * Prepare a message for sending. - * - * @throws Exception - * - * @return bool - */ - public function preSend() - { - if ( - 'smtp' === $this->Mailer - || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) - ) { - //SMTP mandates RFC-compliant line endings - //and it's also used with mail() on Windows - static::setLE(self::CRLF); - } else { - //Maintain backward compatibility with legacy Linux command line mailers - static::setLE(PHP_EOL); - } - //Check for buggy PHP versions that add a header with an incorrect line break - if ( - 'mail' === $this->Mailer - && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) - || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) - && ini_get('mail.add_x_header') === '1' - && stripos(PHP_OS, 'WIN') === 0 - ) { - trigger_error($this->lang('buggy_php'), E_USER_WARNING); - } - - try { - $this->error_count = 0; //Reset errors - $this->mailHeader = ''; - - //Dequeue recipient and Reply-To addresses with IDN - foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { - $params[1] = $this->punyencodeAddress($params[1]); - call_user_func_array([$this, 'addAnAddress'], $params); - } - if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { - throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); - } - - //Validate From, Sender, and ConfirmReadingTo addresses - foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { - $this->{$address_kind} = trim($this->{$address_kind}); - if (empty($this->{$address_kind})) { - continue; - } - $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); - if (!static::validateAddress($this->{$address_kind})) { - $error_message = sprintf( - '%s (%s): %s', - $this->lang('invalid_address'), - $address_kind, - $this->{$address_kind} - ); - $this->setError($error_message); - $this->edebug($error_message); - if ($this->exceptions) { - throw new Exception($error_message); - } - - return false; - } - } - - //Set whether the message is multipart/alternative - if ($this->alternativeExists()) { - $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; - } - - $this->setMessageType(); - //Refuse to send an empty message unless we are specifically allowing it - if (!$this->AllowEmpty && empty($this->Body)) { - throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL); - } - - //Trim subject consistently - $this->Subject = trim($this->Subject); - //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) - $this->MIMEHeader = ''; - $this->MIMEBody = $this->createBody(); - //createBody may have added some headers, so retain them - $tempheaders = $this->MIMEHeader; - $this->MIMEHeader = $this->createHeader(); - $this->MIMEHeader .= $tempheaders; - - //To capture the complete message when using mail(), create - //an extra header list which createHeader() doesn't fold in - if ('mail' === $this->Mailer) { - if (count($this->to) > 0) { - $this->mailHeader .= $this->addrAppend('To', $this->to); - } else { - $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;'); - } - $this->mailHeader .= $this->headerLine( - 'Subject', - $this->encodeHeader($this->secureHeader($this->Subject)) - ); - } - - //Sign with DKIM if enabled - if ( - !empty($this->DKIM_domain) - && !empty($this->DKIM_selector) - && (!empty($this->DKIM_private_string) - || (!empty($this->DKIM_private) - && static::isPermittedPath($this->DKIM_private) - && file_exists($this->DKIM_private) - ) - ) - ) { - $header_dkim = $this->DKIM_Add( - $this->MIMEHeader . $this->mailHeader, - $this->encodeHeader($this->secureHeader($this->Subject)), - $this->MIMEBody - ); - $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE . - static::normalizeBreaks($header_dkim) . static::$LE; - } - - return true; - } catch (Exception $exc) { - $this->setError($exc->getMessage()); - if ($this->exceptions) { - throw $exc; - } - - return false; - } - } - - /** - * Actually send a message via the selected mechanism. - * - * @throws Exception - * - * @return bool - */ - public function postSend() - { - try { - //Choose the mailer and send through it - switch ($this->Mailer) { - case 'sendmail': - case 'qmail': - return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody); - case 'smtp': - return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); - case 'mail': - return $this->mailSend($this->MIMEHeader, $this->MIMEBody); - default: - $sendMethod = $this->Mailer . 'Send'; - if (method_exists($this, $sendMethod)) { - return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); - } - - return $this->mailSend($this->MIMEHeader, $this->MIMEBody); - } - } catch (Exception $exc) { - $this->setError($exc->getMessage()); - $this->edebug($exc->getMessage()); - if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { - $this->smtp->reset(); - } - if ($this->exceptions) { - throw $exc; - } - } - - return false; - } - - /** - * Send mail using the $Sendmail program. - * - * @see PHPMailer::$Sendmail - * - * @param string $header The message headers - * @param string $body The message body - * - * @throws Exception - * - * @return bool - */ - protected function sendmailSend($header, $body) - { - if ($this->Mailer === 'qmail') { - $this->edebug('Sending with qmail'); - } else { - $this->edebug('Sending with sendmail'); - } - $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; - //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver - //A space after `-f` is optional, but there is a long history of its presence - //causing problems, so we don't use one - //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html - //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html - //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html - //Example problem: https://www.drupal.org/node/1057954 - - //PHP 5.6 workaround - $sendmail_from_value = ini_get('sendmail_from'); - if (empty($this->Sender) && !empty($sendmail_from_value)) { - //PHP config has a sender address we can use - $this->Sender = ini_get('sendmail_from'); - } - //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. - if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { - if ($this->Mailer === 'qmail') { - $sendmailFmt = '%s -f%s'; - } else { - $sendmailFmt = '%s -oi -f%s -t'; - } - } else { - //allow sendmail to choose a default envelope sender. It may - //seem preferable to force it to use the From header as with - //SMTP, but that introduces new problems (see - //), and - //it has historically worked this way. - $sendmailFmt = '%s -oi -t'; - } - - $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); - $this->edebug('Sendmail path: ' . $this->Sendmail); - $this->edebug('Sendmail command: ' . $sendmail); - $this->edebug('Envelope sender: ' . $this->Sender); - $this->edebug("Headers: {$header}"); - - if ($this->SingleTo) { - foreach ($this->SingleToArray as $toAddr) { - $mail = @popen($sendmail, 'w'); - if (!$mail) { - throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - $this->edebug("To: {$toAddr}"); - fwrite($mail, 'To: ' . $toAddr . "\n"); - fwrite($mail, $header); - fwrite($mail, $body); - $result = pclose($mail); - $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); - $this->doCallback( - ($result === 0), - [[$addrinfo['address'], $addrinfo['name']]], - $this->cc, - $this->bcc, - $this->Subject, - $body, - $this->From, - [] - ); - $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); - if (0 !== $result) { - throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - } - } else { - $mail = @popen($sendmail, 'w'); - if (!$mail) { - throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - fwrite($mail, $header); - fwrite($mail, $body); - $result = pclose($mail); - $this->doCallback( - ($result === 0), - $this->to, - $this->cc, - $this->bcc, - $this->Subject, - $body, - $this->From, - [] - ); - $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); - if (0 !== $result) { - throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); - } - } - - return true; - } - - /** - * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters. - * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows. - * - * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report - * - * @param string $string The string to be validated - * - * @return bool - */ - protected static function isShellSafe($string) - { - //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, - //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, - //so we don't. - if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { - return false; - } - - if ( - escapeshellcmd($string) !== $string - || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) - ) { - return false; - } - - $length = strlen($string); - - for ($i = 0; $i < $length; ++$i) { - $c = $string[$i]; - - //All other characters have a special meaning in at least one common shell, including = and +. - //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. - //Note that this does permit non-Latin alphanumeric characters based on the current locale. - if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { - return false; - } - } - - return true; - } - - /** - * Check whether a file path is of a permitted type. - * Used to reject URLs and phar files from functions that access local file paths, - * such as addAttachment. - * - * @param string $path A relative or absolute path to a file - * - * @return bool - */ - protected static function isPermittedPath($path) - { - //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 - return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); - } - - /** - * Check whether a file path is safe, accessible, and readable. - * - * @param string $path A relative or absolute path to a file - * - * @return bool - */ - protected static function fileIsAccessible($path) - { - if (!static::isPermittedPath($path)) { - return false; - } - $readable = is_file($path); - //If not a UNC path (expected to start with \\), check read permission, see #2069 - if (strpos($path, '\\\\') !== 0) { - $readable = $readable && is_readable($path); - } - return $readable; - } - - /** - * Send mail using the PHP mail() function. - * - * @see http://www.php.net/manual/en/book.mail.php - * - * @param string $header The message headers - * @param string $body The message body - * - * @throws Exception - * - * @return bool - */ - protected function mailSend($header, $body) - { - $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; - - $toArr = []; - foreach ($this->to as $toaddr) { - $toArr[] = $this->addrFormat($toaddr); - } - $to = trim(implode(', ', $toArr)); - - //If there are no To-addresses (e.g. when sending only to BCC-addresses) - //the following should be added to get a correct DKIM-signature. - //Compare with $this->preSend() - if ($to === '') { - $to = 'undisclosed-recipients:;'; - } - - $params = null; - //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver - //A space after `-f` is optional, but there is a long history of its presence - //causing problems, so we don't use one - //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html - //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html - //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html - //Example problem: https://www.drupal.org/node/1057954 - //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. - - //PHP 5.6 workaround - $sendmail_from_value = ini_get('sendmail_from'); - if (empty($this->Sender) && !empty($sendmail_from_value)) { - //PHP config has a sender address we can use - $this->Sender = ini_get('sendmail_from'); - } - if (!empty($this->Sender) && static::validateAddress($this->Sender)) { - if (self::isShellSafe($this->Sender)) { - $params = sprintf('-f%s', $this->Sender); - } - $old_from = ini_get('sendmail_from'); - ini_set('sendmail_from', $this->Sender); - } - $result = false; - if ($this->SingleTo && count($toArr) > 1) { - foreach ($toArr as $toAddr) { - $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); - $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); - $this->doCallback( - $result, - [[$addrinfo['address'], $addrinfo['name']]], - $this->cc, - $this->bcc, - $this->Subject, - $body, - $this->From, - [] - ); - } - } else { - $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); - $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); - } - if (isset($old_from)) { - ini_set('sendmail_from', $old_from); - } - if (!$result) { - throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL); - } - - return true; - } - - /** - * Get an instance to use for SMTP operations. - * Override this function to load your own SMTP implementation, - * or set one with setSMTPInstance. - * - * @return SMTP - */ - public function getSMTPInstance() - { - if (!is_object($this->smtp)) { - $this->smtp = new SMTP(); - } - - return $this->smtp; - } - - /** - * Provide an instance to use for SMTP operations. - * - * @return SMTP - */ - public function setSMTPInstance(SMTP $smtp) - { - $this->smtp = $smtp; - - return $this->smtp; - } - - /** - * Send mail via SMTP. - * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. - * - * @see PHPMailer::setSMTPInstance() to use a different class. - * - * @uses \PHPMailer\PHPMailer\SMTP - * - * @param string $header The message headers - * @param string $body The message body - * - * @throws Exception - * - * @return bool - */ - protected function smtpSend($header, $body) - { - $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; - $bad_rcpt = []; - if (!$this->smtpConnect($this->SMTPOptions)) { - throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL); - } - //Sender already validated in preSend() - if ('' === $this->Sender) { - $smtp_from = $this->From; - } else { - $smtp_from = $this->Sender; - } - if (!$this->smtp->mail($smtp_from)) { - $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); - throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); - } - - $callbacks = []; - //Attempt to send to all recipients - foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { - foreach ($togroup as $to) { - if (!$this->smtp->recipient($to[0], $this->dsn)) { - $error = $this->smtp->getError(); - $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; - $isSent = false; - } else { - $isSent = true; - } - - $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; - } - } - - //Only send the DATA command if we have viable recipients - if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) { - throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL); - } - - $smtp_transaction_id = $this->smtp->getLastTransactionID(); - - if ($this->SMTPKeepAlive) { - $this->smtp->reset(); - } else { - $this->smtp->quit(); - $this->smtp->close(); - } - - foreach ($callbacks as $cb) { - $this->doCallback( - $cb['issent'], - [[$cb['to'], $cb['name']]], - [], - [], - $this->Subject, - $body, - $this->From, - ['smtp_transaction_id' => $smtp_transaction_id] - ); - } - - //Create error message for any bad addresses - if (count($bad_rcpt) > 0) { - $errstr = ''; - foreach ($bad_rcpt as $bad) { - $errstr .= $bad['to'] . ': ' . $bad['error']; - } - throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE); - } - - return true; - } - - /** - * Initiate a connection to an SMTP server. - * Returns false if the operation failed. - * - * @param array $options An array of options compatible with stream_context_create() - * - * @throws Exception - * - * @uses \PHPMailer\PHPMailer\SMTP - * - * @return bool - */ - public function smtpConnect($options = null) - { - if (null === $this->smtp) { - $this->smtp = $this->getSMTPInstance(); - } - - //If no options are provided, use whatever is set in the instance - if (null === $options) { - $options = $this->SMTPOptions; - } - - //Already connected? - if ($this->smtp->connected()) { - return true; - } - - $this->smtp->setTimeout($this->Timeout); - $this->smtp->setDebugLevel($this->SMTPDebug); - $this->smtp->setDebugOutput($this->Debugoutput); - $this->smtp->setVerp($this->do_verp); - if ($this->Host === null) { - $this->Host = 'localhost'; - } - $hosts = explode(';', $this->Host); - $lastexception = null; - - foreach ($hosts as $hostentry) { - $hostinfo = []; - if ( - !preg_match( - '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', - trim($hostentry), - $hostinfo - ) - ) { - $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); - //Not a valid host entry - continue; - } - //$hostinfo[1]: optional ssl or tls prefix - //$hostinfo[2]: the hostname - //$hostinfo[3]: optional port number - //The host string prefix can temporarily override the current setting for SMTPSecure - //If it's not specified, the default value is used - - //Check the host name is a valid name or IP address before trying to use it - if (!static::isValidHost($hostinfo[2])) { - $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); - continue; - } - $prefix = ''; - $secure = $this->SMTPSecure; - $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); - if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { - $prefix = 'ssl://'; - $tls = false; //Can't have SSL and TLS at the same time - $secure = static::ENCRYPTION_SMTPS; - } elseif ('tls' === $hostinfo[1]) { - $tls = true; - //TLS doesn't use a prefix - $secure = static::ENCRYPTION_STARTTLS; - } - //Do we need the OpenSSL extension? - $sslext = defined('OPENSSL_ALGO_SHA256'); - if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) { - //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled - if (!$sslext) { - throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); - } - } - $host = $hostinfo[2]; - $port = $this->Port; - if ( - array_key_exists(3, $hostinfo) && - is_numeric($hostinfo[3]) && - $hostinfo[3] > 0 && - $hostinfo[3] < 65536 - ) { - $port = (int) $hostinfo[3]; - } - if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { - try { - if ($this->Helo) { - $hello = $this->Helo; - } else { - $hello = $this->serverHostname(); - } - $this->smtp->hello($hello); - //Automatically enable TLS encryption if: - //* it's not disabled - //* we have openssl extension - //* we are not already using SSL - //* the server offers STARTTLS - if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { - $tls = true; - } - if ($tls) { - if (!$this->smtp->startTLS()) { - $message = $this->getSmtpErrorMessage('connect_host'); - throw new Exception($message); - } - //We must resend EHLO after TLS negotiation - $this->smtp->hello($hello); - } - if ( - $this->SMTPAuth && !$this->smtp->authenticate( - $this->Username, - $this->Password, - $this->AuthType, - $this->oauth - ) - ) { - throw new Exception($this->lang('authenticate')); - } - - return true; - } catch (Exception $exc) { - $lastexception = $exc; - $this->edebug($exc->getMessage()); - //We must have connected, but then failed TLS or Auth, so close connection nicely - $this->smtp->quit(); - } - } - } - //If we get here, all connection attempts have failed, so close connection hard - $this->smtp->close(); - //As we've caught all exceptions, just report whatever the last one was - if ($this->exceptions && null !== $lastexception) { - throw $lastexception; - } - if ($this->exceptions) { - // no exception was thrown, likely $this->smtp->connect() failed - $message = $this->getSmtpErrorMessage('connect_host'); - throw new Exception($message); - } - - return false; - } - - /** - * Close the active SMTP session if one exists. - */ - public function smtpClose() - { - if ((null !== $this->smtp) && $this->smtp->connected()) { - $this->smtp->quit(); - $this->smtp->close(); - } - } - - /** - * Set the language for error messages. - * The default language is English. - * - * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") - * Optionally, the language code can be enhanced with a 4-character - * script annotation and/or a 2-character country annotation. - * @param string $lang_path Path to the language file directory, with trailing separator (slash) - * Do not set this from user input! - * - * @return bool Returns true if the requested language was loaded, false otherwise. - */ - public function setLanguage($langcode = 'en', $lang_path = '') - { - //Backwards compatibility for renamed language codes - $renamed_langcodes = [ - 'br' => 'pt_br', - 'cz' => 'cs', - 'dk' => 'da', - 'no' => 'nb', - 'se' => 'sv', - 'rs' => 'sr', - 'tg' => 'tl', - 'am' => 'hy', - ]; - - if (array_key_exists($langcode, $renamed_langcodes)) { - $langcode = $renamed_langcodes[$langcode]; - } - - //Define full set of translatable strings in English - $PHPMAILER_LANG = [ - 'authenticate' => 'SMTP Error: Could not authenticate.', - 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . - ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . - ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', - 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', - 'data_not_accepted' => 'SMTP Error: data not accepted.', - 'empty_message' => 'Message body empty', - 'encoding' => 'Unknown encoding: ', - 'execute' => 'Could not execute: ', - 'extension_missing' => 'Extension missing: ', - 'file_access' => 'Could not access file: ', - 'file_open' => 'File Error: Could not open file: ', - 'from_failed' => 'The following From address failed: ', - 'instantiate' => 'Could not instantiate mail function.', - 'invalid_address' => 'Invalid address: ', - 'invalid_header' => 'Invalid header name or value', - 'invalid_hostentry' => 'Invalid hostentry: ', - 'invalid_host' => 'Invalid host: ', - 'mailer_not_supported' => ' mailer is not supported.', - 'provide_address' => 'You must provide at least one recipient email address.', - 'recipients_failed' => 'SMTP Error: The following recipients failed: ', - 'signing' => 'Signing Error: ', - 'smtp_code' => 'SMTP code: ', - 'smtp_code_ex' => 'Additional SMTP info: ', - 'smtp_connect_failed' => 'SMTP connect() failed.', - 'smtp_detail' => 'Detail: ', - 'smtp_error' => 'SMTP server error: ', - 'variable_set' => 'Cannot set or reset variable: ', - ]; - if (empty($lang_path)) { - //Calculate an absolute path so it can work if CWD is not here - $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; - } - - //Validate $langcode - $foundlang = true; - $langcode = strtolower($langcode); - if ( - !preg_match('/^(?P[a-z]{2})(?P