Router.ts 15.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

14
import $ from 'jquery';
15
16
17
import AjaxRequest = require('TYPO3/CMS/Core/Ajax/AjaxRequest');
import {AjaxResponse} from 'TYPO3/CMS/Core/Ajax/AjaxResponse';
import {AbstractInteractableModule} from './Module/AbstractInteractableModule';
18
import {AbstractInlineModule} from './Module/AbstractInlineModule';
19
20
import Icons = require('TYPO3/CMS/Backend/Icons');
import Modal = require('TYPO3/CMS/Backend/Modal');
21
22
import InfoBox = require('./Renderable/InfoBox');
import ProgressBar = require('./Renderable/ProgressBar');
23
import Severity = require('./Renderable/Severity');
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

class Router {
  private selectorBody: string = '.t3js-body';
  private selectorMainContent: string = '.t3js-module-body';

  public initialize(): void {
    this.registerInstallToolRoutes();

    $(document).on('click', '.t3js-login-lockInstallTool', (e: JQueryEventObject): void => {
      e.preventDefault();
      this.logout();
    });
    $(document).on('click', '.t3js-login-login', (e: JQueryEventObject): void => {
      e.preventDefault();
      this.login();
    });
    $(document).on('keydown', '#t3-install-form-password', (e: JQueryEventObject): void => {
41
      if (e.key === 'Enter') {
42
        e.preventDefault();
43
        $('.t3js-login-login').trigger('click');
44
45
46
      }
    });

47
48
49
50
51
52
    $(document).on('click', '.t3js-modulemenu-action', (e: JQueryEventObject): void => {
      e.preventDefault();
      const $me = $(e.currentTarget);
      window.location.href = $me.data('link');
    });

53
54
55
56
57
58
59
60
    $(document).on('click', '.card .btn', (e: JQueryEventObject): void => {
      e.preventDefault();

      const $me = $(e.currentTarget);
      const requireModule = $me.data('require');
      const inlineState = $me.data('inline');
      const isInline = typeof inlineState !== 'undefined' && parseInt(inlineState, 10) === 1;
      if (isInline) {
61
        require([requireModule], (aModule: AbstractInlineModule): void => {
62
63
64
65
66
          aModule.initialize($me);
        });
      } else {
        const modalTitle = $me.closest('.card').find('.card-title').html();
        const modalSize = $me.data('modalSize') || Modal.sizes.large;
67
68
69
70
71
72
73
74
75
76
77
78
        const $modal = Modal.advanced({
          type: Modal.types.default,
          title: modalTitle,
          size: modalSize,
          content: $('<div class="modal-loading">'),
          additionalCssClasses: ['install-tool-modal'],
          callback: (currentModal: any): void => {
            require([requireModule], (aModule: AbstractInteractableModule): void => {
              aModule.initialize(currentModal);
            });
          },
        });
79
        Icons.getIcon('spinner-circle', Icons.sizes.default, null, null, Icons.markupIdentifiers.inline).then((icon: any): void => {
80
          $modal.find('.modal-loading').append(icon);
81
82
83
84
        });
      }
    });

85
86
87
88
89
90
    const $context = $(this.selectorBody).data('context');
    if ($context === 'backend') {
      this.executeSilentConfigurationUpdate();
    } else {
      this.preAccessCheck();
    }
91
92
93
94
95
96
  }

  public registerInstallToolRoutes(): void {
    if (typeof TYPO3.settings === 'undefined') {
      TYPO3.settings = {
        ajaxUrls: {
97
98
          icons: window.location.origin + window.location.pathname + '?install[controller]=icon&install[action]=getIcon',
          icons_cache: window.location.origin + window.location.pathname + '?install[controller]=icon&install[action]=getCacheIdentifier',
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
        },
      };
    }
  }

  public getUrl(action?: string, controller?: string): string {
    const context = $(this.selectorBody).data('context');
    let url = location.href;
    url = url.replace(location.search, '');
    if (controller === undefined) {
      controller = $(this.selectorBody).data('controller');
    }
    url = url + '?install[controller]=' + controller;
    if (context !== undefined && context !== '') {
      url = url + '&install[context]=' + context;
    }
    if (action !== undefined) {
      url = url + '&install[action]=' + action;
    }
    return url;
  }

  public executeSilentConfigurationUpdate(): void {
    this.updateLoadingInfo('Checking session and executing silent configuration update');
123
124
125
126
127
128
    (new AjaxRequest(this.getUrl('executeSilentConfigurationUpdate', 'layout')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
129
            this.executeSilentTemplateFileUpdate();
130
131
132
133
          } else {
            this.executeSilentConfigurationUpdate();
          }
        },
134
        (error: AjaxResponse): void => {
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
          this.handleAjaxError(error)
        }
      );
  }

  public executeSilentTemplateFileUpdate(): void {
    this.updateLoadingInfo('Checking session and executing silent template file update');
    (new AjaxRequest(this.getUrl('executeSilentTemplateFileUpdate', 'layout')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
            this.executeSilentExtensionConfigurationSynchronization();
          } else {
            this.executeSilentTemplateFileUpdate();
          }
        },
        (error: AjaxResponse): void => {
154
          this.handleAjaxError(error)
155
        }
156
      );
157
158
159
160
161
162
163
164
165
  }

  /**
   * Extensions which come with new default settings in ext_conf_template.txt extension
   * configuration files get their new defaults written to LocalConfiguration.
   */
  public executeSilentExtensionConfigurationSynchronization(): void {
    const $outputContainer = $(this.selectorBody);
    this.updateLoadingInfo('Executing silent extension configuration synchronization');
166
167
168
169
170
171
172
173
174
175
176
177
    (new AjaxRequest(this.getUrl('executeSilentExtensionConfigurationSynchronization', 'layout')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
            this.loadMainLayout();
          } else {
            const message = InfoBox.render(Severity.error, 'Something went wrong', '');
            $outputContainer.empty().append(message);
          }
        },
178
        (error: AjaxResponse): void => {
179
          this.handleAjaxError(error)
180
        }
181
      );
182
183
184
185
186
  }

  public loadMainLayout(): void {
    const $outputContainer = $(this.selectorBody);
    this.updateLoadingInfo('Loading main layout');
187
188
189
190
191
192
193
194
195
196
    (new AjaxRequest(this.getUrl('mainLayout', 'layout')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true && data.html !== 'undefined' && data.html.length > 0) {
            $outputContainer.empty().append(data.html);
            // Mark main module as active in standalone
            if ($(this.selectorBody).data('context') !== 'backend') {
              const controller = $outputContainer.data('controller');
197
              $outputContainer.find('.t3js-modulemenu-action[data-controller="' + controller + '"]').addClass('modulemenu-action-active');
198
199
200
201
202
            }
            this.loadCards();
          } else {
            const message = InfoBox.render(Severity.error, 'Something went wrong', '');
            $outputContainer.empty().append(message);
203
          }
204
        },
205
        (error: AjaxResponse): void => {
206
          this.handleAjaxError(error)
207
        }
208
      );
209
210
  }

211
  public async handleAjaxError(error: AjaxResponse, $outputContainer?: JQuery): Promise<any> {
212
    let $message: any;
213
    if (error.response.status === 403) {
214
215
216
217
218
219
220
221
222
223
224
225
226
      // Install tool session expired - depending on context render error message or login
      const $context = $(this.selectorBody).data('context');
      if ($context === 'backend') {
        $message = InfoBox.render(Severity.error, 'The install tool session expired. Please reload the backend and try again.');
        $(this.selectorBody).empty().append($message);
      } else {
        this.checkEnableInstallToolFile();
      }
    } else {
      // @todo Recovery tests should be started here
      const url = this.getUrl(undefined, 'upgrade');
      $message = $(
        '<div class="t3js-infobox callout callout-sm callout-danger">'
227
228
229
230
231
232
233
234
235
236
237
238
        + '<div class="callout-body">'
        + '<p>Something went wrong. Please use <b><a href="' + url + '">Check for broken'
        + ' extensions</a></b> to see if a loaded extension breaks this part of the install tool'
        + ' and unload it.</p>'
        + '<p>The box below may additionally reveal further details on what went wrong depending on your debug settings.'
        + ' It may help to temporarily switch to debug mode using <b>Settings > Configuration Presets > Debug settings.</b></p>'
        + '<p>If this error happens at an early state and no full exception back trace is shown, it may also help'
        + ' to manually increase debugging output in <code>typo3conf/LocalConfiguration.php</code>:'
        + '<code>[\'BE\'][\'debug\'] => true</code>, <code>[\'SYS\'][\'devIPmask\'] => \'*\'</code>, '
        + '<code>[\'SYS\'][\'displayErrors\'] => 1</code>,'
        + '<code>[\'SYS\'][\'systemLogLevel\'] => 0</code>, <code>[\'SYS\'][\'exceptionalErrors\'] => 12290</code></p>'
        + '</div>'
239
240
        + '</div>'
        + '<div class="panel-group" role="tablist" aria-multiselectable="true">'
241
242
243
        + '<div class="panel panel-default panel-flat searchhit">'
        + '<div class="panel-heading" role="tab" id="heading-error">'
        + '<h3 class="panel-title">'
244
        + '<a role="button" data-bs-toggle="collapse" data-bs-parent="#accordion" href="#collapse-error" aria-expanded="true" '
245
246
247
248
249
250
251
252
253
254
255
256
        + 'aria-controls="collapse-error" class="collapsed">'
        + '<span class="caret"></span>'
        + '<strong>Ajax error</strong>'
        + '</a>'
        + '</h3>'
        + '</div>'
        + '<div id="collapse-error" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-error">'
        + '<div class="panel-body">'
        + (await error.response.text())
        + '</div>'
        + '</div>'
        + '</div>'
257
258
259
260
261
262
263
264
265
266
267
268
269
270
        + '</div>',
      );

      if (typeof $outputContainer !== 'undefined') {
        // Write to given output container. This is typically a modal if given
        $($outputContainer).empty().html($message);
      } else {
        // Else write to main frame
        $(this.selectorBody).empty().html($message);
      }
    }
  }

  public checkEnableInstallToolFile(): void {
271
272
273
274
275
276
277
278
279
280
281
    (new AjaxRequest(this.getUrl('checkEnableInstallToolFile')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
            this.checkLogin();
          } else {
            this.showEnableInstallTool();
          }
        },
282
        (error: AjaxResponse): void => {
283
          this.handleAjaxError(error)
284
        }
285
      );
286
287
288
  }

  public showEnableInstallTool(): void {
289
290
291
292
293
294
295
296
297
    (new AjaxRequest(this.getUrl('showEnableInstallToolFile')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
            $(this.selectorBody).empty().append(data.html);
          }
        },
298
        (error: AjaxResponse): void => {
299
          this.handleAjaxError(error)
300
        }
301
      );
302
303
304
  }

  public checkLogin(): void {
305
306
307
308
309
310
311
312
313
314
315
    (new AjaxRequest(this.getUrl('checkLogin')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
            this.loadMainLayout();
          } else {
            this.showLogin();
          }
        },
316
        (error: AjaxResponse): void => {
317
          this.handleAjaxError(error)
318
        }
319
      );
320
321
322
  }

  public showLogin(): void {
323
324
325
326
327
328
329
330
331
    (new AjaxRequest(this.getUrl('showLogin')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
            $(this.selectorBody).empty().append(data.html);
          }
        },
332
        (error: AjaxResponse): void => {
333
          this.handleAjaxError(error)
334
        }
335
      );
336
337
338
339
340
341
  }

  public login(): void {
    const $outputContainer: JQuery = $('.t3js-login-output');
    const message: any = ProgressBar.render(Severity.loading, 'Loading...', '');
    $outputContainer.empty().html(message);
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
    (new AjaxRequest(this.getUrl()))
      .post({
        install: {
          action: 'login',
          token: $('[data-login-token]').data('login-token'),
          password: $('.t3-install-form-input-text').val(),
        },
      })
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
            this.executeSilentConfigurationUpdate();
          } else {
            data.status.forEach((element: any): void => {
              const m: any = InfoBox.render(element.severity, element.title, element.message);
              $outputContainer.empty().html(m);
            });
          }
361
        },
362
        (error: AjaxResponse): void => {
363
          this.handleAjaxError(error)
364
        }
365
      );
366
367
368
  }

  public logout(): void {
369
370
371
372
373
374
375
376
377
    (new AjaxRequest(this.getUrl('logout')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true) {
            this.showEnableInstallTool();
          }
        },
378
        (error: AjaxResponse): void => {
379
          this.handleAjaxError(error)
380
        }
381
      );
382
383
384
385
  }

  public loadCards(): void {
    const outputContainer = $(this.selectorMainContent);
386
387
388
389
390
391
392
393
394
395
396
397
    (new AjaxRequest(this.getUrl('cards')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.success === true && data.html !== 'undefined' && data.html.length > 0) {
            outputContainer.empty().append(data.html);
          } else {
            const message = InfoBox.render(Severity.error, 'Something went wrong', '');
            outputContainer.empty().append(message);
          }
        },
398
        (error: AjaxResponse): void => {
399
          this.handleAjaxError(error)
400
        }
401
      );
402
403
404
405
406
407
  }

  public updateLoadingInfo(info: string): void {
    const $outputContainer = $(this.selectorBody);
    $outputContainer.find('#t3js-ui-block-detail').text(info);
  }
408
409
410

  private preAccessCheck(): void {
    this.updateLoadingInfo('Execute pre access check');
411
412
413
414
415
416
417
418
419
420
421
422
423
    (new AjaxRequest(this.getUrl('preAccessCheck', 'layout')))
      .get({cache: 'no-cache'})
      .then(
        async (response: AjaxResponse): Promise<any> => {
          const data = await response.resolve();
          if (data.installToolLocked) {
            this.checkEnableInstallToolFile();
          } else if (!data.isAuthorized) {
            this.showLogin();
          } else {
            this.executeSilentConfigurationUpdate();
          }
        },
424
        (error: AjaxResponse): void => {
425
          this.handleAjaxError(error)
426
        }
427
      );
428
  }
429
430
431
}

export = new Router();