<?php /********************************************************************* class.setup.php osTicket setup wizard. Peter Rotich <peter@osticket.com> Copyright (c) 2006-2012 osTicket http://www.osticket.com Released under the GNU General Public License WITHOUT ANY WARRANTY. See LICENSE.TXT for details. vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ Class SetupWizard { //Mimimum requirements var $prereq = array('php' => '4.3', 'mysql' => '4.4'); //Version info - same as the latest version. var $version ='1.7-dpr2'; var $version_verbose='1.7 DPR 2'; //Errors var $errors=array(); function SetupWizard(){ $this->errors=array(); } function load_sql_file($file, $prefix, $abort=true, $debug=false) { if(!file_exists($file) || !($schema=file_get_contents($file))) return $this->abort('Error accessing SQL file '.basename($file)); return $this->load_sql($schema, $prefix, $abort, $debug); } /* load SQL schema - assumes MySQL && existing connection */ function load_sql($schema, $prefix, $abort=true, $debug=false) { # Strip comments and remarks $schema=preg_replace('%^\s*(#|--).*$%m','',$schema); # Replace table prefis $schema = str_replace('%TABLE_PREFIX%',$prefix, $schema); # Split by semicolons - and cleanup if(!($statements = array_filter(array_map('trim', @explode(';', $schema))))) return $this->abort('Error parsing SQL schema'); @mysql_query('SET SESSION SQL_MODE =""'); foreach($statements as $k=>$sql) { if(!mysql_query($sql)) { if($debug) echo "[$sql]=>".mysql_error(); if($abort) return $this->abort("[$sql] - ".mysql_error()); } } return true; } function getVersion() { return $this->version; } function getVersionVerbose() { return $this->version_verbose; } function getPHPVersion() { return $this->prereq['php']; } function getMySQLVersion() { return $this->prereq['mysql']; } function check_php() { return (version_compare(PHP_VERSION,$this->getPHPVersion())>=0); } function check_mysql() { return (extension_loaded('mysql')); } function check_prereq() { return ($this->check_php() && $this->check_mysql()); } /* @error is a mixed var. */ function abort($error) { $this->onError($error); return false; // Always false... It's an abort. } function setError($error) { if($error && is_array($error)) $this->errors = array_merge($this->errors,$error); elseif($error) $this->errors[] = $error; } function getErrors(){ return $this->errors; } function onError($error) { return $this->setError($error); } } class Upgrader extends SetupWizard { var $prefix; var $sqldir; var $signature; function Upgrader($signature, $prefix, $sqldir) { $this->signature = $signature; $this->shash = substr($signature, 0, 8); $this->prefix = $prefix; $this->sqldir = $sqldir; $this->errors = array(); //Init persistent state of upgrade. $this->state = &$_SESSION['ost_upgrader'][$this->getShash()]['state']; //Init the task Manager. if(!isset($_SESSION['ost_upgrader'][$this->getShash()])) $_SESSION['ost_upgrader'][$this->getShash()]['tasks']=array(); //Tasks to perform - saved on the session. $this->tasks = &$_SESSION['ost_upgrader'][$this->getShash()]['tasks']; } function onError($error) { $this->setError($error); $this->setState('aborted'); } function isUpgradable() { return (!$this->isAborted() && $this->getNextPatch()); } function isAborted() { return !strcasecmp($this->getState(), 'aborted'); } function getSchemaSignature() { return $this->signature; } function getShash() { return $this->shash; } function getTablePrefix() { return $this->prefix; } function getSQLDir() { return $this->sqldir; } function getState() { return $this->state; } function setState($state) { $this->state = $state; } function getNextPatch() { if(!($patch=glob($this->getSQLDir().$this->getShash().'-*.patch.sql'))) return null; return $patch[0]; } function getThisPatch() { if(!($patch=glob($this->getSQLDir().'*-'.$this->getShash().'.patch.sql'))) return null; return $patch[0]; } function getNextVersion() { if(!$patch=$this->getNextPatch()) return '(Latest)'; if(preg_match('/\*(.*)\*/', file_get_contents($patch), $matches)) $info=$matches[0]; else $info=substr(basename($patch), 9, 8); return $info; } function getNextAction() { $action='Upgrade osTicket to '.$this->getVersion(); if($this->getNumPendingTasks() && ($task=$this->getNextTask())) { $action = $task['desc']; if($task['status']) //Progress report... $action.=' ('.$task['status'].')'; } elseif($this->isUpgradable() && ($nextversion = $this->getNextVersion())) { $action = "Upgrade to $nextversion"; } return $action; } function getNextStepInfo() { if(($patches=$this->getPatches())) return $patches[0]; if(($hooks=$this->getScriptedHooks())) return $hooks[0]['desc']; return null; } function getPatches($ToSignature) { $signature = $this->getSignature(); $patches = array(); while(($patch=glob($this->getSQLDir().substr($signature,0,8).'-*.patch.sql')) && $i++) { $patches = array_merge($patches, $patch); if(!($signature=substr(basename($patch[0]), 10, 8)) || !strncasecmp($signature, $ToSignature, 8)) break; } return $patches; } function getNumPendingTasks() { return count($this->getPendingTasks()); } function getPendingTasks() { $pending=array(); if(($tasks=$this->getTasks())) { foreach($tasks as $k => $task) { if(!$task['done']) $pending[$k] = $task; } } return $pending; } function getTasks() { return $this->tasks; } function getNextTask() { if(!($tasks=$this->getPendingTasks())) return null; return current($tasks); } function removeTask($tId) { if(isset($this->tasks[$tId])) unset($this->tasks[$tId]); return (!$this->tasks[$tId]); } function setTaskStatus($tId, $status) { if(isset($this->tasks[$tId])) $this->tasks[$tId]['status'] = $status; } function doTasks() { if(!($tasks=$this->getPendingTasks())) return true; //Nothing to do. foreach($tasks as $k => $task) { if(call_user_func(array($this, $task['func']), $k)===0) { $this->tasks[$k]['done'] = true; } else { //Task has pending items to process. break; } } return (!$this->getPendingTasks()); } function upgrade() { if($this->getPendingTasks() || !($patch=$this->getNextPatch())) return false; if(!$this->load_sql_file($patch, $this->getTablePrefix(), true, true)) return false; //TODO: Log the upgrade //Load up post install tasks. $shash = substr(basename($patch), 9, 8); $phash = substr(basename($patch), 0, 17); $tasks=array(); $tasks[] = array('func' => 'sometask', 'desc' => 'Some Task.... blah'); switch($phash) { //Add patch specific scripted tasks. case 'xxxx': //V1.6 ST- 1.7 * $tasks[] = array('func' => 'migrateAttachments2DB', 'desc' => 'Migrating attachments to database, it might take a while depending on the number of files.'); break; } $tasks[] = array('func' => 'cleanup', 'desc' => 'Post-upgrade cleanup!'); //Load up tasks - NOTE: writing directly to the session - back to the future majic. $_SESSION['ost_upgrader'][$shash]['tasks'] = $tasks; $_SESSION['ost_upgrader'][$shash]['state'] = 'upgrade'; //clear previous patch info - unset($_SESSION['ost_upgrader'][$this->getSHash()]); return true; } /************* TASKS **********************/ function sometask($tId) { $this->setTaskStatus($tId, 'Doing... '.time(). ' #'.$_SESSION['sometask']); sleep(2); $_SESSION['sometask']+=1; if($_SESSION['sometask']<4) return 22; $_SESSION['sometask']=0; return 0; //Change to 1 for testing... } function cleanup($tId=0) { $file=$this->getSQLDir().$this->getSchemaSignature().'-cleanup.sql'; if(!file_exists($file)) //No cleanup script. return 0; //We have a cleanup script ::XXX: Don't abort on error? if($this->load_sql_file($file, $this->getTablePrefix(), false, true)) return 0; //XXX: ??? return false; } function migrateAttachments2DB($tId=0) { echo "Process attachments here - $tId"; return 0; } } /* Installer class - latest version. */ class Installer extends SetupWizard { var $config; function Installer($configfile) { $this->config =$configfile; $this->errors=array(); } function getConfigFile() { return $this->config; } function config_exists() { return ($this->getConfigFile() && file_exists($this->getConfigFile())); } function config_writable() { return ($this->getConfigFile() && is_writable($this->getConfigFile())); } function check_config() { return ($this->config_exists() && $this->config_writable()); } //XXX: Latest version insall logic...no carry over. function install($vars) { $this->errors=$f=array(); $f['name'] = array('type'=>'string', 'required'=>1, 'error'=>'Name required'); $f['email'] = array('type'=>'email', 'required'=>1, 'error'=>'Valid email required'); $f['fname'] = array('type'=>'string', 'required'=>1, 'error'=>'First name required'); $f['lname'] = array('type'=>'string', 'required'=>1, 'error'=>'Last name required'); $f['admin_email'] = array('type'=>'email', 'required'=>1, 'error'=>'Valid email required'); $f['username'] = array('type'=>'username', 'required'=>1, 'error'=>'Username required'); $f['passwd'] = array('type'=>'password', 'required'=>1, 'error'=>'Password required'); $f['passwd2'] = array('type'=>'string', 'required'=>1, 'error'=>'Confirm password'); $f['prefix'] = array('type'=>'string', 'required'=>1, 'error'=>'Table prefix required'); $f['dbhost'] = array('type'=>'string', 'required'=>1, 'error'=>'Hostname required'); $f['dbname'] = array('type'=>'string', 'required'=>1, 'error'=>'Database name required'); $f['dbuser'] = array('type'=>'string', 'required'=>1, 'error'=>'Username required'); $f['dbpass'] = array('type'=>'string', 'required'=>1, 'error'=>'password required'); if(!Validator::process($f,$vars,$this->errors) && !$this->errors['err']) $this->errors['err']='Missing or invalid data - correct the errors and try again.'; //Staff's email can't be same as system emails. if($vars['admin_email'] && $vars['email'] && !strcasecmp($vars['admin_email'],$vars['email'])) $this->errors['admin_email']='Conflicts with system email above'; //Admin's pass confirmation. if(!$this->errors && strcasecmp($vars['passwd'],$vars['passwd2'])) $this->errors['passwd2']='passwords to not match!'; //Check table prefix underscore required at the end! if($vars['prefix'] && substr($vars['prefix'], -1)!='_') $this->errors['prefix']='Bad prefix. Must have underscore (_) at the end. e.g \'ost_\''; //Make sure admin username is not very predictable. XXX: feels dirty but necessary if(!$this->errors['username'] && in_array(strtolower($vars['username']),array('admin','admins','username','osticket'))) $this->errors['username']='Bad username'; //MYSQL: Connect to the DB and check the version & database (create database if it doesn't exist!) if(!$this->errors) { if(!db_connect($vars['dbhost'],$vars['dbuser'],$vars['dbpass'])) $this->errors['db']='Unable to connect to MySQL server. Possibly invalid login info.'; elseif(db_version()< $this->getMySQLVersion()) $this->errors['db']=sprintf('osTicket requires MySQL %s or better!',$this->getMySQLVersion()); elseif(!db_select_database($vars['dbname']) && !db_create_database($vars['dbname'])) { $this->errors['dbname']='Database doesn\'t exist'; $this->errors['db']='Unable to create the database.'; } elseif(!db_select_database($vars['dbname'])) { $this->errors['dbname']='Unable to select the database'; } } //bailout on errors. if($this->errors) return false; /*************** We're ready to install ************************/ define('ADMIN_EMAIL',$vars['admin_email']); //Needed to report SQL errors during install. define('PREFIX',$vars['prefix']); //Table prefix $schemaFile =INC_DIR.'sql/osticket-v1.7-mysql.sql'; //DB dump. $debug = true; //XXX:Change it to true to show SQL errors. //Last minute checks. if(!file_exists($schemaFile)) $this->errors['err']='Internal Error - please make sure your download is the latest (#1)'; elseif(!file_exists($this->getConfigFile()) || !($configFile=file_get_contents($this->getConfigFile()))) $this->errors['err']='Unable to read config file. Permission denied! (#2)'; elseif(!($fp = @fopen($this->getConfigFile(),'r+'))) $this->errors['err']='Unable to open config file for writing. Permission denied! (#3)'; elseif(!$this->load_sql_file($schemaFile,$vars['prefix'], true, $debug)) $this->errors['err']='Error parsing SQL schema! Get help from developers (#4)'; if(!$this->errors) { //Create admin user. $sql='INSERT INTO '.PREFIX.'staff SET created=NOW() ' .', isactive=1, isadmin=1, group_id=1, dept_id=1, timezone_id=8, max_page_size=25 ' .', email='.db_input($_POST['admin_email']) .', firstname='.db_input($vars['fname']) .', lastname='.db_input($vars['lname']) .', username='.db_input($vars['username']) .', passwd='.db_input(Passwd::hash($vars['passwd'])); if(!mysql_query($sql) || !($uid=mysql_insert_id())) $this->errors['err']='Unable to create admin user (#6)'; } if(!$this->errors) { //Create config settings---default settings! //XXX: rename ostversion helpdesk_* ?? $sql='INSERT INTO '.PREFIX.'config SET updated=NOW(), isonline=0 ' .', default_email_id=1, alert_email_id=2, default_dept_id=1 ' .', default_sla_id=1, default_timezone_id=8, default_template_id=1 ' .', admin_email='.db_input($vars['admin_email']) .', schema_signature='.db_input(md5_file($schemaFile)) .', helpdesk_url='.db_input(URL) .', helpdesk_title='.db_input($vars['name']); if(!mysql_query($sql) || !($cid=mysql_insert_id())) $this->errors['err']='Unable to create config settings (#7)'; } if($this->errors) return false; //Abort on internal errors. //Rewrite the config file - MUST be done last to allow for installer recovery. $configFile= str_replace("define('OSTINSTALLED',FALSE);","define('OSTINSTALLED',TRUE);",$configFile); $configFile= str_replace('%ADMIN-EMAIL',$vars['admin_email'],$configFile); $configFile= str_replace('%CONFIG-DBHOST',$vars['dbhost'],$configFile); $configFile= str_replace('%CONFIG-DBNAME',$vars['dbname'],$configFile); $configFile= str_replace('%CONFIG-DBUSER',$vars['dbuser'],$configFile); $configFile= str_replace('%CONFIG-DBPASS',$vars['dbpass'],$configFile); $configFile= str_replace('%CONFIG-PREFIX',$vars['prefix'],$configFile); $configFile= str_replace('%CONFIG-SIRI',Misc::randcode(32),$configFile); if(!$fp || !ftruncate($fp,0) || !fwrite($fp,$configFile)) { $this->errors['err']='Unable to write to config file. Permission denied! (#5)'; return false; } @fclose($fp); /************* Make the system happy ***********************/ //Create default emails! $email = $vars['email']; list(,$domain)=explode('@',$vars['email']); $sql='INSERT INTO '.PREFIX.'email (`email_id`, `dept_id`, `name`,`email`,`created`,`updated`) VALUES ' ." (1,1,'Support','$email',NOW(),NOW())" .",(2,1,'osTicket Alerts','alerts@$domain',NOW(),NOW())" .",(3,1,'','noreply@$domain',NOW(),NOW())"; @mysql_query($sql); //Create a ticket to make the system warm and happy. $sql='INSERT INTO '.PREFIX.'ticket SET created=NOW(), status="open", source="Web" ' .' ,priority_id=2, dept_id=1, topic_id=1 ' .' ,ticketID='.db_input(Misc::randNumber(6)) .' ,email="support@osticket.com" ' .' ,name="osTicket Support" ' .' ,subject="osTicket Installed!"'; if(mysql_query($sql) && ($tid=mysql_insert_id())) { if(!($msg=file_get_contents(INC_DIR.'msg/installed.txt'))) $msg='Congratulations and Thank you for choosing osTicket!'; $sql='INSERT INTO '.PREFIX.'ticket_thread SET created=NOW(),source="Web" ' .', poster="System"' .', ticket_id='.db_input($tid) .', body='.db_input($msg); @mysql_query($sql); } //TODO: create another personalized ticket and assign to admin?? //Log a message. $msg="Congratulations osTicket basic installation completed!\n\nThank you for choosing osTicket!"; $sql='INSERT INTO '.PREFIX.'syslog SET created=NOW(),updated=NOW(),log_type="Debug" ' .', title="osTicket installed!"' .', log='.db_input($msg) .', ip_address='.db_input($_SERVER['REMOTE_ADDR']); @mysql_query($sql); return true; } } ?>