add('name', PLUGIN_EVENT_AUTOUPDATE_NAME); $propbag->add('description', PLUGIN_EVENT_AUTOUPDATE_DESC); $propbag->add('stackable', false); $propbag->add('author', 'onli, Ian'); $propbag->add('version', '1.2.1'); $propbag->add('requirements', array( 'serendipity' => '0.8', 'php' => '5.2' )); $propbag->add( 'event_hooks', array( 'plugin_dashboard_updater' => true, 'backend_sidebar_entries_event_display_update' => true, 'backend_sidebar_entries_event_display_update_no_integrity_checks' => true, ) ); $propbag->add('configuration', array( 'disable_integrity_checks', )); $propbag->add('groups', array('BACKEND_FEATURES')); if ($serendipity['version'][0] < 2) { $this->dependencies = array('serendipity_event_dashboard' => 'keep'); } } function generate_content(&$title) { $title = $this->title; } function install() { global $serendipity; if (!$serendipity['serendipityUserlevel'] >= USERLEVEL_ADMIN) { return false; } } /** * @param string $name * @param serendipity_property_bag $propbag * @return bool */ function introspect_config_item($name, &$propbag) { switch ($name) { case 'disable_integrity_checks': $propbag->add('type', 'boolean'); $propbag->add('name', PLUGIN_EVENT_AUTOUPDATE_DISABLE_INTEGRITY_CHECKS); $propbag->add('description', PLUGIN_EVENT_AUTOUPDATE_DISABLE_INTEGRITY_CHECKS_DESC); $propbag->add('default', false); break; default: return false; } return true; } /** * flush progress or error messages */ function show_message($message='', $pname='', $next='') { if (!empty($pname)) { // Total processes $total = 3; ob_implicit_flush(1); // fake processing loop for ($i=1; $i<=$total; $i++) { // Calculate the percentation $percent = intval($i/$total * 100)."%"; // Javascript for updating the progress bar and information echo ' '; //this is for the buffer achieve the minimum size in order to flush data echo str_repeat(' ',1024*64); // need to keep here since this also flushes the progress bar on fastCGI // Send output to browser immediately ob_flush(); flush(); // Sleep one second so we can see the delay sleep(1); } $wait = strstr($pname, 'Function') ? ' Please wait ... Processing: '.$next.' ...' : ''; // no tags allowed here // Tell user that the process is completed echo ''; } echo "$message\n"; $levels = ob_get_level(); for ($i=0; $i<$levels; $i++) { ob_end_flush(); } ob_flush(); flush(); } function event_hook($event, &$bag, &$eventData, $addData = null) { global $serendipity; $hooks = &$bag->get('event_hooks'); if (isset($hooks[$event])) { switch($event) { case 'plugin_dashboard_updater': $eventData = '
' . ($serendipity['version'][0] > 1 ? '' : '') . '
'; return true; break; case 'backend_sidebar_entries_event_display_update_no_integrity_checks': $this->set_config('disable_integrity_checks', true); // `break` statement intentionally omitted! case 'backend_sidebar_entries_event_display_update': if (!(serendipity_checkPermission('siteConfiguration') || serendipity_checkPermission('blogConfiguration'))) { return; } if (!extension_loaded('zip')) { trigger_error(' ZIP extension has not been compiled or loaded in php.', E_USER_WARNING); return; } @ini_set('max_execution_time', 210); // 180 + (21+9 gui happenings) #@ini_set('memory_limit', '-1'); // extending memory_limit may be prevented by suhosin extension. /* As long scripts are not running within safe_mode they are free to change the memory_limit to whatever value they want. Suhosin changes this fact and disallows setting the memory_limit to a value greater than the one the script started with, when this option is left at 0. A value greater than 0 means that Suhosin will disallows scripts setting the memory_limit to a value above this configured hard limit. This is for example usefull if you want to run the script normaly with a limit of 16M but image processing scripts may raise it to 20M. Edit /etc/php5/conf.d/suhosin.ini and add e.g. suhosin.memory_limit = 512M ... */ $self_info = sprintf(USER_SELF_INFO, (function_exists('serendipity_specialchars') ? serendipity_specialchars($serendipity['serendipityUser']) : htmlspecialchars($serendipity['serendipityUser'], ENT_COMPAT, LANG_CHARSET)), $serendipity['permissionLevels'][$serendipity['serendipityUserlevel']]); $lang_char = LANG_CHARSET; $ad_suite = SERENDIPITY_ADMIN_SUITE; $css_upd = file_get_contents(dirname(__FILE__) . '/upgrade.min.css'); $nv = (function_exists('serendipity_specialchars') ? serendipity_specialchars($_REQUEST['serendipity']['newVersion']) : htmlspecialchars($_REQUEST['serendipity']['newVersion'], ENT_COMPAT, LANG_CHARSET)); // reduce to POST only? $logmsg = ''; echo << Startpage - {$ad_suite}

Serendipity Auto-Upgrade Processor

EOS; $this->show_message('

Download, verify, check, unzip, copy, remove temporary stuff for Serendipity Update: ' . $_REQUEST['serendipity']['newVersion'] . ' may take a little while...


'); $this->show_message('

PHP max execution time set to 210 seconds

'); if (false === ($update = $this->fetchUpdate($nv))) { $this->close_page(true); } serendipity_plugin_api::hook_event('backend_plugins_upgradecount', $pluginUpdates); if (empty($pluginUpdates)) { if (!empty($update)) { if ($this->verifyUpdate($update, $nv)) { if ($this->checkWritePermissions($update)) { $unpacked = $this->unpackUpdate($nv); if ($unpacked) { $disableIntegrityChecks = $this->get_config('disable_integrity_checks', false); if ($disableIntegrityChecks !== false || $this->checkIntegrity($nv)) { if ($disableIntegrityChecks !== false) { $this->set_config('disable_integrity_checks', false); //reset config $this->show_message("fcn checkIntegrity() skipped...\nReset 'disable_integrity_checks' to false\n"); } $copied = $this->copyUpdate($nv); if ($copied) { if (true === $this->cleanTemplatesC($nv, true) ) { $this->show_message('

Cleanup download temp done!

'); } sleep(2); echo ''; sleep(2); $this->show_message('

click to start Serendipity Installer here!

'); sleep(1); $this->doUpdate();//$logmsg } else { $this->show_message('

Copying the files for the update failed

'); } } else { $this->showChecksumErrors($nv); echo sprintf( '
%s
', $nv, $serendipity['version'][0] > 1 ? '' : '' ); } } else { $this->show_message('

Unpacking the update failed

'); if (true === $this->cleanTemplatesC($nv, false)) { $this->show_message('

Cleaning up the failed unpack directory!

'); } $this->show_message('

Please reload this page by F5 to have another try upgrading your Blog successfully!

'); } } else { $this->showNotWriteable($update); echo '
'; } } } } else { $this->show_message('

Please update all your plugins first.

'); } $this->close_page(true); return true; break; default: return false; } } else { return false; } } /** * fetch the zip file from server * @param string $version Version * @return mixed updatepath/bool */ function fetchUpdate($version) { global $serendipity; $url = (string)"https://github.com/s9y/Serendipity/releases/download/$version/serendipity-$version.zip"; if (strpos($version, 'beta') !== FALSE) { $url = (string)"https://github.com/s9y/Serendipity/archive/$version.zip"; } $update = (string)$serendipity ['serendipityPath'] . 'templates_c/' . "serendipity-$version.zip"; // do we already have it and is it eventually broken? if (file_exists($update)) { $zip = new ZipArchive; $res = $zip->open($update); if ($res === TRUE) { $done = true; } else { $this->show_message('

Existing Zip file Error, Code:' . $res. '. The autoupdater will try to download again...'); @unlink($update); sleep(1); $done = @copy($url, $update) ? true : false; sleep(1); } $zip->close(); } else { $done = @copy($url, $update) ? true : false; sleep(1); } if (!$done) { //try it again with curl if copy was forbidden if (function_exists('curl_init')) { $out = @fopen($update, 'wb'); $ch = @curl_init(); @curl_setopt($ch, CURLOPT_FILE, $out); @curl_setopt($ch, CURLOPT_HEADER, 0); @curl_setopt($ch, CURLOPT_URL, $url); $success = @curl_exec($ch); if (!$success) { $this->show_message('

Downloading update failed (curl installed, but failed). Does '. $url .' exist?

'); return false; } } else { $this->show_message('

Downloading update failed (copy failed, curl not available). Does '. $url .' exist?

'); return false; } } $this->show_message('

Fetch download to templates_c done

'); return $update; } /** * compare the md5 of downloaded archive with the md5 posted on the downloadpage * @param string updatePath * @param string version * @return boolean */ function verifyUpdate($update, $version) { $url = (string)"https://github.com/s9y/Serendipity/releases/download/$version/serendipity-$version.zip"; $updatePage = (string)$this->getPage("https://github.com/s9y/Serendipity/releases/tag/$version"); $found = array(); // grep the checksum preg_match("/\(MD5: (.*)\)/", $updatePage, $found); $checksum = $found[1]; $this->show_message('

Checking MD5 zip file checksum: ' . $checksum . '

'); $check = md5_file($update); if (strpos($version, 'beta') !== FALSE) { return true; } if ($check == $checksum) { return true; } else { $this->show_message('

Error. Could not verify the update.

'); return false; } } /** * get file content of updatePage * @param string url * @return page content */ function getPage($url) { $page = file_get_contents($url); if (empty($page)) { //try it again with curl if fopen was forbidden if (function_exists('curl_init')) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, "20"); $page = curl_exec($ch); curl_close($ch); } } return $page; } /** * unpack the update to templates_c * @param string version * @return boolean */ function unpackUpdate($version) { global $serendipity; $update = (string)$serendipity['serendipityPath'] . 'templates_c/' . "serendipity-$version.zip"; $updateDir = (string)$serendipity['serendipityPath'] . 'templates_c/' . "serendipity-$version/"; // do we already have it? if (is_dir($updateDir) && is_file($updateDir . '/serendipity/README.markdown') && is_file($updateDir . '/serendipity/checksums.inc.php')) { return true; } $zip = new ZipArchive; if ($zip->open($update) === true) { // 1.get all filenames apart from the root 'serendipity' $i=1; $files = array(); $name = $zip->getNameIndex($i); while (!empty($name)) { $files[] = $name; $name = $zip->getNameIndex($i); $i+=1; } // 2.extraxt all files to temp $zip->extractTo($updateDir); $this->show_message('

Extracting the zip in templates_c done

'); $zip->close(); } else { return false; } return true; } /** * copy the update from templates_c over the existing files * @param string version * @return boolean */ function copyUpdate($version) { global $serendipity; $update = (string)$serendipity['serendipityPath'] . 'templates_c/' . "serendipity-$version.zip"; $updateDir = (string)$serendipity['serendipityPath'] . 'templates_c/' . "serendipity-$version/"; $zip = new ZipArchive; if ($zip->open($update) === true) { // 1.get all filenames apart from the root 'serendipity' $i=1; $files = array(); $name = $zip->getNameIndex($i); while ( !empty($name) ) { $files[] = $name; $name = $zip->getNameIndex($i); $i+=1; } $zip->close(); // 2. copy them over foreach ($files as $file) { $target = $serendipity['serendipityPath'] . preg_replace('/[^\/]*/', '', $file, 1); # removes leading Serendipity[beta…] if (is_dir($updateDir .$file)) { if (!file_exists($target)) { $success = mkdir($target); } else { $success = true; } } else { $success = @copy($updateDir . $file, $target); } if (!$success) { $this->show_message('

Error copying file '. $updateDir . $file .' to '. $target .'

'); return false; } } } else { return false; } return true; } /** * check write permissions * @param string updatePath * @return boolean */ function checkWritePermissions($update) { global $serendipity; $zip = new ZipArchive; if ($zip->open($update) === true) { $i=0; $files = array(); $name = $zip->getNameIndex($i); while (!empty($name)) { $files[] = $name; $name = $zip->getNameIndex($i); $i+=1; } $zip->close(); foreach ($files as $file) { $target = $serendipity['serendipityPath'] . substr($file, 12); if ((!is_writable($target)) && file_exists($target)) { return false; } } } return true; } /** * show not writable * @param string updatePath * @return error */ function showNotWriteable($update) { global $serendipity; $zip = new ZipArchive; if ($zip->open($update) === true) { $i=0; $files = array(); $name = $zip->getNameIndex($i); while (!empty($name)) { $files[] = $name; $name = $zip->getNameIndex($i); $i+=1; } $zip->close(); $notWritable = array(); foreach ($files as $file) { $target = $serendipity['serendipityPath'] . substr($file, 12); if ((!is_writable($target)) && file_exists($target)) { $notWriteable[] = $target; } } } ob_start(); echo '

Unpacking the update failed, because following files were not writeable:

'; echo "
    "; foreach ($notWriteable as $file) { echo "
  • $file
  • "; } echo "
"; $write_error = ob_get_contents(); ob_end_clean(); $this->show_message($write_error); } /** * checks updates checksum file array with updates realfiles * @param string version * @return boolean */ function checkIntegrity($version) { global $serendipity; if (strpos($version, 'beta') !== false) { return true; } $updateDir = $serendipity['serendipityPath'] . 'templates_c/' . "serendipity-$version/"; $checksumFile = $updateDir .'serendipity/checksums.inc.php'; include_once $checksumFile; $checksums = $serendipity['checksums_' . $version]; foreach ($checksums as $file => $checksum) { $path = $updateDir.'serendipity/'.$file; if ($checksum !== serendipity_FTPChecksum($path) && $checksum !== serendipity_FTPChecksum($path, 'text')) { return false; } } return true; } /** * shows error message and list of files with wrong checksum * @param string version * @return void */ function showChecksumErrors($version) { global $serendipity; $updateDir = $serendipity['serendipityPath'] . 'templates_c/' . "serendipity-$version/"; $checksumFile = $updateDir .'serendipity/checksums.inc.php'; include_once $checksumFile; $checksums = $serendipity['checksums_' . $version]; $errors = array(); foreach ($checksums as $file => $checksum) { $path = $updateDir.'serendipity/'.$file; if ($checksum !== serendipity_FTPChecksum($path) && $checksum !== serendipity_FTPChecksum($path, 'text')) { $errors[] = $path; } } ob_start(); echo '

'.PLUGIN_EVENT_AUTOUPDATE_ERROR_INTEGRITY_CHECKS.'

'; echo '
    '; foreach ($errors as $file) { echo "
  • $file
  • "; } echo '
'; $integrity_error = ob_get_clean(); $this->show_message($integrity_error); } /** * close the autoupdate progress page * @param bool 007 title ;-) */ function close_page($terminate = false) { echo <<
EOS; if ($terminate) { echo << EOS; } if ($terminate) die(); } /** * visit the rootpage to manually start the update installer process * @param string update messages * @return refresh page */ function doUpdate() { global $serendipity; $msg = "Autoupdate successfully done!\\nWe now refresh to Serendipity Installer!\\n"; // escape for js $this->show_message('

Autoupdate successfully done - refresh to Serendipity Installer

', 'Autoupdate'); } /** * empty a directory using the Standard PHP Library (SPL) iterator * @param string directory */ function empty_dir($dir) { if (!is_dir($dir)) return; try { $_dir = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); } catch (Exception $e) { return; } $iterator = new RecursiveIteratorIterator($_dir, RecursiveIteratorIterator::CHILD_FIRST); foreach ($iterator as $file) { if ($file->isFile()) { unlink($file->__toString()); } else { @rmdir($file->__toString()); } } } /** * delete all cache-files in cache templates_c to prevent display-errors after update * @param string version * @return boolean */ function cleanTemplatesC($version, $finish) { global $serendipity; $zip = (string)$serendipity['serendipityPath'] . 'templates_c/' . "serendipity-$version.zip"; $zipDir = (string)$serendipity['serendipityPath'] . 'templates_c/' . "serendipity-$version"; // As trying to remove a directory that php is still using, we use open/closedir($handle) to be sure if ($handle = opendir($zipDir)) { $this->empty_dir($zipDir); $this->show_message('

Removing all files in '.$zipDir.' done

'); closedir($handle); } if (rmdir($zipDir)) { $this->show_message('

Removing the empty directory: '.$zipDir.' done

'); } else { $this->show_message('

Removing the empty directory: '.$zipDir.' failed!

'); } // We clear all compiles smarty template files in templates_c which only leaves the page we are on: /serendipity/templates/default/admin/index.tpl if ($finish) { // We have to reduce this call() = all tpl files, to clear the blogs template only, to not have the following automated recompile, force the servers memory // to get exhausted, when using serendipity_event_gravatar plugin, which can eat up some MB... if (method_exists($serendipity['smarty'], 'clearCompiledTemplate')) { // SMARTY 3 if ($serendipity['smarty']->clearCompiledTemplate(null, $serendipity['template'])) { return true; } } if (method_exists($serendipity['smarty'], 'clear_compiled_tpl')) { // SMARTY 2 if ($serendipity['smarty']->clear_compiled_tpl(null, $serendipity['template'])) { return true; } } } } /** * debug * @param string msg */ function debugMsg($msg) { global $serendipity; $this->debug_fp = @fopen ( $serendipity ['serendipityPath'] . 'templates_c/autoupdate.log', 'a' ); if (!$this->debug_fp) { return false; } if (empty($msg)) { fwrite ( $this->debug_fp, "failure \n" ); } else { fwrite ( $this->debug_fp, print_r ( $msg, true ) ); } fclose ( $this->debug_fp ); } } /* vim: set sts=4 ts=4 expandtab : */