24
24
use Exception ;
25
25
use OCA \Richdocuments \Service \DiscoveryService ;
26
26
use Psr \Log \LoggerInterface ;
27
-
27
+ use SimpleXMLElement ;
28
+ use OCP \IL10N ;
29
+ use OCP \Files \File ;
30
+ use OCP \IRequest ;
28
31
class Parser {
32
+
33
+ public const ACTION_EDIT = 'edit ' ;
34
+ public const ACTION_VIEW = 'view ' ;
35
+ public const ACTION_EDITNEW = 'editnew ' ;
36
+
37
+ // https://wopi.readthedocs.io/en/latest/faq/languages.html
38
+ public const SUPPORTED_LANGUAGES = [
39
+ 'af-ZA ' ,
40
+ 'am-ET ' ,
41
+ 'ar-SA ' ,
42
+ 'as-IN ' ,
43
+ 'az-Latn-AZ ' ,
44
+ 'be-BY ' ,
45
+ 'bg-BG ' ,
46
+ 'bn-BD ' ,
47
+ 'bn-IN ' ,
48
+ 'bs-Latn-BA ' ,
49
+ 'ca-ES ' ,
50
+ 'ca-ES-valencia ' ,
51
+ 'chr-Cher-US ' ,
52
+ 'cs-CZ ' ,
53
+ 'cy-GB ' ,
54
+ 'da-DK ' ,
55
+ 'de-DE ' ,
56
+ 'el-GR ' ,
57
+ 'en-gb ' ,
58
+ 'en-US ' ,
59
+ 'es-ES ' ,
60
+ 'es-mx ' ,
61
+ 'et-EE ' ,
62
+ 'eu-ES ' ,
63
+ 'fa-IR ' ,
64
+ 'fi-FI ' ,
65
+ 'fil-PH ' ,
66
+ 'fr-ca ' ,
67
+ 'fr-FR ' ,
68
+ 'ga-IE ' ,
69
+ 'gd-GB ' ,
70
+ 'gl-ES ' ,
71
+ 'gu-IN ' ,
72
+ 'ha-Latn-NG ' ,
73
+ 'he-IL ' ,
74
+ 'hi-IN ' ,
75
+ 'hr-HR ' ,
76
+ 'hu-HU ' ,
77
+ 'hy-AM ' ,
78
+ 'id-ID ' ,
79
+ 'is-IS ' ,
80
+ 'it-IT ' ,
81
+ 'ja-JP ' ,
82
+ 'ka-GE ' ,
83
+ 'kk-KZ ' ,
84
+ 'km-KH ' ,
85
+ 'kn-IN ' ,
86
+ 'kok-IN ' ,
87
+ 'ko-KR ' ,
88
+ 'ky-KG ' ,
89
+ 'lb-LU ' ,
90
+ 'lo-la ' ,
91
+ 'lt-LT ' ,
92
+ 'lv-LV ' ,
93
+ 'mi-NZ ' ,
94
+ 'mk-MK ' ,
95
+ 'ml-IN ' ,
96
+ 'mn-MN ' ,
97
+ 'mr-IN ' ,
98
+ 'ms-MY ' ,
99
+ 'mt-MT ' ,
100
+ 'nb-NO ' ,
101
+ 'ne-NP ' ,
102
+ 'nl-NL ' ,
103
+ 'nn-NO ' ,
104
+ 'or-IN ' ,
105
+ 'pa-IN ' ,
106
+ 'pl-PL ' ,
107
+ 'prs-AF ' ,
108
+ 'pt-BR ' ,
109
+ 'pt-PT ' ,
110
+ 'quz-PE ' ,
111
+ 'ro-Ro ' ,
112
+ 'ru-Ru ' ,
113
+ 'sd-Arab-PK ' ,
114
+ 'si-LK ' ,
115
+ 'sk-SK ' ,
116
+ 'sl-SI ' ,
117
+ 'sq-AL ' ,
118
+ 'sr-Cyrl-BA ' ,
119
+ 'sr-Cyrl-RS ' ,
120
+ 'sr-Latn-RS ' ,
121
+ 'sv-SE ' ,
122
+ 'sw-KE ' ,
123
+ 'ta-IN ' ,
124
+ 'te-IN ' ,
125
+ 'th-TH ' ,
126
+ 'tk-TM ' ,
127
+ 'tr-TR ' ,
128
+ 'tt-RU ' ,
129
+ 'ug-CN ' ,
130
+ 'uk-UA ' ,
131
+ 'ur-PK ' ,
132
+ 'uz-Latn-UZ ' ,
133
+ 'vi-VN ' ,
134
+ 'zh-CN ' ,
135
+ 'zh-TW '
136
+ ];
137
+
138
+ private ?SimpleXMLElement $ parsed = null ;
139
+
29
140
public function __construct (
30
141
private DiscoveryService $ discoveryService ,
31
- private LoggerInterface $ logger
142
+ private LoggerInterface $ logger ,
143
+ private IL10N $ l10n ,
144
+ private IRequest $ request ,
32
145
) {
33
146
}
34
147
@@ -49,19 +162,149 @@ public function getUrlSrcValue(string $appName): string {
49
162
* @throws Exception
50
163
*/
51
164
private function getUrlSrc (string $ mimetype ): array {
52
- $ discovery = $ this ->discoveryService ->get ();
53
- $ this ->logger ->debug ('WOPI::getUrlSrc discovery: {discovery} ' , ['discovery ' => $ discovery ]);
54
- $ discoveryParsed = simplexml_load_string ($ discovery );
55
-
56
- $ result = $ discoveryParsed ->xpath (sprintf ('/wopi-discovery/net-zone/app[@name= \'%s \']/action ' , $ mimetype ));
165
+ $ result = $ this ->getParsed ()->xpath (sprintf ('/wopi-discovery/net-zone/app[@name= \'%s \']/action ' , $ mimetype ));
57
166
if ($ result && count ($ result ) > 0 ) {
58
167
return [
59
168
'urlsrc ' => (string )$ result [0 ]['urlsrc ' ],
60
169
'action ' => (string )$ result [0 ]['name ' ],
61
170
];
62
171
}
63
172
173
+ if ($ this ->getUrlSrcByExtension ('internal-http ' , 'docx ' , 'edit ' )) {
174
+ return [
175
+ 'urlsrc ' => (string )$ this ->getUrlSrcByExtension ('external-http ' , 'docx ' , 'edit ' ),
176
+ 'action ' => 'edit ' ,
177
+ ];
178
+ }
179
+
64
180
$ this ->logger ->error ('Didn \'t find urlsrc for mimetype {mimetype} in this WOPI discovery response: {discovery} ' , ['mimetype ' => $ mimetype , 'discovery ' => $ discovery ]);
65
181
throw new Exception ('Could not find urlsrc for ' . $ mimetype . ' in WOPI discovery response ' );
66
182
}
183
+
184
+ /**
185
+ * @return SimpleXMLElement|bool
186
+ * @throws \Exception
187
+ */
188
+ public function getParsed () {
189
+ if (!empty ($ this ->parsed )) {
190
+ return $ this ->parsed ;
191
+ }
192
+ $ discovery = $ this ->discoveryService ->get ();
193
+ // In PHP 8.0 and later, PHP uses libxml versions from 2.9.0, which disabled XXE by default. libxml_disable_entity_loader() is now deprecated.
194
+ // Ref.: https://php.watch/versions/8.0/libxml_disable_entity_loader-deprecation
195
+ if (\PHP_VERSION_ID < 80000 ) {
196
+ $ loadEntities = libxml_disable_entity_loader (true );
197
+ $ discoveryParsed = simplexml_load_string ($ discovery );
198
+ libxml_disable_entity_loader ($ loadEntities );
199
+ } else {
200
+ $ discoveryParsed = simplexml_load_string ($ discovery );
201
+ }
202
+ $ this ->parsed = $ discoveryParsed ;
203
+ return $ discoveryParsed ;
204
+ }
205
+
206
+ public function getUrlSrcForFile (File $ file , bool $ edit = true ): string {
207
+ $ protocol = $ this ->request ->getServerProtocol ();
208
+ $ fallbackProtocol = $ protocol === 'https ' ? 'http ' : 'https ' ;
209
+
210
+ $ netZones = [
211
+ 'external- ' . $ protocol ,
212
+ 'internal- ' . $ protocol ,
213
+ 'external- ' . $ fallbackProtocol ,
214
+ 'internal- ' . $ fallbackProtocol ,
215
+ ];
216
+
217
+ $ actions = [
218
+ $ edit && $ file ->getSize () === 0 ? self ::ACTION_EDITNEW : null ,
219
+ $ edit ? self ::ACTION_EDIT : null ,
220
+ self ::ACTION_VIEW ,
221
+ ];
222
+ $ actions = array_filter ($ actions );
223
+
224
+ foreach ($ netZones as $ netZone ) {
225
+ foreach ($ actions as $ action ) {
226
+ $ result = $ this ->getUrlSrcByExtension ($ netZone , $ file ->getExtension (), $ action );
227
+ if ($ result ) {
228
+ return $ this ->replaceUrlSrcParams ($ result );
229
+ }
230
+ }
231
+ }
232
+
233
+ foreach ($ netZones as $ netZone ) {
234
+ $ result = $ this ->getUrlSrcByMimetype ($ netZone , $ file ->getMimeType ());
235
+ if ($ result ) {
236
+ return $ this ->replaceUrlSrcParams ($ result );
237
+ }
238
+ }
239
+
240
+ throw new \Exception ('Could not find urlsrc in WOPI ' );
241
+ }
242
+
243
+ public function getUrlSrcByExtension (string $ netZoneName , string $ actionExt , $ actionName ): ?string {
244
+ $ result = $ this ->getParsed ()->xpath (sprintf (
245
+ '/wopi-discovery/net-zone[@name= \'%s \']/app/action[@ext= \'%s \' and @name= \'%s \'] ' ,
246
+ $ netZoneName , $ actionExt , $ actionName
247
+ ));
248
+
249
+ if (!$ result ) {
250
+ return null ;
251
+ }
252
+
253
+ return (string )current ($ result )->attributes ()['urlsrc ' ];
254
+ }
255
+
256
+ private function getUrlSrcByMimetype (string $ netZoneName , string $ mimetype ): ?string {
257
+ $ result = $ this ->getParsed ()->xpath (sprintf (
258
+ '/wopi-discovery/net-zone[@name= \'%s \']/app[@name= \'%s \']/action ' ,
259
+ $ netZoneName , $ mimetype
260
+ ));
261
+
262
+ if (!$ result ) {
263
+ return null ;
264
+ }
265
+
266
+ return (string )current ($ result )->attributes ()['urlsrc ' ];
267
+ }
268
+
269
+ private function replaceUrlSrcParams (string $ urlSrc ): string {
270
+ if (strpos ($ urlSrc , 'UI_LLCC ' ) === false ) {
271
+ return $ urlSrc ;
272
+ }
273
+
274
+ $ urlSrc = preg_replace ('/<ui=UI_LLCC&>/ ' , 'ui= ' . $ this ->getLanguageCode () . '& ' , $ urlSrc );
275
+ return preg_replace ('/<.+>/ ' , '' , $ urlSrc );
276
+ }
277
+
278
+ private function getLanguageCode (): string {
279
+ $ languageCode = $ this ->l10n ->getLanguageCode ();
280
+ $ localeCode = $ this ->l10n ->getLocaleCode ();
281
+ $ splitLocale = explode ('_ ' , $ localeCode );
282
+ if (count ($ splitLocale ) > 1 ) {
283
+ $ localeCode = $ splitLocale [1 ];
284
+ }
285
+
286
+ $ languageMatches = array_filter (self ::SUPPORTED_LANGUAGES , function ($ language ) use ($ languageCode , $ localeCode ) {
287
+ return stripos ($ language , $ languageCode ) === 0 ;
288
+ });
289
+
290
+ // Unique match on the language
291
+ if (count ($ languageMatches ) === 1 ) {
292
+ return array_shift ($ languageMatches );
293
+ }
294
+ $ localeMatches = array_filter ($ languageMatches , function ($ language ) use ($ languageCode , $ localeCode ) {
295
+ return stripos ($ language , $ languageCode . '- ' . $ localeCode ) === 0 ;
296
+ });
297
+
298
+ // Matches with language and locale with region
299
+ if (count ($ localeMatches ) >= 1 ) {
300
+ return array_shift ($ localeMatches );
301
+ }
302
+
303
+ // Fallback to first language match if multiple found and no fitting region is available
304
+ if (count ($ languageMatches ) > 1 ) {
305
+ return array_shift ($ languageMatches );
306
+ }
307
+
308
+ return 'en-US ' ;
309
+ }
67
310
}
0 commit comments