Projet

Général

Profil

Paste
Télécharger (20 ko) Statistiques
| Branche: | Révision:

root / drupal7 / misc / drupal.js @ cee0424c

1

    
2
var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'locale': {} };
3

    
4
// Allow other JavaScript libraries to use $.
5
jQuery.noConflict();
6

    
7
(function ($) {
8

    
9
/**
10
 * Override jQuery.fn.init to guard against XSS attacks.
11
 *
12
 * See http://bugs.jquery.com/ticket/9521
13
 */
14
var jquery_init = $.fn.init;
15
$.fn.init = function (selector, context, rootjQuery) {
16
  // If the string contains a "#" before a "<", treat it as invalid HTML.
17
  if (selector && typeof selector === 'string') {
18
    var hash_position = selector.indexOf('#');
19
    if (hash_position >= 0) {
20
      var bracket_position = selector.indexOf('<');
21
      if (bracket_position > hash_position) {
22
        throw 'Syntax error, unrecognized expression: ' + selector;
23
      }
24
    }
25
  }
26
  return jquery_init.call(this, selector, context, rootjQuery);
27
};
28
$.fn.init.prototype = jquery_init.prototype;
29

    
30
/**
31
 * Pre-filter Ajax requests to guard against XSS attacks.
32
 *
33
 * See https://github.com/jquery/jquery/issues/2432
34
 */
35
if ($.ajaxPrefilter) {
36
  // For newer versions of jQuery, use an Ajax prefilter to prevent
37
  // auto-executing script tags from untrusted domains. This is similar to the
38
  // fix that is built in to jQuery 3.0 and higher.
39
  $.ajaxPrefilter(function (s) {
40
    if (s.crossDomain) {
41
      s.contents.script = false;
42
    }
43
  });
44
}
45
else if ($.httpData) {
46
  // For the version of jQuery that ships with Drupal core, override
47
  // jQuery.httpData to prevent auto-detecting "script" data types from
48
  // untrusted domains.
49
  var jquery_httpData = $.httpData;
50
  $.httpData = function (xhr, type, s) {
51
    // @todo Consider backporting code from newer jQuery versions to check for
52
    //   a cross-domain request here, rather than using Drupal.urlIsLocal() to
53
    //   block scripts from all URLs that are not on the same site.
54
    if (!type && !Drupal.urlIsLocal(s.url)) {
55
      var content_type = xhr.getResponseHeader('content-type') || '';
56
      if (content_type.indexOf('javascript') >= 0) {
57
        // Default to a safe data type.
58
        type = 'text';
59
      }
60
    }
61
    return jquery_httpData.call(this, xhr, type, s);
62
  };
63
  $.httpData.prototype = jquery_httpData.prototype;
64
}
65

    
66
/**
67
 * Attach all registered behaviors to a page element.
68
 *
69
 * Behaviors are event-triggered actions that attach to page elements, enhancing
70
 * default non-JavaScript UIs. Behaviors are registered in the Drupal.behaviors
71
 * object using the method 'attach' and optionally also 'detach' as follows:
72
 * @code
73
 *    Drupal.behaviors.behaviorName = {
74
 *      attach: function (context, settings) {
75
 *        ...
76
 *      },
77
 *      detach: function (context, settings, trigger) {
78
 *        ...
79
 *      }
80
 *    };
81
 * @endcode
82
 *
83
 * Drupal.attachBehaviors is added below to the jQuery ready event and so
84
 * runs on initial page load. Developers implementing AHAH/Ajax in their
85
 * solutions should also call this function after new page content has been
86
 * loaded, feeding in an element to be processed, in order to attach all
87
 * behaviors to the new content.
88
 *
89
 * Behaviors should use
90
 * @code
91
 *   $(selector).once('behavior-name', function () {
92
 *     ...
93
 *   });
94
 * @endcode
95
 * to ensure the behavior is attached only once to a given element. (Doing so
96
 * enables the reprocessing of given elements, which may be needed on occasion
97
 * despite the ability to limit behavior attachment to a particular element.)
98
 *
99
 * @param context
100
 *   An element to attach behaviors to. If none is given, the document element
101
 *   is used.
102
 * @param settings
103
 *   An object containing settings for the current context. If none given, the
104
 *   global Drupal.settings object is used.
105
 */
106
Drupal.attachBehaviors = function (context, settings) {
107
  context = context || document;
108
  settings = settings || Drupal.settings;
109
  // Execute all of them.
110
  $.each(Drupal.behaviors, function () {
111
    if ($.isFunction(this.attach)) {
112
      this.attach(context, settings);
113
    }
114
  });
115
};
116

    
117
/**
118
 * Detach registered behaviors from a page element.
119
 *
120
 * Developers implementing AHAH/Ajax in their solutions should call this
121
 * function before page content is about to be removed, feeding in an element
122
 * to be processed, in order to allow special behaviors to detach from the
123
 * content.
124
 *
125
 * Such implementations should look for the class name that was added in their
126
 * corresponding Drupal.behaviors.behaviorName.attach implementation, i.e.
127
 * behaviorName-processed, to ensure the behavior is detached only from
128
 * previously processed elements.
129
 *
130
 * @param context
131
 *   An element to detach behaviors from. If none is given, the document element
132
 *   is used.
133
 * @param settings
134
 *   An object containing settings for the current context. If none given, the
135
 *   global Drupal.settings object is used.
136
 * @param trigger
137
 *   A string containing what's causing the behaviors to be detached. The
138
 *   possible triggers are:
139
 *   - unload: (default) The context element is being removed from the DOM.
140
 *   - move: The element is about to be moved within the DOM (for example,
141
 *     during a tabledrag row swap). After the move is completed,
142
 *     Drupal.attachBehaviors() is called, so that the behavior can undo
143
 *     whatever it did in response to the move. Many behaviors won't need to
144
 *     do anything simply in response to the element being moved, but because
145
 *     IFRAME elements reload their "src" when being moved within the DOM,
146
 *     behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
147
 *     take some action.
148
 *   - serialize: When an Ajax form is submitted, this is called with the
149
 *     form as the context. This provides every behavior within the form an
150
 *     opportunity to ensure that the field elements have correct content
151
 *     in them before the form is serialized. The canonical use-case is so
152
 *     that WYSIWYG editors can update the hidden textarea to which they are
153
 *     bound.
154
 *
155
 * @see Drupal.attachBehaviors
156
 */
157
Drupal.detachBehaviors = function (context, settings, trigger) {
158
  context = context || document;
159
  settings = settings || Drupal.settings;
160
  trigger = trigger || 'unload';
161
  // Execute all of them.
162
  $.each(Drupal.behaviors, function () {
163
    if ($.isFunction(this.detach)) {
164
      this.detach(context, settings, trigger);
165
    }
166
  });
167
};
168

    
169
/**
170
 * Encode special characters in a plain-text string for display as HTML.
171
 *
172
 * @ingroup sanitization
173
 */
174
Drupal.checkPlain = function (str) {
175
  var character, regex,
176
      replace = { '&': '&amp;', "'": '&#39;', '"': '&quot;', '<': '&lt;', '>': '&gt;' };
177
  str = String(str);
178
  for (character in replace) {
179
    if (replace.hasOwnProperty(character)) {
180
      regex = new RegExp(character, 'g');
181
      str = str.replace(regex, replace[character]);
182
    }
183
  }
184
  return str;
185
};
186

    
187
/**
188
 * Replace placeholders with sanitized values in a string.
189
 *
190
 * @param str
191
 *   A string with placeholders.
192
 * @param args
193
 *   An object of replacements pairs to make. Incidences of any key in this
194
 *   array are replaced with the corresponding value. Based on the first
195
 *   character of the key, the value is escaped and/or themed:
196
 *    - !variable: inserted as is
197
 *    - @variable: escape plain text to HTML (Drupal.checkPlain)
198
 *    - %variable: escape text and theme as a placeholder for user-submitted
199
 *      content (checkPlain + Drupal.theme('placeholder'))
200
 *
201
 * @see Drupal.t()
202
 * @ingroup sanitization
203
 */
204
Drupal.formatString = function(str, args) {
205
  // Transform arguments before inserting them.
206
  for (var key in args) {
207
    if (args.hasOwnProperty(key)) {
208
      switch (key.charAt(0)) {
209
        // Escaped only.
210
        case '@':
211
          args[key] = Drupal.checkPlain(args[key]);
212
          break;
213
        // Pass-through.
214
        case '!':
215
          break;
216
        // Escaped and placeholder.
217
        default:
218
          args[key] = Drupal.theme('placeholder', args[key]);
219
          break;
220
      }
221
    }
222
  }
223

    
224
  return Drupal.stringReplace(str, args, null);
225
};
226

    
227
/**
228
 * Replace substring.
229
 *
230
 * The longest keys will be tried first. Once a substring has been replaced,
231
 * its new value will not be searched again.
232
 *
233
 * @param {String} str
234
 *   A string with placeholders.
235
 * @param {Object} args
236
 *   Key-value pairs.
237
 * @param {Array|null} keys
238
 *   Array of keys from the "args".  Internal use only.
239
 *
240
 * @return {String}
241
 *   Returns the replaced string.
242
 */
243
Drupal.stringReplace = function (str, args, keys) {
244
  if (str.length === 0) {
245
    return str;
246
  }
247

    
248
  // If the array of keys is not passed then collect the keys from the args.
249
  if (!$.isArray(keys)) {
250
    keys = [];
251
    for (var k in args) {
252
      if (args.hasOwnProperty(k)) {
253
        keys.push(k);
254
      }
255
    }
256

    
257
    // Order the keys by the character length. The shortest one is the first.
258
    keys.sort(function (a, b) { return a.length - b.length; });
259
  }
260

    
261
  if (keys.length === 0) {
262
    return str;
263
  }
264

    
265
  // Take next longest one from the end.
266
  var key = keys.pop();
267
  var fragments = str.split(key);
268

    
269
  if (keys.length) {
270
    for (var i = 0; i < fragments.length; i++) {
271
      // Process each fragment with a copy of remaining keys.
272
      fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0));
273
    }
274
  }
275

    
276
  return fragments.join(args[key]);
277
};
278

    
279
/**
280
 * Translate strings to the page language or a given language.
281
 *
282
 * See the documentation of the server-side t() function for further details.
283
 *
284
 * @param str
285
 *   A string containing the English string to translate.
286
 * @param args
287
 *   An object of replacements pairs to make after translation. Incidences
288
 *   of any key in this array are replaced with the corresponding value.
289
 *   See Drupal.formatString().
290
 *
291
 * @param options
292
 *   - 'context' (defaults to the empty context): The context the source string
293
 *     belongs to.
294
 *
295
 * @return
296
 *   The translated string.
297
 */
298
Drupal.t = function (str, args, options) {
299
  options = options || {};
300
  options.context = options.context || '';
301

    
302
  // Fetch the localized version of the string.
303
  if (Drupal.locale.strings && Drupal.locale.strings[options.context] && Drupal.locale.strings[options.context][str]) {
304
    str = Drupal.locale.strings[options.context][str];
305
  }
306

    
307
  if (args) {
308
    str = Drupal.formatString(str, args);
309
  }
310
  return str;
311
};
312

    
313
/**
314
 * Format a string containing a count of items.
315
 *
316
 * This function ensures that the string is pluralized correctly. Since Drupal.t() is
317
 * called by this function, make sure not to pass already-localized strings to it.
318
 *
319
 * See the documentation of the server-side format_plural() function for further details.
320
 *
321
 * @param count
322
 *   The item count to display.
323
 * @param singular
324
 *   The string for the singular case. Please make sure it is clear this is
325
 *   singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
326
 *   Do not use @count in the singular string.
327
 * @param plural
328
 *   The string for the plural case. Please make sure it is clear this is plural,
329
 *   to ease translation. Use @count in place of the item count, as in "@count
330
 *   new comments".
331
 * @param args
332
 *   An object of replacements pairs to make after translation. Incidences
333
 *   of any key in this array are replaced with the corresponding value.
334
 *   See Drupal.formatString().
335
 *   Note that you do not need to include @count in this array.
336
 *   This replacement is done automatically for the plural case.
337
 * @param options
338
 *   The options to pass to the Drupal.t() function.
339
 * @return
340
 *   A translated string.
341
 */
342
Drupal.formatPlural = function (count, singular, plural, args, options) {
343
  args = args || {};
344
  args['@count'] = count;
345
  // Determine the index of the plural form.
346
  var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1);
347

    
348
  if (index == 0) {
349
    return Drupal.t(singular, args, options);
350
  }
351
  else if (index == 1) {
352
    return Drupal.t(plural, args, options);
353
  }
354
  else {
355
    args['@count[' + index + ']'] = args['@count'];
356
    delete args['@count'];
357
    return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args, options);
358
  }
359
};
360

    
361
/**
362
 * Returns the passed in URL as an absolute URL.
363
 *
364
 * @param url
365
 *   The URL string to be normalized to an absolute URL.
366
 *
367
 * @return
368
 *   The normalized, absolute URL.
369
 *
370
 * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js
371
 * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript
372
 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53
373
 */
374
Drupal.absoluteUrl = function (url) {
375
  var urlParsingNode = document.createElement('a');
376

    
377
  // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8
378
  // strings may throw an exception.
379
  try {
380
    url = decodeURIComponent(url);
381
  } catch (e) {}
382

    
383
  urlParsingNode.setAttribute('href', url);
384

    
385
  // IE <= 7 normalizes the URL when assigned to the anchor node similar to
386
  // the other browsers.
387
  return urlParsingNode.cloneNode(false).href;
388
};
389

    
390
/**
391
 * Returns true if the URL is within Drupal's base path.
392
 *
393
 * @param url
394
 *   The URL string to be tested.
395
 *
396
 * @return
397
 *   Boolean true if local.
398
 *
399
 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58
400
 */
401
Drupal.urlIsLocal = function (url) {
402
  // Always use browser-derived absolute URLs in the comparison, to avoid
403
  // attempts to break out of the base path using directory traversal.
404
  var absoluteUrl = Drupal.absoluteUrl(url);
405
  var protocol = location.protocol;
406

    
407
  // Consider URLs that match this site's base URL but use HTTPS instead of HTTP
408
  // as local as well.
409
  if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) {
410
    protocol = 'https:';
411
  }
412
  var baseUrl = protocol + '//' + location.host + Drupal.settings.basePath.slice(0, -1);
413

    
414
  // Decoding non-UTF-8 strings may throw an exception.
415
  try {
416
    absoluteUrl = decodeURIComponent(absoluteUrl);
417
  } catch (e) {}
418
  try {
419
    baseUrl = decodeURIComponent(baseUrl);
420
  } catch (e) {}
421

    
422
  // The given URL matches the site's base URL, or has a path under the site's
423
  // base URL.
424
  return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0;
425
};
426

    
427
/**
428
 * Sanitizes a URL for use with jQuery.ajax().
429
 *
430
 * @param url
431
 *   The URL string to be sanitized.
432
 *
433
 * @return
434
 *   The sanitized URL.
435
 */
436
Drupal.sanitizeAjaxUrl = function (url) {
437
  var regex = /\=\?(&|$)/;
438
  while (url.match(regex)) {
439
    url = url.replace(regex, '');
440
  }
441
  return url;
442
}
443

    
444
/**
445
 * Generate the themed representation of a Drupal object.
446
 *
447
 * All requests for themed output must go through this function. It examines
448
 * the request and routes it to the appropriate theme function. If the current
449
 * theme does not provide an override function, the generic theme function is
450
 * called.
451
 *
452
 * For example, to retrieve the HTML for text that should be emphasized and
453
 * displayed as a placeholder inside a sentence, call
454
 * Drupal.theme('placeholder', text).
455
 *
456
 * @param func
457
 *   The name of the theme function to call.
458
 * @param ...
459
 *   Additional arguments to pass along to the theme function.
460
 * @return
461
 *   Any data the theme function returns. This could be a plain HTML string,
462
 *   but also a complex object.
463
 */
464
Drupal.theme = function (func) {
465
  var args = Array.prototype.slice.apply(arguments, [1]);
466

    
467
  return (Drupal.theme[func] || Drupal.theme.prototype[func]).apply(this, args);
468
};
469

    
470
/**
471
 * Freeze the current body height (as minimum height). Used to prevent
472
 * unnecessary upwards scrolling when doing DOM manipulations.
473
 */
474
Drupal.freezeHeight = function () {
475
  Drupal.unfreezeHeight();
476
  $('<div id="freeze-height"></div>').css({
477
    position: 'absolute',
478
    top: '0px',
479
    left: '0px',
480
    width: '1px',
481
    height: $('body').css('height')
482
  }).appendTo('body');
483
};
484

    
485
/**
486
 * Unfreeze the body height.
487
 */
488
Drupal.unfreezeHeight = function () {
489
  $('#freeze-height').remove();
490
};
491

    
492
/**
493
 * Encodes a Drupal path for use in a URL.
494
 *
495
 * For aesthetic reasons slashes are not escaped.
496
 */
497
Drupal.encodePath = function (item, uri) {
498
  uri = uri || location.href;
499
  return encodeURIComponent(item).replace(/%2F/g, '/');
500
};
501

    
502
/**
503
 * Get the text selection in a textarea.
504
 */
505
Drupal.getSelection = function (element) {
506
  if (typeof element.selectionStart != 'number' && document.selection) {
507
    // The current selection.
508
    var range1 = document.selection.createRange();
509
    var range2 = range1.duplicate();
510
    // Select all text.
511
    range2.moveToElementText(element);
512
    // Now move 'dummy' end point to end point of original range.
513
    range2.setEndPoint('EndToEnd', range1);
514
    // Now we can calculate start and end points.
515
    var start = range2.text.length - range1.text.length;
516
    var end = start + range1.text.length;
517
    return { 'start': start, 'end': end };
518
  }
519
  return { 'start': element.selectionStart, 'end': element.selectionEnd };
520
};
521

    
522
/**
523
 * Add a global variable which determines if the window is being unloaded.
524
 *
525
 * This is primarily used by Drupal.displayAjaxError().
526
 */
527
Drupal.beforeUnloadCalled = false;
528
$(window).bind('beforeunload pagehide', function () {
529
    Drupal.beforeUnloadCalled = true;
530
});
531

    
532
/**
533
 * Displays a JavaScript error from an Ajax response when appropriate to do so.
534
 */
535
Drupal.displayAjaxError = function (message) {
536
  // Skip displaying the message if the user deliberately aborted (for example,
537
  // by reloading the page or navigating to a different page) while the Ajax
538
  // request was still ongoing. See, for example, the discussion at
539
  // http://stackoverflow.com/questions/699941/handle-ajax-error-when-a-user-clicks-refresh.
540
  if (!Drupal.beforeUnloadCalled) {
541
    alert(message);
542
  }
543
};
544

    
545
/**
546
 * Build an error message from an Ajax response.
547
 */
548
Drupal.ajaxError = function (xmlhttp, uri, customMessage) {
549
  var statusCode, statusText, pathText, responseText, readyStateText, message;
550
  if (xmlhttp.status) {
551
    statusCode = "\n" + Drupal.t("An AJAX HTTP error occurred.") +  "\n" + Drupal.t("HTTP Result Code: !status", {'!status': xmlhttp.status});
552
  }
553
  else {
554
    statusCode = "\n" + Drupal.t("An AJAX HTTP request terminated abnormally.");
555
  }
556
  statusCode += "\n" + Drupal.t("Debugging information follows.");
557
  pathText = "\n" + Drupal.t("Path: !uri", {'!uri': uri} );
558
  statusText = '';
559
  // In some cases, when statusCode == 0, xmlhttp.statusText may not be defined.
560
  // Unfortunately, testing for it with typeof, etc, doesn't seem to catch that
561
  // and the test causes an exception. So we need to catch the exception here.
562
  try {
563
    statusText = "\n" + Drupal.t("StatusText: !statusText", {'!statusText': $.trim(xmlhttp.statusText)});
564
  }
565
  catch (e) {}
566

    
567
  responseText = '';
568
  // Again, we don't have a way to know for sure whether accessing
569
  // xmlhttp.responseText is going to throw an exception. So we'll catch it.
570
  try {
571
    responseText = "\n" + Drupal.t("ResponseText: !responseText", {'!responseText': $.trim(xmlhttp.responseText) } );
572
  } catch (e) {}
573

    
574
  // Make the responseText more readable by stripping HTML tags and newlines.
575
  responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi,"");
576
  responseText = responseText.replace(/[\n]+\s+/g,"\n");
577

    
578
  // We don't need readyState except for status == 0.
579
  readyStateText = xmlhttp.status == 0 ? ("\n" + Drupal.t("ReadyState: !readyState", {'!readyState': xmlhttp.readyState})) : "";
580

    
581
  // Additional message beyond what the xmlhttp object provides.
582
  customMessage = customMessage ? ("\n" + Drupal.t("CustomMessage: !customMessage", {'!customMessage': customMessage})) : "";
583

    
584
  message = statusCode + pathText + statusText + customMessage + responseText + readyStateText;
585
  return message;
586
};
587

    
588
// Class indicating that JS is enabled; used for styling purpose.
589
$('html').addClass('js');
590

    
591
// 'js enabled' cookie.
592
document.cookie = 'has_js=1; path=/';
593

    
594
/**
595
 * Additions to jQuery.support.
596
 */
597
$(function () {
598
  /**
599
   * Boolean indicating whether or not position:fixed is supported.
600
   */
601
  if (jQuery.support.positionFixed === undefined) {
602
    var el = $('<div style="position:fixed; top:10px" />').appendTo(document.body);
603
    jQuery.support.positionFixed = el[0].offsetTop === 10;
604
    el.remove();
605
  }
606
});
607

    
608
//Attach all behaviors.
609
$(function () {
610
  Drupal.attachBehaviors(document, Drupal.settings);
611
});
612

    
613
/**
614
 * The default themes.
615
 */
616
Drupal.theme.prototype = {
617

    
618
  /**
619
   * Formats text for emphasized display in a placeholder inside a sentence.
620
   *
621
   * @param str
622
   *   The text to format (plain-text).
623
   * @return
624
   *   The formatted text (html).
625
   */
626
  placeholder: function (str) {
627
    return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>';
628
  }
629
};
630

    
631
})(jQuery);