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

namespace XF\Service\AddOnArchive;

use
XF\Service\AbstractService;
use
XF\Util\File;

// ######## NOTE: SIMILARITY TO XF CORE UPGRADER CODE ############
// Much of this code is similar to the XFUpgraderExtractor class in src/XF/Install/_upgrader/core.php.
// Changes should be mirrored when appropriate.

use function is_array;

class
Extractor extends AbstractService
{
    protected
$addOnId;

    protected
$fileName;

   
/**
     * @var \ZipArchive|null
     */
   
protected $_zip;

    public function
__construct(\XF\App $app, $addOnId, $fileName)
    {
       
parent::__construct($app);

       
$this->addOnId = $addOnId;
       
$this->fileName = $fileName;
    }

    public function
open()
    {
        if (!
$this->_zip)
        {
           
$zip = new \ZipArchive();
           
$openResult = $zip->open($this->fileName);
            if (
$openResult !== true)
            {
                throw new \
LogicException("File could not be opened as a zip ($openResult)");
            }

           
$addOnJson = $zip->locateName($this->getZipAddOnRootDir() . "/addon.json");
            if (
$addOnJson === false)
            {
                throw new \
LogicException("Zip isn't an add-on");
            }

           
$this->_zip = $zip;
        }

        return
true;
    }

    protected function
zip()
    {
       
$this->open();
        return
$this->_zip;
    }

    public function
compareHashes(array $existingHashes)
    {
       
$newHashes = $this->getNewHashes();
        if (!
$newHashes)
        {
            return
null;
        }

       
$changes = [];
        foreach (
$newHashes AS $file => $newHash)
        {
            if (!isset(
$existingHashes[$file]))
            {
               
$changes[$file] = 'create';
            }
            else if (
$newHash !== $existingHashes[$file])
            {
               
$changes[$file] = 'update';
            }
        }

       
$changes[preg_replace('#^upload/#', '', $this->getHashFileName())] = 'update';

        foreach (
$existingHashes AS $oldFile => $null)
        {
            if (!isset(
$newHashes[$oldFile]))
            {
               
$changes[$oldFile] = 'delete';
            }
        }

        return
$changes;
    }

    public function
checkWritable(array $changeset = null, &$failures = [])
    {
       
$zip = $this->zip();
       
$failures = [];

        for (
$i = 0; $i < $zip->numFiles; $i++)
        {
           
$zipFileName = $zip->getNameIndex($i);
           
$fsFileName = $this->getFsFileNameFromZipName($zipFileName);
            if (
$fsFileName === null)
            {
                continue;
            }

            if (
is_array($changeset) && !isset($changeset[$fsFileName]))
            {
               
// we're not changing this file
               
continue;
            }

            if (!
File::isWritable($this->getFinalFsFileName($fsFileName)))
            {
               
$failures[] = $fsFileName;
            }
        }

        return
$failures ? false : true;
    }

    public function
copyFiles(array $changeset = null, $start = 0, \XF\Timer $timer = null)
    {
       
$zip = $this->zip();
       
$lastComplete = $start;

        for (
$i = $start; $i < $zip->numFiles; $i++)
        {
           
$lastComplete = $i;

           
$zipFileName = $zip->getNameIndex($i);
           
$fsFileName = $this->getFsFileNameFromZipName($zipFileName);
            if (
$fsFileName === null)
            {
                continue;
            }

            if (
is_array($changeset) && !isset($changeset[$fsFileName]))
            {
               
// we're not changing this file
               
continue;
            }

           
$finalFileName = $this->getFinalFsFileName($fsFileName);

           
$dataStream = $zip->getStream($zipFileName);
           
$targetWritten = @File::writeFile($finalFileName, $dataStream, false);

            if (!
$targetWritten)
            {
                return [
                   
'status' => 'error',
                   
'error' => "Failed write to {$fsFileName}"
               
];
            }

            if (
$timer && $timer->limitExceeded())
            {
                break;
            }
        }

       
$complete = ($i >= $zip->numFiles);

        if (
$complete)
        {
           
// if we don't have a new hashes file, we need to remove the old one if it exists as it will be wrong
           
$hashZipFileName = $this->getHashFileName();
            if (
$zip->locateName($hashZipFileName) === false)
            {
               
$fsFileName = $this->getFsFileNameFromZipName($hashZipFileName);
               
$finalFileName = $this->getFinalFsFileName($fsFileName);
                if (
file_exists($finalFileName) && !@unlink($finalFileName))
                {
                    return [
                       
'status' => 'error',
                       
'error' => "Failed write to {$fsFileName}"
                   
];
                }
            }
        }

        return [
           
'status' => ($complete ? 'complete' : 'incomplete'),
           
'last' => $lastComplete
       
];
    }

    protected function
getNewHashes()
    {
       
$newHashesJson = $this->zip()->getFromName($this->getHashFileName());
        if (!
$newHashesJson)
        {
            return
null;
        }

        return
json_decode($newHashesJson, true);
    }

    protected function
getFsFileNameFromZipName($fileName)
    {
        if (
substr($fileName, -1) === '/')
        {
           
// this is a directory we can just skip this
           
return null;
        }

        if (!
preg_match("#^upload/.#", $fileName))
        {
           
// file outside of "upload" so we can just skip this
           
return null;
        }

        return
substr($fileName, 7); // remove "upload/"
   
}

    protected function
getFinalFsFileName($fileName)
    {
        return \
XF::getRootDirectory() . \XF::$DS . $fileName;
    }

    protected function
getHashFileName()
    {
        return
$this->getZipAddOnRootDir() . "/hashes.json";
    }

    protected function
getZipAddOnRootDir()
    {
        return
"upload/src/addons/{$this->addOnId}";
    }
}