988f7f3be11f023cf94851547b7a7ce11c34c759
[yaffs-website] / web / core / modules / locale / src / PoDatabaseWriter.php
1 <?php
2
3 namespace Drupal\locale;
4
5 use Drupal\Component\Gettext\PoHeader;
6 use Drupal\Component\Gettext\PoItem;
7 use Drupal\Component\Gettext\PoReaderInterface;
8 use Drupal\Component\Gettext\PoWriterInterface;
9
10 /**
11  * Gettext PO writer working with the locale module database.
12  */
13 class PoDatabaseWriter implements PoWriterInterface {
14
15   /**
16    * An associative array indicating what data should be overwritten, if any.
17    *
18    * Elements of the array:
19    * - override_options
20    *   - not_customized: boolean indicating that not customized strings should
21    *     be overwritten.
22    *   - customized: boolean indicating that customized strings should be
23    *     overwritten.
24    * - customized: the strings being imported should be saved as customized.
25    *     One of LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.
26    *
27    * @var array
28    */
29   private $options;
30
31   /**
32    * Language code of the language being written to the database.
33    *
34    * @var string
35    */
36   private $langcode;
37
38   /**
39    * Header of the po file written to the database.
40    *
41    * @var \Drupal\Component\Gettext\PoHeader
42    */
43   private $header;
44
45   /**
46    * Associative array summarizing the number of changes done.
47    *
48    * Keys for the array:
49    *  - additions: number of source strings newly added
50    *  - updates: number of translations updated
51    *  - deletes: number of translations deleted
52    *  - skips: number of strings skipped due to disallowed HTML
53    *
54    * @var array
55    */
56   private $report;
57
58   /**
59    * Constructor, initialize reporting array.
60    */
61   public function __construct() {
62     $this->setReport();
63   }
64
65   /**
66    * {@inheritdoc}
67    */
68   public function getLangcode() {
69     return $this->langcode;
70   }
71
72   /**
73    * {@inheritdoc}
74    */
75   public function setLangcode($langcode) {
76     $this->langcode = $langcode;
77   }
78
79   /**
80    * Get the report of the write operations.
81    */
82   public function getReport() {
83     return $this->report;
84   }
85
86   /**
87    * Set the report array of write operations.
88    *
89    * @param array $report
90    *   Associative array with result information.
91    */
92   public function setReport($report = []) {
93     $report += [
94       'additions' => 0,
95       'updates' => 0,
96       'deletes' => 0,
97       'skips' => 0,
98       'strings' => [],
99     ];
100     $this->report = $report;
101   }
102
103   /**
104    * Get the options used by the writer.
105    */
106   public function getOptions() {
107     return $this->options;
108   }
109
110   /**
111    * Set the options for the current writer.
112    */
113   public function setOptions(array $options) {
114     if (!isset($options['overwrite_options'])) {
115       $options['overwrite_options'] = [];
116     }
117     $options['overwrite_options'] += [
118       'not_customized' => FALSE,
119       'customized' => FALSE,
120     ];
121     $options += [
122       'customized' => LOCALE_NOT_CUSTOMIZED,
123     ];
124     $this->options = $options;
125   }
126
127   /**
128    * {@inheritdoc}
129    */
130   public function getHeader() {
131     return $this->header;
132   }
133
134   /**
135    * Implements Drupal\Component\Gettext\PoMetadataInterface::setHeader().
136    *
137    * Sets the header and configure Drupal accordingly.
138    *
139    * Before being able to process the given header we need to know in what
140    * context this database write is done. For this the options must be set.
141    *
142    * A langcode is required to set the current header's PluralForm.
143    *
144    * @param \Drupal\Component\Gettext\PoHeader $header
145    *   Header metadata.
146    *
147    * @throws Exception
148    */
149   public function setHeader(PoHeader $header) {
150     $this->header = $header;
151     $locale_plurals = \Drupal::state()->get('locale.translation.plurals') ?: [];
152
153     // Check for options.
154     $options = $this->getOptions();
155     if (empty($options)) {
156       throw new \Exception('Options should be set before assigning a PoHeader.');
157     }
158     $overwrite_options = $options['overwrite_options'];
159
160     // Check for langcode.
161     $langcode = $this->langcode;
162     if (empty($langcode)) {
163       throw new \Exception('Langcode should be set before assigning a PoHeader.');
164     }
165
166     if (array_sum($overwrite_options) || empty($locale_plurals[$langcode]['plurals'])) {
167       // Get and store the plural formula if available.
168       $plural = $header->getPluralForms();
169       if (isset($plural) && $p = $header->parsePluralForms($plural)) {
170         list($nplurals, $formula) = $p;
171         \Drupal::service('locale.plural.formula')->setPluralFormula($langcode, $nplurals, $formula);
172       }
173     }
174   }
175
176   /**
177    * {@inheritdoc}
178    */
179   public function writeItem(PoItem $item) {
180     if ($item->isPlural()) {
181       $item->setSource(implode(LOCALE_PLURAL_DELIMITER, $item->getSource()));
182       $item->setTranslation(implode(LOCALE_PLURAL_DELIMITER, $item->getTranslation()));
183     }
184     $this->importString($item);
185   }
186
187   /**
188    * {@inheritdoc}
189    */
190   public function writeItems(PoReaderInterface $reader, $count = -1) {
191     $forever = $count == -1;
192     while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
193       $this->writeItem($item);
194     }
195   }
196
197   /**
198    * Imports one string into the database.
199    *
200    * @param \Drupal\Component\Gettext\PoItem $item
201    *   The item being imported.
202    *
203    * @return int
204    *   The string ID of the existing string modified or the new string added.
205    */
206   private function importString(PoItem $item) {
207     // Initialize overwrite options if not set.
208     $this->options['overwrite_options'] += [
209       'not_customized' => FALSE,
210       'customized' => FALSE,
211     ];
212     $overwrite_options = $this->options['overwrite_options'];
213     $customized = $this->options['customized'];
214
215     $context = $item->getContext();
216     $source = $item->getSource();
217     $translation = $item->getTranslation();
218
219     // Look up the source string and any existing translation.
220     $strings = \Drupal::service('locale.storage')->getTranslations([
221       'language' => $this->langcode,
222       'source' => $source,
223       'context' => $context,
224     ]);
225     $string = reset($strings);
226
227     if (!empty($translation)) {
228       // Skip this string unless it passes a check for dangerous code.
229       if (!locale_string_is_safe($translation)) {
230         \Drupal::logger('locale')->error('Import of string "%string" was skipped because of disallowed or malformed HTML.', ['%string' => $translation]);
231         $this->report['skips']++;
232         return 0;
233       }
234       elseif ($string) {
235         $string->setString($translation);
236         if ($string->isNew()) {
237           // No translation in this language.
238           $string->setValues([
239             'language' => $this->langcode,
240             'customized' => $customized,
241           ]);
242           $string->save();
243           $this->report['additions']++;
244         }
245         elseif ($overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
246           // Translation exists, only overwrite if instructed.
247           $string->customized = $customized;
248           $string->save();
249           $this->report['updates']++;
250         }
251         $this->report['strings'][] = $string->getId();
252         return $string->lid;
253       }
254       else {
255         // No such source string in the database yet.
256         $string = \Drupal::service('locale.storage')->createString(['source' => $source, 'context' => $context])
257           ->save();
258         \Drupal::service('locale.storage')->createTranslation([
259           'lid' => $string->getId(),
260           'language' => $this->langcode,
261           'translation' => $translation,
262           'customized' => $customized,
263         ])->save();
264
265         $this->report['additions']++;
266         $this->report['strings'][] = $string->getId();
267         return $string->lid;
268       }
269     }
270     elseif ($string && !$string->isNew() && $overwrite_options[$string->customized ? 'customized' : 'not_customized']) {
271       // Empty translation, remove existing if instructed.
272       $string->delete();
273       $this->report['deletes']++;
274       $this->report['strings'][] = $string->lid;
275       return $string->lid;
276     }
277   }
278
279 }