• Main Page
  • Related Pages
  • Namespaces
  • Classes
  • Files
  • Examples
  • File List

system/handlers/installhandler.php

00001 <?php
00007 namespace Habari;
00008 
00009 define( 'MIN_PHP_VERSION', '5.3.3' );
00010 
00014 class InstallHandler extends ActionHandler
00015 {
00016 
00022   public function act_begin_install()
00023   {
00024     // Create a new theme to handle the display of the installer
00025     $this->theme = Themes::create( 'installer', 'RawPHPEngine', HABARI_PATH . '/system/installer/' );
00026 
00030     $this->theme->locales = Locale::list_all();
00031     if ( isset( $_POST['locale'] ) && $_POST['locale'] != null ) {
00032       Locale::set( $_POST['locale'] );
00033     }
00034     else {
00035       Locale::set( Config::get('locale', 'en-us' ) );
00036     }
00037     $this->theme->locale = Locale::get();
00038     $this->handler_vars['locale'] = Locale::get();
00039 
00043     if ( ! $this->check_htaccess() ) {
00044       $this->handler_vars['file_contents'] = htmlentities( implode( "\n", $this->htaccess() ) );
00045       $this->display( 'htaccess' );
00046     }
00047 
00048     // Dispatch AJAX requests.
00049     if ( isset( $_POST['ajax_action'] ) ) {
00050       switch ( $_POST['ajax_action'] ) {
00051         case 'check_mysql_credentials':
00052           self::ajax_check_mysql_credentials();
00053           exit;
00054           break;
00055         case 'check_pgsql_credentials':
00056           self::ajax_check_pgsql_credentials();
00057           exit;
00058           break;
00059         case 'check_sqlite_credentials':
00060           self::ajax_check_sqlite_credentials();
00061           exit;
00062           break;
00063       }
00064     }
00065     // set the default values now, which will be overriden as we go
00066     $this->form_defaults();
00067 
00068     if ( ! $this->meets_all_requirements() ) {
00069       $this->display( 'requirements' );
00070     }
00071 
00075     Plugins::register( Method::create( '\Habari\InstallHandler', 'ajax_check_mysql_credentials' ), 'ajax_', 'check_mysql_credentials' );
00076     Plugins::register( Method::create( '\Habari\InstallHandler', 'ajax_check_pgsql_credentials' ), 'ajax_', 'check_pgsql_credentials' );
00077 
00081     if ( ( ! file_exists( Site::get_dir( 'config_file' ) ) ) && ( ! isset( $_POST['admin_username'] ) ) ) {
00082       // no config file, and no HTTP POST
00083       $this->display( 'db_setup' );
00084     }
00085 
00086     // try to load any values that might be defined in config.php
00087     if ( file_exists( Site::get_dir( 'config_file' ) ) ) {
00088       include( Site::get_dir( 'config_file' ) );
00089 
00090       // check for old style config (global variable, pre-dates registry based config
00091       if ( !Config::exists( 'db_connection' ) && isset( $db_connection ) ) {
00092         // found old style config...
00093 
00094         // set up registry:
00095         Config::set( 'db_connection', $db_connection );
00096 
00097         // assign handler vars (for config file write)
00098         $this->set_handler_vars_from_db_connection();
00099 
00100         // write new config file
00101         if ( $this->write_config_file( true ) ) {
00102           // successful, so redirect:
00103           Utils::redirect( Site::get_url( 'site' ) );
00104         }
00105       }
00106 
00107       if ( Config::exists( 'db_connection' ) ) {
00108         $this->set_handler_vars_from_db_connection();
00109       }
00110       // if a $blog_data array exists in config.php, use it
00111       // to pre-load values for the installer
00112       // ** this is completely optional **
00113       if ( Config::exists( 'blog_data' ) ) {
00114         $blog_data = Config::get('blog_data');
00115         foreach ( $blog_data as $blog_datum => $value ) {
00116           $this->handler_vars[$blog_datum] = $value;
00117         }
00118       }
00119     }
00120 
00121     // now merge in any HTTP POST values that might have been sent
00122     // these will override the defaults and the config.php values
00123     $this->handler_vars = $this->handler_vars->merge( $_POST );
00124 
00125     // we need details for the admin user to install
00126     if ( ( '' == $this->handler_vars['admin_username'] )
00127       || ( '' == $this->handler_vars['admin_pass1'] )
00128       || ( '' == $this->handler_vars['admin_pass2'] )
00129       || ( '' == $this->handler_vars['admin_email'] )
00130     ) {
00131       // if none of the above are set, display the form
00132       $this->display( 'db_setup' );
00133     }
00134 
00135     $db_type = $this->handler_vars['db_type'];
00136     if ( (!Config::exists('db_connection') || Config::get( 'db_connection' )->connection_string == '') && ($db_type == 'mysql' || $db_type == 'pgsql') ) {
00137       $this->handler_vars['db_host'] = $_POST["{$db_type}_db_host"];
00138       $this->handler_vars['db_user'] = $_POST["{$db_type}_db_user"];
00139       $this->handler_vars['db_pass'] = $_POST->raw( "{$db_type}_db_pass" );
00140       $this->handler_vars['db_schema'] = $_POST["{$db_type}_db_schema"];
00141     }
00142 
00143     // we got here, so we have all the info we need to install
00144 
00145     // make sure the admin password is correct
00146     if ( $this->handler_vars['admin_pass1'] !== $this->handler_vars['admin_pass2'] ) {
00147       $this->theme->assign( 'form_errors', array( 'password_mismatch' => _t( 'Password mis-match.' ) ) );
00148       $this->display( 'db_setup' );
00149     }
00150 
00151     // don't accept emails with control characters
00152     if ( !ctype_print($this->handler_vars['admin_email']) ) {
00153       $this->theme->assign( 'form_errors', array( 'admin_email' => _t( 'Only printable characters are allowed.' ) ) );
00154       $this->display( 'db_setup' );
00155     }
00156 
00157     // check whether prefix is valid
00158     if ( isset( $this->handler_vars['table_prefix'] ) && ( preg_replace( '/[^a-zA-Z_]/', '', $this->handler_vars['table_prefix'] ) !== $this->handler_vars['table_prefix'] ) ) {
00159       $this->theme->assign( 'form_errors', array( 'table_prefix' => _t( 'Allowed characters are A-Z, a-z and "_".' ) ) );
00160       $this->display( 'db_setup' );
00161     }
00162 
00163     // Make sure we still have a valid connection
00164     if ( ! call_user_func( array( $this, "check_{$db_type}" ) ) ) {
00165       $this->display( 'db_setup' );
00166     }
00167 
00168     // try to write the config file
00169     if ( ! $this->write_config_file() ) {
00170       $this->theme->assign( 'form_errors', array( 'write_file'=>_t( 'Could not write config.php file&hellip;' ) ) );
00171       $this->display( 'db_setup' );
00172     }
00173 
00174     // try to install the database
00175     if ( ! $this->install_db() ) {
00176       // the installation failed for some reason.
00177       // re-display the form
00178       $this->display( 'db_setup' );
00179     }
00180 
00181     // activate plugins on POST
00182     if ( count( $_POST ) > 0 ) {
00183       $this->activate_theme();
00184       $this->activate_plugins();
00185     }
00186 
00187 
00188 
00189     // Installation complete. Secure sqlite if it was chosen as the database type to use
00190     if ( $db_type == 'sqlite' ) {
00191       if ( !$this->secure_sqlite() ) {
00192         $this->theme->sqlite_contents = implode( "\n", $this->sqlite_contents() );
00193         $this->display( 'sqlite' );
00194       }
00195     }
00196 
00197     EventLog::log( _t( 'Habari successfully installed.' ), 'info', 'default', 'habari' );
00198     Utils::redirect( Site::get_url( 'site' ) );
00199   }
00200 
00204   public function get_plugins()
00205   {
00206     $all_plugins = Plugins::list_all();
00207     $recommended_list = array(
00208       'coredashmodules.plugin.php',
00209       'coreblocks.plugin.php',
00210       'habarisilo.plugin.php',
00211       'pingback.plugin.php',
00212       'spamchecker.plugin.php',
00213       'undelete.plugin.php',
00214       'autop.plugin.php'
00215     );
00216 
00217     foreach ( $all_plugins as $file ) {
00218       $plugin = array();
00219       $plugin_id = Plugins::id_from_file( $file );
00220       $plugin['plugin_id'] = $plugin_id;
00221       $plugin['file'] = $file;
00222 
00223       $error = '';
00224       if ( Utils::php_check_file_syntax( $file, $error ) ) {
00225         $plugin['debug'] = false;
00226         // get this plugin's info()
00227         $plugin['active'] = false;
00228         $plugin['verb'] = _t( 'Activate' );
00229         $plugin['actions'] = array();
00230         $plugin['info'] = Plugins::load_info( $file );
00231         $plugin['recommended'] = in_array( basename( $file ), $recommended_list );
00232         $plugin['requires'] = isset($plugin['info']->requires) ? self::get_feature_list($plugin['info']->requires->children()) : '';
00233         $plugin['provides'] = isset($plugin['info']->provides) ? self::get_feature_list($plugin['info']->provides->children()) : '';
00234         $plugin['conflicts'] = isset($plugin['info']->conflicts) ? self::get_feature_list($plugin['info']->conflicts->children()) : '';
00235       }
00236       else {
00237         // We can't get the plugin info due to an error
00238         // This will show up in the plugin panel, just continue through install
00239         continue;
00240       }
00241 
00242       $plugins[$plugin_id] = $plugin;
00243     }
00244 
00245     return $plugins;
00246   }
00247 
00251   public function get_themes()
00252   {
00253     $all_themes = Themes::get_all_data();
00254 
00255     return $all_themes;
00256   }
00257 
00263   private function display( $template_name )
00264   {
00265     foreach ( $this->handler_vars as $key=>$value ) {
00266       $this->theme->assign( $key, $value );
00267     }
00268 
00269     $this->theme->assign( 'themes', $this->get_themes() );
00270 
00271     $this->theme->assign( 'plugins', $this->get_plugins() );
00272 
00273     $this->theme->display( $template_name );
00274 
00275     exit;
00276   }
00277 
00278   /*
00279    * sets default values for the form
00280    */
00281   public function form_defaults()
00282   {
00283     $formdefaults['db_type'] = 'mysql';
00284     $formdefaults['db_host'] = 'localhost';
00285     $formdefaults['db_user'] = '';
00286     $formdefaults['db_pass'] = '';
00287     $formdefaults['db_file'] = 'habari.db';
00288     $formdefaults['db_schema'] = 'habari';
00289     $formdefaults['table_prefix'] = isset( Config::get( 'db_connection' )->prefix ) ? Config::get( 'db_connection' )->prefix : 'habari__';
00290     $formdefaults['admin_username'] = 'admin';
00291     $formdefaults['admin_pass1'] = '';
00292     $formdefaults['admin_pass2'] = '';
00293     $formdefaults['blog_title'] = 'My Habari';
00294     $formdefaults['admin_email'] = '';
00295 
00296     foreach ( $formdefaults as $key => $value ) {
00297       if ( !isset( $this->handler_vars[$key] ) ) {
00298         $this->handler_vars[$key] = $value;
00299       }
00300     }
00301   }
00302 
00309   private function meets_all_requirements()
00310   {
00311     // Required extensions, this list will augment with time
00312     // Even if they are enabled by default, it seems some install turn them off
00313     // We use the URL in the Installer template to link to the installation page
00314     $required_extensions = array(
00315       'date' => 'http://php.net/datetime',
00316       'pdo' => 'http://php.net/pdo',
00317       'hash' => 'http://php.net/hash',
00318       'json' => 'http://php.net/json',
00319       'mbstring' => 'http://php.net/mbstring',
00320       'pcre' => 'http://php.net/pcre',
00321       'session' => 'http://php.net/session',
00322       'simplexml' => 'http://php.net/simplexml',
00323       'spl' => 'http://php.net/spl',
00324       'tokenizer' => 'http://php.net/tokenizer',
00325       );
00326     $requirements_met = true;
00327 
00328     /* Check versions of PHP */
00329     $php_version_ok = version_compare( phpversion(), MIN_PHP_VERSION, '>=' );
00330     $this->theme->assign( 'php_version_ok', $php_version_ok );
00331     $this->theme->assign( 'PHP_OS', PHP_OS );;
00332     $this->theme->assign( 'PHP_VERSION', phpversion() );
00333     if ( ! $php_version_ok ) {
00334       $requirements_met = false;
00335     }
00336     /* Check for mod_rewrite on Apache */
00337     $mod_rewrite = true;
00338     if ( function_exists( 'apache_get_modules' ) && !in_array( 'mod_rewrite', apache_get_modules() ) ) {
00339       $requirements_met = false;
00340       $mod_rewrite = false;
00341     }
00342     $this->theme->assign( 'mod_rewrite', $mod_rewrite );
00343     /* Check for required extensions */
00344     $missing_extensions = array();
00345     foreach ( $required_extensions as $ext_name => $ext_url ) {
00346       if ( !extension_loaded( $ext_name ) ) {
00347         $missing_extensions[$ext_name] = $ext_url;
00348         $requirements_met = false;
00349       }
00350     }
00351     $this->theme->assign( 'missing_extensions', $missing_extensions );
00352 
00353     if ( extension_loaded( 'pdo' ) ) {
00354       /* Check for PDO drivers */
00355       $pdo_drivers = \PDO::getAvailableDrivers();
00356       if ( ! empty( $pdo_drivers ) ) {
00357         $pdo_drivers = array_combine( $pdo_drivers, $pdo_drivers );
00358         // Include only those drivers that we include database support for
00359         $pdo_schemas = array_map( 'basename', Utils::glob( HABARI_PATH . '/system/schema/*', GLOB_ONLYDIR ) );
00360         $pdo_schemas = array_combine( $pdo_schemas, $pdo_schemas );
00361 
00362         $pdo_drivers = array_intersect_key(
00363           $pdo_drivers,
00364           $pdo_schemas
00365         );
00366         $pdo_missing_drivers = array_diff(
00367           $pdo_schemas,
00368           $pdo_drivers
00369         );
00370 
00371         $pdo_drivers_ok = count( $pdo_drivers );
00372         $this->theme->assign( 'pdo_drivers_ok', $pdo_drivers_ok );
00373         $this->theme->assign( 'pdo_drivers', $pdo_drivers );
00374         $this->theme->assign( 'pdo_missing_drivers', $pdo_missing_drivers );
00375       }
00376       else {
00377         $pdo_drivers_ok = false;
00378         $this->theme->assign( 'pdo_drivers_ok', $pdo_drivers_ok );
00379       }
00380       if ( ! $pdo_drivers_ok ) {
00381         $requirements_met = false;
00382       }
00383     }
00384     else {
00385       $this->theme->assign( 'pdo_drivers_ok', false );
00386       $this->theme->assign( 'pdo_drivers', array() );
00387       $requirements_met = false;
00388     }
00389 
00390     if ( $requirements_met && ! @preg_match( '/\p{L}/u', 'a' ) ) {
00391       $requirements_met = false;
00392     }
00393 
00400     $this->theme->assign( 'local_writable', true );
00401 
00402     return $requirements_met;
00403   }
00404 
00412   private function install_db()
00413   {
00414     $db_host = $this->handler_vars['db_host'];
00415     $db_type = $this->handler_vars['db_type'];
00416     $db_schema = $this->handler_vars['db_schema'];
00417     $db_user = $this->handler_vars['db_user'];
00418     $db_pass = $this->handler_vars['db_pass'];
00419 
00420     switch ( $db_type ) {
00421       case 'mysql':
00422       case 'pgsql':
00423         // MySQL & PostgreSQL requires specific connection information
00424         if ( empty( $db_user ) ) {
00425           $this->theme->assign( 'form_errors', array( "{$db_type}_db_user"=>_t( 'User is required.' ) ) );
00426           return false;
00427         }
00428         if ( empty( $db_schema ) ) {
00429           $this->theme->assign( 'form_errors', array( "{$db_type}_db_schema"=>_t( 'Name for database is required.' ) ) );
00430           return false;
00431         }
00432         if ( empty( $db_host ) ) {
00433           $this->theme->assign( 'form_errors', array( "{$db_type}_db_host"=>_t( 'Host is required.' ) ) );
00434           return false;
00435         }
00436         break;
00437       case 'sqlite':
00438         // If this is a SQLite database, let's check that the file
00439         // exists and that we can access it.
00440         if ( ! $this->check_sqlite() ) {
00441           return false;
00442         }
00443         break;
00444     }
00445 
00446     if ( isset( $this->handler_vars['table_prefix'] ) ) {
00447       // store prefix in the Config singleton so DatabaseConnection can access it
00448       Config::set( 'db_connection', array( 'prefix' => $this->handler_vars['table_prefix'], ) );
00449     }
00450 
00451     if ( ! $this->connect_to_existing_db() ) {
00452       $this->theme->assign( 'form_errors', array( "{$db_type}_db_user"=>_t( 'Problem connecting to supplied database credentials' ) ) );
00453       return false;
00454     }
00455 
00456     DB::begin_transaction();
00457     /* Let's install the DB tables now. */
00458     $create_table_queries = $this->get_create_table_queries(
00459       $this->handler_vars['db_type'],
00460       $this->handler_vars['table_prefix'],
00461       $this->handler_vars['db_schema']
00462     );
00463     DB::clear_errors();
00464     DB::dbdelta( $create_table_queries, true, true, true );
00465 
00466     if ( DB::has_errors() ) {
00467       $error = DB::get_last_error();
00468       $this->theme->assign( 'form_errors', array( 'db_host'=>_t( 'Could not create schema tables&hellip; %s', array( $error['message'] ) ) ) );
00469       DB::rollback();
00470       return false;
00471     }
00472 
00473     // Cool.  DB installed. Create the default options
00474     // but check first, to make sure
00475     if ( ! Options::get( 'installed' ) ) {
00476       if ( ! $this->create_default_options() ) {
00477         $this->theme->assign( 'form_errors', array( 'options'=>_t( 'Problem creating default options' ) ) );
00478         DB::rollback();
00479         return false;
00480       }
00481     }
00482 
00483     // Create the Tags vocabulary
00484     if ( ! $this->create_tags_vocabulary() ) {
00485       $this->theme->assign( 'form_errors', array( 'options'=>_t( 'Problem creating tags vocabulary' ) ) );
00486       DB::rollback();
00487       return false;
00488     }
00489 
00490     // Create the standard post types and statuses
00491     if ( ! $this->create_base_post_types() ) {
00492       $this->theme->assign( 'form_errors', array( 'options'=>_t( 'Problem creating base post types' ) ) );
00493       DB::rollback();
00494       return false;
00495     }
00496 
00497     if ( ! $this->create_base_comment_types() ) {
00498       $this->theme->assign( 'form_errors', array( 'options'=>_t( 'Problem creating base comment types and statuses' ) ) );
00499       DB::rollback();
00500       return false;
00501     }
00502 
00503     // Let's setup the admin user and group now.
00504     // But first, let's make sure that no users exist
00505     $all_users = Users::get_all();
00506     if ( count( $all_users ) < 1 ) {
00507       $user = $this->create_admin_user();
00508       if ( ! $user ) {
00509         $this->theme->assign( 'form_errors', array( 'admin_user'=>_t( 'Problem creating admin user.' ) ) );
00510         DB::rollback();
00511         return false;
00512       }
00513       $admin_group = $this->create_admin_group( $user );
00514       if ( ! $admin_group ) {
00515         $this->theme->assign( 'form_errors', array( 'admin_user'=>_t( 'Problem creating admin group.' ) ) );
00516         DB::rollback();
00517         return false;
00518       }
00519       // create default tokens
00520       ACL::rebuild_permissions( $user );
00521     }
00522 
00523     // create a first post, if none exists
00524     if ( ! Posts::get( array( 'count' => 1 ) ) ) {
00525       if ( ! $this->create_first_post() ) {
00526         $this->theme->assign( 'form_errors', array( 'post'=>_t( 'Problem creating first post.' ) ) );
00527         DB::rollback();
00528         return false;
00529       }
00530     }
00531 
00532     /* Post::save_tags() closes transaction, until we fix that, check and reconnect if needed */
00533     if ( !DB::in_transaction() ) {
00534       DB::begin_transaction();
00535     }
00536 
00537     /* Store current DB version so we don't immediately run dbdelta. */
00538     Version::save_dbversion();
00539 
00540     /* Ready to roll. */
00541     DB::commit();
00542     return true;
00543   }
00544 
00549   public function check_mysql()
00550   {
00551     // Can we connect to the DB?
00552     $pdo = 'mysql:host=' . $this->handler_vars['db_host'] . ';dbname=' . $this->handler_vars['db_schema'];
00553     if ( isset( $this->handler_vars['table_prefix'] ) ) {
00554       // store prefix in the Config singleton so DatabaseConnection can access it
00555       Config::set( 'db_connection', array( 'prefix' => $this->handler_vars['table_prefix'], ) );
00556     }
00557     try {
00558       $connect = DB::connect( $pdo, $this->handler_vars['db_user'], html_entity_decode( $this->handler_vars['db_pass'] ) );
00559       return true;
00560     }
00561     catch( \PDOException $e ) {
00562       if ( strpos( $e->getMessage(), '[1045]' ) ) {
00563         $this->theme->assign( 'form_errors', array( 'mysql_db_pass' => _t( 'Access denied. Make sure these credentials are valid.' ) ) );
00564       }
00565       else if ( strpos( $e->getMessage(), '[1049]' ) ) {
00566         $this->theme->assign( 'form_errors', array( 'mysql_db_schema' => _t( 'That database does not exist.' ) ) );
00567       }
00568       else if ( strpos( $e->getMessage(), '[2005]' ) ) {
00569         $this->theme->assign( 'form_errors', array( 'mysql_db_host' => _t( 'Could not connect to host.' ) ) );
00570       }
00571       else {
00572         $this->theme->assign( 'form_errors', array( 'mysql_db_host' => $e->getMessage() ) );
00573       }
00574       return false;
00575     }
00576   }
00577 
00582   public function check_pgsql()
00583   {
00584     // Can we connect to the DB?
00585     $pdo = 'pgsql:host=' . $this->handler_vars['db_host'] . ';dbname=' . $this->handler_vars['db_schema'];
00586     if ( isset( $this->handler_vars['table_prefix'] ) ) {
00587       // store prefix in the Config singleton so DatabaseConnection can access it
00588       Config::set( 'db_connection', array( 'prefix' => $this->handler_vars['table_prefix'], ) );
00589     }
00590     try {
00591       $connect = DB::connect( $pdo, $this->handler_vars['db_user'], html_entity_decode( $this->handler_vars['db_pass'] ) );
00592       return true;
00593     }
00594     catch( \PDOException $e ) {
00595       if ( strpos( $e->getMessage(), '[1045]' ) ) {
00596         $this->theme->assign( 'form_errors', array( 'pgsql_db_pass' => _t( 'Access denied. Make sure these credentials are valid.' ) ) );
00597       }
00598       else if ( strpos( $e->getMessage(), '[1049]' ) ) {
00599         $this->theme->assign( 'form_errors', array( 'pgsql_db_schema' => _t( 'That database does not exist.' ) ) );
00600       }
00601       else if ( strpos( $e->getMessage(), '[2005]' ) ) {
00602         $this->theme->assign( 'form_errors', array( 'pgsql_db_host' => _t( 'Could not connect to host.' ) ) );
00603       }
00604       else {
00605         $this->theme->assign( 'form_errors', array( 'pgsql_db_host' => $e->getMessage() ) );
00606       }
00607       return false;
00608     }
00609   }
00610 
00615   private function check_sqlite()
00616   {
00617     $db_file = $this->handler_vars['db_file'];
00618     if ( $db_file == basename( $db_file ) ) { // The filename was given without a path
00619       $db_file = Site::get_path( 'user', true ) . $db_file;
00620     }
00621     if ( file_exists( $db_file ) && is_writable( $db_file ) && is_writable( dirname( $db_file ) ) ) {
00622       // the file exists, and is writable.  We're all set
00623       return true;
00624     }
00625 
00626     // try to figure out what the problem is.
00627     if ( file_exists( $db_file ) ) {
00628       // the DB file exists, why can't we access it?
00629       if ( ! is_writable( $db_file ) ) {
00630         $this->theme->assign( 'form_errors', array( 'db_file'=>_t( 'Cannot write to %s. The SQLite data file is not writable by the web server.', array( $db_file ) ) ) );
00631         return false;
00632       }
00633       if ( ! is_writable( dirname( $db_file ) ) ) {
00634         $this->theme->assign( 'form_errors', array( 'db_file'=>_t( 'Cannot write to %s directory. SQLite requires that the directory that holds the DB file be writable by the web server.', array( $db_file ) ) ) );
00635         return false;
00636       }
00637     }
00638 
00639     if ( ! file_exists( $db_file ) ) {
00640       // let's see if the directory is writable
00641       // so that we could create the file
00642       if ( ! is_writable( dirname( $db_file ) ) ) {
00643         $this->theme->assign( 'form_errors', array( 'db_file'=>_t( 'Cannot write to %s directory. The SQLite data file does not exist, and it cannot be created in the specified directory. SQLite requires that the directory containing the database file be writable by the web server.', array( $db_file ) ) ) );
00644         return false;
00645       }
00646     }
00647     return true;
00648   }
00649 
00656   private function connect_to_existing_db()
00657   {
00658     if ( $config = $this->get_config_file() ) {
00659       $config = preg_replace( '/<\\?php(.*)\\?'.'>/ims', '$1', $config );
00660       // Update the db_connection from the config that is about to be written:
00661       eval( $config );
00662 
00663       /* Attempt to connect to the database host */
00664       try {
00665         DB::connect();
00666         return true;
00667       }
00668       catch( \PDOException $e ) {
00669         $this->theme->assign( 'form_errors', array( 'db_user'=>_t( 'Problem connecting to supplied database credentials' ) ) );
00670         return false;
00671 
00672       }
00673     }
00674     // If we couldn't create the config from the template, return an error
00675     return false;
00676   }
00677 
00683   private function create_admin_user()
00684   {
00685     $admin_username = $this->handler_vars['admin_username'];
00686     $admin_email = $this->handler_vars['admin_email'];
00687     $admin_pass = $this->handler_vars['admin_pass1'];
00688 
00689     if ( $admin_pass{0} == '{' ) {
00690       // looks like we might have a crypted password
00691       $password = $admin_pass;
00692 
00693       // but let's double-check
00694       $algo = strtolower( substr( $admin_pass, 1, 3 ) );
00695       if ( ( 'ssh' != $algo ) && ( 'sha' != $algo ) ) {
00696         // we do not have a crypted password
00697         // so let's encrypt it
00698         $password = Utils::crypt( $admin_pass );
00699       }
00700     }
00701     else {
00702       $password = Utils::crypt( $admin_pass );
00703     }
00704 
00705     // Insert the admin user
00706     $user = User::create( array (
00707       'username' => $admin_username,
00708       'email' => $admin_email,
00709       'password' => $password
00710     ) );
00711 
00712     return $user;
00713   }
00714 
00721   private function create_admin_group( $user )
00722   {
00723     // Create the admin group
00724     $group = UserGroup::create( array( 'name' => _t( 'admin' ) ) );
00725     if ( ! $group ) {
00726       return false;
00727     }
00728     $group->add( $user->id );
00729     return $group;
00730   }
00731 
00732   private function create_anonymous_group()
00733   {
00734     // Create the anonymous group
00735     $group = UserGroup::create( array( 'name' => _t( 'anonymous' ) ) );
00736     if ( ! $group ) {
00737       return false;
00738     }
00739     $group->grant( 'post_entry', 'read' );
00740     $group->grant( 'post_page', 'read' );
00741 
00742     // Add the anonymous user to the anonymous group
00743     $group->add( 0 );
00744   }
00745 
00749   private function create_default_options()
00750   {
00751     // Let's prepare the EventLog here, first
00752     EventLog::register_type( 'default', 'habari' );
00753     EventLog::register_type( 'user', 'habari' );
00754     EventLog::register_type( 'authentication', 'habari' );
00755     EventLog::register_type( 'content', 'habari' );
00756     EventLog::register_type( 'comment', 'habari' );
00757 
00758     // Create the default options
00759     $defaults = array(
00760       'installed' => true,
00761       'title' => $this->handler_vars['blog_title'],
00762       'pagination' => 5,
00763       'atom_entries' => 5,
00764       'theme_name' => 'Charcoal',
00765       'theme_dir' => 'charcoal',
00766       'comments_require_id' => 1,
00767       'locale' => $this->handler_vars['locale'],
00768       'timezone' => 'UTC',
00769       'dateformat' => 'Y-m-d',
00770       'timeformat' => 'g:i a',
00771       'log_min_severity' => 3,
00772       'spam_percentage' => 100,
00773       // generate a random-ish number to use as the salt for
00774       // a SHA1 hash that will serve as the unique identifier for
00775       // this installation.  Also for use in cookies
00776       'GUID' => sha1( Utils::nonce() ),
00777       'private-GUID' => sha1( Utils::nonce() ),
00778       'public-GUID' => sha1( Utils::nonce() ),
00779     );
00780 
00781     // Get values from config installation profile
00782     foreach ( $this->handler_vars as $id => $value ) {
00783       if ( preg_match( '/option_(.+)/u', $id, $matches ) ) {
00784         $defaults[$matches[1]] = $value;
00785       }
00786     }
00787 
00788     // Apply values to the options table and activate the default theme
00789     foreach($defaults as $key => $value) {
00790       Options::set($key, $value);
00791     }
00792 
00793     // Add the cronjob to trim the log so that it doesn't get too big
00794     CronTab::add_daily_cron( 'trim_log', Method::create( '\Habari\EventLog', 'trim' ), _t( 'Trim the log table' ) );
00795 
00796     // Add the cronjob to check for plugin updates
00797     CronTab::add_daily_cron( 'update_check', Method::create( '\Habari\Update', 'cron' ), _t( 'Perform a check for plugin updates.' ) );
00798 
00799     // Add the cronjob to run garbage collection on expired sessions
00800     CronTab::add_hourly_cron( 'session_gc', Method::create( '\Habari\Session', 'gc' ), _t( 'Perform session garbage collection.' ) );
00801 
00802     return true;
00803   }
00804 
00808   private function create_base_post_types()
00809   {
00810     // first, let's create our default post types of
00811     // "entry" and "page"
00812     Post::add_new_type( 'entry' );
00813     Post::add_new_type( 'page' );
00814 
00815     // now create post statuses for
00816     // "published" and "draft"
00817     // Should "private" status be added here, or through a plugin?
00818     Post::add_new_status( 'draft' );
00819     Post::add_new_status( 'published' );
00820     Post::add_new_status( 'scheduled', true );
00821 
00822     return true;
00823   }
00824 
00828   private function create_tags_vocabulary()
00829   {
00830     $vocabulary = new Vocabulary( array( 'name' => 'tags', 'description' => 'Habari\'s tags implementation', 'features' => array( 'multiple', 'free' ) ) );
00831     $vocabulary->insert();
00832 
00833     return true;
00834   }
00835 
00839   private function create_first_post()
00840   {
00841     $users = Users::get();
00842     Post::create( array(
00843       'title' => 'Habari',
00844       'content' => _t( 'This site is running <a href="http://habariproject.org/">Habari</a>, a state-of-the-art publishing platform!  Habari is a community-driven project created and supported by people from all over the world.  Please visit <a href="http://habariproject.org/">http://habariproject.org/</a> to find out more!' ),
00845       'user_id' => $users[0]->id,
00846       'status' => Post::status( 'published' ),
00847       'content_type' => Post::type( 'entry' ),
00848       'tags' => 'habari',
00849     ) );
00850 
00851     return true;
00852   }
00853 
00861   private function get_create_table_queries( $db_type, $table_prefix, $db_schema )
00862   {
00863     /* Grab the queries from the RDBMS schema file */
00864     $file_path = HABARI_PATH . "/system/schema/{$db_type}/schema.sql";
00865     $schema_sql = trim( file_get_contents( $file_path ), "\r\n " );
00866     $schema_sql = str_replace( '{$schema}', $db_schema, $schema_sql );
00867     $schema_sql = str_replace( '{$prefix}', $table_prefix, $schema_sql );
00868 
00869     /*
00870      * Just in case anyone creates a schema file with separate statements
00871      * not separated by two newlines, let's clean it here...
00872      * Likewise, let's clean up any separations of *more* than two newlines
00873      */
00874     $schema_sql = str_replace( array( "\r\n", "\r", ), array( "\n", "\n" ), $schema_sql );
00875     $schema_sql = preg_replace( "/;\n([^\n])/", ";\n\n$1", $schema_sql );
00876     $schema_sql = preg_replace( "/\n{3,}/", "\n\n", $schema_sql );
00877     $queries = preg_split( '/(\\r\\n|\\r|\\n)\\1/', $schema_sql );
00878     return $queries;
00879   }
00880 
00881 
00887   private function get_create_schema_and_user_queries()
00888   {
00889     $db_host = $this->handler_vars['db_host'];
00890     $db_type = $this->handler_vars['db_type'];
00891     $db_schema = $this->handler_vars['db_schema'];
00892     $db_user = $this->handler_vars['db_user'];
00893     $db_pass = $this->handler_vars['db_pass'];
00894 
00895     $queries = array();
00896     switch ( $db_type ) {
00897       case 'mysql':
00898         $queries[] = 'CREATE DATABASE ' . $db_schema . ';';
00899         $queries[] = 'GRANT ALL ON ' . $db_schema . '.* TO \'' . $db_user . '\'@\'' . $db_host . '\' ' .
00900         'IDENTIFIED BY \'' . $db_pass . '\';';
00901         break;
00902       case 'pgsql':
00903         $queries[] = 'CREATE DATABASE ' . $db_schema . ';';
00904         $queries[] = 'GRANT ALL ON DATABASE ' . $db_schema . ' TO ' . $db_user . ';';
00905         break;
00906       default:
00907         die( _t( 'currently unsupported.' ) );
00908     }
00909     return $queries;
00910   }
00911 
00917   private function get_config_file()
00918   {
00919     if ( ! ( $file_contents = file_get_contents( HABARI_PATH . "/system/schema/" . $this->handler_vars['db_type'] . "/config.php" ) ) ) {
00920       return false;
00921     }
00922 
00923     $vars = array();
00924     foreach ( $this->handler_vars as $k => $v ) {
00925       $vars[$k] = addslashes( $v );
00926     }
00927     $keys = array();
00928     foreach ( array_keys( $vars ) as $v ) {
00929       $keys[] = Utils::map_array( $v );
00930     }
00931 
00932     $file_contents = str_replace(
00933       $keys,
00934       $vars,
00935       $file_contents
00936     );
00937     return $file_contents;
00938   }
00939 
00947   private function write_config_file( $ignore_registry = false )
00948   {
00949     // first, check if a config.php file exists
00950     if ( file_exists( Site::get_dir( 'config_file' ) ) ) {
00951       // set the defaults for comparison
00952       $db_host = $this->handler_vars['db_host'];
00953       $db_file = $this->handler_vars['db_file'];
00954       $db_type = $this->handler_vars['db_type'];
00955       $db_schema = $this->handler_vars['db_schema'];
00956       $db_user = $this->handler_vars['db_user'];
00957       $db_pass = $this->handler_vars['db_pass'];
00958       $table_prefix = $this->handler_vars['table_prefix'];
00959 
00960       // set the connection string
00961       switch ( $db_type ) {
00962         case 'mysql':
00963           $connection_string = "$db_type:host=$db_host;dbname=$db_schema";
00964           break;
00965         case 'pgsql':
00966           $connection_string = "$db_type:host=$db_host dbname=$db_schema";
00967           break;
00968         case 'sqlite':
00969           $connection_string = "$db_type:$db_file";
00970           break;
00971       }
00972 
00973       // load the config.php file
00974       include( Site::get_dir( 'config_file' ) );
00975 
00976       // and now we compare the values defined there to
00977       // the values POSTed to the installer
00978       if ( !$ignore_registry && Config::exists( 'db_connection' ) &&
00979         ( Config::get( 'db_connection' )->connection_string == $connection_string )
00980         && ( Config::get( 'db_connection' )->username == $db_user )
00981         && ( Config::get( 'db_connection' )->password == $db_pass )
00982         && ( Config::get( 'db_connection' )->prefix == $table_prefix )
00983       ) {
00984         // the values are the same, so don't bother
00985         // trying to write to config.php
00986         return true;
00987       }
00988     }
00989     if ( ! ( $file_contents = file_get_contents( HABARI_PATH . "/system/schema/" . $this->handler_vars['db_type'] . "/config.php" ) ) ) {
00990       return false;
00991     }
00992     if ( $file_contents = html_entity_decode( $this->get_config_file() ) ) {
00993       if ( $file = @fopen( Site::get_dir( 'config_file' ), 'w' ) ) {
00994         if ( fwrite( $file, $file_contents, strlen( $file_contents ) ) ) {
00995           fclose( $file );
00996           return true;
00997         }
00998       }
00999       $this->handler_vars['config_file'] = Site::get_dir( 'config_file' );
01000       $this->handler_vars['file_contents'] = Utils::htmlspecialchars( $file_contents );
01001       $this->display( 'config' );
01002       return false;
01003     }
01004     return false;  // Only happens when config.php template does not exist.
01005   }
01006 
01007 
01008   public function activate_theme()
01009   {
01010     $theme_dir = $this->handler_vars['theme'];
01011 
01012     // set the user_id in the session in case theme activation methods need it
01013     if ( ! $u = User::get_by_name( $this->handler_vars['admin_username'] ) ) {
01014       // @todo die gracefully
01015       die( _t( 'No admin user found' ) );
01016     }
01017     $u->remember();
01018 
01019     $themes = Themes::get_all_data();
01020     $theme = $themes[$theme_dir];
01021     Themes::activate_theme((string)$theme['info']->name, $theme_dir);
01022 
01023     // unset the user_id session variable
01024     Session::clear_userid( $_SESSION['user_id'] );
01025     unset( $_SESSION['user_id'] );
01026   }
01027 
01028   public function activate_plugins()
01029   {
01030     // extract checked plugin IDs from $_POST
01031     $plugin_ids = array();
01032     foreach ( $this->handler_vars as $id => $activate ) {
01033       if ( preg_match( '/plugin_([a-f0-9]{8})/u', $id, $matches ) && $activate ) {
01034         $plugin_ids[] = $matches[1];
01035       }
01036       elseif ( preg_match( '/plugin_(.+)/u', $id, $matches ) && $activate ) {
01037         $plugin_ids[] = $matches[1];
01038       }
01039     }
01040 
01041     // set the user_id in the session in case plugin activation methods need it
01042     if ( ! $u = User::get_by_name( $this->handler_vars['admin_username'] ) ) {
01043       // @todo die gracefully
01044       die( _t( 'No admin user found' ) );
01045     }
01046     $u->remember();
01047 
01048     // loop through all plugins to find matching plugin files
01049     $plugin_files = Plugins::list_all();
01050     foreach ( $plugin_files as $file ) {
01051       if ( in_array( basename($file), $plugin_ids ) ) {
01052         Plugins::activate_plugin( $file );
01053         continue;
01054       }
01055       $id = Plugins::id_from_file( $file );
01056       if ( in_array( $id, $plugin_ids ) ) {
01057         Plugins::activate_plugin( $file );
01058       }
01059     }
01060 
01061     // unset the user_id session variable
01062     Session::clear_userid( $_SESSION['user_id'] );
01063     unset( $_SESSION['user_id'] );
01064   }
01065 
01069   public function htaccess()
01070   {
01071     $htaccess = array(
01072       'open_block' => '### HABARI START',
01073       'engine_on' => 'RewriteEngine On',
01074       'rewrite_cond_f' => 'RewriteCond %{REQUEST_FILENAME} !-f',
01075       'rewrite_cond_d' => 'RewriteCond %{REQUEST_FILENAME} !-d',
01076       'rewrite_favicon' => 'RewriteCond %{REQUEST_URI} !=/favicon.ico',
01077       'rewrite_base' => '#RewriteBase /',
01078       'rewrite_rule' => 'RewriteRule . index.php [PT]',
01079       'hide_habari' => 'RewriteRule ^(system/(classes|handlers|locale|schema|$)) index.php [PT]',
01080       //'http_auth' => 'RewriteRule .* [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] # Make sure HTTP auth works in PHP-CGI configs',
01081       'close_block' => '### HABARI END',
01082     );
01083     $rewrite_base = trim( dirname( $_SERVER['SCRIPT_NAME'] ), '/\\' );
01084     if ( $rewrite_base != '' ) {
01085       $htaccess['rewrite_base'] = 'RewriteBase "/' . $rewrite_base . '"';
01086     }
01087 
01088     return $htaccess;
01089   }
01090 
01095   public function check_htaccess()
01096   {
01097     // default is assume we have mod_rewrite
01098     $this->handler_vars['no_mod_rewrite'] = false;
01099 
01100     // If this is the mod_rewrite check request, then bounce it as a success.
01101     if ( strpos( $_SERVER['REQUEST_URI'], 'check_mod_rewrite' ) !== false ) {
01102       echo 'ok';
01103       exit;
01104     }
01105 
01106     if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Apache' ) ) {
01107       // .htaccess is only needed on Apache
01108       // @TODO: add support for IIS and lighttpd rewrites
01109       return true;
01110     }
01111 
01112     $result = false;
01113     if ( file_exists( HABARI_PATH . '/.htaccess' ) ) {
01114       $htaccess = file_get_contents( HABARI_PATH . '/.htaccess' );
01115       if ( false === strpos( $htaccess, 'HABARI' ) ) {
01116         // the Habari block does not exist in this file
01117         // so try to create it
01118         $result = $this->write_htaccess( true );
01119       }
01120       else {
01121         // the Habari block exists
01122         $result = true;
01123       }
01124     }
01125     else {
01126       // no .htaccess exists.  Try to create one
01127       $result = $this->write_htaccess();
01128     }
01129     if ( $result ) {
01130       // the Habari block exists, but we need to make sure
01131       // it is correct.
01132       // Check that the rewrite rules actually do the job.
01133       $test_ajax_url = Site::get_url( 'site' ) . '/check_mod_rewrite';
01134       $rr = new RemoteRequest( $test_ajax_url, 'POST', 5 );
01135       try {
01136         $rr_result = $rr->execute();
01137       }
01138       catch ( \Exception $e ) {
01139         $result = $this->write_htaccess( true, true, true );
01140       }
01141     }
01142     return $result;
01143   }
01144 
01152   public function write_htaccess( $exists = false, $update = false, $rewritebase = true )
01153   {
01154     $htaccess = $this->htaccess();
01155     if ( $rewritebase ) {
01156       $rewrite_base = trim( dirname( $_SERVER['SCRIPT_NAME'] ), '/\\' );
01157       $htaccess['rewrite_base'] = 'RewriteBase "/' . $rewrite_base . '"';
01158     }
01159     $file_contents = "\n" . implode( "\n", $htaccess ) . "\n";
01160 
01161     if ( ! $exists ) {
01162       if ( ! is_writable( HABARI_PATH ) ) {
01163         // we can't create the file
01164         return false;
01165       }
01166     }
01167     else {
01168       if ( ! is_writable( HABARI_PATH . '/.htaccess' ) ) {
01169         // we can't update the file
01170         return false;
01171       }
01172     }
01173     if ( $update ) {
01174       // we're updating an existing but incomplete .htaccess
01175       // care must be take only to remove the Habari bits
01176       $htaccess = file_get_contents( HABARI_PATH . '/.htaccess' );
01177       $file_contents = preg_replace( '%### HABARI START.*?### HABARI END%ims', $file_contents, $htaccess );
01178       // Overwrite the existing htaccess with one that includes the modified Habari rewrite block
01179       $fmode = 'w';
01180     }
01181     else {
01182       // Append the Habari rewrite block to the existing file.
01183       $fmode = 'a';
01184     }
01185     // Save the .htaccess
01186     if ( $fh = fopen( HABARI_PATH . '/.htaccess', $fmode ) ) {
01187       if ( false === fwrite( $fh, $file_contents ) ) {
01188         return false;
01189       }
01190       fclose( $fh );
01191     }
01192     else {
01193       return false;
01194     }
01195 
01196     return true;
01197   }
01198 
01202   public function sqlite_contents()
01203   {
01204     $db_file = basename( $this->handler_vars['db_file'] );
01205     $contents = array(
01206       '### HABARI SQLITE START',
01207       '<Files "' . $db_file . '">',
01208       'Order deny,allow',
01209       'deny from all',
01210       '</Files>',
01211       '### HABARI SQLITE END'
01212     );
01213 
01214     return $contents;
01215   }
01216 
01222   public function secure_sqlite()
01223   {
01224     if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Apache' ) ) {
01225       // .htaccess is only needed on Apache
01226       // @TODO: Notify people on other servers to take measures to secure the SQLite file.
01227       return true;
01228     }
01229     if ( !file_exists( HABARI_PATH . '/.htaccess' ) ) {
01230       // no .htaccess to write to
01231       return false;
01232     }
01233     if ( !is_writable( HABARI_PATH . DIRECTORY_SEPARATOR . '.htaccess' ) ) {
01234       // we can't update the file
01235       return false;
01236     }
01237 
01238     // Get the files clause
01239     $sqlite_contents = $this->sqlite_contents();
01240     $files_contents = "\n" . implode( "\n", $sqlite_contents ) . "\n";
01241 
01242     // See if it already exists
01243     $current_files_contents = file_get_contents( HABARI_PATH . DIRECTORY_SEPARATOR . '.htaccess' );
01244     if ( false === strpos( $current_files_contents, $files_contents ) ) {
01245       // If not, append the files clause to the .htaccess file
01246       if ( $fh = fopen( HABARI_PATH . DIRECTORY_SEPARATOR . '.htaccess', 'a' ) ) {
01247         if ( false === fwrite( $fh, $files_contents ) ) {
01248           // Can't write to the file
01249           return false;
01250         }
01251         fclose( $fh );
01252       }
01253       else {
01254         // Can't open the file
01255         return false;
01256       }
01257     }
01258     // Success!
01259     return true;
01260   }
01261 
01262   private function upgrade_db_pre ( $current_version )
01263   {
01264 
01265     // this is actually a stripped-down version of DatabaseConnection::upgrade() - it doesn't support files
01266 
01267     $upgrade_functions = get_class_methods( $this );
01268 
01269     $upgrades = array();
01270 
01271     foreach ( $upgrade_functions as $fn ) {
01272 
01273       // match all methods named "upgrade_db_pre_<rev#>"
01274       if ( preg_match( '%^upgrade_db_pre_([0-9]+)$%i', $fn, $matches ) ) {
01275 
01276         $upgrade_version = intval( $matches[1] );
01277 
01278         if ( $upgrade_version > $current_version ) {
01279 
01280           $upgrades[ sprintf( '%010s_1', $upgrade_version ) ] = $fn;
01281 
01282         }
01283 
01284       }
01285 
01286     }
01287 
01288     // sort the upgrades by revision, ascending
01289     ksort( $upgrades );
01290 
01291 
01292     foreach ( $upgrades as $upgrade ) {
01293 
01294       $result =& call_user_func( array( $this, $upgrade ) );
01295 
01296       // if we failed, abort
01297       if ( $result === false ) {
01298         break;
01299       }
01300 
01301     }
01302 
01303   }
01304 
01305   private function upgrade_db_post ( $current_version )
01306   {
01307 
01308     // this is actually a stripped-down version of DatabaseConnection::upgrade() - it doesn't support files
01309 
01310     $upgrade_functions = get_class_methods( $this );
01311 
01312     $upgrades = array();
01313 
01314     foreach ( $upgrade_functions as $fn ) {
01315 
01316       // match all methods named "upgrade_db_post_<rev#>"
01317       if ( preg_match( '%^upgrade_db_post_([0-9]+)$%i', $fn, $matches ) ) {
01318 
01319         $upgrade_version = intval( $matches[1] );
01320 
01321         if ( $upgrade_version > $current_version ) {
01322 
01323           $upgrades[ sprintf( '%010s_1', $upgrade_version ) ] = $fn;
01324 
01325         }
01326 
01327       }
01328 
01329     }
01330 
01331     // sort the upgrades by revision, ascending
01332     ksort( $upgrades );
01333 
01334     foreach ( $upgrades as $upgrade ) {
01335 
01336       $result = call_user_func( array( $this, $upgrade ) );
01337 
01338       // if we failed, abort
01339       if ( $result === false ) {
01340         break;
01341       }
01342 
01343     }
01344 
01345   }
01346 
01347   private function upgrade_db_pre_1345 ()
01348   {
01349 
01350     // fix duplicate tag_slugs
01351 
01352     // first, get all the tags with duplicate entries
01353     $query = 'select id, tag_slug, tag_text from {tags} where tag_slug in ( select tag_slug from {tags} group by tag_slug having count(*) > 1 ) order by id';
01354     $tags = DB::get_results( $query );
01355 
01356     // assuming we got some tags to fix...
01357     if ( count( $tags ) > 0 ) {
01358 
01359       $slug_to_id = array();
01360       $fix_tags = array();
01361 
01362       foreach ( $tags as $tag_row ) {
01363 
01364         // skip the first tag text so we end up with something, presumably the first tag entered (it had the lowest ID in the db)
01365         if ( !isset( $fix_tags[ $tag_row->tag_slug ] ) ) {
01366           $slug_to_id[ $tag_row->tag_slug ] = $tag_row->id;   // collect the slug => id so we can rename with an absolute id later
01367           $fix_tags[ $tag_row->tag_slug ] = array();
01368         }
01369         else {
01370           $fix_tags[ $tag_row->tag_slug ][ $tag_row->id ] = $tag_row->tag_text;
01371         }
01372 
01373       }
01374 
01375       foreach ( $fix_tags as $tag_slug => $tag_texts ) {
01376 
01377         Tags::rename( $slug_to_id[ $tag_slug ], array_keys( $tag_texts ) );
01378 
01379       }
01380 
01381     }
01382 
01383     return true;
01384 
01385   }
01386 
01391   public function upgrade_db()
01392   {
01393     if ( Options::get( 'db_upgrading' ) ) {
01394       // quit with an error message.
01395       $this->display_currently_upgrading();
01396     }
01397 
01398     // don't allow duplicate upgrades.
01399     Options::set( 'db_upgrading', true );
01400 
01401     // This database-specific code needs to be moved into the schema-specific functions
01402     list( $schema, $remainder )= explode( ':', Config::get( 'db_connection' )->connection_string );
01403     switch ( $schema ) {
01404       case 'sqlite':
01405         $db_name = '';
01406         break;
01407       case 'mysql':
01408       case 'pgsql':
01409         $pairs = $this->parse_dsn( $remainder );
01410         
01411         $db_name = $pairs['dbname'];
01412         break;
01413     }
01414 
01415     Cache::purge();
01416 
01417     // get the current db version
01418     $version = Options::get( 'db_version' );
01419 
01420     // do some pre-dbdelta ad-hoc hacky hack code
01421     $this->upgrade_db_pre( $version );
01422 
01423     // run schema-specific upgrade scripts for before dbdelta
01424     DB::upgrade_pre( $version );
01425 
01426     // Get the queries for this database and apply the changes to the structure
01427     $queries = $this->get_create_table_queries( $schema, Config::get( 'db_connection' )->prefix, $db_name );
01428 
01429     DB::dbdelta( $queries );
01430 
01431     // Apply data changes to the database based on version, call the db-specific upgrades, too.
01432     $this->upgrade_db_post( $version );
01433 
01434     // run schema-specific upgrade scripts for after dbdelta
01435     DB::upgrade_post( $version );
01436 
01437     Version::save_dbversion();
01438     Options::delete( 'db_upgrading' );
01439   }
01440 
01441   private function display_currently_upgrading()
01442   {
01443     // Error template.
01444     $error_template = "<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>";
01445 
01446     // Format page with localized messages.
01447     $error_page = sprintf( $error_template,
01448       _t( "Site Maintenance" ), // page title
01449       _t( "Habari is currently being upgraded." ), // H1 tag
01450       _t( "Try again in a little while." ) // Error message.
01451     );
01452 
01453     // Set correct HTTP header and die.
01454     header( 'HTTP/1.1 503 Service Unavailable', true, 503 );
01455     die( $error_page );
01456   }
01457 
01458   private function upgrade_db_post_1310 ()
01459   {
01460 
01461     // Auto-truncate the log table
01462     if ( ! CronTab::get_cronjob( 'truncate_log' ) ) {
01463       CronTab::add_daily_cron( 'truncate_log', Method::create( '\Habari\Utils', 'truncate_log' ), _t( 'Truncate the log table' ) );
01464     }
01465 
01466     return true;
01467 
01468   }
01469 
01470   private function upgrade_db_post_1794 ()
01471   {
01472 
01473     Post::add_new_status( 'scheduled', true );
01474 
01475     return true;
01476 
01477   }
01478 
01479   private function upgrade_db_post_1845 ()
01480   {
01481 
01482     // Strip the base path off active plugins
01483     $base_path = array_map( function($s) {return str_replace('\\', '/', $s);}, array( HABARI_PATH ) );
01484     $activated = Options::get( 'active_plugins' );
01485     if ( is_array( $activated ) ) {
01486       foreach ( $activated as $plugin ) {
01487         $index = array_search( $plugin, $activated );
01488         $plugin = str_replace( $base_path, '', $plugin );
01489         $activated[$index] = $plugin;
01490       }
01491       Options::set( 'active_plugins', $activated );
01492     }
01493 
01494     return true;
01495 
01496   }
01497 
01498   private function upgrade_db_post_2264()
01499   {
01500     // create admin group
01501     $admin_group = UserGroup::get_by_name( 'admin' );
01502     if ( ! ( $admin_group instanceOf UserGroup ) ) {
01503       $admin_group = UserGroup::create( array( 'name' => 'admin' ) );
01504     }
01505 
01506     // add all users to the admin group
01507     $users = Users::get_all();
01508     $ids = array();
01509     foreach ( $users as $user ) {
01510       $ids[] = $user->id;
01511     }
01512     $admin_group->add( $ids );
01513 
01514     return true;
01515   }
01516 
01517   private function upgrade_db_post_2707 ()
01518   {
01519 
01520     // sets a default timezone and date / time formats for the options page
01521     if ( !Options::get( 'timezone' ) ) {
01522       Options::set( 'timezone', 'UTC' );
01523     }
01524 
01525     if ( !Options::get( 'dateformat' ) ) {
01526       Options::set( 'dateformat', 'Y-m-d' );
01527     }
01528 
01529     if ( !Options::get( 'timeformat' ) ) {
01530       Options::set( 'timeformat', 'H:i:s' );
01531     }
01532 
01533     return true;
01534 
01535   }
01536 
01537   private function upgrade_db_post_2786 ()
01538   {
01539 
01540     // fixes all the bad post2tag fields that didn't get deleted when a post was deleted
01541     DB::query( 'DELETE FROM {tag2post} WHERE post_id NOT IN ( SELECT DISTINCT id FROM {posts} )' );
01542 
01543     // now, delete any tags that have no posts left
01544     DB::query( 'DELETE FROM {tags} WHERE id NOT IN ( SELECT DISTINCT tag_id FROM {tag2post} )' );
01545 
01546     return true;
01547 
01548   }
01549 
01550   private function upgrade_db_post_3030()
01551   {
01552     // Create the admin group
01553     $group = UserGroup::create( array( 'name' => 'admin' ) );
01554     if ( ! $group ) {
01555       return false;
01556     }
01557 
01558     // Add the default tokens
01559     ACL::create_default_tokens();
01560 
01561     // Give admin group access to the super_user token
01562     $group->grant( 'super_user' );
01563 
01564     // Until now, all users were admins, restore that
01565     $all_users = Users::get_all();
01566     foreach ( $all_users as $user ) {
01567       $group->add( $user );
01568     }
01569 
01570     // Create the anonymous group
01571     $this->create_anonymous_group();
01572   }
01573 
01574   private function upgrade_db_post_3124()
01575   {
01576     ACL::rebuild_permissions();
01577   }
01578 
01579   private function upgrade_db_post_3158()
01580   {
01581     // delete own_post_typeX tokens rather than rebuild the whole default token set
01582     foreach ( Post::list_active_post_types() as $name => $posttype ) {
01583         ACL::destroy_token( 'own_post_' . Utils::slugify( $name ) );
01584     }
01585 
01586     ACL::destroy_token( 'own_posts_any' );
01587     ACL::create_token( 'own_posts', _t( 'Permissions on one\'s own posts' ), 'Content', true );
01588   }
01589 
01590   private function upgrade_db_post_3236()
01591   {
01592     // Add a default to the number of posts of a feed
01593     $atom_entries = Options::get( 'atom_entries' );
01594     if ( empty( $atom_entries ) ) {
01595       Options::set( 'atom_entries', '5' );
01596     }
01597     // Create the default authenticated group
01598     $authenticated_group = UserGroup::get_by_name( _t( 'authenticated' ) );
01599     if ( ! $authenticated_group instanceof UserGroup ) {
01600       $authenticated_group = UserGroup::create( array( 'name' => _t( 'authenticated' ) ) );
01601     }
01602     $authenticated_group->grant( 'post_entry', 'read' );
01603     $authenticated_group->grant( 'post_page', 'read' );
01604     $authenticated_group->grant( 'comment' );
01605 
01606   }
01607 
01608   private function upgrade_db_post_3539()
01609   {
01610 
01611     // get the global option
01612     $hide = Options::get( 'dashboard__hide_spam_count' );
01613 
01614     // if it was set to hide, get all our available users and set their info values instead
01615     if ( $hide == true ) {
01616 
01617       $users = Users::get();
01618 
01619       foreach ( $users as $user ) {
01620 
01621         $user->info->dashboard_hide_spam_count = 1;
01622         $user->update();
01623 
01624       }
01625 
01626     }
01627 
01628     Options::delete( 'dashboard__hide_spam_count' );
01629 
01630     return true;
01631 
01632   }
01633 
01634   private function upgrade_db_post_3698()
01635   {
01636     ACL::create_token( 'manage_self', _t( 'Edit own profile' ), 'Administration' );
01637   }
01638 
01639   private function upgrade_db_post_3701()
01640   {
01641     ACL::create_token( 'manage_dash_modules', _t( 'Manage dashboard modules' ), 'Administration' );
01642   }
01643 
01644   private function upgrade_db_post_3749()
01645   {
01646     $type_id = Vocabulary::object_type_id( 'post' );
01647 
01648     $vocabulary = Vocabulary::create( array( 'name' => 'tags', 'description' => 'Habari\'s tags implementation', 'features' => array( 'multiple', 'free' ) ) );
01649 
01650     $new_tag = null;
01651     $post_ids = array();
01652     $prefix = Config::get( 'db_connection' )->prefix;
01653 
01654     $results = DB::get_results( "SELECT id, tag_text, tag_slug from {$prefix}tags" );
01655 
01656     foreach ( $results as $tag ) {
01657       $new_tag = $vocabulary->add_term( $tag->tag_text );
01658       $post_ids = DB::get_column( "SELECT post_id FROM {$prefix}tag2post WHERE tag_id = ?", array( $tag->id ) );
01659       foreach ( $post_ids as $id ) {
01660         DB::insert( "{object_terms}", array( 'term_id' => $new_tag->id, 'object_id' => $id, 'object_type_id' => $type_id ) );
01661       }
01662     }
01663   }
01664 
01665   private function upgrade_db_post_4291()
01666   {
01667     // get all plugins so the legacy ones can be deactivated.
01668     $active_plugins = Plugins::list_active();
01669     $all_plugins = Installhandler::get_plugins();
01670 
01671     $legacy_plugins = array();
01672     foreach ( $all_plugins as $plugin ) {
01673       if ( !isset( $plugin[ 'info' ] ) ) {
01674         $key = array_search( $plugin[ 'file' ], $active_plugins );
01675         $legacy_plugins[ $key ] = $plugin[ 'file' ];
01676       }
01677     }
01678     $valid_plugins = array_diff_key( Options::get( 'active_plugins' ), $legacy_plugins );
01679 
01680     // valid_plugins contains only working plugins, but the classnames are missing. The following was previously upgrade_db_post_3484()
01681     $new_plugins = array();
01682     if ( is_array( $valid_plugins ) ) {
01683       foreach ( $valid_plugins as $filename ) {
01684         if ( !file_exists( $filename ) ) {
01685           // try adding base path to stored path
01686           $filename = HABARI_PATH . $filename;
01687         }
01688         if ( file_exists( $filename ) ) {
01689           // it is now safe to do this since plugins with info() functions are not in $valid_plugins
01690           require_once( $filename );
01691           $class = Plugins::class_from_filename( $filename );
01692           $short_file = substr( $filename, strlen( HABARI_PATH ) );
01693           if ( $class ) {
01694             $new_plugins[ $class ] = $short_file;
01695           }
01696         }
01697       }
01698     }
01699     // replace option with only the usuable plugins
01700     Options::set( 'active_plugins', $new_plugins );
01701   }
01702 
01703   private function upgrade_db_post_4382()
01704   {
01705 
01706     // add the new logging limit option
01707     Options::set( 'log_min_severity', 3 );    // 3 is 'info'
01708 
01709   }
01710   
01711   private function upgrade_db_post_4571()
01712   {
01713     
01714     // remove the old truncate_log cronjob
01715     CronTab::delete_cronjob( 'truncate_log' );
01716     
01717     // add the new trim_log cronjob
01718     CronTab::add_daily_cron( 'trim_log', Method::create( '\Habari\EventLog', 'trim' ), _t( 'Trim the log table' ) );
01719     
01720   }
01721   
01722   private function upgrade_db_post_4588()
01723   {
01724     
01725     // Add the cronjob to check for plugin updates
01726     CronTab::add_daily_cron( 'update_check', Method::create( '\Habari\Update', 'cron' ), _t( 'Perform a check for plugin updates.' ) );
01727     
01728   }
01729   
01730   private function upgrade_db_post_4763()
01731   {
01732     
01733     // delete the base_url option, which is no longer used
01734     Options::delete( 'base_url' );
01735     
01736   }
01737 
01738   private function upgrade_db_post_4770()
01739   {
01740     // Add CRUD access tokens for other users' unpublished posts
01741     ACL::create_token( 'post_unpublished', _t( "Permissions to other users' unpublished posts" ), _t( 'Content' ), true );
01742 
01743     // If a group doesn't have super_user permission, deny access to post_unpublished
01744     $groups = UserGroups::get_all();
01745     foreach ( $groups as $group ) {
01746       if ( !ACL::group_can( $group->id, 'super_user', 'read' ) ) {
01747         $group->deny( 'post_unpublished' );
01748       }
01749     }
01750   }
01751   
01752   private function upgrade_db_post_4980 ( ) {
01753     
01754     Options::set( 'spam_percentage', 100 );
01755     
01756   }
01757   
01758   private function upgrade_db_post_5096 ( ) {
01759     
01760     DB::exec( 'drop table {tags}' );
01761     DB::exec( 'drop table {tag2post}' );
01762     
01763   }
01764   
01765   private function upgrade_db_post_5097()
01766   {
01767     /* Add the 'manage_dash_modules' token if it doesn't exist.  This is effectively 
01768        re-doing upgrade_db_post_3701() for those who didn't upgrade from a db_version 
01769        before 3701 or those who have performed a clean install as this token 
01770        wasn't set in the list of default tokens until r5142. */
01771     if ( ! ACL::token_exists( 'manage_dash_modules' ) ) {
01772       $this->upgrade_db_post_3701();
01773     }
01774   }
01775 
01776   private function upgrade_db_post_5105 ( ) {
01777 
01778     // generate two new GUIDs -- one that will be used for private (server-side) operations like caching and another that can be used for public (client-side) operations to uniquely identify this Habari install
01779     // we will not delete the current GUID. though it's use is now deprecated it should be considered synonymous with the new public-GUID
01780     $private = sha1( Utils::nonce() );
01781     $public = sha1( Utils::nonce() );
01782 
01783     // the cache class is using the new private-GUID option now, so let's fake it out and use the existing GUID for a second
01784     Options::set( 'private-GUID', Options::get( 'GUID' ) );
01785 
01786     // purge the cache. this sucks, but it's really the only way...
01787     Cache::purge();
01788 
01789     // now save the new private and public GUIDs
01790     Options::set( 'private-GUID', $private );
01791     Options::set( 'public-GUID', $public );
01792 
01793   }
01794 
01795   private function upgrade_db_post_5106 ( ) {
01796 
01797     // get all unserialized options from the db
01798     // we do this manually just so things are cleaner
01799     $results = DB::get_results( 'SELECT name, value, type FROM {options} where type = :type', array( 'type' => 0 ), 'QueryRecord' );
01800 
01801     foreach ( $results as $result ) {
01802       // simply save it again, no need to duplicate the query
01803       Options::set( $result->name, $result->value );
01804     }
01805 
01806     // @todo i don't feel like seeing if we could properly handle removing the type column at the same time, so that should be done in a later version
01807 
01808   }
01809 
01810   private function upgrade_db_post_5107 ( ) {
01811 
01812     // delete the current un-namespaced CronJobs by these names
01813     CronTab::delete_cronjob( 'trim_log' );
01814     CronTab::delete_cronjob( 'update_check' );
01815 
01816     // we could have a bunch of the single update crons now, and there's no way to handle that using CronTab, so do it manually
01817     $crons = DB::get_results( 'SELECT * FROM {crontab} WHERE name = ?', array( 'update_check_single' ), '\Habari\CronJob' );
01818 
01819     foreach ( $crons as $cron ) {
01820       $cron->delete();
01821     }
01822 
01823     // Add the cronjob to trim the log so that it doesn't get too big
01824     CronTab::add_daily_cron( 'trim_log', Method::create( '\Habari\EventLog', 'trim' ), _t( 'Trim the log table' ) );
01825 
01826     // Add the cronjob to check for plugin updates
01827     CronTab::add_daily_cron( 'update_check', Method::create( '\Habari\Update', 'cron' ), _t( 'Perform a check for plugin updates.' ) );
01828 
01829   }
01830 
01831   public function upgrade_db_post_5111 ( ) {
01832 
01833     // add the cronjob to perform garbage collection on sessions
01834     CronTab::add_hourly_cron( 'session_gc', Method::create( '\Habari\Session', 'gc' ), _t( 'Perform session garbage collection.' ) );
01835 
01836   }
01837 
01838   public function upgrade_db_post_5112 ( ) {
01839 
01840     $this->create_base_comment_types();
01841 
01842     // Throw the existing values out far to avoid collisions
01843     DB::query('UPDATE {comments} SET status = status + 30, type = type + 30');
01844 
01845     // Update statuses
01846     $updates = array('unapproved' => 0, 'approved' => 1, 'spam' => 2, 'deleted' => 3);
01847     foreach($updates as $name => $oldvalue) {
01848       DB::query(
01849         'UPDATE {comments} SET status = :newstatus WHERE status = :oldstatus',
01850         array(
01851           'newstatus' => Comment::status($name),
01852           'oldstatus' => 30 + $oldvalue,
01853         )
01854       );
01855     }
01856 
01857     // Update types
01858     $updates = array('comment' => 0, 'pingback' => 1, 'trackback' => 2);
01859     foreach($updates as $name => $oldvalue) {
01860       DB::query(
01861         'UPDATE {comments} SET type = :newtype WHERE type = :oldtype',
01862         array(
01863           'newtype' => Comment::type($name),
01864           'oldtype' => 30 + $oldvalue
01865         )
01866       );
01867     }
01868   }
01869   
01874   public function ajax_check_mysql_credentials()
01875   {
01876     $xml = new \SimpleXMLElement( '<response></response>' );
01877     // Missing anything?
01878     if ( !isset( $_POST['host'] ) ) {
01879       $xml->addChild( 'status', 0 );
01880       $xml_error = $xml->addChild( 'error' );
01881       $xml_error->addChild( 'id', '#mysqldatabasehost' );
01882       $xml_error->addChild( 'message', _t( 'The database host field was left empty.' ) );
01883     }
01884     if ( !isset( $_POST['database'] ) ) {
01885       $xml->addChild( 'status', 0 );
01886       $xml_error = $xml->addChild( 'error' );
01887       $xml_error->addChild( 'id', '#mysqldatabasename' );
01888       $xml_error->addChild( 'message', _t( 'The database name field was left empty.' ) );
01889     }
01890     if ( !isset( $_POST['user'] ) ) {
01891       $xml->addChild( 'status', 0 );
01892       $xml_error = $xml->addChild( 'error' );
01893       $xml_error->addChild( 'id', '#mysqldatabaseuser' );
01894       $xml_error->addChild( 'message', _t( 'The database user field was left empty.' ) );
01895     }
01896     if ( isset( $_POST['table_prefix'] ) && ( preg_replace( '/[^a-zA-Z_]/', '', $_POST['table_prefix'] ) !== $_POST['table_prefix'] ) ) {
01897       $xml->addChild( 'status', 0 );
01898       $xml_error = $xml->addChild( 'error' );
01899       $xml_error->addChild( 'id', '#tableprefix' );
01900       $xml_error->addChild( 'message', _t( 'Allowed characters are A-Z, a-z and "_".' ) );
01901     }
01902     if ( !isset( $xml_error ) ) {
01903       // Can we connect to the DB?
01904       $pdo = 'mysql:host=' . $_POST['host'] . ';dbname=' . $_POST['database'];
01905       try {
01906         $connect = DB::connect( $pdo, $_POST['user'], $_POST->raw( 'pass' ) );
01907         $xml->addChild( 'status', 1 );
01908       }
01909       catch( \Exception $e ) {
01910         $xml->addChild( 'status', 0 );
01911         $xml_error = $xml->addChild( 'error' );
01912         if ( strpos( $e->getMessage(), '[1045]' ) ) {
01913           $xml_error->addChild( 'id', '#mysqldatabaseuser' );
01914           $xml_error->addChild( 'id', '#mysqldatabasepass' );
01915           $xml_error->addChild( 'message', _t( 'Access denied. Make sure these credentials are valid.' ) );
01916         }
01917         else if ( strpos( $e->getMessage(), '[1049]' ) ) {
01918           $xml_error->addChild( 'id', '#mysqldatabasename' );
01919           $xml_error->addChild( 'message', _t( 'That database does not exist.' ) );
01920         }
01921         else if ( strpos( $e->getMessage(), '[2005]' ) ) {
01922           $xml_error->addChild( 'id', '#mysqldatabasehost' );
01923           $xml_error->addChild( 'message', _t( 'Could not connect to host.' ) );
01924         }
01925         else {
01926           $xml_error->addChild( 'id', '#mysqldatabaseuser' );
01927           $xml_error->addChild( 'id', '#mysqldatabasepass' );
01928           $xml_error->addChild( 'id', '#mysqldatabasename' );
01929           $xml_error->addChild( 'id', '#mysqldatabasehost' );
01930           $xml_error->addChild( 'message', $e->getMessage() );
01931         }
01932       }
01933     }
01934     $xml = $xml->asXML();
01935     ob_clean();
01936     header( "Content-type: application/xml" );
01937     header( "Cache-Control: no-cache" );
01938     print $xml;
01939   }
01940 
01945   public function ajax_check_pgsql_credentials()
01946   {
01947     $xml = new \SimpleXMLElement( '<response></response>' );
01948     // Missing anything?
01949     if ( !isset( $_POST['host'] ) ) {
01950       $xml->addChild( 'status', 0 );
01951       $xml_error = $xml->addChild( 'error' );
01952       $xml_error->addChild( 'id', '#pgsqldatabasehost' );
01953       $xml_error->addChild( 'message', _t( 'The database host field was left empty.' ) );
01954     }
01955     if ( !isset( $_POST['database'] ) ) {
01956       $xml->addChild( 'status', 0 );
01957       $xml_error = $xml->addChild( 'error' );
01958       $xml_error->addChild( 'id', '#pgsqldatabasename' );
01959       $xml_error->addChild( 'message', _t( 'The database name field was left empty.' ) );
01960     }
01961     if ( !isset( $_POST['user'] ) ) {
01962       $xml->addChild( 'status', 0 );
01963       $xml_error = $xml->addChild( 'error' );
01964       $xml_error->addChild( 'id', '#pgsqldatabaseuser' );
01965       $xml_error->addChild( 'message', _t( 'The database user field was left empty.' ) );
01966     }
01967     if ( isset( $_POST['table_prefix'] ) && ( preg_replace( '/[^a-zA-Z_]/', '', $_POST['table_prefix'] ) !== $_POST['table_prefix'] ) ) {
01968       $xml->addChild( 'status', 0 );
01969       $xml_error = $xml->addChild( 'error' );
01970       $xml_error->addChild( 'id', '#tableprefix' );
01971       $xml_error->addChild( 'message', _t( 'Allowed characters are A-Z, a-z and "_".' ) );
01972     }
01973     if ( !isset( $xml_error ) ) {
01974       // Can we connect to the DB?
01975       $pdo = 'pgsql:host=' . $_POST['host'] . ' dbname=' . $_POST['database'];
01976       try {
01977         $connect = DB::connect( $pdo, $_POST['user'], $_POST->raw( 'pass' ) );
01978         $xml->addChild( 'status', 1 );
01979       }
01980       catch( \Exception $e ) {
01981         $xml->addChild( 'status', 0 );
01982         $xml_error = $xml->addChild( 'error' );
01983         if ( strpos( $e->getMessage(), '[1045]' ) ) {
01984           $xml_error->addChild( 'id', '#pgsqldatabaseuser' );
01985           $xml_error->addChild( 'id', '#pgsqldatabasepass' );
01986           $xml_error->addChild( 'message', _t( 'Access denied. Make sure these credentials are valid.' ) );
01987         }
01988         else if ( strpos( $e->getMessage(), '[1049]' ) ) {
01989           $xml_error->addChild( 'id', '#pgsqldatabasename' );
01990           $xml_error->addChild( 'message', _t( 'That database does not exist.' ) );
01991         }
01992         else if ( strpos( $e->getMessage(), '[2005]' ) ) {
01993           $xml_error->addChild( 'id', '#pgsqldatabasehost' );
01994           $xml_error->addChild( 'message', _t( 'Could not connect to host.' ) );
01995         }
01996         else {
01997           $xml_error->addChild( 'id', '#pgsqldatabaseuser' );
01998           $xml_error->addChild( 'id', '#pgsqldatabasepass' );
01999           $xml_error->addChild( 'id', '#pgsqldatabasename' );
02000           $xml_error->addChild( 'id', '#pgsqldatabasehost' );
02001           $xml_error->addChild( 'message', $e->getMessage() );
02002         }
02003       }
02004     }
02005     $xml = $xml->asXML();
02006     ob_clean();
02007     header( "Content-type: application/xml" );
02008     header( "Cache-Control: no-cache" );
02009     print $xml;
02010   }
02011 
02016   public function ajax_check_sqlite_credentials()
02017   {
02018     $db_file = $_POST['file'];
02019     $xml = new \SimpleXMLElement( '<response></response>' );
02020     // Missing anything?
02021     if ( !isset( $db_file ) ) {
02022       $xml->addChild( 'status', 0 );
02023       $xml_error = $xml->addChild( 'error' );
02024       $xml_error->addChild( 'id', '#databasefile' );
02025       $xml_error->addChild( 'message', _t( 'The database file was left empty.' ) );
02026     }
02027     if ( !isset( $xml_error ) ) {
02028       if ( $db_file == basename( $db_file ) ) { // The filename was given without a path
02029         $db_file = Site::get_path( 'user', true ) . $db_file;
02030       }
02031       if ( ! is_writable( dirname( $db_file ) ) ) {
02032         $xml->addChild( 'status', 0 );
02033         $xml_error = $xml->addChild( 'error' );
02034         $xml_error->addChild( 'id', '#databasefile' );
02035         $xml_error->addChild( 'message', _t( 'Cannot write to %s directory. SQLite requires that the directory that holds the DB file be writable by the web server.', array( dirname( $db_file ) ) ) );
02036       }
02037       elseif ( file_exists( Site::get_path( 'user', true ) . $db_file ) && ( ! is_writable( Site::get_path( 'user', true ) . $db_file ) ) ) {
02038         $xml->addChild( 'status', 0 );
02039         $xml_error = $xml->addChild( 'error' );
02040         $xml_error->addChild( 'id', '#databasefile' );
02041 
02042         $xml_error->addChild( 'message', _t( 'Cannot write to %s. The SQLite data file is not writable by the web server.', array( $db_file ) ) );
02043       }
02044       else {
02045         // Can we connect to the DB?
02046         $pdo = 'sqlite:' . $db_file;
02047         $connect = DB::connect( $pdo, null, null );
02048 
02049         // Disconnect, but no longer delete the file - it could already have contents!
02050         DB::disconnect();
02051 
02052         switch ( $connect ) {
02053           case true:
02054             // We were able to connect to an existing database file.
02055             $xml->addChild( 'status', 1 );
02056             break;
02057           default:
02058             // We can't create the database file, send an error message.
02059             $xml->addChild( 'status', 0 );
02060             $xml_error = $xml->addChild( 'error' );
02061             // TODO: Add error codes handling for user-friendly messages
02062             $xml_error->addChild( 'id', '#databasefile' );
02063             $xml_error->addChild( 'message', $connect->getMessage() );
02064         }
02065       }
02066     }
02067     $xml = $xml->asXML();
02068     ob_clean();
02069     header( "Content-type: application/xml" );
02070     header( "Cache-Control: no-cache" );
02071     print $xml;
02072   }
02073 
02079   private function set_handler_vars_from_db_connection()
02080   {
02081     list( $this->handler_vars['db_type'], $remainder )= explode( ':', Config::get( 'db_connection' )->connection_string );
02082     switch ( $this->handler_vars['db_type'] ) {
02083       case 'sqlite':
02084         // SQLite uses less info.
02085         // we stick the path in db_host
02086         $this->handler_vars['db_file'] = $remainder;
02087         break;
02088       case 'mysql':
02089       case 'pgsql':
02090         $pairs = $this->parse_dsn( $remainder );
02091         
02092         $host = $pairs['host'];
02093         
02094         if ( isset( $pairs['port'] ) ) {
02095           $host .= ';port=' . $pairs['port'];
02096         }
02097         
02098         $this->handler_vars['db_host'] = $host;
02099         $this->handler_vars['db_schema'] = $pairs['dbname'];
02100         
02101         break;
02102     }
02103     $this->handler_vars['db_user'] = Config::get( 'db_connection' )->username;
02104     $this->handler_vars['db_pass'] = Config::get( 'db_connection' )->password;
02105     $this->handler_vars['table_prefix'] = Config::get( 'db_connection' )->prefix;
02106   }
02107   
02108   private function parse_dsn ( $dsn ) {
02109     
02110     // before we replace spaces, which may give us double semicolons if there's one after a semicolon, condense it down
02111     $dsn = str_replace( '; ', ';', $dsn );
02112     
02113     // spaces are also allowed in the dsn, but let's unify everything with a semicolon for parsing
02114     $dsn = str_replace( ' ', ';', $dsn );
02115     
02116     // now to split them apart
02117     $temp_pairs = explode( ';', $dsn );
02118     
02119     // and then by =
02120     $pairs = array();
02121     foreach ( $temp_pairs as $temp_pair ) {
02122       list( $k, $v ) = explode( '=', $temp_pair );
02123       $pairs[ $k ] = $v;
02124     }
02125     
02126     return $pairs;
02127     
02128   }
02129 
02135   public static function get_feature_list($features) {
02136     $output = array();
02137     foreach($features as $feature) {
02138       $output[(string)$feature] = (string)$feature;
02139     }
02140     return implode(',', $output);
02141   }
02142 
02146   private function create_base_comment_types()
02147   {
02148     Comment::add_type('comment');
02149     Comment::add_type('pingback');
02150     Comment::add_type('trackback');
02151 
02152     Comment::add_status('unapproved', true);
02153     Comment::add_status('approved', true);
02154     Comment::add_status('spam', true);
02155     Comment::add_status('deleted', true);
02156 
02157     return true;
02158   }
02159 
02160 }
02161 ?>

Generated on Sun Aug 4 2013 12:51:44 for Habari by  doxygen 1.7.1