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

/administrator/components/com_akeeba/models/confwiz.php

https://bitbucket.org/kraymitchell/apex
PHP | 487 lines | 305 code | 55 blank | 127 comment | 75 complexity | cb177110bf7aa1194963a0e52ae3ce9c 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. * @since 1.3
  7. */
  8. // Protect from unauthorized access
  9. defined('_JEXEC') or die();
  10. /**
  11. * The Configuration Wizard model class
  12. */
  13. class AkeebaModelConfwiz extends FOFModel
  14. {
  15. /**
  16. * Attempts to automatically figure out where on Earth the output and
  17. * temporary directories should point, adjusting their permissions should
  18. * it be necessary.
  19. *
  20. * @param $dontRecurse int Used internally. Always skip this parameter when calling this method.
  21. * @return bool True if we could fix the directories
  22. */
  23. public function autofixDirectories( $dontRecurse = 0 )
  24. {
  25. // Get the profile ID
  26. $profile_id = AEPlatform::getInstance()->get_active_profile();
  27. // Get the output and temporary directory
  28. $aeconfig = AEFactory::getConfiguration();
  29. $outdir = $aeconfig->get('akeeba.basic.output_directory','');
  30. $fixTemp = false; $fixOut = false;
  31. if(is_dir($outdir)) {
  32. // Test the writability of the directory
  33. $filename = $outdir.'/test.dat';
  34. $fixOut = !@file_put_contents($filename,'test');
  35. if(!$fixOut) {
  36. // Directory writable, remove the temp file
  37. @unlink($filename);
  38. } else {
  39. // Try to chmod the directory
  40. $this->chmod($outdir, 511);
  41. // Repeat the test
  42. $fixOut = !@file_put_contents($filename,'test');
  43. if(!$fixOut) {
  44. // Directory writable, remove the temp file
  45. @unlink($filename);
  46. }
  47. }
  48. } else {
  49. $fixOut = true;
  50. }
  51. // Do I have to fix the output directory?
  52. if($fixOut && ($dontRecurse < 1)) {
  53. $aeconfig->set('akeeba.basic.output_directory','[DEFAULT_OUTPUT]');
  54. AEPlatform::getInstance()->save_configuration($profile_id);
  55. // After fixing the directory, run ourselves again
  56. return $this->autofixDirectories(1);
  57. } elseif($fixOut) {
  58. // If we reached this point after recursing, we can't fix the permissions
  59. // and the user has to RTFM and fix the issue!
  60. return false;
  61. }
  62. return true;
  63. }
  64. /**
  65. * Creates a temporary file of a specific size
  66. * @param $blocks int How many 128Kb blocks to write. Common values: 1, 2, 4, 16, 40, 80, 81
  67. * @param $tempdir
  68. * @return unknown_type
  69. */
  70. public function createTempFile($blocks = 1, $tempdir = null)
  71. {
  72. if(empty($tempdir)) {
  73. $aeconfig = AEFactory::getConfiguration();
  74. $tempdir = $aeconfig->get('akeeba.basic.output_directory','');
  75. }
  76. $sixtyfourBytes = '012345678901234567890123456789012345678901234567890123456789ABCD';
  77. $oneKilo = ''; $oneBlock = '';
  78. for($i = 0; $i < 16; $i++) $oneKilo .= $sixtyfourBytes;
  79. for($i = 0; $i < 128; $i++) $oneBlock .= $oneKilo;
  80. $filename = $tempdir.'/test.dat';
  81. @unlink($filename);
  82. $fp = @fopen($filename, 'w');
  83. if($fp !== false) {
  84. for($i = 0; $i < $blocks; $i++) {
  85. if(!@fwrite($fp, $oneBlock)) {
  86. @fclose($fp);
  87. @unlink($filename);
  88. return false;
  89. }
  90. }
  91. @fclose($fp);
  92. @unlink($filename);
  93. } else {
  94. return false;
  95. }
  96. return true;
  97. }
  98. /**
  99. * Sleeps for a given amount of time. Returns false if the sleep time requested is over
  100. * the maximum execution time.
  101. * @param $secondsDelay int Seconds to sleep
  102. * @return bool
  103. */
  104. public function doNothing($secondsDelay = 1)
  105. {
  106. // Try to get the maximum execution time and PHP memory limit
  107. if(function_exists('ini_get')) {
  108. $maxexec = ini_get("max_execution_time");
  109. $memlimit = ini_get("memory_limit");
  110. } else {
  111. $maxexec = 14;
  112. $memlimit = 16777216;
  113. }
  114. if(!is_numeric($maxexec) || ($maxexec == 0)) $maxexec = 10; // Unknown time limit; suppose 10s
  115. if($maxexec > 180) $maxexec = 10; // Some servers report silly values, i.e. 30000, which Do Not Work??? :(
  116. // Sometimes memlimit comes with the M or K suffixes. Parse them.
  117. if(is_string($memlimit)) {
  118. $memlimit = strtoupper(trim(str_replace(' ','',$memlimit)));
  119. if( substr($memlimit,-1) == 'K' ) {
  120. $memlimit = 1024 * substr($memlimit, 0, -1);
  121. } elseif( substr($memlimit,-1) == 'M' ) {
  122. $memlimit = 1024 * 1024 * substr($memlimit, 0, -1);
  123. } elseif( substr($memlimit,-1) == 'G' ) {
  124. $memlimit = 1024 * 1024 * 1024 * substr($memlimit, 0, -1);
  125. }
  126. }
  127. if(!is_numeric($memlimit) || ($memlimit === 0)) $memlimit = 16777216; // Unknown limit; suppose 16M
  128. if($memlimit === -1) $memlimit = 134217728; // No limit; suppose 128M
  129. // Get the current memory usage (or assume one if the metric is not available)
  130. if(function_exists('memory_get_usage')) {
  131. $usedram = memory_get_usage();
  132. } else {
  133. $usedram = 7340032; // Suppose 7M of RAM usage if the metric isn't available;
  134. }
  135. // If we have less than 12M of RAM left, we have to limit ourselves to 6 seconds of
  136. // total execution time (emperical value!) to avoid deadly memory outages
  137. if( ($memlimit - $usedram) < 12582912 ) {
  138. $maxexec = 5;
  139. }
  140. // If the requested delay is over the $maxexec limit (minus one second
  141. // for application initialization), return false
  142. if($secondsDelay > ($maxexec - 1)) return false;
  143. // And now, run the silly loop to simulate the CPU usage pattern during backup
  144. $start = microtime(true);
  145. $loop = true;
  146. while($loop) {
  147. // Waste some CPU power...
  148. for($i = 1; $i < 1000; $i++) {
  149. $j = exp(($i * $i / 123 * 864) >> 2);
  150. }
  151. // ... then sleep for a millisec
  152. usleep(1000);
  153. // Are we done yet?
  154. $end = microtime(true);
  155. if( ($end - $start) >= $secondsDelay ) $loop = false;
  156. }
  157. return true;
  158. }
  159. /**
  160. * This method will analyze your database tables and try to figure out the optimal
  161. * batch row count value so that its select doesn't return excessive amounts of data.
  162. * The only drawback is that it only accounts for the core tables, but that is usually
  163. * a good metric.
  164. */
  165. public function analyzeDatabase()
  166. {
  167. // Try to get the PHP memory limit
  168. if(function_exists('ini_get')) {
  169. $memlimit = ini_get("memory_limit");
  170. } else {
  171. $memlimit = 16777216;
  172. }
  173. if(!is_numeric($memlimit) || ($memlimit === 0)) $memlimit = 16777216; // Unknown limit; suppose 16M
  174. if($memlimit === -1) $memlimit = 134217728; // No limit; suppose 128M
  175. // Get the current memory usage (or assume one if the metric is not available)
  176. if(function_exists('memory_get_usage')) {
  177. $usedram = memory_get_usage();
  178. } else {
  179. $usedram = 7340032; // Suppose 7M of RAM usage if the metric isn't available;
  180. }
  181. // How much RAM can I spare? It's the max memory minus the current memory usage and an extra
  182. // 5Mb to cater for Akeeba Engine's peak memory usage
  183. $max_mem_usage = $usedram + 5242880;
  184. $ram_allowance = $memlimit - $max_mem_usage;
  185. // If the RAM allowance is too low, assume 2Mb (emperical value)
  186. if($ram_allowance < 2097152) $ram_allowance = 2097152;
  187. $rowCount = 100;
  188. // Get the table statistics
  189. $db = $this->getDBO();
  190. if(strtolower(substr($db->name,0,5)) == 'mysql') {
  191. // The table analyzer only works with MySQL
  192. $db->setQuery( "SHOW TABLE STATUS" );
  193. $metrics = $db->loadAssocList();
  194. if($db->getError()) {
  195. // SHOW TABLE STATUS is not supported. I'll assume a safe-ish value of 100 rows
  196. $rowCount = 100;
  197. } else {
  198. $rowCount = 1000; // Start with the default value
  199. if(!empty($metrics)) {
  200. foreach($metrics as $table) {
  201. // Get row count and average row length
  202. $rows = $table['Rows'];
  203. $avg_len = $table['Avg_row_length'];
  204. // Calculate RAM usage with current settings
  205. $max_rows = min($rows, $rowCount);
  206. $max_ram_current = $max_rows * $avg_len;
  207. if($max_ram_current > $ram_allowance) {
  208. // Hm... over the allowance. Let's try to find a sweet spot.
  209. $max_rows = (int)($ram_allowance / $avg_len);
  210. // Quantize to multiple of 10 rows
  211. $max_rows = 10 * floor($max_rows / 10);
  212. // Can't really go below 10 rows / batch
  213. if($max_rows < 10) $max_rows = 10;
  214. // If the new setting is less than the current $rowCount, use the new setting
  215. if($rowCount > $max_rows) $rowCount = $max_rows;
  216. }
  217. }
  218. }
  219. }
  220. }
  221. $profile_id = AEPlatform::getInstance()->get_active_profile();
  222. $config = AEFactory::getConfiguration();
  223. // Save the row count per batch
  224. $config->set('engine.dump.common.batchsize',$rowCount);
  225. // Enable SQL file splitting - default is 512K unless the part_size is less than that!
  226. $splitsize = 524288;
  227. $partsize = $config->get('engine.archiver.common.part_size',0);
  228. if( ($partsize < $splitsize) && !empty($partsize) ) $splitsize = $partsize;
  229. $config->set('engine.dump.common.splitsize',$splitsize);
  230. // Enable extended INSERTs
  231. $config->set('engine.dump.common.extended_inserts','1');
  232. // Determine optimal packet size (must be at most two fifths of the split size and no more than 256K)
  233. $packet_size = (int)$splitsize * 0.4;
  234. if($packet_size > 262144) $packet_size = 262144;
  235. $config->set('engine.dump.common.packet_size',$packet_size);
  236. // Enable the native dump engine
  237. $config->set('akeeba.advanced.dump_engine','native');
  238. AEPlatform::getInstance()->save_configuration($profile_id);
  239. }
  240. /**
  241. * Changes the permissions of a file or directory using direct file access or
  242. * Joomla!'s FTP layer, whichever works
  243. * @param $path string Absolute path to the file/dir to chmod
  244. * @param $mode The permissions mode to apply
  245. * @return bool True on success
  246. */
  247. private function chmod($path, $mode)
  248. {
  249. if(is_string($mode))
  250. {
  251. $mode = octdec($mode);
  252. }
  253. // Initialize variables
  254. jimport('joomla.client.helper');
  255. $ftpOptions = JClientHelper::getCredentials('ftp');
  256. // Check to make sure the path valid and clean
  257. $path = JPath::clean($path);
  258. if ($ftpOptions['enabled'] == 1) {
  259. // Connect the FTP client
  260. jimport('joomla.client.ftp');
  261. if(version_compare(JVERSION,'3.0','ge')) {
  262. $ftp = JClientFTP::getInstance(
  263. $ftpOptions['host'], $ftpOptions['port'], null,
  264. $ftpOptions['user'], $ftpOptions['pass']
  265. );
  266. } else {
  267. $ftp = JFTP::getInstance(
  268. $ftpOptions['host'], $ftpOptions['port'], null,
  269. $ftpOptions['user'], $ftpOptions['pass']
  270. );
  271. }
  272. }
  273. if(@chmod($path, $mode))
  274. {
  275. $ret = true;
  276. } elseif ($ftpOptions['enabled'] == 1) {
  277. // Translate path and delete
  278. jimport('joomla.client.ftp');
  279. $path = JPath::clean(str_replace(JPATH_ROOT, $ftpOptions['root'], $path), '/');
  280. // FTP connector throws an error
  281. $ret = $ftp->chmod($path, $mode);
  282. } else {
  283. return false;
  284. }
  285. }
  286. public function runAjax()
  287. {
  288. $act = $this->getState('act');
  289. if(method_exists($this, $act)) {
  290. $result = $this->$act();
  291. } else {
  292. $result = false;
  293. }
  294. return $result;
  295. }
  296. private function ping()
  297. {
  298. return true;
  299. }
  300. /**
  301. * Try different values of minimum execution time
  302. */
  303. private function minexec()
  304. {
  305. $seconds = FOFInput::getFloat('seconds', '0.5', $this->input);
  306. if ($seconds < 1) {
  307. usleep($seconds*1000000);
  308. } else {
  309. sleep($seconds);
  310. }
  311. return true;
  312. }
  313. /**
  314. * Saves the AJAX preference and the minimum execution time
  315. * @return bool
  316. */
  317. private function applyminexec()
  318. {
  319. // Get the user parameters
  320. $iframes = FOFInput::getInt('iframes', 0, $this->input);
  321. $minexec = FOFInput::getFloat('minecxec', 2.0, $this->input);
  322. // Save the settings
  323. $profile_id = AEPlatform::getInstance()->get_active_profile();
  324. $config = AEFactory::getConfiguration();
  325. $config->set('akeeba.basic.useiframe', $iframes);
  326. $config->set('akeeba.tuning.min_exec_time', $minexec * 1000);
  327. AEPlatform::getInstance()->save_configuration($profile_id);
  328. // Enforce the min exec time
  329. $timer = AEFactory::getTimer();
  330. $timer->enforce_min_exec_time(false);
  331. // Done!
  332. return true;
  333. }
  334. /**
  335. * Try to make the directories writable or provide a set of writable directories
  336. * @return bool
  337. */
  338. private function directories()
  339. {
  340. $timer = AEFactory::getTimer();
  341. if(interface_exists('JModel')) {
  342. $model = JModelLegacy::getInstance('Confwiz','AkeebaModel');
  343. } else {
  344. $model = JModel::getInstance('Confwiz','AkeebaModel');
  345. }
  346. $result = $model->autofixDirectories();
  347. $timer->enforce_min_exec_time(false);
  348. return $result;
  349. }
  350. /**
  351. * Analyze the database and apply optimized database dump settings
  352. * @return bool
  353. */
  354. private function database()
  355. {
  356. $timer = AEFactory::getTimer();
  357. if(interface_exists('JModel')) {
  358. $model = JModelLegacy::getInstance('Confwiz','AkeebaModel');
  359. } else {
  360. $model = JModel::getInstance('Confwiz','AkeebaModel');
  361. }
  362. $model->analyzeDatabase();
  363. $timer->enforce_min_exec_time(false);
  364. return true;
  365. }
  366. /**
  367. * Try to apply a specific maximum execution time setting
  368. * @return bool
  369. */
  370. private function maxexec()
  371. {
  372. $seconds = FOFInput::getInt('seconds', 30, $this->input);
  373. $timer = AEFactory::getTimer();
  374. if(interface_exists('JModel')) {
  375. $model = JModelLegacy::getInstance('Confwiz','AkeebaModel');
  376. } else {
  377. $model = JModel::getInstance('Confwiz','AkeebaModel');
  378. }
  379. $result = $model->doNothing($seconds);
  380. $timer->enforce_min_exec_time(false);
  381. return $result;
  382. }
  383. /**
  384. * Save a specific maximum execution time preference to the database
  385. * @return bool
  386. */
  387. private function applymaxexec()
  388. {
  389. // Get the user parameters
  390. $maxexec = FOFInput::getInt('seconds', 2, $this->input);
  391. // Save the settings
  392. $timer = AEFactory::getTimer();
  393. $profile_id = AEPlatform::getInstance()->get_active_profile();
  394. $config = AEFactory::getConfiguration();
  395. $config->set('akeeba.tuning.max_exec_time', $maxexec);
  396. $config->set('akeeba.tuning.run_time_bias','75');
  397. $config->set('akeeba.advanced.scan_engine', 'smart');
  398. // @todo This should be an option (choose format, zip/jpa)
  399. $config->set('akeeba.advanced.archiver_engine', 'jpa');
  400. AEPlatform::getInstance()->save_configuration($profile_id);
  401. // Enforce the min exec time
  402. $timer->enforce_min_exec_time(false);
  403. // Done!
  404. return true;
  405. }
  406. /**
  407. * Creates a dummy file of a given size. Remember to give the filesize
  408. * query parameter in bytes!
  409. */
  410. public function partsize()
  411. {
  412. $timer = AEFactory::getTimer();
  413. $blocks = FOFInput::getInt('blocks', 1, $this->input);
  414. if(interface_exists('JModel')) {
  415. $model = JModelLegacy::getInstance('Confwiz','AkeebaModel');
  416. } else {
  417. $model = JModel::getInstance('Confwiz','AkeebaModel');
  418. }
  419. $result = $model->createTempFile($blocks);
  420. if($result) {
  421. // Save the setting
  422. if($blocks > 200) $blocks = 16383; // Over 25Mb = 2Gb minus 128Kb limit (safe setting for PHP not running on 64-bit Linux)
  423. $profile_id = AEPlatform::getInstance()->get_active_profile();
  424. $config = AEFactory::getConfiguration();
  425. $config->set('engine.archiver.common.part_size', $blocks * 128 * 1024);
  426. AEPlatform::getInstance()->save_configuration($profile_id);
  427. }
  428. // Enforce the min exec time
  429. $timer->enforce_min_exec_time(false);
  430. return $result;
  431. }
  432. }