PageRenderTime 74ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/componentlib.class.php

https://bitbucket.org/ciceidev/cicei_moodle_conditional_activities
PHP | 526 lines | 236 code | 38 blank | 252 comment | 55 complexity | 4cea565844ae1022a6fb96b83d054a4b MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause
  1. <?php //$Id$
  2. ///////////////////////////////////////////////////////////////////////////
  3. // //
  4. // NOTICE OF COPYRIGHT //
  5. // //
  6. // Moodle - Modular Object-Oriented Dynamic Learning Environment //
  7. // http://moodle.com //
  8. // //
  9. // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
  10. // (C) 2001-3001 Eloy Lafuente (stronk7) http://contiento.com //
  11. // //
  12. // This program is free software; you can redistribute it and/or modify //
  13. // it under the terms of the GNU General Public License as published by //
  14. // the Free Software Foundation; either version 2 of the License, or //
  15. // (at your option) any later version. //
  16. // //
  17. // This program is distributed in the hope that it will be useful, //
  18. // but WITHOUT ANY WARRANTY; without even the implied warranty of //
  19. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
  20. // GNU General Public License for more details: //
  21. // //
  22. // http://www.gnu.org/copyleft/gpl.html //
  23. // //
  24. ///////////////////////////////////////////////////////////////////////////
  25. // This library includes all the necessary stuff to use the one-click
  26. // download and install feature of Moodle, used to keep updated some
  27. // items like languages, pear, enviroment... i.e, components.
  28. //
  29. // It has been developed harcoding some important limits that are
  30. // explained below:
  31. // - It only can check, download and install items under moodledata.
  32. // - Every downloadeable item must be one zip file.
  33. // - The zip file root content must be 1 directory, i.e, everything
  34. // is stored under 1 directory.
  35. // - Zip file name and root directory must have the same name (but
  36. // the .zip extension, of course).
  37. // - Every .zip file must be defined in one .md5 file that will be
  38. // stored in the same remote directory than the .zip file.
  39. // - The name of such .md5 file is free, although it's recommended
  40. // to use the same name than the .zip (that's the default
  41. // assumption if no specified).
  42. // - Every remote .md5 file will be a comma separated (CVS) file where each
  43. // line will follow this format:
  44. // - Field 1: name of the zip file (without extension). Mandatory.
  45. // - Field 2: md5 of the zip file. Mandatory.
  46. // - Field 3: whatever you want (or need). Optional.
  47. // -Every local .md5 file will:
  48. // - Have the zip file name (without the extension) plus -md5
  49. // - Will reside inside the expanded zip file dir
  50. // - Will contain the md5 od the latest installed component
  51. // With all these details present, the process will perform this tasks:
  52. // - Perform security checks. Only admins are allowed to use this for now.
  53. // - Read the .md5 file from source (1).
  54. // - Extract the correct line for the .zip being requested.
  55. // - Compare it with the local .md5 file (2).
  56. // - If different:
  57. // - Download the newer .zip file from source.
  58. // - Calculate its md5 (3).
  59. // - Compare (1) and (3).
  60. // - If equal:
  61. // - Delete old directory.
  62. // - Uunzip the newer .zip file.
  63. // - Create the new local .md5 file.
  64. // - Delete the .zip file.
  65. // - If different:
  66. // - ERROR. Old package won't be modified. We shouldn't
  67. // reach here ever.
  68. // - If component download is not possible, a message text about how to do
  69. // the process manually (remotedownloaderror) must be displayed to explain it.
  70. //
  71. // General Usage:
  72. //
  73. // To install one component:
  74. //
  75. // require_once($CFG->libdir.'/componentlib.class.php');
  76. // if ($cd = new component_installer('http://download.moodle.org', 'lang16',
  77. // 'es_utf8.zip', 'languages.md5', 'lang')) {
  78. // $status = $cd->install(); //returns COMPONENT_(ERROR | UPTODATE | INSTALLED)
  79. // switch ($status) {
  80. // case COMPONENT_ERROR:
  81. // if ($cd->get_error() == 'remotedownloaderror') {
  82. // $a = new stdClass();
  83. // $a->url = 'http://download.moodle.org/lang16/es_utf8.zip';
  84. // $a->dest= $CFG->dataroot.'/lang';
  85. // print_error($cd->get_error(), 'error', '', $a);
  86. // } else {
  87. // print_error($cd->get_error(), 'error');
  88. // }
  89. // break;
  90. // case COMPONENT_UPTODATE:
  91. // //Print error string or whatever you want to do
  92. // break;
  93. // case COMPONENT_INSTALLED:
  94. // //Print/do whatever you want
  95. // break;
  96. // default:
  97. // //We shouldn't reach this point
  98. // }
  99. // } else {
  100. // //We shouldn't reach this point
  101. // }
  102. //
  103. // To switch of component (maintaining the rest of settings):
  104. //
  105. // $status = $cd->change_zip_file('en_utf8.zip'); //returns boolean false on error
  106. //
  107. // To retrieve all the components in one remote md5 file
  108. //
  109. // $components = $cd->get_all_components_md5(); //returns boolean false on error, array instead
  110. //
  111. // To check if current component needs to be updated
  112. //
  113. // $status = $cd->need_upgrade(); //returns COMPONENT_(ERROR | UPTODATE | NEEDUPDATE)
  114. //
  115. // To get the 3rd field of the md5 file (optional)
  116. //
  117. // $field = $cd->get_extra_md5_field(); //returns string (empty if not exists)
  118. //
  119. // For all the error situations the $cd->get_error() method should return always the key of the
  120. // error to be retrieved by one standard get_string() call against the error.php lang file.
  121. //
  122. // That's all!
  123. global $CFG;
  124. require_once($CFG->libdir.'/filelib.php');
  125. // Some needed constants
  126. define('COMPONENT_ERROR', 0);
  127. define('COMPONENT_UPTODATE', 1);
  128. define('COMPONENT_NEEDUPDATE', 2);
  129. define('COMPONENT_INSTALLED', 3);
  130. /**
  131. * This class is used to check, download and install items from
  132. * download.moodle.org to the moodledata directory. It always
  133. * return true/false in all their public methods to say if
  134. * execution has ended succesfuly or not. If there is any problem
  135. * its getError() method can be called, returning one error string
  136. * to be used with the standard get/print_string() functions.
  137. */
  138. class component_installer {
  139. var $sourcebase; /// Full http URL, base for downloadable items
  140. var $zippath; /// Relative path (from sourcebase) where the
  141. /// downloadeable item resides.
  142. var $zipfilename; /// Name of the .zip file to be downloaded
  143. var $md5filename; /// Name of the .md5 file to be read
  144. var $componentname;/// Name of the component. Must be the zip name without
  145. /// the extension. And it defines a lot of things:
  146. /// the md5 line to search for, the default m5 file name
  147. /// and the name of the root dir stored inside the zip file
  148. var $destpath; /// Relative path (from moodledata) where the .zip
  149. /// file will be expanded.
  150. var $errorstring; /// Latest error produced. It will contain one lang string key.
  151. var $extramd5info; /// Contents of the optional third field in the .md5 file.
  152. var $requisitesok; /// Flag to see if requisites check has been passed ok.
  153. var $cachedmd5components; /// Array of cached components to avoid to
  154. /// download the same md5 file more than once per request.
  155. /**
  156. * Standard constructor of the class. It will initialize all attributes.
  157. * without performing any check at all.
  158. *
  159. * @param string Full http URL, base for downloadeable items
  160. * @param string Relative path (from sourcebase) where the
  161. * downloadeable item resides
  162. * @param string Name of the .zip file to be downloaded
  163. * @param string Name of the .md5 file to be read (default '' = same
  164. * than zipfilename)
  165. * @param string Relative path (from moodledata) where the .zip file will
  166. * be expanded (default='' = moodledataitself)
  167. * @return object
  168. */
  169. function component_installer ($sourcebase, $zippath, $zipfilename, $md5filename='', $destpath='') {
  170. $this->sourcebase = $sourcebase;
  171. $this->zippath = $zippath;
  172. $this->zipfilename = $zipfilename;
  173. $this->md5filename = $md5filename;
  174. $this->componentname= '';
  175. $this->destpath = $destpath;
  176. $this->errorstring = '';
  177. $this->extramd5info = '';
  178. $this->requisitesok = false;
  179. $this->cachedmd5components = array();
  180. $this->check_requisites();
  181. }
  182. /**
  183. * This function will check if everything is properly set to begin
  184. * one installation. Also, it will check for required settings
  185. * and will fill everything as needed.
  186. *
  187. * @return boolean true/false (plus detailed error in errorstring)
  188. */
  189. function check_requisites() {
  190. global $CFG;
  191. $this->requisitesok = false;
  192. /// Check that everything we need is present
  193. if (empty($this->sourcebase) || empty($this->zippath) || empty($this->zipfilename)) {
  194. $this->errorstring='missingrequiredfield';
  195. return false;
  196. }
  197. /// Check for correct sourcebase (this will be out in the future)
  198. if ($this->sourcebase != 'http://download.moodle.org') {
  199. $this->errorstring='wrongsourcebase';
  200. return false;
  201. }
  202. /// Check the zip file is a correct one (by extension)
  203. if (stripos($this->zipfilename, '.zip') === false) {
  204. $this->errorstring='wrongzipfilename';
  205. return false;
  206. }
  207. /// Check that exists under dataroot
  208. if (!empty($this->destpath)) {
  209. if (!file_exists($CFG->dataroot.'/'.$this->destpath)) {
  210. $this->errorstring='wrongdestpath';
  211. return false;
  212. }
  213. }
  214. /// Calculate the componentname
  215. $pos = stripos($this->zipfilename, '.zip');
  216. $this->componentname = substr($this->zipfilename, 0, $pos);
  217. /// Calculate md5filename if it's empty
  218. if (empty($this->md5filename)) {
  219. $this->md5filename = $this->componentname.'.md5';
  220. }
  221. /// Set the requisites passed flag
  222. $this->requisitesok = true;
  223. return true;
  224. }
  225. /**
  226. * This function will perform the full installation if needed, i.e.
  227. * compare md5 values, download, unzip, install and regenerate
  228. * local md5 file
  229. *
  230. * @return int COMPONENT_(ERROR | UPTODATE | INSTALLED)
  231. */
  232. function install() {
  233. global $CFG;
  234. /// Check requisites are passed
  235. if (!$this->requisitesok) {
  236. return COMPONENT_ERROR;
  237. }
  238. /// Confirm we need upgrade
  239. if ($this->need_upgrade() === COMPONENT_ERROR) {
  240. return COMPONENT_ERROR;
  241. } else if ($this->need_upgrade() === COMPONENT_UPTODATE) {
  242. $this->errorstring='componentisuptodate';
  243. return COMPONENT_UPTODATE;
  244. }
  245. /// Create temp directory if necesary
  246. if (!make_upload_directory('temp', false)) {
  247. $this->errorstring='cannotcreatetempdir';
  248. return COMPONENT_ERROR;
  249. }
  250. /// Download zip file and save it to temp
  251. $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->zipfilename;
  252. $zipfile= $CFG->dataroot.'/temp/'.$this->zipfilename;
  253. if($contents = download_file_content($source)) {
  254. if ($file = fopen($zipfile, 'w')) {
  255. if (!fwrite($file, $contents)) {
  256. fclose($file);
  257. $this->errorstring='cannotsavezipfile';
  258. return COMPONENT_ERROR;
  259. }
  260. } else {
  261. $this->errorstring='cannotsavezipfile';
  262. return COMPONENT_ERROR;
  263. }
  264. fclose($file);
  265. } else {
  266. $this->errorstring='cannotdownloadzipfile';
  267. return COMPONENT_ERROR;
  268. }
  269. /// Calculate its md5
  270. $new_md5 = md5($contents);
  271. /// Compare it with the remote md5 to check if we have the correct zip file
  272. if (!$remote_md5 = $this->get_component_md5()) {
  273. return COMPONENT_ERROR;
  274. }
  275. if ($new_md5 != $remote_md5) {
  276. $this->errorstring='downloadedfilecheckfailed';
  277. return COMPONENT_ERROR;
  278. }
  279. /// Move current revision to a safe place
  280. $destinationdir = $CFG->dataroot.'/'.$this->destpath;
  281. $destinationcomponent = $destinationdir.'/'.$this->componentname;
  282. @remove_dir($destinationcomponent.'_old'); //Deleting possible old components before
  283. @rename ($destinationcomponent, $destinationcomponent.'_old'); //Moving to a safe place
  284. /// Unzip new version
  285. if (!unzip_file($zipfile, $destinationdir, false)) {
  286. /// Error so, go back to the older
  287. @remove_dir($destinationcomponent);
  288. @rename ($destinationcomponent.'_old', $destinationcomponent);
  289. $this->errorstring='cannotunzipfile';
  290. return COMPONENT_ERROR;
  291. }
  292. /// Delete old component version
  293. @remove_dir($destinationcomponent.'_old');
  294. /// Create local md5
  295. if ($file = fopen($destinationcomponent.'/'.$this->componentname.'.md5', 'w')) {
  296. if (!fwrite($file, $new_md5)) {
  297. fclose($file);
  298. $this->errorstring='cannotsavemd5file';
  299. return COMPONENT_ERROR;
  300. }
  301. } else {
  302. $this->errorstring='cannotsavemd5file';
  303. return COMPONENT_ERROR;
  304. }
  305. fclose($file);
  306. /// Delete temp zip file
  307. @unlink($zipfile);
  308. return COMPONENT_INSTALLED;
  309. }
  310. /**
  311. * This function will detect if remote component needs to be installed
  312. * because it's different from the local one
  313. *
  314. * @return int COMPONENT_(ERROR | UPTODATE | NEEDUPDATE)
  315. */
  316. function need_upgrade() {
  317. /// Check requisites are passed
  318. if (!$this->requisitesok) {
  319. return COMPONENT_ERROR;
  320. }
  321. /// Get local md5
  322. $local_md5 = $this->get_local_md5();
  323. /// Get remote md5
  324. if (!$remote_md5 = $this->get_component_md5()) {
  325. return COMPONENT_ERROR;
  326. }
  327. /// Return result
  328. if ($local_md5 == $remote_md5) {
  329. return COMPONENT_UPTODATE;
  330. } else {
  331. return COMPONENT_NEEDUPDATE;
  332. }
  333. }
  334. /**
  335. * This function will change the zip file to install on the fly
  336. * to allow the class to process different components of the
  337. * same md5 file without intantiating more objects.
  338. *
  339. * @param string New zip filename to process
  340. * @return boolean true/false
  341. */
  342. function change_zip_file($newzipfilename) {
  343. $this->zipfilename = $newzipfilename;
  344. return $this->check_requisites();
  345. }
  346. /**
  347. * This function will get the local md5 value of the installed
  348. * component.
  349. *
  350. * @return string md5 of the local component (false on error)
  351. */
  352. function get_local_md5() {
  353. global $CFG;
  354. /// Check requisites are passed
  355. if (!$this->requisitesok) {
  356. return false;
  357. }
  358. $return_value = 'needtobeinstalled'; /// Fake value to force new installation
  359. /// Calculate source to read
  360. $source = $CFG->dataroot.'/'.$this->destpath.'/'.$this->componentname.'/'.$this->componentname.'.md5';
  361. /// Read md5 value stored (if exists)
  362. if (file_exists($source)) {
  363. if ($temp = file_get_contents($source)) {
  364. $return_value = $temp;
  365. }
  366. }
  367. return $return_value;
  368. }
  369. /**
  370. * This function will download the specified md5 file, looking for the
  371. * current componentname, returning its md5 field and storing extramd5info
  372. * if present. Also it caches results to cachedmd5components for better
  373. * performance in the same request.
  374. *
  375. * @return mixed md5 present in server (or false if error)
  376. */
  377. function get_component_md5() {
  378. /// Check requisites are passed
  379. if (!$this->requisitesok) {
  380. return false;
  381. }
  382. /// Get all components of md5 file
  383. if (!$comp_arr = $this->get_all_components_md5()) {
  384. if (empty($this->errorstring)) {
  385. $this->errorstring='cannotdownloadcomponents';
  386. }
  387. return false;
  388. }
  389. /// Search for the componentname component
  390. if (empty($comp_arr[$this->componentname]) || !$component = $comp_arr[$this->componentname]) {
  391. $this->errorstring='cannotfindcomponent';
  392. return false;
  393. }
  394. /// Check we have a valid md5
  395. if (empty($component[1]) || strlen($component[1]) != 32) {
  396. $this->errorstring='invalidmd5';
  397. return false;
  398. }
  399. /// Set the extramd5info field
  400. if (!empty($component[2])) {
  401. $this->extramd5info = $component[2];
  402. }
  403. return $component[1];
  404. }
  405. /**
  406. * This function allows you to retrieve the complete array of components found in
  407. * the md5filename
  408. *
  409. * @return array array of components in md5 file or false if error
  410. */
  411. function get_all_components_md5() {
  412. /// Check requisites are passed
  413. if (!$this->requisitesok) {
  414. return false;
  415. }
  416. /// Initialize components array
  417. $comp_arr = array();
  418. /// Define and retrieve the full md5 file
  419. $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->md5filename;
  420. /// Check if we have downloaded the md5 file before (per request cache)
  421. if (!empty($this->cachedmd5components[$source])) {
  422. $comp_arr = $this->cachedmd5components[$source];
  423. } else {
  424. /// Not downloaded, let's do it now
  425. $availablecomponents = array();
  426. if ($contents = download_file_content($source)) {
  427. /// Split text into lines
  428. $lines=preg_split('/\r?\n/',$contents);
  429. /// Each line will be one component
  430. foreach($lines as $line) {
  431. $availablecomponents[] = split(',', $line);
  432. }
  433. /// If no components have been found, return error
  434. if (empty($availablecomponents)) {
  435. $this->errorstring='cannotdownloadcomponents';
  436. return false;
  437. }
  438. /// Build an associative array of components for easily search
  439. /// applying trim to avoid linefeeds and other...
  440. $comp_arr = array();
  441. foreach ($availablecomponents as $component) {
  442. /// Avoid sometimes empty lines
  443. if (empty($component[0])) {
  444. continue;
  445. }
  446. $component[0]=trim($component[0]);
  447. if (!empty($component[1])) {
  448. $component[1]=trim($component[1]);
  449. }
  450. if (!empty($component[2])) {
  451. $component[2]=trim($component[2]);
  452. }
  453. $comp_arr[$component[0]] = $component;
  454. }
  455. /// Cache components
  456. $this->cachedmd5components[$source] = $comp_arr;
  457. } else {
  458. /// Return error
  459. $this->errorstring='remotedownloaderror';
  460. return false;
  461. }
  462. }
  463. /// If there is no commponents or erros found, error
  464. if (!empty($this->errorstring)) {
  465. return false;
  466. } else if (empty($comp_arr)) {
  467. $this->errorstring='cannotdownloadcomponents';
  468. return false;
  469. }
  470. return $comp_arr;
  471. }
  472. /**
  473. * This function returns the errorstring
  474. *
  475. * @return string the error string
  476. */
  477. function get_error() {
  478. return $this->errorstring;
  479. }
  480. /** This function returns the extramd5 field (optional in md5 file)
  481. *
  482. * @return string the extramd5 field
  483. */
  484. function get_extra_md5_field() {
  485. return $this->extramd5info;
  486. }
  487. } /// End of component_installer class
  488. ?>