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 /** 18 * Custom lang importer. 19 * 20 * @package tool_customlang 21 * @copyright 2020 Ferran Recio <ferran@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace tool_customlang\local; 26 27 use tool_customlang\local\mlang\phpparser; 28 use tool_customlang\local\mlang\logstatus; 29 use tool_customlang\local\mlang\langstring; 30 use core\output\notification; 31 use stored_file; 32 use coding_exception; 33 use moodle_exception; 34 use core_component; 35 use stdClass; 36 37 /** 38 * Class containing tha custom lang importer 39 * 40 * @package tool_customlang 41 * @copyright 2020 Ferran Recio <ferran@moodle.com> 42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 43 */ 44 class importer { 45 46 /** @var int imports will only create new customizations */ 47 public const IMPORTNEW = 1; 48 /** @var int imports will only update the current customizations */ 49 public const IMPORTUPDATE = 2; 50 /** @var int imports all strings */ 51 public const IMPORTALL = 3; 52 53 /** 54 * @var string the language name 55 */ 56 protected $lng; 57 58 /** 59 * @var int the importation mode (new, update, all) 60 */ 61 protected $importmode; 62 63 /** 64 * @var string request folder path 65 */ 66 private $folder; 67 68 /** 69 * @var array import log messages 70 */ 71 private $log; 72 73 /** 74 * Constructor for the importer class. 75 * 76 * @param string $lng the current language to import. 77 * @param int $importmode the import method (IMPORTALL, IMPORTNEW, IMPORTUPDATE). 78 */ 79 public function __construct(string $lng, int $importmode = self::IMPORTALL) { 80 $this->lng = $lng; 81 $this->importmode = $importmode; 82 $this->log = []; 83 } 84 85 /** 86 * Returns the last parse log. 87 * 88 * @return logstatus[] mlang logstatus with the messages 89 */ 90 public function get_log(): array { 91 return $this->log; 92 } 93 94 /** 95 * Import customlang files. 96 * 97 * @param stored_file[] $files array of files to import 98 */ 99 public function import(array $files): void { 100 // Create a temporal folder to store the files. 101 $this->folder = make_request_directory(false); 102 103 $langfiles = $this->deploy_files($files); 104 105 $this->process_files($langfiles); 106 } 107 108 /** 109 * Deploy all files into a request folder. 110 * 111 * @param stored_file[] $files array of files to deploy 112 * @return string[] of file paths 113 */ 114 private function deploy_files(array $files): array { 115 $result = []; 116 // Desploy all files. 117 foreach ($files as $file) { 118 if ($file->get_mimetype() == 'application/zip') { 119 $result = array_merge($result, $this->unzip_file($file)); 120 } else { 121 $path = $this->folder.'/'.$file->get_filename(); 122 $file->copy_content_to($path); 123 $result = array_merge($result, [$path]); 124 } 125 } 126 return $result; 127 } 128 129 /** 130 * Unzip a file into the request folder. 131 * 132 * @param stored_file $file the zip file to unzip 133 * @return string[] of zip content paths 134 */ 135 private function unzip_file(stored_file $file): array { 136 $fp = get_file_packer('application/zip'); 137 $zipcontents = $fp->extract_to_pathname($file, $this->folder); 138 if (!$zipcontents) { 139 throw new moodle_exception("Error Unzipping file", 1); 140 } 141 $result = []; 142 foreach ($zipcontents as $contentname => $success) { 143 if ($success) { 144 $result[] = $this->folder.'/'.$contentname; 145 } 146 } 147 return $result; 148 } 149 150 /** 151 * Import strings from a list of langfiles. 152 * 153 * @param string[] $langfiles an array with file paths 154 */ 155 private function process_files(array $langfiles): void { 156 $parser = phpparser::get_instance(); 157 foreach ($langfiles as $filepath) { 158 $component = $this->component_from_filepath($filepath); 159 if ($component) { 160 $strings = $parser->parse(file_get_contents($filepath)); 161 $this->import_strings($strings, $component); 162 } 163 } 164 } 165 166 /** 167 * Try to get the component from a filepath. 168 * 169 * @param string $filepath the filepath 170 * @return stdCalss|null the DB record of that component 171 */ 172 private function component_from_filepath(string $filepath) { 173 global $DB; 174 175 // Get component from filename. 176 $pathparts = pathinfo($filepath); 177 if (empty($pathparts['filename'])) { 178 throw new coding_exception("Cannot get filename from $filepath", 1); 179 } 180 $filename = $pathparts['filename']; 181 182 $normalized = core_component::normalize_component($filename); 183 if (count($normalized) == 1 || empty($normalized[1])) { 184 $componentname = $normalized[0]; 185 } else { 186 $componentname = implode('_', $normalized); 187 } 188 189 $result = $DB->get_record('tool_customlang_components', ['name' => $componentname]); 190 191 if (!$result) { 192 $this->log[] = new logstatus('notice_missingcomponent', notification::NOTIFY_ERROR, null, $componentname); 193 return null; 194 } 195 return $result; 196 } 197 198 /** 199 * Import an array of strings into the customlang tables. 200 * 201 * @param langstring[] $strings the langstring to set 202 * @param stdClass $component the target component 203 */ 204 private function import_strings(array $strings, stdClass $component): void { 205 global $DB; 206 207 foreach ($strings as $newstring) { 208 // Check current DB entry. 209 $customlang = $DB->get_record('tool_customlang', [ 210 'componentid' => $component->id, 211 'stringid' => $newstring->id, 212 'lang' => $this->lng, 213 ]); 214 if (!$customlang) { 215 $customlang = null; 216 } 217 218 if ($this->can_save_string($customlang, $newstring, $component)) { 219 $customlang->local = $newstring->text; 220 $customlang->timecustomized = $newstring->timemodified; 221 $customlang->outdated = 0; 222 $customlang->modified = 1; 223 $DB->update_record('tool_customlang', $customlang); 224 } 225 } 226 } 227 228 /** 229 * Determine if a specific string can be saved based on the current importmode. 230 * 231 * @param stdClass $customlang customlang original record 232 * @param langstring $newstring the new strign to store 233 * @param stdClass $component the component target 234 * @return bool if the string can be stored 235 */ 236 private function can_save_string(?stdClass $customlang, langstring $newstring, stdClass $component): bool { 237 $result = false; 238 $message = 'notice_success'; 239 if (empty($customlang)) { 240 $message = 'notice_inexitentstring'; 241 $this->log[] = new logstatus($message, notification::NOTIFY_ERROR, null, $component->name, $newstring); 242 return $result; 243 } 244 245 switch ($this->importmode) { 246 case self::IMPORTNEW: 247 $result = empty($customlang->local); 248 $warningmessage = 'notice_ignoreupdate'; 249 break; 250 case self::IMPORTUPDATE: 251 $result = !empty($customlang->local); 252 $warningmessage = 'notice_ignorenew'; 253 break; 254 case self::IMPORTALL: 255 $result = true; 256 break; 257 } 258 if ($result) { 259 $errorlevel = notification::NOTIFY_SUCCESS; 260 } else { 261 $errorlevel = notification::NOTIFY_ERROR; 262 $message = $warningmessage; 263 } 264 $this->log[] = new logstatus($message, $errorlevel, null, $component->name, $newstring); 265 266 return $result; 267 } 268 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body