Jump to content
iwato

SwiftMailer - An Odyssey of Reconstruction

Recommended Posts

BACKGROUND:  I have never experienced anything like it.  It were as if the authors purposely sabotaged their software for non-professionals.  I have now edited close to a dozen files removing extraneous code with each new fatal error.  Finally, however, I have come across a failure that baffles me.

DISCUSSION:  Line 90 of the  error message (see below) is the first line of code of the class's construct function -- namely,

$this->cacheKey = bin2hex(random_bytes(16)); // set 32 hex values

Now, I have researched both the bin2hex() and random_bytes() function, and I can discover nothing amiss with the implementation of either. The problem appears to lie elsewhere. But, where to look?

The ERROR:

Quote

[18-Nov-2018 04:47:02 UTC] PHP Fatal error:  Call to undefined function random_bytes() in .../vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php on line 90

The FILE:

<?php

/*
 * This file is part of SwiftMailer.
 * (c) 2004-2009 Chris Corbyn
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A MIME entity, in a multipart message.
 *
 * @author Chris Corbyn
 */
class Swift_Mime_SimpleMimeEntity implements Swift_Mime_CharsetObserver, Swift_Mime_EncodingObserver
{
    /** Main message document; there can only be one of these */
    const LEVEL_TOP = 16;

    /** An entity which nests with the same precedence as an attachment */
    const LEVEL_MIXED = 256;

    /** An entity which nests with the same precedence as a mime part */
    const LEVEL_ALTERNATIVE = 4096;

    /** An entity which nests with the same precedence as embedded content */
    const LEVEL_RELATED = 65536;

    /** A collection of Headers for this mime entity */
    private $headers;

    /** The body as a string, or a stream */
    private $body;

    /** The encoder that encodes the body into a streamable format */
    private $encoder;

    /** Message ID generator */
    private $idGenerator;

    /** A mime boundary, if any is used */
    private $boundary;

    /** Mime types to be used based on the nesting level */
    private $compositeRanges = [
        'multipart/mixed' => [self::LEVEL_TOP, self::LEVEL_MIXED],
        'multipart/alternative' => [self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE],
        'multipart/related' => [self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED],
    ];

    /** A set of filter rules to define what level an entity should be nested at */
    private $compoundLevelFilters = [];

    /** The nesting level of this entity */
    private $nestingLevel = self::LEVEL_ALTERNATIVE;

    /** A KeyCache instance used during encoding and streaming */
    private $cache;

    /** Direct descendants of this entity */
    private $immediateChildren = [];

    /** All descendants of this entity */
    private $children = [];

    /** The maximum line length of the body of this entity */
    private $maxLineLength = 78;

    /** The order in which alternative mime types should appear */
    private $alternativePartOrder = [
        'text/plain' => 1,
        'text/html' => 2,
        'multipart/related' => 3,
    ];

    /** The CID of this entity */
    private $id;

    /** The key used for accessing the cache */
    private $cacheKey;

    protected $userContentType;

    /**
     * Create a new SimpleMimeEntity with $headers, $encoder and $cache.
     */
    public function __construct(Swift_Mime_SimpleHeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_IdGenerator $idGenerator)
    {
        $this->cacheKey = bin2hex(random_bytes(16)); // set 32 hex values
        $this->cache = $cache;
        $this->headers = $headers;
        $this->idGenerator = $idGenerator;
        $this->setEncoder($encoder);
        $this->headers->defineOrdering(['Content-Type', 'Content-Transfer-Encoding']);

        // This array specifies that, when the entire MIME document contains
        // $compoundLevel, then for each child within $level, if its Content-Type
        // is $contentType then it should be treated as if it's level is
        // $neededLevel instead.  I tried to write that unambiguously! :-\
        // Data Structure:
        // array (
        //   $compoundLevel => array(
        //     $level => array(
        //       $contentType => $neededLevel
        //     )
        //   )
        // )

        $this->compoundLevelFilters = [
            (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => [
                self::LEVEL_ALTERNATIVE => [
                    'text/plain' => self::LEVEL_ALTERNATIVE,
                    'text/html' => self::LEVEL_RELATED,
                    ],
                ],
            ];

        $this->id = $this->idGenerator->generateId();
    }

    /**
     * Generate a new Content-ID or Message-ID for this MIME entity.
     *
     * @return string
     */
    public function generateId()
    {
        $this->setId($this->idGenerator->generateId());

        return $this->id;
    }

    /**
     * Get the {@link Swift_Mime_SimpleHeaderSet} for this entity.
     *
     * @return Swift_Mime_SimpleHeaderSet
     */
    public function getHeaders()
    {
        return $this->headers;
    }

    /**
     * Get the nesting level of this entity.
     *
     * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE
     *
     * @return int
     */
    public function getNestingLevel()
    {
        return $this->nestingLevel;
    }

    /**
     * Get the Content-type of this entity.
     *
     * @return string
     */
    public function getContentType()
    {
        return $this->getHeaderFieldModel('Content-Type');
    }

    /**
     * Get the Body Content-type of this entity.
     *
     * @return string
     */
    public function getBodyContentType()
    {
        return $this->userContentType;
    }

    /**
     * Set the Content-type of this entity.
     *
     * @param string $type
     *
     * @return $this
     */
    public function setContentType($type)
    {
        $this->setContentTypeInHeaders($type);
        // Keep track of the value so that if the content-type changes automatically
        // due to added child entities, it can be restored if they are later removed
        $this->userContentType = $type;

        return $this;
    }

    /**
     * Get the CID of this entity.
     *
     * The CID will only be present in headers if a Content-ID header is present.
     *
     * @return string
     */
    public function getId()
    {
        $tmp = (array) $this->getHeaderFieldModel($this->getIdField());

        return $this->headers->has($this->getIdField()) ? current($tmp) : $this->id;
    }

    /**
     * Set the CID of this entity.
     *
     * @param string $id
     *
     * @return $this
     */
    public function setId($id)
    {
        if (!$this->setHeaderFieldModel($this->getIdField(), $id)) {
            $this->headers->addIdHeader($this->getIdField(), $id);
        }
        $this->id = $id;

        return $this;
    }

    /**
     * Get the description of this entity.
     *
     * This value comes from the Content-Description header if set.
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->getHeaderFieldModel('Content-Description');
    }

    /**
     * Set the description of this entity.
     *
     * This method sets a value in the Content-ID header.
     *
     * @param string $description
     *
     * @return $this
     */
    public function setDescription($description)
    {
        if (!$this->setHeaderFieldModel('Content-Description', $description)) {
            $this->headers->addTextHeader('Content-Description', $description);
        }

        return $this;
    }

    /**
     * Get the maximum line length of the body of this entity.
     *
     * @return int
     */
    public function getMaxLineLength()
    {
        return $this->maxLineLength;
    }

    /**
     * Set the maximum line length of lines in this body.
     *
     * Though not enforced by the library, lines should not exceed 1000 chars.
     *
     * @param int $length
     *
     * @return $this
     */
    public function setMaxLineLength($length)
    {
        $this->maxLineLength = $length;

        return $this;
    }

    /**
     * Get all children added to this entity.
     *
     * @return Swift_Mime_SimpleMimeEntity[]
     */
    public function getChildren()
    {
        return $this->children;
    }

    /**
     * Set all children of this entity.
     *
     * @param Swift_Mime_SimpleMimeEntity[] $children
     * @param int                           $compoundLevel For internal use only
     *
     * @return $this
     */
    public function setChildren(array $children, $compoundLevel = null)
    {
        // TODO: Try to refactor this logic
        $compoundLevel = $compoundLevel ? $this->getCompoundLevel($children) :
        $immediateChildren = [];
        $grandchildren = [];
        $newContentType = $this->userContentType;

        foreach ($children as $child) {
            $level = $this->getNeededChildLevel($child, $compoundLevel);
            if (empty($immediateChildren)) {
                //first iteration
                $immediateChildren = [$child];
            } else {
                $nextLevel = $this->getNeededChildLevel($immediateChildren[0], $compoundLevel);
                if ($nextLevel == $level) {
                    $immediateChildren[] = $child;
                } elseif ($level < $nextLevel) {
                    // Re-assign immediateChildren to grandchildren
                    $grandchildren = array_merge($grandchildren, $immediateChildren);
                    // Set new children
                    $immediateChildren = [$child];
                } else {
                    $grandchildren[] = $child;
                }
            }
        }

        if ($immediateChildren) {
            $lowestLevel = $this->getNeededChildLevel($immediateChildren[0], $compoundLevel);

            // Determine which composite media type is needed to accommodate the
            // immediate children
            foreach ($this->compositeRanges as $mediaType => $range) {
                if ($lowestLevel > $range[0] && $lowestLevel <= $range[1]) {
                    $newContentType = $mediaType;

                    break;
                }
            }

            // Put any grandchildren in a subpart
            if (!empty($grandchildren)) {
                $subentity = $this->createChild();
                $subentity->setNestingLevel($lowestLevel);
                $subentity->setChildren($grandchildren, $compoundLevel);
                array_unshift($immediateChildren, $subentity);
            }
        }

        $this->immediateChildren = $immediateChildren;
        $this->children = $children;
        $this->setContentTypeInHeaders($newContentType);
        $this->fixHeaders();
        $this->sortChildren();

        return $this;
    }

    /**
     * Get the body of this entity as a string.
     *
     * @return string
     */
    public function getBody()
    {
        return $this->body instanceof Swift_OutputByteStream ? $this->readStream($this->body) : $this->body;
    }

    /**
     * Set the body of this entity, either as a string, or as an instance of
     * {@link Swift_OutputByteStream}.
     *
     * @param mixed  $body
     * @param string $contentType optional
     *
     * @return $this
     */
    public function setBody($body, $contentType = null)
    {
        if ($body !== $this->body) {
            $this->clearCache();
        }

        $this->body = $body;
        if (null !== $contentType) {
            $this->setContentType($contentType);
        }

        return $this;
    }

    /**
     * Get the encoder used for the body of this entity.
     *
     * @return Swift_Mime_ContentEncoder
     */
    public function getEncoder()
    {
        return $this->encoder;
    }

    /**
     * Set the encoder used for the body of this entity.
     *
     * @return $this
     */
    public function setEncoder(Swift_Mime_ContentEncoder $encoder)
    {
        if ($encoder !== $this->encoder) {
            $this->clearCache();
        }

        $this->encoder = $encoder;
        $this->setEncoding($encoder->getName());
        $this->notifyEncoderChanged($encoder);

        return $this;
    }

    /**
     * Get the boundary used to separate children in this entity.
     *
     * @return string
     */
    public function getBoundary()
    {
        if (!isset($this->boundary)) {
            $this->boundary = '_=_swift_'.time().'_'.bin2hex(random_bytes(16)).'_=_';
        }

        return $this->boundary;
    }

    /**
     * Set the boundary used to separate children in this entity.
     *
     * @param string $boundary
     *
     * @throws Swift_RfcComplianceException
     *
     * @return $this
     */
    public function setBoundary($boundary)
    {
        $this->assertValidBoundary($boundary);
        $this->boundary = $boundary;

        return $this;
    }

    /**
     * Receive notification that the charset of this entity, or a parent entity
     * has changed.
     *
     * @param string $charset
     */
    public function charsetChanged($charset)
    {
        $this->notifyCharsetChanged($charset);
    }

    /**
     * Receive notification that the encoder of this entity or a parent entity
     * has changed.
     */
    public function encoderChanged(Swift_Mime_ContentEncoder $encoder)
    {
        $this->notifyEncoderChanged($encoder);
    }

    /**
     * Get this entire entity as a string.
     *
     * @return string
     */
    public function toString()
    {
        $string = $this->headers->toString();
        $string .= $this->bodyToString();

        return $string;
    }

    /**
     * Get this entire entity as a string.
     *
     * @return string
     */
    protected function bodyToString()
    {
        $string = '';

        if (isset($this->body) && empty($this->immediateChildren)) {
            if ($this->cache->hasKey($this->cacheKey, 'body')) {
                $body = $this->cache->getString($this->cacheKey, 'body');
            } else {
                $body = "\r\n".$this->encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength());
                $this->cache->setString($this->cacheKey, 'body', $body, Swift_KeyCache::MODE_WRITE);
            }
            $string .= $body;
        }

        if (!empty($this->immediateChildren)) {
            foreach ($this->immediateChildren as $child) {
                $string .= "\r\n\r\n--".$this->getBoundary()."\r\n";
                $string .= $child->toString();
            }
            $string .= "\r\n\r\n--".$this->getBoundary()."--\r\n";
        }

        return $string;
    }

    /**
     * Returns a string representation of this object.
     *
     * @see toString()
     *
     * @return string
     */
    public function __toString()
    {
        return $this->toString();
    }

    /**
     * Write this entire entity to a {@see Swift_InputByteStream}.
     */
    public function toByteStream(Swift_InputByteStream $is)
    {
        $is->write($this->headers->toString());
        $is->commit();

        $this->bodyToByteStream($is);
    }

    /**
     * Write this entire entity to a {@link Swift_InputByteStream}.
     */
    protected function bodyToByteStream(Swift_InputByteStream $is)
    {
        if (empty($this->immediateChildren)) {
            if (isset($this->body)) {
                if ($this->cache->hasKey($this->cacheKey, 'body')) {
                    $this->cache->exportToByteStream($this->cacheKey, 'body', $is);
                } else {
                    $cacheIs = $this->cache->getInputByteStream($this->cacheKey, 'body');
                    if ($cacheIs) {
                        $is->bind($cacheIs);
                    }

                    $is->write("\r\n");

                    if ($this->body instanceof Swift_OutputByteStream) {
                        $this->body->setReadPointer(0);

                        $this->encoder->encodeByteStream($this->body, $is, 0, $this->getMaxLineLength());
                    } else {
                        $is->write($this->encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength()));
                    }

                    if ($cacheIs) {
                        $is->unbind($cacheIs);
                    }
                }
            }
        }

        if (!empty($this->immediateChildren)) {
            foreach ($this->immediateChildren as $child) {
                $is->write("\r\n\r\n--".$this->getBoundary()."\r\n");
                $child->toByteStream($is);
            }
            $is->write("\r\n\r\n--".$this->getBoundary()."--\r\n");
        }
    }

    /**
     * Get the name of the header that provides the ID of this entity.
     */
    protected function getIdField()
    {
        return 'Content-ID';
    }

    /**
     * Get the model data (usually an array or a string) for $field.
     */
    protected function getHeaderFieldModel($field)
    {
        if ($this->headers->has($field)) {
            return $this->headers->get($field)->getFieldBodyModel();
        }
    }

    /**
     * Set the model data for $field.
     */
    protected function setHeaderFieldModel($field, $model)
    {
        if ($this->headers->has($field)) {
            $this->headers->get($field)->setFieldBodyModel($model);

            return true;
        }

        return false;
    }

    /**
     * Get the parameter value of $parameter on $field header.
     */
    protected function getHeaderParameter($field, $parameter)
    {
        if ($this->headers->has($field)) {
            return $this->headers->get($field)->getParameter($parameter);
        }
    }

    /**
     * Set the parameter value of $parameter on $field header.
     */
    protected function setHeaderParameter($field, $parameter, $value)
    {
        if ($this->headers->has($field)) {
            $this->headers->get($field)->setParameter($parameter, $value);

            return true;
        }

        return false;
    }

    /**
     * Re-evaluate what content type and encoding should be used on this entity.
     */
    protected function fixHeaders()
    {
        if (count($this->immediateChildren)) {
            $this->setHeaderParameter('Content-Type', 'boundary',
                $this->getBoundary()
                );
            $this->headers->remove('Content-Transfer-Encoding');
        } else {
            $this->setHeaderParameter('Content-Type', 'boundary', null);
            $this->setEncoding($this->encoder->getName());
        }
    }

    /**
     * Get the KeyCache used in this entity.
     *
     * @return Swift_KeyCache
     */
    protected function getCache()
    {
        return $this->cache;
    }

    /**
     * Get the ID generator.
     *
     * @return Swift_IdGenerator
     */
    protected function getIdGenerator()
    {
        return $this->idGenerator;
    }

    /**
     * Empty the KeyCache for this entity.
     */
    protected function clearCache()
    {
        $this->cache->clearKey($this->cacheKey, 'body');
    }

    private function readStream(Swift_OutputByteStream $os)
    {
        $string = '';
        while (false !== $bytes = $os->read(8192)) {
            $string .= $bytes;
        }

        $os->setReadPointer(0);

        return $string;
    }

    private function setEncoding($encoding)
    {
        if (!$this->setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) {
            $this->headers->addTextHeader('Content-Transfer-Encoding', $encoding);
        }
    }

    private function assertValidBoundary($boundary)
    {
        if (!preg_match('/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di', $boundary)) {
            throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.');
        }
    }

    private function setContentTypeInHeaders($type)
    {
        if (!$this->setHeaderFieldModel('Content-Type', $type)) {
            $this->headers->addParameterizedHeader('Content-Type', $type);
        }
    }

    private function setNestingLevel($level)
    {
        $this->nestingLevel = $level;
    }

    private function getCompoundLevel($children)
    {
        $level = 0;
        foreach ($children as $child) {
            $level |= $child->getNestingLevel();
        }

        return $level;
    }

    private function getNeededChildLevel($child, $compoundLevel)
    {
        $filter = [];
        foreach ($this->compoundLevelFilters as $bitmask => $rules) {
            if (($compoundLevel & $bitmask) === $bitmask) {
                $filter = $rules + $filter;
            }
        }

        $realLevel = $child->getNestingLevel();
        $lowercaseType = strtolower($child->getContentType());

        if (isset($filter[$realLevel]) && isset($filter[$realLevel][$lowercaseType])) {
            return $filter[$realLevel][$lowercaseType];
        }

        return $realLevel;
    }

    private function createChild()
    {
        return new self($this->headers->newInstance(), $this->encoder, $this->cache, $this->idGenerator);
    }

    private function notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder)
    {
        foreach ($this->immediateChildren as $child) {
            $child->encoderChanged($encoder);
        }
    }

    private function notifyCharsetChanged($charset)
    {
        $this->encoder->charsetChanged($charset);
        $this->headers->charsetChanged($charset);
        foreach ($this->immediateChildren as $child) {
            $child->charsetChanged($charset);
        }
    }

    private function sortChildren()
    {
        $shouldSort = false;
        foreach ($this->immediateChildren as $child) {
            // NOTE: This include alternative parts moved into a related part
            if (self::LEVEL_ALTERNATIVE == $child->getNestingLevel()) {
                $shouldSort = true;
                break;
            }
        }

        // Sort in order of preference, if there is one
        if ($shouldSort) {
            // Group the messages by order of preference
            $sorted = [];
            foreach ($this->immediateChildren as $child) {
                $type = $child->getContentType();
                $level = array_key_exists($type, $this->alternativePartOrder) ? $this->alternativePartOrder[$type] : max($this->alternativePartOrder) + 1;

                if (empty($sorted[$level])) {
                    $sorted[$level] = [];
                }

                $sorted[$level][] = $child;
            }

            ksort($sorted);

            $this->immediateChildren = array_reduce($sorted, 'array_merge', []);
        }
    }

    /**
     * Empties it's own contents from the cache.
     */
    public function __destruct()
    {
        if ($this->cache instanceof Swift_KeyCache) {
            $this->cache->clearAll($this->cacheKey);
        }
    }

    /**
     * Make a deep copy of object.
     */
    public function __clone()
    {
        $this->headers = clone $this->headers;
        $this->encoder = clone $this->encoder;
        $this->cacheKey = bin2hex(random_bytes(16)); // set 32 hex values
        $children = [];
        foreach ($this->children as $pos => $child) {
            $children[$pos] = clone $child;
        }
        $this->setChildren($children);
    }
}

Please advise.

Roddy

Share this post


Link to post
Share on other sites

You shouldn't be trying to shift the blame of your problems to other people. The people developing these things don't have the time or incentive to sabotage their own software.

Your problem is that the software requires PHP version 7, as stated in the system requirements on their website: https://swiftmailer.symfony.com/docs/introduction.html. The random_bytes() function is exclusive to PHP 7, your server is probably running PHP 5.x.

Share this post


Link to post
Share on other sites

Ingolme:  My, my!  Who bit you and why?  You need to find out, for it was not me.

The version of PHP that I am using to run SwiftMailer, and that I installed with Composer is 7.2.  What is more, I said that I researched both the bin2hex( ) and random_bytes( ) functions before I posted.

Now, although it is true that I am running locally at PHP 5+, SwiftMailer is not installed on my machine.

Any other ideas?

Roddy

p.s.  Here are just two of the many syntax errors that I can easily remember having corrected.

function ( ... ) : string { ... }

and

$variable = ??;

 

Edited by iwato

Share this post


Link to post
Share on other sites

The error message you showed earlier "Call to undefined function random_bytes()" is an indication that the code was not running on a server with PHP 7.

Can you show me the file where those syntax errors are? They're not in the initial code you posted.

  • Thanks 1

Share this post


Link to post
Share on other sites

The syntax errors that I showed you have been corrected file after file after file.  Were I to retrace the errors I would have to reinstall the software and start all over again.

It were as if the authors were trying to show the painstaking work that they went through to create the software, or alternatively all of the different ways that it could be adapted to specific needs.  This is what I meant when I said it is software designed for professionals.

Roddy

Edited by iwato

Share this post


Link to post
Share on other sites

I have now edited close to a dozen files removing extraneous code with each new fatal error.

You what now?  You're going through a project and deleting code?  Who has judged the code to be extraneous, is it you?  Do you have examples of the things you've decided to remove, just so I know what the situation is?

Really though, Ingolme is right:

It were as if the authors purposely sabotaged their software for non-professionals.

No developer has the time or motivation to do anything like this.  This is not the goal of any professional.  But for some reason you've decided to need to delete pieces of code from the project, and do you still expect the project to work the same way?  I don't get it.  It's a weird attitude to take, "this developer is sabotaging their own project so I'm going to delete a bunch of their code and still try to use their software."

Now, your error about an undefined function has a single possible reason: you're not using PHP 7, as Ingolme pointed out.  That function is part of the CSPRNG extension, and this is the installation page for it:

http://php.net/manual/en/csprng.installation.php

As you can see, they are part of the PHP core.  So, if they are not defined in your environment, then the only possibility is that you are not using PHP 7.

But really, the bigger question is why you're trying to go through and edit a project's code in general.  You do not have to do that.  If it doesn't work, it's not because their code is bad, it's probably your environment.  You specifically should never need to alter the code of any project you're trying to use, as long as it works in your environment.  Trying to do that means that you have taken yourself out of the upgrade loop, you can't upgrade that software to fix bugs without going through and doing all of your changes again, and if you didn't document everything you did then that's going to cause problems.  Hopefully you're not just planning to never upgrade.

Share this post


Link to post
Share on other sites

OK.  I will uninstall and reinstall the software.  However, rather than editing the software as I did before and thereby  removing each and every fatal error that I encountered until PHP told that a PHP CORE function does not work.  I will let the mentorship of W3Schools have a look at the code and error first.

Indeed, what I expected to hear from y'all, I did not -- namely,  that the PHP error regarding the random_bytes() function is not the true source of the error, and that something in the preceding line of code is triggering it -- say a call to a faulty class that I had perhaps created during one of my many edits.

I am looking forward to a very interesting and perhaps even enlightening week with W3Schools for all concerned.

Roddy

Share this post


Link to post
Share on other sites

Indeed, what I expected to hear from y'all, I did not -- namely,  that the PHP error regarding the random_bytes() function is not the true source of the error, and that something in the preceding line of code is triggering it -- say a call to a faulty class that I had perhaps created during one of my many edits.

If it says the function is undefined, it's trying to execute the function and it's not defined.  That's all there is to it.  There is no code that you can put before that which would delete the function so that it's no longer defined.  While it's possible to disable specific functions in PHP, there is no reason why a cryptographic random number generator would ever be disabled.  The problem is your version of PHP.  If you were running PHP 7 then that function would be defined.

That's about as simple as it gets, there's not much mystery here.

Share this post


Link to post
Share on other sites

It should be pretty easy to test that:

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

echo 'PHP version ' . PHP_VERSION . ', getting random bytes: ';
$bytes = random_bytes(8);

echo 'Got ' . strlen($bytes) . ' bytes';

 

Share this post


Link to post
Share on other sites

I have an easier and perhaps more informative way of achieving the same:

[...@vps ~]$ php -v
ea-php-cli Copyright 2017 cPanel, Inc.
PHP 7.2.12 (cli) (built: Nov 13 2018 21:12:16) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

Roddy

Share this post


Link to post
Share on other sites

You have successfully determined the version number of the version of PHP that runs in your shell on that server.  Now to determine the version that is configured with the web server.

Share this post


Link to post
Share on other sites

With your and Ingolme's insistence that the problem lies with the PHP version I have performed a little exploration and discovered that the bin folder path to the PHP binary file for my CRONTAB is set to PHP 5.6 and not to PHP 7.2 as are my primary domain and each of my add-on domains that employ PHP.  I have notified my webserver host in regard to this inconsistency and am waiting for their reply.

You see, Swiftmailer is used by CRON Scheduler to deliver notification about the successful execution of CRON jobs.  Is it not likely then that the same version of PHP is used to run both pieces of software?

In any case, I have already removed Swiftmailer and am in the process of reinstalling it.  Further, changing the version of PHP should have no effect on the defective PHP code that I was compelled to modify in order to get SwiftMailer to even execute up to the point where the random_bytes( ) CORE function was needed.

Hopefully, I will be back in a short while with the first problematic piece of SwiftMailer code.

Roddy

 

Share this post


Link to post
Share on other sites

You see, Swiftmailer is used by CRON Scheduler to deliver notification about the successful execution of CRON jobs.

That's kind of a strange setup, to use middleware like that when it's not necessary.  Cron can (and does) send things directly to the mail server (assuming there is a local mail exchanger).  If any of my cron jobs send output, I get that emailed to me.  PHP is not used in that process, I'm not sure why your server adds it.

Is it not likely then that the same version of PHP is used to run both pieces of software?

What both pieces?  Swiftmailer and what else?

Further, changing the version of PHP should have no effect on the defective PHP code that I was compelled to modify in order to get SwiftMailer to even execute up to the point where the random_bytes( ) CORE function was needed.

What's your basis for that statement?  There are many functional changes in PHP 7, why would changing the version have no effect on how the code runs (if there were no changes, why would there be a newer version)?  You're asserting that Swiftmailer contains "defective" code, why are you making that assertion?  It seems like a fairly mature project, so why are you assuming that a mature project contains "defective" code?  What code are you specifically talking about?  The library is here, identify the "defective" code by file and line number and tell me why you think it's "defective."  Note also the description of the project:

Swift Mailer is a component based mailing solution for PHP 7.

The only thing that you should be compelled to do is run that in the environment for which it was designed.  If you need to remove any code at all, there's something wrong on your end.  Consider that the vast majority of users do not need to alter or remove any code.  This is not to say the project has no bugs or issues, I'm just trying to point out that any software project worth any weight will not release a version that simply doesn't work.  There is no reason to do so, you keep working until you can at least release something that works.

Share this post


Link to post
Share on other sites

OK.  I had lunch in between, but here it is:  the first reported error.

    public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher, $localDomain = '127.0.0.1', Swift_AddressEncoder $addressEncoder = null)
    {
        $this->buffer = $buf;
        $this->eventDispatcher = $dispatcher;
        $this->addressEncoder = $addressEncoder ?? new Swift_AddressEncoder_IdnAddressEncoder();
        $this->setLocalDomain($localDomain);
    }

The line indicated by PHP as the source of error.

$this->addressEncoder = $addressEncoder ?? new Swift_AddressEncoder_IdnAddressEncoder();

If you need the error message, here is that as well.

[19-Nov-2018 21:38:02 UTC] PHP Parse error:  syntax error, unexpected '?' in /home/thege0/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/AbstractSmtpTransport.php on line 53

Roddy

p.s.  The software was downloaded and installed with no version requirement using the following command.  If I remember correctly, in the absence of a version number Composer provides automatically the most recent stable version.

[...@vps ~]$ php composer.phar require swiftmailer/swiftmailer

 

Edited by iwato

Share this post


Link to post
Share on other sites
Quote

The software was downloaded and installed with no version requirement using the following command. 

I may be wrong, but I don't know if composer has the capability of determining whether an installed version of PHP meets a package's minimum requirements for the PHP version.  It would be up to you to verify that.  Even if it does have that capability, you have already demonstrated that you are running version 7.x in your shell, which is what composer would check.  And again, don't assume that PHP running in a shell (especially on a cPanel server) is the same version that you're running in the web server.  We run multiple cPanel servers, and I can set any version to work on the shell and any version to work on any account, independently.  Don't assume, verify.  You do that by creating a web page to tell you what's going on and running it through the web server.

The line of code you are showing uses the null coalesce operator, which naturally was added in PHP 7.  You are getting a syntax error because your version of PHP does not recognize that a question mark can follow another question mark.  That grammar rule was added with the new operator in PHP 7.

PHP 7 adds so many new syntax items that you can expect a lot of syntax errors if you try to get an earlier version of PHP to interpret code that targets PHP 7.  The spaceship operator (<=>) and the Elvis operator (?:) are other ones that come to mind, if you write code with "<=>" in it, PHP 5 is going to tell you it's a syntax error.  PHP 7 does not.

There's nothing defective about the code.  Not only is that a valid operator in PHP 7, but that's exactly the case where you would see it used.  No one is sabotaging any projects, no one is trying to make things confusing for non-professionals.  They are just targeting the most recent version of PHP, and considering the improvements that were made to PHP 7, I don't blame them.

The solution isn't to blame the project developers or get snarky with the people trying to help you, just understand the environment you're using and the requirements for what you're downloading.  Ingolme was right - the only blame for this issue is on your end for not understanding your own environment.

If you find yourself needing to rewrite large pieces of a popular piece of software, you should ask yourself why.  The answer is almost never because they have released broken software that everyone else manages to still use somehow.

Share this post


Link to post
Share on other sites

I'm curious about your statement about removing extraneous code.  What would you do in that particular situation?  Deleting one question mark wouldn't be enough, I'm curious about what you decided to do.  If you had searched for something like "php ?? operator" then you would have gotten results indicating that it was added in PHP 7, and that should have been enough for you.  You could also have seen that the equivalent line would be this:

$this->addressEncoder = !empty($addressEncoder) ? $addressEncoder : new Swift_AddressEncoder_IdnAddressEncoder();

That's how you would convert that line to the PHP 5 equivalent, but in order to know that you would have needed to look up what that operator does, and if you did that then you would have learned that it was only available in PHP 7.  But, if you didn't do that, and you replace that line with anything other than the above, then you would have broken the software, it wouldn't have been doing what it needed to do (without errors or warnings, anyway).  So I'm curious what your solution to that error would have been.

Share this post


Link to post
Share on other sites

 

$this->addressEncoder = !empty($addressEncoder) ? $addressEncoder : new Swift_AddressEncoder_IdnAddressEncoder();

This is exactly what I did do and moved onto the next error and correction, but before going any further let us wait until I have clarified with my host provider why all of my CRONTAB jobs are pointed to 5.6 and my default and domain settings are pointing to 7.2.  This now appears to be the likely source of the problem, for after all, the scheduler.php that I set up depends on the CRONTAB for its existence.

Roddy

Edited by iwato

Share this post


Link to post
Share on other sites

Yeah, you need to correct the version number.  The solution to this issue is not to change any code.  In fact, it is hardly ever the solution when you download a popular package and then find yourself needing to change a bunch of their code.  If you ever find yourself in that solution, stop and consider the problem.  Determine what assumptions you're making and test them.

How did you know how to change that line?  Did you look up the operator?

Share this post


Link to post
Share on other sites

No,  I did not look up the operator, for it made no sense.  What did make sense was a typo.  Then,  I looked at the surrounding code and tried to understand what was going on.  After which I played with it until I found something that made sense and PHP no longer complained.  I made a dozen other similar corrections on a variety of pages before I finally stumbled with PHP rejecting its own CORE function.

Roddy

Share this post


Link to post
Share on other sites
Quote

No,  I did not look up the operator, for it made no sense.  What did make sense was a typo.

How do you reconcile that with the admission that you're not a professional programmer?  You'll say that you're not a professional programmer, but then you'll download a project, see some error messages, and assume that it's a bunch of broken code that you need to fix.  Why is that the assumption you make, why isn't the assumption that the code isn't being executed correctly for whatever reason?

One of the most important things I learned throughout school was how to do research.  I understand programming theory in general, but I don't try to memorize all of the details of every language.  When I run into something that I don't recognize or don't understand it's about doing research to figure things out.  Programmers learn pretty quickly that assumptions are probably one of the biggest sources of errors.  During the initial computer science courses when I tried to complete an assignment in C but it wasn't working, 100% of the time I would eventually figure out my problem and get it fixed, and I learned the process to debug.  It starts by not making assumptions, and verifying everything.  With every bug, I would find out that one of the variables or something wasn't what I assumed it was, and then it was just working backwards to find the source.

I guess I'm saying that the answers are out there.  Just because it doesn't make sense to you, doesn't mean it doesn't make sense.  Keep in mind that you, or anyone else, don't know everything, we're always learning.  Not only that, but programming language grammars are always changing, things are getting added or removed.  Just be careful about the assumptions you're making with programming because incorrect assumptions can waste a lot of time.

Share this post


Link to post
Share on other sites

Just to be clear - if you had posted the initial error message and that line of code, the issue with PHP 7 would have been pointed out then and you would not have spent all of the time changing code until finding a function that was only introduced in PHP 7.

Share this post


Link to post
Share on other sites

This reminds me of whatever reason of this scene:

https://www.youtube.com/watch?v=G67eMq1YwmI

Like Occam tried to explain, often the simple answer is correct.  If a function is not defined in your environment, and it was introduced in a certain version, and you assume you're using that version, then your assumption is probably wrong.  It's not a question of other code doing something mysterious, it's just the wrong version.  And, like you've learned, don't assume that something you see in one environment (a console shell) is the same as in another environment (a web server).

  • Thanks 1

Share this post


Link to post
Share on other sites
Quote

Why is that the assumption you make, why isn't the assumption that the code isn't being executed correctly for whatever reason?

The reason for this assumption is that I have run into a lot of junk in my brief two years of coding.

In any case, the problem has been resolved by resetting the PHP bin path for my server's CRONTAB.  Further, my first scheduled email using Peppe Occhi's CRON Scheduler has been received along with the output for my newly installed CRON Scheduler CRON job.

Finally, a credit on the Grammar Captive webpage has been posted for both Peppe Occhi and Symphony under the headings CRON Scheduler and SwiftMailer in the CREDITS section of the Grammar Captive mainpage.

Hooray!  Hooray!

Once again, thank you W3Schools.

Roddy

Edited by iwato

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×