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

/admin/report/customlang/locallib.php

https://github.com/mylescarrick/moodle
PHP | 514 lines | 307 code | 70 blank | 137 comment | 46 complexity | 09e66f9289d8c0530232c6f91a0e486f MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, 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. * Definition of classes used by Langugae customization admin report
  18. *
  19. * @package report
  20. * @subpackage customlang
  21. * @copyright 2010 David Mudrak <david@moodle.com>
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. /**
  26. * Provides various utilities to be used by the plugin
  27. *
  28. * All the public methods here are static ones, this class can not be instantiated
  29. */
  30. class report_customlang_utils {
  31. /** @var array cache of {@link self::list_components()} results */
  32. protected static $components = null;
  33. /**
  34. * This class can not be instantiated
  35. */
  36. private function __construct() {
  37. }
  38. /**
  39. * Returns a list of all components installed on the server
  40. *
  41. * @return array (string)legacyname => (string)frankenstylename
  42. */
  43. public static function list_components() {
  44. $list['moodle'] = 'core';
  45. $coresubsystems = get_core_subsystems();
  46. ksort($coresubsystems); // should be but just in case
  47. foreach ($coresubsystems as $name => $location) {
  48. if ($name != 'moodle.org') {
  49. $list[$name] = 'core_'.$name;
  50. }
  51. }
  52. $plugintypes = get_plugin_types();
  53. foreach ($plugintypes as $type => $location) {
  54. $pluginlist = get_plugin_list($type);
  55. foreach ($pluginlist as $name => $ununsed) {
  56. if ($type == 'mod') {
  57. if (array_key_exists($name, $list)) {
  58. throw new Exception('Activity module and core subsystem name collision');
  59. }
  60. $list[$name] = $type.'_'.$name;
  61. } else {
  62. $list[$type.'_'.$name] = $type.'_'.$name;
  63. }
  64. }
  65. }
  66. return $list;
  67. }
  68. /**
  69. * Updates the translator database with the strings from files
  70. *
  71. * This should be executed each time before going to the translation page
  72. *
  73. * @param string $lang language code to checkout
  74. */
  75. public static function checkout($lang) {
  76. global $DB;
  77. // make sure that all components are registered
  78. $current = $DB->get_records('report_customlang_components', null, 'name', 'name,version,id');
  79. foreach (self::list_components() as $component) {
  80. if (empty($current[$component])) {
  81. $record = new stdclass();
  82. $record->name = $component;
  83. if (!$version = get_component_version($component)) {
  84. $record->version = null;
  85. } else {
  86. $record->version = $version;
  87. }
  88. $DB->insert_record('report_customlang_components', $record);
  89. } elseif ($version = get_component_version($component)) {
  90. if (is_null($current[$component]->version) or ($version > $current[$component]->version)) {
  91. $DB->set_field('report_customlang_components', 'version', $version, array('id' => $current[$component]->id));
  92. }
  93. }
  94. }
  95. unset($current);
  96. // reload components and fetch their strings
  97. $stringman = get_string_manager();
  98. $components = $DB->get_records('report_customlang_components');
  99. foreach ($components as $component) {
  100. $sql = "SELECT stringid, id, lang, componentid, original, master, local, timemodified, timecustomized, outdated, modified
  101. FROM {report_customlang} s
  102. WHERE lang = ? AND componentid = ?
  103. ORDER BY stringid";
  104. $current = $DB->get_records_sql($sql, array($lang, $component->id));
  105. $english = $stringman->load_component_strings($component->name, 'en', true, true);
  106. if ($lang == 'en') {
  107. $master =& $english;
  108. } else {
  109. $master = $stringman->load_component_strings($component->name, $lang, true, true);
  110. }
  111. $local = $stringman->load_component_strings($component->name, $lang, true, false);
  112. foreach ($english as $stringid => $stringoriginal) {
  113. $stringmaster = isset($master[$stringid]) ? $master[$stringid] : null;
  114. $stringlocal = isset($local[$stringid]) ? $local[$stringid] : null;
  115. $now = time();
  116. if (isset($current[$stringid])) {
  117. $needsupdate = false;
  118. $currentoriginal = $current[$stringid]->original;
  119. $currentmaster = $current[$stringid]->master;
  120. $currentlocal = $current[$stringid]->local;
  121. if ($currentoriginal !== $stringoriginal or $currentmaster !== $stringmaster) {
  122. $needsupdate = true;
  123. $current[$stringid]->original = $stringoriginal;
  124. $current[$stringid]->master = $stringmaster;
  125. $current[$stringid]->timemodified = $now;
  126. $current[$stringid]->outdated = 1;
  127. }
  128. if ($stringmaster !== $stringlocal) {
  129. $needsupdate = true;
  130. $current[$stringid]->local = $stringlocal;
  131. $current[$stringid]->timecustomized = $now;
  132. }
  133. if ($needsupdate) {
  134. $DB->update_record('report_customlang', $current[$stringid]);
  135. continue;
  136. }
  137. } else {
  138. $record = new stdclass();
  139. $record->lang = $lang;
  140. $record->componentid = $component->id;
  141. $record->stringid = $stringid;
  142. $record->original = $stringoriginal;
  143. $record->master = $stringmaster;
  144. $record->timemodified = $now;
  145. $record->outdated = 0;
  146. if ($stringmaster !== $stringlocal) {
  147. $record->local = $stringlocal;
  148. $record->timecustomized = $now;
  149. } else {
  150. $record->local = null;
  151. $record->timecustomized = null;
  152. }
  153. $DB->insert_record('report_customlang', $record);
  154. }
  155. }
  156. }
  157. }
  158. /**
  159. * Exports the translator database into disk files
  160. *
  161. * @param mixed $lang language code
  162. */
  163. public static function checkin($lang) {
  164. global $DB, $USER, $CFG;
  165. require_once($CFG->libdir.'/filelib.php');
  166. if ($lang !== clean_param($lang, PARAM_LANG)) {
  167. return false;
  168. }
  169. // get all customized strings from updated components
  170. $sql = "SELECT s.*, c.name AS component
  171. FROM {report_customlang} s
  172. JOIN {report_customlang_components} c ON s.componentid = c.id
  173. WHERE s.lang = ?
  174. AND (s.local IS NOT NULL OR s.modified = 1)
  175. ORDER BY componentid, stringid";
  176. $strings = $DB->get_records_sql($sql, array($lang));
  177. $files = array();
  178. foreach ($strings as $string) {
  179. if (!is_null($string->local)) {
  180. $files[$string->component][$string->stringid] = $string->local;
  181. }
  182. }
  183. fulldelete(self::get_localpack_location($lang));
  184. foreach ($files as $component => $strings) {
  185. self::dump_strings($lang, $component, $strings);
  186. }
  187. $DB->set_field_select('report_customlang', 'modified', 0, 'lang = ?', array($lang));
  188. $sm = get_string_manager();
  189. $sm->reset_caches();
  190. }
  191. /**
  192. * Returns full path to the directory where local packs are dumped into
  193. *
  194. * @param string $lang language code
  195. * @return string full path
  196. */
  197. protected static function get_localpack_location($lang) {
  198. global $CFG;
  199. return $CFG->langlocalroot.'/'.$lang.'_local';
  200. }
  201. /**
  202. * Writes strings into a local language pack file
  203. *
  204. * @param string $component the name of the component
  205. * @param array $strings
  206. */
  207. protected static function dump_strings($lang, $component, $strings) {
  208. global $CFG;
  209. if ($lang !== clean_param($lang, PARAM_LANG)) {
  210. debugging('Unable to dump local strings for non-installed language pack .'.s($lang));
  211. return false;
  212. }
  213. if ($component !== clean_param($component, PARAM_SAFEDIR)) {
  214. throw new coding_exception('Incorrect component name');
  215. }
  216. if (!$filename = self::get_component_filename($component)) {
  217. debugging('Unable to find the filename for the component '.s($component));
  218. return false;
  219. }
  220. if ($filename !== clean_param($filename, PARAM_FILE)) {
  221. throw new coding_exception('Incorrect file name '.s($filename));
  222. }
  223. list($package, $subpackage) = normalize_component($component);
  224. $packageinfo = " * @package $package";
  225. if (!is_null($subpackage)) {
  226. $packageinfo .= "\n * @subpackage $subpackage";
  227. }
  228. $filepath = self::get_localpack_location($lang);
  229. $filepath = $filepath.'/'.$filename;
  230. if (!is_dir(dirname($filepath))) {
  231. check_dir_exists(dirname($filepath));
  232. }
  233. if (!$f = fopen($filepath, 'w')) {
  234. debugging('Unable to write '.s($filepath));
  235. return false;
  236. }
  237. fwrite($f, <<<EOF
  238. <?php
  239. // This file is part of Moodle - http://moodle.org/
  240. //
  241. // Moodle is free software: you can redistribute it and/or modify
  242. // it under the terms of the GNU General Public License as published by
  243. // the Free Software Foundation, either version 3 of the License, or
  244. // (at your option) any later version.
  245. //
  246. // Moodle is distributed in the hope that it will be useful,
  247. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  248. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  249. // GNU General Public License for more details.
  250. //
  251. // You should have received a copy of the GNU General Public License
  252. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  253. /**
  254. * Local language pack from $CFG->wwwroot
  255. *
  256. $packageinfo
  257. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  258. */
  259. defined('MOODLE_INTERNAL') || die();
  260. EOF
  261. );
  262. foreach ($strings as $stringid => $text) {
  263. if ($stringid !== clean_param($stringid, PARAM_STRINGID)) {
  264. debugging('Invalid string identifier '.s($stringid));
  265. continue;
  266. }
  267. fwrite($f, '$string[\'' . $stringid . '\'] = ');
  268. fwrite($f, var_export($text, true));
  269. fwrite($f, ";\n");
  270. }
  271. fclose($f);
  272. }
  273. /**
  274. * Returns the name of the file where the component's local strings should be exported into
  275. *
  276. * @param string $component normalized name of the component, eg 'core' or 'mod_workshop'
  277. * @return string|boolean filename eg 'moodle.php' or 'workshop.php', false if not found
  278. */
  279. protected static function get_component_filename($component) {
  280. if (is_null(self::$components)) {
  281. self::$components = self::list_components();
  282. }
  283. $return = false;
  284. foreach (self::$components as $legacy => $normalized) {
  285. if ($component === $normalized) {
  286. $return = $legacy.'.php';
  287. break;
  288. }
  289. }
  290. return $return;
  291. }
  292. /**
  293. * Returns the number of modified strings checked out in the translator
  294. *
  295. * @param string $lang language code
  296. * @return int
  297. */
  298. public static function get_count_of_modified($lang) {
  299. global $DB;
  300. return $DB->count_records('report_customlang', array('lang'=>$lang, 'modified'=>1));
  301. }
  302. /**
  303. * Saves filter data into a persistant storage such as user session
  304. *
  305. * @see self::load_filter()
  306. * @param stdclass $data filter values
  307. * @param stdclass $persistant storage object
  308. */
  309. public static function save_filter(stdclass $data, stdclass $persistant) {
  310. if (!isset($persistant->report_customlang_filter)) {
  311. $persistant->report_customlang_filter = array();
  312. }
  313. foreach ($data as $key => $value) {
  314. if ($key !== 'submit') {
  315. $persistant->report_customlang_filter[$key] = serialize($value);
  316. }
  317. }
  318. }
  319. /**
  320. * Loads the previously saved filter settings from a persistent storage
  321. *
  322. * @see self::save_filter()
  323. * @param stdclass $persistant storage object
  324. * @return stdclass filter data
  325. */
  326. public static function load_filter(stdclass $persistant) {
  327. $data = new stdclass();
  328. if (isset($persistant->report_customlang_filter)) {
  329. foreach ($persistant->report_customlang_filter as $key => $value) {
  330. $data->{$key} = unserialize($value);
  331. }
  332. }
  333. return $data;
  334. }
  335. }
  336. /**
  337. * Represents the action menu of the report
  338. */
  339. class report_customlang_menu implements renderable {
  340. /** @var menu items */
  341. protected $items = array();
  342. public function __construct(array $items = array()) {
  343. global $CFG;
  344. foreach ($items as $itemkey => $item) {
  345. $this->add_item($itemkey, $item['title'], $item['url'], empty($item['method']) ? 'post' : $item['method']);
  346. }
  347. }
  348. /**
  349. * Returns the menu items
  350. *
  351. * @return array (string)key => (object)[->(string)title ->(moodle_url)url ->(string)method]
  352. */
  353. public function get_items() {
  354. return $this->items;
  355. }
  356. /**
  357. * Adds item into the menu
  358. *
  359. * @param string $key item identifier
  360. * @param string $title localized action title
  361. * @param moodle_url $url action handler
  362. * @param string $method form method
  363. */
  364. public function add_item($key, $title, moodle_url $url, $method) {
  365. if (isset($this->items[$key])) {
  366. throw new coding_exception('Menu item already exists');
  367. }
  368. if (empty($title) or empty($key)) {
  369. throw new coding_exception('Empty title or item key not allowed');
  370. }
  371. $item = new stdclass();
  372. $item->title = $title;
  373. $item->url = $url;
  374. $item->method = $method;
  375. $this->items[$key] = $item;
  376. }
  377. }
  378. /**
  379. * Represents the translation tool
  380. */
  381. class report_customlang_translator implements renderable {
  382. /** @const int number of rows per page */
  383. const PERPAGE = 100;
  384. /** @var int total number of the rows int the table */
  385. public $numofrows = 0;
  386. /** @var moodle_url */
  387. public $handler;
  388. /** @var string language code */
  389. public $lang;
  390. /** @var int page to display, starting with page 0 */
  391. public $currentpage = 0;
  392. /** @var array of stdclass strings to display */
  393. public $strings = array();
  394. /** @var stdclass */
  395. protected $filter;
  396. public function __construct(moodle_url $handler, $lang, $filter, $currentpage = 0) {
  397. global $DB;
  398. $this->handler = $handler;
  399. $this->lang = $lang;
  400. $this->filter = $filter;
  401. $this->currentpage = $currentpage;
  402. if (empty($filter) or empty($filter->component)) {
  403. // nothing to do
  404. $this->currentpage = 1;
  405. return;
  406. }
  407. list($insql, $inparams) = $DB->get_in_or_equal($filter->component, SQL_PARAMS_NAMED);
  408. $csql = "SELECT COUNT(*)";
  409. $fsql = "SELECT s.id, s.*, c.name AS component";
  410. $sql = " FROM {report_customlang_components} c
  411. JOIN {report_customlang} s ON s.componentid = c.id
  412. WHERE s.lang = :lang
  413. AND c.name $insql";
  414. $params = array_merge(array('lang' => $lang), $inparams);
  415. if (!empty($filter->customized)) {
  416. $sql .= " AND s.local IS NOT NULL";
  417. }
  418. if (!empty($filter->modified)) {
  419. $sql .= " AND s.modified = 1";
  420. }
  421. if (!empty($filter->stringid)) {
  422. $sql .= " AND s.stringid = :stringid";
  423. $params['stringid'] = $filter->stringid;
  424. }
  425. if (!empty($filter->substring)) {
  426. $sql .= " AND (".$DB->sql_like('s.original', ':substringoriginal', false)." OR
  427. ".$DB->sql_like('s.master', ':substringmaster', false)." OR
  428. ".$DB->sql_like('s.local', ':substringlocal', false).")";
  429. $params['substringoriginal'] = '%'.$filter->substring.'%';
  430. $params['substringmaster'] = '%'.$filter->substring.'%';
  431. $params['substringlocal'] = '%'.$filter->substring.'%';
  432. }
  433. if (!empty($filter->helps)) {
  434. $sql .= " AND ".$DB->sql_like('s.stringid', ':help', false); //ILIKE
  435. $params['help'] = '%\_help';
  436. } else {
  437. $sql .= " AND ".$DB->sql_like('s.stringid', ':link', false, true, true); //NOT ILIKE
  438. $params['link'] = '%\_link';
  439. }
  440. $osql = " ORDER BY c.name, s.stringid";
  441. $this->numofrows = $DB->count_records_sql($csql.$sql, $params);
  442. $this->strings = $DB->get_records_sql($fsql.$sql.$osql, $params, ($this->currentpage) * self::PERPAGE, self::PERPAGE);
  443. }
  444. }