PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/update.class.php

http://snowcms.googlecode.com/
PHP | 567 lines | 294 code | 60 blank | 213 comment | 78 complexity | 17960f8d255d9e477cbdebe3f5f106bd MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. <?php
  2. ////////////////////////////////////////////////////////////////////////////
  3. // SnowCMS v2.0 //
  4. // By the SnowCMS Team //
  5. // www.snowcms.com //
  6. // Released under the Microsoft Reciprocal License //
  7. // www.opensource.org/licenses/ms-rl.html //
  8. ////////////////////////////////////////////////////////////////////////////
  9. // //
  10. // SnowCMS originally pawned by soren121 started in early 2008 //
  11. // //
  12. ////////////////////////////////////////////////////////////////////////////
  13. // //
  14. // SnowCMS v2.0 began in November 2009 //
  15. // //
  16. ////////////////////////////////////////////////////////////////////////////
  17. // File version: SnowCMS 2.0 //
  18. ////////////////////////////////////////////////////////////////////////////
  19. if(!defined('INSNOW'))
  20. {
  21. die('Nice try...');
  22. }
  23. /*
  24. Class: Update
  25. The Update class facilitates the means for you guessed it, updating files,
  26. such as updating the SnowCMS system in its entirety, or a plugin (though
  27. the <Component> class is more apt to doing that).
  28. */
  29. class Update
  30. {
  31. // Variable: filename
  32. // The location of the update package.
  33. private $filename;
  34. /*
  35. Method: __construct
  36. */
  37. public function __construct()
  38. {
  39. $this->filename = null;
  40. }
  41. /*
  42. Method: set_filename
  43. Parameters:
  44. string $filename - The name of the file which contains the update
  45. package.
  46. Returns:
  47. bool - Returns true on success, false on failure.
  48. */
  49. public function set_filename($filename)
  50. {
  51. if(!is_file($filename))
  52. {
  53. return false;
  54. }
  55. $this->filename = $filename;
  56. return true;
  57. }
  58. /*
  59. Method: download
  60. Downloads the specified update package, and if supplied, the integrity of
  61. the downloaded package will be checked as well.
  62. Parameters:
  63. string $download_url - The URL at which the update package will be downloaded from.
  64. string $save_to - The complete path (including the files name as well) of where the
  65. update package will be saved (downloading the package will fail if
  66. the supplied path's directory doesn't exist, or if PHP does not have
  67. write access to the specified path).
  68. string $checksum_url - The URL at which the checksum should be downloaded from. If the
  69. length of the string is 32 characters, it will be assumed as MD5,
  70. if it is 40 characters, SHA-1 is assumed. If the number of characters
  71. is neither, then checking the files integrity will fail.
  72. Returns:
  73. array - Returns an array containing a downloaded (true if the file was downloaded successfully)
  74. and valid (true if the checksum downloaded matched that of the downloaded package, this
  75. index will be set to null if $checksum_url was not supplied).
  76. */
  77. public function download($download_url, $save_to, $checksum_url = null)
  78. {
  79. // No download or save to URL?
  80. if(empty($download_url) || empty($save_to))
  81. {
  82. // Well we can't download it!
  83. return false;
  84. }
  85. // Attempt to open the save to path...
  86. $fp = fopen($save_to, 'w');
  87. // Did it work?
  88. if(empty($fp))
  89. {
  90. // No it did not, so the download failed.
  91. return array(
  92. 'downloaded' => false,
  93. 'valid' => !empty($checksum_url) ? false : null,
  94. );
  95. }
  96. // Close it... We just needed to check.
  97. fclose($fp);
  98. // We need the HTTP class.
  99. $http = api()->load_class('HTTP');
  100. // Now download the update package.
  101. $downloaded = $http->request($download_url, array(), 0, $save_to);
  102. // Did it download?
  103. if(empty($downloaded))
  104. {
  105. return array(
  106. 'downloaded' => false,
  107. 'valid' => !empty($checksum_url) ? false : null,
  108. );
  109. }
  110. // Do we need to download a checksum?
  111. if(empty($checksum_url))
  112. {
  113. // Nope.
  114. return array(
  115. 'downloaded' => true,
  116. 'valid' => null,
  117. );
  118. }
  119. // Now time to download the checksum.
  120. $checksum = $http->request($checksum_url);
  121. $valid = strlen($checksum) == 40 ? sha1_file($save_to) == $checksum : (strlen($checksum) == 32 ? md5_file($save_to) == $checksum : false);
  122. // Is it not valid? Then delete it!
  123. if(empty($valid))
  124. {
  125. @unlink($save_to);
  126. }
  127. // We are done, well, almost :P
  128. return array(
  129. 'downloaded' => true,
  130. 'valid' => $valid,
  131. );
  132. }
  133. /*
  134. Method: extract
  135. Extracts the specified package to the specified directory.
  136. Parameters:
  137. string $filename - The file containing the update package.
  138. string $path - The path of where the update package should be
  139. extracted to. This needs to be a temporary location
  140. as another method handles the actual copying of
  141. the files to their new destination. Of course,
  142. this directory must be writable.
  143. string $type - The type of the specified package, such as tar (not
  144. tar.gz, as if the Tar class detects the tar as being
  145. gzipped, it will be extracted from the gzip automatically).
  146. If no type is supplied, the type will be determined by
  147. the files extension.
  148. Returns:
  149. bool - Returns true if the specified update package was successfully extracted
  150. to the specified path.
  151. NOTE:
  152. The supplied $path must exist! and as specified, be writable!!!
  153. */
  154. public function extract($filename, $path, $type = null)
  155. {
  156. // No supplied type? Try to auto-detect.
  157. if(empty($type) && strpos($filename, '.') !== false)
  158. {
  159. $tmp = explode('.', $filename);
  160. $extension = strtolower(array_pop($tmp));
  161. if($extension == 'gz' || $extension == 'tgz' || $extension == 'tar')
  162. {
  163. // It's a tarball...
  164. if($extension == 'tar' || $extension == 'tgz' || strtolower(array_pop($tmp)) == 'tar')
  165. {
  166. $type = 'tar';
  167. }
  168. }
  169. elseif($extension == 'zip')
  170. {
  171. // Change the type to zip :-)
  172. $type = 'zip';
  173. }
  174. }
  175. // Still empty..?
  176. if(empty($type))
  177. {
  178. // Hmm... Try detecting it another way.
  179. $zip = api()->load_class('Zip');
  180. $tar = api()->load_class('Tar');
  181. if($zip->open($filename))
  182. {
  183. // Looks like it's a zip! Cool.
  184. $type = 'zip';
  185. $zip->close();
  186. }
  187. elseif($tar->open($filename))
  188. {
  189. $type = 'tar';
  190. $tar->close();
  191. }
  192. }
  193. // Does the file not exist? Or the path (not writable either)? Or does is the type not supported?
  194. if(!file_exists($filename) || !is_file($filename) || !file_exists($path) || !is_dir($path) || !is_writable($path) || ($type != 'tar' && $type != 'zip'))
  195. {
  196. return false;
  197. }
  198. if($type == 'zip')
  199. {
  200. $zip = api()->load_class('Zip');
  201. if(!$zip->open($filename))
  202. {
  203. return false;
  204. }
  205. elseif(!$zip->extract($path))
  206. {
  207. return false;
  208. }
  209. else
  210. {
  211. // Unlock the file! Sheesh!
  212. $zip->close();
  213. }
  214. }
  215. elseif($type == 'tar')
  216. {
  217. // We need the Tar class.
  218. $tar = api()->load_class('Tar');
  219. // Now open the tarball, or at least try.
  220. if(!$tar->open($filename))
  221. {
  222. // Sorry, we couldn't open it for some reason.
  223. return false;
  224. }
  225. else
  226. {
  227. // Is it gzipped? If it is, remove it from its gzipped state.
  228. if($tar->is_gzipped())
  229. {
  230. if(!$tar->ungzip())
  231. {
  232. // Well, that sucked.
  233. return false;
  234. }
  235. }
  236. // Now extract the tarball to the path specified.
  237. if(!$tar->extract($path))
  238. {
  239. // That didn't work :/
  240. return false;
  241. }
  242. else
  243. {
  244. // Unlock the file, please.
  245. $tar->close();
  246. }
  247. }
  248. }
  249. // All done!
  250. return true;
  251. }
  252. /*
  253. Method: get_listing
  254. With the supplied update directory, this will return an array containing
  255. all the files which are now in the update directory.
  256. Parameters:
  257. string $path - The base path of where the update package is located in.
  258. Returns:
  259. array - Returns an array containing all the files which are in the specified
  260. path, including directories as well. However, if the specified path
  261. does not exist, false will be returned.
  262. */
  263. public function get_listing($path, $implode = true)
  264. {
  265. // Get the stuff in the specified path.
  266. $files = scandir($path);
  267. $listing = array();
  268. // Was anything there (does it exist..?)
  269. if(count($files) > 0)
  270. {
  271. foreach($files as $file)
  272. {
  273. // Ignore the . and .. directories.
  274. if($file == '.' || $file == '..')
  275. {
  276. continue;
  277. }
  278. // Is it a directory? As this index will contain the files and folders
  279. // within an array.
  280. if(is_dir($path. '/'. $file))
  281. {
  282. // Woo for recursion!
  283. $listing[$file. '/'] = $this->get_listing($path. '/'. $file, false);
  284. }
  285. // Otherwise it will just be set to the files name ;-)
  286. else
  287. {
  288. $listing[$file] = $file;
  289. }
  290. }
  291. }
  292. else
  293. {
  294. // Doesn't exist, sorry.
  295. return false;
  296. }
  297. // This only happens on the very first call of the method.
  298. if(!empty($implode))
  299. {
  300. $tmp = array();
  301. if(count($listing))
  302. {
  303. foreach($listing as $file => $f)
  304. {
  305. $tmp[] = $file;
  306. if(is_array($f))
  307. {
  308. $append = $this->get_listing_implode($f);
  309. if(count($append))
  310. {
  311. foreach($append as $a)
  312. {
  313. $tmp[] = $file. $a;
  314. }
  315. }
  316. }
  317. }
  318. $listing = $tmp;
  319. }
  320. }
  321. return $listing;
  322. }
  323. /*
  324. Method: get_listing_implode
  325. Converts the supplied array into a full path instead of relative
  326. paths, the reason get_listing sets a directory to an array containing
  327. its children is so that the directory can be initially created if need
  328. be. Don't understand? That's fine ;-) You don't need to.
  329. Parameters:
  330. array $array
  331. Returns:
  332. array
  333. */
  334. private function get_listing_implode($array)
  335. {
  336. $tmp = array();
  337. if(count($array))
  338. {
  339. foreach($array as $a => $d)
  340. {
  341. $tmp[] = $a;
  342. if(is_array($d))
  343. {
  344. $append = $this->get_listing_implode($d);
  345. if(count($append))
  346. foreach($append as $g)
  347. $tmp[] = $a. '/'. $g;
  348. }
  349. }
  350. }
  351. return $tmp;
  352. }
  353. /*
  354. Method: copy
  355. Copies the specified update file to its new destination.
  356. Parameters:
  357. string $path - The base path of where the update files are located.
  358. string $new_path - The new base path of where the file should be copied.
  359. string $filename - The name of the file to be copied from {$path}/{$filename}
  360. to {$new_path}/{$filename}. If no file name is supplied,
  361. then the directory listing will be obtainined internally
  362. and all files will be copied at once.
  363. Returns:
  364. mixed - Returns true if the file was copied successfully, false if not. However,
  365. if no file name was supplied, an array containing all the files in path
  366. will be returned (the index) and their value will be either true or false,
  367. true if the file was copied, false if not.
  368. */
  369. public function copy($path, $new_path, $filename = null)
  370. {
  371. // Make sure everything exists and what not.
  372. if(!file_exists($path) || !is_dir($path) || !is_readable($path) || !file_exists($new_path) || !is_dir($new_path) || !is_writable($new_path) || (!empty($filename) && !file_exists($path. '/'. $filename)))
  373. {
  374. return false;
  375. }
  376. // One more check, make sure the specified file you want to move is actually in $path.
  377. $path = realpath($path);
  378. $tmp = realpath($path. '/'. $filename);
  379. if(substr($tmp, 0, strlen($path)) != $path)
  380. {
  381. // It is not within the specified path, sorry! No trying to do something naughty ;-)
  382. return false;
  383. }
  384. if(empty($filename))
  385. {
  386. // Get the directory ourself...
  387. $listing = array_flip($this->get_listing($path));
  388. foreach($listing as $file => $d)
  389. {
  390. // Now copy them!
  391. $listing[$file] = $this->copy($path, $new_path, $file);
  392. }
  393. return $listing;
  394. }
  395. else
  396. {
  397. // Time to copy! Woo!
  398. // That is, if the file exists...
  399. if(!file_exists($path. '/'. $filename))
  400. {
  401. return false;
  402. }
  403. // Create the directory, if need be.
  404. $dirname = dirname($new_path. '/'. $filename);
  405. if(!file_exists($dirname))
  406. {
  407. @mkdir($dirname, 0755, true);
  408. }
  409. // Is it not a directory? Must be a file we need to copy then.
  410. if(!is_dir($path. '/'. $filename))
  411. {
  412. // Open up the file to be copied into in write and binary mode.
  413. $fp = fopen($new_path. '/'. $filename, 'wb');
  414. // Failed to open? :-(
  415. if(empty($fp))
  416. {
  417. return false;
  418. }
  419. // It's mine!
  420. flock($fp, LOCK_EX);
  421. // Now the file we will get the data from.
  422. $new_fp = fopen($path. '/'. $filename, 'rb');
  423. // Failed to open? :-(
  424. if(empty($new_fp))
  425. {
  426. return false;
  427. }
  428. flock($new_fp, LOCK_SH);
  429. // Now copy that data over :-)
  430. while(!feof($new_fp))
  431. {
  432. fwrite($fp, fread($new_fp, 8192));
  433. }
  434. // We are done with these now.
  435. flock($fp, LOCK_UN);
  436. flock($new_fp, LOCK_UN);
  437. fclose($fp);
  438. fclose($new_fp);
  439. // It was done successfully!
  440. return true;
  441. }
  442. else
  443. {
  444. // Just incase :P
  445. if(!file_exists($new_path. '/'. $filename))
  446. {
  447. return @mkdir($new_path. '/'. $filename);
  448. }
  449. }
  450. }
  451. }
  452. /*
  453. Method: finish
  454. Finishes the update process, which includes deleting the directory
  455. and the files which were copied over to their new respective locations,
  456. running the update.php file in the new paths base directory (if any, if
  457. there isn't an update.php file, it won't be ran, of course, once it is
  458. ran, however, it will be deleted).
  459. Parameters:
  460. string $path - The base path of where the update files are located.
  461. string $new_path - The new base path of where the file should be copied.
  462. Returns:
  463. bool - Returns true if the update process was successfully finished, false
  464. if it was not.
  465. */
  466. public function finish($path, $new_path)
  467. {
  468. // Remove all files and folders in the update location.
  469. if(!recursive_unlink($path))
  470. {
  471. return false;
  472. }
  473. // Any update file? Run it then!
  474. if(file_exists($new_path. '/update.php'))
  475. {
  476. // We run it by simply including it.
  477. require_once($new_path. '/update.php');
  478. // Now remove it.
  479. @unlink($new_path. '/update.php');
  480. }
  481. // Alright! All done!
  482. return true;
  483. }
  484. }
  485. ?>