PageRenderTime 57ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/webapp/_lib/class.AppUpgraderDiskUtil.php

https://github.com/SimonCoopey/ThinkUp
PHP | 297 lines | 192 code | 1 blank | 104 comment | 47 complexity | 02b3884acdf5c978d0d417114a0fd9d3 MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. * ThinkUp/webapp/_lib/class.AppUpgraderDiskUtil.php
  5. *
  6. * Copyright (c) 2012-2016 Mark Wilkie
  7. *
  8. * LICENSE:
  9. *
  10. * This file is part of ThinkUp (http://thinkup.com).
  11. *
  12. * ThinkUp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
  13. * License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any
  14. * later version.
  15. *
  16. * ThinkUp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
  17. * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  18. * details.
  19. *
  20. * You should have received a copy of the GNU General Public License along with ThinkUp. If not, see
  21. * <http://www.gnu.org/licenses/>.
  22. *
  23. * @license http://www.gnu.org/licenses/gpl.html
  24. * @copyright 2012-2016 Mark Wilkie
  25. * @author Mark Wilkie <mwilkie[at]gmail[dot]com>
  26. */
  27. class AppUpgraderDiskUtil {
  28. /**
  29. * @var int Required amount of disk space to run web update, 100MB default
  30. */
  31. static $DISK_SPACE_NEEDED = 104857600;
  32. /**
  33. * @var array Config file regexes
  34. */
  35. var $CONFIG = array( array('/\/config\.inc\.php$/', 'config.inc.php') );
  36. /**
  37. * @var array Files to ignore during web update
  38. */
  39. var $IGNORE_FILES = array('/_lib\/view\/compiled_view/', '/\/data\//');
  40. /**
  41. * @var str ThinkUp's current version
  42. */
  43. var $app_version = '0';
  44. /**
  45. * @var str Application directory
  46. */
  47. private $app_dir;
  48. /**
  49. * @var str Application data directory
  50. */
  51. private $data_dir;
  52. /**
  53. * Constructor
  54. * @param $app_dir str Application directory
  55. * @returns AppUpgraderDiskUtil
  56. */
  57. public function __construct($app_dir) {
  58. $this->app_dir = $app_dir;
  59. $this->app_version = Config::getInstance()->getValue('THINKUP_VERSION');
  60. $this->data_path = FileDataManager::getDataPath();
  61. }
  62. /**
  63. * Get the amount of available disk space.
  64. * @throws Exception If there's not enough available disk space
  65. * @return int Number of available megabytes; -1 if not known
  66. */
  67. public function getAvailableDiskSpace() {
  68. $disk_free_space = disk_free_space($this->app_dir);
  69. if ($disk_free_space !== false && $disk_free_space != '') {
  70. $available = (int) round(($disk_free_space / 1024) / 1024 );
  71. $needed = (int) (self::$DISK_SPACE_NEEDED / 1024) / 1024;
  72. if ($available > $needed) {
  73. return $available;
  74. } else {
  75. throw new exception("There is not enough free disk space to perform an update. " .$available.
  76. "MB available, but ".$needed."MB required.");
  77. }
  78. } else {
  79. return -1;
  80. }
  81. }
  82. /**
  83. * Get the amount of disk space required by web updater in bytes.
  84. * @return int
  85. */
  86. public function getDiskSpaceNeeded() {
  87. return self::$DISK_SPACE_NEEDED;
  88. }
  89. /**
  90. * Delete current ThinkUp application files.
  91. * @throws Exception
  92. */
  93. public function deleteOldInstall() {
  94. $files = $this->findAllFiles($this->app_dir);
  95. foreach($files as $file) {
  96. $ignore = false;
  97. foreach($this->IGNORE_FILES as $ignore_regex) {
  98. if (preg_match($ignore_regex, $file)) {
  99. $ignore = true;
  100. break;
  101. }
  102. }
  103. if ($ignore) {
  104. continue;
  105. }
  106. if (is_dir($file)) {
  107. rmdir($file);
  108. } else {
  109. if (!unlink($file)) {
  110. throw new Exception("Unable able to delete $file");
  111. };
  112. }
  113. }
  114. }
  115. /**
  116. * Set the amount of disk space needed in bytes.
  117. * @param $bytes
  118. */
  119. public function setDiskSpaceNeeded($bytes) {
  120. self::$DISK_SPACE_NEEDED = $bytes;
  121. }
  122. /**
  123. * Back up current installation files.
  124. * @throws Exception
  125. */
  126. public function backUpInstall() {
  127. $backupzip = new ZipArchive();
  128. $backup_zipfile = $this->getBackupFilename();
  129. if ($backupzip->open($backup_zipfile, ZIPARCHIVE::CREATE)!==true) {
  130. throw new Exception("Unable to open backup file to export: $backup_zipfile");
  131. }
  132. $files = $this->findAllFiles($this->app_dir);
  133. foreach($files as $file) {
  134. $ignore = false;
  135. foreach($this->IGNORE_FILES as $ignore_regex) {
  136. if (preg_match($ignore_regex, $file)) {
  137. $ignore = true;
  138. break;
  139. }
  140. }
  141. if ($ignore) {
  142. continue;
  143. }
  144. $config = false;
  145. $backup_config = $this->getBackupFilename(true);
  146. foreach($this->CONFIG as $config_regex) {
  147. if (preg_match($config_regex[0], $file)) {
  148. copy($file, $backup_config);
  149. $config = true;
  150. break;
  151. }
  152. }
  153. if (!$config && $file != $this->data_path) {
  154. $backupzip->addFile($file);
  155. }
  156. }
  157. $zip_close_status = $backupzip->close();
  158. if ($zip_close_status == false) {
  159. throw new Exception("Unable to create zip archive to back up. $backup_zipfile");
  160. }
  161. return array('backup' => $backup_zipfile, 'config' => $backup_config);
  162. }
  163. /**
  164. * Find all files.
  165. * @param $file File or directory name
  166. * @throws Exception
  167. * @return array
  168. */
  169. public function findAllFiles($file) {
  170. if (!is_writable($file)) {
  171. throw new Exception(self::getInadequateFilePermissionsException());
  172. }
  173. $root = scandir($file);
  174. foreach($root as $value) {
  175. if ($value === '.' || $value === '..') {
  176. continue;
  177. }
  178. if (is_file("$file/$value")) {
  179. $result[]="$file/$value";
  180. continue;
  181. }
  182. $files = $this->findAllFiles("$file/$value");
  183. if (isset($files)) {
  184. foreach($files as $value) {
  185. $result[]=$value;
  186. }
  187. }
  188. }
  189. return (isset($result) ? $result : array());
  190. }
  191. /**
  192. * Get backup filename.
  193. * @param bool $config Whether or not it's a config file, defaults to false.
  194. * @return str File name
  195. */
  196. private function getBackupFilename($config = false) {
  197. $update_dir = dirname(__FILE__);
  198. $date = time();
  199. $filename = $this->data_path . '' . $date . '-v' . $this->app_version . '-';
  200. if ($config) {
  201. $filename .= 'config.inc.backup.php';
  202. } else {
  203. $filename .= 'backup.zip';
  204. }
  205. return $filename;
  206. }
  207. /**
  208. * Write zip file contents to the application data directory.
  209. * @param str $data
  210. * @throws Exception if unable to write file
  211. * @return str $filename
  212. */
  213. public function writeZip($data) {
  214. $date = time();
  215. $filename = $this->data_path . 'latest_update.zip';
  216. $result = file_put_contents($filename, $data);
  217. if ($result === false) {
  218. throw new Exception("Unable to save ".$filename.". Result ".$result);
  219. } else if (is_int($result)) {
  220. if ($result < 1) {
  221. throw new Exception("Unable to save ".$filename.". Wrote ".$result.' bytes.');
  222. }
  223. }
  224. return $filename;
  225. }
  226. /**
  227. * Copy new install files.
  228. * @param str $src
  229. * @param str $dst
  230. */
  231. public function recurseCopy($src,$dst) {
  232. $dir = opendir($src);
  233. @mkdir($dst);
  234. while(false !== ( $file = readdir($dir)) ) {
  235. if (( $file != '.' ) && ( $file != '..' )) {
  236. if ( is_dir($src . '/' . $file) ) {
  237. $this->recurseCopy($src . '/' . $file,$dst . '/' . $file);
  238. } else {
  239. copy($src . '/' . $file,$dst . '/' . $file);
  240. }
  241. }
  242. }
  243. closedir($dir);
  244. }
  245. /**
  246. * Remove a temporary thinkup directory.
  247. * @param str $new_version_dir
  248. */
  249. public function deleteDir($file) {
  250. if (!preg_match('/\/thinkup(\/)?/', $file )) {
  251. throw new Exception("The deleteDir function is designed to remove a temporary thinkup directory: " . $file);
  252. }
  253. if (!file_exists($file)) {
  254. throw new Exception("Trying to delete a directory or file that does not exist: $file");
  255. }
  256. if (!is_dir($file)) {
  257. return unlink($file);
  258. }
  259. foreach (scandir($file) as $item) {
  260. if ($item == '.' || $item == '..') {
  261. continue;
  262. }
  263. if (!$this->deleteDir($file . "/" . $item)) {
  264. return false;
  265. }
  266. }
  267. return rmdir($file);
  268. }
  269. /**
  270. * Checks if application file permissions allow web-based update.
  271. * @throws Exception
  272. * @param $app_path
  273. */
  274. public function validateUpdatePermissions($app_path) {
  275. $app_files = $this->findAllFiles($app_path);
  276. foreach($app_files as $file) {
  277. if (!is_writable($file)) {
  278. throw new Exception(self::getInadequateFilePermissionsException());
  279. }
  280. }
  281. }
  282. /**
  283. * Get detailed file permissions user error text.
  284. * @return str
  285. */
  286. private function getInadequateFilePermissionsException() {
  287. $whoami = @exec('whoami');
  288. if (empty($whoami)) {
  289. $whoami = 'nobody';
  290. }
  291. return "<b>Oops!</b> ThinkUp can't upgrade itself because it doesn't have the right file permissions. ".
  292. "To fix this problem, run<br /><br /><code>sudo chown -R $whoami ". THINKUP_WEBAPP_PATH."</code><br /><br />".
  293. "on your server, using root (or sudo). If you don't have root access, try the following: ".
  294. "<br /><br /> <code>chmod -R a+rw ".THINKUP_WEBAPP_PATH."</code><br /><br />";
  295. }
  296. }