diff --git a/.gitignore b/.gitignore index b5c98c948d9c2eb75df122268b315b29f15a9e93..3bdc4e066866d0433aafc3341fa4b43b0b420b58 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ include/ost-config.php .DS_Store .vagrant Vagrantfile + +# Staging directory used for packaging script +stage diff --git a/setup/cleanup-codebase.sh b/setup/scripts/cleanup-codebase.sh similarity index 100% rename from setup/cleanup-codebase.sh rename to setup/scripts/cleanup-codebase.sh diff --git a/setup/scripts/manage.php b/setup/scripts/manage.php new file mode 100755 index 0000000000000000000000000000000000000000..c738318291c9fb861bad7139f7fd75403da6d876 --- /dev/null +++ b/setup/scripts/manage.php @@ -0,0 +1,60 @@ +#!/usr/bin/env php +<?php + +require_once "modules/class.module.php"; + +class Manager extends Module { + var $prologue = + "Manage one or more osTicket installations"; + + var $arguments = array( + 'action' => "Action to be managed" + ); + + var $usage = '$script action [options] [arguments]'; + + var $autohelp = false; + + function showHelp() { + foreach (glob(dirname(__file__).'/modules/*.php') as $script) + include_once $script; + + global $registered_modules; + $this->epilog = + "Currently available modules follow. Use 'manage.php <module> + --help' for usage regarding each respective module:"; + + parent::showHelp(); + + echo "\n"; + foreach ($registered_modules as $name=>$mod) + echo str_pad($name, 20) . $mod->prologue . "\n"; + } + + function run() { + if ($this->getOption('help') && !$this->getArgument('action')) + $this->showHelp(); + + else { + $action = $this->getArgument('action'); + + global $argv; + foreach ($argv as $idx=>$val) + if ($val == $action) + unset($argv[$idx]); + + include_once dirname(__file__) . '/modules/' . $action . '.php'; + $module = Module::getInstance($action); + $module->run(); + } + } +} + +if (php_sapi_name() != "cli") + die("Management only supported from command-line\n"); + +$manager = new Manager(); +$manager->parseOptions(); +$manager->run(); + +?> diff --git a/setup/scripts/modules/class.module.php b/setup/scripts/modules/class.module.php new file mode 100644 index 0000000000000000000000000000000000000000..de30f16321457bec2bb0c8762ed790d583e3ca6e --- /dev/null +++ b/setup/scripts/modules/class.module.php @@ -0,0 +1,214 @@ +<?php + +class Option { + + var $default = false; + + function Option() { + call_user_func_array(array($this, "__construct"), func_get_args()); + } + + function __construct($options=false) { + list($this->short, $this->long) = array_slice($options, 0, 2); + $this->help = (isset($options['help'])) ? $options['help'] : ""; + $this->action = (isset($options['action'])) ? $options['action'] + : "store"; + $this->dest = (isset($options['dest'])) ? $options['dest'] + : substr($this->long, 2); + $this->type = (isset($options['type'])) ? $options['type'] + : 'string'; + $this->const = (isset($options['const'])) ? $options['const'] + : null; + $this->default = (isset($options['default'])) ? $options['default'] + : null; + $this->metavar = (isset($options['metavar'])) ? $options['metavar'] + : 'var'; + } + + function hasArg() { + return $this->action != 'store_true' + && $this->action != 'store_false'; + } + + function handleValue(&$destination, $args) { + $nargs = 0; + $value = array_shift($args); + if ($value[0] == '-') + $value = null; + elseif ($value) + $nargs = 1; + switch ($this->action) { + case 'store_true': + $value = true; + break; + case 'store_false': + $value = false; + break; + case 'store_const': + $value = $this->const; + break; + case 'store': + default: + if ($this->type == 'int') + $value = (int)$value; + break; + } + $destination[$this->dest] = $value; + return $nargs; + } + + function toString() { + $short = explode(':', $this->short); + $long = explode(':', $this->long); + if ($this->nargs == '?') + $switches = sprintf(' %s [%3$s], %s[=%3$s]', $short[0], + $long[0], $this->metavar); + elseif ($this->hasArg()) + $switches = sprintf(' %s %3$s, %s=%3$s', $short[0], $long[0], + $this->metavar); + else + $switches = sprintf(" %s, %s", $short[0], $long[0]); + $help = preg_replace('/\s+/', ' ', $this->help); + if (strlen($switches) > 24) + $help = "\n" . str_repeat(" ", 24) . $help; + else + $switches = str_pad($switches, 24); + $help = wordwrap($help, 54, "\n" . str_repeat(" ", 24)); + return $switches . $help; + } +} + +class Module { + + var $options = array(); + var $arguments = array(); + var $prologue = ""; + var $epilog = ""; + var $usage = '$script [options] $args [arguments]'; + var $autohelp = true; + + function Module() { + call_user_func_array(array($this, '__construct'), func_get_args()); + } + + function __construct() { + $this->options['help'] = array("-h","--help", + 'action'=>'store_true', + 'help'=>"Display this help message"); + foreach ($this->options as &$opt) + $opt = new Option($opt); + } + + function showHelp() { + if ($this->prologue) + echo $this->prologue . "\n\n"; + + echo "Usage:\n"; + global $argv; + echo " " . str_replace( + array('$script', '$args'), + array($argv[0], implode(' ', array_keys($this->arguments))), + $this->usage) . "\n"; + + ksort($this->options); + if ($this->options) { + echo "\nOptions:\n"; + foreach ($this->options as $name=>$opt) + echo $opt->toString() . "\n"; + } + + if ($this->arguments) { + echo "\nArguments:\n"; + foreach ($this->arguments as $name=>$help) + echo $name . "\n " . wordwrap( + preg_replace('/\s+/', ' ', $help), 76, "\n "); + } + + if ($this->epilog) { + echo "\n\n"; + $epilog = preg_replace('/\s+/', ' ', $this->epilog); + echo wordwrap($epilog, 76, "\n"); + } + + echo "\n"; + } + + function getOption($name, $default=false) { + $this->parseOptions(); + if (isset($this->_options[$name])) + return $this->_options[$name]; + else + return $default; + } + + function getArgument($name, $default=false) { + $this->parseOptions(); + foreach (array_keys($this->arguments) as $idx=>$arg) + if ($arg == $name && isset($this->_args[$idx])) + return $this->_args[$idx]; + return $default; + } + + function parseOptions() { + if (is_array($this->_options)) + return; + + global $argv; + list($this->_options, $this->_args) = + $this->parseArgs(array_slice($argv, 1)); + + foreach (array_keys($this->arguments) as $idx=>$name) + if (!isset($this->_args[$idx])) + $this->optionError($name . " is a required argument"); + + if ($this->autohelp && $this->getOption('help')) { + $this->showHelp(); + die(); + } + } + + function optionError($error) { + echo "Error: " . $error . "\n\n"; + $this->showHelp(); + die(); + } + + /* abstract */ function run() { + } + + /* static */ function register($action, $class) { + global $registered_modules; + $registered_modules[$action] = new $class(); + } + + /* static */ function getInstance($action) { + global $registered_modules; + return $registered_modules[$action]; + } + + function parseArgs($argv) { + $options = $args = array(); + $argv = array_slice($argv, 0); + while ($arg = array_shift($argv)) { + if (strpos($arg, '=') !== false) { + list($arg, $value) = explode('=', $arg, 2); + array_unshift($argv, $value); + } + $found = false; + foreach ($this->options as $opt) { + if ($opt->short == $arg || $opt->long == $arg) { + if ($opt->handleValue($options, $argv)) + array_shift($argv); + $found = true; + } + } + if (!$found && $arg[0] != '-') + $args[] = $arg; + } + return array($options, $args); + } +} + +$registered_modules = array(); + +?> diff --git a/setup/scripts/modules/unpack.php b/setup/scripts/modules/unpack.php new file mode 100644 index 0000000000000000000000000000000000000000..0f6aed8ab9cc6ac862e09389393c57f3e62a7fb8 --- /dev/null +++ b/setup/scripts/modules/unpack.php @@ -0,0 +1,158 @@ +<?php + +require_once dirname(__file__) . "/class.module.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." + ), + ); + + 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.", + ); + + function find_upload_folder() { + # Hop up to the root folder + $start = dirname(__file__); + for (;;) { + if (is_dir($start . '/upload')) break; + $start .= '/..'; + } + return realpath($start.'/upload'); + } + + function change_include_dir($include_path) { + # Read the main.inc.php script + $main_inc_php = $this->destination . '/main.inc.php'; + $lines = explode("\n", file_get_contents($main_inc_php)); + # Try and use ROOT_PATH + if (strpos($include_path, $this->destination) === 0) + $include_path = "ROOT_PATH . '" . + str_replace($this->destination, '', $include_path) . "'"; + else + $include_path = "'$include_path'"; + # Find the line that defines INCLUDE_DIR + foreach ($lines as &$line) { + 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($main_inc_php, implode("\n", $lines))) + die("Unable to configure location of INCLUDE_DIR in main.inc.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 unpackage($folder, $destination, $recurse=true, $exclude=false) { + foreach (glob($folder, GLOB_BRACE|GLOB_NOSORT) as $file) { + if ($this->exclude($exclude, $file)) + continue; + if (is_file($file)) { + if (!is_dir($destination)) + mkdir($destination, 0751, true); + copy($file, $destination . '/' . basename($file)); + } + } + if ($recurse) { + foreach (glob(dirname($folder).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { + if ($this->exclude($exclude, $dir)) + continue; + $this->unpackage( + dirname($folder).'/'.basename($dir).'/'.basename($folder), + $destination.'/'.basename($dir), + $recurse - 1, $exclude); + } + } + } + + function get_include_dir() { + $main_inc_php = $this->destination . '/main.inc.php'; + $lines = preg_grep("/define\s*\(\s*'INCLUDE_DIR'/", + explode("\n", file_get_contents($main_inc_php))); + + // NOTE: that this won't work for crafty folks who have a define or some + // variable in the value of their include path + if (!defined('ROOT_DIR')) define('ROOT_DIR', $this->destination . '/'); + foreach ($lines as $line) + eval($line); + + return INCLUDE_DIR; + } + + function run() { + $this->destination = $this->getArgument('install-path'); + if (!is_dir($this->destination)) + if (!mkdir($this->destination, 0751, true)) + $this->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->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'); diff --git a/setup/scripts/package.php b/setup/scripts/package.php new file mode 100755 index 0000000000000000000000000000000000000000..22542b6e0fe9d7dc944af1839c91b473047e4e9d --- /dev/null +++ b/setup/scripts/package.php @@ -0,0 +1,131 @@ +#!/usr/bin/env php +<?php + +if (php_sapi_name() != 'cli') + die("Only command-line packaging is supported"); + +$stage_folder = "stage"; +$stage_path = dirname(__file__) . '/' . $stage_folder; + +function get_osticket_root_path() { + # Hop up to the root folder + $start = dirname(__file__); + for (;;) { + if (file_exists($start . '/main.inc.php')) break; + $start .= '/..'; + } + return realpath($start); +} + +# Check PHP syntax across all php files +function glob_recursive($pattern, $flags = 0) { + $files = glob($pattern, $flags); + foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { + $files = array_merge($files, + glob_recursive($dir.'/'.basename($pattern), $flags)); + } + return $files; +} + +$root = get_osticket_root_path(); + +function exclude($pattern, $match) { + if (is_array($pattern)) { + foreach ($pattern as $p) + if (fnmatch($p, $match)) + return true; + } + else + return fnmatch($pattern, $match); + return false; +} + +function package($pattern, $destination, $recurse=false, $exclude=false) { + global $root, $stage_path; + $search = $root . '/' . $pattern; + echo "Packaging " . $search . "\n"; + foreach (glob($search, GLOB_BRACE|GLOB_NOSORT) as $file) { + if (is_file($file)) { + if ($exclude && exclude($exclude, $file)) + continue; + if (!is_dir("$stage_path/$destination")) + mkdir("$stage_path/$destination", 0777, true); + copy($file, $stage_path . '/' . $destination . '/' . basename($file)); + } + } + if ($recurse) { + foreach (glob(dirname($search).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) { + if ($exclude && exclude($exclude, $dir)) + continue; + package(dirname($pattern).'/'.basename($dir).'/'.basename($pattern), + $destination.'/'.basename($dir), + $recurse-1, $exclude); + } + } +} + +# Create the stage folder for the install files +if (!is_dir($stage_path)) + mkdir($stage_path); +else { + $dirs = array(); + foreach (glob_recursive($stage_path . '/*') as $file) + if (is_dir($file)) + $dirs[] = $file; + else + unlink($file); + sort($dirs); + foreach (array_reverse($dirs) as $dir) + rmdir($dir); +} + +# Source code goes into 'upload' +mkdir($stage_path . '/upload'); + +# Load the root directory files +package("*.php", 'upload/'); + +# Load the client interface +foreach (array('assets','css','images','js') as $dir) + package("$dir/*", "upload/$dir", -1, "*less"); + +# Load the knowledgebase +package("kb/*.php", "upload/kb"); + +# Load the staff interface +package("scp/*.php", "upload/scp/", -1); +foreach (array('css','images','js') as $dir) + package("$dir/*", "upload/scp/$dir", -1); + +# Load in the scripts +mkdir("$stage_path/scripts/"); +package("setup/scripts/*", "scripts/", -1, "*stage"); + +# Load the heart of the system +package("include/*.php", "upload/include", -1); +# And the sql patches +package("include/upgrader/*.sql", "upload/include/upgrader", -1); + +# Include the installer +package("setup/*.{php,txt}", "upload/setup", -1, array("*scripts","*test","*stage")); +foreach (array('css','images','js') as $dir) + package("setup/$dir/*", "upload/setup/$dir", -1); +package("setup/inc/sql/*.{sql,md5}", "upload/setup/inc/sql", -1); + +# Load the license and documentation +package("*.{txt,md}", ""); + +# Make an archive of the stage folder +$version_info = preg_grep('/THIS_VERSION/', + explode("\n", file_get_contents("$root/main.inc.php"))); + +foreach ($version_info as $line) + eval($line); + +$pwd = getcwd(); +chdir($stage_path); +shell_exec("tar cjf '$pwd/osticket-".THIS_VERSION.".tar.bz2' *"); +shell_exec("zip -r '$pwd/osticket-".THIS_VERSION.".zip' *"); + +chdir($pwd); +?>