/include/cli/modules/unpack.php
PHP | 270 lines | 206 code | 33 blank | 31 comment | 40 complexity | 1a249204d2df851851fe62c17cbe03db MD5 | raw file
- <?php
- class Unpacker extends Module {
- var $prologue = "Unpacks osTicket into target install path";
- var $epilog =
- "Copies an unpacked osticket tarball or zipfile into a production
- location, optionally placing the include/ folder in a separate
- location if requested";
- var $options = array(
- 'include' => array('-i','--include', 'metavar'=>'path', 'help'=>
- "The include/ folder, which contains the bulk of osTicket's source
- code can be located outside of the install path. This is recommended
- for better security. If you would like to install the include/
- folder somewhere else, give the path here. Note that the full
- path is assumed, so give path/to/include/ to unpack the source
- code in that folder. The folder will be automatically created if
- it doesn't already exist."
- ),
- 'verbose' => array('-v','--verbose', 'default'=>false, 'nargs'=>0,
- 'action'=>'store_true', 'help'=>
- "Move verbose logging to stdout"),
- );
- var $arguments = array(
- 'install-path' =>
- "The destination for osTicket to reside. Use the --include
- option to specify destination of the include/ folder, if the
- administrator should chose to locate it separate from the
- main installation path.",
- );
- var $manifest;
- var $source;
- var $destination;
- function realpath($path) {
- return ($p = realpath($path)) ? $p : $path;
- }
- function find_upload_folder() {
- # Hop up to the root folder
- $start = dirname(__file__);
- for (;;) {
- if (is_dir($start . '/upload')) break;
- $start .= '/..';
- }
- return self::realpath($start.'/upload');
- }
- function change_include_dir($include_path) {
- # Read the main.inc.php script
- $bootstrap_php = $this->destination . '/bootstrap.php';
- $lines = explode("\n", file_get_contents($bootstrap_php));
- $include_path = preg_replace('://+:', '/', $include_path);
- # Try and use ROOT_DIR
- if (strpos($include_path, $this->destination) === 0)
- $include_path = "ROOT_DIR . '" .
- str_replace($this->destination, '', $include_path) . "'";
- else
- $include_path = "'$include_path'";
- # Find the line that defines INCLUDE_DIR
- $match = array();
- foreach ($lines as &$line) {
- // TODO: Change THIS_VERSION inline to be current `git describe`
- if (preg_match("/(\s*)define\s*\(\s*'INCLUDE_DIR'/", $line, $match)) {
- # Replace the definition with the new locatin
- $line = $match[1] . "define('INCLUDE_DIR', "
- . $include_path
- . "); // Set by installer";
- break;
- }
- }
- if (!file_put_contents($bootstrap_php, implode("\n", $lines)))
- die("Unable to configure location of INCLUDE_DIR in bootstrap.php\n");
- }
- function exclude($pattern, $match) {
- if (!$pattern) {
- return false;
- } elseif (is_array($pattern)) {
- foreach ($pattern as $p)
- if (fnmatch($p, $match))
- return true;
- } else {
- return fnmatch($pattern, $match);
- }
- return false;
- }
- function readManifest($file) {
- if (isset($this->manifest))
- return @$this->manifest[$file] ?: null;
- $this->manifest = $lines = array();
- $path = $this->get_include_dir() . '/.MANIFEST';
- if (!is_file($path))
- return null;
- if (!preg_match_all('/^([\w:,]+) (.+)$/mu', file_get_contents($path),
- $lines, PREG_PATTERN_ORDER)
- ) {
- return null;
- }
- $this->manifest = array_combine($lines[2], $lines[1]);
- return @$this->manifest[$file] ?: null;
- }
- function hashFile($file) {
- static $hashes = array();
- if (!isset($hashes[$file])) {
- $md5 = md5_file($file);
- $sha1 = sha1_file($file);
- $hash = substr($md5, -20) . substr($sha1, -20);
- $hashes[$file] = $hash;
- }
- return $hashes[$file];
- }
- function isChanged($source, $hash=false) {
- $local = str_replace($this->source.'/', '', $source);
- $hash = $hash ?: $this->hashFile($source);
- return $this->readManifest($local) != $hash;
- }
- function updateManifest($file, $hash=false) {
- $hash = $hash ?: $this->hashFile($file);
- $local = str_replace($this->source.'/', '', $file);
- $this->manifest[$local] = $hash;
- }
- function copyFile($src, $dest, $hash=false, $mode=0644) {
- $this->updateManifest($src, $hash);
- return copy($src, $dest) && chmod($dest, $mode);
- }
- /**
- * Copy from source to desination, perhaps recursing up to n folders.
- * Exclusions are also permitted. If any files match an MD5 sum, they
- * will be excluded from the copy operation.
- *
- * Parameters:
- * folder - (string) source folder root
- * destination - (string) destination folder root
- * recurse - (int) recuse up to this many folders. Use 0 or false to
- * disable recursion, and -1 to recurse infinite folders.
- * exclude - (string | array<string>) patterns that will be matched
- * using the PHP `fnmatch` function. If any file or folder matches,
- * it will be excluded from the copy procedure. Omit or use false
- * to disable exclusions
- */
- function unpackage($folder, $destination, $recurse=0, $exclude=false) {
- $dryrun = $this->getOption('dry-run', false);
- $verbose = $this->getOption('verbose') || $dryrun;
- $force = $this->getOption('force', false);
- if (substr($destination, -1) !== '/')
- $destination .= '/';
- foreach (glob($folder, GLOB_BRACE|GLOB_NOSORT) as $file) {
- if ($this->exclude($exclude, $file))
- continue;
- if (is_file($file)) {
- $target = $destination . basename($file);
- $hash = $this->hashFile($file);
- if (!$force && is_file($target)
- && false === ($flag = $this->isChanged($file, $hash)))
- continue;
- if ($verbose) {
- $msg = $target;
- if (is_string($flag))
- $msg = "$msg ({$flag})";
- $this->stdout->write("$msg\n");
- }
- if ($dryrun)
- continue;
- if (!is_dir($destination))
- mkdir($destination, 0755, true);
- $this->copyFile($file, $target, $hash);
- }
- }
- if ($recurse) {
- $folders = glob(dirname($folder).'/'.basename($folder),
- GLOB_BRACE|GLOB_ONLYDIR|GLOB_NOSORT);
- foreach ($folders as $dir) {
- if (in_array(basename($dir), array('.','..')))
- continue;
- elseif ($this->exclude($exclude, $dir))
- continue;
- $this->unpackage(
- dirname($folder).'/'.basename($dir).'/'.basename($folder),
- $destination.basename($dir),
- $recurse - 1, $exclude);
- }
- }
- }
- function get_include_dir() {
- static $location;
- if (isset($location))
- return $location;
- $pipes = array();
- $php = proc_open('php', array(
- 0 => array('pipe', 'r'),
- 1 => array('pipe', 'w'),
- ), $pipes);
- fwrite($pipes[0], "<?php
- include '{$this->destination}/bootstrap.php';
- print INCLUDE_DIR;
- ");
- fclose($pipes[0]);
- $INCLUDE_DIR = fread($pipes[1], 8192);
- proc_close($php);
- return $location = rtrim($INCLUDE_DIR, '/').'/';
- }
- function bootstrap() {
- // Don't load config and frieds as that will likely crash if not yet
- // installed
- }
- function run($args, $options) {
- $this->destination = $args['install-path'];
- if (!is_dir($this->destination))
- if (!mkdir($this->destination, 0751, true))
- die("Destination path does not exist and cannot be created");
- # Determine if this is an upgrade, and if so, where the include/
- # folder is currently located
- $upgrade = file_exists("{$this->destination}/main.inc.php");
- # Locate the upload folder
- $upload = $this->source = $this->find_upload_folder();
- # Unpack the upload folder to the destination, except the include folder
- if ($upgrade)
- # Get the current value of the INCLUDE_DIR before overwriting
- # main.inc.php
- $include = $this->get_include_dir();
- $this->unpackage("$upload/{,.}*", $this->destination, -1, "*include");
- if (!$upgrade) {
- if ($this->getOption('include')) {
- $location = $this->getOption('include');
- if (!is_dir("$location/"))
- if (!mkdir("$location/", 0751, true))
- die("Unable to create folder for include/ files\n");
- $this->unpackage("$upload/include/{,.}*", $location, -1);
- $this->change_include_dir($location);
- }
- else
- $this->unpackage("$upload/include/{,.}*", "{$this->destination}/include", -1);
- }
- else {
- $this->unpackage("$upload/include/{,.}*", $include, -1);
- # Change the new main.inc.php to reflect the location of the
- # include/ directory
- $this->change_include_dir($include);
- }
- }
- }
- Module::register('unpack', 'Unpacker');