} CdiDumpDB::backup('manual'); $result["status"] = 'success'; $result["result"] = htmlspecialchars(CdiPreferences::appendRestore()->generate()); } catch(Exception $e) { $result["status"] = "error"; $result["message"] = $e->getMessage(); Symphony::Log()->pushToLog('[CDI] ' . $e->getMessage(), E_ERROR, true); } } // CDI Restore else if(isset($_POST["action"]["cdi_restore"])) { try { CdiDumpDB::restore(); $result["status"] = 'success'; } catch(Exception $e) { $result["status"] = "error"; $result["message"] = $e->getMessage(); Symphony::Log()->pushToLog('[CDI] ' . $e->getMessage(), E_NOTICE, true); } } // No action? Error! else { $result["status"] = "error"; $result["message"] = "You can only execute actions if you actually post one!"; Symphony::Log()->pushToLog('[CDI] You can only execute actions if you actually post one!', E_NOTICE, true); }
public static function appendRestore() { $div = new XMLElement('div', NULL,array('style'=>'margin-bottom: 1.5em;','class' => 'cdiRestore')); if(CdiUtil::hasDumpDBInstalled()) { $div->appendChild(new XMLElement('h3','Restore Symphony database',array('style' => 'margin: 5px 0;'))); $table = new XMLElement('table', NULL, array('cellpadding' => '0', 'cellspacing' => '0', 'border' => '0', 'style' => 'margin-bottom: 10px;')); $files = CdiDumpDB::getBackupFiles(); if(count($files) > 0) { rsort($files); foreach($files as $file) { $filename = explode('-',$file); if($entryCount == 5) { break; } $tr = new XMLElement('tr',null); $linkbutton = new XMLElement('a',date('d-m-Y H:i:s', (int)$filename[0]),array('href' => URL . '/symphony/extension/cdi/download/?ref=' . $file)); $tr->appendChild(new XMLElement('td',$linkbutton->generate(),array('width' => '150', 'style' => 'vertical-align:middle;'))); $tr->appendChild(new XMLElement('td',$filename[1],array('style' => 'vertical-align:middle;'))); $td = new XMLElement('td',null,array('width' => '75')); $button = new XMLElement('input',null, array('value' => 'Restore', 'name' => 'action[cdi_restore]', 'type' => 'button', 'class' => 'cdi_restore_action', 'ref' => $file)); $td->appendChild($button); $tr->appendChild($td); $table->appendChild($tr); $entryCount++; } } $tr = new XMLElement('tr',null,array('class' => 'cdiNoLastBackupCell')); $tr->appendChild(new XMLElement('td','There is no recent Symphony database to restore')); if($entryCount != 0) { $tr->setAttribute('style','display: none'); } $table->appendChild($tr); $div->appendChild($table); $uploadContainer = new XMLElement('div',null,array('class' => 'cdiRestoreUpload')); if($entryCount != 0) { $uploadContainer->setAttribute('style','display: none'); } $span = new XMLElement('span',NULL,array('class' => 'frame')); $span->appendChild(new XMLElement('input',NULL,array('name' => 'dumpdb_restore_file', 'type' => 'file'))); $uploadContainer->appendChild($span); $button = new XMLElement('div',NULL,array('style' => 'margin: 10px 0;')); $button->appendChild(new XMLElement('input',null,array('value' => 'Upload', 'name' => 'action[dumpdb_restore]', 'type' => 'submit', 'class' => 'cdi_import_action'))); $button->appendChild(new XMLElement('span',' Press "Upload" to restore the Symphony Database.')); $uploadContainer->appendChild($button); $div->appendChild($uploadContainer); if($entryCount != 0) { $button = new XMLElement('div',NULL,array('style' => 'margin: 0 0 10px 10px;')); $button->appendChild(new XMLElement('input', null, array('value' => 'Clear', 'name' => 'action[cdi_clear_restore]', 'type' => 'button', 'class' => 'cdi_clear_restore_action'))); $button->appendChild(new XMLElement('span',' Press "Clear" to remove all Symphony database backups')); $div->appendChild($button); } $div->appendChild(new XMLElement('p', 'Restoring a backup of your Symphony database will replace the entire structure and data of this instance. You can use this to synchronize instances, but be carefull to prevent data loss.', array('class' => 'help'))); } return $div; }
public function appendPreferences($context){ // Import the db_sync.sql file when the cdi_import action is called // The import action is the only left to require a post-back because AJAX file upload is cumbersome if(isset($_POST["action"]["cdi_import"])) { CdiDBSync::import(); } else if(isset($_POST["action"]["dumpdb_restore"])) { CdiDumpDB::restore(); } // Create the Preferences user-interface for the CDI extension $group = new XMLElement('fieldset'); $group->setAttribute('class', 'cdi settings'); $group->appendChild(new XMLElement('legend', 'Continuous Database Integration')); $group->appendChild(new XMLElement('div', '<span class="image"> </span><span>Processing... please wait.</span>', array('class' => 'help cdiLoading cdiHidden'))); Administration::instance()->Page->Form->setAttribute('enctype', 'multipart/form-data'); $group->appendChild(CdiPreferences::appendCdiMode()); if(CdiUtil::isCdi()) { $group->appendChild(CdiPreferences::appendCdiPreferences()); } else if(CdiUtil::isCdiDBSync()) { $group->appendChild(CdiPreferences::appendDBSyncPreferences()); } // Append preferences $context['wrapper']->appendChild($group); }
/** * 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); }