1 (function (GCN) {
  2 
  3 	'use strict';
  4 
  5 	/**
  6 	 * @const
  7 	 * @type {string} All rendered content blocks come with this prefixed to
  8 	 *                their id.
  9 	 */
 10 	var BLOCK_ENCODING_PREFIX = 'GCN_BLOCK_TMP__';
 11 
 12 	/**
 13 	 * @type {RexExp} Will match <span id="GENTICS_block_123"></span>" but not
 14 	 *                "<node abc123>" tags.  The first backreference contains
 15 	 *                the tagname of the tag corresponding to this block.
 16 	 */
 17 	var contentBlockRegExp = new RegExp(
 18 			'<(?!node)[a-z]+\\s'        + // "<span or "<div " but not "<node "
 19 				'[^>]*?'                + // ...
 20 				'id\\s*=\\s*[\\"\\\']?' + // "id = '"
 21 				BLOCK_ENCODING_PREFIX   + // "GCN_BLOCK_TMP__"
 22 				'([^\\"\\\'\\s>]+)'     + // "_abc-123"
 23 				'[\\"\\\']?[^>]*>'      + // "' ...>"
 24 				'<\\s*\\/[a-z]+>',        // "</span>" or "</div>"
 25 			'gim'
 26 		);
 27 
 28 	/**
 29 	 * @private
 30 	 * @type {RegExp} Will match <node foo> or <node bar_123> or <node foo-bar>
 31 	 *                but not <node "blah">.
 32 	 */
 33 	var nodeNotationRegExp = /<node ([a-z0-9_\-]+?)>/gim;
 34 
 35 	/**
 36 	 * Examins a string for "<node>" tags, and for each occurance of this
 37 	 * notation, the given callback will be invoked to manipulate the string.
 38 	 *
 39 	 * @private
 40 	 * @static
 41 	 * @param {string} str The string that will be examined for "<node>" tags.
 42 	 * @param {function} onMatchFound Callback function that should receive the
 43 	 *                                following three parameters:
 44 	 *
 45 	 *                    name:string The name of the tag being notated by the
 46 	 *                                node substring.  If the `str' arguments
 47 	 *                                is "<node myTag>", then the `name' value
 48 	 *                                will be "myTag".
 49 	 *                  offset:number The offset where the node substring was
 50 	 *                                found within the examined string.
 51 	 *                     str:string The string in which the "<node *>"
 52 	 *                                substring occured.
 53 	 *
 54 	 *                                The return value of the function will
 55 	 *                                replace the entire "<node>" substring
 56 	 *                                that was passed to it within the examined
 57 	 *                                string.
 58 	 */
 59 	function replaceNodeTags(str, onMatchFound) {
 60 		var parsed = str.replace(nodeNotationRegExp, function (substr, tagname,
 61 		                                                       offset,
 62 		                                                       examined) {
 63 				return onMatchFound(tagname, offset, examined);
 64 			});
 65 
 66 		return parsed;
 67 	}
 68 
 69 	/**
 70 	 * @class
 71 	 * @name ContentObjectAPI
 72 	 */
 73 	GCN.ContentObjectAPI = GCN.defineChainback({
 74 		/** @lends ContentObjectAPI */
 75 
 76 		/**
 77 		 * @private
 78 		 * @type {string} A string denoting a content node type.  This value is
 79 		 *                used to compose the correct REST API ajax urls.  The
 80 		 *                following are valid values: "node", "folder",
 81 		 *                "template", "page", "file", "image".
 82 		 */
 83 		_type: null,
 84 
 85 		/**
 86 		 * @private
 87 		 * @type {object<string,*>} An internal object to store data that we
 88 		 *                          get from the server.
 89 		 */
 90 		_data: {},
 91 
 92 		/**
 93 		 * @private
 94 		 * @type {object<string,*>} An internal object to store updates to
 95 		 *                          the content object.  Should reflect the
 96 		 *                          structural typography of the `_data'
 97 		 *                          object.
 98 		 */
 99 		_shadow: {},
100 
101 		/**
102 		 * @type {boolean} Flags whether or not data for this content object have
103 		 *                 been fetched from the server.
104 		 */
105 		_fetched: false,
106 
107 		/**
108 		 * @private
109 		 * @type {object} will contain an objects internal settings
110 		 */
111 		_settings: null,
112 
113 		/**
114 		 * @public
115 		 * @type {Array.<string} Writeable properties for all content objects.
116 		 */
117 		WRITEABLE_PROPS: [],
118 
119 		/**
120 		 * Fetches this content object's data from the backend.
121 		 *
122 		 * @param {function(object)} success A function to receive the server
123 		 *                                   response.
124 		 * @param {function(GCNError):boolean} error Optional custrom error
125 		 *                                           handler.
126 		 */
127 		'!fetch': function (success, error) {
128 			var channel = GCN.channel();
129 			channel = channel ? '?nodeId=' + channel : '';
130 
131 			var that = this;
132 			var ajax = function () {
133 				that._authAjax({
134 					url     : GCN.settings.BACKEND_PATH + '/rest/' +
135 					          that._type + '/load/' + that.id() + channel,
136 					data    : that._loadParams(),
137 					error   : error,
138 					success : success
139 				});
140 			};
141 
142 			// If this chainback object has an ancestor, then invoke that
143 			// parent's `_read()' method before fetching the data for this
144 			// chainback object.
145 			var parent = this._ancestor();
146 			if (parent) {
147 				parent._read(ajax, error);
148 			} else {
149 				ajax();
150 			}
151 		},
152 
153 		/**
154 		 * Internal method, to fetch this object's data from the server.
155 		 *
156 		 * @private
157 		 * @param {function(ContentObjectAPI)=} success Optional callback that
158 		 *                                              receives this object as
159 		 *                                              its only argument.
160 		 * @param {function(GCNError):boolean=} error Optional customer error
161 		 *                                            handler.
162 		 */
163 		'!_read': function (success, error) {
164 			if (this._fetched) {
165 				if (success) {
166 					success(this);
167 				}
168 
169 				return;
170 			}
171 
172 			var that = this;
173 			var id = this.id();
174 
175 			if (null === id || undefined === id) {
176 				this._getIdFromParent(function () {
177 					that._read(success, error);
178 				}, error);
179 
180 				return;
181 			}
182 
183 			this.fetch(function (response) {
184 				that._processResponse(response);
185 				that._fetched = true;
186 				if (success) {
187 					success(that);
188 				}
189 			}, error);
190 		},
191 
192 		/**
193 		 * Retrieves this object's id from its parent.  This function is used
194 		 * in order for this object to be able to fetch its data from that
195 		 * backend.
196 		 *
197 		 * @private
198 		 * @param {function(ContentObjectAPI)=} success Optional callback that
199 		 *                                              receives this object as
200 		 *                                              its only argument.
201 		 * @param {function(GCNError):boolean=} error Optional customer error
202 		 *                                            handler.
203 		 * @throws CANNOT_GET_OBJECT_ID
204 		 */
205 		'!_getIdFromParent': function (success, error) {
206 			var parent = this._ancestor();
207 
208 			if (!parent) {
209 				var err = GCN.createError('CANNOT_GET_OBJECT_ID',
210 					'Cannot get an id for object', this);
211 
212 				GCN.handleError(err, error);
213 
214 				return;
215 			}
216 
217 			var that = this;
218 
219 			parent._read(function () {
220 				if (that._type === 'folder') {
221 					// There are 3 possible property names that an object can
222 					// hold the id of the folder that it is related to:
223 					//
224 					// "folderId": for pages, templates, files, and images.
225 					// "motherId": for folders
226 					// "nodeId":   for nodes
227 					//
228 					// We need to see which of this properties is set, the
229 					// first one we find will be our folder's id.
230 					var props = ['folderId', 'motherId', 'nodeId'];
231 					var prop = props.pop();
232 					var id;
233 
234 					while (prop) {
235 						id = parent.prop(prop);
236 
237 						if (typeof id !== 'undefined') {
238 							break;
239 						}
240 
241 						prop = props.pop();
242 					}
243 
244 					that._data.id = id;
245 				} else {
246 					that._data.id = parent.prop(that._type + 'Id');
247 				}
248 
249 				if (that._data.id === null || typeof that._data.id === 'undefined') {
250 					var err = GCN.createError('CANNOT_GET_OBJECT_ID',
251 						'Cannot get an id for object', this);
252 
253 					GCN.handleError(err, error);
254 
255 					return;
256 				}
257 
258 				that._setHash(that._data.id)._addToCache();
259 
260 				if (success) {
261 					success();
262 				}
263 			}, error);
264 		},
265 
266 		/**
267 		 * Gets this object's id. We'll return the id of the object when it has
268 		 * been loaded. This can only be a localid. Otherwise we'll return the
269 		 * id which was provided by the user. This can either be a localid or a
270 		 * globalid.
271 		 *
272 		 * @name id
273 		 * @function
274 		 * @memberOf ContentObjectAPI
275 		 * @public
276 		 * @return {number}
277 		 */
278 		'!id': function () {
279 			return this._data.id;
280 		},
281 
282 		/**
283 		 * Alias for `id()'
284 		 *
285 		 * @name id
286 		 * @function
287 		 * @memberOf ContentObjectAPI
288 		 * @private
289 		 * @return {number}
290 		 */
291 		'!localId': function () {
292 			return this.id();
293 		},
294 
295 		/**
296 		 * Update the `_shadow' object that maintains changes to properties
297 		 * that reflected the internal `_data' object.  This shadow object is
298 		 * used to persist differential changes to a REST API object.
299 		 *
300 		 * @private
301 		 * @param {string} path The path through the object to the property we
302 		 *                      want to modify.
303 		 * @param {*} value The value we wish to set the property to.
304 		 * @param {function=} error Custom error handler.
305 		 * @param {boolean=} force If true, no error will be thrown if `path'
306 		 *                         cannot be fully resolved against the
307 		 *                         internal `_data' object, instead, the path
308 		 *                         will be created on the shadow object.
309 		 */
310 		'!_update': function (pathStr, value, error, force) {
311 			var path = pathStr.split('.');
312 			var shadow = this._shadow;
313 			var actual = this._data;
314 			var i = 0;
315 			var j = path.length;
316 			var pathNode;
317 			// Whether or not the traversal path in `_data' and `_shadow' are
318 			// at the same position in the respective objects.
319 			var areMirrored = true;
320 
321 			while (true) {
322 				pathNode = path[i++];
323 
324 				if (areMirrored) {
325 					actual = actual[pathNode];
326 					areMirrored = jQuery.type(actual) !== 'undefined';
327 				}
328 
329 				if (i === j) {
330 					break;
331 				}
332 
333 				if (shadow[pathNode]) {
334 					shadow = shadow[pathNode];
335 				} else if (areMirrored || force) {
336 					shadow = (shadow[pathNode] = {});
337 				} else {
338 					break; // goto error
339 				}
340 			}
341 
342 			if (i === j && (areMirrored || force)) {
343 				shadow[pathNode] = value;
344 			} else {
345 				var err = GCN.createError('TYPE_ERROR', 'Object "' +
346 					path.slice(0, i).join('.') + '" does not exist',
347 					actual);
348 
349 				GCN.handleError(err, error);
350 			}
351 		},
352 
353 		/**
354 		 * Receives the response from a REST API request, and stores it in the
355 		 * internal `_data' object.
356 		 *
357 		 * @private
358 		 * @param {object} data Parsed JSON response data.
359 		 */
360 		'!_processResponse': function (data) {
361 			jQuery.extend(this._data, data[this._type]);
362 		},
363 
364 		/**
365 		 * Specifies a list of parameters that will be added to the url when
366 		 * loading the content object from the server.
367 		 *
368 		 * @private
369 		 * @return {object} object With parameters to be appended to the load
370 		 *                         request
371 		 */
372 		'!_loadParams': function () {},
373 
374 		/**
375 		 * Reads the proporty `property' of this content object if this
376 		 * property is among those in the WRITEABLE_PROPS array.  If a send
377 		 * argument is provided, them the property is updated with that value.
378 		 *
379 		 * @name prop
380 		 * @function
381 		 * @memberOf ContentObjectAPI
382 		 * @param {String} property Name of the property to be read or updated.
383 		 * @param {String} value Value to be set property to.
384 		 * @return {?*} Meta attribute.
385 		 * @throws UNFETCHED_OBJECT_ACCESS
386 		 * @throws READONLY_ATTRIBUTE
387 		 */
388 		'!prop': function (property, value) {
389 			if (!this._fetched) {
390 				GCN.error('UNFETCHED_OBJECT_ACCESS',
391 					'Object not fetched yet.');
392 
393 				return;
394 			}
395 
396 			if (value) {
397 				if (jQuery.inArray(property, this.WRITEABLE_PROPS) >= 0) {
398 					this._update(property, value);
399 				} else {
400 					GCN.error('READONLY_ATTRIBUTE',
401 						'Attribute "' + property + '" of ' + this._type +
402 						' is read-only. Writeable properties are: ' +
403 						this.WRITEABLE_PROPS);
404 				}
405 			}
406 
407 			return ((jQuery.type(this._shadow[property]) !== 'undefined'
408 				? this._shadow : this._data)[property]);
409 		},
410 
411 		/**
412 		 * Sends the a template string to the Aloha Servlet for rendering.
413 		 *
414 		 * @TODO: Consider making this function public.  At least one developer
415 		 *        has had need to render a custom template for a content
416 		 *        object.
417 		 *
418 		 * @private
419 		 * @param {string} template Template which will be rendered.
420 		 * @param {string} mode The rendering mode.  Valid values are "view",
421 		 *                      "edit", "pub."
422 		 * @param {function(object)} success A callback the receives the render
423 		 *                                   response.
424 		 * @param {function(GCNError):boolean} error Error handler.
425 		 */
426 		'!_renderTemplate' : function (template, mode, success, error) {
427 			var url = GCN.settings.BACKEND_PATH + '/rest/' + this._type +
428 			          '/render/' + this.id();
429 
430 			url += '?edit=' + ('edit' === mode)
431 			    + '&template=' + encodeURIComponent(template);
432 
433 			this._authAjax({
434 				url     : url,
435 				error   : error,
436 				success : success
437 			});
438 		},
439 
440 		/**
441 		 * Wrapper for internal chainback _ajax method.
442 		 *
443 		 * @private
444 		 * @param {object<string, *>} settings Settings for the ajax request.
445 		 *                                     The settings object is identical
446 		 *                                     to that of the `GCN.ajax'
447 		 *                                     method, which handles the actual
448 		 *                                     ajax transportation.
449 		 * @throws AJAX_ERROR
450 		 */
451 		'!_ajax': function (settings) {
452 			var that = this;
453 
454 			// force no cache for all API calls
455 			settings.cache = false;
456 			settings.success = (function (onSuccess, onError) {
457 				return function (data) {
458 					// Ajax calls that do not target the REST API servlet do
459 					// not response data with a `responseInfo' object.
460 					// "/CNPortletapp/alohatag" is an example.  So we cannot
461 					// just assume that it exists.
462 					if (data.responseInfo) {
463 						switch (data.responseInfo.responseCode) {
464 						case 'OK':
465 							break;
466 						case 'AUTHREQUIRED':
467 							GCN.clearSession();
468 							that._authAjax(settings);
469 							return;
470 						default:
471 							GCN.handleResponseError(data, onError);
472 							return;
473 						}
474 					}
475 
476 					if (onSuccess) {
477 						onSuccess(data);
478 					}
479 				};
480 			}(settings.success, settings.error, settings.url));
481 
482 			this._queueAjax(settings);
483 		},
484 
485 		/**
486 		 * Similar to `_ajax', except that it prefixes the ajax url with the
487 		 * current session's `sid', and will trigger an
488 		 * `authentication-required' event if the session is not authenticated.
489 		 *
490 		 * @TODO(petro): Consider simplifiying this function signature to read:
491 		 *               `_auth( url, success, error )'
492 		 *
493 		 * @private
494 		 * @param {object<string, *>} settings Settings for the ajax request.
495 		 * @throws AUTHENTICATION_FAILED
496 		 */
497 		_authAjax: function (settings) {
498 			var that = this;
499 
500 			if (GCN.isAuthenticating) {
501 				GCN.afterNextAuthentication(function () {
502 					that._authAjax(settings);
503 				});
504 
505 				return;
506 			}
507 
508 			if (!GCN.sid) {
509 				var cancel;
510 
511 				if (settings.error) {
512 					cancel = function (error) {
513 						GCN.handleError(
514 							error || GCN.createError('AUTHENTICATION_FAILED'),
515 							settings.error
516 						);
517 					};
518 				} else {
519 					cancel = function (error) {
520 						if (error) {
521 							GCN.error(error.code, error.message, error.data);
522 						} else {
523 							GCN.error('AUTHENTICATION_FAILED');
524 						}
525 					};
526 				}
527 
528 				GCN.afterNextAuthentication(function () {
529 					that._authAjax(settings);
530 				});
531 
532 				if (GCN.usingSSO) {
533 					// First, try to automatically authenticate via
534 					// Single-SignOn
535 					GCN.loginWithSSO(GCN.onAuthenticated, function () {
536 						// ... if SSO fails, then fallback to requesting user
537 						// credentials: broadcast `authentication-required'
538 						// message.
539 						GCN.authenticate(cancel);
540 					});
541 				} else {
542 					// Trigger the `authentication-required' event to request
543 					// user credentials.
544 					GCN.authenticate(cancel);
545 				}
546 
547 				return;
548 			}
549 
550 			// Append "?sid=..." or "&sid=..." if needed.
551 
552 			var urlFragment = settings.url.substr(
553 				GCN.settings.BACKEND_PATH.length
554 			);
555 			var isSidInUrl = /[\?\&]sid=/.test(urlFragment);
556 			if (!isSidInUrl) {
557 				var isFirstParam = (jQuery.inArray('?',
558 					urlFragment.split('')) === -1);
559 
560 				settings.url += (isFirstParam ? '?' : '&') + 'sid='
561 				             +  (GCN.sid || '');
562 			}
563 
564 			this._ajax(settings);
565 		},
566 
567 		/**
568 		 * Recursively call `_continueWith()'.
569 		 *
570 		 * @private
571 		 * @override
572 		 */
573 		'!_onContinue': function (success, error) {
574 			var that = this;
575 			this._continueWith(function () {
576 				that._read(success, error);
577 			}, error);
578 		},
579 
580 		/**
581 		 * Initializes this content object.  If a `success' callback is
582 		 * provided, it will cause this object's data to be fetched and passed
583 		 * to the callback.  This object's data will be fetched from the cache
584 		 * if is available, otherwise it will be fetched from the server.  If
585 		 * this content object API contains parent chainbacks, it will get its
586 		 * parent to fetch its own data first.
587 		 *
588 		 * You might also provide an object for initialization, to directly
589 		 * instantiate the object's data without loading it from the server.
590 		 * To do so just pass in a data object as received from the server
591 		 * instead of an id--just make sure this object has an `id' property.
592 		 *
593 		 * If an `error' handler is provided, as the third parameter, it will
594 		 * catch any errors that have occured since the invocation of this
595 		 * call.  It allows the global error handler to be intercepted before
596 		 * stopping the error or allowing it to propagate on to the global
597 		 * handler.
598 		 *
599 		 * @private
600 		 * @param {number|string|object} id
601 		 * @param {function(ContentObjectAPI))=} success Optional success
602 		 *                                                   callback that will
603 		 *                                                   receive this
604 		 *                                                   object as its only
605 		 *                                                   argument.
606 		 * @param {function(GCNError):boolean=} error Optional custom error
607 		 *                                            handler.
608 		 * @param {object} settings Basic settings for this object.
609 		 * @throws INVALID_DATA If no id is found when providing an object for
610 		 *                      initialization.
611 		 */
612 		_init: function (id, success, error, settings) {
613 			this._settings = settings;
614 
615 			if (jQuery.type(id) === 'object') {
616 				if (!id.id) {
617 					var err = GCN.createError('INVALID_DATA',
618 						'Data not sufficient for initalization: id is missing',
619 						id);
620 
621 					GCN.handleError(err, error);
622 
623 					return;
624 				}
625 
626 				// Clear the old data and populate the _data field using the given object data
627 				this._data = {};
628 				this._data = id;
629 				this._fetched = true;
630 
631 				if (success) {
632 					success(this);
633 				}
634 			} else {
635 				// Ensure that each object has its very own `_data' and
636 				// `_shadow' objects.
637 				if (!this._fetched) {
638 					this._data = {};
639 					this._shadow = {};
640 					this._data.id = id;
641 				}
642 
643 				if (success) {
644 					this._read(success, error);
645 				}
646 			}
647 		},
648 
649 		/**
650 		 * Replaces tag blocks with appropriate "<node *>" notation in a given
651 		 * string.
652 		 *
653 		 * Given an element whose innerHTML is:
654 		 * <pre>
655 		 *		"<span id="GENTICS_BLOCK_123">My Tag</span>",
656 		 * </pre>
657 		 * `encode()' will return:
658 		 * <pre>
659 		 *		"<node 123>".
660 		 * </pre>
661 		 *
662 		 * @name encode
663 		 * @function
664 		 * @memberOf ContentObjectAPI
665 		 * @param {string} HTML string to be encoded.
666 		 * @return {string} The encoded HTML string.
667 		 */
668 		'!encode': function (html) {
669 			var that = this;
670 			var clone = jQuery('<div>' + html + '</div>');
671 			// Empty all content blocks of their innerHTML.
672 			var id;
673 			var $block;
674 			for (id in this._blocks) {
675 				if (this._blocks.hasOwnProperty(id)) {
676 					$block = clone.find('#' + this._blocks[id].element);
677 					if ($block.length) {
678 						$block.html('').attr('id', BLOCK_ENCODING_PREFIX +
679 							this._blocks[id].tagname);
680 					}
681 				}
682 			}
683 
684 			return clone.html().replace(contentBlockRegExp,
685 			                            function (substr, match) {
686 					return '<node ' + match + '>';
687 				});
688 		},
689 
690 		/**
691 		 * For a given string, replace all occurances of "<node>" with
692 		 * appropriate HTML markup, allowing notated tags to be rendered within
693 		 * the surrounding HTML content.
694 		 *
695 		 * The `success()' handler will receives a string containing the
696 		 * contents of the `str' string with references to "<node>" having been
697 		 * inflated into their appropriate tag rendering.
698 		 *
699 		 * @name decode
700 		 * @function
701 		 * @memberOf ContentObjectAPI
702 		 * @param {string} str The content string, in which  "<node *>" tags
703 		 *                     will be inflated with their HTML rendering.
704 		 * @param {function(ContentObjectAPI))} success Success callback that
705 		 *                                              will receive the
706 		 *                                              decoded string.
707 		 * @param {function(GCNError):boolean=} error Optional custom error
708 		 *                                            handler.
709 		 */
710 		'!decode': function (str, success, error) {
711 			if (!success) {
712 				return;
713 			}
714 
715 			var prefix = 'gcn-tag-placeholder-';
716 			var toRender = [];
717 			var html = replaceNodeTags(str, function (name, offset, str) {
718 				toRender.push('<node ', name, '>');
719 				return '<div id="' + prefix + name + '"></div>';
720 			});
721 
722 			if (!toRender.length) {
723 				success(html);
724 				return;
725 			}
726 
727 			// Instead of rendering each tag individually, we render them
728 			// together in one string, and map the results back into our
729 			// original html string.  This allows us to perform one request to
730 			// the server for any number of node tags found.
731 
732 			var parsed = jQuery('<div>' + html + '</div>');
733 			var template = toRender.join('');
734 			var that = this;
735 
736 			this._renderTemplate(template, 'edit', function (data) {
737 				var content = data.content;
738 				var tag;
739 				var tags = data.tags;
740 				var j = tags.length;
741 				var rendered = jQuery('<div>' + content + '</div>');
742 
743 				var replaceTag = (function (numTags) {
744 					return function (tag) {
745 						parsed.find('#' + prefix + tag.prop('name'))
746 							.replaceWith(
747 								rendered.find('#' + tag.prop('id'))
748 							);
749 
750 						if (0 === --numTags) {
751 							success(parsed.html());
752 						}
753 					};
754 				}(j));
755 
756 				while (j) {
757 					that.tag(tags[--j], replaceTag);
758 				}
759 			}, error);
760 		},
761 
762 		/**
763 		 * Clears this object from its constructor's cache so that the next
764 		 * attempt to access this object will result in a brand new instance
765 		 * being initialized and placed in the cache.
766 		 *
767 		 * @name clear
768 		 * @function
769 		 * @memberOf ContentObjectAPI
770 		 */
771 		'!clear': function () {
772 			// Do not clear the id from the _data.
773 			var id = this._data.id;
774 			this._data = {};
775 			this._data.id = id;
776 			this._shadow = {};
777 			this._fetched = false;
778 			this._clearCache();
779 		},
780 
781 		/**
782 		 * Retreives this objects parent folder.
783 		 *
784 		 * @param {function(ContentObjectAPI)=} success Callback that will
785 		 *                                              receive the requested
786 		 *                                              object.
787 		 * @param {function(GCNError):boolean=} error Custom error handler.
788 		 * @return {ContentObjectAPI)} API object for the retrieved GCN folder.
789 		 */
790 		'!folder': function (success, error) {
791 			return this._continue(GCN.FolderAPI, this._data.folderId, success,
792 				error);
793 		},
794 
795 		/**
796 		 * Saves changes made to this content object to the backend.
797 		 *
798 		 * @param {object=} settings Optional settings to pass on to the ajax
799 		 *                           function.
800 		 * @param {function(ContentObjectAPI)=} success Optional callback that
801 		 *                                              receives this object as
802 		 *                                              its only argument.
803 		 * @param {function(GCNError):boolean=} error Optional customer error
804 		 *                                            handler.
805 		 */
806 		save: function () {
807 			var settings;
808 			var success;
809 			var error;
810 			var args = Array.prototype.slice.call(arguments);
811 			var len = args.length;
812 			var i;
813 
814 			for (i = 0; i < len; ++i) {
815 				switch (jQuery.type(args[i])) {
816 				case 'object':
817 					if (!settings) {
818 						settings = args[i];
819 					}
820 					break;
821 				case 'function':
822 					if (!success) {
823 						success = args[i];
824 					} else {
825 						error = args[i];
826 					}
827 					break;
828 				case 'undefined':
829 					break;
830 				default:
831 					var err = GCN.createError('UNKNOWN_ARGUMENT',
832 						'Don\'t know what to do with arguments[' + i + '] ' +
833 						'value: "' + args[i] + '"', args);
834 					GCN.handleError(err, error);
835 					return;
836 				}
837 			}
838 
839 			this._save(settings, success, error);
840 		},
841 
842 		/**
843 		 * Persists this object's local data onto the server.  If the object
844 		 * has not yet been fetched we need to get it first so we can update
845 		 * its internals properly...
846 		 *
847 		 * @private
848 		 * @param {object} settings Object which will extend the basic
849 		 *                          settings of the ajax call
850 		 * @param {function(ContentObjectAPI)=} success Optional callback that
851 		 *                                              receives this object as
852 		 *                                              its only argument.
853 		 * @param {function(GCNError):boolean=} error Optional customer error
854 		 *                                            handler.
855 		 */
856 		'!_save': function (settings, success, error) {
857 			var that = this;
858 			this._continueWith(function () {
859 				that._persist(settings, success, error);
860 			}, error);
861 		},
862 
863 		/**
864 		 * Sends the current state of this content object to be stored on the
865 		 * server.
866 		 *
867 		 * @private
868 		 * @param {function(ContentObjectAPI)=} success Optional callback that
869 		 *                                              receives this object as
870 		 *                                              its only argument.
871 		 * @param {function(GCNError):boolean=} error Optional customer error
872 		 *                                            handler.
873 		 * @throws HTTP_ERROR
874 		 */
875 		'!_persist': function (settings, success, error) {
876 			var that = this;
877 
878 			if (!this._fetched) {
879 				that._read(function () {
880 					that._persist(settings, success, error);
881 				}, error);
882 
883 				return;
884 			}
885 
886 			var json = {};
887 
888 			if (this._deletedTags.length) {
889 				json['delete'] = this._deletedTags;
890 			}
891 
892 			if (this._deletedBlocks.length) {
893 				json['delete'] = json['delete']
894 				               ? json['delete'].concat(this._deletedBlocks)
895 							   : this._deletedBlocks;
896 			}
897 
898 			json[this._type] = this._shadow;
899 			json[this._type].id = this._data.id;
900 
901 			jQuery.extend(json, settings);
902 
903 			this._authAjax({
904 				url     : GCN.settings.BACKEND_PATH + '/rest/' + that._type +
905 				          '/save/' + that.id(),
906 				type    : 'POST',
907 				error   : error,
908 				json    : json,
909 				success : function (response) {
910 					// We must not overwrite the `_data.tags' object with this
911 					// one.
912 					delete that._shadow.tags;
913 
914 					// Everything else in `_shadow' should be written over
915 					// `_data' before resetting the `_shadow' object.
916 					jQuery.extend(that._data, that._shadow);
917 					that._shadow = {};
918 
919 					// Tags have been deleted.
920 					that._deletedTags = [];
921 					that._deletedBlocks = [];
922 
923 					if (success) {
924 						success(that);
925 					}
926 				}
927 			});
928 		},
929 
930 		/**
931 		 * Deletes this content object from its containing parent.
932 		 *
933 		 * @param {function(ContentObjectAPI)=} success Optional callback that
934 		 *                                              receives this object as
935 		 *                                              its only argument.
936 		 * @param {function(GCNError):boolean=} error Optional customer error
937 		 *                                            handler.
938 		 */
939 		remove: function (success, error) {
940 			this._remove(success, error);
941 		},
942 
943 		/**
944 		 * Performs a REST API request to delete this object from the server.
945 		 *
946 		 * @private
947 		 * @param {function(ContentObjectAPI)=} success Optional callback that
948 		 *                                              will be invoked once
949 		 *                                              this object has been
950 		 *                                              removed.
951 		 * @param {function(GCNError):boolean=} error Optional customer error
952 		 *                                            handler.
953 		 */
954 		'!_remove': function (success, error) {
955 			var that = this;
956 			this._authAjax({
957 				url     : GCN.settings.BACKEND_PATH + '/rest/' + that._type +
958 					      '/delete/' + that.id(),
959 				type    : 'POST',
960 				error   : error,
961 				success : function (response) {
962 					// Clean cache & reset object to make sure it can't be used
963 					// properly any more.
964 					that._clearCache();
965 					that._data = {};
966 					that._shadow = {};
967 
968 					// Don't forward the object to the success handler since
969 					// it's been deleted.
970 					if (success) {
971 						success();
972 					}
973 				}
974 			});
975 		},
976 
977 		/**
978 		 * Removes any additionaly data stored on this objec which pertains to
979 		 * a tag matching the given tagname.  This function will be called when
980 		 * a tag is being removed in order to bring the content object to a
981 		 * consistant state.
982 		 * Should be overriden by subclasses.
983 		 *
984 		 * @param {string} tagid The Id of the tag whose associated data we
985 		 *                       want we want to remove.
986 		 */
987 		'!_removeAssociatedTagData': function (tagname) {}
988 
989 	});
990 
991 	/**
992 	 * Generates a factory method for chainback classes.  The method signature
993 	 * used with this factory function will match that of the target class'
994 	 * constructor.  Therefore this function is expected to be invoked with the
995 	 * follow combination of arguments ...
996 	 *
997 	 * Examples for GCN.pages api:
998 	 *
999 	 * To get an array containing 1 page:
1000 	 * pages(1)
1001 	 * pages(1, function () {})
1002 	 *
1003 	 * To get an array containing 2 pages:
1004 	 * pages([1, 2])
1005 	 * pages([1, 2], function () {})
1006 	 *
1007 	 * To get an array containing any and all pages:
1008 	 * pages()
1009 	 * pages(function () {})
1010 	 *
1011 	 * To get an array containing no pages:
1012 	 * pages([])
1013 	 * pages([], function () {});
1014 	 *
1015 	 * @param {Chainback} clazz The Chainback class we want to expose.
1016 	 * @throws UNKNOWN_ARGUMENT
1017 	 */
1018 	GCN.exposeAPI = function (clazz) {
1019 		return function () {
1020 			// Convert arguments into an array
1021 			// https://developer.mozilla.org/en/JavaScript/Reference/...
1022 			// ...Functions_and_function_scope/arguments
1023 			var args = Array.prototype.slice.call(arguments);
1024 			var id;
1025 			var ids;
1026 			var success;
1027 			var error;
1028 			var settings;
1029 
1030 			// iterate over arguments to find id || ids, succes, error and
1031 			// settings
1032 			jQuery.each(args, function (i, arg) {
1033 				switch (jQuery.type(arg)) {
1034 				// set id
1035 				case 'string':
1036 				case 'number':
1037 					if (!id && !ids) {
1038 						id = arg;
1039 					} else {
1040 						GCN.error('UNKNOWN_ARGUMENT',
1041 							'id is already set. Don\'t know what to do with ' +
1042 							'arguments[' + i + '] value: "' + arg + '"');
1043 					}
1044 					break;
1045 				// set ids
1046 				case 'array':
1047 					if (!id && !ids) {
1048 						ids = args[0];
1049 					} else {
1050 						GCN.error('UNKNOWN_ARGUMENT',
1051 							'ids is already set. Don\'t know what to do with' +
1052 							' arguments[' + i + '] value: "' + arg + '"');
1053 					}
1054 					break;
1055 				// success and error handlers
1056 				case 'function':
1057 					if (!success) {
1058 						success = arg;
1059 					} else if (success && !error) {
1060 						error = arg;
1061 					} else {
1062 						GCN.error('UNKNOWN_ARGUMENT',
1063 							'success and error handler already set. Don\'t ' +
1064 							'know what to do with arguments[' + i + ']');
1065 					}
1066 					break;
1067 				// settings
1068 				case 'object':
1069 					if (!id && !ids) {
1070 						id = arg;
1071 					} else if (!settings) {
1072 						settings = arg;
1073 					} else {
1074 						GCN.error('UNKNOWN_ARGUMENT',
1075 							'settings are already present. Don\'t know what ' +
1076 							'to do with arguments[' + i + '] value:' + ' "' +
1077 							arg + '"');
1078 					}
1079 					break;
1080 				default:
1081 					GCN.error('UNKNOWN_ARGUMENT',
1082 						'Don\'t know what to do with arguments[' + i +
1083 						'] value: "' + arg + '"');
1084 				}
1085 			});
1086 
1087 			// Prepare a new set of arguments to pass on during initialzation
1088 			// of callee object.
1089 			args = [];
1090 
1091 			// settings should always be an object, even if it's just empty
1092 			if (!settings) {
1093 				settings = {};
1094 			}
1095 
1096 			args[0] = (typeof id !== 'undefined') ? id : ids;
1097 			args[1] = success || settings.success || null;
1098 			args[2] = error || settings.error || null;
1099 			args[3] = settings;
1100 
1101 			var hash = (id || ids)
1102 			         ? clazz._makeHash(ids ? ids.sort().join(',') : id)
1103 					 : null;
1104 
1105 			return GCN.getChainback(clazz, hash, null, args);
1106 		};
1107 
1108 	};
1109 
1110 }(GCN));
1111