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

namespace XF;

use
XF\Mvc\Entity\Entity;
use
XF\Util\File;
use
XF\Util\Json;

class
DesignerOutput
{
    protected
$enabled;
    protected
$basePath;

    protected
$assetsFilename = 'assets.json';

    protected
$metadataFilename = '_metadata.json';
    protected
$metadataCache = [];

    protected
$batchMode = false;
    protected
$batchesPending = [];

    protected
$handlerCache = [];

    public function
__construct($enabled, $basePath)
    {
       
$this->enabled = $enabled;
       
$this->basePath = \XF\Util\File::canonicalizePath($basePath);
    }

    public function
hasNameChange(Entity $entity)
    {
       
$handler = $this->getHandler($entity->structure()->shortName);
        return
$handler->hasNameChange($entity);
    }

    public function
export(Entity $entity)
    {
       
$handler = $this->getHandler($entity->structure()->shortName);
        return
$handler->export($entity);
    }

    public function
import($shortName, $name, $styleId, $contents, array $metadata, array $options = [])
    {
       
$handler = $this->getHandler($shortName);
        return
$handler->import($name, $styleId, $contents, $metadata, $options);
    }

    public function
delete(Entity $entity, $new = true)
    {
       
$handler = $this->getHandler($entity->structure()->shortName);
        return
$handler->delete($entity, $new);
    }

   
/**
     * @param $shortName
     *
     * @return \XF\DesignerOutput\AbstractHandler
     */
   
public function getHandler($shortName)
    {
        if (isset(
$this->handlerCache[$shortName]))
        {
            return
$this->handlerCache[$shortName];
        }

       
$class = \XF::stringToClass($shortName, '%s\DesignerOutput\%s');
        if (!
class_exists($class))
        {
            throw new \
InvalidArgumentException("No handler for $shortName found ($class expected)");
        }

       
$class = \XF::extendClass($class);

       
$handler = new $class($this, $shortName);
        if (!(
$handler instanceof \XF\DesignerOutput\AbstractHandler))
        {
            throw new \
InvalidArgumentException("Cannot instantiate development output type handler for class '$class'");
        }
       
$this->handlerCache[$shortName] = $handler;

        return
$handler;
    }

    public function
enableBatchMode()
    {
       
$this->batchMode = true;
    }

    public function
clearBatchMode()
    {
       
$this->flushBatches();
       
$this->batchMode = false;
    }

    public function
flushBatches()
    {
        foreach (
$this->batchesPending AS $typeDir => $designerModeIds)
        {
            foreach (
$designerModeIds AS $designerModeId => $null)
            {
               
$typeMetadata = $this->metadataCache[$typeDir][$designerModeId];

               
$metadataPath = $this->getMetadataFileName($typeDir, $designerModeId);
               
file_put_contents($metadataPath, Json::jsonEncodePretty($typeMetadata));
            }
        }

       
$this->batchesPending = [];
    }

    protected function
isValidDir(\DirectoryIterator $entry)
    {
       
/** @var \DirectoryIterator $entry */
       
if (!$entry->isDir()
            ||
$entry->isDot()
            || !
preg_match('/^[a-z0-9_]+$/i', $entry->getBasename())
        )
        {
            return
false;
        }

        return
true;
    }

    public function
getAvailableDesignerModeIds()
    {
        if (!
file_exists($this->basePath))
        {
            return [];
        }

       
$designerModeIds = [];
        foreach (new \
DirectoryIterator($this->basePath) AS $entry)
        {
            if (!
$this->isValidDir($entry))
            {
                continue;
            }

           
$designerModeIds[] = $entry->getBasename();
        }

        return
$designerModeIds;
    }

    public function
getMetadata($typeDir, $designerMode)
    {
        if (isset(
$this->metadataCache[$typeDir][$designerMode]))
        {
            return
$this->metadataCache[$typeDir][$designerMode];
        }

       
$metadataPath = $this->getMetadataFileName($typeDir, $designerMode);
        if (
file_exists($metadataPath))
        {
           
$metadata = json_decode(file_get_contents($metadataPath), true);
        }
        else
        {
           
$metadata = [];
        }

       
$this->metadataCache[$typeDir][$designerMode] = $metadata;

        return
$metadata;
    }

    protected function
getMetadataFileName($typeDir, $designerMode)
    {
       
$ds = \XF::$DS;
        return
"{$this->basePath}{$ds}{$designerMode}{$ds}{$typeDir}{$ds}{$this->metadataFilename}";
    }

    protected function
writeTypeMetadata($typeDir, $designerMode, array $typeMetadata)
    {
       
ksort($typeMetadata);

       
$this->metadataCache[$typeDir][$designerMode] = $typeMetadata;

       
$metadataPath = $this->getMetadataFileName($typeDir, $designerMode);
       
file_put_contents($metadataPath, Json::jsonEncodePretty($typeMetadata));

        if (
$this->batchMode)
        {
           
$this->batchesPending[$typeDir][$designerMode] = true;
        }
        else
        {
           
$metadataPath = $this->getMetadataFileName($typeDir, $designerMode);
           
file_put_contents($metadataPath, Json::jsonEncodePretty($typeMetadata));
        }
    }

    protected function
updateMetadata($typeDir, $designerMode, $fileName, array $metadata)
    {
       
$typeMetadata = $this->getMetadata($typeDir, $designerMode);

       
$existing = $typeMetadata[$fileName] ?? null;
        if (
$existing == $metadata)
        {
            return;
        }

       
$typeMetadata[$fileName] = $metadata;
       
$this->writeTypeMetadata($typeDir, $designerMode, $typeMetadata);
    }

    public function
getTypes()
    {
       
$types = [];
       
$ds = \XF::$DS;
       
$dir = $this->basePath;

       
$designerModeIds = $this->getAvailableDesignerModeIds();
        foreach (
$designerModeIds AS $designerModeId)
        {
           
$designerModeDir = "{$dir}{$ds}{$designerModeId}";
            if (!
file_exists($designerModeDir))
            {
                continue;
            }
            foreach (new \
DirectoryIterator($designerModeDir) AS $designerModeEntry)
            {
                if (!
$this->isValidDir($designerModeEntry))
                {
                    continue;
                }

               
$types[] = $designerModeEntry->getBasename();
            }
        }

       
$types = array_unique($types);
       
sort($types);
        return
$types;
    }

    public function
getAvailableTypeFiles($typeDir, $designerMode)
    {
       
$files = [];

       
$ds = \XF::$DS;
       
$dir = $this->basePath;

       
$designerModeTypeDir = "{$dir}{$ds}{$designerMode}{$ds}{$typeDir}";
        if (
file_exists($designerModeTypeDir))
        {
            foreach (new \
DirectoryIterator($designerModeTypeDir) AS $file)
            {
               
/** @var \DirectoryIterator $file */
               
$fileName = $file->getBasename();
                if (!
$file->isDot() && $file->isFile() && $fileName != $this->metadataFilename && substr($fileName, 0, 1) != '.')
                {
                   
$files[$fileName] = $file->getPathname();
                }
                else if (!
$file->isDot() && $file->isDir() && substr($fileName, 0, 1) != '.')
                {
                    foreach (new \
DirectoryIterator($file->getPathname()) AS $childFile)
                    {
                        if (!
$childFile->isDot() && $childFile->isFile())
                        {
                           
$files[$fileName . '/' . $childFile->getBasename()] = $childFile->getPathname();
                        }
                    }
                }
            }
        }

        return
$files;
    }

    public function
rebuildTypeMetadata($type, $designerMode)
    {
       
$changes = [];

       
$typeFiles = $this->getAvailableTypeFiles($type, $designerMode);

       
$metadata = $this->getMetadata($type, $designerMode);
       
$changed = false;

        foreach (
$typeFiles AS $fileName => $path)
        {
            if (isset(
$metadata[$fileName]))
            {
               
$data = $metadata[$fileName];
               
$hash = $this->hashContents(file_get_contents($path));
                if (!isset(
$data['hash']) || $hash != $data['hash'])
                {
                   
$metadata[$fileName]['hash'] = $hash;
                   
$changes[$designerMode][] = $fileName;
                   
$changed = true;
                }
            }
        }

        if (
$changed)
        {
           
$this->writeTypeMetadata($type, $designerMode, $metadata);
        }

        return
$changes;
    }

    public function
writeFile($typeDir, \XF\Entity\Style $style, $fileName, $fileContents, array $metadata = [], $verifyChange = true)
    {
        if (!
$this->isDesignerModeEnabled($style))
        {
            return
false;
        }

       
$fullPath = $this->getFilePath($typeDir, $style->designer_mode, $fileName);

        if (
$verifyChange)
        {
            if (!
file_exists($fullPath))
            {
               
$write = true;
            }
            else
            {
               
$write = file_get_contents($fullPath) != $fileContents;
            }
        }
        else
        {
           
$write = true;
        }

        if (
$write)
        {
           
File::writeFile($fullPath, $fileContents, false);
        }

       
$metadata['hash'] = $this->hashContents($fileContents);
       
$this->updateMetadata($typeDir, $style->designer_mode, $fileName, $metadata);

        return
true;
    }

    public function
removeMetadata($typeDir, $designerMode, $fileName)
    {
       
$typeMetadata = $this->getMetadata($typeDir, $designerMode);
        if (isset(
$typeMetadata[$fileName]))
        {
            unset(
$typeMetadata[$fileName]);

           
$this->writeTypeMetadata($typeDir, $designerMode, $typeMetadata);
        }
    }

    public function
getFilePath($typeDir, $designerMode, $fileName)
    {
       
$ds = \XF::$DS;
        return
"{$this->basePath}{$ds}{$designerMode}{$ds}{$typeDir}{$ds}{$fileName}";
    }

    public function
getDesignerModePath($id)
    {
       
$ds = \XF::$DS;
        return
"{$this->basePath}{$ds}{$id}";
    }

    public function
deleteFile($typeDir, \XF\Entity\Style $style, $fileName)
    {
        if (!
$this->enabled)
        {
            return
false;
        }

       
$fullPath = $this->getFilePath($typeDir, $style->designer_mode, $fileName);
        if (
file_exists($fullPath))
        {
           
unlink($fullPath);
        }

       
$this->removeMetadata($typeDir, $style->designer_mode, $fileName);

        return
true;
    }

    protected function
getAssetsFileName($designerMode)
    {
       
$ds = \XF::$DS;
        return
"{$this->basePath}{$ds}{$designerMode}{$ds}{$this->assetsFilename}";
    }

    public function
getAssets($designerMode)
    {
       
$assetsPath = $this->getAssetsFileName($designerMode);
        if (
file_exists($assetsPath))
        {
           
$assets = json_decode(file_get_contents($assetsPath), true);
        }
        else
        {
           
$assets = [];
        }

        return
$assets;
    }

    public function
rebuildAssetsFile(\XF\Entity\Style $style)
    {
        if (!
$this->isDesignerModeEnabled($style))
        {
            return
false;
        }

        return
$this->writeSpecialFile(
           
$style->designer_mode,
           
$this->assetsFilename,
           
Json::jsonEncodePretty($style->assets)
        );
    }

    public function
writeSpecialFile($designerMode, $fileName, $fileContents)
    {
        if (!
$this->enabled)
        {
            return
false;
        }

       
$fullPath = $this->getSpecialFilePath($designerMode, $fileName);
       
File::writeFile($fullPath, $fileContents, false);

        return
true;
    }

    public function
deleteSpecialFile($designerMode, $fileName)
    {
        if (!
$this->enabled)
        {
            return
false;
        }

       
$fullPath = $this->getSpecialFilePath($designerMode, $fileName);
        if (
file_exists($fullPath))
        {
           
unlink($fullPath);
        }

        return
true;
    }

    public function
getSpecialFilePath($designerMode, $fileName)
    {
       
$ds = \XF::$DS;
        return
"{$this->basePath}{$ds}{$designerMode}{$ds}{$fileName}";
    }

    public function
hashContents($contents)
    {
       
$contents = str_replace("\r", '', $contents);
        return
md5($contents);
    }

    public function
isEnabled()
    {
        return
$this->enabled;
    }

    protected function
isDesignerModeEnabled(\XF\Entity\Style $style)
    {
        return (
$style->designer_mode && $this->enabled);
    }
}