PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/components/com_akeeba/models/jsons.php

https://bitbucket.org/kraymitchell/apex
PHP | 1251 lines | 967 code | 166 blank | 118 comment | 124 complexity | b1bc0fc4049bd9980957e5f2ded1433c MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, BSD-3-Clause, LGPL-2.1, GPL-3.0
  1. <?php
  2. /**
  3. * @package AkeebaBackup
  4. * @copyright Copyright (c)2009-2012 Nicholas K. Dionysopoulos
  5. * @license GNU General Public License version 3, or later
  6. *
  7. * @since 3.0
  8. */
  9. // Protect from unauthorized access
  10. defined('_JEXEC') or die();
  11. // JSON API version number
  12. define('AKEEBA_JSON_API_VERSION', '320');
  13. /*
  14. * Short API version history:
  15. * 300 First draft. Basic backup working. Encryption semi-broken.
  16. * 316 Fixed download feature.
  17. */
  18. // Force load the AEUtilEncrypt class if it's Akeeba Backup Professional
  19. if(AKEEBA_PRO == 1) $dummy = new AEUtilEncrypt;
  20. if(!defined('AKEEBA_BACKUP_ORIGIN'))
  21. {
  22. define('AKEEBA_BACKUP_ORIGIN','json');
  23. }
  24. class AkeebaModelJsons extends FOFModel
  25. {
  26. const STATUS_OK = 200; // Normal reply
  27. const STATUS_NOT_AUTH = 401; // Invalid credentials
  28. const STATUS_NOT_ALLOWED = 403; // Not enough privileges
  29. const STATUS_NOT_FOUND = 404; // Requested resource not found
  30. const STATUS_INVALID_METHOD = 405; // Unknown JSON method
  31. const STATUS_ERROR = 500; // An error occurred
  32. const STATUS_NOT_IMPLEMENTED = 501; // Not implemented feature
  33. const STATUS_NOT_AVAILABLE = 503; // Remote service not activated
  34. const ENCAPSULATION_RAW = 1; // Data in plain-text JSON
  35. const ENCAPSULATION_AESCTR128 = 2; // Data in AES-128 stream (CTR) mode encrypted JSON
  36. const ENCAPSULATION_AESCTR256 = 3; // Data in AES-256 stream (CTR) mode encrypted JSON
  37. const ENCAPSULATION_AESCBC128 = 4; // Data in AES-128 standard (CBC) mode encrypted JSON
  38. const ENCAPSULATION_AESCBC256 = 5; // Data in AES-256 standard (CBC) mode encrypted JSON
  39. private $json_errors = array(
  40. 'JSON_ERROR_NONE' => 'No error has occurred (probably emtpy data passed)',
  41. 'JSON_ERROR_DEPTH' => 'The maximum stack depth has been exceeded',
  42. 'JSON_ERROR_CTRL_CHAR' => 'Control character error, possibly incorrectly encoded',
  43. 'JSON_ERROR_SYNTAX' => 'Syntax error'
  44. );
  45. /** @var int The status code */
  46. private $status = 200;
  47. /** @var int Data encapsulation format */
  48. private $encapsulation = 1;
  49. /** @var mixed Any data to be returned to the caller */
  50. private $data = '';
  51. /** @var string A password passed to us by the caller */
  52. private $password = null;
  53. /** @var string The method called by the client */
  54. private $method_name = null;
  55. public function execute($json)
  56. {
  57. // Check if we're activated
  58. $enabled = AEPlatform::getInstance()->get_platform_configuration_option('frontend_enable', 0);
  59. if(!$enabled)
  60. {
  61. $this->data = 'Access denied';
  62. $this->status = self::STATUS_NOT_AVAILABLE;
  63. $this->encapsulation = self::ENCAPSULATION_RAW;
  64. return $this->getResponse();
  65. }
  66. // Try to JSON-decode the request's input first
  67. $request = @$this->json_decode($json, false);
  68. if(is_null($request))
  69. {
  70. // Could not decode JSON
  71. $this->data = 'JSON decoding error';
  72. $this->status = self::STATUS_ERROR;
  73. $this->encapsulation = self::ENCAPSULATION_RAW;
  74. return $this->getResponse();
  75. }
  76. // Decode the request body
  77. // Request format: {encapsulation, body{ [key], [challenge], method, [data] }} or {[challenge], method, [data]}
  78. if( isset($request->encapsulation) && isset($request->body) )
  79. {
  80. if(!class_exists('AEUtilEncrypt') && !($request->encapsulation == self::ENCAPSULATION_RAW))
  81. {
  82. // Encrypted request found, but there is no encryption class available!
  83. $this->data = 'This server does not support encrypted requests';
  84. $this->status = self::STATUS_NOT_AVAILABLE;
  85. $this->encapsulation = self::ENCAPSULATION_RAW;
  86. return $this->getResponse();
  87. }
  88. // Fully specified request
  89. switch( $request->encapsulation )
  90. {
  91. case self::ENCAPSULATION_AESCBC128:
  92. if(!isset($body))
  93. {
  94. $request->body = base64_decode($request->body);
  95. $body = AEUtilEncrypt::AESDecryptCBC($request->body, $this->serverKey(), 128);
  96. }
  97. break;
  98. case self::ENCAPSULATION_AESCBC256:
  99. if(!isset($body))
  100. {
  101. $request->body = base64_decode($request->body);
  102. $body = AEUtilEncrypt::AESDecryptCBC($request->body, $this->serverKey(), 256);
  103. }
  104. break;
  105. case self::ENCAPSULATION_AESCTR128:
  106. if(!isset($body))
  107. {
  108. $body = AEUtilEncrypt::AESDecryptCtr($request->body, $this->serverKey(), 128);
  109. }
  110. break;
  111. case self::ENCAPSULATION_AESCTR256:
  112. if(!isset($body))
  113. {
  114. $body = AEUtilEncrypt::AESDecryptCtr($request->body, $this->serverKey(), 256);
  115. }
  116. break;
  117. case self::ENCAPSULATION_RAW:
  118. $body = $request->body;
  119. break;
  120. }
  121. if(!empty($request->body))
  122. {
  123. $body = rtrim( $body, chr(0) );
  124. $request->body = $this->json_decode($body);
  125. if(is_null($request->body))
  126. {
  127. // Decryption failed. The user is an imposter! Go away, hacker!
  128. $this->data = 'Authentication failed';
  129. $this->status = self::STATUS_NOT_AUTH;
  130. $this->encapsulation = self::ENCAPSULATION_RAW;
  131. return $this->getResponse();
  132. }
  133. }
  134. }
  135. elseif( isset($request->body) )
  136. {
  137. // Partially specified request, assume RAW encapsulation
  138. $request->encapsulation = self::ENCAPSULATION_RAW;
  139. $request->body = $this->json_decode($request->body);
  140. }
  141. else
  142. {
  143. // Legacy request
  144. $legacyRequest = clone $request;
  145. $request = (object) array( 'encapsulation' => self::ENCAPSULATION_RAW, 'body' => null );
  146. $request->body = $this->json_decode($legacyRequest);
  147. unset($legacyRequest);
  148. }
  149. // Authenticate the user. Do note that if an encrypted request was made, we can safely assume that
  150. // the user is authenticated (he already knows the server key!)
  151. if($request->encapsulation == self::ENCAPSULATION_RAW)
  152. {
  153. $authenticated = false;
  154. if(isset($request->body->challenge))
  155. {
  156. list($challenge,$check) = explode(':', $request->body->challenge);
  157. $crosscheck = strtolower(md5($challenge.$this->serverKey()));
  158. $authenticated = ($crosscheck == $check);
  159. }
  160. if(!$authenticated)
  161. {
  162. // If the challenge was missing or it was wrong, don't let him go any further
  163. $this->data = 'Invalid login credentials';
  164. $this->status = self::STATUS_NOT_AUTH;
  165. $this->encapsulation = self::ENCAPSULATION_RAW;
  166. return $this->getResponse();
  167. }
  168. }
  169. // Replicate the encapsulation preferences of the client for our own output
  170. $this->encapsulation = $request->encapsulation;
  171. // Store the client-specified key, or use the server key if none specified and the request
  172. // came encrypted.
  173. $this->password = isset($request->body->key) ? $request->body->key : null;
  174. $hasKey = property_exists($request->body, 'key') ? !is_null($request->body->key) : false;
  175. if(!$hasKey && ($request->encapsulation != self::ENCAPSULATION_RAW) )
  176. {
  177. $this->password = $this->serverKey();
  178. }
  179. // Does the specified method exist?
  180. $method_exists = false;
  181. $method_name = '';
  182. if(isset($request->body->method))
  183. {
  184. $method_name = ucfirst($request->body->method);
  185. $this->method_name = $method_name;
  186. $method_exists = method_exists($this, '_api'.$method_name );
  187. }
  188. if(!$method_exists)
  189. {
  190. // The requested method doesn't exist. Oops!
  191. $this->data = "Invalid method $method_name";
  192. $this->status = self::STATUS_INVALID_METHOD;
  193. $this->encapsulation = self::ENCAPSULATION_RAW;
  194. return $this->getResponse();
  195. }
  196. // Run the method
  197. $params = array();
  198. if(isset($request->body->data)) $params = (array)$request->body->data;
  199. $this->data = call_user_func( array($this, '_api'.$method_name) , $params);
  200. return $this->getResponse();
  201. }
  202. /**
  203. * Packages the response to a JSON-encoded object, optionally encrypting the
  204. * data part with a caller-supplied password.
  205. * @return string The JSON-encoded response
  206. */
  207. private function getResponse()
  208. {
  209. // Initialize the response
  210. $response = array(
  211. 'encapsulation' => $this->encapsulation,
  212. 'body' => array(
  213. 'status' => $this->status,
  214. 'data' => null
  215. )
  216. );
  217. switch($this->method_name)
  218. {
  219. case 'Download':
  220. $data = json_encode($this->data);
  221. break;
  222. default:
  223. $data = $this->json_encode($this->data);
  224. break;
  225. }
  226. if(empty($this->password)) $this->encapsulation = self::ENCAPSULATION_RAW;
  227. switch($this->encapsulation)
  228. {
  229. case self::ENCAPSULATION_RAW:
  230. break;
  231. case self::ENCAPSULATION_AESCTR128:
  232. $data = AEUtilEncrypt::AESEncryptCtr($data, $this->password, 128);
  233. break;
  234. case self::ENCAPSULATION_AESCTR256:
  235. $data = AEUtilEncrypt::AESEncryptCtr($data, $this->password, 256);
  236. break;
  237. case self::ENCAPSULATION_AESCBC128:
  238. $data = base64_encode(AEUtilEncrypt::AESEncryptCBC($data, $this->password, 128));
  239. break;
  240. case self::ENCAPSULATION_AESCBC256:
  241. $data = base64_encode(AEUtilEncrypt::AESEncryptCBC($data, $this->password, 256));
  242. break;
  243. }
  244. $response['body']['data'] = $data;
  245. switch($this->method_name)
  246. {
  247. case 'Download':
  248. return '###' . json_encode($response) . '###';
  249. break;
  250. default:
  251. return '###' . $this->json_encode($response) . '###';
  252. break;
  253. }
  254. }
  255. private function serverKey()
  256. {
  257. static $key = null;
  258. if(is_null($key))
  259. {
  260. $key = AEPlatform::getInstance()->get_platform_configuration_option('frontend_secret_word', '');
  261. }
  262. return $key;
  263. }
  264. private function _apiGetVersion()
  265. {
  266. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/liveupdate.php';
  267. $updateInformation = LiveUpdate::getUpdateInformation();
  268. $edition = AKEEBA_PRO ? 'pro' : 'core';
  269. return (object)array(
  270. 'api' => AKEEBA_JSON_API_VERSION,
  271. 'component' => AKEEBA_VERSION,
  272. 'date' => AKEEBA_DATE,
  273. 'edition' => $edition,
  274. 'updateinfo' => $updateInformation,
  275. );
  276. }
  277. private function _apiGetProfiles()
  278. {
  279. require_once JPATH_SITE.'/administrator/components/com_akeeba/models/profiles.php';
  280. $model = new AkeebaModelProfiles();
  281. $profiles = $model->getProfilesList(true);
  282. $ret = array();
  283. if(count($profiles))
  284. {
  285. foreach($profiles as $profile)
  286. {
  287. $temp = new stdClass();
  288. $temp->id = $profile->id;
  289. $temp->name = $profile->description;
  290. $ret[] = $temp;
  291. }
  292. }
  293. return $ret;
  294. }
  295. private function _apiStartBackup($config)
  296. {
  297. // Get the passed configuration values
  298. $defConfig = array(
  299. 'profile' => 1,
  300. 'description' => '',
  301. 'comment' => ''
  302. );
  303. $config = array_merge($defConfig, $config);
  304. foreach($config as $key => $value) {
  305. if(!array_key_exists($key, $defConfig)) unset($config[$key]);
  306. }
  307. extract($config);
  308. // Nuke the factory
  309. AEFactory::nuke();
  310. // Set the profile
  311. $profile = (int)$profile;
  312. if(!is_numeric($profile)) $profile = 1;
  313. $session = JFactory::getSession();
  314. $session->set('profile', $profile, 'akeeba');
  315. AEPlatform::getInstance()->load_configuration($profile);
  316. // Use the default description if none specified
  317. if(empty($description))
  318. {
  319. jimport('joomla.utilities.date');
  320. $dateNow = new JDate();
  321. /*
  322. $user = JFactory::getUser();
  323. $userTZ = $user->getParam('timezone',0);
  324. $dateNow->setOffset($userTZ);
  325. */
  326. $description = JText::_('BACKUP_DEFAULT_DESCRIPTION').' '.$dateNow->format(JText::_('DATE_FORMAT_LC2'), true);
  327. }
  328. // Start the backup
  329. AECoreKettenrad::reset(array(
  330. 'maxrun' => 0
  331. ));
  332. AEUtilTempvars::reset(AKEEBA_BACKUP_ORIGIN);
  333. $kettenrad = AECoreKettenrad::load(AKEEBA_BACKUP_ORIGIN);
  334. $options = array(
  335. 'description' => $description,
  336. 'comment' => $comment,
  337. 'tag' => AKEEBA_BACKUP_ORIGIN
  338. );
  339. $kettenrad->setup($options); // Setting up the engine
  340. $array = $kettenrad->tick(); // Initializes the init domain
  341. AECoreKettenrad::save(AKEEBA_BACKUP_ORIGIN);
  342. $array = $kettenrad->getStatusArray();
  343. if($array['Error'] != '')
  344. {
  345. // A backup error had occurred. Why are we here?!
  346. $this->status = self::STATUS_ERROR;
  347. $this->encapsulation = self::ENCAPSULATION_RAW;
  348. return 'A backup error had occurred: '.$array['Error'];
  349. }
  350. else
  351. {
  352. $statistics = AEFactory::getStatistics();
  353. $array['BackupID'] = $statistics->getId();
  354. $array['HasRun'] = 1; // Force the backup to go on.
  355. return $array;
  356. }
  357. }
  358. private function _apiStartSRPBackup($config)
  359. {
  360. // Get the passed configuration values
  361. $defConfig = array(
  362. 'tag' => 'restorepoint',
  363. 'type' => 'component',
  364. 'name' => 'akeeba',
  365. 'group' => '',
  366. 'customdirs' => array(),
  367. 'extraprefixes' => array(),
  368. 'customtables' => array(),
  369. 'skiptables' => array(),
  370. 'xmlname' => '',
  371. );
  372. $config = array_merge($defConfig, $config);
  373. foreach($config as $key => $value) {
  374. if(!array_key_exists($key, $defConfig)) unset($config[$key]);
  375. }
  376. // Fetch the extension's version information
  377. require_once JPATH_ADMINISTRATOR.'/components/com_akeeba/liveupdate/classes/xmlslurp.php';
  378. $slurp = new LiveUpdateXMLSlurp();
  379. $exttype = $config['type'];
  380. switch($exttype) {
  381. case 'component':
  382. $extname = 'com_';
  383. break;
  384. case 'module':
  385. $extname = 'mod_';
  386. break;
  387. case 'plugin':
  388. $extname = 'plg_';
  389. break;
  390. case 'template':
  391. $extname = 'tpl_';
  392. break;
  393. }
  394. $extname .= $config['name'];
  395. $info = $slurp->getInfo($extname, '');
  396. $configOverrides = array(
  397. 'akeeba.basic.archive_name' => 'restore-point-[DATE]-[TIME]',
  398. 'akeeba.basic.backup_type' => 'full',
  399. 'akeeba.basic.backup_type' => 'full',
  400. 'akeeba.advanced.archiver_engine' => 'jpa',
  401. 'akeeba.advanced.proc_engine' => 'none',
  402. 'akeeba.advanced.embedded_installer' => 'none',
  403. 'engine.archiver.common.dereference_symlinks' => true, // hopefully no extension has symlinks inside its own directories...
  404. 'core.filters.srp.type' => $config['type'],
  405. 'core.filters.srp.group' => $config['group'],
  406. 'core.filters.srp.name' => $config['name'],
  407. 'core.filters.srp.customdirs' => $config['customdirs'],
  408. 'core.filters.srp.customfiles' => $config['customfiles'],
  409. 'core.filters.srp.extraprefixes' => $config['extraprefixes'],
  410. 'core.filters.srp.customtables' => $config['customtables'],
  411. 'core.filters.srp.skiptables' => $config['skiptables'],
  412. 'core.filters.srp.langfiles' => $config['langfiles']
  413. );
  414. // Parse a local file stored in (backend)/assets/srpdefs/$extname.xml
  415. jimport('joomla.filesystem.file');
  416. $filename = JPATH_COMPONENT_ADMINISTRATOR.'/assets/srpdefs/'.$extname.'.xml';
  417. if(JFile::exists($filename)) {
  418. $xml = JFactory::getXMLParser('simple');
  419. if($xml->loadFile($filename)) {
  420. $extraConfig = $this->parseRestorePointXML($xml->document);
  421. if($extraConfig !== false) $this->mergeSRPConfig($configOverrides, $extraConfig);
  422. }
  423. unset($xml);
  424. }
  425. // Parse the extension's manifest file and look for a <restorepoint> tag
  426. if(!empty($info['xmlfile'])) {
  427. $xml = JFactory::getXMLParser('simple');
  428. if($xml->loadFile($info['xmlfile'])) {
  429. $restorepoint = $xml->document->getElementByPath('restorepoint');
  430. if($restorepoint) {
  431. $extraConfig = $this->parseRestorePointXML($restorepoint);
  432. if($extraConfig !== false) $this->mergeSRPConfig($configOverrides, $extraConfig);
  433. }
  434. }
  435. unset($restorepoint);
  436. unset($xml);
  437. }
  438. // Create an SRP descriptor
  439. $srpdescriptor = array(
  440. 'type' => $config['type'],
  441. 'name' => $config['name'],
  442. 'group' => $config['group'],
  443. 'version' => $info['version'],
  444. 'date' => $info['date']
  445. );
  446. // Set the description and comment
  447. $description = "System Restore Point - ".JText::_($exttype).": $extname";
  448. $comment = "---BEGIN SRP---\n".json_encode($srpdescriptor)."\n---END SRP---";
  449. $jpskey = '';
  450. // Set a custom finalization action queue
  451. $configOverrides['volatile.core.finalization.action_handlers'] = array(
  452. new AEFinalizationSrpquotas()
  453. );
  454. $configOverrides['volatile.core.finalization.action_queue'] = array(
  455. 'remove_temp_files',
  456. 'update_statistics',
  457. 'update_filesizes',
  458. 'apply_srp_quotas'
  459. );
  460. // Apply the configuration overrides, please
  461. $platform = AEPlatform::getInstance();
  462. $platform->configOverrides = $configOverrides;
  463. // Nuke the factory
  464. AEFactory::nuke();
  465. $profile = 1;
  466. $session = JFactory::getSession();
  467. $session->set('profile', $profile, 'akeeba');
  468. AEPlatform::getInstance()->load_configuration($profile);
  469. AEUtilTempvars::reset('restorepoint');
  470. $kettenrad = AECoreKettenrad::load('restorepoint');
  471. $options = array(
  472. 'description' => $description,
  473. 'comment' => $comment,
  474. 'tag' => 'restorepoint'
  475. );
  476. $kettenrad->setup($options); // Setting up the engine
  477. $kettenrad->tick();
  478. if( ($kettenrad->getState() != 'running') && ($tag == 'restorepoint') ) {
  479. $kettenrad->tick();
  480. }
  481. $kettenrad->resetWarnings(); // So as not to have duplicate warnings reports
  482. AECoreKettenrad::save($tag);
  483. $array = $kettenrad->getStatusArray();
  484. if($array['Error'] != '')
  485. {
  486. // A backup error had occurred. Why are we here?!
  487. $this->status = self::STATUS_ERROR;
  488. $this->encapsulation = self::ENCAPSULATION_RAW;
  489. return 'A backup error had occurred: '.$array['Error'];
  490. }
  491. else
  492. {
  493. $statistics = AEFactory::getStatistics();
  494. $array['BackupID'] = $statistics->getId();
  495. $array['HasRun'] = 1; // Force the backup to go on.
  496. return $array;
  497. }
  498. }
  499. private function _apiStepBackup($config)
  500. {
  501. $defConfig = array(
  502. 'profile' => null,
  503. 'tag' => AKEEBA_BACKUP_ORIGIN
  504. );
  505. $config = array_merge($defConfig, $config);
  506. extract($config);
  507. // Try to set the profile from the setup parameters
  508. if(!empty($profile))
  509. {
  510. $registry = AEFactory::getConfiguration();
  511. $session = JFactory::getSession();
  512. $session->set('profile', $profile, 'akeeba');
  513. }
  514. $kettenrad = AECoreKettenrad::load($tag);
  515. $registry = AEFactory::getConfiguration();
  516. $session = JFactory::getSession();
  517. $session->set('profile', $registry->activeProfile, 'akeeba');
  518. $array = $kettenrad->tick();
  519. $ret_array = $kettenrad->getStatusArray();
  520. $array['Progress'] = $ret_array['Progress'];
  521. AECoreKettenrad::save($tag);
  522. if($array['Error'] != '')
  523. {
  524. // A backup error had occurred. Why are we here?!
  525. $this->status = self::STATUS_ERROR;
  526. $this->encapsulation = self::ENCAPSULATION_RAW;
  527. return 'A backup error had occurred: '.$array['Error'];
  528. } elseif($array['HasRun'] == false) {
  529. AEFactory::nuke();
  530. AEUtilTempvars::reset();
  531. }
  532. return $array;
  533. }
  534. private function _apiListBackups($config)
  535. {
  536. $defConfig = array(
  537. 'from' => 0,
  538. 'limit' => 50
  539. );
  540. $config = array_merge($defConfig, $config);
  541. extract($config);
  542. require_once JPATH_COMPONENT_ADMINISTRATOR.'/models/statistics.php';
  543. $model = new AkeebaModelStatistics();
  544. return $model->getStatisticsListWithMeta(true);
  545. }
  546. private function _apiGetBackupInfo($config)
  547. {
  548. $defConfig = array(
  549. 'backup_id' => '0'
  550. );
  551. $config = array_merge($defConfig, $config);
  552. extract($config);
  553. // Get the basic statistics
  554. $record = AEPlatform::getInstance()->get_statistics($backup_id);
  555. // Get a list of filenames
  556. $backup_stats = AEPlatform::getInstance()->get_statistics($backup_id);
  557. // Backup record doesn't exist
  558. if(empty($backup_stats))
  559. {
  560. $this->status = self::STATUS_NOT_FOUND;
  561. $this->encapsulation = self::ENCAPSULATION_RAW;
  562. return 'Invalid backup record identifier';
  563. }
  564. $filenames = AEUtilStatistics::get_all_filenames($record);
  565. if(empty($filenames))
  566. {
  567. // Archives are not stored on the server or no files produced
  568. $record['filenames'] = array();
  569. }
  570. else
  571. {
  572. $filedata = array();
  573. $i = 0;
  574. // Get file sizes per part
  575. foreach($filenames as $file)
  576. {
  577. $i++;
  578. $size = @filesize($file);
  579. $size = is_numeric($size) ? $size : 0;
  580. $filedata[] = array(
  581. 'part' => $i,
  582. 'name' => basename($file),
  583. 'size' => $size
  584. );
  585. }
  586. // Add the file info to $record['filenames']
  587. $record['filenames'] = $filedata;
  588. }
  589. return $record;
  590. }
  591. private function _apiDownload($config)
  592. {
  593. $defConfig = array(
  594. 'backup_id' => 0,
  595. 'part_id' => 1,
  596. 'segment' => 1,
  597. 'chunk_size' => 1
  598. );
  599. $config = array_merge($defConfig, $config);
  600. extract($config);
  601. $backup_stats = AEPlatform::getInstance()->get_statistics($backup_id);
  602. if(empty($backup_stats))
  603. {
  604. // Backup record doesn't exist
  605. $this->status = self::STATUS_NOT_FOUND;
  606. $this->encapsulation = self::ENCAPSULATION_RAW;
  607. return 'Invalid backup record identifier';
  608. }
  609. $files = AEUtilStatistics::get_all_filenames($backup_stats);
  610. if( (count($files) < $part_id) || ($part_id <= 0) )
  611. {
  612. // Invalid part
  613. $this->status = self::STATUS_NOT_FOUND;
  614. $this->encapsulation = self::ENCAPSULATION_RAW;
  615. return 'Invalid backup part';
  616. }
  617. $file = $files[$part_id-1];
  618. $filesize = @filesize($file);
  619. $seekPos = $chunk_size * 1048756 * ($segment - 1);
  620. if($seekPos > $filesize) {
  621. // Trying to seek past end of file
  622. $this->status = self::STATUS_NOT_FOUND;
  623. $this->encapsulation = self::ENCAPSULATION_RAW;
  624. return 'Invalid segment';
  625. }
  626. $fp = fopen($file, 'rb');
  627. if($fp === false)
  628. {
  629. // Could not read file
  630. $this->status = self::STATUS_ERROR;
  631. $this->encapsulation = self::ENCAPSULATION_RAW;
  632. return 'Error reading backup archive';
  633. }
  634. rewind($fp);
  635. if(fseek($fp, $seekPos, SEEK_SET) === -1)
  636. {
  637. // Could not seek to position
  638. $this->status = self::STATUS_ERROR;
  639. $this->encapsulation = self::ENCAPSULATION_RAW;
  640. return 'Error reading specified segment';
  641. }
  642. $buffer = fread($fp, 1048756);
  643. if($buffer === false)
  644. {
  645. // Could not read
  646. $this->status = self::STATUS_ERROR;
  647. $this->encapsulation = self::ENCAPSULATION_RAW;
  648. return 'Error reading specified segment';
  649. }
  650. fclose($fp);
  651. switch($this->encapsulation)
  652. {
  653. case self::ENCAPSULATION_RAW:
  654. return base64_encode($buffer);
  655. break;
  656. case self::ENCAPSULATION_AESCTR128:
  657. $this->encapsulation = self::ENCAPSULATION_AESCBC128;
  658. return $buffer;
  659. break;
  660. case self::ENCAPSULATION_AESCTR256:
  661. $this->encapsulation = self::ENCAPSULATION_AESCBC256;
  662. return $buffer;
  663. break;
  664. default:
  665. // On encrypted comms the encryption will take care of transport encoding
  666. return $buffer;
  667. break;
  668. }
  669. }
  670. private function _apiDelete($config)
  671. {
  672. $defConfig = array(
  673. 'backup_id' => 0
  674. );
  675. $config = array_merge($defConfig, $config);
  676. extract($config);
  677. require_once JPATH_COMPONENT_ADMINISTRATOR.'/models/statistics.php';
  678. $model = new AkeebaModelStatistics();
  679. $model->setState('id', (int)$backup_id);
  680. $result = $model->delete();
  681. if(!$result)
  682. {
  683. $this->status = self::STATUS_ERROR;
  684. $this->encapsulation = self::ENCAPSULATION_RAW;
  685. return $model->getError();
  686. }
  687. else
  688. {
  689. return true;
  690. }
  691. }
  692. private function _apiDeleteFiles($config)
  693. {
  694. $defConfig = array(
  695. 'backup_id' => 0
  696. );
  697. $config = array_merge($defConfig, $config);
  698. extract($config);
  699. require_once JPATH_COMPONENT_ADMINISTRATOR.'/models/statistics.php';
  700. $model = new AkeebaModelStatistics();
  701. $model->setState('id', (int)$backup_id);
  702. $result = $model->deleteFile();
  703. if(!$result)
  704. {
  705. $this->status = self::STATUS_ERROR;
  706. $this->encapsulation = self::ENCAPSULATION_RAW;
  707. return $model->getError();
  708. }
  709. else
  710. {
  711. return true;
  712. }
  713. }
  714. private function _apiGetLog($config)
  715. {
  716. $defConfig = array(
  717. 'tag' => 'remote'
  718. );
  719. $config = array_merge($defConfig, $config);
  720. extract($config);
  721. $filename = AEUtilLogger::logName($tag);
  722. $buffer = file_get_contents($filename);
  723. switch($this->encapsulation)
  724. {
  725. case self::ENCAPSULATION_RAW:
  726. return base64_encode($buffer);
  727. break;
  728. case self::ENCAPSULATION_AESCTR128:
  729. $this->encapsulation = self::ENCAPSULATION_AESCBC128;
  730. return $buffer;
  731. break;
  732. case self::ENCAPSULATION_AESCTR256:
  733. $this->encapsulation = self::ENCAPSULATION_AESCBC256;
  734. return $buffer;
  735. break;
  736. default:
  737. // On encrypted comms the encryption will take care of transport encoding
  738. return $buffer;
  739. break;
  740. }
  741. }
  742. private function _apiDownloadDirect($config)
  743. {
  744. $defConfig = array(
  745. 'backup_id' => 0,
  746. 'part_id' => 1
  747. );
  748. $config = array_merge($defConfig, $config);
  749. extract($config);
  750. $backup_stats = AEPlatform::getInstance()->get_statistics($backup_id);
  751. if(empty($backup_stats))
  752. {
  753. // Backup record doesn't exist
  754. $this->status = self::STATUS_NOT_FOUND;
  755. $this->encapsulation = self::ENCAPSULATION_RAW;
  756. @ob_end_clean();
  757. header('HTTP/1.1 500 Invalid backup record identifier');
  758. flush();
  759. JFactory::getApplication()->close();
  760. }
  761. $files = AEUtilStatistics::get_all_filenames($backup_stats);
  762. if( (count($files) < $part_id) || ($part_id <= 0) )
  763. {
  764. // Invalid part
  765. $this->status = self::STATUS_NOT_FOUND;
  766. $this->encapsulation = self::ENCAPSULATION_RAW;
  767. @ob_end_clean();
  768. header('HTTP/1.1 500 Invalid backup part');
  769. flush();
  770. JFactory::getApplication()->close();
  771. }
  772. $filename = $files[$part_id-1];
  773. @clearstatcache();
  774. // For a certain unmentionable browser -- Thank you, Nooku, for the tip
  775. if(function_exists('ini_get') && function_exists('ini_set')) {
  776. if(ini_get('zlib.output_compression')) {
  777. ini_set('zlib.output_compression', 'Off');
  778. }
  779. }
  780. // Remove php's time limit -- Thank you, Nooku, for the tip
  781. if(function_exists('ini_get') && function_exists('set_time_limit')) {
  782. if(!ini_get('safe_mode') ) {
  783. @set_time_limit(0);
  784. }
  785. }
  786. $basename = @basename($filename);
  787. $filesize = @filesize($filename);
  788. $extension = strtolower(str_replace(".", "", strrchr($filename, ".")));
  789. while (@ob_end_clean());
  790. @clearstatcache();
  791. // Send MIME headers
  792. header('MIME-Version: 1.0');
  793. header('Content-Disposition: attachment; filename="'.$basename.'"');
  794. header('Content-Transfer-Encoding: binary');
  795. header('Accept-Ranges: bytes');
  796. switch($extension)
  797. {
  798. case 'zip':
  799. // ZIP MIME type
  800. header('Content-Type: application/zip');
  801. break;
  802. default:
  803. // Generic binary data MIME type
  804. header('Content-Type: application/octet-stream');
  805. break;
  806. }
  807. // Notify of filesize, if this info is available
  808. if($filesize > 0) header('Content-Length: '.@filesize($filename));
  809. // Disable caching
  810. header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
  811. header("Expires: 0");
  812. header('Pragma: no-cache');
  813. flush();
  814. if($filesize > 0)
  815. {
  816. // If the filesize is reported, use 1M chunks for echoing the data to the browser
  817. $blocksize = 1048756; //1M chunks
  818. $handle = @fopen($filename, "r");
  819. // Now we need to loop through the file and echo out chunks of file data
  820. if($handle !== false) while(!@feof($handle)){
  821. echo @fread($handle, $blocksize);
  822. @ob_flush();
  823. flush();
  824. }
  825. if($handle !== false) @fclose($handle);
  826. } else {
  827. // If the filesize is not reported, hope that readfile works
  828. @readfile($filename);
  829. }
  830. flush();
  831. JFactory::getApplication()->close();
  832. }
  833. private function _apiUpdateGetInformation($config)
  834. {
  835. $defConfig = array(
  836. 'force' => 0
  837. );
  838. $config = array_merge($defConfig, $config);
  839. extract($config);
  840. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/liveupdate.php';
  841. $updateInformation = LiveUpdate::getUpdateInformation($force);
  842. return (object)$updateInformation;
  843. }
  844. private function _apiUpdateDownload($config)
  845. {
  846. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/liveupdate.php';
  847. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/classes/model.php';
  848. // Do we need to update?
  849. $updateInformation = LiveUpdate::getUpdateInformation();
  850. if(!$updateInformation->hasUpdates) {
  851. return (object)array(
  852. 'download' => 0
  853. );
  854. }
  855. $model = new LiveupdateModel();
  856. $ret = $model->download();
  857. $session = JFactory::getSession();
  858. $target = $session->get('target', '', 'liveupdate');
  859. $tempdir = $session->get('tempdir', '', 'liveupdate');
  860. // Save the target and tempdir
  861. $session = JFactory::getSession();
  862. $session->set('profile', 1, 'akeeba');
  863. AEPlatform::getInstance()->load_configuration(1);
  864. $config = AEFactory::getConfiguration();
  865. $config->set('remoteupdate.target', $target);
  866. $config->set('remoteupdate.tempdir', $tempdir);
  867. AEPlatform::getInstance()->save_configuration(1);
  868. if(!$ret) {
  869. // An error ocurred :(
  870. $this->status = self::STATUS_ERROR;
  871. $this->encapsulation = self::ENCAPSULATION_RAW;
  872. return "Could not download the update package";
  873. } else {
  874. return (object)array(
  875. 'download' => 1
  876. );
  877. }
  878. }
  879. private function _apiUpdateExtract($config)
  880. {
  881. $session = JFactory::getSession();
  882. $session->set('profile', 1, 'akeeba');
  883. AEPlatform::getInstance()->load_configuration(1);
  884. $config = AEFactory::getConfiguration();
  885. $target = $config->get('remoteupdate.target', '');
  886. $tempdir = $config->get('remoteupdate.tempdir', '');
  887. $session = JFactory::getSession();
  888. $session->set('target', $target, 'liveupdate');
  889. $session->set('tempdir', $tempdir, 'liveupdate');
  890. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/liveupdate.php';
  891. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/classes/model.php';
  892. $model = new LiveupdateModel();
  893. $ret = $model->extract();
  894. jimport('joomla.filesystem.file');
  895. JFile::delete($target);
  896. if(!$ret) {
  897. // An error ocurred :(
  898. $this->status = self::STATUS_ERROR;
  899. $this->encapsulation = self::ENCAPSULATION_RAW;
  900. return "Could not extract the update package";
  901. } else {
  902. return (object)array(
  903. 'extract' => 1
  904. );
  905. }
  906. }
  907. private function _apiUpdateInstall($config) {
  908. $session = JFactory::getSession();
  909. $session->set('profile', 1, 'akeeba');
  910. AEPlatform::getInstance()->load_configuration(1);
  911. $config = AEFactory::getConfiguration();
  912. $target = $config->get('remoteupdate.target', '');
  913. $tempdir = $config->get('remoteupdate.tempdir', '');
  914. $session = JFactory::getSession();
  915. $session->set('tempdir', $tempdir, 'liveupdate');
  916. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/liveupdate.php';
  917. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/classes/model.php';
  918. $model = new LiveupdateModel();
  919. $ret = $model->install();
  920. if(!$ret) {
  921. // An error ocurred :(
  922. $this->status = self::STATUS_ERROR;
  923. $this->encapsulation = self::ENCAPSULATION_RAW;
  924. return "Could not install the update package";
  925. } else {
  926. return (object)array(
  927. 'install' => 1
  928. );
  929. }
  930. }
  931. private function _apiUpdateCleanup($config) {
  932. $session = JFactory::getSession();
  933. $session->set('profile', 1, 'akeeba');
  934. AEPlatform::getInstance()->load_configuration(1);
  935. $config = AEFactory::getConfiguration();
  936. $target = $config->get('remoteupdate.target', '');
  937. $tempdir = $config->get('remoteupdate.tempdir', '');
  938. $session = JFactory::getSession();
  939. $session->set('target', $target, 'liveupdate');
  940. $session->set('tempdir', $tempdir, 'liveupdate');
  941. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/liveupdate.php';
  942. require_once JPATH_ROOT.'/administrator/components/com_akeeba/liveupdate/classes/model.php';
  943. $model = new LiveupdateModel();
  944. $ret = $model->cleanup();
  945. jimport('joomla.filesystem.file');
  946. JFile::delete($target);
  947. $config->set('remoteupdate.target', null);
  948. $config->set('remoteupdate.tempdir', null);
  949. AEPlatform::getInstance()->save_configuration(1);
  950. return (object)array(
  951. 'cleanup' => 1
  952. );
  953. }
  954. /**
  955. * Encodes a variable to JSON using PEAR's Services_JSON
  956. * @param mixed $value The value to encode
  957. * @param int $options Encoding preferences flags
  958. * @return string The JSON-encoded string
  959. */
  960. private function json_encode($value, $options = 0) {
  961. $flags = SERVICES_JSON_LOOSE_TYPE;
  962. if( $options & JSON_FORCE_OBJECT ) $flags = 0;
  963. $encoder = new Akeeba_Services_JSON($flags);
  964. return $encoder->encode($value);
  965. }
  966. /**
  967. * Decodes a JSON string to a variable using PEAR's Services_JSON
  968. * @param string $value The JSON-encoded string
  969. * @param bool $assoc True to return an associative array instead of an object
  970. * @return mixed The decoded variable
  971. */
  972. private function json_decode($value, $assoc = false)
  973. {
  974. $flags = 0;
  975. if($assoc) $flags = SERVICES_JSON_LOOSE_TYPE;
  976. $decoder = new Akeeba_Services_JSON($flags);
  977. return $decoder->decode($value);
  978. }
  979. private function parseRestorePointXML($xml)
  980. {
  981. if(!count($xml->children())) return false;
  982. $ret = array();
  983. // 1. Group name -- core.filters.srp.group
  984. $group = $xml->getElementByPath('group');
  985. if($group) {
  986. $ret['core.filters.srp.group'] = $group->data();
  987. }
  988. // 2. Custom dirs -- core.filters.srp.customdirs
  989. $customdirs = $xml->getElementByPath('customdirs');
  990. if($customdirs) {
  991. $stack = array();
  992. if(count($customdirs->children())) {
  993. $children = $customdirs->children();
  994. foreach($children as $child) {
  995. if($child->name() == 'dir') {
  996. $stack[] = $child->data();
  997. }
  998. }
  999. }
  1000. if(!empty($stack)) $ret['core.filters.srp.customdirs'] = $stack;
  1001. }
  1002. // 3. Extra prefixes -- core.filters.srp.extraprefixes
  1003. $extraprefixes = $xml->getElementByPath('extraprefixes');
  1004. if($extraprefixes) {
  1005. $stack = array();
  1006. if(count($extraprefixes->children())) {
  1007. $children = $extraprefixes->children();
  1008. foreach($children as $child) {
  1009. if($child->name() == 'prefix') {
  1010. $stack[] = $child->data();
  1011. }
  1012. }
  1013. }
  1014. if(!empty($stack)) $ret['core.filters.srp.extraprefixes'] = $stack;
  1015. }
  1016. // 4. Custom tables -- core.filters.srp.customtables
  1017. $customtables = $xml->getElementByPath('customtables');
  1018. if($customtables) {
  1019. $stack = array();
  1020. if(count($customtables->children())) {
  1021. $children = $customtables->children();
  1022. foreach($children as $child) {
  1023. if($child->name() == 'table') {
  1024. $stack[] = $child->data();
  1025. }
  1026. }
  1027. }
  1028. if(!empty($stack)) $ret['core.filters.srp.customtables'] = $stack;
  1029. }
  1030. // 5. Skip tables -- core.filters.srp.skiptables
  1031. $skiptables = $xml->getElementByPath('skiptables');
  1032. if($skiptables) {
  1033. $stack = array();
  1034. if(count($skiptables->children())) {
  1035. $children = $skiptables->children();
  1036. foreach($children as $child) {
  1037. if($child->name() == 'table') {
  1038. $stack[] = $child->data();
  1039. }
  1040. }
  1041. }
  1042. if(!empty($stack)) $ret['core.filters.srp.skiptables'] = $stack;
  1043. }
  1044. // 6. Language files -- core.filters.srp.langfiles
  1045. $langfiles = $xml->getElementByPath('langfiles');
  1046. if($langfiles) {
  1047. $stack = array();
  1048. if(count($langfiles->children())) {
  1049. $children = $langfiles->children();
  1050. foreach($children as $child) {
  1051. if($child->name() == 'lang') {
  1052. $stack[] = $child->data();
  1053. }
  1054. }
  1055. }
  1056. if(!empty($stack)) $ret['core.filters.srp.langfiles'] = $stack;
  1057. }
  1058. // 7. Custom files -- core.filters.srp.customfiles
  1059. $customfiles = $xml->getElementByPath('customfiles');
  1060. if($customfiles) {
  1061. $stack = array();
  1062. if(count($customfiles->children())) {
  1063. $children = $customfiles->children();
  1064. foreach($children as $child) {
  1065. if($child->name() == 'file') {
  1066. $stack[] = $child->data();
  1067. }
  1068. }
  1069. }
  1070. if(!empty($stack)) $ret['core.filters.srp.customfiles'] = $stack;
  1071. }
  1072. if(empty($ret)) return false;
  1073. return $ret;
  1074. }
  1075. private function mergeSRPConfig(&$config, $extraConfig)
  1076. {
  1077. foreach($config as $key => $value) {
  1078. if(array_key_exists($key, $extraConfig)) {
  1079. if(is_array($value) && is_array($extraConfig[$key])) {
  1080. $config[$key] = array_merge($extraConfig[$key], $value);
  1081. }
  1082. }
  1083. }
  1084. }
  1085. }