Вход Регистрация
Файл: library/XenForo/Model/ImageProxy.php
Строк: 499
<?php

class XenForo_Model_ImageProxy extends XenForo_Model
{
    
/**
     * Fetches image info from the cache, or requests it if it is not available
     *
     * @param string $url
     * @param bool $forceRefresh If true, the image is always refreshed
     *
     * @return array
     *
     * @throws InvalidArgumentException
     */
    
public function getImage($url$forceRefresh false)
    {
        
$image $this->getImageByUrl($url);
        if (
$image)
        {
            if (
$forceRefresh)
            {
                
$this->_fetchAndCacheImage($url$image);
            }
            else
            {
                
$image $this->refreshImageIfRequired($image);
            }
        }
        else
        {
            
$image $this->_fetchAndCacheImage($url);
        }

        return 
$image;
    }

    
/**
     * Gets image info for an image known by ID
     *
     * @param integer $imageId
     *
     * @return array
     */
    
public function getImageById($imageId)
    {
        return 
$this->_getDb()->fetchRow('
            SELECT *
            FROM xf_image_proxy
            WHERE image_id = ?
        '
$imageId);
    }

    
/**
     * Gets the cached image for a URL
     *
     * @param string $url
     *
     * @return array
     */
    
public function getImageByUrl($url)
    {
        if (!
$url || !preg_match('#^https?://#i'$url))
        {
            throw new 
InvalidArgumentException('Invalid URL');
        }

        return 
$this->_getDb()->fetchRow('
            SELECT *
            FROM xf_image_proxy
            WHERE url_hash = ?
        '
md5($url));
    }

    
/**
     * Prepares an image for output
     *
     * @param array $image
     *
     * @return array
     */
    
public function prepareImage(array $image)
    {
        
$image['file_path'] = $this->getImagePath($image);
        
$image['use_file'] = file_exists($image['file_path']);
        
$image['refreshable'] = $this->_requiresRefetch($image);

        return 
$image;
    }

    
/**
     * @param array $images
     * @return array
     */
    
public function prepareImages(array $images)
    {
        foreach (
$images AS &$image)
        {
            
$image $this->prepareImage($image);
        }

        return 
$images;
    }

    
/**
     * Refreshes the image if required
     *
     * @param array $image
     *
     * @return array
     */
    
public function refreshImageIfRequired(array $image)
    {
        if (
$this->_requiresRefetch($image))
        {
            
$image $this->_fetchAndCacheImage($image['url'], $image);
        }

        return 
$image;
    }

    
/**
     * Logs an image view
     *
     * @param array $image
     *
     * @return bool
     */
    
public function logImageView(array $image)
    {
        if (empty(
$image['image_id']))
        {
            return 
false;
        }

        
$this->_getDb()->query('
            UPDATE xf_image_proxy SET
                views = views + 1,
                last_request_date = ?
            WHERE image_id = ?
        '
, array(XenForo_Application::$time$image['image_id']));

        return 
true;
    }

    
/**
     * Determines if a refresh is needed
     *
     * @param array $image
     *
     * @return bool
     */
    
protected function _requiresRefetch(array $image)
    {
        
$filePath $this->getImagePath($image);

        if (
$image['is_processing'] && XenForo_Application::$time $image['is_processing'] < 5)
        {
            if (
file_exists($filePath))
            {
                
// likely being refreshed
                
return false;
            }

            
sleep(- (XenForo_Application::$time $image['is_processing']));

            
$newImage $this->getImageByUrl($image['url']);
            if (
$newImage)
            {
                
$image $newImage;
            }
        }

        if (
$image['failed_date'] && $image['fail_count'])
        {
            
$nextCheck $this->_failedGetNextCheckDate($image['failed_date'], $image['fail_count']);
            return (
XenForo_Application::$time >= $nextCheck);
        }

        if (
$image['pruned'])
        {
            return 
true;
        }

        if (
XenForo_Application::getOptions()->imageCacheTTL)
        {
            if (
$image['fetch_date'] < XenForo_Application::$time 86400 XenForo_Application::getOptions()->imageCacheTTL)
            {
                return 
true;
            }
        }

        if (!
file_exists($filePath))
        {
            return 
true;
        }

        if (
XenForo_Application::getOptions()->imageCacheRefresh && !$image['fail_count'])
        {
            if (
$image['fetch_date'] < XenForo_Application::$time 86400 XenForo_Application::getOptions()->imageCacheRefresh)
            {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * Based on the last failure and the number of consecutive failures, determine
     * the next time we can refresh a failed image. After 10, we stop trying
     *
     * @param integer $failDate Last fail date
     * @param integer $failCount Total failures
     *
     * @return int
     */
    
protected function _failedGetNextCheckDate($failDate$failCount)
    {
        if (!
$failCount)
        {
            
// not failed - may need to check now
            
return XenForo_Application::$time;
        }

        if (
$failCount 10)
        {
            
// too many failures, always in the future
            
return XenForo_Application::$time 86400;
        }

        switch (
$failCount)
        {
            case 
1$delay 60; break; // 1 minute
            
case 2$delay 60; break; // 5 minutes
            
case 3$delay 30 60; break; // 30 minutes
            
case 4$delay 3600; break; // 1 hour
            
case 5$delay 3600; break; // 6 hours

            
default:
                
$delay = ($failCount 5) * 86400// 1, 2, 3... days
        
}

        return 
$failDate $delay;
    }

    
/**
     * Fetches a remote image, stores it in the file system and records it in the database
     *
     * @param string $url
     * @param array|null $image
     *
     * @return array
     */
    
protected function _fetchAndCacheImage($url, array $image null)
    {
        
$urlHash md5($url);
        
$time XenForo_Application::$time;

        if (!
$image || empty($image['image_id']))
        {
            
$image = array(
                
'url' => $url,
                
'url_hash' => $urlHash,
                
'fetch_date' => $time,
                
'file_size' => 0,
                
'file_name' => '',
                
'mime_type' => '',
                
'views' => 0,
                
'first_request_date' => $time,
                
'last_request_date' => $time,
                
'pruned' => 1,
                
'failed_date' => 0,
                
'fail_count' => 0
            
);
        }

        
$image['is_processing'] = time(); // intentionally time() as we might have slept

        
$db $this->_getDb();

        if (empty(
$image['image_id']))
        {
            
$db->insert('xf_image_proxy'$image);
            
$image['image_id'] = $db->lastInsertId();
        }
        else
        {
            
$db->query("
                UPDATE xf_image_proxy
                SET is_processing = ?
                WHERE image_id = ?
            "
, array($image['is_processing'], $image['image_id']));
        }

        
$results $this->_fetchImageForProxy($url);
        
$requestFailed $results['failed'];
        
$streamFile $results['tempFile'];
        
$fileName $results['fileName'];
        
$mimeType $results['mimeType'];
        
$fileSize $results['fileSize'];

        if (!
$requestFailed)
        {
            
$filePath $this->getImagePath($image);
            
$dirName dirname($filePath);
            @
unlink($filePath);

            if (
XenForo_Helper_File::createDirectory($dirNametrue)
                && 
XenForo_Helper_File::safeRename($streamFile$filePath)
            )
            {
                
// ensure the filename fits -- if it's too long, take off from the beginning to keep extension
                
$length utf8_strlen($fileName);
                if (
$length 250)
                {
                    
$fileName utf8_substr($fileName$length 250);
                }

                
$data = array(
                    
'fetch_date' => time(),
                    
'file_size' => $fileSize,
                    
'file_name' => $fileName,
                    
'mime_type' => $mimeType,
                    
'pruned' => 0,
                    
'is_processing' => 0,
                    
'failed_date' => 0,
                    
'fail_count' => 0
                
);
                
$image array_merge($image$data);

                
$db->update('xf_image_proxy'$data'image_id = ' $db->quote($image['image_id']));
            }
        }

        @
unlink($streamFile);

        if (
$requestFailed)
        {
            
$data = array(
                
'is_processing' => 0,
                
'failed_date' => time(),
                
'fail_count' => $image['fail_count'] + 1
            
);
            
$image array_merge($image$data);

            
$db->update('xf_image_proxy'$data'image_id = ' $db->quote($image['image_id']));
        }

        return 
$image;
    }

    
/**
     * Does a test fetch for the specified image for debugging purposes.
     * The image will always be fetched and the temporary file will be removed.
     *
     * @param string $url
     *
     * @return array Associative array of information about the fetch
     */
    
public function testImageProxyFetch($url)
    {
        
$results $this->_fetchImageForProxy($url);
        @
unlink($results['tempFile']);
        unset(
$results['tempFile']);

        return 
$results;
    }

    
/**
     * Fetches the image at the specified URL using the standard proxy config.
     *
     * @param string $url
     *
     * @return array
     */
    
protected function _fetchImageForProxy($url)
    {
        
$urlHash md5($url);
        
$urlParts parse_url($url);

        
XenForo_ImageProxyStream::register();

        
// convert kilobytes to bytes
        
XenForo_ImageProxyStream::setMaxSize(XenForo_Application::getOptions()->imageProxyMaxSize 1024);

        
$streamUri 'xf-image-proxy://' $urlHash '-' uniqid();
        
$streamFile XenForo_ImageProxyStream::getTempFile($streamUri);

        
$requestFailed true;
        
$error false;
        
$imageMeta null;
        
$fileName = !empty($urlParts['path']) ? basename($urlParts['path']) : '';
        
$mimeType '';
        
$fileSize 0;
        
$image false;

        try
        {
            
$response XenForo_Helper_Http::getClient($url, array(
                
'output_stream' => $streamUri,
                
'timeout' => 10
            
))->setHeaders('Accept-encoding''identity')->request('GET');
            if (
$response->isSuccessful())
            {
                
$disposition $response->getHeader('Content-Disposition');
                if (
is_array($disposition))
                {
                    
$disposition end($disposition);
                }
                if (
$disposition && preg_match('/filename=('|"|)(.+)\1/siU', $disposition$match))
                {
                    
$fileName = $match[2];
                }
                if (!
$fileName)
                {
                    
$fileName = 'image';
                }

                
$mimeHeader = $response->getHeader('Content-Type');
                if (is_array(
$mimeHeader))
                {
                    
$mimeHeader = end($mimeHeader);
                }
                
$mimeType = $mimeHeader ? $mimeHeader : 'unknown/unknown';

                
$imageMeta = XenForo_ImageProxyStream::getMetaData($streamUri);
                if (!empty(
$imageMeta['error']))
                {
                    switch (
$imageMeta['error'])
                    {
                        case 'not_image':
                            
$error = new XenForo_Phrase('file_not_an_image');
                            break;

                        case 'too_large':
                            
$error = new XenForo_Phrase('file_is_too_large');
                            break;

                        case 'invalid_type':
                            
$error = new XenForo_Phrase('image_is_invalid_type');
                            break;

                        default:
                            
$error = $imageMeta['error'];
                    }
                }
                else
                {
                    
$requestFailed = false;
                    
$image = $imageMeta['image'];
                    
$mimeType = $image['mime'];
                    
$fileSize = $imageMeta['length'];

                    
$extension = XenForo_Helper_File::getFileExtension($fileName);
                    
$extensionMap = array(
                        IMAGETYPE_GIF => array('gif'),
                        IMAGETYPE_JPEG => array('jpg', 'jpeg', 'jpe'),
                        IMAGETYPE_PNG => array('png')
                    );
                    
$validExtensions = $extensionMap[$image[2]];
                    if (!in_array(
$extension$validExtensions))
                    {
                        
$extensionStart = strrpos($fileName, '.');
                        
$fileName = ($extensionStart ? substr($fileName, 0, $extensionStart) : $fileName) . '.' . $validExtensions[0];
                    }
                }
            }
            else
            {
                
$error = new XenForo_Phrase('received_unexpected_response_code_x_message_y', array(
                    'code' => 
$response->getStatus(),
                    'message' => 
$response->getMessage()
                ));
            }
        }
        catch (Exception 
$e)
        {
            
$error = $e->getMessage();
            
$response = null;
        }

        
$response = null;

        return array(
            'url' => 
$url,
            'failed' => 
$requestFailed,
            'error' => 
$error,
            'image' => 
$image,
            'fileName' => 
$fileName,
            'mimeType' => 
$mimeType,
            'fileSize' => 
$fileSize,
            'tempFile' => 
$streamFile
        );
    }

    /**
     * Deletes an image from the file system image cache
     *
     * @param array 
$image
     */
    protected function _deleteFile(array 
$image)
    {
        
$filePath = $this->getImagePath($image);

        @unlink(
$filePath);
    }

    /**
     * Gets the path to an image in the file system image cache
     *
     * @param array 
$image
     *
     * @return string
     */
    public function getImagePath(array 
$image)
    {
        return sprintf('%s/image_cache/%d/%d-%s.data',
            XenForo_Helper_File::getInternalDataPath(),
            floor(
$image['image_id'] / 1000),
            
$image['image_id'],
            
$image['url_hash']
        );
    }

    /**
     * Prunes images from the file system cache that have expired
     *
     * @param integer|null 
$pruneDate
     */
    public function pruneImageCache(
$pruneDate = null)
    {
        
$db = $this->_getDb();

        if (
$pruneDate === null)
        {
            if (!XenForo_Application::getOptions()->imageCacheTTL)
            {
                return;
            }

            
$pruneDate = XenForo_Application::$time - (86400 * XenForo_Application::getOptions()->imageCacheTTL);
        }

        
$images = $this->fetchAllKeyed('
            SELECT *
            FROM xf_image_proxy
            WHERE fetch_date < ?
                AND pruned = 0
        ', 'image_id', 
$pruneDate);

        if (
$images)
        {
            foreach (
$images AS $imageId => $image)
            {
                
$this->_deleteFile($image);
            }

            
$db->update('xf_image_proxy', array(
                'pruned' => 1
            ), 'image_id IN (' . 
$db->quote(array_keys($images)) . ')');
        }
    }

    /**
     * Prunes unused image proxy log entries.
     *
     * @param null|int 
$pruneDate
     *
     * @return int
     */
    public function pruneImageProxyLogs(
$pruneDate = null)
    {
        if (
$pruneDate === null)
        {
            
$options = XenForo_Application::getOptions();

            if (!
$options->imageLinkProxyLogLength)
            {
                return 0;
            }
            if (!
$options->imageCacheTTL)
            {
                // we're keeping images forever - can't prune
                return 0;
            }

            
$maxTtl = max($options->imageLinkProxyLogLength$options->imageCacheTTL);
            
$pruneDate = XenForo_Application::$time - (86400 * $maxTtl);
        }

        // we can only remove logs where we've pruned the image
        return 
$this->_getDb()->delete('xf_image_proxy',
            'pruned = 1 AND last_request_date < ' . intval(
$pruneDate)
        );
    }

    /**
     * Prepares a collection of image proxy fetching related conditions into an SQL clause
     *
     * @param array 
$conditions List of conditions
     * @param array 
$fetchOptions Modifiable set of fetch options (may have joins pushed on to it)
     *
     * @return string SQL clause (at least 1=1)
     */
    public function prepareImageProxyConditions(array 
$conditions, array &$fetchOptions)
    {
        
$sqlConditions = array();
        
$db = $this->_getDb();

        if (!empty(
$conditions['url']))
        {
            if (is_array(
$conditions['url']))
            {
                
$sqlConditions[] = 'image_proxy.url LIKE ' . XenForo_Db::quoteLike($conditions['url'][0], $conditions['url'][1], $db);
            }
            else
            {
                
$sqlConditions[] = 'image_proxy.url LIKE ' . XenForo_Db::quoteLike($conditions['url'], 'lr', $db);
            }
        }

        return 
$this->getConditionsForClause($sqlConditions);
    }

    /**
     * Fetches image proxy items for log display
     *
     * @param array 
$conditions
     * @param array 
$fetchOptions
     *
     * @return array
     */
    public function getImageProxyLogs(array 
$conditions = array(), array $fetchOptions = array())
    {
        
$limitOptions = $this->prepareLimitFetchOptions($fetchOptions);
        
$whereConditions = $this->prepareImageProxyConditions($conditions$fetchOptions);

        
$orderBy = 'last_request_date';
        if (!empty(
$fetchOptions['order']))
        {
            switch (
$fetchOptions['order'])
            {
                case 'last_request_date':
                case 'first_request_date':
                case 'views':
                case 'file_size':
                    
$orderBy = $fetchOptions['order'];
            }
        }

        return 
$this->fetchAllKeyed($this->limitQueryResults(
            "
                
SELECT image_proxy.*
                
FROM xf_image_proxy AS image_proxy
                WHERE $whereConditions
                ORDER BY image_proxy
.$orderBy DESC
            
", $limitOptions['limit'], $limitOptions['offset']
        ), 'image_id');
    }

    /**
     * Counts all image proxy items
     *
     * @param array 
$conditions
     *
     * @return integer
     */
    public function countImageProxyItems(array 
$conditions = array())
    {
        
$fetchOptions = array();
        
$whereConditions = $this->prepareImageProxyConditions($conditions$fetchOptions);

        return 
$this->_getDb()->fetchOne("
            
SELECT COUNT(*)
            
FROM xf_image_proxy AS image_proxy
            WHERE $whereConditions
        
");
    }

    /**
     * Gets the placeholder image fallback for errors.
     *
     * @return array
     */
    public function getPlaceHolderImage()
    {
        
$path = 'styles/default/xenforo/icons/missing-image.png';
        
$url = XenForo_Application::getOptions()->boardUrl . '/' . $path;
        
$filePath = XenForo_Application::getInstance()->getRootDir() . '/' . $path;
        
$lastModified = filemtime($filePath);

        return array(
            'url' => 
$url,
            'url_hash' => md5(
$url),
            'file_size' => filesize(
$filePath),
            'file_name' => 'missing-image.png',
            'mime_type' => 'image/png',
            'fetch_date' => 
$lastModified,
            'first_request_date' => 
$lastModified,
            'last_request_date' => XenForo_Application::
$time,
            'views' => 1,
            'pruned' => 0,
            'is_processing' => 0,
            'failed_date' => 0,
            'fail_count' => 0,
            'file_path' => 
$filePath,
            'use_file' => true
        );
    }
}
Онлайн: 1
Реклама