pinpie/pinpie

View on GitHub
src/Tags/Staticon.php

Summary

Maintainability
D
1 day
Test Coverage
<?php

namespace pinpie\pinpie\Tags;

use \pinpie\pinpie\PP as PP;


class Staticon extends Tag {
    public
        $dimensions = [],
        $fileExist = false,
        $gzip = false,
        $gzipLevel = 1,
        $minify = false,
        $minifiedPath = false,
        $minifiedURL = false,
        $staticHash = false,
        $staticPath = false,
        $staticType = false,
        $url = false;

    /**
     * Internal cache for any computed values like url
     * @var array
     */
    protected $c = [];

    public function __construct(PP $pinpie, $settings, $fulltag, $type, $placeholder, $template, $cachetime, $fullname, Tag $parentTag = null, $priority = 10000, $depth = 0) {
        parent::__construct($pinpie, $settings, $fulltag, $type, $placeholder, $template, $cachetime, $fullname, $parentTag, $priority, $depth);
        $this->pinpie->times[] = [microtime(true), 'static ' . $this->fulltag . ' construct started'];
        if (!isset($this->pinpie->inCa['static'])) {
            $this->pinpie->inCa['static'] = [];
        }
        $this->c = &$this->pinpie->inCa['static'];

        $this->prepareSettings();
        $this->prepareStaticPath();

        $this->filename = $this->getStaticPath();
        if ($this->filename === false) {
            $this->fileExist = false;
            $this->error('file not found');
            /* Required for relative paths or paths where file was not found.
             The HTML tag will be generated anyway based on $this->value value. */
            $this->filename = $this->value;
        } else {
            $this->fileExist = true;
        }

        $this->process();
        $this->url = $this->getStaticUrl();
    }

    /**
     * Checks and trims static path. Logs error if static path is empty.
     */
    protected function prepareStaticPath() {
        $this->staticPath = $this->value . (!empty($this->params) ? '?' . implode('&', $this->params) : '');
        if ($this->staticPath === '' OR $this->staticPath === false OR $this->staticPath === null) {
            $this->error($this->fulltag . ' static file path is empty');
        } else {
            if ($this->staticPath{0} !== '/') {
                $this->staticPath = rtrim($this->pinpie->url->path, '/') . '/' . $this->staticPath;
            }
        }
    }

    /**
     * Sets some settings to empty arrays if they are false or null.
     */
    protected function prepareSettings() {
        if (empty($this->settings['minify types'])) {
            $this->settings['minify types'] = [];
        }
        if (empty($this->settings['gzip types'])) {
            $this->settings['gzip types'] = [];
        }
        if (empty($this->settings['dimensions types'])) {
            $this->settings['dimensions types'] = [];
        }
    }

    public function getStaticHash() {
        return md5($this->filename . '*' . $this->filetime);
    }

    public function getStaticUrl() {
        if ($this->staticPath === '' OR $this->staticPath === false OR $this->staticPath === null) {
            return false;
        }
        if (!isset($this->c['getStaticUrl'])) {
            $this->c['getStaticUrl'] = [];
        }
        if (!isset($this->c['getStaticUrl'][$this->filename])) {
            if ($this->minify AND $this->minifiedURL) {
                $file = $this->minifiedURL;
            } else {
                $file = $this->staticPath;
            }
            $this->c['getStaticUrl'][$this->filename] = $this->getServer() . ($file[0] == '/' ? '' : '/') . $file;
        }
        return $this->c['getStaticUrl'][$this->filename];
    }


    protected function getStaticPath() {
        if (!isset($this->c['getStaticPath'])) {
            $this->c['getStaticPath'] = [];
        }
        if (isset($this->c['getStaticPath'][$this->staticPath])) {
            return $this->c['getStaticPath'][$this->staticPath];
        }
        $this->c['getStaticPath'][$this->staticPath] = $this->getStaticPathReal();
        return $this->c['getStaticPath'][$this->staticPath];
    }

    protected function getStaticPathReal() {
        $path = rtrim($this->settings['folder'], '/\\') . DIRECTORY_SEPARATOR . ltrim($this->staticPath, '/\\');
        $this->pinpie->times[] = [microtime(true), 'processing static path ' . $path];
        if ($this->settings['realpath check']) {
            $this->pinpie->times[] = [microtime(true), 'realpath check required'];
            $path = $this->pinpie->checkPathIsInFolder($path, $this->settings['folder']);
            if ($path === false) {
                $this->pinpie->times[] = [microtime(true), 'realpath check failed'];
            } else {
                $this->pinpie->times[] = [microtime(true), 'realpath check successful, realpath is ' . $path];
            }
        } else {
            $this->pinpie->times[] = [microtime(true), 'realpath check skipped'];
        }
        if ($path === false OR $path === null OR $path === '') {
            $this->pinpie->times[] = [microtime(true), 'path is empty'];
            return false;
        }
        if (!file_exists($path)) {
            // no such file
            $this->pinpie->times[] = [microtime(true), 'file not found at path ' . $path];
            return false;
        }
        $this->pinpie->times[] = [microtime(true), 'getStaticPathReal done'];
        return $path;
    }

    public function getServer() {
        if ($this->filename === false OR $this->filename === null OR $this->filename === '') {
            return false;
        }
        if (!isset($this->c['getServer'])) {
            $this->c['getServer'] = [];
        }
        if (isset($this->c['getServer'][$this->filename])) {
            return $this->c['getServer'][$this->filename];
        }
        if (empty($this->pinpie->conf->tags['%']['servers'])) {
            $this->url = '//' . $this->pinpie->conf->pinpie['site url'];
        } else {
            $a = abs(crc32($this->filename)) % count($this->pinpie->conf->tags['%']['servers']);
            $this->url = '//' . $this->pinpie->conf->tags['%']['servers'][$a];
        }
        $this->c['getServer'][$this->filename] = $this->url;

        return $this->url;
    }

    protected function checkAndRunGzip() {
        $r = false;
        if (!$this->checkMTime($this->filename, $this->filename . '.gz')) {
            $this->pinpie->times[] = [microtime(true), '#gzipping start ' . $this->filename];
            if (is_file($this->filename)) {
                $fp = fopen($this->filename, 'r');
                if ($fp !== false AND flock($fp, LOCK_EX | LOCK_NB)) {
                    $gz = gzopen($this->filename . '.gz', 'w' . (int)$this->gzipLevel);
                    if ($gz !== false) {
                        $size = filesize($this->filename);
                        if ($size) {
                            /* prevents warning about zero size */
                            $r = gzwrite($gz, fread($fp, $size));
                        } else {
                            /* dunno what to do: create empty .gz or do nothing? */
                        }
                    }
                    flock($fp, LOCK_UN);
                    fclose($fp);
                }
            }
            $this->pinpie->times[] = [microtime(true), '#gzipping done ' . $this->filename];
        }
        return $r;
    }

    protected function checkAndRunMinifier() {
        if (!$this->minify) {
            return false;
        }
        if (empty($this->settings['minify function'])) {
            return false;
        }
        $fp = fopen($this->filename, 'r');
        if (empty($fp)) {
            return false;
        }

        /* *
       * We can't lock file for writing, external minifiers like Yahoo YUI Compressor
         * or Google Closure Compiler will have no access to file in that case.
       * Locking file for reading will prevent file from any modifications.
       * We have only to attempt to lock it for writing, and switch back.
         * If the file is already locked for reading - writing lock will fail.
         * Success means the file is not locked for reading in another process.
         * */
        if (
            flock($fp, LOCK_EX) === false
            // Switching back to reading lock to make file readable by any external processes
            OR flock($fp, LOCK_SH) === false
        ) {
            return false;
        }
        // Calling user function, where minification is made
        $func = $this->settings['minify function'];
        $this->minifiedPath = $func($this);
        // Releasing lock
        flock($fp, LOCK_UN);
        fclose($fp);
        if (!$this->minifiedPath) {
            $this->pinpie->times[] = [microtime(true), '#minify func cancels use of min path by returning false ' . $this->filename];
            return false;
        }
        return $this->checkMTime($this->filename, $this->minifiedPath);
    }

    /** Return true if $older is older or equal than $newer.
     * @param $older
     * @param $newer
     * @return bool
     */
    protected function checkMTime($older, $newer) {
        $molder = $this->pinpie->filemtime($older);
        $mnewer = $this->pinpie->filemtime($newer);
        if ($molder !== false AND $mnewer !== false AND $molder <= $mnewer) {
            return true;
        }
        return false;
    }


    /**
     * Looks for minified version of the file in the static folder.
     */
    protected function getMinified() {
        $this->pinpie->times[] = [microtime(true), 'getMinified'];
        if (!isset($this->c['getMinified'])) {
            $this->c['getMinified'] = [];
        }
        if (isset($this->c['getMinified'][$this->staticPath])) {
            $this->minifiedURL = $this->c['getMinified'][$this->staticPath]['url'];
            $this->minifiedPath = $this->c['getMinified'][$this->staticPath]['path'];
        } else {
            $pi = pathinfo('/' . trim($this->staticPath, '/\\'));
            $this->minifiedURL = trim($pi['dirname'], '/\\') . '/min.' . $pi['basename'];
            $this->minifiedPath = $this->settings['folder'] . DIRECTORY_SEPARATOR . trim($this->minifiedURL, '/\\');
            if ($this->checkMTime($this->filename, $this->minifiedPath)) {
                $useminify = true;
            } else {
                $useminify = $this->checkAndRunMinifier();
                $this->pinpie->times[] = [microtime(true), 'checkAndRunMinifier done'];
            }
            if ($useminify) {
                // if minified path exitst - we need to change $this->filename to $this->minifiedPath because from now we will work with that path.
                $this->filename = $this->minifiedPath;
            }else {
                $this->minifiedURL = false;
            }
            $this->c['getMinified'][$this->staticPath]['url'] = $this->minifiedURL;
            $this->c['getMinified'][$this->staticPath]['path'] = $this->minifiedPath;
        }
        $this->pinpie->times[] = [microtime(true), 'getMinified done'];
    }


    public function getDimensions() {
        if (!isset($this->c['getDimensions'])) {
            $this->c['getDimensions'] = [];
        }
        if (!isset($this->c['getDimensions'][$this->filename])) {
            $this->c['getDimensions'][$this->filename] = $this->measureDimensions();
        }
        return $this->c['getDimensions'][$this->filename];
    }

    /**
     * @param $path
     * @return array|bool
     */
    public function measureDimensions() {
        if (empty($this->filename)) return false;
        $imginfo = getimagesize($this->filename);
        $r = [];
        $r['type'] = $imginfo['mime'];
        $r['width'] = $imginfo[0];
        $r['height'] = $imginfo[1];
        return $r;
    }

    public function getOutput() {
        $this->content = $this->getContent();
        //Apply template to tag content
        if (!empty($this->template)) {
            $this->output = $this->applyTemplate();
        } else {
            $this->output = $this->content;
        }
        if ($this->placeholder) {
            $this->varPut();
        }
        return $this->output;
    }

    public function getContent() {
        $this->pinpie->times[] = [microtime(true), 'getContent'];
        if (!empty($this->settings['draw function'])) {
            $this->content = $this->settings['draw function']($this);
            $this->pinpie->times[] = [microtime(true), 'drawn by func'];
            return $this->content;
        }
        if ($this->cachetime) {
            /* exclamation mark AKA cachetime = return path only */
            $this->content = $this->url . '?time=' . $this->staticHash;
        } else {
            $this->content = $this->draw();
        }
        $this->pinpie->times[] = [microtime(true), 'drawn'];
        if (!empty($this->template)) {
            if (isset($this->dimensions['width'])) {
                $this->varsLocal['width'][0][] = $this->dimensions['width'];
            }
            if (isset($this->dimensions['height'])) {
                $this->varsLocal['height'][0][] = $this->dimensions['height'];
            }
            $this->varsLocal['file path'][0][] = $this->filename;
            $this->varsLocal['time'][0][] = $this->filetime;
            $this->varsLocal['time getHash'][0][] = $this->staticHash;
            $this->varsLocal['url'][0][] = $this->url;
        }
        $this->pinpie->times[] = [microtime(true), 'getContent done'];
        return $this->content;
    }

    protected function draw() {
        if ($this->url !== false) {
            switch ($this->staticType) {
                case 'js':
                    return '<script type="text/javascript" src="' . $this->url . '?time=' . $this->staticHash . '"></script>';
                case 'css':
                    return '<link rel="stylesheet" type="text/css" href="' . $this->url . '?time=' . $this->staticHash . '">';
                case 'img':
                    return '<img src="' . $this->url . '?time=' . $this->staticHash . '"' . (isset($this->dimensions['width']) ? ' width="' . $this->dimensions['width'] . '"' : '') . (isset($this->dimensions['height']) ? ' height="' . $this->dimensions['height'] . '"' : '') . '>';
            }
        }
        if ($this->pinpie->conf->debug) {
            return $this->fulltag;
        }
        return '';
    }

    protected function process() {

        $this->staticType = $this->name;
        $this->minify = in_array($this->staticType, $this->settings['minify types']);
        $this->gzip = in_array($this->staticType, $this->settings['gzip types']);

        if (!$this->fileExist) {
            return null;
        }

        if ($this->minify) {
            $this->pinpie->times[] = [microtime(true), 'minification'];
            $this->getMinified();
            $this->pinpie->times[] = [microtime(true), 'minification done'];
        }

        if ($this->gzip) {
            $this->pinpie->times[] = [microtime(true), 'gzip'];
            $this->checkAndRunGzip();
            $this->pinpie->times[] = [microtime(true), 'gzip done'];
        }

        if (in_array($this->staticType, $this->settings['dimensions types'])) {
            $this->pinpie->times[] = [microtime(true), 'getDimensions'];
            $this->dimensions = $this->getDimensions();
            $this->pinpie->times[] = [microtime(true), 'getDimensions done'];
        }

        $this->filetime = $this->pinpie->filemtime($this->filename);
        $this->staticHash = $this->getStaticHash();
    }


}