PageRenderTime 24ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/componentlib.class.php

https://bitbucket.org/ngmares/moodle
PHP | 812 lines | 355 code | 78 blank | 379 comment | 88 complexity | 08a93955a74afb2d00f7063a7a6fa4f4 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, MPL-2.0-no-copyleft-exception, GPL-3.0, Apache-2.0, BSD-3-Clause
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * This library includes all the necessary stuff to use the one-click
  18. * download and install feature of Moodle, used to keep updated some
  19. * items like languages, pear, enviroment... i.e, components.
  20. *
  21. * It has been developed harcoding some important limits that are
  22. * explained below:
  23. * - It only can check, download and install items under moodledata.
  24. * - Every downloadeable item must be one zip file.
  25. * - The zip file root content must be 1 directory, i.e, everything
  26. * is stored under 1 directory.
  27. * - Zip file name and root directory must have the same name (but
  28. * the .zip extension, of course).
  29. * - Every .zip file must be defined in one .md5 file that will be
  30. * stored in the same remote directory than the .zip file.
  31. * - The name of such .md5 file is free, although it's recommended
  32. * to use the same name than the .zip (that's the default
  33. * assumption if no specified).
  34. * - Every remote .md5 file will be a comma separated (CVS) file where each
  35. * line will follow this format:
  36. * - Field 1: name of the zip file (without extension). Mandatory.
  37. * - Field 2: md5 of the zip file. Mandatory.
  38. * - Field 3: whatever you want (or need). Optional.
  39. * -Every local .md5 file will:
  40. * - Have the zip file name (without the extension) plus -md5
  41. * - Will reside inside the expanded zip file dir
  42. * - Will contain the md5 od the latest installed component
  43. * With all these details present, the process will perform this tasks:
  44. * - Perform security checks. Only admins are allowed to use this for now.
  45. * - Read the .md5 file from source (1).
  46. * - Extract the correct line for the .zip being requested.
  47. * - Compare it with the local .md5 file (2).
  48. * - If different:
  49. * - Download the newer .zip file from source.
  50. * - Calculate its md5 (3).
  51. * - Compare (1) and (3).
  52. * - If equal:
  53. * - Delete old directory.
  54. * - Uunzip the newer .zip file.
  55. * - Create the new local .md5 file.
  56. * - Delete the .zip file.
  57. * - If different:
  58. * - ERROR. Old package won't be modified. We shouldn't
  59. * reach here ever.
  60. * - If component download is not possible, a message text about how to do
  61. * the process manually (remotedownloaderror) must be displayed to explain it.
  62. *
  63. * General Usage:
  64. *
  65. * To install one component:
  66. * <code>
  67. * require_once($CFG->libdir.'/componentlib.class.php');
  68. * if ($cd = new component_installer('http://download.moodle.org', 'langpack/2.0',
  69. * 'es.zip', 'languages.md5', 'lang')) {
  70. * $status = $cd->install(); //returns COMPONENT_(ERROR | UPTODATE | INSTALLED)
  71. * switch ($status) {
  72. * case COMPONENT_ERROR:
  73. * if ($cd->get_error() == 'remotedownloaderror') {
  74. * $a = new stdClass();
  75. * $a->url = 'http://download.moodle.org/langpack/2.0/es.zip';
  76. * $a->dest= $CFG->dataroot.'/lang';
  77. * print_error($cd->get_error(), 'error', '', $a);
  78. * } else {
  79. * print_error($cd->get_error(), 'error');
  80. * }
  81. * break;
  82. * case COMPONENT_UPTODATE:
  83. * //Print error string or whatever you want to do
  84. * break;
  85. * case COMPONENT_INSTALLED:
  86. * //Print/do whatever you want
  87. * break;
  88. * default:
  89. * //We shouldn't reach this point
  90. * }
  91. * } else {
  92. * //We shouldn't reach this point
  93. * }
  94. * </code>
  95. *
  96. * To switch of component (maintaining the rest of settings):
  97. * <code>
  98. * $status = $cd->change_zip_file('en.zip'); //returns boolean false on error
  99. * </code>
  100. *
  101. * To retrieve all the components in one remote md5 file
  102. * <code>
  103. * $components = $cd->get_all_components_md5(); //returns boolean false on error, array instead
  104. * </code>
  105. *
  106. * To check if current component needs to be updated
  107. * <code>
  108. * $status = $cd->need_upgrade(); //returns COMPONENT_(ERROR | UPTODATE | NEEDUPDATE)
  109. * </code>
  110. *
  111. * To get the 3rd field of the md5 file (optional)
  112. * <code>
  113. * $field = $cd->get_extra_md5_field(); //returns string (empty if not exists)
  114. * </code>
  115. *
  116. * For all the error situations the $cd->get_error() method should return always the key of the
  117. * error to be retrieved by one standard get_string() call against the error.php lang file.
  118. *
  119. * That's all!
  120. *
  121. * @package core
  122. * @copyright (C) 2001-3001 Eloy Lafuente (stronk7) {@link http://contiento.com}
  123. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  124. */
  125. defined('MOODLE_INTERNAL') || die();
  126. /**
  127. * @global object $CFG
  128. * @name $CFG
  129. */
  130. global $CFG;
  131. require_once($CFG->libdir.'/filelib.php');
  132. // Some needed constants
  133. define('COMPONENT_ERROR', 0);
  134. define('COMPONENT_UPTODATE', 1);
  135. define('COMPONENT_NEEDUPDATE', 2);
  136. define('COMPONENT_INSTALLED', 3);
  137. /**
  138. * This class is used to check, download and install items from
  139. * download.moodle.org to the moodledata directory.
  140. *
  141. * It always return true/false in all their public methods to say if
  142. * execution has ended succesfuly or not. If there is any problem
  143. * its getError() method can be called, returning one error string
  144. * to be used with the standard get/print_string() functions.
  145. *
  146. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  147. * @package moodlecore
  148. */
  149. class component_installer {
  150. /**
  151. * @var string
  152. */
  153. var $sourcebase; /// Full http URL, base for downloadable items
  154. var $zippath; /// Relative path (from sourcebase) where the
  155. /// downloadeable item resides.
  156. var $zipfilename; /// Name of the .zip file to be downloaded
  157. var $md5filename; /// Name of the .md5 file to be read
  158. var $componentname;/// Name of the component. Must be the zip name without
  159. /// the extension. And it defines a lot of things:
  160. /// the md5 line to search for, the default m5 file name
  161. /// and the name of the root dir stored inside the zip file
  162. var $destpath; /// Relative path (from moodledata) where the .zip
  163. /// file will be expanded.
  164. var $errorstring; /// Latest error produced. It will contain one lang string key.
  165. var $extramd5info; /// Contents of the optional third field in the .md5 file.
  166. var $requisitesok; /// Flag to see if requisites check has been passed ok.
  167. /**
  168. * @var array
  169. */
  170. var $cachedmd5components; /// Array of cached components to avoid to
  171. /// download the same md5 file more than once per request.
  172. /**
  173. * Standard constructor of the class. It will initialize all attributes.
  174. * without performing any check at all.
  175. *
  176. * @param string $sourcebase Full http URL, base for downloadeable items
  177. * @param string $zippath Relative path (from sourcebase) where the
  178. * downloadeable item resides
  179. * @param string $zipfilename Name of the .zip file to be downloaded
  180. * @param string $md5filename Name of the .md5 file to be read (default '' = same
  181. * than zipfilename)
  182. * @param string $destpath Relative path (from moodledata) where the .zip file will
  183. * be expanded (default='' = moodledataitself)
  184. * @return object
  185. */
  186. function component_installer ($sourcebase, $zippath, $zipfilename, $md5filename='', $destpath='') {
  187. $this->sourcebase = $sourcebase;
  188. $this->zippath = $zippath;
  189. $this->zipfilename = $zipfilename;
  190. $this->md5filename = $md5filename;
  191. $this->componentname= '';
  192. $this->destpath = $destpath;
  193. $this->errorstring = '';
  194. $this->extramd5info = '';
  195. $this->requisitesok = false;
  196. $this->cachedmd5components = array();
  197. $this->check_requisites();
  198. }
  199. /**
  200. * This function will check if everything is properly set to begin
  201. * one installation. Also, it will check for required settings
  202. * and will fill everything as needed.
  203. *
  204. * @global object
  205. * @return boolean true/false (plus detailed error in errorstring)
  206. */
  207. function check_requisites() {
  208. global $CFG;
  209. $this->requisitesok = false;
  210. /// Check that everything we need is present
  211. if (empty($this->sourcebase) || empty($this->zippath) || empty($this->zipfilename)) {
  212. $this->errorstring='missingrequiredfield';
  213. return false;
  214. }
  215. /// Check for correct sourcebase (this will be out in the future)
  216. if ($this->sourcebase != 'http://download.moodle.org') {
  217. $this->errorstring='wrongsourcebase';
  218. return false;
  219. }
  220. /// Check the zip file is a correct one (by extension)
  221. if (stripos($this->zipfilename, '.zip') === false) {
  222. $this->errorstring='wrongzipfilename';
  223. return false;
  224. }
  225. /// Check that exists under dataroot
  226. if (!empty($this->destpath)) {
  227. if (!file_exists($CFG->dataroot.'/'.$this->destpath)) {
  228. $this->errorstring='wrongdestpath';
  229. return false;
  230. }
  231. }
  232. /// Calculate the componentname
  233. $pos = stripos($this->zipfilename, '.zip');
  234. $this->componentname = substr($this->zipfilename, 0, $pos);
  235. /// Calculate md5filename if it's empty
  236. if (empty($this->md5filename)) {
  237. $this->md5filename = $this->componentname.'.md5';
  238. }
  239. /// Set the requisites passed flag
  240. $this->requisitesok = true;
  241. return true;
  242. }
  243. /**
  244. * This function will perform the full installation if needed, i.e.
  245. * compare md5 values, download, unzip, install and regenerate
  246. * local md5 file
  247. *
  248. * @global object
  249. * @uses COMPONENT_ERROR
  250. * @uses COMPONENT_UPTODATE
  251. * @uses COMPONENT_ERROR
  252. * @uses COMPONENT_INSTALLED
  253. * @return int COMPONENT_(ERROR | UPTODATE | INSTALLED)
  254. */
  255. function install() {
  256. global $CFG;
  257. /// Check requisites are passed
  258. if (!$this->requisitesok) {
  259. return COMPONENT_ERROR;
  260. }
  261. /// Confirm we need upgrade
  262. if ($this->need_upgrade() === COMPONENT_ERROR) {
  263. return COMPONENT_ERROR;
  264. } else if ($this->need_upgrade() === COMPONENT_UPTODATE) {
  265. $this->errorstring='componentisuptodate';
  266. return COMPONENT_UPTODATE;
  267. }
  268. /// Create temp directory if necesary
  269. if (!make_temp_directory('', false)) {
  270. $this->errorstring='cannotcreatetempdir';
  271. return COMPONENT_ERROR;
  272. }
  273. /// Download zip file and save it to temp
  274. $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->zipfilename;
  275. $zipfile= $CFG->tempdir.'/'.$this->zipfilename;
  276. if($contents = download_file_content($source)) {
  277. if ($file = fopen($zipfile, 'w')) {
  278. if (!fwrite($file, $contents)) {
  279. fclose($file);
  280. $this->errorstring='cannotsavezipfile';
  281. return COMPONENT_ERROR;
  282. }
  283. } else {
  284. $this->errorstring='cannotsavezipfile';
  285. return COMPONENT_ERROR;
  286. }
  287. fclose($file);
  288. } else {
  289. $this->errorstring='cannotdownloadzipfile';
  290. return COMPONENT_ERROR;
  291. }
  292. /// Calculate its md5
  293. $new_md5 = md5($contents);
  294. /// Compare it with the remote md5 to check if we have the correct zip file
  295. if (!$remote_md5 = $this->get_component_md5()) {
  296. return COMPONENT_ERROR;
  297. }
  298. if ($new_md5 != $remote_md5) {
  299. $this->errorstring='downloadedfilecheckfailed';
  300. return COMPONENT_ERROR;
  301. }
  302. /// Move current revision to a safe place
  303. $destinationdir = $CFG->dataroot.'/'.$this->destpath;
  304. $destinationcomponent = $destinationdir.'/'.$this->componentname;
  305. @remove_dir($destinationcomponent.'_old'); //Deleting possible old components before
  306. @rename ($destinationcomponent, $destinationcomponent.'_old'); //Moving to a safe place
  307. /// Unzip new version
  308. if (!unzip_file($zipfile, $destinationdir, false)) {
  309. /// Error so, go back to the older
  310. @remove_dir($destinationcomponent);
  311. @rename ($destinationcomponent.'_old', $destinationcomponent);
  312. $this->errorstring='cannotunzipfile';
  313. return COMPONENT_ERROR;
  314. }
  315. /// Delete old component version
  316. @remove_dir($destinationcomponent.'_old');
  317. /// Create local md5
  318. if ($file = fopen($destinationcomponent.'/'.$this->componentname.'.md5', 'w')) {
  319. if (!fwrite($file, $new_md5)) {
  320. fclose($file);
  321. $this->errorstring='cannotsavemd5file';
  322. return COMPONENT_ERROR;
  323. }
  324. } else {
  325. $this->errorstring='cannotsavemd5file';
  326. return COMPONENT_ERROR;
  327. }
  328. fclose($file);
  329. /// Delete temp zip file
  330. @unlink($zipfile);
  331. return COMPONENT_INSTALLED;
  332. }
  333. /**
  334. * This function will detect if remote component needs to be installed
  335. * because it's different from the local one
  336. *
  337. * @uses COMPONENT_ERROR
  338. * @uses COMPONENT_UPTODATE
  339. * @uses COMPONENT_NEEDUPDATE
  340. * @return int COMPONENT_(ERROR | UPTODATE | NEEDUPDATE)
  341. */
  342. function need_upgrade() {
  343. /// Check requisites are passed
  344. if (!$this->requisitesok) {
  345. return COMPONENT_ERROR;
  346. }
  347. /// Get local md5
  348. $local_md5 = $this->get_local_md5();
  349. /// Get remote md5
  350. if (!$remote_md5 = $this->get_component_md5()) {
  351. return COMPONENT_ERROR;
  352. }
  353. /// Return result
  354. if ($local_md5 == $remote_md5) {
  355. return COMPONENT_UPTODATE;
  356. } else {
  357. return COMPONENT_NEEDUPDATE;
  358. }
  359. }
  360. /**
  361. * This function will change the zip file to install on the fly
  362. * to allow the class to process different components of the
  363. * same md5 file without intantiating more objects.
  364. *
  365. * @param string $newzipfilename New zip filename to process
  366. * @return boolean true/false
  367. */
  368. function change_zip_file($newzipfilename) {
  369. $this->zipfilename = $newzipfilename;
  370. return $this->check_requisites();
  371. }
  372. /**
  373. * This function will get the local md5 value of the installed
  374. * component.
  375. *
  376. * @global object
  377. * @return bool|string md5 of the local component (false on error)
  378. */
  379. function get_local_md5() {
  380. global $CFG;
  381. /// Check requisites are passed
  382. if (!$this->requisitesok) {
  383. return false;
  384. }
  385. $return_value = 'needtobeinstalled'; /// Fake value to force new installation
  386. /// Calculate source to read
  387. $source = $CFG->dataroot.'/'.$this->destpath.'/'.$this->componentname.'/'.$this->componentname.'.md5';
  388. /// Read md5 value stored (if exists)
  389. if (file_exists($source)) {
  390. if ($temp = file_get_contents($source)) {
  391. $return_value = $temp;
  392. }
  393. }
  394. return $return_value;
  395. }
  396. /**
  397. * This function will download the specified md5 file, looking for the
  398. * current componentname, returning its md5 field and storing extramd5info
  399. * if present. Also it caches results to cachedmd5components for better
  400. * performance in the same request.
  401. *
  402. * @return mixed md5 present in server (or false if error)
  403. */
  404. function get_component_md5() {
  405. /// Check requisites are passed
  406. if (!$this->requisitesok) {
  407. return false;
  408. }
  409. /// Get all components of md5 file
  410. if (!$comp_arr = $this->get_all_components_md5()) {
  411. if (empty($this->errorstring)) {
  412. $this->errorstring='cannotdownloadcomponents';
  413. }
  414. return false;
  415. }
  416. /// Search for the componentname component
  417. if (empty($comp_arr[$this->componentname]) || !$component = $comp_arr[$this->componentname]) {
  418. $this->errorstring='cannotfindcomponent';
  419. return false;
  420. }
  421. /// Check we have a valid md5
  422. if (empty($component[1]) || strlen($component[1]) != 32) {
  423. $this->errorstring='invalidmd5';
  424. return false;
  425. }
  426. /// Set the extramd5info field
  427. if (!empty($component[2])) {
  428. $this->extramd5info = $component[2];
  429. }
  430. return $component[1];
  431. }
  432. /**
  433. * This function allows you to retrieve the complete array of components found in
  434. * the md5filename
  435. *
  436. * @return bool|array array of components in md5 file or false if error
  437. */
  438. function get_all_components_md5() {
  439. /// Check requisites are passed
  440. if (!$this->requisitesok) {
  441. return false;
  442. }
  443. /// Initialize components array
  444. $comp_arr = array();
  445. /// Define and retrieve the full md5 file
  446. $source = $this->sourcebase.'/'.$this->zippath.'/'.$this->md5filename;
  447. /// Check if we have downloaded the md5 file before (per request cache)
  448. if (!empty($this->cachedmd5components[$source])) {
  449. $comp_arr = $this->cachedmd5components[$source];
  450. } else {
  451. /// Not downloaded, let's do it now
  452. $availablecomponents = array();
  453. if ($contents = download_file_content($source)) {
  454. /// Split text into lines
  455. $lines=preg_split('/\r?\n/',$contents);
  456. /// Each line will be one component
  457. foreach($lines as $line) {
  458. $availablecomponents[] = explode(',', $line);
  459. }
  460. /// If no components have been found, return error
  461. if (empty($availablecomponents)) {
  462. $this->errorstring='cannotdownloadcomponents';
  463. return false;
  464. }
  465. /// Build an associative array of components for easily search
  466. /// applying trim to avoid linefeeds and other...
  467. $comp_arr = array();
  468. foreach ($availablecomponents as $component) {
  469. /// Avoid sometimes empty lines
  470. if (empty($component[0])) {
  471. continue;
  472. }
  473. $component[0]=trim($component[0]);
  474. if (!empty($component[1])) {
  475. $component[1]=trim($component[1]);
  476. }
  477. if (!empty($component[2])) {
  478. $component[2]=trim($component[2]);
  479. }
  480. $comp_arr[$component[0]] = $component;
  481. }
  482. /// Cache components
  483. $this->cachedmd5components[$source] = $comp_arr;
  484. } else {
  485. /// Return error
  486. $this->errorstring='remotedownloaderror';
  487. return false;
  488. }
  489. }
  490. /// If there is no commponents or erros found, error
  491. if (!empty($this->errorstring)) {
  492. return false;
  493. } else if (empty($comp_arr)) {
  494. $this->errorstring='cannotdownloadcomponents';
  495. return false;
  496. }
  497. return $comp_arr;
  498. }
  499. /**
  500. * This function returns the errorstring
  501. *
  502. * @return string the error string
  503. */
  504. function get_error() {
  505. return $this->errorstring;
  506. }
  507. /** This function returns the extramd5 field (optional in md5 file)
  508. *
  509. * @return string the extramd5 field
  510. */
  511. function get_extra_md5_field() {
  512. return $this->extramd5info;
  513. }
  514. } /// End of component_installer class
  515. /**
  516. * Language packs installer
  517. *
  518. * This class wraps the functionality provided by {@link component_installer}
  519. * and adds support for installing a set of language packs.
  520. *
  521. * Given an array of required language packs, this class fetches them all
  522. * and installs them. It detects eventual dependencies and installs
  523. * all parent languages, too.
  524. *
  525. * @copyright 2011 David Mudrak <david@moodle.com>
  526. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  527. */
  528. class lang_installer {
  529. /** lang pack was successfully downloaded and deployed */
  530. const RESULT_INSTALLED = 'installed';
  531. /** lang pack was up-to-date so no download was needed */
  532. const RESULT_UPTODATE = 'uptodate';
  533. /** there was a problem with downloading the lang pack */
  534. const RESULT_DOWNLOADERROR = 'downloaderror';
  535. /** @var array of languages to install */
  536. protected $queue = array();
  537. /** @var string the code of language being currently installed */
  538. protected $current;
  539. /** @var array of languages already installed by this instance */
  540. protected $done = array();
  541. /** @var string this Moodle major version */
  542. protected $version;
  543. /**
  544. * Prepare the installer
  545. *
  546. * @param string|array $langcode a code of the language to install
  547. */
  548. public function __construct($langcode = '') {
  549. global $CFG;
  550. $this->set_queue($langcode);
  551. $this->version = moodle_major_version(true);
  552. if (!empty($CFG->langotherroot) and $CFG->langotherroot !== $CFG->dataroot . '/lang') {
  553. debugging('The in-built language pack installer does not support alternative location ' .
  554. 'of languages root directory. You are supposed to install and update your language '.
  555. 'packs on your own.');
  556. }
  557. }
  558. /**
  559. * Sets the queue of language packs to be installed
  560. *
  561. * @param string|array $langcodes language code like 'cs' or a list of them
  562. */
  563. public function set_queue($langcodes) {
  564. if (is_array($langcodes)) {
  565. $this->queue = $langcodes;
  566. } else if (!empty($langcodes)) {
  567. $this->queue = array($langcodes);
  568. }
  569. }
  570. /**
  571. * Runs the installer
  572. *
  573. * This method calls {@link self::install_language_pack} for every language in the
  574. * queue. If a dependency is detected, the parent language is added to the queue.
  575. *
  576. * @return array results, array of self::RESULT_xxx constants indexed by language code
  577. */
  578. public function run() {
  579. $results = array();
  580. while ($this->current = array_shift($this->queue)) {
  581. if ($this->was_processed($this->current)) {
  582. // do not repeat yourself
  583. continue;
  584. }
  585. if ($this->current === 'en') {
  586. $this->mark_processed($this->current);
  587. continue;
  588. }
  589. $results[$this->current] = $this->install_language_pack($this->current);
  590. if (in_array($results[$this->current], array(self::RESULT_INSTALLED, self::RESULT_UPTODATE))) {
  591. if ($parentlang = $this->get_parent_language($this->current)) {
  592. if (!$this->is_queued($parentlang) and !$this->was_processed($parentlang)) {
  593. $this->add_to_queue($parentlang);
  594. }
  595. }
  596. }
  597. $this->mark_processed($this->current);
  598. }
  599. return $results;
  600. }
  601. /**
  602. * Returns the URL where a given language pack can be downloaded
  603. *
  604. * Alternatively, if the parameter is empty, returns URL of the page with the
  605. * list of all available language packs.
  606. *
  607. * @param string $langcode language code like 'cs' or empty for unknown
  608. * @return string URL
  609. */
  610. public function lang_pack_url($langcode = '') {
  611. if (empty($langcode)) {
  612. return 'http://download.moodle.org/langpack/'.$this->version.'/';
  613. } else {
  614. return 'http://download.moodle.org/download.php/langpack/'.$this->version.'/'.$langcode.'.zip';
  615. }
  616. }
  617. /**
  618. * Returns the list of available language packs from download.moodle.org
  619. *
  620. * @return array|bool false if can not download
  621. */
  622. public function get_remote_list_of_languages() {
  623. $source = 'http://download.moodle.org/langpack/' . $this->version . '/languages.md5';
  624. $availablelangs = array();
  625. if ($content = download_file_content($source)) {
  626. $alllines = explode("\n", $content);
  627. foreach($alllines as $line) {
  628. if (!empty($line)){
  629. $availablelangs[] = explode(',', $line);
  630. }
  631. }
  632. return $availablelangs;
  633. } else {
  634. return false;
  635. }
  636. }
  637. // Internal implementation /////////////////////////////////////////////////
  638. /**
  639. * Adds a language pack (or a list of them) to the queue
  640. *
  641. * @param string|array $langcodes code of the language to install or a list of them
  642. */
  643. protected function add_to_queue($langcodes) {
  644. if (is_array($langcodes)) {
  645. $this->queue = array_merge($this->queue, $langcodes);
  646. } else if (!empty($langcodes)) {
  647. $this->queue[] = $langcodes;
  648. }
  649. }
  650. /**
  651. * Checks if the given language is queued or if the queue is empty
  652. *
  653. * @example $installer->is_queued('es'); // is Spanish going to be installed?
  654. * @example $installer->is_queued(); // is there a language queued?
  655. *
  656. * @param string $langcode language code or empty string for "any"
  657. * @return boolean
  658. */
  659. protected function is_queued($langcode = '') {
  660. if (empty($langcode)) {
  661. return !empty($this->queue);
  662. } else {
  663. return in_array($langcode, $this->queue);
  664. }
  665. }
  666. /**
  667. * Checks if the given language has already been processed by this instance
  668. *
  669. * @see self::mark_processed()
  670. * @param string $langcode
  671. * @return boolean
  672. */
  673. protected function was_processed($langcode) {
  674. return isset($this->done[$langcode]);
  675. }
  676. /**
  677. * Mark the given language pack as processed
  678. *
  679. * @see self::was_processed()
  680. * @param string $langcode
  681. */
  682. protected function mark_processed($langcode) {
  683. $this->done[$langcode] = 1;
  684. }
  685. /**
  686. * Returns a parent language of the given installed language
  687. *
  688. * @param string $langcode
  689. * @return string parent language's code
  690. */
  691. protected function get_parent_language($langcode) {
  692. return get_parent_language($langcode);
  693. }
  694. /**
  695. * Perform the actual language pack installation
  696. *
  697. * @uses component_installer
  698. * @param string $langcode
  699. * @return int return status
  700. */
  701. protected function install_language_pack($langcode) {
  702. // initialise new component installer to process this language
  703. $installer = new component_installer('http://download.moodle.org', 'download.php/direct/langpack/' . $this->version,
  704. $langcode . '.zip', 'languages.md5', 'lang');
  705. if (!$installer->requisitesok) {
  706. throw new lang_installer_exception('installer_requisites_check_failed');
  707. }
  708. $status = $installer->install();
  709. if ($status == COMPONENT_ERROR) {
  710. if ($installer->get_error() === 'remotedownloaderror') {
  711. return self::RESULT_DOWNLOADERROR;
  712. } else {
  713. throw new lang_installer_exception($installer->get_error(), $langcode);
  714. }
  715. } else if ($status == COMPONENT_UPTODATE) {
  716. return self::RESULT_UPTODATE;
  717. } else if ($status == COMPONENT_INSTALLED) {
  718. return self::RESULT_INSTALLED;
  719. } else {
  720. throw new lang_installer_exception('unexpected_installer_result', $status);
  721. }
  722. }
  723. }
  724. /**
  725. * Exception thrown by {@link lang_installer}
  726. *
  727. * @copyright 2011 David Mudrak <david@moodle.com>
  728. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  729. */
  730. class lang_installer_exception extends moodle_exception {
  731. public function __construct($errorcode, $debuginfo = null) {
  732. parent::__construct($errorcode, 'error', '', null, $debuginfo);
  733. }
  734. }