Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Service/ImageProxy.php
<?php

namespace XF\Service;

use function
defined, in_array;

class
ImageProxy extends AbstractService
{
    protected
$forceRefresh = false;
    protected
$maxConcurrent = 10;

   
/**
     * @var \XF\Repository\ImageProxy
     */
   
protected $proxyRepo;

    protected function
setup()
    {
       
$this->proxyRepo = $this->repository('XF:ImageProxy');
    }

    public function
forceRefresh($value = true)
    {
       
$this->forceRefresh = (bool)$value;
    }

    public function
isRefreshForced()
    {
        return
$this->forceRefresh;
    }

    public function
getImage($url)
    {
       
$image = $this->proxyRepo->getImageByUrl($url);
        if (
$image)
        {
            if (
$this->isRefreshRequired($image))
            {
               
$this->refetchImage($image);
            }
        }
        else
        {
           
// no image, if we can fetch an image, create it
           
if ($this->canFetchImage())
            {
               
$image = $this->fetchNewImage($url);
            }
        }

        return
$image;
    }

    protected function
isRefreshRequired(\XF\Entity\ImageProxy $image)
    {
        if (
$this->forceRefresh)
        {
            return
true;
        }

        return
$image->isRefreshRequired() && $this->canFetchImage();
    }

    public function
canFetchImage()
    {
        if (
$this->forceRefresh)
        {
            return
true;
        }

       
$active = $this->proxyRepo->getTotalActiveFetches();
        return (
$active < $this->maxConcurrent);
    }

    public function
fetchNewImage($url)
    {
       
/** @var \XF\Entity\ImageProxy $image */
       
$image = $this->em()->create('XF:ImageProxy');
       
$image->url = $url;
       
$image->pruned = true;
       
$image->is_processing = time(); // may have slept, need to set to now

       
try
        {
           
$image->save();
        }
        catch (\
XF\Db\Exception $e)
        {
           
// this is mostly a duplicate key issue
           
return null;
        }

       
$fetchResults = $this->fetchImageDataFromUrl($image->url);
       
$this->finalizeFromFetchResults($image, $fetchResults);

        return
$image;
    }

    public function
refetchImage(\XF\Entity\ImageProxy $image)
    {
       
$image->is_processing = time();
       
$image->save();

       
$fetchResults = $this->fetchImageDataFromUrl($image->url);
       
$this->finalizeFromFetchResults($image, $fetchResults);

        return
$image;
    }

    public function
testImageFetch($url)
    {
       
$results = $this->fetchImageDataFromUrl($url);
        if (
$results['dataFile'])
        {
            @
unlink($results['dataFile']);
           
$results['dataFile'] = null;
        }

        return
$results;
    }

    protected function
fetchImageDataFromUrl($url)
    {
       
$url = $this->proxyRepo->cleanUrlForFetch($url);
        if (!
preg_match('#^https?://#i', $url))
        {
            throw new \
InvalidArgumentException("URL must be http or https");
        }

       
$urlParts = @parse_url($url);

       
$validImage = false;
       
$fileName = !empty($urlParts['path']) ? basename($urlParts['path']) : null;
       
$mimeType = null;
       
$error = null;
       
$streamFile = \XF\Util\File::getTempDir() . '/' . strtr(md5($url) . '-' . uniqid(), '/\\.', '---') . '.temp';
       
$imageProxyMaxSize = $this->app->options()->imageProxyMaxSize * 1024;

        try
        {
           
$options = [
               
'headers' => [
                   
'Accept' => 'image/*,*/*;q=0.8'
               
]
            ];
           
$limits = [
               
'time' => 8,
               
'bytes' => $imageProxyMaxSize ?: -1
           
];
           
$response = $this->app->http()->reader()->getUntrusted($url, $limits, $streamFile, $options, $error);
        }
        catch (\
Exception $e)
        {
           
$response = null;
           
$error = $e->getMessage();
        }

        if (
$response)
        {
           
$response->getBody()->close();

            if (
$response->getStatusCode() == 200)
            {
               
$disposition = $response->getHeader('Content-Disposition');
                if (!empty(
$disposition) && preg_match('/filename=(\'|"|)(.+)\\1/siU', $disposition[0], $match))
                {
                   
$fileName = $match[2];
                }
                if (!
$fileName)
                {
                   
$fileName = 'image';
                }

               
$imageInfo = filesize($streamFile) ? @getimagesize($streamFile) : false;
                if (
$imageInfo)
                {
                   
$imageType = $imageInfo[2];

                   
$extension = \XF\Util\File::getFileExtension($fileName);
                   
$extensionMap = [
                       
IMAGETYPE_GIF => ['gif'],
                       
IMAGETYPE_JPEG => ['jpg', 'jpeg', 'jpe'],
                       
IMAGETYPE_PNG => ['png'],
                       
IMAGETYPE_ICO => ['ico']
                    ];

                    if (
defined('IMAGETYPE_WEBP'))
                    {
                       
$extensionMap[IMAGETYPE_WEBP] = ['webp'];
                    }

                    if (isset(
$extensionMap[$imageType]))
                    {
                       
$mimeType = $imageInfo['mime'];

                       
$validExtensions = $extensionMap[$imageType];
                        if (!
in_array($extension, $validExtensions))
                        {
                           
$extensionStart = strrpos($fileName, '.');
                           
$fileName = (
                               
$extensionStart
                                   
? substr($fileName, 0, $extensionStart)
                                    :
$fileName
                               
) . '.' . $validExtensions[0];
                        }

                       
$validImage = true;
                    }
                    else
                    {
                       
$error = \XF::phraseDeferred('image_is_invalid_type');
                    }
                }
                else
                {
                   
$error = \XF::phraseDeferred('file_not_an_image');
                }
            }
            else
            {
               
$error = \XF::phraseDeferred('received_unexpected_response_code_x_message_y', [
                   
'code' => $response->getStatusCode(),
                   
'message' => $response->getReasonPhrase()
                ]);
            }
        }

        if (!
$validImage)
        {
            @
unlink($streamFile);
        }

        return [
           
'valid' => $validImage,
           
'error' => $error,
           
'dataFile' => $validImage ? $streamFile : null,
           
'fileName' => $fileName,
           
'mimeType' => $mimeType
       
];
    }

    protected function
finalizeFromFetchResults(\XF\Entity\ImageProxy $image, array $fetchResults)
    {
       
$image->is_processing = 0;

        if (
$fetchResults['valid'])
        {
           
$fileHash = md5_file($fetchResults['dataFile']);
            if (
$image->file_hash === $fileHash)
            {
               
$saved = true;
            }
            else
            {
               
$newImagePath = $image->getAbstractedImagePath();
               
$saved = \XF\Util\File::copyFileToAbstractedPath(
                   
$fetchResults['dataFile'],
                   
$newImagePath
               
);
            }

            if (
$saved)
            {
               
$image->fetch_date = time();
               
$image->file_name = $fetchResults['fileName'];
               
$image->file_size = filesize($fetchResults['dataFile']);
               
$image->file_hash = $fileHash;
               
$image->mime_type = $fetchResults['mimeType'];
               
$image->pruned = false;
               
$image->failed_date = 0;
               
$image->fail_count = 0;
            }
            else
            {
               
$image->pruned = true;
            }

            @
unlink($fetchResults['dataFile']);
        }
        else
        {
           
$image->failed_date = time();
           
$image->fail_count++;
        }

       
$image->save();
    }
}