ï»¿ï»¿PK      A£ò\ô$Bö¼  ö¼  '  elFinderVolumeLocalFileSystem.class.phpnu W+A„¶        <?php

// Implement similar functionality in PHP 5.2 or 5.3
// http://php.net/manual/class.recursivecallbackfilteriterator.php#110974
if (!class_exists('RecursiveCallbackFilterIterator', false)) {
    class RecursiveCallbackFilterIterator extends RecursiveFilterIterator
    {
        private $callback;

        public function __construct(RecursiveIterator $iterator, $callback)
        {
            $this->callback = $callback;
            parent::__construct($iterator);
        }

        public function accept()
        {
            return call_user_func($this->callback, parent::current(), parent::key(), parent::getInnerIterator());
        }

        public function getChildren()
        {
            return new self($this->getInnerIterator()->getChildren(), $this->callback);
        }
    }
}

/**
 * elFinder driver for local filesystem.
 *
 * @author Dmitry (dio) Levashov
 * @author Troex Nevelin
 **/
class elFinderVolumeLocalFileSystem extends elFinderVolumeDriver
{

    /**
     * Driver id
     * Must be started from letter and contains [a-z0-9]
     * Used as part of volume id
     *
     * @var string
     **/
    protected $driverId = 'l';

    /**
     * Required to count total archive files size
     *
     * @var int
     **/
    protected $archiveSize = 0;

    /**
     * Is checking stat owner
     *
     * @var        boolean
     */
    protected $statOwner = false;

    /**
     * Path to quarantine directory
     *
     * @var string
     */
    private $quarantine;

    /**
     * Constructor
     * Extend options with required fields
     *
     * @author Dmitry (dio) Levashov
     */
    public function __construct()
    {
        $this->options['alias'] = '';              // alias to replace root dir name
        $this->options['dirMode'] = 0755;            // new dirs mode
        $this->options['fileMode'] = 0644;            // new files mode
        $this->options['rootCssClass'] = 'elfinder-navbar-root-local';
        $this->options['followSymLinks'] = true;
        $this->options['detectDirIcon'] = '';         // file name that is detected as a folder icon e.g. '.diricon.png'
        $this->options['keepTimestamp'] = array('copy', 'move'); // keep timestamp at inner filesystem allowed 'copy', 'move' and 'upload'
        $this->options['substituteImg'] = true;       // support substitute image with dim command
        $this->options['statCorrector'] = null;       // callable to correct stat data `function(&$stat, $path, $statOwner, $volumeDriveInstance){}`
        if (DIRECTORY_SEPARATOR === '/') {
            // Linux
            $this->options['acceptedName'] = '/^[^\.\/\x00][^\/\x00]*$/';
        } else {
            // Windows
            $this->options['acceptedName'] = '/^[^\.\/\x00\\\:*?"<>|][^\/\x00\\\:*?"<>|]*$/';
        }
    }

    /*********************************************************************/
    /*                        INIT AND CONFIGURE                         */
    /*********************************************************************/

    /**
     * Prepare driver before mount volume.
     * Return true if volume is ready.
     *
     * @return bool
     **/
    protected function init()
    {
        // Normalize directory separator for windows
        if (DIRECTORY_SEPARATOR !== '/') {
            foreach (array('path', 'tmbPath', 'tmpPath', 'quarantine') as $key) {
                if (!empty($this->options[$key])) {
                    $this->options[$key] = str_replace('/', DIRECTORY_SEPARATOR, $this->options[$key]);
                }
            }
            // PHP >= 7.1 Supports UTF-8 path on Windows
            if (version_compare(PHP_VERSION, '7.1', '>=')) {
                $this->options['encoding'] = '';
                $this->options['locale'] = '';
            }
        }
        if (!$cwd = getcwd()) {
            return $this->setError('elFinder LocalVolumeDriver requires a result of getcwd().');
        }
        // detect systemRoot
        if (!isset($this->options['systemRoot'])) {
            if ($cwd[0] === DIRECTORY_SEPARATOR || $this->root[0] === DIRECTORY_SEPARATOR) {
                $this->systemRoot = DIRECTORY_SEPARATOR;
            } else if (preg_match('/^([a-zA-Z]:' . preg_quote(DIRECTORY_SEPARATOR, '/') . ')/', $this->root, $m)) {
                $this->systemRoot = $m[1];
            } else if (preg_match('/^([a-zA-Z]:' . preg_quote(DIRECTORY_SEPARATOR, '/') . ')/', $cwd, $m)) {
                $this->systemRoot = $m[1];
            }
        }
        $this->root = $this->getFullPath($this->root, $cwd);
        if (!empty($this->options['startPath'])) {
            $this->options['startPath'] = $this->getFullPath($this->options['startPath'], $this->root);
        }

        if (is_null($this->options['syncChkAsTs'])) {
            $this->options['syncChkAsTs'] = true;
        }
        if (is_null($this->options['syncCheckFunc'])) {
            $this->options['syncCheckFunc'] = array($this, 'localFileSystemInotify');
        }
        // check 'statCorrector'
        if (empty($this->options['statCorrector']) || !is_callable($this->options['statCorrector'])) {
            $this->options['statCorrector'] = null;
        }

        return true;
    }

    /**
     * Configure after successfull mount.
     *
     * @return void
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov
     */
    protected function configure()
    {
        $hiddens = array();
        $root = $this->stat($this->root);

        // check thumbnails path
        if (!empty($this->options['tmbPath'])) {
            if (strpos($this->options['tmbPath'], DIRECTORY_SEPARATOR) === false) {
                $hiddens['tmb'] = $this->options['tmbPath'];
                $this->options['tmbPath'] = $this->_abspath($this->options['tmbPath']);
            } else {
                $this->options['tmbPath'] = $this->_normpath($this->options['tmbPath']);
            }
        }
        // check temp path
        if (!empty($this->options['tmpPath'])) {
            if (strpos($this->options['tmpPath'], DIRECTORY_SEPARATOR) === false) {
                $hiddens['temp'] = $this->options['tmpPath'];
                $this->options['tmpPath'] = $this->_abspath($this->options['tmpPath']);
            } else {
                $this->options['tmpPath'] = $this->_normpath($this->options['tmpPath']);
            }
        }
        // check quarantine path
        $_quarantine = '';
        if (!empty($this->options['quarantine'])) {
            if (strpos($this->options['quarantine'], DIRECTORY_SEPARATOR) === false) {
                $_quarantine = $this->_abspath($this->options['quarantine']);
                $this->options['quarantine'] = '';
            } else {
                $this->options['quarantine'] = $this->_normpath($this->options['quarantine']);
            }
        } else {
            $_quarantine = $this->_abspath('.quarantine');
        }
        is_dir($_quarantine) && self::localRmdirRecursive($_quarantine);

        parent::configure();

        // check tmbPath
        if (!$this->tmbPath && isset($hiddens['tmb'])) {
            unset($hiddens['tmb']);
        }

        // if no thumbnails url - try detect it
        if ($root['read'] && !$this->tmbURL && $this->URL) {
            if (strpos($this->tmbPath, $this->root) === 0) {
                $this->tmbURL = $this->URL . str_replace(DIRECTORY_SEPARATOR, '/', substr($this->tmbPath, strlen($this->root) + 1));
                if (preg_match("|[^/?&=]$|", $this->tmbURL)) {
                    $this->tmbURL .= '/';
                }
            }
        }

        // set $this->tmp by options['tmpPath']
        $this->tmp = '';
        if (!empty($this->options['tmpPath'])) {
            if ((is_dir($this->options['tmpPath']) || mkdir($this->options['tmpPath'], $this->options['dirMode'], true)) && is_writable($this->options['tmpPath'])) {
                $this->tmp = $this->options['tmpPath'];
            } else {
                if (isset($hiddens['temp'])) {
                    unset($hiddens['temp']);
                }
            }
        }
        if (!$this->tmp && ($tmp = elFinder::getStaticVar('commonTempPath'))) {
            $this->tmp = $tmp;
        }

        // check quarantine dir
        $this->quarantine = '';
        if (!empty($this->options['quarantine'])) {
            if ((is_dir($this->options['quarantine']) || mkdir($this->options['quarantine'], $this->options['dirMode'], true)) && is_writable($this->options['quarantine'])) {
                $this->quarantine = $this->options['quarantine'];
            } else {
                if (isset($hiddens['quarantine'])) {
                    unset($hiddens['quarantine']);
                }
            }
        } else if ($_path = elFinder::getCommonTempPath()) {
            $this->quarantine = $_path;
        }

        if (!$this->quarantine) {
            if (!$this->tmp) {
                $this->archivers['extract'] = array();
                $this->disabled[] = 'extract';
            } else {
                $this->quarantine = $this->tmp;
            }
        }

        if ($hiddens) {
            foreach ($hiddens as $hidden) {
                $this->attributes[] = array(
                    'pattern' => '~^' . preg_quote(DIRECTORY_SEPARATOR . $hidden, '~') . '$~',
                    'read' => false,
                    'write' => false,
                    'locked' => true,
                    'hidden' => true
                );
            }
        }

        if (!empty($this->options['keepTimestamp'])) {
            $this->options['keepTimestamp'] = array_flip($this->options['keepTimestamp']);
        }

        $this->statOwner = (!empty($this->options['statOwner']));

        // enable WinRemoveTailDots plugin on Windows server
        if (DIRECTORY_SEPARATOR !== '/') {
            if (!isset($this->options['plugin'])) {
                $this->options['plugin'] = array();
            }
            $this->options['plugin']['WinRemoveTailDots'] = array('enable' => true);
        }
    }

    /**
     * Long pooling sync checker
     * This function require server command `inotifywait`
     * If `inotifywait` need full path, Please add `define('ELFINER_INOTIFYWAIT_PATH', '/PATH_TO/inotifywait');` into connector.php
     *
     * @param string $path
     * @param int    $standby
     * @param number $compare
     *
     * @return number|bool
     * @throws elFinderAbortException
     */
    public function localFileSystemInotify($path, $standby, $compare)
    {
        if (isset($this->sessionCache['localFileSystemInotify_disable'])) {
            return false;
        }
        $path = realpath($path);
        $mtime = filemtime($path);
        if (!$mtime) {
            return false;
        }
        if ($mtime != $compare) {
            return $mtime;
        }
        $inotifywait = defined('ELFINER_INOTIFYWAIT_PATH') ? ELFINER_INOTIFYWAIT_PATH : 'inotifywait';
        $standby = max(1, intval($standby));
        $cmd = $inotifywait . ' ' . escapeshellarg($path) . ' -t ' . $standby . ' -e moved_to,moved_from,move,close_write,delete,delete_self';
        $this->procExec($cmd, $o, $r);
        if ($r === 0) {
            // changed
            clearstatcache();
            if (file_exists($path)) {
                $mtime = filemtime($path); // error on busy?
                return $mtime ? $mtime : time();
            } else {
                // target was removed
                return 0;
            }
        } else if ($r === 2) {
            // not changed (timeout)
            return $compare;
        }
        // error
        // cache to $_SESSION
        $this->sessionCache['localFileSystemInotify_disable'] = true;
        $this->session->set($this->id, $this->sessionCache);
        return false;
    }

    /*********************************************************************/
    /*                               FS API                              */
    /*********************************************************************/

    /*********************** paths/urls *************************/

    /**
     * Return parent directory path
     *
     * @param  string $path file path
     *
     * @return string
     * @author Dmitry (dio) Levashov
     **/
    protected function _dirname($path)
    {
        return dirname($path);
    }

    /**
     * Return file name
     *
     * @param  string $path file path
     *
     * @return string
     * @author Dmitry (dio) Levashov
     **/
    protected function _basename($path)
    {
        return basename($path);
    }

    /**
     * Join dir name and file name and retur full path
     *
     * @param  string $dir
     * @param  string $name
     *
     * @return string
     * @author Dmitry (dio) Levashov
     **/
    protected function _joinPath($dir, $name)
    {
        $dir = rtrim($dir, DIRECTORY_SEPARATOR);
        $path = realpath($dir . DIRECTORY_SEPARATOR . $name);
        // realpath() returns FALSE if the file does not exist
        if ($path === false || strpos($path, $this->root) !== 0) {
            if (DIRECTORY_SEPARATOR !== '/') {
                $dir = str_replace('/', DIRECTORY_SEPARATOR, $dir);
                $name = str_replace('/', DIRECTORY_SEPARATOR, $name);
            }
            // Directory traversal measures
            if (strpos($dir, '..' . DIRECTORY_SEPARATOR) !== false || substr($dir, -2) == '..') {
                $dir = $this->root;
            }
            if (strpos($name, '..' . DIRECTORY_SEPARATOR) !== false) {
                $name = basename($name);
            }
            $path = $dir . DIRECTORY_SEPARATOR . $name;
        }
        return $path; 
    }

    /**
     * Return normalized path, this works the same as os.path.normpath() in Python
     *
     * @param  string $path path
     *
     * @return string
     * @author Troex Nevelin
     **/
    protected function _normpath($path)
    {
        if (empty($path)) {
            return '.';
        }

        $changeSep = (DIRECTORY_SEPARATOR !== '/');
        if ($changeSep) {
            $drive = '';
            if (preg_match('/^([a-zA-Z]:)(.*)/', $path, $m)) {
                $drive = $m[1];
                $path = $m[2] ? $m[2] : '/';
            }
            $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
        }

        if (strpos($path, '/') === 0) {
            $initial_slashes = true;
        } else {
            $initial_slashes = false;
        }

        if (($initial_slashes)
            && (strpos($path, '//') === 0)
            && (strpos($path, '///') === false)) {
            $initial_slashes = 2;
        }

        $initial_slashes = (int)$initial_slashes;

        $comps = explode('/', $path);
        $new_comps = array();
        foreach ($comps as $comp) {
            if (in_array($comp, array('', '.'))) {
                continue;
            }

            if (($comp != '..')
                || (!$initial_slashes && !$new_comps)
                || ($new_comps && (end($new_comps) == '..'))) {
                array_push($new_comps, $comp);
            } elseif ($new_comps) {
                array_pop($new_comps);
            }
        }
        $comps = $new_comps;
        $path = implode('/', $comps);
        if ($initial_slashes) {
            $path = str_repeat('/', $initial_slashes) . $path;
        }

        if ($changeSep) {
            $path = $drive . str_replace('/', DIRECTORY_SEPARATOR, $path);
        }

        return $path ? $path : '.';
    }

    /**
     * Return file path related to root dir
     *
     * @param  string $path file path
     *
     * @return string
     * @author Dmitry (dio) Levashov
     **/
    protected function _relpath($path)
    {
        if ($path === $this->root) {
            return '';
        } else {
            if (strpos($path, $this->root) === 0) {
                return ltrim(substr($path, strlen($this->root)), DIRECTORY_SEPARATOR);
            } else {
                // for link
                return $path;
            }
        }
    }

    /**
     * Convert path related to root dir into real path
     *
     * @param  string $path file path
     *
     * @return string
     * @author Dmitry (dio) Levashov
     **/
    protected function _abspath($path)
    {
        if ($path === DIRECTORY_SEPARATOR) {
            return $this->root;
        } else {
            $path = $this->_normpath($path);
            if (strpos($path, $this->systemRoot) === 0) {
                return $path;
            } else if (DIRECTORY_SEPARATOR !== '/' && preg_match('/^[a-zA-Z]:' . preg_quote(DIRECTORY_SEPARATOR, '/') . '/', $path)) {
                return $path;
            } else {
                return $this->_joinPath($this->root, $path);
            }
        }
    }

    /**
     * Return fake path started from root dir
     *
     * @param  string $path file path
     *
     * @return string
     * @author Dmitry (dio) Levashov
     **/
    protected function _path($path)
    {
        return $this->rootName . ($path == $this->root ? '' : $this->separator . $this->_relpath($path));
    }

    /**
     * Return true if $path is children of $parent
     *
     * @param  string $path   path to check
     * @param  string $parent parent path
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _inpath($path, $parent)
    {
        $cwd = getcwd();
        $real_path = $this->getFullPath($path, $cwd);
        $real_parent = $this->getFullPath($parent, $cwd);
        if ($real_path && $real_parent) {
            return $real_path === $real_parent || strpos($real_path, rtrim($real_parent, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR) === 0;
        }
        return false;
    }



    /***************** file stat ********************/

    /**
     * Return stat for given path.
     * Stat contains following fields:
     * - (int)    size    file size in b. required
     * - (int)    ts      file modification time in unix time. required
     * - (string) mime    mimetype. required for folders, others - optionally
     * - (bool)   read    read permissions. required
     * - (bool)   write   write permissions. required
     * - (bool)   locked  is object locked. optionally
     * - (bool)   hidden  is object hidden. optionally
     * - (string) alias   for symlinks - link target path relative to root path. optionally
     * - (string) target  for symlinks - link target path. optionally
     * If file does not exists - returns empty array or false.
     *
     * @param  string $path file path
     *
     * @return array|false
     * @author Dmitry (dio) Levashov
     **/
    protected function _stat($path)
    {
        $stat = array();

        if (!file_exists($path) && !is_link($path)) {
            return $stat;
        }

        //Verifies the given path is the root or is inside the root. Prevents directory traveral.
        if (!$this->_inpath($path, $this->root)) {
            return $stat;
        }

        $stat['isowner'] = false;
        $linkreadable = false;
        if ($path != $this->root && is_link($path)) {
            if (!$this->options['followSymLinks']) {
                return array();
            }
            if (!($target = $this->readlink($path))
                || $target == $path) {
                if (is_null($target)) {
                    $stat = array();
                    return $stat;
                } else {
                    $stat['mime'] = 'symlink-broken';
                    $target = readlink($path);
                    $lstat = lstat($path);
                    $ostat = $this->getOwnerStat($lstat['uid'], $lstat['gid']);
                    $linkreadable = !empty($ostat['isowner']);
                }
            }
            $stat['alias'] = $this->_path($target);
            $stat['target'] = $target;
        }

        $readable = is_readable($path);

        if ($readable) {
            $size = sprintf('%u', filesize($path));
            $stat['ts'] = filemtime($path);
            if ($this->statOwner) {
                $fstat = stat($path);
                $uid = $fstat['uid'];
                $gid = $fstat['gid'];
                $stat['perm'] = substr((string)decoct($fstat['mode']), -4);
                $stat = array_merge($stat, $this->getOwnerStat($uid, $gid));
            }
        }

        if (($dir = is_dir($path)) && $this->options['detectDirIcon']) {
            $favicon = $path . DIRECTORY_SEPARATOR . $this->options['detectDirIcon'];
            if ($this->URL && file_exists($favicon)) {
                $stat['icon'] = $this->URL . str_replace(DIRECTORY_SEPARATOR, '/', substr($favicon, strlen($this->root) + 1));
            }
        }

        if (!isset($stat['mime'])) {
            $stat['mime'] = $dir ? 'directory' : $this->mimetype($path);
        }
        //logical rights first
        $stat['read'] = ($linkreadable || $readable) ? null : false;
        $stat['write'] = is_writable($path) ? null : false;

        if (is_null($stat['read'])) {
            if ($dir) {
                $stat['size'] = 0;
            } else if (isset($size)) {
                $stat['size'] = $size;
            }
        }

        if ($this->options['statCorrector']) {
            call_user_func_array($this->options['statCorrector'], array(&$stat, $path, $this->statOwner, $this));
        }

        return $stat;
    }

    /**
     * Get stat `owner`, `group` and `isowner` by `uid` and `gid`
     * Sub-fuction of _stat() and _scandir()
     *
     * @param integer $uid
     * @param integer $gid
     *
     * @return array  stat
     */
    protected function getOwnerStat($uid, $gid)
    {
        static $names = null;
        static $phpuid = null;

        if (is_null($names)) {
            $names = array('uid' => array(), 'gid' => array());
        }
        if (is_null($phpuid)) {
            if (is_callable('posix_getuid')) {
                $phpuid = posix_getuid();
            } else {
                $phpuid = 0;
            }
        }

        $stat = array();

        if ($uid) {
            $stat['isowner'] = ($phpuid == $uid);
            if (isset($names['uid'][$uid])) {
                $stat['owner'] = $names['uid'][$uid];
            } else if (is_callable('posix_getpwuid')) {
                $pwuid = posix_getpwuid($uid);
                $stat['owner'] = $names['uid'][$uid] = $pwuid['name'];
            } else {
                $stat['owner'] = $names['uid'][$uid] = $uid;
            }
        }
        if ($gid) {
            if (isset($names['gid'][$gid])) {
                $stat['group'] = $names['gid'][$gid];
            } else if (is_callable('posix_getgrgid')) {
                $grgid = posix_getgrgid($gid);
                $stat['group'] = $names['gid'][$gid] = $grgid['name'];
            } else {
                $stat['group'] = $names['gid'][$gid] = $gid;
            }
        }

        return $stat;
    }

    /**
     * Return true if path is dir and has at least one childs directory
     *
     * @param  string $path dir path
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _subdirs($path)
    {

        $dirs = false;
        if (is_dir($path) && is_readable($path)) {
            if (class_exists('FilesystemIterator', false)) {
                $dirItr = new ParentIterator(
                    new RecursiveDirectoryIterator($path,
                        FilesystemIterator::SKIP_DOTS |
                        FilesystemIterator::CURRENT_AS_SELF |
                        (defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') ?
                            RecursiveDirectoryIterator::FOLLOW_SYMLINKS : 0)
                    )
                );
                $dirItr->rewind();
                if ($dirItr->hasChildren()) {
                    $dirs = true;
                    $name = $dirItr->getSubPathName();
                    while ($dirItr->valid()) {
                        if (!$this->attr($path . DIRECTORY_SEPARATOR . $name, 'read', null, true)) {
                            $dirs = false;
                            $dirItr->next();
                            $name = $dirItr->getSubPathName();
                            continue;
                        }
                        $dirs = true;
                        break;
                    }
                }
            } else {
                $path = strtr($path, array('[' => '\\[', ']' => '\\]', '*' => '\\*', '?' => '\\?'));
                return (bool)glob(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*', GLOB_ONLYDIR);
            }
        }
        return $dirs;
    }

    /**
     * Return object width and height
     * Usualy used for images, but can be realize for video etc...
     *
     * @param  string $path file path
     * @param  string $mime file mime type
     *
     * @return string
     * @author Dmitry (dio) Levashov
     **/
    protected function _dimensions($path, $mime)
    {
        clearstatcache();
        return strpos($mime, 'image') === 0 && is_readable($path) && filesize($path) && ($s = getimagesize($path)) !== false
            ? $s[0] . 'x' . $s[1]
            : false;
    }
    /******************** file/dir content *********************/

    /**
     * Return symlink target file
     *
     * @param  string $path link path
     *
     * @return string
     * @author Dmitry (dio) Levashov
     **/
    protected function readlink($path)
    {
        if (!($target = readlink($path))) {
            return null;
        }

        if (strpos($target, $this->systemRoot) !== 0) {
            $target = $this->_joinPath(dirname($path), $target);
        }

        if (!file_exists($target)) {
            return false;
        }

        return $target;
    }

    /**
     * Return files list in directory.
     *
     * @param  string $path dir path
     *
     * @return array
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov
     */
    protected function _scandir($path)
    {
        elFinder::checkAborted();
        $files = array();
        $cache = array();
        $dirWritable = is_writable($path);
        $dirItr = array();
        $followSymLinks = $this->options['followSymLinks'];
        try {
            $dirItr = new DirectoryIterator($path);
        } catch (UnexpectedValueException $e) {
        }

        foreach ($dirItr as $file) {
            try {
                if ($file->isDot()) {
                    continue;
                }

                $files[] = $fpath = $file->getPathname();

                $br = false;
                $stat = array();

                $stat['isowner'] = false;
                $linkreadable = false;
                if ($file->isLink()) {
                    if (!$followSymLinks) {
                        continue;
                    }
                    if (!($target = $this->readlink($fpath))
                        || $target == $fpath) {
                        if (is_null($target)) {
                            $stat = array();
                            $br = true;
                        } else {
                            $_path = $fpath;
                            $stat['mime'] = 'symlink-broken';
                            $target = readlink($_path);
                            $lstat = lstat($_path);
                            $ostat = $this->getOwnerStat($lstat['uid'], $lstat['gid']);
                            $linkreadable = !empty($ostat['isowner']);
                            $dir = false;
                            $stat['alias'] = $this->_path($target);
                            $stat['target'] = $target;
                        }
                    } else {
                        $dir = is_dir($target);
                        $stat['alias'] = $this->_path($target);
                        $stat['target'] = $target;
                        $stat['mime'] = $dir ? 'directory' : $this->mimetype($stat['alias']);
                    }
                } else {
                    if (($dir = $file->isDir()) && $this->options['detectDirIcon']) {
                        $path = $file->getPathname();
                        $favicon = $path . DIRECTORY_SEPARATOR . $this->options['detectDirIcon'];
                        if ($this->URL && file_exists($favicon)) {
                            $stat['icon'] = $this->URL . str_replace(DIRECTORY_SEPARATOR, '/', substr($favicon, strlen($this->root) + 1));
                        }
                    }
                    $stat['mime'] = $dir ? 'directory' : $this->mimetype($fpath);
                }
                $size = sprintf('%u', $file->getSize());
                $stat['ts'] = $file->getMTime();
                if (!$br) {
                    if ($this->statOwner && !$linkreadable) {
                        $uid = $file->getOwner();
                        $gid = $file->getGroup();
                        $stat['perm'] = substr((string)decoct($file->getPerms()), -4);
                        $stat = array_merge($stat, $this->getOwnerStat($uid, $gid));
                    }

                    //logical rights first
                    $stat['read'] = ($linkreadable || $file->isReadable()) ? null : false;
                    $stat['write'] = $file->isWritable() ? null : false;
                    $stat['locked'] = $dirWritable ? null : true;

                    if (is_null($stat['read'])) {
                        $stat['size'] = $dir ? 0 : $size;
                    }

                    if ($this->options['statCorrector']) {
                        call_user_func_array($this->options['statCorrector'], array(&$stat, $fpath, $this->statOwner, $this));
                    }
                }

                $cache[] = array($fpath, $stat);
            } catch (RuntimeException $e) {
                continue;
            }
        }

        if ($cache) {
            $cache = $this->convEncOut($cache, false);
            foreach ($cache as $d) {
                $this->updateCache($d[0], $d[1]);
            }
        }

        return $files;
    }

    /**
     * Open file and return file pointer
     *
     * @param  string $path file path
     * @param string  $mode
     *
     * @return false|resource
     * @internal param bool $write open file for writing
     * @author   Dmitry (dio) Levashov
     */
    protected function _fopen($path, $mode = 'rb')
    {
        return fopen($path, $mode);
    }

    /**
     * Close opened file
     *
     * @param  resource $fp file pointer
     * @param string    $path
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     */
    protected function _fclose($fp, $path = '')
    {
        return (is_resource($fp) && fclose($fp));
    }

    /********************  file/dir manipulations *************************/

    /**
     * Create dir and return created dir path or false on failed
     *
     * @param  string $path parent dir path
     * @param string  $name new directory name
     *
     * @return string|bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _mkdir($path, $name)
    {
        $path = $this->_joinPath($path, $name);

        if (mkdir($path)) {
            chmod($path, $this->options['dirMode']);
            return $path;
        }

        return false;
    }

    /**
     * Create file and return it's path or false on failed
     *
     * @param  string $path parent dir path
     * @param string  $name new file name
     *
     * @return string|bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _mkfile($path, $name)
    {
        $path = $this->_joinPath($path, $name);

        if (($fp = fopen($path, 'w'))) {
            fclose($fp);
            chmod($path, $this->options['fileMode']);
            return $path;
        }
        return false;
    }

    /**
     * Create symlink
     *
     * @param  string $source    file to link to
     * @param  string $targetDir folder to create link in
     * @param  string $name      symlink name
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _symlink($source, $targetDir, $name)
    {
        return $this->localFileSystemSymlink($source, $this->_joinPath($targetDir, $name));
    }

    /**
     * Copy file into another file
     *
     * @param  string $source    source file path
     * @param  string $targetDir target directory path
     * @param  string $name      new file name
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _copy($source, $targetDir, $name)
    {
        $mtime = filemtime($source);
        $target = $this->_joinPath($targetDir, $name);
        if ($ret = copy($source, $target)) {
            isset($this->options['keepTimestamp']['copy']) && $mtime && touch($target, $mtime);
        }
        return $ret;
    }

    /**
     * Move file into another parent dir.
     * Return new file path or false.
     *
     * @param  string $source source file path
     * @param         $targetDir
     * @param  string $name   file name
     *
     * @return bool|string
     * @internal param string $target target dir path
     * @author   Dmitry (dio) Levashov
     */
    protected function _move($source, $targetDir, $name)
    {
        $mtime = filemtime($source);
        $target = $this->_joinPath($targetDir, $name);
        if ($ret = rename($source, $target) ? $target : false) {
            isset($this->options['keepTimestamp']['move']) && $mtime && touch($target, $mtime);
        }
        return $ret;
    }

    /**
     * Remove file
     *
     * @param  string $path file path
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _unlink($path)
    {
        return is_file($path) && unlink($path);
    }

    /**
     * Remove dir
     *
     * @param  string $path dir path
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _rmdir($path)
    {
        return rmdir($path);
    }

    /**
     * Create new file and write into it from file pointer.
     * Return new file path or false on error.
     *
     * @param  resource $fp   file pointer
     * @param  string   $dir  target dir path
     * @param  string   $name file name
     * @param  array    $stat file stat (required by some virtual fs)
     *
     * @return bool|string
     * @author Dmitry (dio) Levashov
     **/
    protected function _save($fp, $dir, $name, $stat)
    {
        $path = $this->_joinPath($dir, $name);

        $meta = stream_get_meta_data($fp);
        $uri = isset($meta['uri']) ? $meta['uri'] : '';
        if ($uri && !preg_match('#^[a-zA-Z0-9]+://#', $uri) && !is_link($uri)) {
            fclose($fp);
            $mtime = filemtime($uri);
            $isCmdPaste = ($this->ARGS['cmd'] === 'paste');
            $isCmdCopy = ($isCmdPaste && empty($this->ARGS['cut']));
            if (($isCmdCopy || !rename($uri, $path)) && !copy($uri, $path)) {
                return false;
            }
            // keep timestamp on upload
            if ($mtime && $this->ARGS['cmd'] === 'upload') {
                touch($path, isset($this->options['keepTimestamp']['upload']) ? $mtime : time());
            }
        } else {
            if (file_put_contents($path, $fp, LOCK_EX) === false) {
                return false;
            }
        }

        chmod($path, $this->options['fileMode']);
        return $path;
    }

    /**
     * Get file contents
     *
     * @param  string $path file path
     *
     * @return string|false
     * @author Dmitry (dio) Levashov
     **/
    protected function _getContents($path)
    {
        return file_get_contents($path);
    }

    /**
     * Write a string to a file
     *
     * @param  string $path    file path
     * @param  string $content new file content
     *
     * @return bool
     * @author Dmitry (dio) Levashov
     **/
    protected function _filePutContents($path, $content)
    {
        return (file_put_contents($path, $content, LOCK_EX) !== false);
    }

    /**
     * Detect available archivers
     *
     * @return void
     * @throws elFinderAbortException
     */
    protected function _checkArchivers()
    {
        $this->archivers = $this->getArchivers();
        return;
    }

    /**
     * chmod availability
     *
     * @param string $path
     * @param string $mode
     *
     * @return bool
     */
    protected function _chmod($path, $mode)
    {
        $modeOct = is_string($mode) ? octdec($mode) : octdec(sprintf("%04o", $mode));
        return chmod($path, $modeOct);
    }

    /**
     * Recursive symlinks search
     *
     * @param  string $path file/dir path
     *
     * @return bool
     * @throws Exception
     * @author Dmitry (dio) Levashov
     */
    protected function _findSymlinks($path)
    {
        return self::localFindSymlinks($path);
    }

    /**
     * Extract files from archive
     *
     * @param  string $path archive path
     * @param  array  $arc  archiver command and arguments (same as in $this->archivers)
     *
     * @return array|string|boolean
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov,
     * @author Alexey Sukhotin
     */
    protected function _extract($path, $arc)
    {

        if ($this->quarantine) {

            $dir = $this->quarantine . DIRECTORY_SEPARATOR . md5(basename($path) . mt_rand());
            $archive = (isset($arc['toSpec']) || $arc['cmd'] === 'phpfunction') ? '' : $dir . DIRECTORY_SEPARATOR . basename($path);

            if (!mkdir($dir)) {
                return false;
            }

            // insurance unexpected shutdown
            register_shutdown_function(array($this, 'rmdirRecursive'), realpath($dir));

            chmod($dir, 0777);

            // copy in quarantine
            if (!is_readable($path) || ($archive && !copy($path, $archive))) {
                return false;
            }

            // extract in quarantine
            try {
                $this->unpackArchive($path, $arc, $archive ? true : $dir);
            } catch(Exception $e) {
                return $this->setError($e->getMessage());
            }

            // get files list
            try {
                $ls = self::localScandir($dir);
            } catch (Exception $e) {
                return false;
            }

            // no files - extract error ?
            if (empty($ls)) {
                return false;
            }

            $this->archiveSize = 0;

            // find symlinks and check extracted items
            $checkRes = $this->checkExtractItems($dir);
            if ($checkRes['symlinks']) {
                self::localRmdirRecursive($dir);
                return $this->setError(array_merge($this->error, array(elFinder::ERROR_ARC_SYMLINKS)));
            }
            $this->archiveSize = $checkRes['totalSize'];
            if ($checkRes['rmNames']) {
                foreach ($checkRes['rmNames'] as $name) {
                    $this->addError(elFinder::ERROR_SAVE, $name);
                }
            }

            // check max files size
            if ($this->options['maxArcFilesSize'] > 0 && $this->options['maxArcFilesSize'] < $this->archiveSize) {
                $this->delTree($dir);
                return $this->setError(elFinder::ERROR_ARC_MAXSIZE);
            }

            $extractTo = $this->extractToNewdir; // 'auto', ture or false

            // archive contains one item - extract in archive dir
            $name = '';
            $src = $dir . DIRECTORY_SEPARATOR . $ls[0];
            if (($extractTo === 'auto' || !$extractTo) && count($ls) === 1 && is_file($src)) {
                $name = $ls[0];
            } else if ($extractTo === 'auto' || $extractTo) {
                // for several files - create new directory
                // create unique name for directory
                $src = $dir;
                $splits = elFinder::splitFileExtention(basename($path));
                $name = $splits[0];
                $test = dirname($path) . DIRECTORY_SEPARATOR . $name;
                if (file_exists($test) || is_link($test)) {
                    $name = $this->uniqueName(dirname($path), $name, '-', false);
                }
            }

            if ($name !== '') {
                $result = dirname($path) . DIRECTORY_SEPARATOR . $name;

                if (!rename($src, $result)) {
                    $this->delTree($dir);
                    return false;
                }
            } else {
                $dstDir = dirname($path);
                $result = array();
                foreach ($ls as $name) {
                    $target = $dstDir . DIRECTORY_SEPARATOR . $name;
                    if (self::localMoveRecursive($dir . DIRECTORY_SEPARATOR . $name, $target, true, $this->options['copyJoin'])) {
                        $result[] = $target;
                    }
                }
                if (!$result) {
                    $this->delTree($dir);
                    return false;
                }
            }

            is_dir($dir) && $this->delTree($dir);

            return (is_array($result) || file_exists($result)) ? $result : false;
        }
        //TODO: Add return statement here
        return false;
    }

    /**
     * Create archive and return its path
     *
     * @param  string $dir   target dir
     * @param  array  $files files names list
     * @param  string $name  archive name
     * @param  array  $arc   archiver options
     *
     * @return string|bool
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov,
     * @author Alexey Sukhotin
     */
    protected function _archive($dir, $files, $name, $arc)
    {
        return $this->makeArchive($dir, $files, $name, $arc);
    }

    /******************** Over write functions *************************/

    /**
     * File path of local server side work file path
     *
     * @param  string $path
     *
     * @return string
     * @author Naoki Sawada
     */
    protected function getWorkFile($path)
    {
        return $path;
    }

    /**
     * Delete dirctory trees
     *
     * @param string $localpath path need convert encoding to server encoding
     *
     * @return boolean
     * @throws elFinderAbortException
     * @author Naoki Sawada
     */
    protected function delTree($localpath)
    {
        return $this->rmdirRecursive($localpath);
    }

    /**
     * Return fileinfo based on filename
     * For item ID based path file system
     * Please override if needed on each drivers
     *
     * @param  string $path file cache
     *
     * @return array|boolean false
     */
    protected function isNameExists($path)
    {
        $exists = file_exists($this->convEncIn($path));
        // restore locale
        $this->convEncOut();
        return $exists ? $this->stat($path) : false;
    }

    /******************** Over write (Optimized) functions *************************/

    /**
     * Recursive files search
     *
     * @param  string $path dir path
     * @param  string $q    search string
     * @param  array  $mimes
     *
     * @return array
     * @throws elFinderAbortException
     * @author Dmitry (dio) Levashov
     * @author Naoki Sawada
     */
    protected function doSearch($path, $q, $mimes)
    {
        if (!empty($this->doSearchCurrentQuery['matchMethod']) || $this->encoding || !class_exists('FilesystemIterator', false)) {
            // has custom match method or non UTF-8, use elFinderVolumeDriver::doSearch()
            return parent::doSearch($path, $q, $mimes);
        }

        $result = array();

        $timeout = $this->options['searchTimeout'] ? $this->searchStart + $this->options['searchTimeout'] : 0;
        if ($timeout && $timeout < time()) {
            $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode($path)));
            return $result;
        }
        elFinder::extendTimeLimit($this->options['searchTimeout'] + 30);

        $match = array();
        try {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveCallbackFilterIterator(
                    new RecursiveDirectoryIterator($path,
                        FilesystemIterator::KEY_AS_PATHNAME |
                        FilesystemIterator::SKIP_DOTS |
                        ((defined('RecursiveDirectoryIterator::FOLLOW_SYMLINKS') && $this->options['followSymLinks']) ?
                            RecursiveDirectoryIterator::FOLLOW_SYMLINKS : 0)
                    ),
                    array($this, 'localFileSystemSearchIteratorFilter')
                ),
                RecursiveIteratorIterator::SELF_FIRST,
                RecursiveIteratorIterator::CATCH_GET_CHILD
            );
            foreach ($iterator as $key => $node) {
                if ($timeout && ($this->error || $timeout < time())) {
                    !$this->error && $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode($node->getPath)));
                    break;
                }
                if ($node->isDir()) {
                    if ($this->stripos($node->getFilename(), $q) !== false) {
                        $match[] = $key;
                    }
                } else {
                    $match[] = $key;
                }
            }
        } catch (Exception $e) {
        }

        if ($match) {
            foreach ($match as $p) {
                if ($timeout && ($this->error || $timeout < time())) {
                    !$this->error && $this->setError(elFinder::ERROR_SEARCH_TIMEOUT, $this->path($this->encode(dirname($p))));
                    break;
                }

                $stat = $this->stat($p);

                if (!$stat) { // invalid links
                    continue;
                }

                if (!empty($stat['hidden']) || !$this->mimeAccepted($stat['mime'], $mimes)) {
                    continue;
                }

                if ((!$mimes || $stat['mime'] !== 'directory')) {
                    $stat['path'] = $this->path($stat['hash']);
                    if ($this->URL && !isset($stat['url'])) {
                        $_path = str_replace(DIRECTORY_SEPARATOR, '/', substr($p, strlen($this->root) + 1));
                        $stat['url'] = $this->URL . str_replace('%2F', '/', rawurlencode($_path));
                    }

                    $result[] = $stat;
                }
            }
        }

        return $result;
    }

    /******************** Original local functions ************************
     *
     * @param $file
     * @param $key
     * @param $iterator
     *
     * @return bool
     */

    public function localFileSystemSearchIteratorFilter($file, $key, $iterator)
    {
        /* @var FilesystemIterator $file */
        /* @var RecursiveDirectoryIterator $iterator */
        $name = $file->getFilename();
        if ($this->doSearchCurrentQuery['excludes']) {
            foreach ($this->doSearchCurrentQuery['excludes'] as $exclude) {
                if ($this->stripos($name, $exclude) !== false) {
                    return false;
                }
            }
        }
        if ($iterator->hasChildren()) {
            if ($this->options['searchExDirReg'] && preg_match($this->options['searchExDirReg'], $key)) {
                return false;
            }
            return (bool)$this->attr($key, 'read', null, true);
        }
        return ($this->stripos($name, $this->doSearchCurrentQuery['q']) === false) ? false : true;
    }

    /**
     * Creates a symbolic link
     *
     * @param      string   $target  The target
     * @param      string   $link    The link
     *
     * @return     boolean  ( result of symlink() )
     */
    protected function localFileSystemSymlink($target, $link)
    {
        $res = false;
        if (function_exists('symlink') and is_callable('symlink')) {
            $errlev = error_reporting();
            error_reporting($errlev ^ E_WARNING);
            if ($res = symlink(realpath($target), $link)) {
                $res = is_readable($link);
            }
            error_reporting($errlev);
        }
        return $res;
    }
} // END class PK      A£ò\•}ÿ‰  ‰    changelog_handler.phpnu W+A„¶        <?php
/**
 * Changleog Handler
 *
 * Handles parsing for Changelog files.
 *
 * @package Neve\Admin\Dashboard
 */

namespace Neve\Admin\Dashboard;

/**
 * Class Changelog_Handler
 *
 * @package Neve\Admin\Dashboard
 */
class Changelog_Handler {
	/**
	 * Get the parsed changelog.
	 *
	 * @param string $changelog_path the changelog path.
	 *
	 * @return array
	 */
	public function get_changelog( $changelog_path ) {

		if ( ! is_file( $changelog_path ) ) {
			return [];
		}

		if ( ! WP_Filesystem() ) {
			return [];
		}

		return $this->parse_changelog( $changelog_path );
	}

	/**
	 * Parse the changelog file.
	 *
	 * @param string $changelog_path the changelog path.
	 *
	 * @return array
	 */
	private function parse_changelog( $changelog_path ) {
		WP_Filesystem();
		global $wp_filesystem;
		$changelog = $wp_filesystem->get_contents( $changelog_path );
		if ( is_wp_error( $changelog ) ) {
			$changelog = '';
		}
		$changelog       = explode( PHP_EOL, $changelog );
		$releases        = [];
		$release_count   = 0;
		$current_section = ''; // Holds the current section ('Improvements', 'Bug Fixes', etc.)

		foreach ( $changelog as $changelog_line ) {
			if ( strpos( $changelog_line, '**Changes:**' ) !== false || empty( $changelog_line ) ) {
				continue;
			}
			if ( substr( ltrim( $changelog_line ), 0, 3 ) === '###' && ! preg_match( '/###\s?(New Features|Bug Fixes)/', $changelog_line ) ) {
				// Extract version and date
				preg_match( '/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/', $changelog_line, $found_v );
				preg_match( '/[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}/', $changelog_line, $found_d );
				if ( isset( $found_v[0] ) && isset( $found_d[0] ) ) {
					$release_count++;
					$releases[ $release_count ] = array(
						'version' => $found_v[0],
						'date'    => $found_d[0],
					);
				}

				$current_section = '';
				continue;
			}

			// Check for the new headers 'New Features', 'Bug Fixes', etc.
			if ( preg_match( '/###\s?(New Features|Bug Fixes)/', $changelog_line, $section_matches ) ) {
				$current_section = strtolower( $section_matches[1] );
				continue;
			}

			// Extracting items based on the new changelog structure
			if ( $current_section === 'bug fixes' ) {
				$changelog_line                        = preg_replace( '/-\s?\*\*(.*?):\*\*/', '', $changelog_line );
				$changelog_line                        = $this->parse_md_and_clean( $changelog_line );
				$releases[ $release_count ]['fixes'][] = $changelog_line;
				continue;
			}

			if ( $current_section === 'new features' ) {
				$changelog_line                           = preg_replace( '/-\s?\*\*(.*?):\*\*/', '', $changelog_line );
				$changelog_line                           = $this->parse_md_and_clean( $changelog_line );
				$releases[ $release_count ]['features'][] = $changelog_line;
				continue;
			}

			// Legacy structure handling for feats and fixes
			if ( preg_match( '/[*|-]?\s?(\[fix]|\[Fix]|fix|Fix)[:]?\s?(\b|(?=\[))/', $changelog_line ) ) {
				$changelog_line                        = preg_replace( '/[*|-]?\s?(\[fix]|\[Fix]|fix|Fix)[:]?\s?(\b|(?=\[))/', '', $changelog_line );
				$releases[ $release_count ]['fixes'][] = $this->parse_md_and_clean( $changelog_line );
				continue;
			}

			if ( preg_match( '/[*|-]?\s?(\[feat]|\[Feat]|feat|Feat)[:]?\s?(\b|(?=\[))/', $changelog_line ) ) {
				$changelog_line                           = preg_replace( '/[*|-]?\s?(\[feat]|\[Feat]|feat|Feat)[:]?\s?(\b|(?=\[))/', '', $changelog_line );
				$releases[ $release_count ]['features'][] = $this->parse_md_and_clean( $changelog_line );
				continue;
			}

			$changelog_line = $this->parse_md_and_clean( $changelog_line );
			if ( empty( $changelog_line ) ) {
				continue;
			}
			if ( ! isset( $releases[ $release_count ]['tweaks'] ) ) {
				$releases[ $release_count ]['tweaks'] = [];
			}
			$releases[ $release_count ]['tweaks'][] = $changelog_line;
		}

		return array_values( $releases );
	}

	/**
	 * Parse markdown links, convert bold markers (**) to <b> tags, and cleanup string.
	 *
	 * @param string $string changelog line.
	 *
	 * @return string
	 */
	private function parse_md_and_clean( $string ) {
		// Drop spaces, starting lines | asterisks.
		$string = trim( $string );
		$string = ltrim( $string, '*' );
		$string = ltrim( $string, '-' );

		// Replace markdown links with <a> tags.
		$string = preg_replace_callback(
			'/\[(.*?)]\((.*?)\)/',
			function ( $matches ) {
				return '<a href="' . $matches[2] . '" target="_blank" rel="noopener"><i class="dashicons dashicons-external"></i>' . $matches[1] . '</a>';
			},
			htmlspecialchars( $string )
		);

		// Convert bold markdown (**text**) to <b>text</b>.
		$string = preg_replace( '/\*\*(.*?)\*\*/', '<b>$1</b>', $string );

		return $string;
	}

}
PK      A£ò\ßUk^‚  ‚    plugin_helper.phpnu W+A„¶        <?php
/**
 * Plugin Action Helper
 *
 * @package Neve\Admin\Dashboard
 */

namespace Neve\Admin\Dashboard;

/**
 * Class Plugin_Helper
 *
 * @package Neve\Admin\Dashboard
 */
class Plugin_Helper {
	/**
	 * Check plugin state.
	 *
	 * @param string $slug - plugin slug.
	 *
	 * @return string
	 */
	public function get_plugin_state( $slug ) {
		$plugin_link_suffix = $this->get_plugin_path( $slug );
		if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin_link_suffix ) ) {
			return is_plugin_active( $plugin_link_suffix ) ? 'deactivate' : 'activate';
		}

		return 'install';
	}

	/**
	 * Get plugin path based on plugin slug.
	 *
	 * @param string $slug - plugin slug.
	 *
	 * @return string
	 */
	public function get_plugin_path( $slug ) {
		switch ( $slug ) {
			case 'mailin':
				return $slug . '/sendinblue.php';
			case 'wpforms-lite':
				return $slug . '/wpforms.php';
			case 'intergeo-maps':
			case 'visualizer':
			case 'translatepress-multilingual':
				return $slug . '/index.php';
			case 'beaver-builder-lite-version':
				return $slug . '/fl-builder.php';
			case 'adblock-notify-by-bweb':
				return $slug . '/adblock-notify.php';
			case 'feedzy-rss-feeds':
				return $slug . '/feedzy-rss-feed.php';
			case 'wp-cloudflare-page-cache':
				return $slug . '/wp-cloudflare-super-page-cache.php';
			default:
				return $slug . '/' . $slug . '.php';
		}
	}

	/**
	 * Call plugin api
	 *
	 * @param string $slug plugin slug.
	 *
	 * @return object
	 */
	public function get_plugin_details( $slug ) {
		include_once ABSPATH . 'wp-admin/includes/plugin-install.php';

		return plugins_api(
			'plugin_information',
			array(
				'slug'   => $slug,
				'fields' => array(
					'downloaded'        => false,
					'rating'            => false,
					'description'       => false,
					'short_description' => true,
					'donate_link'       => false,
					'tags'              => false,
					'sections'          => false,
					'homepage'          => false,
					'added'             => false,
					'last_updated'      => false,
					'compatibility'     => false,
					'tested'            => false,
					'requires'          => false,
					'downloadlink'      => false,
					'icons'             => false,
					'banners'           => true,
				),
			)
		);
	}

	/**
	 * Get Plugin Action link.
	 *
	 * @param string $slug plugin slug.
	 * @param string $action action [activate, deactivate].
	 * @return string
	 */
	public function get_plugin_action_link( $slug, $action = 'activate' ) {
		if ( ! in_array( $action, [ 'activate', 'deactivate' ] ) ) {
			return '';
		}

		return add_query_arg(
			array(
				'action'        => $action,
				'plugin'        => rawurlencode( $this->get_plugin_path( $slug ) ),
				'plugin_status' => 'all',
				'paged'         => '1',
				'_wpnonce'      => wp_create_nonce( $action . '-plugin_' . $this->get_plugin_path( $slug ) ),
			),
			esc_url( 'plugins.php' )
		);
	}

	/**
	 * Get plugin version.
	 *
	 * @param string $slug plugin slug.
	 * @return string | bool
	 */
	public function get_plugin_version( $slug, $default = false ) {
		$plugin_file = $this->get_plugin_path( $slug );
		if ( ! is_plugin_active( $plugin_file ) ) {
			return $default;
		}

		$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file );
		if ( ! array_key_exists( 'Version', $plugin_data ) ) {
			return $default;
		}
		return $plugin_data['Version'];
	}

	/**
	 * Check if current plugin is activated network-wide
	 *
	 * @param string $slug plugin slug.
	 * @return bool
	 */
	public function get_is_network_wide( $slug ) {
		if ( ! is_multisite() ) {
			return false;
		}
		$plugin_file = $this->get_plugin_path( $slug );
		return is_plugin_active_for_network( $plugin_file );
	}
}
PK      A£ò\beà¸y}  y}    main.phpnu W+A„¶        <?php
/**
 * Main class of the Neve Dashboard
 *
 * @package neve
 */

namespace Neve\Admin\Dashboard;

use Neve\Core\Limited_Offers;
use Neve\Core\Theme_Info;
/**
 * Class Main
 *
 * @package Neve\Admin\Dashboard
 */
class Main {

	use Theme_Info;
	/**
	 * Changelog Handler.
	 *
	 * @var Changelog_Handler
	 */
	private $cl_handler;
	/**
	 * Plugin Helper instance.
	 *
	 * @var Plugin_Helper
	 */
	private $plugin_helper;
	/**
	 * Current theme args.
	 *
	 * @var array
	 */
	private $theme_args = [];

	/**
	 * Useful plugins array.
	 *
	 * @var array
	 */
	private $useful_plugins = [
		'optimole-wp',
		'wp-landing-kit',
		'otter-blocks',
		'wp-cloudflare-page-cache',
		'templates-patterns-collection',
		'themeisle-companion',
		'translatepress-multilingual',
		'amp',
	];

	/**
	 * Plugins Cache key.
	 *
	 * @var string
	 */
	private $plugins_cache_key = 'neve_dash_useful_plugins';

	/**
	 * Plugins Cache Hash key.
	 *
	 * @var string
	 */
	private $plugins_cache_hash_key = 'neve_dash_useful_plugins_hash';

	/**
	 * Main constructor.
	 */
	public function __construct() {
		$this->plugin_helper = new Plugin_Helper();
		$this->cl_handler    = new Changelog_Handler();
	}

	/**
	 * Run WordPress attached to actions.
	 */
	public function init() {

		$this->setup_config();
		add_action( 'init', [ $this, 'setup_config' ] );
		add_action( 'admin_menu', [ $this, 'register' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
		add_action( 'init', array( $this, 'register_settings' ) );
		add_action( 'init', array( $this, 'register_about_page' ), 1 );
	}

	/**
	 * Add the about page with respect to the white label settings.
	 *
	 * @return void
	 */
	public function register_about_page() {
		$theme         = wp_get_theme();
		$filtered_name = apply_filters( 'ti_wl_theme_name', $theme->__get( 'Name' ) );
		$slug          = $theme->__get( 'stylesheet' );

		if ( empty( $slug ) || empty( $filtered_name ) ) {
			return;
		}

		// We check if the name is different from the filtered name,
		// if it is, the whitelabel is in use and we should not add the about page.
		// this check allows for child themes to use the about page.
		if ( $filtered_name !== $theme->__get( 'Name' ) ) {
			return;
		}

		add_filter(
			'neve_about_us_metadata',
			function () use ( $filtered_name ) {

				return [
					// Top-level page in the dashboard sidebar
					'location'         => 'neve-welcome',
					// Logo to display on the page
					'logo'             => get_template_directory_uri() . '/assets/img/dashboard/logo.svg',
					// Condition to show or hide the upgrade menu in the sidebar
					'has_upgrade_menu' => ! defined( 'NEVE_PRO_VERSION' ),
					// Add predefined product pages to the about page.
					'product_pages'    => [ 'otter-page' ],
					// Upgrade menu item link & text
					'upgrade_link'     => tsdk_utmify( esc_url( 'https://themeisle.com/themes/neve/upgrade/' ), 'aboutfilter', 'nevedashboard' ),
					'upgrade_text'     => __( 'Upgrade', 'neve' ) . ' ' . $filtered_name,
				];
			}
		);
	}

	/**
	 * Register Logger Setting
	 */
	public function register_settings() {
		register_setting(
			'neve_settings',
			'neve_logger_flag',
			[
				'type'         => 'string',
				'show_in_rest' => true,
				'default'      => '',
			]
		);
	}

	/**
	 * Setup the class props based on current theme.
	 */
	public function setup_config() {
		$theme = wp_get_theme();

		$this->theme_args['name']        = apply_filters( 'ti_wl_theme_name', $theme->__get( 'Name' ) );
		$this->theme_args['template']    = $theme->get( 'Template' );
		$this->theme_args['version']     = $theme->__get( 'Version' );
		$this->theme_args['description'] = apply_filters( 'ti_wl_theme_description', $theme->__get( 'Description' ) );
		$this->theme_args['slug']        = $theme->__get( 'stylesheet' );
	}

	/**
	 * Register theme options page.
	 *
	 * @return void
	 */
	public function register() {
		$theme = $this->theme_args;

		if ( empty( $theme['name'] ) || empty( $theme['slug'] ) ) {
			return;
		}

		$theme_page = ! empty( $theme['template'] ) ? $theme['template'] . '-welcome' : $theme['slug'] . '-welcome';

		$icon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI3LjQuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzMiAzMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzIgMzI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRkZGRkZGO30KPC9zdHlsZT4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTAsMS42QzAsMC43LDAuNywwLDEuNiwwaDI4LjhDMzEuMywwLDMyLDAuNywzMiwxLjZWMzBjMCwwLjktMC43LDEuNi0xLjYsMS42SDEuNkMwLjcsMzEuNSwwLDMwLjgsMCwzMFYxLjZ6CgkgTTEzLDE1Ljh2Ny41SDkuMVY4YzAtMC4xLDAtMC4xLDAuMS0wLjJjMCwwLDAuMSwwLDAuMiwwLjFsOS42LDcuOFY4LjJoMy44djE1LjJjMCwwLjEsMCwwLjEtMC4xLDAuMmMwLDAtMC4xLDAtMC4yLTAuMUwxMywxNS44egoJIE0yMi44LDI1LjdIOS4xVjI3aDEzLjdWMjUuN3oiLz4KPC9zdmc+Cg==';
		if ( $theme['name'] !== 'Neve' ) {
			$icon = 'dashicons-admin-appearance';
		}
		$neve_icon  = apply_filters( 'neve_menu_icon', $icon );
		$priority   = apply_filters( 'neve_menu_priority', 59 );  // The position of the menu item, 60 is the position of the Appearance menu.
		$capability = 'manage_options';

		// Place a theme page in the Appearance menu, for older versions of Neve Pro or TPC. to maintain backwards compatibility.
		if (
			( defined( 'NEVE_PRO_VERSION' ) && version_compare( NEVE_PRO_VERSION, '2.6.1', '<=' ) ) ||
			( defined( 'TIOB_VERSION' ) && version_compare( TIOB_VERSION, '1.1.38', '<=' ) )
		) {
			add_theme_page(
				/* translators: %s - Theme name */
				sprintf( __( '%s Options', 'neve' ), wp_kses_post( $theme['name'] ) ),
				/* translators: %s - Theme name */
				sprintf( __( '%s Options', 'neve' ), wp_kses_post( $theme['name'] ) ),
				$capability,
				'admin.php?page=neve-welcome'
			);
		}

		add_menu_page( // phpcs:ignore WPThemeReview.PluginTerritory.NoAddAdminPages.add_menu_pages_add_menu_page
			wp_kses_post( $theme['name'] ),
			wp_kses_post( $theme['name'] ),
			$capability,
			$theme_page,
			[ $this, 'render' ],
			$neve_icon, // The URL to the icon to be used for this menu
			$priority
		);

		// Add Dashboard submenu. Same slug as parent to allow renaming the automatic submenu that is added.
		add_submenu_page( // phpcs:ignore WPThemeReview.PluginTerritory.NoAddAdminPages.add_menu_pages_add_submenu_page
			$theme_page,
			/* translators: %s - Theme name */
			sprintf( __( '%s Options', 'neve' ), wp_kses_post( $theme['name'] ) ),
			/* translators: %s - Theme name */
			sprintf( __( '%s Options', 'neve' ), wp_kses_post( $theme['name'] ) ),
			$capability,
			$theme_page,
			[ $this, 'render' ]
		);

		$this->copy_customizer_page( $theme_page );

		if ( ! defined( 'NEVE_PRO_VERSION' ) || 'valid' !== apply_filters( 'product_neve_license_status', false ) ) {
			// Add Custom Layout submenu for upsell.
			add_submenu_page( // phpcs:ignore WPThemeReview.PluginTerritory.NoAddAdminPages.add_menu_pages_add_submenu_page
				$theme_page,
				__( 'Custom Layouts', 'neve' ),
				__( 'Custom Layouts', 'neve' ),
				$capability,
				'admin.php?page=neve-welcome#custom-layouts'
			);
		}
	}

	/**
	 * Copy the customizer page to the dashboard.
	 *
	 * @param string $theme_page The theme page slug.
	 *
	 * @return void
	 */
	private function copy_customizer_page( $theme_page ) {
		global $submenu;
		if ( ! isset( $submenu['themes.php'] ) ) {
			return;
		}
		$themes_menu = $submenu['themes.php'];
		if ( empty( $themes_menu ) ) {
			return;
		}
		$customize_pos = array_search( 'customize', array_column( $themes_menu, 1 ) );
		if ( false === $customize_pos ) {
			return;
		}
		$themes_page_keys = array_keys( $themes_menu );
		if ( ! isset( $themes_page_keys[ $customize_pos ] ) ) {
			return;
		}

		$customizer_menu_item = array_splice( $themes_menu, $customize_pos, 1 );
		$customizer_menu_item = reset( $customizer_menu_item );
		if ( empty( $customizer_menu_item ) ) {
			return;
		}

		add_submenu_page( // phpcs:ignore WPThemeReview.PluginTerritory.NoAddAdminPages.add_menu_pages_add_submenu_page
			$theme_page,
			$customizer_menu_item[0],
			$customizer_menu_item[0],
			'manage_options',
			'customize.php'
		);
	}

	/**
	 * Render the application stub.
	 *
	 * @return void
	 */
	public function render() {
		echo '<div id="neve-dashboard"></div>';
	}

	/**
	 * Load css and scripts for the about page
	 */
	public function enqueue() {
		$screen = get_current_screen();
		if ( ! isset( $screen->id ) ) {
			return;
		}

		$theme      = $this->theme_args;
		$theme_page = ! empty( $theme['template'] ) ? $theme['template'] . '-welcome' : $theme['slug'] . '-welcome';

		if ( $screen->id !== 'toplevel_page_' . $theme_page ) {
			return;
		}

		$build_path   = get_template_directory_uri() . '/assets/apps/dashboard/build/';
		$dependencies = ( include get_template_directory() . '/assets/apps/dashboard/build/dashboard.asset.php' );

		wp_register_style( 'neve-dash-style', $build_path . 'style-dashboard.css', [ 'wp-components', 'neve-components' ], $dependencies['version'] );
		wp_style_add_data( 'neve-dash-style', 'rtl', 'replace' );
		wp_enqueue_style( 'neve-dash-style' );
		wp_register_script( 'neve-dash-script', $build_path . 'dashboard.js', array_merge( $dependencies['dependencies'], [ 'updates' ] ), $dependencies['version'], true );
		wp_localize_script( 'neve-dash-script', 'neveDash', apply_filters( 'neve_dashboard_page_data', $this->get_localization() ) );
		wp_enqueue_script( 'neve-dash-script' );

		if ( function_exists( 'wp_set_script_translations' ) ) {
			wp_set_script_translations( 'neve-dash-script', 'neve' );
		}
	}

	/**
	 * Get localization data for the dashboard script.
	 *
	 * @return array
	 */
	private function get_localization() {

		$offer = new Limited_Offers();

		$old_about_config  = apply_filters( 'ti_about_config_filter', [ 'useful_plugins' => true ] );
		$theme_name        = apply_filters( 'ti_wl_theme_name', $this->theme_args['name'] );
		$plugin_name       = apply_filters( 'ti_wl_plugin_name', 'Neve Pro' );
		$plugin_name_addon = apply_filters( 'ti_wl_plugin_name', 'Neve Pro Addon' );
		$data              = [
			'nonce'                   => wp_create_nonce( 'wp_rest' ),
			'version'                 => 'v' . NEVE_VERSION,
			'assets'                  => get_template_directory_uri() . '/assets/img/dashboard/',
			'hasOldPro'               => (bool) ( defined( 'NEVE_PRO_VERSION' ) && version_compare( NEVE_PRO_VERSION, '1.1.11', '<' ) ),
			'isRTL'                   => is_rtl(),
			'isValidLicense'          => $this->has_valid_addons(),
			'notifications'           => $this->get_notifications(),
			'customizerShortcuts'     => $this->get_customizer_shortcuts(),
			'plugins'                 => $this->get_useful_plugins(),
			'featureData'             => $this->get_free_pro_features(),
			'showFeedbackNotice'      => $this->should_show_feedback_notice(),
			'allfeaturesNeveProURL'   => tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'seeallfeatures', 'freevspropage' ),
			'startSitesgetNeveProURL' => tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'welcomestartersitescard', 'nevedashboard' ),
			'customLayoutsNeveProURL' => tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'customlayoutscard', 'nevedashboard' ),
			'upgradeURL'              => apply_filters( 'neve_upgrade_link_from_child_theme_filter', tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'getpronow', 'freevspropage' ) ),
			'supportURL'              => esc_url( 'https://wordpress.org/support/theme/neve/' ),
			'docsURL'                 => esc_url( 'https://docs.themeisle.com/article/946-neve-doc' ),
			'codexURL'                => esc_url( 'https://codex.nevewp.com/' ),
			'strings'                 => [
				'proTabTitle'                   => wp_kses_post( $plugin_name ),
				/* translators: %s - Theme name */
				'header'                        => sprintf( __( '%s Options', 'neve' ), wp_kses_post( $theme_name ) ),
				/* translators: %s - Theme name */
				'starterSitesCardDescription'   => sprintf( __( '%s now comes with a sites library with various designs to pick from. Visit our collection of demos that are constantly being added.', 'neve' ), wp_kses_post( $theme_name ) ),
				'starterSitesCardUpsellMessage' => esc_html__( 'Upgrade to the Pro version and get instant access to all Premium Starter Sites â€” including Expert Sites â€” and much more.', 'neve' ),
				/* translators: %s - Theme name */
				'starterSitesTabDescription'    => sprintf( __( 'With %s, you can choose from multiple unique demos, specially designed for you, that can be installed with a single click. You just need to choose your favorite, and we will take care of everything else.', 'neve' ), wp_kses_post( $theme_name ) ),
				/* translators: 1 - Theme name, 2 - Cloud Templates & Patterns Collection */
				'starterSitesUnavailableActive' => sprintf( __( 'In order to be able to import any starter sites for %1$s you would need to have the %2$s plugin active.', 'neve' ), wp_kses_post( $theme_name ), 'Cloud Templates & Patterns Collection' ),
				/* translators: %s - Theme name */
				'starterSitesUnavailableUpdate' => sprintf( __( 'In order to be able to import any starter sites for %1$s you would need to have the %2$s plugin updated to the latest version.', 'neve' ), wp_kses_post( $theme_name ), 'Cloud Templates & Patterns Collection' ),
				/* translators: %s - Theme name */
				'supportCardDescription'        => sprintf( __( 'We want to make sure you have the best experience using %1$s, and that is why we have gathered all the necessary information here for you. We hope you will enjoy using %1$s as much as we enjoy creating great products.', 'neve' ), wp_kses_post( $theme_name ) ),
				/* translators: %s - Theme name */
				'docsCardDescription'           => sprintf( __( 'Need more details? Please check our full documentation for detailed information on how to use %s.', 'neve' ), wp_kses_post( $theme_name ) ),
				/* translators: %s - "Neve Pro Addon" */
				'licenseCardHeading'            => sprintf( __( '%s license', 'neve' ), wp_kses_post( $plugin_name_addon ) ),
				/* translators: %s - "Neve Pro Addon" */
				'updateOldPro'                  => sprintf( __( 'Please update %s to the latest version and then refresh this page to have access to the options.', 'neve' ), wp_kses_post( $plugin_name_addon ) ),
				/* translators: %1$s - Author link - Themeisle */
				'licenseCardDescription'        => sprintf(
				// translators: store name (Themeisle)
					__( 'Enter your license from %1$s purchase history in order to get plugin updates', 'neve' ),
					'<a target="_blank" rel="external noreferrer noopener" href="https://store.themeisle.com/">ThemeIsle<span class="components-visually-hidden">' . esc_html__( '(opens in a new tab)', 'neve' ) . '</span><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" class="components-external-link__icon" role="img" aria-hidden="true" focusable="false" style="fill: #0073AA"><path d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"></path></svg></a>'
				),
			],
			'changelog'               => $this->cl_handler->get_changelog( get_template_directory() . '/CHANGELOG.md' ),
			'onboarding'              => [],
			'hasFileSystem'           => WP_Filesystem(),
			'hidePluginsTab'          => apply_filters( 'neve_hide_useful_plugins', ! array_key_exists( 'useful_plugins', $old_about_config ) ),
			'tpcPath'                 => defined( 'TIOB_PATH' ) ? TIOB_PATH . 'template-patterns-collection.php' : 'template-patterns-collection/template-patterns-collection.php',
			'tpcAdminURL'             => admin_url( 'admin.php?page=tiob-starter-sites' ),
			'pluginsURL'              => esc_url( admin_url( 'plugins.php' ) ),
			'getPluginStateBaseURL'   => esc_url( rest_url( '/nv/v1/dashboard/plugin-state/' ) ),
			'canInstallPlugins'       => current_user_can( 'install_plugins' ),
			'canActivatePlugins'      => current_user_can( 'activate_plugins' ),
			'deal'                    => ! defined( 'NEVE_PRO_VERSION' ) ? $offer->get_localized_data() : array(),
		];

		if ( defined( 'NEVE_PRO_PATH' ) ) {
			$installed_plugins                     = get_plugins();
			$is_otter_installed                    = array_key_exists( 'otter-pro/otter-pro.php', $installed_plugins );
			$is_sparks_installed                   = array_key_exists( 'sparks-for-woocommerce/sparks-for-woocommerce.php', $installed_plugins );
			$data['changelogPro']                  = $this->cl_handler->get_changelog( NEVE_PRO_PATH . '/CHANGELOG.md' );
			$data['isOtterProInstalled']           = $is_otter_installed;
			$data['otterProInstall']               = $is_otter_installed ? esc_url( wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=otter-pro%2Fotter-pro.php&plugin_status=all&paged=1&s' ), 'activate-plugin_otter-pro/otter-pro.php' ) ) : esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=install_otter_pro' ), 'install_otter_pro' ) );
			$data['sparksInstallActivateEndpoint'] = $is_sparks_installed ? esc_url( wp_nonce_url( admin_url( 'plugins.php?action=activate&plugin=sparks-for-woocommerce%2Fsparks-for-woocommerce.php&plugin_status=all&paged=1&s' ), 'activate-plugin_sparks-for-woocommerce/sparks-for-woocommerce.php' ) ) : esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=install_sparks' ), 'install_sparks' ) );
			$data['moduleObserver']                = array(
				'customLayouts' => array(
					'labelSubMenu' => __( 'Custom Layouts', 'neve' ),
					'linkSubMenu'  => 'edit.php?post_type=neve_custom_layouts',
				),
			);

		}

		if ( isset( $_GET['onboarding'] ) && $_GET['onboarding'] === 'yes' ) {
			$data['isOnboarding'] = true;
		}

		return $data;
	}

	/**
	 * Get the notifications for plugin and theme updates.
	 *
	 * @return array
	 */
	public function get_notifications() {
		$notifications = [];
		$slug          = 'neve';
		$themes_update = get_site_transient( 'update_themes' );

		$plugin_folder = defined( 'NEVE_PRO_BASEFILE' ) ? basename( dirname( NEVE_PRO_BASEFILE ) ) : null;
		$plugin_path   = $plugin_folder ? $plugin_folder . '/neve-pro-addon.php' : null;

		if ( isset( $themes_update->response[ $slug ] ) ) {
			$update                = $themes_update->response[ $slug ];
			$notifications['neve'] = [
				// translators: s - theme name (Neve).
				'text'   => sprintf( __( 'New theme update for %1$s! Please update to %2$s.', 'neve' ), wp_kses_post( $this->theme_args['name'] ), wp_kses_post( $update['new_version'] ) ),
				'update' => [
					'type' => 'theme',
					'slug' => $slug,
				],
				'cta'    => __( 'Update Now', 'neve' ),
				'type'   => ( $plugin_path && is_plugin_active( $plugin_path ) ) ? 'warning' : null,
			];
		}

		if ( $plugin_path ) {
			$plugins_update = get_site_transient( 'update_plugins' );
			if ( is_plugin_active( $plugin_path ) && isset( $plugins_update->response[ $plugin_path ] ) ) {
				$update                          = $plugins_update->response[ $plugin_path ];
				$notifications['neve-pro-addon'] = [
					'text'   => sprintf(
					// translators: s - Pro plugin name (Neve Pro)
						__( 'New plugin update for %1$s! Please update to %2$s.', 'neve' ),
						wp_kses_post( apply_filters( 'ti_wl_plugin_name', 'Neve Pro' ) ),
						wp_kses_post( $update->new_version )
					),
					'update' => [
						'type' => 'plugin',
						'slug' => 'neve-pro-addon',
						'path' => $plugin_path,
					],
					'cta'    => __( 'Update Now', 'neve' ),
					'type'   => 'warning',
				];
			}
		}

		if ( $this->show_branding_notice() ) {
			$notifications['branding-discount'] = [
				'text'        => sprintf(
				// translators: s - Discount Code
					__( 'From 3.3.0 we decided to remove the copyright component from the free version. You can continue using it if you rollback to 3.2.x or you can upgrade to pro, using a one time 50%% discount: %s', 'neve' ),
					wp_kses_post( '<code>NEVEBRANDING50</code>' )
				),
				'url'         => tsdk_utmify( 'https://themeisle.com/themes/neve/upgrade/', 'copyrightnotice', 'nevedashboard' ),
				'targetBlank' => true,
				'cta'         => __( 'Upgrade', 'neve' ),
			];
		}

		if ( count( $notifications ) === 1 && is_plugin_active( $plugin_path ) ) {
			foreach ( $notifications as $key => $notification ) {
				/* translators: 1 - Theme Name (Neve), 2 - Plugin Name (Neve Pro) */
				$notifications[ $key ]['text'] = sprintf( __( 'We recommend that both %1$s and %2$s are updated to the latest version to ensure optimal intercompatibility.', 'neve' ), wp_kses_post( $this->theme_args['name'] ), apply_filters( 'ti_wl_plugin_name', 'Neve Pro' ) );
			}
		}

		$notifications = apply_filters( 'neve_dashboard_notifications', $notifications );

		return $notifications;
	}

	/**
	 * Should branding notice be shown.
	 *
	 * @return bool
	 */
	private function show_branding_notice() {
		if ( $this->has_valid_addons() ) {
			return false;
		}

		return time() < strtotime( '2022-07-06' );
	}

	/**
	 * Get the Customizer Shortcut Links.
	 *
	 * @return array
	 */
	private function get_customizer_shortcuts() {
		return [
			[
				'text' => __( 'Upload Logo', 'neve' ),
				'link' => add_query_arg( [ 'autofocus[control]' => 'custom_logo' ], admin_url( 'customize.php' ) ),
			],
			[
				'text' => __( 'Set Colors', 'neve' ),
				'link' => add_query_arg( [ 'autofocus[section]' => 'neve_colors_background_section' ], admin_url( 'customize.php' ) ),
			],
			[
				'text' => __( 'Customize Fonts', 'neve' ),
				'link' => add_query_arg( [ 'autofocus[control]' => 'neve_headings_font_family' ], admin_url( 'customize.php' ) ),
			],
			[
				'text' => __( 'Layout Options', 'neve' ),
				'link' => add_query_arg( [ 'autofocus[panel]' => 'neve_layout' ], admin_url( 'customize.php' ) ),
			],
			[
				'text' => __( 'Header Options', 'neve' ),
				'link' => add_query_arg( [ 'autofocus[panel]' => 'hfg_header' ], admin_url( 'customize.php' ) ),
			],
			[
				'text' => __( 'Blog Layouts', 'neve' ),
				'link' => add_query_arg( [ 'autofocus[section]' => 'neve_blog_archive_layout' ], admin_url( 'customize.php' ) ),
			],
			[
				'text' => __( 'Footer Options', 'neve' ),
				'link' => add_query_arg( [ 'autofocus[panel]' => 'hfg_footer' ], admin_url( 'customize.php' ) ),
			],
			[
				'text' => __( 'Content / Sidebar', 'neve' ),
				'link' => add_query_arg( [ 'autofocus[section]' => 'neve_sidebar' ], admin_url( 'customize.php' ) ),
			],
		];
	}

	/**
	 * Get doc link.
	 *
	 * @param string $utm_term utm term to use for doc link.
	 * @param string $url url to doc.
	 * @return string
	 */
	private function get_doc_link( $utm_term, $url ) {

		return tsdk_utmify( $url, $utm_term, 'freevspropage' );
	}

	/**
	 * Get the pro features for the free v pro table.
	 *
	 * @return array
	 */
	private function get_free_pro_features() {
		return [
			[
				'title'       => __( 'Header/Footer builder', 'neve' ),
				'description' => __( 'Easily build your header and footer by dragging and dropping all the important elements in the real-time WordPress Customizer. More advanced options are available in PRO.', 'neve' ),
				'inLite'      => true,
				'docsLink'    => $this->get_doc_link( 'Header/Footer builder', 'https://docs.themeisle.com/category/1251-neve-header-builder' ),
			],
			[
				'title'       => __( 'Page Builder Compatibility', 'neve' ),
				'description' => __( 'Neve is fully compatible with Gutenberg, the new WordPress editor and for all of you page builder fans, Neve has full compatibility with Elementor, Beaver Builder, and all the other popular page builders.', 'neve' ),
				'inLite'      => true,
				'docsLink'    => $this->get_doc_link( 'Page Builder Compatibility', 'https://docs.themeisle.com/article/946-neve-doc#pagebuilders' ),
			],
			[
				'title'       => __( 'Header Booster', 'neve' ),
				'description' => __( 'Take the header builder to a new level with new awesome components: socials, contact, breadcrumbs, language switcher, multiple HTML, sticky and transparent menu, page header builder and many more.', 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'Header Booster', 'https://docs.themeisle.com/article/1057-header-booster-documentation' ),
			],
			[
				'title'       => __( 'Page Header Builder', 'neve' ),
				'description' => __( 'The Page Header is the horizontal area that sits directly below the header and contains the page/post title. Easily design an attractive Page Header area using our dedicated builder.', 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'Page Header Builder', 'https://docs.themeisle.com/article/1262-neve-page-header' ),
			],
			[
				'title'       => __( 'Custom Layouts', 'neve' ),
				'description' => __( 'Powerful Custom Layouts builder which allows you to easily create your own header, footer or custom content on any of the hook locations available in the theme.', 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'Custom Layouts', 'https://docs.themeisle.com/article/1062-custom-layouts-module' ),
			],
			[
				'title'       => __( 'Blog Booster', 'neve' ),
				'description' => __( 'Give a huge boost to your entire blogging experience with features specially designed for increased user experience.', 'neve' ) . ' ' . __( 'Sharing, custom article sorting, comments integrations, number of minutes needed to read an article and many more.', 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'Blog Booster', 'https://docs.themeisle.com/article/1059-blog-booster-documentation' ),
			],
			[
				'title'       => __( 'Elementor Booster', 'neve' ),
				'description' => __( 'Leverage the true flexibility of Elementor with powerful addons and templates that you can import with just one click.', 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'Elementor Booster', 'https://docs.themeisle.com/article/1063-elementor-booster-module-documentation' ),
			],
			[
				'title'       => __( 'WooCommerce Booster', 'neve' ),
				'description' => __( 'Empower your online store with awesome new features, specially designed for a smooth WooCommerce integration.', 'neve' ) . ' ' . __( 'Wishlist, quick view, video products, advanced reviews, multiple dedicated layouts and many more.', 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'WooCommerce Booster', 'https://docs.themeisle.com/article/1058-woocommerce-booster-documentation' ),
			],
			[
				'title'       => __( 'LifterLMS Booster', 'neve' ),
				'description' => __( 'Make your LifterLMS pages look stunning with our PRO design options. Specially created to help you set up your online courses with minimum customizations.', 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'LifterLMS Booster', 'https://docs.themeisle.com/article/1084-lifterlms-booster-documentation' ),
			],
			[
				'title'       => __( 'Typekit(Adobe) Fonts', 'neve' ),
				'description' => __( "The module allows for an easy way of enabling new awesome Adobe (previous Typekit) Fonts in Neve's Typography options.", 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'Typekit(Adobe) Fonts', 'https://docs.themeisle.com/article/1085-typekit-fonts-documentation' ),
			],
			[
				'title'       => __( 'White Label', 'neve' ),
				'description' => __( "For any developer or agency out there building websites for their own clients, we've made it easy to present the theme as your own.", 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'White Label', 'https://docs.themeisle.com/article/1061-white-label-module-documentation' ),
			],
			[
				'title'       => __( 'Scroll To Top', 'neve' ),
				'description' => __( 'Simple but effective module to help you navigate back to the top of the really long pages.', 'neve' ),
				'inLite'      => false,
				'docsLink'    => $this->get_doc_link( 'Scroll To Top', 'https://docs.themeisle.com/article/1060-scroll-to-top-module-documentation' ),
			],
			[
				'title'          => __( 'See all PRO features', 'neve' ),
				'presentational' => true,
			],
		];
	}

	/**
	 * Get the useful plugin data.
	 *
	 * @return array
	 */
	private function get_useful_plugins() {
		$available    = get_transient( $this->plugins_cache_key );
		$hash         = get_transient( $this->plugins_cache_hash_key );
		$current_hash = substr( md5( wp_json_encode( $this->useful_plugins ) ), 0, 5 );

		if ( $available !== false && $hash === $current_hash ) {
			$available = json_decode( $available, true );
			foreach ( $available as $slug => $args ) {
				$available[ $slug ]['cta']        = ( $args['cta'] === 'external' ) ? 'external' : $this->plugin_helper->get_plugin_state( $slug );
				$available[ $slug ]['path']       = $this->plugin_helper->get_plugin_path( $slug );
				$available[ $slug ]['activate']   = $this->plugin_helper->get_plugin_action_link( $slug );
				$available[ $slug ]['deactivate'] = $this->plugin_helper->get_plugin_action_link( $slug, 'deactivate' );
				$available[ $slug ]['network']    = $this->plugin_helper->get_is_network_wide( $slug );
				$available[ $slug ]['version']    = ! empty( $available[ $slug ]['version'] ) ? $this->plugin_helper->get_plugin_version( $slug, $available[ $slug ]['version'] ) : '';
			}

			return $available;
		}

		$data = [];
		foreach ( $this->useful_plugins as $slug ) {

			if ( array_key_exists( $slug, $this->get_external_plugins_data() ) ) {
				$data[ $slug ] = $this->get_external_plugins_data()[ $slug ];
				continue;
			}

			$current_plugin = $this->plugin_helper->get_plugin_details( $slug );
			if ( $current_plugin instanceof \WP_Error ) {
				continue;
			}
			$data[ $slug ] = [
				'banner'      => $current_plugin->banners['low'],
				'name'        => html_entity_decode( $current_plugin->name ),
				'description' => html_entity_decode( $current_plugin->short_description ),
				'version'     => $current_plugin->version,
				'author'      => html_entity_decode( wp_strip_all_tags( $current_plugin->author ) ),
				'cta'         => $this->plugin_helper->get_plugin_state( $slug ),
				'path'        => $this->plugin_helper->get_plugin_path( $slug ),
				'activate'    => $this->plugin_helper->get_plugin_action_link( $slug ),
				'deactivate'  => $this->plugin_helper->get_plugin_action_link( $slug, 'deactivate' ),
				'network'     => $this->plugin_helper->get_is_network_wide( $slug ),
			];
		}

		set_transient( $this->plugins_cache_hash_key, $current_hash );
		set_transient( $this->plugins_cache_key, wp_json_encode( $data ) );

		return $data;
	}

	/**
	 * Check if feedback notice should be shown after 14 days since activation.
	 *
	 * @return bool
	 */
	private function should_show_feedback_notice() {
		$activated_time = get_option( 'neve_install' );
		if ( ! empty( $activated_time ) ) {
			if ( time() - intval( $activated_time ) > 14 * DAY_IN_SECONDS ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Get data of external plugins that are not hosted on wp.org.
	 *
	 * @return array
	 */
	private function get_external_plugins_data() {

		$plugins = array(
			'wp-landing-kit' => array(
				'banner'      => NEVE_ASSETS_URL . 'img/dashboard/wp-landing.jpg',
				'name'        => 'WP Landing Kit',
				'description' => 'Turn WordPress into a landing page powerhouse with Landing Kit. Map domains to pages or any other published resource.',
				'author'      => 'Themeisle',
				'cta'         => 'external',
				'url'         => tsdk_utmify( 'https://wplandingkit.com/', 'recommendedplugins', 'nevedashboard' ),
				'premium'     => true,
			),

		);

		return $plugins;
	}

}
PK        A£ò\ô$Bö¼  ö¼  '                elFinderVolumeLocalFileSystem.class.phpnu W+A„¶        PK        A£ò\•}ÿ‰  ‰              M½  changelog_handler.phpnu W+A„¶        PK        A£ò\ßUk^‚  ‚              Ð  plugin_helper.phpnu W+A„¶        PK        A£ò\beà¸y}  y}              ÞÞ  main.phpnu W+A„¶        PK      U  