1 /*global global: true, process: true, require: true, module: true */
  2 
  3 /**
  4  * Establishes the `GCN' object and exposes it in the global context.
  5  */
  6 GCN = (function (global) {
  7 	'use strict';
  8 
  9 	// Check whether we are in nodeJS context.
 10 	if (typeof process !== 'undefined' && process.versions
 11 			&& process.versions.node) {
 12 		global.isNode = true;
 13 		var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
 14 		jQuery = global.$ = global.jQuery = require('jquery');
 15 		global.jQuery.ajaxSettings.xhr = function createNodeXHRForGCN() {
 16 			return new XMLHttpRequest();
 17 		};
 18 		// http://stackoverflow.com/a/6432602
 19 		global.jQuery.support.cors = true;
 20 	}
 21 
 22 	// Temporarily deactivate JS Lints "dangerous '.'" test for regular expressions.
 23 	/*jslint regexp: false*/
 24 	/**
 25 	 * Match URL to internal page
 26 	 *
 27 	 * @const
 28 	 * @type {RegExp}
 29 	 */
 30 	var INTERNAL_PAGEURL = /\/CNPortletapp\/alohapage\?.*realid=([0-9]+).*$/;
 31 
 32 	/**
 33 	 * Match URL to internal file
 34 	 *
 35 	 * @const
 36 	 * @type {RegExp}
 37 	 */
 38 	var INTERNAL_FILEURL = /^http.*\?.*&do=16000&id=([0-9]+).*$/;
 39 	/*jslint regexp: true*/
 40 
 41 	/**
 42 	 * @private
 43 	 * @type {object} An object to indicate which handlers have been
 44 	 *                 registered through the `GCN.onRender()' function.
 45 	 */
 46 	var onRenderHandler = {};
 47 
 48 	/**
 49 	 * @private
 50 	 * @type {boolean} A flag to indicate whether or not a handler has been
 51 	 *                 registerd through the `GCN.onError()' function.
 52 	 */
 53 	var hasOnErrorHandler = false;
 54 
 55 	/**
 56 	 * @ignore
 57 	 * @type {boolean} An internal flag that stores whether an authentication
 58 	 *                 handler has been set.
 59 	 */
 60 	var hasAuthenticationHandler = false;
 61 
 62 	/**
 63 	 * GCN JS API error object.  This is the object passed to error handlers.
 64 	 *
 65 	 * @class 
 66 	 * @name GCNError
 67 	 * @param {string} code error code for the error
 68 	 * @param {string} message descriptive error message
 69 	 * @param {object} data additional data
 70 	 */
 71 	var GCNError = function (code, message, data) {
 72 		this.code = code;
 73 		this.message = message;
 74 		this.data = data;
 75 	};
 76 
 77 	/**
 78 	 * Returns a human-readable representation of this error object.
 79 	 *
 80 	 * @public
 81 	 * @return {string}
 82 	 */
 83 	GCNError.prototype.toString = function () {
 84 		return 'GCN ERROR (' + this.code + '): "' + (this.message || '') + '"';
 85 	};
 86 
 87 	/**
 88 	 * @name GCN
 89 	 * @class
 90 	 *
 91 	 * Base namespace for the Gentics Content.Node JavaScript API.
 92 	 */
 93 	var GCN = global.GCN || {};
 94 
 95 	jQuery.extend(GCN, {
 96 		/** @lends GCN */
 97 
 98 		/**
 99 		 * Reference to the global context.
100 		 * 
101 		 * @type {object} 
102 		 */
103 		global: global,
104 
105 		/**
106 		 * Settings for the Gentics Content.Node JavaScript API.
107 		 * 
108 		 * @type {object<string, object>}
109 		 */
110 		settings: {
111 
112 			/**
113 			 * The language code with which to render tags.
114 			 * 
115 			 * @const
116 			 * @name settings.lang
117 			 * @default 'en'
118 			 * @memberOf GCN
119 			 * @type {string} 
120 			 */
121 			lang: 'en',
122 
123 			/**
124 			 * Default GCN backend path. Do not add a trailing slash here.
125 			 * 
126 			 * @const
127 			 * @default '/CNPortletapp'
128 			 * @name settings.BACKEND_PATH
129 			 * @memberOf GCN
130 			 * @type {string}
131 			 */
132 			BACKEND_PATH: '/CNPortletapp',
133 
134 			/**
135 			 * The keyword for the construct that defines Aloha Editor links. In
136 			 * most Content.Node installations this will be "gtxalohapagelink",
137 			 * but can be otherwise defined.
138 			 * 
139 			 * @const
140 			 * @default 'gtxalohapagelink'
141 			 * @name settings.MAGIC_LINK
142 			 * @memberOf GCN
143 			 * @type {string}
144 			 */
145 			MAGIC_LINK: 'gtxalohapagelink',
146 
147 			/**
148 			 * Determines whether links will be rendered as back-end urls or
149 			 * front-end urls. Can either be set to "backend" or "frontend".
150 			 * 
151 			 * @const
152 			 * @default 'backend'
153 			 * @name settings.linksRenderMode
154 			 * @memberOf GCN
155 			 * @type {string}
156 			 */
157 			linksRenderMode: 'backend',
158 
159 			/**
160 			 * The default callback to determine if a URL is an internal link.
161 			 *
162 			 * Matches the given href agains <code>INTERNAL_PAGEURL</code> and
163 			 * <code>INTERNAL_FILEURL</code> respectively.
164 			 *
165 			 * @const
166 			 * @type {function}
167 			 * @memberOf GCN
168 			 * @name settings.checkForInternalLink
169 			 * @param {string} href The URL to be checked.
170 			 * @return {object} An object containing the fields
171 			 * <ul>
172 			 *   <li>match (boolean): indicating if the URL is internal</li>
173 			 *   <li>url (string|integer): The ID of the internal page or 0 if
174 			 *       the URL points to an internal file, or the external URL</li>
175 			 *   <li>fileurl (string|integer): The ID of the internal file, and
176 			 *       zero if the URL is external or points to a page.</li>
177 			 * </ul>
178 			 */
179 			checkForInternalLink: function (href) {
180 				var urlMatch = href.match(INTERNAL_PAGEURL);
181 
182 				if (urlMatch) {
183 					return { match: true, url: parseInt(urlMatch[1], 10), fileurl: 0 };
184 				}
185 
186 				urlMatch = href.match(INTERNAL_FILEURL);
187 
188 				if (urlMatch) {
189 					return { match: true, url: 0, fileurl: parseInt(urlMatch[1], 10) };
190 				}
191 
192 				return { match: false, url: href, fileurl: 0 };
193 			},
194 
195 			/**
196 			 * Set a channelid to work on for multichannelling or false if no
197 			 * channel should be used
198 			 * 
199 			 * @memberOf GCN
200 			 * @default false
201 			 * @type {bool|int|string}
202 			 */
203 			channel: false
204 		},
205 
206 		/**
207 		 * Publish a message
208 		 *
209 		 * @param {string} message channel name
210 		 * @param {*=} params
211 		 */
212 		pub: function (channel, params) {
213 			if (!hasOnErrorHandler && channel === 'error-encountered') {
214 				// throw an error if there is no subscription to
215 				// error-encountered.
216 				throw params;
217 			}
218 
219 			switch (channel) {
220 			case 'tag.rendered':
221 			case 'page.rendered':
222 			case 'content-rendered':
223 				// for these channels, we need to have a custom implementation:
224 				// param[0] is the html of the rendered tag
225 				// param[1] is the tag object
226 				// param[2] is the callback, that must be called from the event handler
227 				// If more than one handler subscribed, we will chain them by calling the
228 				// next handler in the callback of the previous. Only the callback of the
229 				// last handler will call the original callback
230 				if (jQuery.isArray(onRenderHandler[channel])) {
231 					var handlers = onRenderHandler[channel];
232 					// substitute callback function with wrapper
233 					var callback = params[2], i = 0;
234 					params[2] = function (html) {
235 						if (++i < handlers.length) {
236 							// call the next handler. Pass the html returned from the
237 							// previous callback to the next handler
238 							params[0] = html;
239 							handlers[i].apply(null, params);
240 						} else {
241 							// there are no more handlers, so call the original
242 							// callback with the final html
243 							callback(html);
244 						}
245 					};
246 					handlers[0].apply(null, params);
247 				}
248 				return;
249 			}
250 			// errors and exceptions in (custom-)event-handler should not stop the execution of GCN-JS-API methods
251 			try {
252 				jQuery(GCN).trigger(channel, params);
253 			} catch (err) {
254 				// include the original error and stack in the error message
255 				var msg = 'Encountered error when publishing message for channel "' + channel + '" with following details: \n';
256 				msg += 'original error: ' + err + '\n';
257 				// check if the stack property exists on the error object because it is non-standard
258 				if (typeof err.stack !== 'undefined') {
259 					msg += 'and stack: \n' + err.stack;
260 				}
261 				GCN.error('PUBSUB_HANDLER_FAILED', msg);
262 			}
263 		},
264 
265 		/**
266 		 * Subscribe to a message channel
267 		 *
268 		 * @param {string} message channel name
269 		 * @param {function} handler function - message parameters will be
270 		 *                           passed.
271 		 */
272 		sub: function (channel, handler) {
273 			// register default handlers
274 			switch (channel) {
275 			case 'error-encountered':
276 				hasOnErrorHandler = true;
277 				break;
278 			case 'tag.rendered':
279 			case 'page.rendered':
280 			case 'content-rendered':
281 				// store all the handlers in an array.
282 				onRenderHandler[channel] = onRenderHandler[channel] || [];
283 				onRenderHandler[channel].push(handler);
284 				return;
285 			case 'authentication-required':
286 			case 'session.authentication-required':
287 				hasAuthenticationHandler = true;
288 				break;
289 			}
290 
291 			jQuery(GCN).bind(channel, function (event, param1, param2, param3) {
292 				handler(param1, param2, param3);
293 			});
294 		},
295 
296 		/**
297 		 * Tigger an error message 'error-encountered'.
298 		 *
299 		 * @param {string} error code
300 		 * @param {string} error message
301 		 * @param {object} additional error data
302 		 */
303 		error: function (code, message, data) {
304 			var error = new GCNError(code, message, data);
305 			this.pub('error-encountered', error);
306 		},
307 
308 		/**
309 		 * Returns an object containing the formal error fields.  The object
310 		 * contains a `toString' method to print any uncaught exceptions
311 		 * nicely.
312 		 *
313 		 * @param {string} code
314 		 * @param {string} message
315 		 * @param {object} data
316 		 * @return {GCNError}
317 		 */
318 		createError: function (code, message, data) {
319 			return new GCNError(code, message, data);
320 		},
321 
322 		/**
323 		 * Wraps the `jQuery.ajax()' method.
324 		 *
325 		 * @public
326 		 * @param {object} settings
327 		 * @throws HTTP_ERROR
328 		 */
329 		ajax: function (settings) {
330 			if (settings.json) {
331 				settings.data = JSON.stringify(settings.json);
332 				delete settings.json;
333 			}
334 			settings.dataType = 'json';
335 			settings.contentType = 'application/json; charset=utf-8';
336 			jQuery.ajax(settings);
337 		},
338 
339 		/**
340 		 * Set links render mode if a parameter is given
341 		 * retrieve it if not
342 		 *
343 		 * @param {string} mode
344 		 * @return {string} mode
345 		 */
346 		linksRenderMode: function (mode) {
347 			if (mode) {
348 				GCN.settings.linksRenderMode = mode;
349 			}
350 			return GCN.settings.linksRenderMode;
351 		},
352 
353 		/**
354 		 * Set channel if a parameter is given retrieve it otherwise.
355 		 *
356 		 * If you don't want to work on a channel just set it to false, which
357 		 * is the default value.
358 		 *
359 		 * @param {string|boolean} channel The id of the channel to be set or false to unset the channel.
360 		 * @return {string} current channel id.
361 		 */
362 		channel: function (channel) {
363 			if (channel || false === channel) {
364 				GCN.settings.channel = channel;
365 			}
366 			return GCN.settings.channel;
367 		},
368 
369 		/**
370 		 * Constructs the nodeId query parameter for rest calls.
371 		 *
372 		 * @param {AbstractContentObject} contentObject A content object instance.
373 		 * @param {string=} delimiter Optional delimiter character.
374 		 * @return {string} Query parameter string.
375 		 */
376 		_getChannelParameter: function (contentObject, delimiter) {
377 			if (false === contentObject._channel) {
378 				return '';
379 			}
380 			return (delimiter || '?') + 'nodeId=' + contentObject._channel;
381 		},
382 
383 		/**
384 		 * @param {string} html Rendered content
385 		 * @param {Chainback} obj The rendered ContentObject.
386 		 * @param {function(html)} callback Receives the processed html.
387 		 */
388 		_handleContentRendered: function (html, obj, callback) {
389 			var channel = obj._type + '.rendered';
390 			if (onRenderHandler[channel]) {
391 				GCN.pub(channel, [html, obj, callback]);
392 			} else if (onRenderHandler['content-rendered']) {
393 				// Because 'content-rendered' has been deprecated in favor of
394 				// '{tag|page}.rendered'.
395 				GCN.pub('content-rendered', [html, obj, callback]);
396 			} else {
397 				callback(html);
398 			}
399 		},
400 
401 		/**
402 		 * Handles the ajax transport error.  It will invoke the custom error
403 		 * handler if one is provided, and propagate the error onto the global
404 		 * handler if the an error handler does not return `false'.
405 		 *
406 		 * @param {object} xhr
407 		 * @param {string} msg The error message
408 		 * @param {function} handler Custom error handler.
409 		 * @throws HTTP_ERROR
410 		 */
411 		handleHttpError: function (xhr, msg, handler) {
412 			var throwException = true;
413 
414 			if (handler) {
415 				throwException = handler(GCN.createError('HTTP_ERROR', msg,
416 					xhr));
417 			}
418 
419 			if (throwException !== 'false') {
420 				GCN.error('HTTP_ERROR', msg, xhr);
421 			}
422 		},
423 
424 		/**
425 		 * Handles error that occur when an ajax request succeeds but the
426 		 * backend responds with an error.
427 		 *
428 		 * @param {object} reponse The REST API response object.
429 		 * @param {function(GCNError):boolean} handler Custom error handler.
430 		 */
431 		handleResponseError: function (response, handler) {
432 			var info = response.responseInfo;
433 			var throwException = true;
434 
435 			if (handler) {
436 				throwException = handler(GCN.createError(
437 					info.responseCode,
438 					info.responseMessage,
439 					response
440 				));
441 			}
442 
443 			if (throwException !== false) {
444 				GCN.error(info.responseCode, info.responseMessage, response);
445 			}
446 		},
447 
448 		/**
449 		 * Tiggers the GCN error event.
450 		 *
451 		 * @param {GCNError} error
452 		 * @param {function(GCNError):boolean} handler Custom error handler.
453 		 * @return {boolean} Whether or not to the exception was thrown.
454 		 */
455 		handleError: function (error, handler) {
456 			var throwException = true;
457 
458 			if (handler) {
459 				throwException = handler(error);
460 			}
461 
462 			if (throwException !== false) {
463 				GCN.error(error.code, error.message, error.data);
464 			}
465 
466 			return throwException;
467 		},
468 
469 		/**
470 		 * Check if an authentication handler has been registered.
471 		 *
472 		 * @return {boolean} True if an handler for the
473 		 *                   'authentication-required' message has been
474 		 *                   registered.
475 		 */
476 		_hasAuthenticationHandler: function () {
477 			return hasAuthenticationHandler;
478 		}
479 
480 	});
481 
482 	// Expose the Gentics Content.Node JavaScript API to the global context.
483 	// This will be `window' in most cases.
484 	return (global.GCN = GCN);
485 
486 }(typeof global !== 'undefined' ? global : window));
487