Пример #1
0
		/**
		 * 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);
		}
Пример #2
0
		/**
		 * The rollback() function removes the query either from file (MASTER) or from the CDI log (SLAVE)
		 * @param String $hash The MD5 hash created using the SQL statement, the timestamp and the execution order
		 * @param Timestamp $timestamp The UNIX timestamp on which the query was originally logged
		 * @param Integer $order The execution order of the query (in case of multiple executions with the same timestamp)
		 */
		public static function rollback($hash,$timestamp,$order) {
			// do not rollback erronous changes to tbl_cdi_log
			$tbl_prefix = Symphony::Configuration()->get('tbl_prefix', 'database');
			if (preg_match("/{$tbl_prefix}cdi_log/i", $query)) return true;
			
			try {
				if(CdiUtil::isCdiSlave()) {
					// On the SLAVE instance we need to remove the execution log entry from the database
					Symphony::Database()->query("DELETE FROM `tbl_cdi_log` WHERE `query_hash` LIKE '" . $hash . "'");
				} else if(CdiUtil::isCdiMaster()) {
					// On the MASTER instance we need to remove the persisted SQL Statement from disk (if it exists)
					$entries = self::getCdiLogEntries();
					unset($entries[$hash]);
					file_put_contents(CDI_FILE, json_encode($entries));
				} else {
					throw new Exception("Invalid value for the CDI 'mode' configuration option, 'CdiMaster' or 'CdiSlave' expected.");
				}
			} catch(Exception $e) {
				//TODO: think of some smart way of dealing with errors, perhaps through the preference screen or a CDI Status content page?
				//In this case it is perhaps better to simply throw the exception because the rollback failed.
				throw $e;
			}
		}
Пример #3
0
		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);
			}
		}
Пример #4
0
	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();
			}
			$result["status"] = 'success';
		} catch(Exception $e) {
			$result["status"] = "error";
			$result["message"] = $e->getMessage();
			Symphony::Log()->pushToLog('[CDI] ' . $e->getMessage(), E_ERROR, true);
		}
	}

	// Clean the database backup list when cdi_clear_restore action is called
	else if(isset($_POST["action"]["cdi_clear_restore"])) {
		try {
			CdiDumpDB::uninstall();
Пример #5
0
		public function savePreferences($context){
			if(CdiPreferences::save()) {
				// apply config changes
				if(CdiUtil::isCdiSlave()) { CdiSlave::install(); } 
				else if (CdiUtil::isCdiMaster()) { CdiMaster::install(); }
				else { CdiDBSync::install(); }
			} else {
				Administration::instance()->Page->pageAlert(_('An unknown error occurred while saving preferences for CDI. Your changes have not been saved.'));
				return false;
			}
		}
Пример #6
0
<?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.");
Пример #7
0
		public static function appendClearLog() {
			$div = new XMLElement('div',NULL,array('class' => 'cdiClear'));
			$div->appendChild(new XMLElement('h3','Clear Log Entries',array('style' => 'margin-bottom: 5px;')));
			$button = new XMLElement('div',NULL,array('style' => 'margin: 10px 0;'));
			$button->appendChild(new XMLElement('input', null, array('value' => 'Clear', 'name' => 'action[cdi_clear]', 'type' => 'button', 'class' => 'cdi_clear_action')));
			if(CdiUtil::isCdi()) {
				$button->appendChild(new XMLElement('span','&nbsp;Press "Clear" to remove all CDI log entries from disk and/or Symphony Database'));
				$div->appendChild($button);
				$div->appendChild(new XMLElement('p', 'You can use the "Clear" button to clean up old CDI logs. 
														 Ensure that all your Symphony have been updated either by CDI (check the last executed queries list above) or by manually restoring the same database backup on all instances.
														 Make sure that you clear the log files on every instance (including the "Master" instance). It is important that the database schemas are synchronized before starting with a clean sheet.', array('class' => 'help')));
			} else {
				$button->appendChild(new XMLElement('span','&nbsp;Press "Clear" to remove <em>' . CDI_DB_SYNC_FILENAME . '</em> from disk'));
				$div->appendChild($button);
				$div->appendChild(new XMLElement('p', 'You can use the "Clear" button to remove current <em>' . CDI_DB_SYNC_FILENAME . '</em> file. 
													   Ensure that all your Symphony have been updated either by CDI or by manually restoring the same database backup on all instances.
													   Make sure that you clear the <em>' . CDI_DB_SYNC_FILENAME . '</em> files on every instance (including the "Master" instance). It is important that the database schemas are synchronized before starting with a clean sheet.', array('class' => 'help')));
			}
			return $div;
		}		
Пример #8
0
<?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();
Пример #9
0
		/**
		 * 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);
		}