1 (function (GCN) {
  2 
  3 	'use strict';
  4 
  5 	/**
  6 	 * Updates the internal data of the given content object.
  7 	 *
  8 	 * This function extends and overwrites properties of the instances's
  9 	 * internal data structure.  No property is deleted on account of being
 10 	 * absent from the given `props' object.
 11 	 *
 12 	 * @param {ContentObjectAPI} obj An instance whose internal data is to be
 13 	 *                               reset.
 14 	 * @param {object} props The properties with which to replace the internal
 15 	 *                       data of the given chainback instance.
 16 	 */
 17 	function update(obj, props) {
 18 		jQuery.extend(obj._data, props);
 19 	}
 20 
 21 	/**
 22 	 * The prefix that will be temporarily applied to block tags during an
 23 	 * encode() process.
 24 	 *
 25 	 * @type {string}
 26 	 * @const
 27 	 */
 28 	var BLOCK_ENCODING_PREFIX = 'GCN_BLOCK_TMP__';
 29 
 30 	/**
 31 	 * Will match <span id="GENTICS_block_123"></span>" but not "<node abc123>"
 32 	 * tags.  The first backreference contains the tagname of the tag
 33 	 * corresponding to this block.
 34 	 *
 35 	 * Limitation: Will not work with unicode characters.
 36 	 *
 37 	 * @type {RexExp}
 38 	 * @const
 39 	 */
 40     var CONTENT_BLOCK = new RegExp(
 41 			// "<span" or "<div" but not "<node"
 42 			'<(?!node)[a-z]+'            +
 43 				// "class=... data-*..."
 44 				'(?:\\s+[^/<>\\s=]+(?:=(?:"[^"]*"|\'[^\']*\'|[^>/\\s]+))?)*?' +
 45 				// " id = "
 46 				'\\s+id\\s*=\\s*["\']?'  +
 47 				// "GCN_BLOCK_TMP__"
 48 				BLOCK_ENCODING_PREFIX    +
 49 				// "_abc-123"
 50 				'([^"\'/<>\\s=]*)["\']?' +
 51 				// class=... data-*...
 52 				'(?:\\s+[^/<>\\s=]+(?:=(?:"[^"]*"|\'[^\']*\'|[^>/\\s]+))?)*' +
 53 				// "' ...></span>" or "</div>"
 54 				'\\s*></[a-z]+>',
 55 			'gi'
 56 		);
 57 
 58 	/**
 59 	 * Will match <node foo> or <node bar_123> or <node foo-bar> but not
 60 	 * <node "blah">.
 61 	 *
 62 	 * @type {RegExp}
 63 	 * @const
 64 	 */
 65 	var NODE_NOTATION = /<node ([a-z0-9_\-]+?)>/gim;
 66 
 67 	/**
 68 	 * Examines a string for "<node>" tags, and for each occurance of this
 69 	 * notation, the given callback will be invoked to manipulate the string.
 70 	 *
 71 	 * @private
 72 	 * @static
 73 	 * @param {string} str The string that will be examined for "<node>" tags.
 74 	 * @param {function} onMatchFound Callback function that should receive the
 75 	 *                                following three parameters:
 76 	 *
 77 	 *                    name:string The name of the tag being notated by the
 78 	 *                                node substring.  If the `str' arguments
 79 	 *                                is "<node myTag>", then the `name' value
 80 	 *                                will be "myTag".
 81 	 *                  offset:number The offset where the node substring was
 82 	 *                                found within the examined string.
 83 	 *                     str:string The string in which the "<node *>"
 84 	 *                                substring occured.
 85 	 *
 86 	 *                                The return value of the function will
 87 	 *                                replace the entire "<node>" substring
 88 	 *                                that was passed to it within the examined
 89 	 *                                string.
 90 	 */
 91 	function replaceNodeTags(str, onMatchFound) {
 92 		var parsed = str.replace(NODE_NOTATION, function (substr, tagname,
 93 		                                                  offset, examined) {
 94 				return onMatchFound(tagname, offset, examined);
 95 			});
 96 		return parsed;
 97 	}
 98 
 99 	/*
100 	 * have a look at _init 
101 	 */
102 	GCN.ContentObjectAPI = GCN.defineChainback({
103 		/** @lends ContentObjectAPI */
104 
105 		/**
106 		 * @private
107 		 * @type {string} A string denoting a content node type.  This value is
108 		 *                used to compose the correct REST API ajax urls.  The
109 		 *                following are valid values: "node", "folder",
110 		 *                "template", "page", "file", "image".
111 		 */
112 		_type: null,
113 
114 		/**
115 		 * @private
116 		 * @type {object<string,*>} An internal object to store data that we
117 		 *                          get from the server.
118 		 */
119 		_data: {},
120 
121 		/**
122 		 * @private
123 		 * @type {object<string,*>} An internal object to store updates to
124 		 *                          the content object.  Should reflect the
125 		 *                          structural typography of the `_data'
126 		 *                          object.
127 		 */
128 		_shadow: {},
129 
130 		/**
131 		 * @type {boolean} Flags whether or not data for this content object have
132 		 *                 been fetched from the server.
133 		 */
134 		_fetched: false,
135 
136 		/**
137 		 * @private
138 		 * @type {object} will contain an objects internal settings
139 		 */
140 		_settings: null,
141 
142 		/**
143 		 * An array of all properties of an object that can be changed by the
144 		 * user. Writeable properties for all content objects.
145 		 * 
146 		 * @public
147 		 * @type {Array.string}
148 		 */
149 		WRITEABLE_PROPS: [],
150 
151 		/**
152 		 * <p>This object can contain various contrains for writeable props. 
153 		 * Those contrains will be checked when the user tries to set/save a
154 		 * property. Currently only maxLength is beeing handled.</p>
155 		 *
156 		 * <p>Example:</p>
157 		 * <pre>WRITEABLE_PROPS_CONSTRAINTS: {
158 		 *    'name': {
159 		 *        maxLength: 255
160 		 *     } 
161 		 * }</pre>
162 		 * @type {object}
163 		 * @const
164 		 *
165 		 */
166 		WRITEABLE_PROPS_CONSTRAINTS: {},
167 
168 		/**
169 		 * Fetches this content object's data from the backend.
170 		 *
171 		 * @ignore
172 		 * @param {function(object)} success A function to receive the server
173 		 *                                   response.
174 		 * @param {function(GCNError):boolean} error Optional custrom error
175 		 *                                           handler.
176 		 */
177 		'!fetch': function (success, error, stack) {
178 			var obj = this;
179 			var ajax = function () {
180 				obj._authAjax({
181 					url: GCN.settings.BACKEND_PATH + '/rest/' + obj._type +
182 					     '/load/' + obj.id() + GCN._getChannelParameter(obj),
183 					data: obj._loadParams(),
184 					error: error,
185 					success: success
186 				});
187 			};
188 
189 			// If this chainback object has an ancestor, then invoke that
190 			// parent's `_read()' method before fetching the data for this
191 			// chainback object.
192 			if (obj._chain) {
193 				var circularReference =
194 						stack && -1 < jQuery.inArray(obj._chain, stack);
195 				if (!circularReference) {
196 					stack = stack || [];
197 					stack.push(obj._chain);
198 					obj._chain._read(ajax, error, stack);
199 					return;
200 				}
201 			}
202 
203 			ajax();
204 		},
205 
206 		/**
207 		 * Internal method, to fetch this object's data from the server.
208 		 *
209 		 * @ignore
210 		 * @private
211 		 * @param {function(ContentObjectAPI)=} success Optional callback that
212 		 *                                              receives this object as
213 		 *                                              its only argument.
214 		 * @param {function(GCNError):boolean=} error Optional customer error
215 		 *                                            handler.
216 		 */
217 		'!_read': function (success, error, stack) {
218 			var obj = this;
219 			if (obj._fetched) {
220 				if (success) {
221 					obj._invoke(success, [obj]);
222 				}
223 				return;
224 			}
225 
226 			if (obj.multichannelling) {
227 				obj.multichannelling.read(obj, success, error);
228 				return;
229 			}
230 
231 			var id = obj.id();
232 
233 			if (null === id || undefined === id) {
234 				obj._getIdFromParent(function () {
235 					obj._read(success, error, stack);
236 				}, error, stack);
237 				return;
238 			}
239 
240 			obj.fetch(function (response) {
241 				obj._processResponse(response);
242 				obj._fetched = true;
243 				if (success) {
244 					obj._invoke(success, [obj]);
245 				}
246 			}, error, stack);
247 		},
248 
249 		/**
250 		 * Retrieves this object's id from its parent.  This function is used
251 		 * in order for this object to be able to fetch its data from the
252 		 * backend.
253 		 *
254 		 * FIXME: If the id that `obj` aquires results in it having a hash that
255 		 * is found in the cache, then `obj` should not replace the object that
256 		 * was in the cache, rather, `obj` should be masked by the object in the
257 		 * cache.  This scenario will arise in the following scenario:
258 		 *
259 		 * page.node().constructs();
260 		 * page.node().folders();
261 		 *
262 		 * The above will cause the same node to be fetched from the server
263 		 * twice, each time, clobbering the previosly loaded data in the cache.
264 		 *
265 		 * @ignore
266 		 * @private
267 		 * @param {function(ContentObjectAPI)=} success Optional callback that
268 		 *                                              receives this object as
269 		 *                                              its only argument.
270 		 * @param {function(GCNError):boolean=} error Optional customer error
271 		 *                                            handler.
272 		 * @throws CANNOT_GET_OBJECT_ID
273 		 */
274 		'!_getIdFromParent': function (success, error, stack) {
275 			var parent = this._ancestor();
276 
277 			if (!parent) {
278 				var err = GCN.createError('CANNOT_GET_OBJECT_ID',
279 					'Cannot get an id for object', this);
280 				GCN.handleError(err, error);
281 				return;
282 			}
283 
284 			var that = this;
285 
286 			parent._read(function () {
287 				if ('folder' === that._type) {
288 					// There are 3 possible property names that an object can
289 					// use to hold the id of the folder that it is related to:
290 					//
291 					// "folderId": for pages, templates, files, and images.
292 					// "motherId": for folders
293 					// "nodeId":   for nodes
294 					//
295 					// We need to see which of this properties is set, the
296 					// first one we find will be our folder's id.
297 					var props = ['folderId', 'motherId', 'nodeId'];
298 					var prop = props.pop();
299 					var id;
300 
301 					while (prop) {
302 						id = parent.prop(prop);
303 						if (typeof id !== 'undefined') {
304 							break;
305 						}
306 						prop = props.pop();
307 					}
308 
309 					that._data.id = id;
310 				} else {
311 					that._data.id = parent.prop(that._type + 'Id');
312 				}
313 
314 				if (that._data.id === null || typeof that._data.id === 'undefined') {
315 					var err = GCN.createError('CANNOT_GET_OBJECT_ID',
316 						'Cannot get an id for object', this);
317 					GCN.handleError(err, error);
318 					return;
319 				}
320 
321 				that._setHash(that._data.id)._addToCache();
322 
323 				if (success) {
324 					success();
325 				}
326 			}, error, stack);
327 		},
328 
329 		/**
330 		 * Gets this object's node id. If used in a multichannelling is enabled
331 		 * it will return the channel id or 0 if no channel was set.
332 		 * 
333 		 * @public
334 		 * @function
335 		 * @name nodeId
336 		 * @memberOf ContentObjectAPI
337 		 * @return {number} The channel to which this object is set. 0 if no
338 		 *         channel is set.
339 		 */
340 		'!nodeId': function () {
341 			return this._channel || 0;
342 		},
343 
344 		/**
345 		 * Gets this object's id. We'll return the id of the object when it has
346 		 * been loaded - this can only be a localid. Otherwise we'll return the
347 		 * id which was provided by the user. This can either be a localid or a
348 		 * globalid.
349 		 *
350 		 * @name id
351 		 * @function
352 		 * @memberOf ContentObjectAPI
353 		 * @public
354 		 * @return {number}
355 		 */
356 		'!id': function () {
357 			return this._data.id;
358 		},
359 
360 		/**
361 		 * Alias for {@link ContentObjectAPI#id}
362 		 *
363 		 * @name localId
364 		 * @function
365 		 * @memberOf ContentObjectAPI
366 		 * @private
367 		 * @return {number}
368 		 * @decprecated
369 		 */
370 		'!localId': function () {
371 			return this.id();
372 		},
373 
374 		/**
375 		 * Update the `_shadow' object that maintains changes to properties
376 		 * that reflected the internal `_data' object.  This shadow object is
377 		 * used to persist differential changes to a REST API object.
378 		 *
379 		 * @ignore
380 		 * @private
381 		 * @param {string} path The path through the object to the property we
382 		 *                      want to modify if a node in the path contains
383 		 *                      dots, then these dots should be escaped.  This
384 		 *                      can be done using the GCN.escapePropertyName()
385 		 *                      convenience function.
386 		 * @param {*} value The value we wish to set the property to.
387 		 * @param {function=} error Custom error handler.
388 		 * @param {boolean=} force If true, no error will be thrown if `path'
389 		 *                         cannot be fully resolved against the
390 		 *                         internal `_data' object, instead, the path
391 		 *                         will be created on the shadow object.
392 		 */
393 		'!_update': function (pathStr, value, error, force) {
394 			var boundary = Math.random().toString(8).substring(2);
395 			var path = pathStr.replace(/\./g, boundary)
396 			                  .replace(new RegExp('\\\\' + boundary, 'g'), '.')
397 			                  .split(boundary);
398 			var shadow = this._shadow;
399 			var actual = this._data;
400 			var i = 0;
401 			var numPathNodes = path.length;
402 			var pathNode;
403 			// Whether or not the traversal path in `_data' and `_shadow' are
404 			// at the same position in the respective objects.
405 			var areMirrored = true;
406 
407 			while (true) {
408 				pathNode = path[i++];
409 
410 				if (areMirrored) {
411 					actual = actual[pathNode];
412 					areMirrored = jQuery.type(actual) !== 'undefined';
413 				}
414 
415 				if (i === numPathNodes) {
416 					break;
417 				}
418 
419 				if (shadow[pathNode]) {
420 					shadow = shadow[pathNode];
421 				} else if (areMirrored || force) {
422 					shadow = (shadow[pathNode] = {});
423 				} else {
424 					break; // goto error
425 				}
426 			}
427 
428 			if (i === numPathNodes && (areMirrored || force)) {
429 				shadow[pathNode] = value;
430 			} else {
431 				var err = GCN.createError('TYPE_ERROR', 'Object "' +
432 					path.slice(0, i).join('.') + '" does not exist',
433 					actual);
434 				GCN.handleError(err, error);
435 			}
436 		},
437 
438 		/**
439 		 * Receives the response from a REST API request, and adds any new data
440 		 * in the internal `_data' object.
441 		 *
442 		 * Note that data already present in `_data' will not be removed or
443 		 * overwritten.
444 		 *
445 		 * @private
446 		 * @param {object} data Parsed JSON response data.
447 		 */
448 		'!_processResponse': function (data) {
449 			this._data = jQuery.extend(true, {}, data[this._type], this._data);
450 		},
451 
452 		/**
453 		 * Specifies a list of parameters that will be added to the url when
454 		 * loading the content object from the server.
455 		 *
456 		 * @private
457 		 * @return {object} object With parameters to be appended to the load
458 		 *                         request
459 		 */
460 		'!_loadParams': function () {},
461 
462 		/**
463 		 * Reads the property `property' of this content object if this
464 		 * property is among those in the WRITEABLE_PROPS array. If a second
465 		 * argument is provided, them the property is updated with that value.
466 		 *
467 		 * @name prop
468 		 * @function
469 		 * @memberOf ContentObjectAPI
470 		 * @param {String} property Name of the property to be read or updated.
471 		 * @param {String} value Optional value to set property to. If omitted the property will just be read.
472 		 * @param {function(GCNError):boolean=} error Custom error handler to 
473 		 *                                      stop error propagation for this
474 		 *                                      synchronous call. 
475 		 * @return {?*} Meta attribute.
476 		 * @throws UNFETCHED_OBJECT_ACCESS if the object has not been fetched from the server yet
477 		 * @throws READONLY_ATTRIBUTE whenever trying to write to an attribute that's readonly
478 		 */
479 		'!prop': function (property, value, error) {
480 			if (!this._fetched) {
481 				GCN.handleError(GCN.createError(
482 					'UNFETCHED_OBJECT_ACCESS',
483 					'Object not fetched yet.'
484 				), error);
485 				return;
486 			}
487 
488 			if (typeof value !== 'undefined') {
489 				// Check whether the property is writable
490 				if (jQuery.inArray(property, this.WRITEABLE_PROPS) >= 0) {
491 					// Check wether the property has a constraint and verify it
492 					var constraint = this.WRITEABLE_PROPS_CONSTRAINTS[property];
493 					if (constraint) {
494 						// verify maxLength
495 						if (constraint.maxLength && value.length >= constraint.maxLength) {
496 							var data = { name: property, value: value, maxLength: constraint.maxLength };
497 							var constraintError = GCN.createError('ATTRIBUTE_CONSTRAINT_VIOLATION',
498 								'Attribute "' + property + '" of ' + this._type +
499 								' is too long. The \'maxLength\' was set to {' + constraint.maxLength + '} ', data);
500 							GCN.handleError(constraintError, error);
501 							return;
502 						}
503 					}
504 					this._update(GCN.escapePropertyName(property), value);
505 				} else {
506 					GCN.handleError(GCN.createError('READONLY_ATTRIBUTE',
507 						'Attribute "' + property + '" of ' + this._type +
508 						' is read-only. Writeable properties are: ' +
509 						this.WRITEABLE_PROPS, this.WRITEABLE_PROPS), error);
510 					return;
511 				}
512 			}
513 
514 			return (
515 				(jQuery.type(this._shadow[property]) !== 'undefined'
516 					? this._shadow
517 					: this._data)[property]
518 			);
519 		},
520 
521 		/**
522 		 * Sends the a template string to the Aloha Servlet for rendering.
523 		 *
524 		 * @ignore
525 		 * @TODO: Consider making this function public.  At least one developer
526 		 *        has had need to render a custom template for a content
527 		 *        object.
528 		 *
529 		 * @private
530 		 * @param {string} template Template which will be rendered.
531 		 * @param {string} mode The rendering mode.  Valid values are "view",
532 		 *                      "edit", "pub."
533 		 * @param {function(object)} success A callback the receives the render
534 		 *                                   response.
535 		 * @param {function(GCNError):boolean} error Error handler.
536 		 * @param {boolean} post flag to POST the data
537 		 */
538 		'!_renderTemplate' : function (template, mode, success, error, post) {
539 			var channelParam = GCN._getChannelParameter(this);
540 			var url = GCN.settings.BACKEND_PATH +
541 					'/rest/' + this._type +
542 					(post ? '/render' : '/render/' + this.id()) +
543 			        channelParam +
544 			        (channelParam ? '&' : '?') +
545 			        'edit=' + ('edit' === mode) +
546 			        '&template=' + encodeURIComponent(template);
547 			if (mode === 'edit') {
548 				url += '&links=' + encodeURIComponent(GCN.settings.linksRenderMode);
549 			}
550 			if (post) {
551 				var jsonData = jQuery.extend({}, this._data);
552 				// remove some data, we don't want to serialize and POST to the server
553 				jsonData.pageVariants = null;
554 				jsonData.languageVariants = null;
555 				this._authAjax({
556 					type: 'POST',
557 					json: jsonData,
558 					url: url,
559 					error: error,
560 					success: success
561 				});
562 			} else {
563 				this._authAjax({
564 					url: url,
565 					error: error,
566 					success: success
567 				});
568 			}
569 		},
570 
571 		/**
572 		 * Wrapper for internal chainback _ajax method.
573 		 * 
574 		 * @ignore
575 		 * @private
576 		 * @param {object<string, *>} settings Settings for the ajax request.
577 		 *                                     The settings object is identical
578 		 *                                     to that of the `GCN.ajax'
579 		 *                                     method, which handles the actual
580 		 *                                     ajax transportation.
581 		 * @throws AJAX_ERROR
582 		 */
583 		'!_ajax': function (settings) {
584 			var that = this;
585 
586 			// force no cache for all API calls
587 			settings.cache = false;
588 			settings.success = (function (onSuccess, onError) {
589 				return function (data) {
590 					// Ajax calls that do not target the REST API servlet do
591 					// not response data with a `responseInfo' object.
592 					// "/CNPortletapp/alohatag" is an example.  So we cannot
593 					// just assume that it exists.
594 					if (data.responseInfo) {
595 						switch (data.responseInfo.responseCode) {
596 						case 'OK':
597 							break;
598 						case 'AUTHREQUIRED':
599 							GCN.clearSession();
600 							that._authAjax(settings);
601 							return;
602 						default:
603 							// Since GCN.handleResponseError can throw an error,
604 							// we pass this function to _invoke, so the error is caught,
605 							// remembered and thrown in the end.
606 							that._invoke(GCN.handleResponseError, [data, onError]);
607 							return;
608 						}
609 					}
610 
611 					if (onSuccess) {
612 						onSuccess(data);
613 					}
614 				};
615 			}(settings.success, settings.error, settings.url));
616 
617 			this._queueAjax(settings);
618 		},
619 
620 		/**
621 		 * Concrete implementatation of _fulfill().
622 		 *
623 		 * Resolves all promises made by this content object while ensuring
624 		 * that circularReferences, (which are completely possible, and valid)
625 		 * do not result in infinit recursion.
626 		 *
627 		 * @override
628 		 */
629 		'!_fulfill': function (success, error, stack) {
630 			var obj = this;
631 			if (obj._chain) {
632 				var circularReference =
633 						stack && -1 < jQuery.inArray(obj._chain, stack);
634 				if (!circularReference) {
635 					stack = stack || [];
636 					stack.push(obj._chain);
637 					obj._fulfill(function () {
638 						obj._read(success, error);
639 					}, error, stack);
640 					return;
641 				}
642 			}
643 			obj._read(success, error);
644 		},
645 
646 		/**
647 		 * Similar to `_ajax', except that it prefixes the ajax url with the
648 		 * current session's `sid', and will trigger an
649 		 * `authentication-required' event if the session is not authenticated.
650 		 *
651 		 * @ignore
652 		 * @TODO(petro): Consider simplifiying this function signature to read:
653 		 *               `_auth( url, success, error )'
654 		 *
655 		 * @private
656 		 * @param {object<string, *>} settings Settings for the ajax request.
657 		 * @throws AUTHENTICATION_FAILED
658 		 */
659 		_authAjax: function (settings) {
660 			var that = this;
661 
662 			if (GCN.isAuthenticating) {
663 				GCN.afterNextAuthentication(function () {
664 					that._authAjax(settings);
665 				});
666 				return;
667 			}
668 
669 			if (!GCN.sid) {
670 				var cancel;
671 
672 				if (settings.error) {
673 					/**
674 					 * @ignore
675 					 */
676 					cancel = function (error) {
677 						GCN.handleError(
678 							error || GCN.createError('AUTHENTICATION_FAILED'),
679 							settings.error
680 						);
681 					};
682 				} else {
683 					/**
684 					 * @ignore
685 					 */
686 					cancel = function (error) {
687 						if (error) {
688 							GCN.error(error.code, error.message, error.data);
689 						} else {
690 							GCN.error('AUTHENTICATION_FAILED');
691 						}
692 					};
693 				}
694 
695 				GCN.afterNextAuthentication(function () {
696 					that._authAjax(settings);
697 				});
698 
699 				if (GCN.usingSSO) {
700 					// First, try to automatically authenticate via
701 					// Single-SignOn
702 					GCN.loginWithSSO(GCN.onAuthenticated, function () {
703 						// ... if SSO fails, then fallback to requesting user
704 						// credentials: broadcast `authentication-required'
705 						// message.
706 						GCN.authenticate(cancel);
707 					});
708 				} else {
709 					// Trigger the `authentication-required' event to request
710 					// user credentials.
711 					GCN.authenticate(cancel);
712 				}
713 
714 				return;
715 			}
716 
717 			// Append "?sid=..." or "&sid=..." if needed.
718 
719 			var urlFragment = settings.url.substr(
720 				GCN.settings.BACKEND_PATH.length
721 			);
722 			var isSidInUrl = /[\?\&]sid=/.test(urlFragment);
723 			if (!isSidInUrl) {
724 				var isFirstParam = (jQuery.inArray('?',
725 					urlFragment.split('')) === -1);
726 				settings.url += (isFirstParam ? '?' : '&') + 'sid='
727 				             +  (GCN.sid || '');
728 			}
729 
730 			this._ajax(settings);
731 		},
732 
733 		/**
734 		 * Recursively call `_continueWith()'.
735 		 *
736 		 * @ignore
737 		 * @private
738 		 * @override
739 		 */
740 		'!_onContinue': function (success, error) {
741 			var that = this;
742 			this._continueWith(function () {
743 				that._read(success, error);
744 			}, error);
745 		},
746 
747 		/**
748 		 * Initializes this content object.  If a `success' callback is
749 		 * provided, it will cause this object's data to be fetched and passed
750 		 * to the callback.  This object's data will be fetched from the cache
751 		 * if is available, otherwise it will be fetched from the server.  If
752 		 * this content object API contains parent chainbacks, it will get its
753 		 * parent to fetch its own data first.
754 		 *
755 		 * <p>
756 		 * Basic content object implementation which all other content objects
757 		 * will inherit from.
758 		 * </p>
759 		 * 
760 		 * <p>
761 		 * If a `success' callback is provided,
762 		 * it will cause this object's data to be fetched and passed to the
763 		 * callback. This object's data will be fetched from the cache if is
764 		 * available, otherwise it will be fetched from the server. If this
765 		 * content object API contains parent chainbacks, it will get its parent
766 		 * to fetch its own data first.
767 		 * </p>
768 		 * 
769 		 * <p>
770 		 * You might also provide an object for initialization, to directly
771 		 * instantiate the object's data without loading it from the server. To
772 		 * do so just pass in a data object as received from the server instead
773 		 * of an id--just make sure this object has an `id' property.
774 		 * </p>
775 		 * 
776 		 * <p>
777 		 * If an `error' handler is provided, as the third parameter, it will
778 		 * catch any errors that have occured since the invocation of this call.
779 		 * It allows the global error handler to be intercepted before stopping
780 		 * the error or allowing it to propagate on to the global handler.
781 		 * </p>
782 		 * 
783 		 * @class
784 		 * @name ContentObjectAPI
785 		 * @param {number|string|object}
786 		 *            id
787 		 * @param {function(ContentObjectAPI))=}
788 		 *            success Optional success callback that will receive this
789 		 *            object as its only argument.
790 		 * @param {function(GCNError):boolean=}
791 		 *            error Optional custom error handler.
792 		 * @param {object}
793 		 *            settings Basic settings for this object - depends on the
794 		 *            ContentObjetAPI Object used.
795 		 * @throws INVALID_DATA
796 		 *             If no id is found when providing an object for
797 		 *             initialization.
798 		 */
799 		_init: function (data, success, error, settings) {
800 			this._settings = settings;
801 			var id;
802 
803 			if (jQuery.type(data) === 'object') {
804 				if (data.multichannelling) {
805 					this.multichannelling = data;
806 					// Remove the inherited object from the chain.
807 					if (this._chain) {
808 						this._chain = this._chain._chain;
809 					}
810 					id = this.multichannelling.derivedFrom.id();
811 				} else {
812 					if (!data.id) {
813 						var err = GCN.createError(
814 							'INVALID_DATA',
815 							'Data not sufficient for initalization: id is missing',
816 							data
817 						);
818 						GCN.handleError(err, error);
819 						return;
820 					}
821 					this._data = data;
822 					this._fetched = true;
823 					if (success) {
824 						this._invoke(success, [this]);
825 					}
826 					return;
827 				}
828 			} else {
829 				id = data;
830 			}
831 
832 			// Ensure that each object has its very own `_data' and `_shadow'
833 			// objects.
834 			if (!this._fetched) {
835 				this._data = {};
836 				this._shadow = {};
837 				this._data.id = id;
838 			}
839 			if (success) {
840 				this._read(success, error);
841 			}
842 		},
843 
844 		/**
845 		 * <p>
846 		 * Replaces tag blocks and editables with appropriate "<node *>"
847 		 * notation in a given string. Given an element whose innerHTML is:
848 		 *
849 		 * <pre>
850 		 *		<span id="GENTICS_BLOCK_123">My Tag</span>
851 		 * </pre>
852 		 *
853 		 * <p>
854 		 * encode() will return:
855 		 *
856 		 * <pre>
857 		 *		<node 123>
858 		 * </pre>
859 		 *
860 		 * @name encode
861 		 * @function
862 		 * @memberOf ContentObjectAPI
863 		 * @param {!jQuery} $element
864 		 *       An element whose contents are to be encoded.
865 		 * @param {?function(!Element): string} serializeFn
866 		 *       A function that returns the serialized contents of the
867 		 *       given element as a HTML string, excluding the start and end
868 		 *       tag of the element. If not provided, jQuery.html() will
869 		 *       be used.
870 		 * @return {string} The encoded HTML string.
871 		 */
872 		'!encode': function ($element, serializeFn) {
873 			var $clone = $element.clone();
874 			var id;
875 			var $block;
876 			var tags = jQuery.extend({}, this._blocks, this._editables);
877 			for (id in tags) {
878 				if (tags.hasOwnProperty(id)) {
879 					$block = $clone.find('#' + tags[id].element);
880 					if ($block.length) {
881 						// Empty all content blocks of their innerHTML.
882 						$block.html('').attr('id', BLOCK_ENCODING_PREFIX +
883 							tags[id].tagname);
884 					}
885 				}
886 			}
887 			serializeFn = serializeFn || function ($element) {
888 				return jQuery($element).html();
889 			};
890 			var html = serializeFn($clone[0]);
891 			return html.replace(CONTENT_BLOCK, function (substr, match) {
892 				return '<node ' + match + '>';
893 			});
894 		},
895 
896 		/**
897 		 * For a given string, replace all occurances of "<node>" with
898 		 * appropriate HTML markup, allowing notated tags to be rendered within
899 		 * the surrounding HTML content.
900 		 *
901 		 * The success() handler will receives a string containing the contents
902 		 * of the `str' string with references to "<node>" having been inflated
903 		 * into their appropriate tag rendering.
904 		 *
905 		 * @name decode
906 		 * @function
907 		 * @memberOf ContentObjectAPI
908 		 * @param {string} str The content string, in which  "<node *>" tags
909 		 *                     will be inflated with their HTML rendering.
910 		 * @param {function(ContentObjectAPI))} success Success callback that
911 		 *                                              will receive the
912 		 *                                              decoded string.
913 		 * @param {function(GCNError):boolean=} error Optional custom error
914 		 *                                            handler.
915 		 */
916 		'!decode': function (str, success, error) {
917 			if (!success) {
918 				return;
919 			}
920 
921 			var prefix = 'gcn-tag-placeholder-';
922 			var toRender = [];
923 			var html = replaceNodeTags(str, function (name, offset, str) {
924 				toRender.push('<node ', name, '>');
925 				return '<div id="' + prefix + name + '"></div>';
926 			});
927 
928 			if (!toRender.length) {
929 				success(html);
930 				return;
931 			}
932 
933 			// Instead of rendering each tag individually, we render them
934 			// together in one string, and map the results back into our
935 			// original html string.  This allows us to perform one request to
936 			// the server for any number of node tags found.
937 
938 			var parsed = jQuery('<div>' + html + '</div>');
939 			var template = toRender.join('');
940 			var that = this;
941 
942 			this._renderTemplate(template, 'edit', function (data) {
943 				var content = data.content;
944 				var tag;
945 				var tags = data.tags;
946 				var j = tags.length;
947 				var rendered = jQuery('<div>' + content + '</div>');
948 
949 				var replaceTag = (function (numTags) {
950 					return function (tag) {
951 						parsed.find('#' + prefix + tag.prop('name'))
952 							.replaceWith(
953 								rendered.find('#' + tag.prop('id'))
954 							);
955 
956 						if (0 === --numTags) {
957 							success(parsed.html());
958 						}
959 					};
960 				}(j));
961 
962 				while (j) {
963 					that.tag(tags[--j], replaceTag);
964 				}
965 			}, error);
966 		},
967 
968 		/**
969 		 * Clears this object from its constructor's cache so that the next
970 		 * attempt to access this object will result in a brand new instance
971 		 * being initialized and placed in the cache.
972 		 *
973 		 * @name clear
974 		 * @function
975 		 * @memberOf ContentObjectAPI
976 		 */
977 		'!clear': function () {
978 			// Do not clear the id from the _data.
979 			var id = this._data.id;
980 			this._data = {};
981 			this._data.id = id;
982 			this._shadow = {};
983 			this._fetched = false;
984 			this._clearCache();
985 		},
986 
987 		/**
988 		 * Retrieves this objects parent folder.
989 		 * 
990 		 * @name folder
991 		 * @function
992 		 * @memberOf ContentObjectAPI
993 		 * @param {function(FolderAPI)=}
994 		 *            success Callback that will receive the requested object.
995 		 * @param {function(GCNError):boolean=}
996 		 *            error Custom error handler.
997 		 * @return {FolderAPI} API object for the retrieved GCN folder.
998 		 */
999 		'!folder': function (success, error) {
1000 			return this._continue(GCN.FolderAPI, this._data.folderId, success,
1001 				error);
1002 		},
1003 
1004 		/**
1005 		 * Saves changes made to this content object to the backend.
1006 		 * 
1007 		 * @param {object=}
1008 		 *            settings Optional settings to pass on to the ajax
1009 		 *            function.
1010 		 * @param {function(ContentObjectAPI)=}
1011 		 *            success Optional callback that receives this object as its
1012 		 *            only argument.
1013 		 * @param {function(GCNError):boolean=}
1014 		 *            error Optional customer error handler.
1015 		 */
1016 		save: function () {
1017 			var settings;
1018 			var success;
1019 			var error;
1020 			var args = Array.prototype.slice.call(arguments);
1021 			var len = args.length;
1022 			var i;
1023 
1024 			for (i = 0; i < len; ++i) {
1025 				switch (jQuery.type(args[i])) {
1026 				case 'object':
1027 					if (!settings) {
1028 						settings = args[i];
1029 					}
1030 					break;
1031 				case 'function':
1032 					if (!success) {
1033 						success = args[i];
1034 					} else {
1035 						error = args[i];
1036 					}
1037 					break;
1038 				case 'undefined':
1039 					break;
1040 				default:
1041 					var err = GCN.createError('UNKNOWN_ARGUMENT',
1042 						'Don\'t know what to do with arguments[' + i + '] ' +
1043 						'value: "' + args[i] + '"', args);
1044 					GCN.handleError(err, error);
1045 					return;
1046 				}
1047 			}
1048 
1049 			this._save(settings, success, error);
1050 		},
1051 
1052 		/**
1053 		 * Persists this object's local data onto the server.  If the object
1054 		 * has not yet been fetched we need to get it first so we can update
1055 		 * its internals properly...
1056 		 *
1057 		 * @private
1058 		 * @param {object} settings Object which will extend the basic
1059 		 *                          settings of the ajax call
1060 		 * @param {function(ContentObjectAPI)=} success Optional callback that
1061 		 *                                              receives this object as
1062 		 *                                              its only argument.
1063 		 * @param {function(GCNError):boolean=} error Optional customer error
1064 		 *                                            handler.
1065 		 */
1066 		'!_save': function (settings, success, error) {
1067 			var obj = this;
1068 			this._fulfill(function () {
1069 				GCN.pub(obj._type + '.before-save');
1070 				obj._persist(settings, success, error);
1071 			}, error);
1072 		},
1073 
1074 		/**
1075 		 * Returns the bare data structure of this content object.
1076 		 * To be used for creating the save POST body data.
1077 		 *
1078 		 * @param {object<string, *>} Plain old object representation of this
1079 		 *                            content object.
1080 		 */
1081 		'!json': function () {
1082 			var json = {};
1083 
1084 			if (this._deletedTags.length) {
1085 				json['delete'] = this._deletedTags;
1086 			}
1087 
1088 			if (this._deletedBlocks.length) {
1089 				json['delete'] = json['delete']
1090 				               ? json['delete'].concat(this._deletedBlocks)
1091 				               : this._deletedBlocks;
1092 			}
1093 
1094 			json[this._type] = jQuery.extend(true, {}, this._shadow);
1095 			json[this._type].id = this._data.id;
1096 			return json;
1097 		},
1098 
1099 		/**
1100 		 * Sends the current state of this content object to be stored on the
1101 		 * server.
1102 		 *
1103 		 * @private
1104 		 * @param {function(ContentObjectAPI)=} success Optional callback that
1105 		 *                                              receives this object as
1106 		 *                                              its only argument.
1107 		 * @param {function(GCNError):boolean=} error Optional customer error
1108 		 *                                            handler.
1109 		 * @throws HTTP_ERROR
1110 		 */
1111 		_persist: function (settings, success, error) {
1112 			var that = this;
1113 
1114 			if (!this._fetched) {
1115 				this._read(function () {
1116 					that._persist(settings, success, error);
1117 				}, error);
1118 				return;
1119 			}
1120 
1121 			this._authAjax({
1122 				url   : GCN.settings.BACKEND_PATH + '/rest/'
1123 				        + this._type + '/save/' + this.id()
1124 				        + GCN._getChannelParameter(this),
1125 				type  : 'POST',
1126 				error : error,
1127 				json  : jQuery.extend(this.json(), settings),
1128 				success : function (response) {
1129 					// We must not overwrite the `_data.tags' object with this
1130 					// one.
1131 					delete that._shadow.tags;
1132 
1133 					// Everything else in `_shadow' should be written over to
1134 					// `_data' before resetting the `_shadow' object.
1135 					jQuery.extend(that._data, that._shadow);
1136 					that._shadow = {};
1137 					that._deletedTags = [];
1138 					that._deletedBlocks = [];
1139 
1140 					if (success) {
1141 						that._invoke(success, [that]);
1142 					}
1143 				}
1144 			});
1145 		},
1146 
1147 		/**
1148 		 * Deletes this content object from its containing parent.
1149 		 * 
1150 		 * @param {function(ContentObjectAPI)=}
1151 		 *            success Optional callback that receives this object as its
1152 		 *            only argument.
1153 		 * @param {function(GCNError):boolean=}
1154 		 *            error Optional customer error handler.
1155 		 */
1156 		remove: function (success, error) {
1157 			this._remove(success, error);
1158 		},
1159 
1160 		/**
1161 		 * Get a channel-local copy of this content object.
1162 		 *
1163 		 * @public
1164 		 * @function
1165 		 * @name localize
1166 		 * @memberOf ContentObjectAPI
1167 		 * @param {funtion(ContentObjectAPI)=} success Optional callback to
1168 		 *                                             receive this content
1169 		 *                                             object as the only
1170 		 *                                             argument.
1171 		 * @param {function(GCNError):boolean=} error Optional custom error
1172 		 *                                            handler.
1173 		 */
1174 		'!localize': function (success, error) {
1175 			if (!this._channel && !GCN.channel()) {
1176 				var err = GCN.createError(
1177 					'NO_CHANNEL_ID_SET',
1178 					'No channel is set in which to get the localized object',
1179 					GCN
1180 				);
1181 				GCN.handleError(err, error);
1182 				return false;
1183 			}
1184 			var local = this._continue(
1185 				this._constructor,
1186 				{
1187 					derivedFrom: this,
1188 					multichannelling: true,
1189 					read: GCN.multichannelling.localize
1190 				},
1191 				success,
1192 				error
1193 			);
1194 			return local;
1195 		},
1196 
1197 		/**
1198 		 * Remove this channel-local object, and delete its local copy in the
1199 		 * backend.
1200 		 *
1201 		 * @public
1202 		 * @function
1203 		 * @name unlocalize
1204 		 * @memberOf ContentObjectAPI
1205 		 * @param {funtion(ContentObjectAPI)=} success Optional callback to
1206 		 *                                             receive this content
1207 		 *                                             object as the only
1208 		 *                                             argument.
1209 		 * @param {function(GCNError):boolean=} error Optional custom error
1210 		 *                                            handler.
1211 		 */
1212 		'!unlocalize': function (success, error) {
1213 			if (!this._channel && !GCN.channel()) {
1214 				var err = GCN.createError(
1215 					'NO_CHANNEL_ID_SET',
1216 					'No channel is set in which to get the unlocalized object',
1217 					GCN
1218 				);
1219 				GCN.handleError(err, error);
1220 				return false;
1221 			}
1222 			var placeholder = {
1223 				multichannelling: {
1224 					derivedFrom: this
1225 				}
1226 			};
1227 			var that = this;
1228 			GCN.multichannelling.unlocalize(placeholder, function () {
1229 				// TODO: This should be done inside of
1230 				// multichannelling.unlocalize() and not in this callback.
1231 				// Clean cache & reset object to make sure it can't be used
1232 				// properly any more.
1233 				that._clearCache();
1234 				that._data = {};
1235 				that._shadow = {};
1236 				if (success) {
1237 					success();
1238 				}
1239 			}, error);
1240 		},
1241 
1242 		/**
1243 		 * Performs a REST API request to delete this object from the server.
1244 		 *
1245 		 * @private
1246 		 * @param {function()=} success Optional callback that
1247 		 *                                              will be invoked once
1248 		 *                                              this object has been
1249 		 *                                              removed.
1250 		 * @param {function(GCNError):boolean=} error Optional customer error
1251 		 *                                            handler.
1252 		 */
1253 		'!_remove': function (success, error) {
1254 			var that = this;
1255 			this._authAjax({
1256 				url     : GCN.settings.BACKEND_PATH + '/rest/'
1257 				          + this._type + '/delete/' + this.id()
1258 				          + GCN._getChannelParameter(that),
1259 				type    : 'POST',
1260 				error   : error,
1261 				success : function (response) {
1262 					// Clean cache & reset object to make sure it can't be used
1263 					// properly any more.
1264 					that._clearCache();
1265 					that._data = {};
1266 					that._shadow = {};
1267 
1268 					// Don't forward the object to the success handler since
1269 					// it's been deleted.
1270 					if (success) {
1271 						that._invoke(success);
1272 					}
1273 				}
1274 			});
1275 		},
1276 
1277 		/**
1278 		 * Removes any additionaly data stored on this objec which pertains to
1279 		 * a tag matching the given tagname.  This function will be called when
1280 		 * a tag is being removed in order to bring the content object to a
1281 		 * consistant state.
1282 		 * Should be overriden by subclasses.
1283 		 *
1284 		 * @param {string} tagid The Id of the tag whose associated data we
1285 		 *                       want we want to remove.
1286 		 */
1287 		'!_removeAssociatedTagData': function (tagname) {},
1288 
1289 		/**
1290 		 * Return the replacement value, when this object is transformed to stringified JSON.
1291 		 * This is necessary to avoid endless loops, because objects may have chainback objects
1292 		 * stored in their _data.
1293 		 * 
1294 		 * @private
1295 		 * @param {string} key
1296 		 * @return {object} _data
1297 		 */
1298 		'!toJSON': function (key) {
1299 			return this._data;
1300 		}
1301 	});
1302 
1303 	GCN.ContentObjectAPI.update = update;
1304 
1305 	/**
1306 	 * Generates a factory method for chainback classes.  The method signature
1307 	 * used with this factory function will match that of the target class'
1308 	 * constructor.  Therefore this function is expected to be invoked with the
1309 	 * follow combination of arguments ...
1310 	 *
1311 	 * Examples for GCN.pages api:
1312 	 *
1313 	 * To get an array containing 1 page:
1314 	 * pages(1)
1315 	 * pages(1, function () {})
1316 	 *
1317 	 * To get an array containing 2 pages:
1318 	 * pages([1, 2])
1319 	 * pages([1, 2], function () {})
1320 	 *
1321 	 * To get an array containing any and all pages:
1322 	 * pages()
1323 	 * pages(function () {})
1324 	 *
1325 	 * To get an array containing no pages:
1326 	 * pages([])
1327 	 * pages([], function () {});
1328 	 *
1329 	 * @param {Chainback} ctor The Chainback constructor we want to expose.
1330 	 * @throws UNKNOWN_ARGUMENT
1331 	 */
1332 	GCN.exposeAPI = function (ctor) {
1333 		return function () {
1334 			// Convert arguments into an array
1335 			// https://developer.mozilla.org/en/JavaScript/Reference/...
1336 			// ...Functions_and_function_scope/arguments
1337 			var args = Array.prototype.slice.call(arguments);
1338 			var id;
1339 			var ids;
1340 			var success;
1341 			var error;
1342 			var settings;
1343 
1344 			// iterate over arguments to find id || ids, succes, error and
1345 			// settings
1346 			jQuery.each(args, function (i, arg) {
1347 				switch (jQuery.type(arg)) {
1348 				// set id
1349 				case 'string':
1350 				case 'number':
1351 					if (!id && !ids) {
1352 						id = arg;
1353 					} else {
1354 						GCN.error('UNKNOWN_ARGUMENT',
1355 							'id is already set. Don\'t know what to do with ' +
1356 							'arguments[' + i + '] value: "' + arg + '"');
1357 					}
1358 					break;
1359 				// set ids
1360 				case 'array':
1361 					if (!id && !ids) {
1362 						ids = args[0];
1363 					} else {
1364 						GCN.error('UNKNOWN_ARGUMENT',
1365 							'ids is already set. Don\'t know what to do with' +
1366 							' arguments[' + i + '] value: "' + arg + '"');
1367 					}
1368 					break;
1369 				// success and error handlers
1370 				case 'function':
1371 					if (!success) {
1372 						success = arg;
1373 					} else if (success && !error) {
1374 						error = arg;
1375 					} else {
1376 						GCN.error('UNKNOWN_ARGUMENT',
1377 							'success and error handler already set. Don\'t ' +
1378 							'know what to do with arguments[' + i + ']');
1379 					}
1380 					break;
1381 				// settings
1382 				case 'object':
1383 					if (!id && !ids) {
1384 						id = arg;
1385 					} else if (!settings) {
1386 						settings = arg;
1387 					} else {
1388 						GCN.error('UNKNOWN_ARGUMENT',
1389 							'settings are already present. Don\'t know what ' +
1390 							'to do with arguments[' + i + '] value:' + ' "' +
1391 							arg + '"');
1392 					}
1393 					break;
1394 				default:
1395 					GCN.error('UNKNOWN_ARGUMENT',
1396 						'Don\'t know what to do with arguments[' + i +
1397 						'] value: "' + arg + '"');
1398 				}
1399 			});
1400 
1401 			// Prepare a new set of arguments to pass on during initialzation
1402 			// of callee object.
1403 			args = [];
1404 
1405 			// settings should always be an object, even if it's just empty
1406 			if (!settings) {
1407 				settings = {};
1408 			}
1409 
1410 			args[0] = (typeof id !== 'undefined') ? id : ids;
1411 			args[1] = success || settings.success || null;
1412 			args[2] = error || settings.error || null;
1413 			args[3] = settings;
1414 
1415 			// We either add 0 (no channel) or the channelid to the hash
1416 			var channel = GCN.settings.channel;
1417 
1418 			// Check if the value is false, and set it to 0 in this case
1419 			if (!channel) {
1420 				channel = 0;
1421 			}
1422 
1423 			var hash = (id || ids)
1424 			         ? ctor._makeHash(channel + '/' + (ids ? ids.sort().join(',') : id))
1425 			         : null;
1426 
1427 			return GCN.getChainback(ctor, hash, null, args);
1428 		};
1429 
1430 	};
1431 
1432 }(GCN));
1433