rpicms

A CMS for the Raspberry Pi
git clone git://archive.git.mtrnord.blog/RpicmsTeam/rpicms.git
Log | Files | Refs | README | LICENSE

commit 5908871299a903a1c76f24dad5257775e945bec4
parent b42b52b2802f6b58e2e1de64f1404790b3965cec
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sun, 22 Feb 2015 19:05:20 +0100

update UPdater for tests

Diffstat:
Mcore/api/v1/api.php | 12++++++------
Acore/new_updater/LICENSE.md | 18++++++++++++++++++
Acore/new_updater/index.php | 10++++++++++
Acore/new_updater/update/index.php | 47+++++++++++++++++++++++++++++++++++++++++++++++
Acore/new_updater/update/update.php | 781+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 862 insertions(+), 6 deletions(-)

diff --git a/core/api/v1/api.php b/core/api/v1/api.php @@ -176,10 +176,10 @@ $app->get('/tasks', 'authenticate', function() { /** * Listing single task of particual user * method GET - * url /tasks/:id + * url /posts/:id * Will return 404 if the task doesn't belongs to user */ -$app->get('/tasks/:id', 'authenticate', function($task_id) { +$app->get('/posts/:id', 'authenticate', function($task_id) { global $user_id; $response = array(); $db = new DbHandler(); @@ -205,9 +205,9 @@ $app->get('/tasks/:id', 'authenticate', function($task_id) { * Creating new task in db * method POST * params - name - * url - /tasks/ + * url - /post/ */ -$app->post('/tasks', 'authenticate', function() use ($app) { +$app->post('/post', 'authenticate', function() use ($app) { // check for required params verifyRequiredParams(array('task')); @@ -238,7 +238,7 @@ $app->post('/tasks', 'authenticate', function() use ($app) { * params task, status * url - /tasks/:id */ -$app->put('/tasks/:id', 'authenticate', function($task_id) use($app) { +$app->put('/posts/:id', 'authenticate', function($task_id) use($app) { // check for required params verifyRequiredParams(array('task', 'status')); @@ -268,7 +268,7 @@ $app->put('/tasks/:id', 'authenticate', function($task_id) use($app) { * method DELETE * url /tasks */ -$app->delete('/tasks/:id', 'authenticate', function($task_id) use($app) { +$app->delete('/posts/:id', 'authenticate', function($task_id) use($app) { global $user_id; $db = new DbHandler(); diff --git a/core/new_updater/LICENSE.md b/core/new_updater/LICENSE.md @@ -0,0 +1,18 @@ +License +== + +PHP AutoUpdate +-- +Copyright 2015 - VisualAppeal GbR - www.visualappeal.de + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +`http://www.apache.org/licenses/LICENSE-2.0` + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/core/new_updater/index.php b/core/new_updater/index.php @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> + <head> + <title>PHP Auto Update</title> + </head> + <body> + <p>This is the test index.</p> + <p><a href="update/index.php">Update now</a>!</p> + </body> +</html> diff --git a/core/new_updater/update/index.php b/core/new_updater/update/index.php @@ -0,0 +1,46 @@ +<?php +####################### +# flush browser cache # +####################### +header("Cache-Control: no-cache, must-revalidate, no-store"); + +require('update.php'); + +use \VisualAppeal\AutoUpdate; + +$update = new AutoUpdate($root . '/temp/', $root . '/', 60); +$update->setCurrentVersion('1.0.0'); +$update->setUpdateUrl('http://media.nordgedanken.de/rpicms/server'); + +// Optional: +$update->addLogHandler(new Monolog\Handler\StreamHandler($root . 'core/update/update.log')); + + +//Check for a new update +if ($update->checkUpdate() === false) + die('Could not check for updates! See log file for details.'); +if ($update->newVersionAvailable()) { + //Install new update + echo 'New Version: ' . $update->getLatestVersion() . '<br>'; + echo 'Installing Updates: <br>'; + echo '<pre>'; + var_dump(array_map(function($version) { + return (string) $version; + }, $update->getVersionsToUpdate())); + echo '</pre>'; + $result = $update->update(); + if ($result === true) { + echo 'Update successful<br>'; + } else { + echo 'Update failed: ' . $result . '!<br>'; + if ($result = AutoUpdate::ERROR_SIMULATE) { + echo '<pre>'; + var_dump($update->getSimulationResults()); + echo '</pre>'; + } + } +} else { + echo 'Current Version is up to date<br>'; +} + +?> +\ No newline at end of file diff --git a/core/new_updater/update/update.php b/core/new_updater/update/update.php @@ -0,0 +1,781 @@ +<?php namespace VisualAppeal; + +use vierbergenlars\SemVer\version; +use vierbergenlars\SemVer\expression; +use vierbergenlars\SemVer\SemVerException; + +use Desarrolla2\Cache\Cache; +use Desarrolla2\Cache\Adapter\NotCache; + +use Monolog\Logger; +use Monolog\Handler\NullHandler; + + +############################### +# include files from root dir # +############################### +$root_1 = realpath($_SERVER["DOCUMENT_ROOT"]); +$currentdir = getcwd(); +$root_2 = str_replace($root_1, '', $currentdir); +$root_3 = explode("/", $root_2); +if ($root_3[1] == 'core') { + echo $root_3[1]; + $root = realpath($_SERVER["DOCUMENT_ROOT"]); +}else{ + $root = $root_1 . '/' . $root_3[1]; +} + + +/** + * Auto update class. + */ +class AutoUpdate +{ + /** + * The latest version. + * + * @var vierbergenlars\SemVer\version + */ + private $_latestVersion = null; + + /** + * Updates not yet installed. + * + * @var array + */ + private $_updates = null; + + /** + * Cache for update requests. + * + * @var Desarrolla2\Cache\Cache + */ + private $_cache = null; + + /** + * Result of simulated install. + * + * @var array + */ + private $_simulationResults = array(); + + /** + * Url to the update folder on the server. + * + * @var string + */ + protected $_updateUrl = 'http://media.nordgedanken.de/rpicms/update/server/'; + + /** + * Version filename on the server. + * + * @var string + */ + protected $_updateFile = 'update.json.$this->branch'; + + /* + * branch on the server + */ + public $branch = 'stable'; + + /** + * Current version. + * + * @var vierbergenlars\SemVer\version + */ + protected $_currentVersion = null; + + /** + * Temporary download directory. + * + * @var string + */ + private $_tempDir = ''; + + /** + * Install directory. + * + * @var string + */ + private $_installDir = ''; + + /** + * Create new folders with this privileges. + * + * @var int + */ + public $dirPermissions = 0755; + + /** + * Update script filename. + * + * @var string + */ + public $updateScriptName = '_upgrade.php'; + + /** + * No update available. + */ + const NO_UPDATE_AVAILABLE = 0; + + /** + * Zip file could not be opened. + */ + const ERROR_INVALID_ZIP = 10; + + /** + * Could not check for last version. + */ + const ERROR_VERSION_CHECK = 20; + + /** + * Temp directory does not exist or is not writable. + */ + const ERROR_TEMP_DIR = 30; + + /** + * Install directory does not exist or is not writable. + */ + const ERROR_INSTALL_DIR = 35; + + /** + * Could not download update. + */ + const ERROR_DOWNLOAD_UPDATE = 40; + + /** + * Could not delete zip update file. + */ + const ERROR_DELETE_TEMP_UPDATE = 50; + + /** + * Error while installing the update. + */ + const ERROR_INSTALL = 60; + + /** + * Error in simulated install. + */ + const ERROR_SIMULATE = 70; + + /** + * Create new instance + * + * @param string $tempDir + * @param string $installDir + * @param int $maxExecutionTime + */ + public function __construct($tempDir = null, $installDir = null, $maxExecutionTime = 60) { + $this->setTempDir(($tempDir !== null) ? $tempDir : __DIR__ . '/temp/'); + $this->setInstallDir(($installDir !== null) ? $installDir : __DIR__ . '/../../'); + + $this->_latestVersion = new version('0.0.0'); + $this->_currentVersion = new version('0.0.0'); + + // Init cache + $this->_cache = new Cache(new NotCache()); + + // Init logger + $this->_log = new Logger('auto-update'); + $this->_log->pushHandler(new NullHandler()); + + ini_set('max_execution_time', $maxExecutionTime); + } + + /** + * Set the temporary download directory. + * + * @param string $dir + */ + public function setTempDir($dir) + { + // Add slash at the end of the path + if (substr($dir, -1 != '/')) + $dir = $dir . '/'; + + if (!is_dir($dir)) { + if (!mkdir($dir, 0755, true)) { + $this->_log->addCritical(sprintf('Could not create temporary directory "%s"', $dir)); + return; + } + } + + $this->_tempDir = $dir; + } + + /** + * Set the install directory. + * + * @param string $dir + */ + public function setInstallDir($dir) + { + // Add slash at the end of the path + if (substr($dir, -1 != '/')) + $dir = $dir . '/'; + + if (!is_dir($dir)) { + if (!mkdir($dir, 0755, true)) { + $this->_log->addCritical(sprintf('Could not create temporary directory "%s"', $dir)); + return; + } + } + + $this->_installDir = $dir; + } + + /** + * Set the update filename. + * + * @param string $updateFile + */ + public function setUpdateFile($updateFile) + { + $this->_updateFile = $updateFile; + } + + /** + * Set the update filename. + * + * @param string $updateUrl + */ + public function setUpdateUrl($updateUrl) + { + $this->_updateUrl = $updateUrl; + } + + /** + * Set the cache component. + * + * @param Desarrolla2\Cache\Adapter\AdapterInterface $adapter See https://github.com/desarrolla2/Cache + * @param int $ttl Time to live in seconds + */ + public function setCache($adapter, $ttl = 3600) + { + $adapter->setOption('ttl', $ttl); + $this->_cache = new Cache($adapter); + } + + /** + * Set the version of the current installed software. + * + * @param string $currentVersion + * + * @return bool + */ + public function setCurrentVersion($currentVersion) + { + $version = new version($currentVersion); + if ($version->valid() === null) { + $this->_log->addError(sprintf('Invalid current version "%s"', $currentVersion)); + return false; + } + + $this->_currentVersion = $version; + return true; + } + + /** + * Add a new logging handler. + * + * @param Monolog\Handler\HandlerInterface $handler See https://github.com/Seldaek/monolog + */ + public function addLogHandler(\Monolog\Handler\HandlerInterface $handler) + { + $this->_log->pushHandler($handler); + } + + /** + * Get the name of the latest version. + * + * @return vierbergenlars\SemVer\version + */ + public function getLatestVersion() + { + return $this->_latestVersion; + } + + /** + * Get an array of versions which will be installed. + * + * @return array + */ + public function getVersionsToUpdate() + { + return array_map(function($update) { + return $update['version']; + }, $this->_updates); + } + + /** + * Get the results of the last simulation. + * + * @return array + */ + public function getSimulationResults() + { + return $this->_simulationResults; + } + + /** + * Remove directory recursively. + * + * @param string $dir + * + * @return void + */ + private function _removeDir($dir) { + $this->_log->addDebug(sprintf('Remove directory "%s"', $dir)); + + if (!is_dir($dir)) { + $this->_log->addWarning(sprintf('"%s" is not a directory!', $dir)); + return false; + } + + $objects = array_diff(scandir($dir), array('.', '..')); + foreach ($objects as $object) { + if (is_dir($dir . '/' . $object)) + $this->_removeDir($dir . '/' . $object); + else + unlink($dir . '/' . $object); + } + + return rmdir($dir); + } + + /** + * Check for a new version + * + * @return int|bool + * true: New version is available + * false: Error while checking for update + * int: Status code (i.e. AutoUpdate::NO_UPDATE_AVAILABLE) + */ + public function checkUpdate() { + $this->_log->addNotice('Checking for a new update...'); + + // Reset previous updates + $this->_latestVersion = new version('0.0.0'); + $this->_updates = []; + + $versions = $this->_cache->get('update-versions'); + + // Check if cache is empty + if ($versions === false) { + $updateFile = $this->_updateUrl . '/' . $this->_updateFile; + $this->_log->addDebug(sprintf('Get new updates from %s', $updateFile)); + + // Read update file from update server + $update = @file_get_contents($updateFile); + if ($update === false) { + $this->_log->addInfo(sprintf('Could not download update file "%s"!', $updateFile)); + return false; + } + + // Parse update file + $updateFileExtension = substr(strrchr($this->_updateFile, '.'), 1); + switch ($updateFileExtension) { + case 'ini': + $versions = parse_ini_string($update, true); + if (!is_array($versions)) { + $this->_log->addInfo('Unable to parse update file!'); + return false; + } + + $versions = array_map(function($block) { + return isset($block['url']) ? $block['url'] : false; + }, $versions); + + break; + case 'json': + $versions = json_decode($update); + + break; + default: + $this->_log->addInfo(sprintf('Unknown file extension "%s"', $updateFileExtension)); + return false; + } + + $this->_cache->set('update-versions', $versions); + } else { + $this->_log->addDebug('Got updates from cache'); + } + + // Check for latest version + foreach ($versions as $versionRaw => $updateUrl) { + $version = new version($versionRaw); + if ($version->valid() === null) { + $this->_log->addInfo(sprintf('Could not parse version "%s" from update server "%s"', $versionRaw, $updateFile)); + continue; + } + + if (version::gt($version, $this->_currentVersion)) { + if (version::gt($version, $this->_latestVersion)) + $this->_latestVersion = $version; + + $this->_updates[] = [ + 'version' => $version, + 'url' => $updateUrl, + ]; + } + } + + // Sort versions to install + usort($this->_updates, function($a, $b) { + return version::compare($a['version'], $b['version']); + }); + + if ($this->newVersionAvailable()) { + $this->_log->addDebug(sprintf('New version "%s" available', $this->_latestVersion)); + return true; + } else { + $this->_log->addDebug('No new version available'); + return self::NO_UPDATE_AVAILABLE; + } + } + + /** + * Check if a new version is available. + * + * @return bool + */ + public function newVersionAvailable() + { + return version::gt($this->_latestVersion, $this->_currentVersion); + } + + /** + * Download the update + * + * @param string $updateUrl Url where to download from + * @param string $updateFile Path where to save the download + * + * @return bool + */ + protected function _downloadUpdate($updateUrl, $updateFile) { + $this->_log->addInfo(sprintf('Downloading update "%s" to "%s"', $updateUrl, $updateFile)); + $update = @file_get_contents($updateUrl); + + if ($update === false) { + $this->_log->addError(sprintf('Could not download update "%s"!', $updateUrl)); + return false; + } + + $handle = fopen($updateFile, 'w'); + + if (!$handle) { + $this->_log->addError(sprintf('Could not open file handle to save update to "%s"!', $updateFile)); + return false; + } + + if (!fwrite($handle, $update)) { + $this->_log->addError(sprintf('Could not write update to file "%s"!', $updateFile)); + fclose($handle); + return false; + } + + fclose($handle); + + return true; + } + + /** + * Simulate update process. + * + * @param string $updateFile + * + * @return bool + */ + protected function _simulateInstall($updateFile) + { + $this->_log->addNotice('[SIMULATE] Install new version'); + clearstatcache(); + + // Check if zip file could be opened + $zip = zip_open($updateFile); + if (!is_resource($zip)) { + $this->_log->addError(sprintf('Could not open zip file "%s", error: %d', $updateFile, $zip)); + return false; + } + + $i = -1; + $files = []; + $simulateSuccess = true; + + while ($file = zip_read($zip)) { + $i++; + + $filename = zip_entry_name($file); + $foldername = $this->_installDir . dirname($filename); + $absoluteFilename = $this->_installDir . $filename; + + $files[$i] = [ + 'filename' => $filename, + 'foldername' => $foldername, + 'absolute_filename' => $absoluteFilename, + ]; + + $this->_log->addDebug(sprintf('[SIMULATE] Updating file "%s"', $filename)); + + // Check if parent directory is writable + if (!is_dir($foldername)) { + $this->_log->addDebug(sprintf('[SIMULATE] Create directory "%s"', $foldername)); + $files[$i]['parent_folder_exists'] = false; + + $parent = dirname($foldername); + if (!is_writable($parent)) { + $files[$i]['parent_folder_writable'] = false; + + $simulateSuccess = false; + $this->_log->addWarning(sprintf('[SIMULATE] Directory "%s" has to be writeable!', $parent)); + } else { + $files[$i]['parent_folder_writable'] = true; + } + } + + // Skip if entry is a directory + if (substr($filename, -1, 1) == '/') + continue; + + // Read file contents from archive + $contents = zip_entry_read($file, zip_entry_filesize($file)); + if ($contents === false) { + $files[$i]['extractable'] = false; + + $simulateSuccess = false; + $this->_log->addWarning(sprintf('[SIMULATE] Coud not read contents of file "%s" from zip file!', $filename)); + } + + // Write to file + if (file_exists($absoluteFilename)) { + $files[$i]['file_exists'] = true; + if (!is_writable($absoluteFilename)) { + $files[$i]['file_writable'] = false; + + $simulateSuccess = false; + $this->_log->addWarning('[SIMULATE] Could not overwrite "%s"!', $absoluteFilename); + } + } else { + $files[$i]['file_exists'] = false; + + if (is_dir($foldername)) { + if (!is_writable($foldername)) { + $files[$i]['file_writable'] = false; + + $simulateSuccess = false; + $this->_log->addWarning(sprintf('[SIMULATE] The file "%s" could not be created!', $absoluteFilename)); + } else { + $files[$i]['file_writable'] = true; + } + } else { + $files[$i]['file_writable'] = true; + + $this->_log->addDebug(sprintf('[SIMULATE] The file "%s" could be created', $absoluteFilename)); + } + } + + if ($filename == $this->updateScriptName) { + $this->_log->addDebug('[SIMULATE] Update script "%s" found', $absoluteFilename); + $files[$i]['update_script'] = true; + } else { + $files[$i]['update_script'] = false; + } + } + + $this->_simulationResults = $files; + return $simulateSuccess; + } + + /** + * Install update. + * + * @param string $updateFile Path to the update file + * @param bool $simulateInstall Check for directory and file permissions before copying files + * + * @return bool + */ + protected function _install($updateFile, $simulateInstall, $version) { + $this->_log->addNotice(sprintf('Trying to install update "%s"', $updateFile)); + + // Check if install should be simulated + if ($simulateInstall && !$this->_simulateInstall($updateFile)) { + $this->_log->addCritical('Simulation of update process failed!'); + zip_close($zip); + return self::ERROR_SIMULATE; + } + + clearstatcache(); + + // Check if zip file could be opened + $zip = zip_open($updateFile); + if (!is_resource($zip)) { + $this->_log->addError(sprintf('Could not open zip file "%s", error: %d', $updateFile, $zip)); + return false; + } + + // Read every file from archive + while ($file = zip_read($zip)) { + $filename = zip_entry_name($file); + $foldername = $this->_installDir . dirname($filename); + $absoluteFilename = $this->_installDir . $filename; + + $this->_log->addDebug(sprintf('Updating file "%s"', $filename)); + + if (!is_dir($foldername)) { + if (!mkdir($foldername, $this->dirPermissions, true)) { + $this->_log->addError(sprintf('Directory "%s" has to be writeable!', $parent)); + return false; + } + } + + // Skip if entry is a directory + if (substr($filename, -1, 1) == '/') + continue; + + // Read file contents from archive + $contents = zip_entry_read($file, zip_entry_filesize($file)); + + if ($contents === false) { + $this->_log->addError(sprintf('Coud not read zip entry "%s"', $file)); + continue; + } + + // Write to file + if (file_exists($absoluteFilename)) { + if (!is_writable($absoluteFilename)) { + $this->_log->addError('Could not overwrite "%s"!', $absoluteFilename); + + zip_close($zip); + return false; + } + } else { + if (!touch($absoluteFilename)) { + $this->_log->addError(sprintf('[SIMULATE] The file "%s" could not be created!', $absoluteFilename)); + zip_close($zip); + return false; + } + + $this->_log->addDebug(sprintf('File "%s" created', $absoluteFilename)); + } + + $updateHandle = @fopen($absoluteFilename, 'w'); + + if (!$updateHandle) { + $this->_log->addError(sprintf('Could not open file "%s"!', $absoluteFilename)); + zip_close($zip); + return false; + } + + if (!fwrite($updateHandle, $contents)) { + $this->_log->addError(sprintf('Could not write to file "%s"!', $absoluteFilename)); + zip_close($zip); + return false; + } + + fclose($updateHandle); + + //If file is a update script, include + if ($filename == $this->updateScriptName) { + $this->_log->addDebug(sprintf('Try to include update script "%s"', $absoluteFilename)); + require($absoluteFilename); + + $this->_log->addInfo(sprintf('Update script "%s" included!', $absoluteFilename)); + if (!unlink($absoluteFilename)) { + $this->_log->addWarning(sprintf('Could not delete update script "%s"!', $absoluteFilename)); + } + } + } + + zip_close($zip); + + // TODO + $this->_log->addNotice(sprintf('Update "%s" successfully installed', $version)); + + return true; + } + + + /** + * Update to the latest version + * + * @param bool $simulateInstall Check for directory and file permissions before copying files (Default: true) + * @param bool $deleteDownload Delete download after update (Default: true) + * + * @return mixed integer|bool + */ + public function update($simulateInstall = true, $deleteDownload = true) { + $this->_log->addInfo('Trying to perform update'); + + // Check for latest version + if ($this->_latestVersion === null || count($this->_updates) === 0) + $this->checkUpdate(); + + if ($this->_latestVersion === null || count($this->_updates) === 0) { + $this->_log->addError('Could not get latest version from server!'); + return self::ERROR_VERSION_CHECK; + } + + // Check if current version is up to date + if (!$this->newVersionAvailable()) { + $this->_log->addWarning('No update available!'); + return self::NO_UPDATE_AVAILABLE; + } + + foreach ($this->_updates as $update) { + $this->_log->addDebug(sprintf('Update to version "%s"', $update['version'])); + + // Check for temp directory + if (empty($this->_tempDir) || !is_dir($this->_tempDir) || !is_writable($this->_tempDir)) { + $this->_log->addCritical(sprintf('Temporary directory "%s" does not exist or is not writeable!', $this->_tempDir)); + return self::ERROR_TEMP_DIR; + } + + // Check for install directory + if (empty($this->_installDir) || !is_dir($this->_installDir) || !is_writable($this->_installDir)) { + $this->_log->addCritical(sprintf('Install directory "%s" does not exist or is not writeable!', $this->_installDir)); + return self::ERROR_INSTALL_DIR; + } + + $updateFile = $this->_tempDir . $update['version'] . '.zip'; + + // Download update + if (!is_file($updateFile)) { + if (!$this->_downloadUpdate($update['url'], $updateFile)) { + $this->_log->addCritical(sprintf('Failed to download update from "%s" to "%s"!', $update['url'], $updateFile)); + return self::ERROR_DOWNLOAD_UPDATE; + } + + $this->_log->addDebug(sprintf('Latest update downloaded to "%s"', $updateFile)); + } else { + $this->_log->addInfo(sprintf('Latest update already downloaded to "%s"', $updateFile)); + } + + // Install update + $result = $this->_install($updateFile, $simulateInstall, $update['version']); + if ($result === true) { + if ($deleteDownload) { + $this->_log->addDebug(sprintf('Trying to delete update file "%s" after successfull update', $updateFile)); + if (@unlink($updateFile)) { + $this->_log->addInfo(sprintf('Update file "%s" deleted after successfull update', $updateFile)); + } else { + $this->_log->addError(sprintf('Could not delete update file "%s" after successfull update!', $updateFile)); + return self::ERROR_DELETE_TEMP_UPDATE; + } + } + } else { + if ($deleteDownload) { + $this->_log->addDebug(sprintf('Trying to delete update file "%s" after failed update', $updateFile)); + if (@unlink($updateFile)) { + $this->_log->addInfo(sprintf('Update file "%s" deleted after failed update', $updateFile)); + } else { + $this->_log->addError(sprintf('Could not delete update file "%s" after failed update!', $updateFile)); + } + } + + return $result; + } + } + + return true; + } +}