PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/system/handlers/installhandler.php

https://github.com/HabariMag/habarimag-old
PHP | 1934 lines | 1323 code | 271 blank | 340 comment | 202 complexity | c0a6547c97505890fca7b09f40d87be1 MD5 | raw file
Possible License(s): Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * @package Habari
  4. *
  5. */
  6. define( 'MIN_PHP_VERSION', '5.2.0' );
  7. /**
  8. * The class which responds to installer actions
  9. */
  10. class InstallHandler extends ActionHandler
  11. {
  12. /**
  13. * Entry point for installation. The reason there is a begin_install
  14. * method to handle is that conceivably, the user can stop installation
  15. * mid-install and need an alternate entry point action at a later time.
  16. */
  17. public function act_begin_install()
  18. {
  19. // Create a new theme to handle the display of the installer
  20. $this->theme = Themes::create( 'installer', 'RawPHPEngine', HABARI_PATH . '/system/installer/' );
  21. /**
  22. * Set user selected Locale or default
  23. */
  24. $this->theme->locales = HabariLocale::list_all();
  25. if ( isset( $_POST['locale'] ) && $_POST['locale'] != null ) {
  26. HabariLocale::set( $_POST['locale'] );
  27. $this->theme->locale = $_POST['locale'];
  28. $this->handler_vars['locale'] = $_POST['locale'];
  29. }
  30. else {
  31. HabariLocale::set( 'en-us' );
  32. $this->theme->locale = 'en-us';
  33. $this->handler_vars['locale'] = 'en-us';
  34. }
  35. /*
  36. * Check .htaccess first because ajax doesn't work without it.
  37. */
  38. if ( ! $this->check_htaccess() ) {
  39. $this->handler_vars['file_contents'] = htmlentities( implode( "\n", $this->htaccess() ) );
  40. $this->display( 'htaccess' );
  41. }
  42. // Dispatch AJAX requests.
  43. if ( isset( $_POST['ajax_action'] ) ) {
  44. switch ( $_POST['ajax_action'] ) {
  45. case 'check_mysql_credentials':
  46. self::ajax_check_mysql_credentials();
  47. exit;
  48. break;
  49. case 'check_pgsql_credentials':
  50. self::ajax_check_pgsql_credentials();
  51. exit;
  52. break;
  53. case 'check_sqlite_credentials':
  54. self::ajax_check_sqlite_credentials();
  55. exit;
  56. break;
  57. }
  58. }
  59. // set the default values now, which will be overriden as we go
  60. $this->form_defaults();
  61. if ( ! $this->meets_all_requirements() ) {
  62. $this->display( 'requirements' );
  63. }
  64. /*
  65. * Add the AJAX hooks
  66. */
  67. Plugins::register( array( 'InstallHandler', 'ajax_check_mysql_credentials' ), 'ajax_', 'check_mysql_credentials' );
  68. Plugins::register( array( 'InstallHandler', 'ajax_check_pgsql_credentials' ), 'ajax_', 'check_pgsql_credentials' );
  69. /*
  70. * Let's check the config.php file if no POST data was submitted
  71. */
  72. if ( ( ! file_exists( Site::get_dir( 'config_file' ) ) ) && ( ! isset( $_POST['admin_username'] ) ) ) {
  73. // no config file, and no HTTP POST
  74. $this->display( 'db_setup' );
  75. }
  76. // try to load any values that might be defined in config.php
  77. if ( file_exists( Site::get_dir( 'config_file' ) ) ) {
  78. include( Site::get_dir( 'config_file' ) );
  79. // check for old style config (global variable, pre-dates registry based config
  80. if ( !Config::exists( 'db_connection' ) && isset( $db_connection ) ) {
  81. // found old style config...
  82. // set up registry:
  83. Config::set( 'db_connection', $db_connection );
  84. // assign handler vars (for config file write)
  85. $this->set_handler_vars_from_db_connection();
  86. // write new config file
  87. if ( $this->write_config_file( true ) ) {
  88. // successful, so redirect:
  89. Utils::redirect( Site::get_url( 'habari' ) );
  90. }
  91. }
  92. if ( Config::exists( 'db_connection' ) ) {
  93. $this->set_handler_vars_from_db_connection();
  94. }
  95. // if a $blog_data array exists in config.php, use it
  96. // to pre-load values for the installer
  97. // ** this is completely optional **
  98. if ( isset( $blog_data ) ) {
  99. foreach ( $blog_data as $blog_datum => $value ) {
  100. $this->handler_vars[$blog_datum] = $value;
  101. }
  102. }
  103. }
  104. // now merge in any HTTP POST values that might have been sent
  105. // these will override the defaults and the config.php values
  106. $this->handler_vars = $this->handler_vars->merge( $_POST );
  107. // we need details for the admin user to install
  108. if ( ( '' == $this->handler_vars['admin_username'] )
  109. || ( '' == $this->handler_vars['admin_pass1'] )
  110. || ( '' == $this->handler_vars['admin_pass2'] )
  111. || ( '' == $this->handler_vars['admin_email'] )
  112. ) {
  113. // if none of the above are set, display the form
  114. $this->display( 'db_setup' );
  115. }
  116. $db_type = $this->handler_vars['db_type'];
  117. if ( $db_type == 'mysql' || $db_type == 'pgsql' ) {
  118. $this->handler_vars['db_host'] = $_POST["{$db_type}_db_host"];
  119. $this->handler_vars['db_user'] = $_POST["{$db_type}_db_user"];
  120. $this->handler_vars['db_pass'] = $_POST["{$db_type}_db_pass"];
  121. $this->handler_vars['db_schema'] = $_POST["{$db_type}_db_schema"];
  122. }
  123. // we got here, so we have all the info we need to install
  124. // make sure the admin password is correct
  125. if ( $this->handler_vars['admin_pass1'] !== $this->handler_vars['admin_pass2'] ) {
  126. $this->theme->assign( 'form_errors', array( 'password_mismatch'=>_t( 'Password mis-match.' ) ) );
  127. $this->display( 'db_setup' );
  128. }
  129. // check whether prefix is valid
  130. if ( isset( $this->handler_vars['table_prefix'] ) && ( preg_replace( '/[^a-zA-Z_]/', '', $this->handler_vars['table_prefix'] ) !== $this->handler_vars['table_prefix'] ) ) {
  131. $this->theme->assign( 'form_errors', array( 'table_prefix' => _t( 'Allowed characters are A-Z, a-z and "_".' ) ) );
  132. $this->display( 'db_setup' );
  133. }
  134. // Make sure we still have a valid connection
  135. if ( ! call_user_func( array( $this, "check_{$db_type}" ) ) ) {
  136. $this->display( 'db_setup' );
  137. }
  138. // try to write the config file
  139. if ( ! $this->write_config_file() ) {
  140. $this->theme->assign( 'form_errors', array( 'write_file'=>_t( 'Could not write config.php file&hellip;' ) ) );
  141. $this->display( 'db_setup' );
  142. }
  143. // try to install the database
  144. if ( ! $this->install_db() ) {
  145. // the installation failed for some reason.
  146. // re-display the form
  147. $this->display( 'db_setup' );
  148. }
  149. // activate plugins on POST
  150. if ( count( $_POST ) > 0 ) {
  151. $this->activate_plugins();
  152. }
  153. // Installation complete. Secure sqlite if it was chosen as the database type to use
  154. if ( $db_type == 'sqlite' ) {
  155. if ( !$this->secure_sqlite() ) {
  156. $this->theme->sqlite_contents = implode( "\n", $this->sqlite_contents() );
  157. $this->display( 'sqlite' );
  158. }
  159. }
  160. EventLog::log( _t( 'Habari successfully installed.' ), 'info', 'default', 'habari' );
  161. Utils::redirect( Site::get_url( 'habari' ) );
  162. }
  163. /*
  164. * Helper function to grab list of plugins
  165. */
  166. public function get_plugins()
  167. {
  168. $all_plugins = Plugins::list_all();
  169. $recommended_list = array(
  170. 'coredashmodules.plugin.php',
  171. 'coreblocks.plugin.php',
  172. 'habarisilo.plugin.php',
  173. 'pingback.plugin.php',
  174. 'spamchecker.plugin.php',
  175. 'undelete.plugin.php',
  176. 'autop.plugin.php'
  177. );
  178. foreach ( $all_plugins as $file ) {
  179. $plugin = array();
  180. $plugin_id = Plugins::id_from_file( $file );
  181. $plugin['plugin_id'] = $plugin_id;
  182. $plugin['file'] = $file;
  183. $error = '';
  184. if ( Utils::php_check_file_syntax( $file, $error ) ) {
  185. $plugin['debug'] = false;
  186. // get this plugin's info()
  187. $plugin['active'] = false;
  188. $plugin['verb'] = _t( 'Activate' );
  189. $plugin['actions'] = array();
  190. $plugin['info'] = Plugins::load_info( $file );
  191. $plugin['recommended'] = in_array( basename( $file ), $recommended_list );
  192. }
  193. else {
  194. // We can't get the plugin info due to an error
  195. // This will show up in the plugin panel, just continue through install
  196. continue;
  197. }
  198. $plugins[$plugin_id] = $plugin;
  199. }
  200. return $plugins;
  201. }
  202. /**
  203. * Helper function to remove code repetition
  204. *
  205. * @param template_name Name of template to use
  206. */
  207. private function display( $template_name )
  208. {
  209. foreach ( $this->handler_vars as $key=>$value ) {
  210. $this->theme->assign( $key, $value );
  211. }
  212. $this->theme->assign( 'plugins', $this->get_plugins() );
  213. $this->theme->display( $template_name );
  214. exit;
  215. }
  216. /*
  217. * sets default values for the form
  218. */
  219. public function form_defaults()
  220. {
  221. $formdefaults['db_type'] = 'mysql';
  222. $formdefaults['db_host'] = 'localhost';
  223. $formdefaults['db_user'] = '';
  224. $formdefaults['db_pass'] = '';
  225. $formdefaults['db_file'] = 'habari.db';
  226. $formdefaults['db_schema'] = 'habari';
  227. $formdefaults['table_prefix'] = isset( Config::get( 'db_connection' )->prefix ) ? Config::get( 'db_connection' )->prefix : 'habari__';
  228. $formdefaults['admin_username'] = 'admin';
  229. $formdefaults['admin_pass1'] = '';
  230. $formdefaults['admin_pass2'] = '';
  231. $formdefaults['blog_title'] = 'My Habari';
  232. $formdefaults['admin_email'] = '';
  233. foreach ( $formdefaults as $key => $value ) {
  234. if ( !isset( $this->handler_vars[$key] ) ) {
  235. $this->handler_vars[$key] = $value;
  236. }
  237. }
  238. }
  239. /**
  240. * Gathers information about the system in order to make sure
  241. * requirements for install are met
  242. *
  243. * @returns bool are all requirements met?
  244. */
  245. private function meets_all_requirements()
  246. {
  247. // Required extensions, this list will augment with time
  248. // Even if they are enabled by default, it seems some install turn them off
  249. // We use the URL in the Installer template to link to the installation page
  250. $required_extensions = array(
  251. 'date' => 'http://php.net/datetime',
  252. 'pdo' => 'http://php.net/pdo',
  253. 'hash' => 'http://php.net/hash',
  254. 'json' => 'http://php.net/json',
  255. 'mbstring' => 'http://php.net/mbstring',
  256. 'pcre' => 'http://php.net/pcre',
  257. 'session' => 'http://php.net/session',
  258. 'simplexml' => 'http://php.net/simplexml',
  259. 'spl' => 'http://php.net/spl',
  260. 'tokenizer' => 'http://php.net/tokenizer',
  261. );
  262. $requirements_met = true;
  263. /* Check versions of PHP */
  264. $php_version_ok = version_compare( phpversion(), MIN_PHP_VERSION, '>=' );
  265. $this->theme->assign( 'php_version_ok', $php_version_ok );
  266. $this->theme->assign( 'PHP_OS', PHP_OS );;
  267. $this->theme->assign( 'PHP_VERSION', phpversion() );
  268. if ( ! $php_version_ok ) {
  269. $requirements_met = false;
  270. }
  271. /* Check for mod_rewrite on Apache */
  272. $mod_rewrite = true;
  273. if ( function_exists( 'apache_get_modules' ) && !in_array( 'mod_rewrite', apache_get_modules() ) ) {
  274. $requirements_met = false;
  275. $mod_rewrite = false;
  276. }
  277. $this->theme->assign( 'mod_rewrite', $mod_rewrite );
  278. /* Check for required extensions */
  279. $missing_extensions = array();
  280. foreach ( $required_extensions as $ext_name => $ext_url ) {
  281. if ( !extension_loaded( $ext_name ) ) {
  282. $missing_extensions[$ext_name] = $ext_url;
  283. $requirements_met = false;
  284. }
  285. }
  286. $this->theme->assign( 'missing_extensions', $missing_extensions );
  287. if ( extension_loaded( 'pdo' ) ) {
  288. /* Check for PDO drivers */
  289. $pdo_drivers = PDO::getAvailableDrivers();
  290. if ( ! empty( $pdo_drivers ) ) {
  291. $pdo_drivers = array_combine( $pdo_drivers, $pdo_drivers );
  292. // Include only those drivers that we include database support for
  293. $pdo_schemas = array_map( 'basename', Utils::glob( HABARI_PATH . '/system/schema/*', GLOB_ONLYDIR ) );
  294. $pdo_schemas = array_combine( $pdo_schemas, $pdo_schemas );
  295. $pdo_drivers = array_intersect_key(
  296. $pdo_drivers,
  297. $pdo_schemas
  298. );
  299. $pdo_missing_drivers = array_diff(
  300. $pdo_schemas,
  301. $pdo_drivers
  302. );
  303. $pdo_drivers_ok = count( $pdo_drivers );
  304. $this->theme->assign( 'pdo_drivers_ok', $pdo_drivers_ok );
  305. $this->theme->assign( 'pdo_drivers', $pdo_drivers );
  306. $this->theme->assign( 'pdo_missing_drivers', $pdo_missing_drivers );
  307. }
  308. else {
  309. $pdo_drivers_ok = false;
  310. $this->theme->assign( 'pdo_drivers_ok', $pdo_drivers_ok );
  311. }
  312. if ( ! $pdo_drivers_ok ) {
  313. $requirements_met = false;
  314. }
  315. }
  316. else {
  317. $this->theme->assign( 'pdo_drivers_ok', false );
  318. $this->theme->assign( 'pdo_drivers', array() );
  319. $requirements_met = false;
  320. }
  321. if ( $requirements_met && ! @preg_match( '/\p{L}/u', 'a' ) ) {
  322. $requirements_met = false;
  323. }
  324. /**
  325. * $local_writable is used in the template, but never set in Habari
  326. * Won't remove the template code since it looks like it should be there
  327. *
  328. * This will only meet the requirement so there's no "undefined variable" exception
  329. */
  330. $this->theme->assign( 'local_writable', true );
  331. return $requirements_met;
  332. }
  333. /**
  334. * Attempts to install the database. Returns the result of
  335. * the installation, adding errors to the theme if any
  336. * occur
  337. *
  338. * @return bool result of installation
  339. */
  340. private function install_db()
  341. {
  342. $db_host = $this->handler_vars['db_host'];
  343. $db_type = $this->handler_vars['db_type'];
  344. $db_schema = $this->handler_vars['db_schema'];
  345. $db_user = $this->handler_vars['db_user'];
  346. $db_pass = $this->handler_vars['db_pass'];
  347. switch ( $db_type ) {
  348. case 'mysql':
  349. case 'pgsql':
  350. // MySQL & PostgreSQL requires specific connection information
  351. if ( empty( $db_user ) ) {
  352. $this->theme->assign( 'form_errors', array( "{$db_type}_db_user"=>_t( 'User is required.' ) ) );
  353. return false;
  354. }
  355. if ( empty( $db_schema ) ) {
  356. $this->theme->assign( 'form_errors', array( "{$db_type}_db_schema"=>_t( 'Name for database is required.' ) ) );
  357. return false;
  358. }
  359. if ( empty( $db_host ) ) {
  360. $this->theme->assign( 'form_errors', array( "{$db_type}_db_host"=>_t( 'Host is required.' ) ) );
  361. return false;
  362. }
  363. break;
  364. case 'sqlite':
  365. // If this is a SQLite database, let's check that the file
  366. // exists and that we can access it.
  367. if ( ! $this->check_sqlite() ) {
  368. return false;
  369. }
  370. break;
  371. }
  372. if ( isset( $this->handler_vars['table_prefix'] ) ) {
  373. // store prefix in the Config singleton so DatabaseConnection can access it
  374. Config::set( 'db_connection', array( 'prefix' => $this->handler_vars['table_prefix'], ) );
  375. }
  376. if ( ! $this->connect_to_existing_db() ) {
  377. $this->theme->assign( 'form_errors', array( "{$db_type}_db_user"=>_t( 'Problem connecting to supplied database credentials' ) ) );
  378. return false;
  379. }
  380. DB::begin_transaction();
  381. /* Let's install the DB tables now. */
  382. $create_table_queries = $this->get_create_table_queries(
  383. $this->handler_vars['db_type'],
  384. $this->handler_vars['table_prefix'],
  385. $this->handler_vars['db_schema']
  386. );
  387. DB::clear_errors();
  388. DB::dbdelta( $create_table_queries, true, true, true );
  389. if ( DB::has_errors() ) {
  390. $error = DB::get_last_error();
  391. $this->theme->assign( 'form_errors', array( 'db_host'=>sprintf( _t( 'Could not create schema tables&hellip; %s' ), $error['message'] ) ) );
  392. DB::rollback();
  393. return false;
  394. }
  395. // Cool. DB installed. Create the default options
  396. // but check first, to make sure
  397. if ( ! Options::get( 'installed' ) ) {
  398. if ( ! $this->create_default_options() ) {
  399. $this->theme->assign( 'form_errors', array( 'options'=>_t( 'Problem creating default options' ) ) );
  400. DB::rollback();
  401. return false;
  402. }
  403. }
  404. // Create the Tags vocabulary
  405. if ( ! $this->create_tags_vocabulary() ) {
  406. $this->theme->assign( 'form_errors', array( 'options'=>_t( 'Problem creating tags vocabulary' ) ) );
  407. DB::rollback();
  408. return false;
  409. }
  410. // Create the standard post types and statuses
  411. if ( ! $this->create_base_post_types() ) {
  412. $this->theme->assign( 'form_errors', array( 'options'=>_t( 'Problem creating base post types' ) ) );
  413. DB::rollback();
  414. return false;
  415. }
  416. // Let's setup the admin user and group now.
  417. // But first, let's make sure that no users exist
  418. $all_users = Users::get_all();
  419. if ( count( $all_users ) < 1 ) {
  420. $user = $this->create_admin_user();
  421. if ( ! $user ) {
  422. $this->theme->assign( 'form_errors', array( 'admin_user'=>_t( 'Problem creating admin user.' ) ) );
  423. DB::rollback();
  424. return false;
  425. }
  426. $admin_group = $this->create_admin_group( $user );
  427. if ( ! $admin_group ) {
  428. $this->theme->assign( 'form_errors', array( 'admin_user'=>_t( 'Problem creating admin group.' ) ) );
  429. DB::rollback();
  430. return false;
  431. }
  432. // create default tokens
  433. ACL::rebuild_permissions( $user );
  434. }
  435. // create a first post, if none exists
  436. if ( ! Posts::get( array( 'count' => 1 ) ) ) {
  437. if ( ! $this->create_first_post() ) {
  438. $this->theme->assign( 'form_errors', array( 'post'=>_t( 'Problem creating first post.' ) ) );
  439. DB::rollback();
  440. return false;
  441. }
  442. }
  443. /* Post::save_tags() closes transaction, until we fix that, check and reconnect if needed */
  444. if ( !DB::in_transaction() ) {
  445. DB::begin_transaction();
  446. }
  447. /* Store current DB version so we don't immediately run dbdelta. */
  448. Version::save_dbversion();
  449. /* Ready to roll. */
  450. DB::commit();
  451. return true;
  452. }
  453. /**
  454. * Validate database credentials for MySQL
  455. * Try to connect and verify if database name exists
  456. */
  457. public function check_mysql()
  458. {
  459. // Can we connect to the DB?
  460. $pdo = 'mysql:host=' . $this->handler_vars['db_host'] . ';dbname=' . $this->handler_vars['db_schema'];
  461. if ( isset( $this->handler_vars['table_prefix'] ) ) {
  462. // store prefix in the Config singleton so DatabaseConnection can access it
  463. Config::set( 'db_connection', array( 'prefix' => $this->handler_vars['table_prefix'], ) );
  464. }
  465. try {
  466. $connect = DB::connect( $pdo, $this->handler_vars['db_user'], $this->handler_vars['db_pass'] );
  467. return true;
  468. }
  469. catch( PDOException $e ) {
  470. if ( strpos( $e->getMessage(), '[1045]' ) ) {
  471. $this->theme->assign( 'form_errors', array( 'mysql_db_pass' => _t( 'Access denied. Make sure these credentials are valid.' ) ) );
  472. }
  473. else if ( strpos( $e->getMessage(), '[1049]' ) ) {
  474. $this->theme->assign( 'form_errors', array( 'mysql_db_schema' => _t( 'That database does not exist.' ) ) );
  475. }
  476. else if ( strpos( $e->getMessage(), '[2005]' ) ) {
  477. $this->theme->assign( 'form_errors', array( 'mysql_db_host' => _t( 'Could not connect to host.' ) ) );
  478. }
  479. else {
  480. $this->theme->assign( 'form_errors', array( 'mysql_db_host' => $e->getMessage() ) );
  481. }
  482. return false;
  483. }
  484. }
  485. /**
  486. * Validate database credentials for PostgreSQL
  487. * Try to connect and verify if database name exists
  488. */
  489. public function check_pgsql()
  490. {
  491. // Can we connect to the DB?
  492. $pdo = 'pgsql:host=' . $this->handler_vars['db_host'] . ';dbname=' . $this->handler_vars['db_schema'];
  493. if ( isset( $this->handler_vars['table_prefix'] ) ) {
  494. // store prefix in the Config singleton so DatabaseConnection can access it
  495. Config::set( 'db_connection', array( 'prefix' => $this->handler_vars['table_prefix'], ) );
  496. }
  497. try {
  498. $connect = DB::connect( $pdo, $this->handler_vars['db_user'], $this->handler_vars['db_pass'] );
  499. return true;
  500. }
  501. catch( PDOException $e ) {
  502. if ( strpos( $e->getMessage(), '[1045]' ) ) {
  503. $this->theme->assign( 'form_errors', array( 'pgsql_db_pass' => _t( 'Access denied. Make sure these credentials are valid.' ) ) );
  504. }
  505. else if ( strpos( $e->getMessage(), '[1049]' ) ) {
  506. $this->theme->assign( 'form_errors', array( 'pgsql_db_schema' => _t( 'That database does not exist.' ) ) );
  507. }
  508. else if ( strpos( $e->getMessage(), '[2005]' ) ) {
  509. $this->theme->assign( 'form_errors', array( 'pgsql_db_host' => _t( 'Could not connect to host.' ) ) );
  510. }
  511. else {
  512. $this->theme->assign( 'form_errors', array( 'pgsql_db_host' => $e->getMessage() ) );
  513. }
  514. return false;
  515. }
  516. }
  517. /**
  518. * Checks for the existance of a SQLite datafile
  519. * tries to create it if it does not exist
  520. **/
  521. private function check_sqlite()
  522. {
  523. $db_file = $this->handler_vars['db_file'];
  524. if ( $db_file == basename( $db_file ) ) { // The filename was given without a path
  525. $db_file = Site::get_path( 'user', true ) . $db_file;
  526. }
  527. if ( file_exists( $db_file ) && is_writable( $db_file ) && is_writable( dirname( $db_file ) ) ) {
  528. // the file exists, and is writable. We're all set
  529. return true;
  530. }
  531. // try to figure out what the problem is.
  532. if ( file_exists( $db_file ) ) {
  533. // the DB file exists, why can't we access it?
  534. if ( ! is_writable( $db_file ) ) {
  535. $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 ) ) ) );
  536. return false;
  537. }
  538. if ( ! is_writable( dirname( $db_file ) ) ) {
  539. $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 ) ) ) );
  540. return false;
  541. }
  542. }
  543. if ( ! file_exists( $db_file ) ) {
  544. // let's see if the directory is writable
  545. // so that we could create the file
  546. if ( ! is_writable( dirname( $db_file ) ) ) {
  547. $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 ) ) ) );
  548. return false;
  549. }
  550. }
  551. return true;
  552. }
  553. /**
  554. * Checks that there is a database matching the supplied
  555. * arguments.
  556. *
  557. * @return bool Database exists with credentials?
  558. */
  559. private function connect_to_existing_db()
  560. {
  561. if ( $config = $this->get_config_file() ) {
  562. $config = preg_replace( '/<\\?php(.*)\\?'.'>/ims', '$1', $config );
  563. // Update the db_connection from the config that is about to be written:
  564. eval( $config );
  565. /* Attempt to connect to the database host */
  566. try {
  567. DB::connect();
  568. return true;
  569. }
  570. catch( PDOException $e ) {
  571. $this->theme->assign( 'form_errors', array( 'db_user'=>_t( 'Problem connecting to supplied database credentials' ) ) );
  572. return false;
  573. }
  574. }
  575. // If we couldn't create the config from the template, return an error
  576. return false;
  577. }
  578. /**
  579. * Creates the administrator user from form information
  580. *
  581. * @return mixed. the user on success, false on failure
  582. */
  583. private function create_admin_user()
  584. {
  585. $admin_username = $this->handler_vars['admin_username'];
  586. $admin_email = $this->handler_vars['admin_email'];
  587. $admin_pass = $this->handler_vars['admin_pass1'];
  588. if ( $admin_pass{0} == '{' ) {
  589. // looks like we might have a crypted password
  590. $password = $admin_pass;
  591. // but let's double-check
  592. $algo = strtolower( substr( $admin_pass, 1, 3 ) );
  593. if ( ( 'ssh' != $algo ) && ( 'sha' != $algo ) ) {
  594. // we do not have a crypted password
  595. // so let's encrypt it
  596. $password = Utils::crypt( $admin_pass );
  597. }
  598. }
  599. else {
  600. $password = Utils::crypt( $admin_pass );
  601. }
  602. // Insert the admin user
  603. $user = User::create( array (
  604. 'username'=>$admin_username,
  605. 'email'=>$admin_email,
  606. 'password'=>$password
  607. ) );
  608. return $user;
  609. }
  610. /**
  611. * Creates the admin group using the created user
  612. *
  613. * @param $user User the administrative user who is installing
  614. * @return mixed the user group on success, false on failure
  615. */
  616. private function create_admin_group( $user )
  617. {
  618. // Create the admin group
  619. $group = UserGroup::create( array( 'name' => _t( 'admin' ) ) );
  620. if ( ! $group ) {
  621. return false;
  622. }
  623. $group->add( $user->id );
  624. return $group;
  625. }
  626. private function create_anonymous_group()
  627. {
  628. // Create the anonymous group
  629. $group = UserGroup::create( array( 'name' => _t( 'anonymous' ) ) );
  630. if ( ! $group ) {
  631. return false;
  632. }
  633. $group->grant( 'post_entry', 'read' );
  634. $group->grant( 'post_page', 'read' );
  635. // Add the anonumous user to the anonymous group
  636. $group->add( 0 );
  637. }
  638. /**
  639. * Write the default options
  640. */
  641. private function create_default_options()
  642. {
  643. // Create the default options
  644. Options::set( 'installed', true );
  645. Options::set( 'title', $this->handler_vars['blog_title'] );
  646. Options::set( 'pagination', '5' );
  647. Options::set( 'atom_entries', '5' );
  648. Options::set( 'theme_name', 'k2' );
  649. Options::set( 'theme_dir', 'k2' );
  650. Options::set( 'comments_require_id', 1 );
  651. Options::set( 'locale', $this->handler_vars['locale'] );
  652. Options::set( 'timezone', 'UTC' );
  653. Options::set( 'dateformat', 'Y-m-d' );
  654. Options::set( 'timeformat', 'g:i a' );
  655. Options::set( 'log_min_severity', 3 ); // the default logging level - 3 should be 'info'
  656. Options::set( 'spam_percentage', 100 );
  657. // generate a random-ish number to use as the salt for
  658. // a SHA1 hash that will serve as the unique identifier for
  659. // this installation. Also for use in cookies
  660. Options::set( 'GUID', sha1( Utils::nonce() ) );
  661. // Let's prepare the EventLog here, as well
  662. EventLog::register_type( 'default', 'habari' );
  663. EventLog::register_type( 'user', 'habari' );
  664. EventLog::register_type( 'authentication', 'habari' );
  665. EventLog::register_type( 'content', 'habari' );
  666. EventLog::register_type( 'comment', 'habari' );
  667. // Add the cronjob to trim the log so that it doesn't get too big
  668. CronTab::add_daily_cron( 'trim_log', array( 'EventLog', 'trim' ), _t( 'Trim the log table' ) );
  669. // Add the cronjob to check for plugin updates
  670. CronTab::add_daily_cron( 'update_check', array( 'Update', 'cron' ), _t( 'Perform a check for plugin updates.' ) );
  671. return true;
  672. }
  673. /**
  674. * Add the standard post types and statuses to the database
  675. */
  676. private function create_base_post_types()
  677. {
  678. // first, let's create our default post types of
  679. // "entry" and "page"
  680. Post::add_new_type( 'entry' );
  681. Post::add_new_type( 'page' );
  682. // now create post statuses for
  683. // "published" and "draft"
  684. // Should "private" status be added here, or through a plugin?
  685. Post::add_new_status( 'draft' );
  686. Post::add_new_status( 'published' );
  687. Post::add_new_status( 'scheduled', true );
  688. return true;
  689. }
  690. /**
  691. * Add the tags vocabulary
  692. */
  693. private function create_tags_vocabulary()
  694. {
  695. $vocabulary = new Vocabulary( array( 'name' => 'tags', 'description' => 'Habari\'s tags implementation', 'features' => array( 'multiple', 'free' ) ) );
  696. $vocabulary->insert();
  697. return true;
  698. }
  699. /**
  700. * Create the first post
  701. **/
  702. private function create_first_post()
  703. {
  704. $users = Users::get();
  705. Post::create( array(
  706. 'title' => 'Habari',
  707. '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!' ),
  708. 'user_id' => $users[0]->id,
  709. 'status' => Post::status( 'published' ),
  710. 'content_type' => Post::type( 'entry' ),
  711. 'tags' => 'habari',
  712. ) );
  713. return true;
  714. }
  715. /**
  716. * Install schema tables from the respective RDBMS schema
  717. * @param $db_type string The schema string for the database
  718. * @param $table_prefix string The prefix to use on each table name
  719. * @param $db_schema string The database name
  720. * @return array Array of queries to execute
  721. */
  722. private function get_create_table_queries( $db_type, $table_prefix, $db_schema )
  723. {
  724. /* Grab the queries from the RDBMS schema file */
  725. $file_path = HABARI_PATH . "/system/schema/{$db_type}/schema.sql";
  726. $schema_sql = trim( file_get_contents( $file_path ), "\r\n " );
  727. $schema_sql = str_replace( '{$schema}', $db_schema, $schema_sql );
  728. $schema_sql = str_replace( '{$prefix}', $table_prefix, $schema_sql );
  729. /*
  730. * Just in case anyone creates a schema file with separate statements
  731. * not separated by two newlines, let's clean it here...
  732. * Likewise, let's clean up any separations of *more* than two newlines
  733. */
  734. $schema_sql = str_replace( array( "\r\n", "\r", ), array( "\n", "\n" ), $schema_sql );
  735. $schema_sql = preg_replace( "/;\n([^\n])/", ";\n\n$1", $schema_sql );
  736. $schema_sql = preg_replace( "/\n{3,}/", "\n\n", $schema_sql );
  737. $queries = preg_split( '/(\\r\\n|\\r|\\n)\\1/', $schema_sql );
  738. return $queries;
  739. }
  740. /**
  741. * Returns an RDMBS-specific CREATE SCHEMA plus user SQL expression(s)
  742. *
  743. * @return string[] array of SQL queries to execute
  744. */
  745. private function get_create_schema_and_user_queries()
  746. {
  747. $db_host = $this->handler_vars['db_host'];
  748. $db_type = $this->handler_vars['db_type'];
  749. $db_schema = $this->handler_vars['db_schema'];
  750. $db_user = $this->handler_vars['db_user'];
  751. $db_pass = $this->handler_vars['db_pass'];
  752. $queries = array();
  753. switch ( $db_type ) {
  754. case 'mysql':
  755. $queries[] = 'CREATE DATABASE ' . $db_schema . ';';
  756. $queries[] = 'GRANT ALL ON ' . $db_schema . '.* TO \'' . $db_user . '\'@\'' . $db_host . '\' ' .
  757. 'IDENTIFIED BY \'' . $db_pass . '\';';
  758. break;
  759. case 'pgsql':
  760. $queries[] = 'CREATE DATABASE ' . $db_schema . ';';
  761. $queries[] = 'GRANT ALL ON DATABASE ' . $db_schema . ' TO ' . $db_user . ';';
  762. break;
  763. default:
  764. die( _t( 'currently unsupported.' ) );
  765. }
  766. return $queries;
  767. }
  768. /**
  769. * Gets the configuration template, inserts the variables into it, and returns it as a string
  770. *
  771. * @return string The config.php template for the db_type schema
  772. */
  773. private function get_config_file()
  774. {
  775. if ( ! ( $file_contents = file_get_contents( HABARI_PATH . "/system/schema/" . $this->handler_vars['db_type'] . "/config.php" ) ) ) {
  776. return false;
  777. }
  778. $vars = array();
  779. foreach ( $this->handler_vars as $k => $v ) {
  780. $vars[$k] = addslashes( $v );
  781. }
  782. $keys = array();
  783. foreach ( array_keys( $vars ) as $v ) {
  784. $keys[] = Utils::map_array( $v );
  785. }
  786. $file_contents = str_replace(
  787. $keys,
  788. $vars,
  789. $file_contents
  790. );
  791. return $file_contents;
  792. }
  793. /**
  794. * Writes the configuration file with the variables needed for
  795. * initialization of the application
  796. *
  797. * @param Bool $ignore_registry skip the configuration registry check (used in config rewrite)
  798. * @return bool Did the file get written?
  799. */
  800. private function write_config_file( $ignore_registry = false )
  801. {
  802. // first, check if a config.php file exists
  803. if ( file_exists( Site::get_dir( 'config_file' ) ) ) {
  804. // set the defaults for comparison
  805. $db_host = $this->handler_vars['db_host'];
  806. $db_file = $this->handler_vars['db_file'];
  807. $db_type = $this->handler_vars['db_type'];
  808. $db_schema = $this->handler_vars['db_schema'];
  809. $db_user = $this->handler_vars['db_user'];
  810. $db_pass = $this->handler_vars['db_pass'];
  811. $table_prefix = $this->handler_vars['table_prefix'];
  812. // set the connection string
  813. switch ( $db_type ) {
  814. case 'mysql':
  815. $connection_string = "$db_type:host=$db_host;dbname=$db_schema";
  816. break;
  817. case 'pgsql':
  818. $connection_string = "$db_type:host=$db_host dbname=$db_schema";
  819. break;
  820. case 'sqlite':
  821. $connection_string = "$db_type:$db_file";
  822. break;
  823. }
  824. // load the config.php file
  825. include( Site::get_dir( 'config_file' ) );
  826. // and now we compare the values defined there to
  827. // the values POSTed to the installer
  828. if ( !$ignore_registry && Config::exists( 'db_connection' ) &&
  829. ( Config::get( 'db_connection' )->connection_string == $connection_string )
  830. && ( Config::get( 'db_connection' )->username == $db_user )
  831. && ( Config::get( 'db_connection' )->password == $db_pass )
  832. && ( Config::get( 'db_connection' )->prefix == $table_prefix )
  833. ) {
  834. // the values are the same, so don't bother
  835. // trying to write to config.php
  836. return true;
  837. }
  838. }
  839. if ( ! ( $file_contents = file_get_contents( HABARI_PATH . "/system/schema/" . $this->handler_vars['db_type'] . "/config.php" ) ) ) {
  840. return false;
  841. }
  842. if ( $file_contents = $this->get_config_file() ) {
  843. if ( $file = @fopen( Site::get_dir( 'config_file' ), 'w' ) ) {
  844. if ( fwrite( $file, $file_contents, strlen( $file_contents ) ) ) {
  845. fclose( $file );
  846. return true;
  847. }
  848. }
  849. $this->handler_vars['config_file'] = Site::get_dir( 'config_file' );
  850. $this->handler_vars['file_contents'] = Utils::htmlspecialchars( $file_contents );
  851. $this->display( 'config' );
  852. return false;
  853. }
  854. return false; // Only happens when config.php template does not exist.
  855. }
  856. public function activate_plugins()
  857. {
  858. // extract checked plugin IDs from $_POST
  859. $plugin_ids = array();
  860. foreach ( $_POST as $id => $activate ) {
  861. if ( preg_match( '/plugin_\w+/u', $id ) && $activate ) {
  862. $id = substr( $id, 7 );
  863. $plugin_ids[] = $id;
  864. }
  865. }
  866. // set the user_id in the session in case plugin activation methods need it
  867. if ( ! $u = User::get_by_name( $this->handler_vars['admin_username'] ) ) {
  868. // @todo die gracefully
  869. die( _t( 'No admin user found' ) );
  870. }
  871. $u->remember();
  872. // loop through all plugins to find matching plugin files
  873. $plugin_files = Plugins::list_all();
  874. foreach ( $plugin_files as $file ) {
  875. $id = Plugins::id_from_file( $file );
  876. if ( in_array( $id, $plugin_ids ) ) {
  877. Plugins::activate_plugin( $file );
  878. }
  879. }
  880. // unset the user_id session variable
  881. Session::clear_userid( $_SESSION['user_id'] );
  882. unset( $_SESSION['user_id'] );
  883. }
  884. /**
  885. * returns an array of .htaccess declarations used by Habari
  886. */
  887. public function htaccess()
  888. {
  889. $htaccess = array(
  890. 'open_block' => '### HABARI START',
  891. 'engine_on' => 'RewriteEngine On',
  892. 'rewrite_cond_f' => 'RewriteCond %{REQUEST_FILENAME} !-f',
  893. 'rewrite_cond_d' => 'RewriteCond %{REQUEST_FILENAME} !-d',
  894. 'rewrite_favicon' => 'RewriteCond %{REQUEST_URI} !=/favicon.ico',
  895. 'rewrite_base' => '#RewriteBase /',
  896. 'rewrite_rule' => 'RewriteRule . index.php [PT]',
  897. 'hide_habari' => 'RewriteRule ^(system/(classes|handlers|locale|schema|$)) index.php [PT]',
  898. //'http_auth' => 'RewriteRule .* [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] # Make sure HTTP auth works in PHP-CGI configs',
  899. 'close_block' => '### HABARI END',
  900. );
  901. $rewrite_base = trim( dirname( $_SERVER['SCRIPT_NAME'] ), '/\\' );
  902. if ( $rewrite_base != '' ) {
  903. $htaccess['rewrite_base'] = 'RewriteBase /' . $rewrite_base;
  904. }
  905. return $htaccess;
  906. }
  907. /**
  908. * checks for the presence of an .htaccess file
  909. * invokes write_htaccess() as needed
  910. */
  911. public function check_htaccess()
  912. {
  913. // default is assume we have mod_rewrite
  914. $this->handler_vars['no_mod_rewrite'] = false;
  915. // If this is the mod_rewrite check request, then bounce it as a success.
  916. if ( strpos( $_SERVER['REQUEST_URI'], 'check_mod_rewrite' ) !== false ) {
  917. echo 'ok';
  918. exit;
  919. }
  920. if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Apache' ) ) {
  921. // .htaccess is only needed on Apache
  922. // @TODO: add support for IIS and lighttpd rewrites
  923. return true;
  924. }
  925. $result = false;
  926. if ( file_exists( HABARI_PATH . '/.htaccess' ) ) {
  927. $htaccess = file_get_contents( HABARI_PATH . '/.htaccess' );
  928. if ( false === strpos( $htaccess, 'HABARI' ) ) {
  929. // the Habari block does not exist in this file
  930. // so try to create it
  931. $result = $this->write_htaccess( true );
  932. }
  933. else {
  934. // the Habari block exists
  935. $result = true;
  936. }
  937. }
  938. else {
  939. // no .htaccess exists. Try to create one
  940. $result = $this->write_htaccess();
  941. }
  942. if ( $result ) {
  943. // the Habari block exists, but we need to make sure
  944. // it is correct.
  945. // Check that the rewrite rules actually do the job.
  946. $test_ajax_url = Site::get_url( 'habari' ) . '/check_mod_rewrite';
  947. $rr = new RemoteRequest( $test_ajax_url, 'POST', 5 );
  948. try {
  949. $rr_result = $rr->execute();
  950. }
  951. catch ( Exception $e ) {
  952. $result = $this->write_htaccess( true, true, true );
  953. }
  954. }
  955. return $result;
  956. }
  957. /**
  958. * attempts to write the .htaccess file if none exists
  959. * or to write the Habari-specific portions to an existing .htaccess
  960. * @param bool whether an .htaccess file already exists or not
  961. * @param bool whether to remove and re-create any existing Habari block
  962. * @param bool whether to try a rewritebase in the .htaccess
  963. **/
  964. public function write_htaccess( $exists = false, $update = false, $rewritebase = true )
  965. {
  966. $htaccess = $this->htaccess();
  967. if ( $rewritebase ) {
  968. $rewrite_base = trim( dirname( $_SERVER['SCRIPT_NAME'] ), '/\\' );
  969. $htaccess['rewrite_base'] = 'RewriteBase /' . $rewrite_base;
  970. }
  971. $file_contents = "\n" . implode( "\n", $htaccess ) . "\n";
  972. if ( ! $exists ) {
  973. if ( ! is_writable( HABARI_PATH ) ) {
  974. // we can't create the file
  975. return false;
  976. }
  977. }
  978. else {
  979. if ( ! is_writable( HABARI_PATH . '/.htaccess' ) ) {
  980. // we can't update the file
  981. return false;
  982. }
  983. }
  984. if ( $update ) {
  985. // we're updating an existing but incomplete .htaccess
  986. // care must be take only to remove the Habari bits
  987. $htaccess = file_get_contents( HABARI_PATH . '/.htaccess' );
  988. $file_contents = preg_replace( '%### HABARI START.*?### HABARI END%ims', $file_contents, $htaccess );
  989. // Overwrite the existing htaccess with one that includes the modified Habari rewrite block
  990. $fmode = 'w';
  991. }
  992. else {
  993. // Append the Habari rewrite block to the existing file.
  994. $fmode = 'a';
  995. }
  996. //Save the htaccess
  997. if ( $fh = fopen( HABARI_PATH . '/.htaccess', $fmode ) ) {
  998. if ( false === fwrite( $fh, $file_contents ) ) {
  999. return false;
  1000. }
  1001. fclose( $fh );
  1002. }
  1003. else {
  1004. return false;
  1005. }
  1006. return true;
  1007. }
  1008. /**
  1009. * returns an array of Files declarations used by Habari
  1010. */
  1011. public function sqlite_contents()
  1012. {
  1013. $db_file = basename( $this->handler_vars['db_file'] );
  1014. $contents = array(
  1015. '### HABARI SQLITE START',
  1016. '<Files "' . $db_file . '">',
  1017. 'Order deny,allow',
  1018. 'deny from all',
  1019. '</Files>',
  1020. '### HABARI SQLITE END'
  1021. );
  1022. return $contents;
  1023. }
  1024. /**
  1025. * attempts to write the Files clause to the .htaccess file
  1026. * if the clause for this sqlite doesn't exist.
  1027. * @return bool success or failure
  1028. **/
  1029. public function secure_sqlite()
  1030. {
  1031. if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Apache' ) ) {
  1032. // .htaccess is only needed on Apache
  1033. // @TODO: Notify people on other servers to take measures to secure the SQLite file.
  1034. return true;
  1035. }
  1036. if ( !file_exists( HABARI_PATH . '/.htaccess' ) ) {
  1037. // no .htaccess to write to
  1038. return false;
  1039. }
  1040. if ( !is_writable( HABARI_PATH . DIRECTORY_SEPARATOR . '.htaccess' ) ) {
  1041. // we can't update the file
  1042. return false;
  1043. }
  1044. // Get the files clause
  1045. $sqlite_contents = $this->sqlite_contents();
  1046. $files_contents = "\n" . implode( "\n", $sqlite_contents ) . "\n";
  1047. // See if it already exists
  1048. $current_files_contents = file_get_contents( HABARI_PATH . DIRECTORY_SEPARATOR . '.htaccess' );
  1049. if ( false === strpos( $current_files_contents, $files_contents ) ) {
  1050. // If not, append the files clause to the .htaccess file
  1051. if ( $fh = fopen( HABARI_PATH . DIRECTORY_SEPARATOR . '.htaccess', 'a' ) ) {
  1052. if ( false === fwrite( $fh, $files_contents ) ) {
  1053. // Can't write to the file
  1054. return false;
  1055. }
  1056. fclose( $fh );
  1057. }
  1058. else {
  1059. // Can't open the file
  1060. return false;
  1061. }
  1062. }
  1063. // Success!
  1064. return true;
  1065. }
  1066. private function upgrade_db_pre ( $current_version )
  1067. {
  1068. // this is actually a stripped-down version of DatabaseConnection::upgrade() - it doesn't support files
  1069. $upgrade_functions = get_class_methods( $this );
  1070. $upgrades = array();
  1071. foreach ( $upgrade_functions as $fn ) {
  1072. // match all methods named "upgrade_db_pre_<rev#>"
  1073. if ( preg_match( '%^upgrade_db_pre_([0-9]+)$%i', $fn, $matches ) ) {
  1074. $upgrade_version = intval( $matches[1] );
  1075. if ( $upgrade_version > $current_version ) {
  1076. $upgrades[ sprintf( '%010s_1', $upgrade_version ) ] = $fn;
  1077. }
  1078. }
  1079. }
  1080. // sort the upgrades by revision, ascending
  1081. ksort( $upgrades );
  1082. foreach ( $upgrades as $upgrade ) {
  1083. $result =& call_user_func( array( $this, $upgrade ) );
  1084. // if we failed, abort
  1085. if ( $result === false ) {
  1086. break;
  1087. }
  1088. }
  1089. }
  1090. private function upgrade_db_post ( $current_version )
  1091. {
  1092. // this is actually a stripped-down version of DatabaseConnection::upgrade() - it doesn't support files
  1093. $upgrade_functions = get_class_methods( $this );
  1094. $upgrades = array();
  1095. foreach ( $upgrade_functions as $fn ) {
  1096. // match all methods named "upgrade_db_post_<rev#>"
  1097. if ( preg_match( '%^upgrade_db_post_([0-9]+)$%i', $fn, $matches ) ) {
  1098. $upgrade_version = intval( $matches[1] );
  1099. if ( $upgrade_version > $current_version ) {
  1100. $upgrades[ sprintf( '%010s_1', $upgrade_version ) ] = $fn;
  1101. }
  1102. }
  1103. }
  1104. // sort the upgrades by revision, ascending
  1105. ksort( $upgrades );
  1106. foreach ( $upgrades as $upgrade ) {
  1107. $result = call_user_func( array( $this, $upgrade ) );
  1108. // if we failed, abort
  1109. if ( $result === false ) {
  1110. break;
  1111. }
  1112. }
  1113. }
  1114. private function upgrade_db_pre_1345 ()
  1115. {
  1116. // fix duplicate tag_slugs
  1117. // first, get all the tags with duplicate entries
  1118. $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';
  1119. $tags = DB::get_results( $query );
  1120. // assuming we got some tags to fix...
  1121. if ( count( $tags ) > 0 ) {
  1122. $slug_to_id = array();
  1123. $fix_tags = array();
  1124. foreach ( $tags as $tag_row ) {
  1125. // skip the first tag text so we end up with something, presumably the first tag entered (it had the lowest ID in the db)
  1126. if ( !isset( $fix_tags[ $tag_row->tag_slug ] ) ) {
  1127. $slug_to_id[ $tag_row->tag_slug ] = $tag_row->id; // collect the slug => id so we can rename with an absolute id later
  1128. $fix_tags[ $tag_row->tag_slug ] = array();
  1129. }
  1130. else {
  1131. $fix_tags[ $tag_row->tag_slug ][ $tag_row->id ] = $tag_row->tag_text;
  1132. }
  1133. }
  1134. foreach ( $fix_tags as $tag_slug => $tag_texts ) {
  1135. Tags::rename( $slug_to_id[ $tag_slug ], array_keys( $tag_texts ) );
  1136. }
  1137. }
  1138. return true;
  1139. }
  1140. /**
  1141. * Upgrade the database when the database version stored is lower than the one in source
  1142. * @todo Make more db-independent
  1143. */
  1144. public function upgrade_db()
  1145. {
  1146. if ( Options::get( 'db_upgrading' ) ) {
  1147. // quit with an error message.
  1148. $this->display_currently_upgrading();
  1149. }
  1150. // don't allow duplicate upgrades.
  1151. Options::set( 'db_upgrading', true );
  1152. // This database-specific code needs to be moved into the schema-specific functions
  1153. list( $schema, $remainder )= explode( ':', Config::get( 'db_connection' )->connection_string );
  1154. switch ( $schema ) {
  1155. case 'sqlite':
  1156. $db_name = '';
  1157. break;
  1158. case 'mysql':
  1159. list( $host,$name ) = explode( ';', $remainder );
  1160. list( $discard, $db_name ) = explode( '=', $name );
  1161. break;
  1162. case 'pgsql':
  1163. list( $host,$name ) = explode( ' ', $remainder );
  1164. list( $discard, $db_name ) = explode( '=', $name );
  1165. break;
  1166. }
  1167. Cache::purge();
  1168. // get the current db version
  1169. $version = Options::get( 'db_version' );
  1170. // do some pre-dbdelta ad-hoc hacky hack code
  1171. $this->upgrade_db_pre( $version );
  1172. // run schema-specific upgrade scripts for before dbdelta
  1173. DB::upgrade_pre( $version );
  1174. // Get the queries for this database and apply the changes to the structure
  1175. $queries = $this->get_create_table_queries( $schema, Config::get( 'db_connection' )->prefix, $db_name );
  1176. DB::dbdelta( $queries );
  1177. // Apply data changes to the database based on version, call the db-specific upgrades, too.
  1178. $this->upgrade_db_post( $version );
  1179. // run schema-specific upgrade scripts for after dbdelta
  1180. DB::upgrade_post( $version );
  1181. Version::save_dbversion();
  1182. Options::delete( 'db_upgrading' );
  1183. }
  1184. private function display_currently_upgrading()
  1185. {
  1186. // Error template.
  1187. $error_template = "<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>";
  1188. // Format page with localized messages.
  1189. $error_page = sprintf( $error_template,
  1190. _t( "Site Maintenance" ), // page title
  1191. _t( "Habari is currently being upgraded." ), // H1 tag
  1192. _t( "Try again in a little while." ) // Error message.
  1193. );
  1194. // Set correct HTTP header and die.
  1195. header( 'HTTP/1.1 503 Service Unavailable', true, 503 );
  1196. die( $error_page );
  1197. }
  1198. private function upgrade_db_post_1310 ()
  1199. {
  1200. // Auto-truncate the log table
  1201. if ( ! CronTab::get_cronjob( 'truncate_log' ) ) {
  1202. CronTab::add_daily_cron( 'truncate_log', array( 'Utils', 'truncate_log' ), _t( 'Truncate the log table' ) );
  1203. }
  1204. return true;
  1205. }
  1206. private function upgrade_db_post_1794 ()
  1207. {
  1208. Post::add_new_status( 'scheduled', true );
  1209. return true;
  1210. }
  1211. private function upgrade_db_post_1845 ()
  1212. {
  1213. // Strip the base path off active plugins
  1214. $base_path = array_map( create_function( '$s', 'return str_replace(\'\\\\\', \'/\', $s);' ), array( HABARI_PATH ) );
  1215. $activated = Options::get( 'active_plugins' );
  1216. if ( is_array( $activated ) ) {
  1217. foreach ( $activated as $plugin ) {
  1218. $index = array_search( $plugin, $activated );
  1219. $plugin = str_replace( $base_path, '', $plugin );
  1220. $activated[$index] = $plugin;
  1221. }
  1222. Options::set( 'active_plugins', $activated );
  1223. }
  1224. return true;
  1225. }
  1226. private function upgrade_db_post_2264()
  1227. {
  1228. // create admin group
  1229. $admin_group = UserGroup::get_by_name( 'admin' );
  1230. if ( ! ( $admin_group instanceOf UserGroup ) ) {
  1231. $admin_group = UserGroup::create( array( 'name' => 'admin' ) );
  1232. }
  1233. // add all users to the admin group
  1234. $users = Users::get_all();
  1235. $ids = array();
  1236. foreach ( $users as $user ) {
  1237. $ids[] = $user->id;
  1238. }
  1239. $admin_group->add( $ids );
  1240. return true;
  1241. }
  1242. private function upgrade_db_post_2707 ()
  1243. {
  1244. // sets a default timezone and date / time formats for the options page
  1245. if ( !Options::get( 'timezone' ) ) {
  1246. Options::set( 'timezone', 'UTC' );
  1247. }
  1248. if ( !Options::get( 'dateformat' ) ) {
  1249. Options::set( 'dateformat', 'Y-m-d' );
  1250. }
  1251. if ( !Options::get( 'timeformat' ) ) {
  1252. Options::set( 'timeformat', 'H:i:s' );
  1253. }
  1254. return true;
  1255. }
  1256. private function upgrade_db_post_2786 ()
  1257. {
  1258. // fixes all the bad post2tag fields that didn't get deleted when a post was deleted
  1259. DB::query( 'DELETE FROM {tag2post} WHERE post_id NOT IN ( SELECT DISTINCT id FROM {posts} )' );
  1260. // now, delete any tags that have no posts left
  1261. DB::query( 'DELETE FROM {tags} WHERE id NOT IN ( SELECT DISTINCT tag_id FROM {tag2post} )' );
  1262. return true;
  1263. }
  1264. private function upgrade_db_post_3030()
  1265. {
  1266. // Create the admin group
  1267. $group = UserGroup::create( array( 'name' => 'admin' ) );
  1268. if ( ! $group ) {
  1269. return false;
  1270. }
  1271. // Add the default tokens
  1272. ACL::create_default_tokens();
  1273. // Give admin group access to the super_user token
  1274. $group->grant( 'super_user' );
  1275. // Until now, all users were admins, restore that
  1276. $all_users = Users::get_all();
  1277. foreach ( $all_users as $user ) {
  1278. $group->add( $user );
  1279. }
  1280. // Create the anonymous group
  1281. $this->create_anonymous_group();
  1282. }
  1283. private function upgrade_db_post_3124()
  1284. {
  1285. ACL::rebuild_permissions();
  1286. }
  1287. private function upgrade_db_post_3158()
  1288. {
  1289. // delete own_post_typeX tokens rather than rebuild the whole default token set
  1290. foreach ( Post::list_active_post_types() as $name => $posttype ) {
  1291. ACL::destroy_token( 'own_post_' . Utils::slugify( $name ) );
  1292. }
  1293. ACL::destroy_token( 'own_posts_any' );
  1294. ACL::create_token( 'own_posts', _t( 'Permissions on one\'s own posts' ), 'Content', true );
  1295. }
  1296. private function upgrade_db_post_3236()
  1297. {
  1298. // Add a default to the number of posts of a feed
  1299. $atom_entries = Options::get( 'atom_entries' );
  1300. if ( empty( $atom_entries ) ) {
  1301. Options::set( 'atom_entries', '5' );
  1302. }
  1303. // Create the default authenticated group
  1304. $authenticated_group = UserGroup::get_by_name( _t( 'authenticated' ) );
  1305. if ( ! $authenticated_group instanceof UserGroup ) {
  1306. $authenticated_group = UserGroup::create( array( 'name' => _t( 'authenticated' ) ) );
  1307. }
  1308. $authenticated_group->grant( 'post_entry', 'read' );
  1309. $authenticated_group->grant( 'post_page', 'read' );
  1310. $authenticated_group->grant( 'comment' );
  1311. }
  1312. private function upgrade_db_post_3539()
  1313. {
  1314. // get the global option
  1315. $hide = Options::get( 'dashboard__hide_spam_count' );
  1316. // if it was set to hide, get all our available users and set their info values instead
  1317. if ( $hide == true ) {
  1318. $users = Users::get();
  1319. foreach ( $users as $user ) {
  1320. $user->info->dashboard_hide_spam_count = 1;
  1321. $user->update();
  1322. }
  1323. }
  1324. Options::delete( 'dashboard__hide_spam_count' );
  1325. return true;
  1326. }
  1327. private function upgrade_db_post_3698()
  1328. {
  1329. ACL::create_token( 'manage_self', _t( 'Edit own profile' ), 'Administration' );
  1330. }
  1331. private function upgrade_db_post_3701()
  1332. {
  1333. ACL::create_token( 'manage_dash_modules', _t( 'Manage dashboard modules' ), 'Administration' );
  1334. }
  1335. private function upgrade_db_post_3749()
  1336. {
  1337. $type_id = Vocabulary::object_type_id( 'post' );
  1338. $vocabulary = Vocabulary::create( array( 'name' => 'tags', 'description' => 'Habari\'s tags implementation', 'features' => array( 'multiple', 'free' ) ) );
  1339. $new_tag = null;
  1340. $post_ids = array();
  1341. $prefix = Config::get( 'db_connection' )->prefix;
  1342. $results = DB::get_results( "SELECT id, tag_text, tag_slug from {$prefix}tags" );
  1343. foreach ( $results as $tag ) {
  1344. $new_tag = $vocabulary->add_term( $tag->tag_text );
  1345. $post_ids = DB::get_column( "SELECT post_id FROM {$prefix}tag2post WHERE tag_id = ?", array( $tag->id ) );
  1346. foreach ( $post_ids as $id ) {
  1347. DB::insert( "{object_terms}", array( 'term_id' => $new_tag->id, 'object_id' => $id, 'object_type_id' => $type_id ) );
  1348. }
  1349. }
  1350. }
  1351. private function upgrade_db_post_4291()
  1352. {
  1353. // get all plugins so the legacy ones can be deactivated.
  1354. $active_plugins = Plugins::list_active();
  1355. $all_plugins = Installhandler::get_plugins();
  1356. $legacy_plugins = array();
  1357. foreach ( $all_plugins as $plugin ) {
  1358. if ( !isset( $plugin[ 'info' ] ) ) {
  1359. $key = array_search( $plugin[ 'file' ], $active_plugins );
  1360. $legacy_plugins[ $key ] = $plugin[ 'file' ];
  1361. }
  1362. }
  1363. $valid_plugins = array_diff_key( Options::get( 'active_plugins' ), $legacy_plugins );
  1364. // valid_plugins contains only working plugins, but the classnames are missing. The following was previously upgrade_db_post_3484()
  1365. $new_plugins = array();
  1366. if ( is_array( $valid_plugins ) ) {
  1367. foreach ( $valid_plugins as $filename ) {
  1368. if ( !file_exists( $filename ) ) {
  1369. // try adding base path to stored path
  1370. $filename = HABARI_PATH . $filename;
  1371. }
  1372. if ( file_exists( $filename ) ) {
  1373. // it is now safe to do this since plugins with info() functions are not in $valid_plugins
  1374. require_once( $filename );
  1375. $class = Plugins::class_from_filename( $filename );
  1376. $short_file = substr( $filename, strlen( HABARI_PATH ) );
  1377. if ( $class ) {
  1378. $new_plugins[ $class ] = $short_file;
  1379. }
  1380. }
  1381. }
  1382. }
  1383. // replace option with only the usuable plugins
  1384. Options::set( 'active_plugins', $new_plugins );
  1385. }

Large files files are truncated, but you can click here to view the full file