[SPIP][PLUGINS] v3.0-->v3.2
[lhc/web/www.git] / www / plugins-dist / organiseur / lib / fullcalendar / gcal.js
index 076d3ef..7e89533 100644 (file)
-/*
- * FullCalendar v1.5 Google Calendar Plugin
- *
- * Copyright (c) 2011 Adam Shaw
- * Dual licensed under the MIT and GPL licenses, located in
- * MIT-LICENSE.txt and GPL-LICENSE.txt respectively.
- *
- * Date: Sat Mar 19 18:59:37 2011 -0700
- *
+/*!
+ * FullCalendar v3.2.0 Google Calendar Plugin
+ * Docs & License: https://fullcalendar.io/
+ * (c) 2017 Adam Shaw
  */
  
-(function($) {
+(function(factory) {
+       if (typeof define === 'function' && define.amd) {
+               define([ 'jquery' ], factory);
+       }
+       else if (typeof exports === 'object') { // Node/CommonJS
+               module.exports = factory(require('jquery'));
+       }
+       else {
+               factory(jQuery);
+       }
+})(function($) {
 
 
-var fc = $.fullCalendar;
-var formatDate = fc.formatDate;
-var parseISO8601 = fc.parseISO8601;
-var addDays = fc.addDays;
-var applyAll = fc.applyAll;
+var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars';
+var FC = $.fullCalendar;
+var applyAll = FC.applyAll;
 
 
-fc.sourceNormalizers.push(function(sourceOptions) {
-       if (sourceOptions.dataType == 'gcal' ||
-               sourceOptions.dataType === undefined &&
-               (sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) {
-                       sourceOptions.dataType = 'gcal';
-                       if (sourceOptions.editable === undefined) {
-                               sourceOptions.editable = false;
-                       }
+FC.sourceNormalizers.push(function(sourceOptions) {
+       var googleCalendarId = sourceOptions.googleCalendarId;
+       var url = sourceOptions.url;
+       var match;
+
+       // if the Google Calendar ID hasn't been explicitly defined
+       if (!googleCalendarId && url) {
+
+               // detect if the ID was specified as a single string.
+               // will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars.
+               if (/^[^\/]+@([^\/\.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) {
+                       googleCalendarId = url;
                }
-});
+               // try to scrape it out of a V1 or V3 API feed URL
+               else if (
+                       (match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^\/]*)/.exec(url)) ||
+                       (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^\/]*)/.exec(url))
+               ) {
+                       googleCalendarId = decodeURIComponent(match[1]);
+               }
+
+               if (googleCalendarId) {
+                       sourceOptions.googleCalendarId = googleCalendarId;
+               }
+       }
 
 
-fc.sourceFetchers.push(function(sourceOptions, start, end) {
-       if (sourceOptions.dataType == 'gcal') {
-               return transformOptions(sourceOptions, start, end);
+       if (googleCalendarId) { // is this a Google Calendar?
+
+               // make each Google Calendar source uneditable by default
+               if (sourceOptions.editable == null) {
+                       sourceOptions.editable = false;
+               }
+
+               // We want removeEventSource to work, but it won't know about the googleCalendarId primitive.
+               // Shoehorn it into the url, which will function as the unique primitive. Won't cause side effects.
+               // This hack is obsolete since 2.2.3, but keep it so this plugin file is compatible with old versions.
+               sourceOptions.url = googleCalendarId;
        }
 });
 
 
-function transformOptions(sourceOptions, start, end) {
+FC.sourceFetchers.push(function(sourceOptions, start, end, timezone) {
+       if (sourceOptions.googleCalendarId) {
+               return transformOptions(sourceOptions, start, end, timezone, this); // `this` is the calendar
+       }
+});
+
 
+function transformOptions(sourceOptions, start, end, timezone, calendar) {
+       var url = API_BASE + '/' + encodeURIComponent(sourceOptions.googleCalendarId) + '/events?callback=?'; // jsonp
+       var apiKey = sourceOptions.googleCalendarApiKey || calendar.options.googleCalendarApiKey;
        var success = sourceOptions.success;
-       var data = $.extend({}, sourceOptions.data || {}, {
-               'start-min': formatDate(start, 'u'),
-               'start-max': formatDate(end, 'u'),
-               'singleevents': true,
-               'max-results': 9999
-       });
-       
-       var ctz = sourceOptions.currentTimezone;
-       if (ctz) {
-               data.ctz = ctz = ctz.replace(' ', '_');
+       var data;
+       var timezoneArg; // populated when a specific timezone. escaped to Google's liking
+
+       function reportError(message, apiErrorObjs) {
+               var errorObjs = apiErrorObjs || [ { message: message } ]; // to be passed into error handlers
+
+               // call error handlers
+               (sourceOptions.googleCalendarError || $.noop).apply(calendar, errorObjs);
+               (calendar.options.googleCalendarError || $.noop).apply(calendar, errorObjs);
+
+               // print error to debug console
+               FC.warn.apply(null, [ message ].concat(apiErrorObjs || []));
        }
 
+       if (!apiKey) {
+               reportError("Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/");
+               return {}; // an empty source to use instead. won't fetch anything.
+       }
+
+       // The API expects an ISO8601 datetime with a time and timezone part.
+       // Since the calendar's timezone offset isn't always known, request the date in UTC and pad it by a day on each
+       // side, guaranteeing we will receive all events in the desired range, albeit a superset.
+       // .utc() will set a zone and give it a 00:00:00 time.
+       if (!start.hasZone()) {
+               start = start.clone().utc().add(-1, 'day');
+       }
+       if (!end.hasZone()) {
+               end = end.clone().utc().add(1, 'day');
+       }
+
+       // when sending timezone names to Google, only accepts underscores, not spaces
+       if (timezone && timezone != 'local') {
+               timezoneArg = timezone.replace(' ', '_');
+       }
+
+       data = $.extend({}, sourceOptions.data || {}, {
+               key: apiKey,
+               timeMin: start.format(),
+               timeMax: end.format(),
+               timeZone: timezoneArg,
+               singleEvents: true,
+               maxResults: 9999
+       });
+
        return $.extend({}, sourceOptions, {
-               url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?',
-               dataType: 'jsonp',
+               googleCalendarId: null, // prevents source-normalizing from happening again
+               url: url,
                data: data,
-               startParam: false,
-               endParam: false,
+               startParam: false, // `false` omits this parameter. we already included it above
+               endParam: false, // same
+               timezoneParam: false, // same
                success: function(data) {
                        var events = [];
-                       if (data.feed.entry) {
-                               $.each(data.feed.entry, function(i, entry) {
-                                       var startStr = entry['gd$when'][0]['startTime'];
-                                       var start = parseISO8601(startStr, true);
-                                       var end = parseISO8601(entry['gd$when'][0]['endTime'], true);
-                                       var allDay = startStr.indexOf('T') == -1;
-                                       var url;
-                                       $.each(entry.link, function(i, link) {
-                                               if (link.type == 'text/html') {
-                                                       url = link.href;
-                                                       if (ctz) {
-                                                               url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + ctz;
-                                                       }
-                                               }
-                                       });
-                                       if (allDay) {
-                                               addDays(end, -1); // make inclusive
+                       var successArgs;
+                       var successRes;
+
+                       if (data.error) {
+                               reportError('Google Calendar API: ' + data.error.message, data.error.errors);
+                       }
+                       else if (data.items) {
+                               $.each(data.items, function(i, entry) {
+                                       var url = entry.htmlLink || null;
+
+                                       // make the URLs for each event show times in the correct timezone
+                                       if (timezoneArg && url !== null) {
+                                               url = injectQsComponent(url, 'ctz=' + timezoneArg);
                                        }
+
                                        events.push({
-                                               id: entry['gCal$uid']['value'],
-                                               title: entry['title']['$t'],
+                                               id: entry.id,
+                                               title: entry.summary,
+                                               start: entry.start.dateTime || entry.start.date, // try timed. will fall back to all-day
+                                               end: entry.end.dateTime || entry.end.date, // same
                                                url: url,
-                                               start: start,
-                                               end: end,
-                                               allDay: allDay,
-                                               location: entry['gd$where'][0]['valueString'],
-                                               description: entry['content']['$t']
+                                               location: entry.location,
+                                               description: entry.description
                                        });
                                });
+
+                               // call the success handler(s) and allow it to return a new events array
+                               successArgs = [ events ].concat(Array.prototype.slice.call(arguments, 1)); // forward other jq args
+                               successRes = applyAll(success, this, successArgs);
+                               if ($.isArray(successRes)) {
+                                       return successRes;
+                               }
                        }
-                       var args = [events].concat(Array.prototype.slice.call(arguments, 1));
-                       var res = applyAll(success, this, args);
-                       if ($.isArray(res)) {
-                               return res;
-                       }
+
                        return events;
                }
        });
-       
 }
 
 
-// legacy
-fc.gcalFeed = function(url, sourceOptions) {
-       return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' });
-};
+// Injects a string like "arg=value" into the querystring of a URL
+function injectQsComponent(url, component) {
+       // inject it after the querystring but before the fragment
+       return url.replace(/(\?.*?)?(#|$)/, function(whole, qs, hash) {
+               return (qs ? qs + '&' : '?') + component + hash;
+       });
+}
 
 
-})(jQuery);
+});