/** * Imports the SQL statements from the db_sync.sql file, or from the file provided through file upload. * This function can only be called from the Symphony backend, the CDI extension must be enabled and running in Database Synchronisation mode. */ public static function import() { // We should not be processing any queries when the extension is disabled or when it is in 'Continuous Database Integration' mode if((!class_exists('Administration')) || !CdiUtil::isEnabled() || !CdiUtil::isCdiDBSyncSlave()) { throw new Exception("You can only import the Database Synchroniser file from the Preferences page. The CDI extension must be enabled and should be a 'Slave' instance in 'Database Synchronisation' mode."); } // Prevent the CdiLogQuery::log() from persisting queries that are executed by CDI itself // This should not be possible anyway because we can only import in "Slave" mode, but just to be sure! CdiLogQuery::isUpdating(true); // Handle file upload $syncFile = CDI_DB_SYNC_FILE; if(!empty($_FILES['cdi_import_file']['tmp_name'])) { $syncFile = $_FILES['cdi_import_file']['tmp_name']; } //Execute the queries from file try { if(file_exists($syncFile)) { $contents = file_get_contents($syncFile); $queries = explode(';',$contents); foreach($queries as $query) { $query = trim($query); // ommit comments and empty statements if(!preg_match('/^--/i', $query) && !$query=='') { Symphony::Database()->query($query); } } if(isset($_POST['settings']['cdi']['deleteSyncFile'])) { unlink($syncFile); } } } catch (Exception $e) { // Re-enable CdiLogQuery::log() to persist queries CdiLogQuery::isUpdating(false); throw $e; } // Save the last update date to configuration Symphony::Configuration()->set('last-update', time(), 'cdi'); Symphony::Configuration()->write(); // Re-enable CdiLogQuery::log() to persist queries CdiLogQuery::isUpdating(false); }
/** * The CdiQueryLog::log() function is called from the Database implementation responsible for executing Symphony SQL queries * If in MASTER mode, CDI will save the query to disk allowing it to be committed to the VCS. From there it will be available * for automatic query exection by CDI slave instances (see also CdiLogQuery::executeQueries()). * @param String $query */ public static function log($query) { // Prevent execution on the frontend and check configuration conditions // Do not log the query when CDI is disabled, in SLAVE mode or busy executing queries. // Additionally if the logger is not installed, you should not be able to call this function if((!class_exists('Administration')) || !CdiUtil::isEnabled() || self::$isUpdating) { return true; } $query = trim($query); $tbl_prefix = Symphony::Configuration()->get('tbl_prefix', 'database'); /* FILTERS */ // do not register changes to tbl_cdi_log if (preg_match("/{$tbl_prefix}cdi_log/i", $query)) return true; // only structural changes, no SELECT statements if (!preg_match('/^(insert|update|delete|create|drop|alter|rename)/i', $query)) return true; // un-tracked tables (sessions, cache, authors) if (preg_match("/{$tbl_prefix}(authors|cache|forgotpass|sessions)/i", $query)) return true; // content updates in tbl_entries (includes tbl_entries_fields_*) if (preg_match('/^(insert|delete|update)/i', $query) && preg_match("/({$config->tbl_prefix}entries)/i", $query)) return true; // append query delimeter if it doesn't exist if (!preg_match('/;$/', $query)) $query .= ";"; // Replace the table prefix in the query // This allows query execution on slave instances with different table prefix. $query = str_replace($tbl_prefix,'tbl_',$query); // We've come far enough... let's try to save it to disk! if(CdiUtil::isCdiMaster()) { return CdiMaster::persistQuery($query); } else if(CdiUtil::isCdiDBSyncMaster()) { return CdiDBSync::persistQuery($query); } else { //TODO: error handling for the unusual event that we are dealing with here. return true; } }
public static function restore() { // We should only backup the database when the extension is enabled and version 1.09 of the Dump_DB extension is installed. if((!class_exists('Administration')) || !CdiUtil::isEnabled()) { throw new Exception("You can only restore the Symphony database from the Preferences page"); } if(!CdiUtil::hasDumpDBInstalled()) { throw new Exception('No valid version of <a href="http://symphony-cms.com/download/extensions/view/40986/">Dump DB</a> found. Please make sure it is installed.'); } else { require_once(EXTENSIONS . '/dump_db/lib/class.mysqlrestore.php'); // Prevent the CdiLogQuery::log() from persisting queries that are executed by CDI itself CdiLogQuery::isUpdating(true); // COPIED FROM Dump_DB version 1.09 // Adjust to only support FULL database dump $restore = new MySQLRestore(Symphony::Database()); $filename = CDI_BACKUP_ROOT . '/' . $_POST["ref"]; if(isset($_FILES['dumpdb_restore_file'])) { $filename = self::getFileName('manual'); rename($_FILES['dumpdb_restore_file']['tmp_name'],$filename); } if(file_exists($filename)) { $data = file_get_contents($filename); $data = str_replace('tbl_', Symphony::Configuration()->get('tbl_prefix', 'database'), $data); $restore->import($data); } else { throw new Exception("The provided restore file '" . $filename . "' could not be found."); } // Re-enable CdiLogQuery::log() to persist queries CdiLogQuery::isUpdating(false); } }
<?php $result = array(); require_once(EXTENSIONS . '/cdi/lib/class.cdiutil.php'); require_once(EXTENSIONS . '/cdi/lib/class.cdimaster.php'); require_once(EXTENSIONS . '/cdi/lib/class.cdislave.php'); require_once(EXTENSIONS . '/cdi/lib/class.cdidbsync.php'); require_once(EXTENSIONS . '/cdi/lib/class.cdidumpdb.php'); require_once(EXTENSIONS . '/cdi/lib/class.cdilogquery.php'); require_once(EXTENSIONS . '/cdi/lib/class.cdipreferences.php'); // We should not be processing any queries when the extension is disabled or when we are the Master instance if((!class_exists('Administration')) || !CdiUtil::isEnabled()) { $result["status"] = "error"; $result["message"] = "You can only execute actions from within Symphony and when the CDI extension is enabled"; Symphony::Log()->pushToLog('[CDI] You can only execute actions from within Symphony and when the CDI extension is enabled', E_NOTICE, true); } // Clean the database and log files when the cdi_clear action is called if(isset($_POST["action"]["cdi_clear"])) { try { if(CdiUtil::isCdiMaster()) { CdiMaster::uninstall(); CdiMaster::install(); } else if (CdiUtil::isCdiSlave()) { CdiSlave::uninstall(); CdiSlave::install(); } else if(CdiUtil::isCdiDBSync()) { CdiDBSync::uninstall(); CdiDBSync::install(); }
<?php // We should only allow download of the database from the administration interface when the extension is enabled. if((!class_exists('Administration')) || !Administration::instance()->isLoggedIn() || !CdiUtil::isEnabled()) { throw new Exception("You can only download CDI content from the Preferences page"); } require_once(EXTENSIONS . '/cdi/lib/class.cdiutil.php'); $filename = $_REQUEST["ref"]; if($filename == CDI_FILENAME || $filename == CDI_DB_SYNC_FILENAME) { $file = CDIROOT . '/' . $filename; } else { $file = CDI_BACKUP_ROOT . '/' . $filename; } if(file_exists($file)) { $data = file_get_contents($file); header("Pragma: public"); header("Expires: 0"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Content-Type: application/octet-stream"); header("Content-Transfer-Encoding: binary"); header("Content-Disposition: attachment; filename=" . $filename); echo $data; die(); } else { throw new Exception("The provided backup file '" . $file . "' could not be found.");
<?php require_once(EXTENSIONS . '/cdi/lib/class.cdiutil.php'); require_once(EXTENSIONS . '/cdi/lib/class.cdislave.php'); // We should not be processing any queries when the extension is disabled or when we are the Master instance if((!class_exists('Administration')) || !CdiUtil::isEnabled() || (CdiUtil::isCdiMaster() || CdiUtil::isCdiDBSync())) { echo "WARNING: You are not calling this page from Symphony, the CDI extension is disabled or you are running the queryies on the Master instance. No queries have been executed."; } else { $callback = Administration::getPageCallback(); if(Symphony::Configuration()->get('api_key','cdi') !== $callback['context'][0]){ echo "WARNING: Invalid API key. The correct key can be found in the configuration page."; die(); } else{ CdiSlave::update(); } } die();
/** * The executeQueries() function will try to execute all SQL statements that are available in the manifest folder. * It will check the CDI log to see if the statement has already been executed based on the MD5 hash. * This function can only be called by SLAVE instances */ public static function update() { // We should not be processing any queries when the extension is disabled or when we are the Master instance // Check also exists on content page, but just to be sure! if((!class_exists('Administration')) || !CdiUtil::isEnabled() || !CdiUtil::isCdiSlave()) { echo "WARNING: CDI is disabled or you are running the queryies on the Master instance. No queries have been executed."; return; } // Prevent the CdiLogQuery::log() from persisting queries that are executed by CDI itself // Technically this should not be possible because it will not log queries on a SLAVE instance // and you can only run the executeQueries when in SLAVE mode. This is just to be sure. CdiLogQuery::isUpdating(true); // Switch to maintenance mode if(Symphony::Configuration()->get('maintenance-enabled', 'cdi') == 'yes') { Symphony::Configuration()->set('enabled', 'yes', 'maintenance_mode'); Symphony::Configuration()->write(); } // Implement automatic backup before processing structural changes if(Symphony::Configuration()->get('backup-enabled', 'cdi') == 'yes') { try { if(CdiUtil::hasDumpDBInstalled()) { require_once(EXTENSIONS . '/cdi/lib/class.cdidumpdb.php'); $currentBackup = CdiDumpDB::backup("automatic"); } else { throw new Exception('You can only enable automatic backup files when the "Dump DB" extension (version 1.10 or higher) is installed and enabled.'); } }catch (Exception $e) { echo "ERROR: " . $e->getMessage() , ". Failed to backup database before update, aborting."; die(); } } try { $skipped = 0; $executed = 0; $entries = CdiLogQuery::getCdiLogEntries(); foreach($entries as $entry) { $ts = $entry[0]; $order = $entry[1]; $hash = $entry[2]; $query = $entry[3]; $date = date('Y-m-d H:i:s', $ts); // Replace the table prefix in the query // Rename the generic table prefix to the prefix used by this instance $tbl_prefix = Symphony::Configuration()->get('tbl_prefix', 'database'); $query = str_replace('tbl_', $tbl_prefix, $query); try { // Look for available CDI log entries based on the provided MD5 hash $cdiLogEntry = Symphony::Database()->fetchRow(0,"SELECT * FROM tbl_cdi_log WHERE `query_hash` LIKE '" . $hash . "'"); if(empty($cdiLogEntry)) { // The query has not been found in the log, thus it has not been executed // So let's execute the query and add it to the log! Symphony::Database()->query("INSERT INTO `tbl_cdi_log` (`query_hash`,`author`,`url`,`date`,`order`) VALUES ('" . $hash . "','" . CdiUtil::getAuthor() . "','" . CdiUtil::getURL() . "','" . $date . "'," . $order . ")"); Symphony::Database()->query($query); $executed++; } else { // The query has already been executed, let's do nothing; $skipped++; } } catch (Exception $e) { //TODO: think of some smart way of dealing with errors, perhaps through the preference screen or a CDI Status content page? //Due to the error we need to perform a rollback to allow this query to be executed at a later stage. CdiLogQuery::rollback($hash,$ts,$order); // Implement automatic restore on error if((Symphony::Configuration()->get('backup-enabled', 'cdi') == 'yes') && (Symphony::Configuration()->get('restore-enabled', 'cdi') == 'yes')) { try { CdiDumpDB::restore($currentBackup); }catch (Exception $r) { echo "ERROR: " . $r->getMessage() , ". Failed to restore latest database backup. This instance needs immediate attention!"; die(); } // Restore was succesful, at least we can jump out of maintenance mode if(Symphony::Configuration()->get('maintenance-enabled', 'cdi') == 'yes') { Symphony::Configuration()->set('enabled', 'no', 'maintenance_mode'); Symphony::Configuration()->write(); } echo "ERROR: " . $e->getMessage() , ". Rollback & Restore have been executed."; } else { if(Symphony::Configuration()->get('maintenance-enabled', 'cdi') == 'yes') { echo "ERROR: " . $e->getMessage() , ". Rollback has been executed. This instance is now in maintenance mode and needs immediate attention!"; } else { echo "ERROR: " . $e->getMessage() , ". Rollback has been executed."; } } // Stop processing die(); } } echo "OK: " . $executed . " queries executed, " . $skipped . " skipped."; } catch (Exception $e) { //TODO: think of some smart way of dealing with errors, perhaps through the preference screen or a CDI Status content page? echo "ERROR: " . $e->getMessage(); } // Save the last update date to configuration Symphony::Configuration()->set('last-update', time(), 'cdi'); // No more maintenance mode if(Symphony::Configuration()->get('maintenance-enabled', 'cdi') == 'yes') { Symphony::Configuration()->set('enabled', 'no', 'maintenance_mode'); } Symphony::Configuration()->write(); // Re-enable CdiLogQuery::log() to persist queries CdiLogQuery::isUpdating(false); }