1 /*global jQuery: true, GCN: true */
  2 /**
  3  * admin.js defines the AdminAPI, that will
  4  * give access to global data (like all existing constructs).
  5  * A single instance is of this API is exposed as GCN.Admin
  6  */
  7 (function (GCN) {
  8 
  9 	'use strict';
 10 
 11 	/**
 12 	 * Maps constructcategories that were fetched via the Rest API
 13 	 * into a sorted nested array of constructs.
 14 	 *
 15 	 * @param {object<string, object>} constructs
 16 	 * @return {object<string, object>}
 17 	 */
 18 	function mapConstructCategories(constructs) {
 19 		var constructKeyword;
 20 		var categoryMap = { categories: {}, categorySortorder: [] };
 21 		var constructCategoryArray = [];
 22 
 23 		for (constructKeyword in constructs) {
 24 			if (constructs.hasOwnProperty(constructKeyword)) {
 25 				var construct = constructs[constructKeyword];
 26 
 27 				var constructCategoryName = construct.category;
 28 				var categorySortorder = construct.categorySortorder;
 29 
 30 				// Use a custom name for constructs that have not been assigned to a category
 31 				if (!constructCategoryName) {
 32 					constructCategoryName = "GCN_UNCATEGORIZED";
 33 					categorySortorder = -1;
 34 				}
 35 
 36 				// Initialize the inner array of constructs
 37 				if (!categoryMap.categories[constructCategoryName]) {
 38 					var newCategory = {};
 39 					newCategory.constructs = {};
 40 					newCategory.sortorder = categorySortorder;
 41 					newCategory.name = constructCategoryName;
 42 					categoryMap.categories[constructCategoryName] = newCategory;
 43 					constructCategoryArray.push(newCategory);
 44 				}
 45 
 46 				// Add the construct to the category
 47 				categoryMap.categories[constructCategoryName].constructs[constructKeyword] = construct;
 48 			}
 49 		}
 50 
 51 		// Sort the categories by the sortorder
 52 		constructCategoryArray.sort(function (a, b) {
 53 			return a.sortorder - b.sortorder;
 54 		});
 55 
 56 		// Add the sorted category names to the sortorder field
 57 		var k;
 58 		for (k in constructCategoryArray) {
 59 			if (constructCategoryArray.hasOwnProperty(k)) {
 60 				var category = constructCategoryArray[k];
 61 				if (typeof category.sortorder !== 'undefined' && category.sortorder !== -1) {
 62 					categoryMap.categorySortorder.push(category.name);
 63 				}
 64 			}
 65 		}
 66 
 67 		return categoryMap;
 68 	}
 69 
 70 	/**
 71 	 * Maps constructs, that were fetched via the Rest API, using their keyword
 72 	 * as the keys.
 73 	 *
 74 	 * @param {object<string, object>} constructs Consturcts mapped against
 75 	 *                                            their id.
 76 	 * @return {object<string, object>} Constructs mapped against their keys.
 77 	 */
 78 	function mapConstructs(constructs) {
 79 		if (!constructs) {
 80 			return {};
 81 		}
 82 		var map = {};
 83 		var constructId;
 84 		for (constructId in constructs) {
 85 			if (constructs.hasOwnProperty(constructId)) {
 86 				map[constructs[constructId].keyword] = constructs[constructId];
 87 			}
 88 		}
 89 		return map;
 90 	}
 91 
 92 	/**
 93 	 * Admin object.
 94 	 *
 95 	 * @name AdminAPI
 96 	 * @class
 97 	 * @augments Chainback
 98 	 */
 99 	var AdminAPI = GCN.defineChainback({
100 		/** @lends AdminAPI */
101 
102 		__chainbacktype__: 'AdminAPI',
103 		_type: 'admin',
104 
105 		/**
106 		 * @private
107 		 * @type {object<string, number} Constructs for this node are cached
108 		 *                               here so that we only need to fetch
109 		 *                               this once.
110 		 */
111 		_constructs: null,
112 
113 		/**
114 		 * @private
115 		 * @type {object<string, object} Constructs categories for this node.
116 		 *                               Cached here so that we only need to
117 		 *                               fetch this once.
118 		 */
119 		_constructCategories: null,
120 
121 		/**
122 		 * Initialize
123 		 * @private
124 		 */
125 		_init: function (id, success, error, settings) {
126 			if (typeof success === 'function') {
127 				success(this);
128 			}
129 		},
130 
131 		/**
132 		 * Wrapper for internal chainback _ajax method.
133 		 *
134 		 * @private
135 		 * @param {object<string, *>} settings Settings for the ajax request.
136 		 *                                     The settings object is identical
137 		 *                                     to that of the `GCN.ajax'
138 		 *                                     method, which handles the actual
139 		 *                                     ajax transportation.
140 		 * @throws AJAX_ERROR
141 		 */
142 		'!_ajax': function (settings) {
143 			var that = this;
144 
145 			// force no cache for all API calls
146 			settings.cache = false;
147 			settings.success = (function (onSuccess, onError) {
148 				return function (data) {
149 					// Ajax calls that do not target the REST API servlet do
150 					// not response data with a `responseInfo' object.
151 					// "/CNPortletapp/alohatag" is an example.  So we cannot
152 					// just assume that it exists.
153 					if (data.responseInfo) {
154 						switch (data.responseInfo.responseCode) {
155 						case 'OK':
156 							break;
157 						case 'AUTHREQUIRED':
158 							GCN.clearSession();
159 							that._authAjax(settings);
160 							return;
161 						default:
162 							GCN.handleResponseError(data, onError);
163 							return;
164 						}
165 					}
166 
167 					if (onSuccess) {
168 						onSuccess(data);
169 					}
170 				};
171 			}(settings.success, settings.error, settings.url));
172 
173 			this._queueAjax(settings);
174 		},
175 
176 		/**
177 		 * Similar to `_ajax', except that it prefixes the ajax url with the
178 		 * current session's `sid', and will trigger an
179 		 * `authentication-required' event if the session is not authenticated.
180 		 *
181 		 * @TODO(petro): Consider simplifiying this function signature to read:
182 		 *               `_auth( url, success, error )'
183 		 *
184 		 * @private
185 		 * @param {object<string, *>} settings Settings for the ajax request.
186 		 * @throws AUTHENTICATION_FAILED
187 		 */
188 		_authAjax: function (settings) {
189 			var that = this;
190 
191 			if (GCN.isAuthenticating) {
192 				GCN.afterNextAuthentication(function () {
193 					that._authAjax(settings);
194 				});
195 
196 				return;
197 			}
198 
199 			if (!GCN.sid) {
200 				var cancel;
201 
202 				if (settings.error) {
203 					cancel = function (error) {
204 						GCN.handleError(
205 							error || GCN.createError('AUTHENTICATION_FAILED'),
206 							settings.error
207 						);
208 					};
209 				} else {
210 					cancel = function (error) {
211 						if (error) {
212 							GCN.error(error.code, error.message, error.data);
213 						} else {
214 							GCN.error('AUTHENTICATION_FAILED');
215 						}
216 					};
217 				}
218 
219 				GCN.afterNextAuthentication(function () {
220 					that._authAjax(settings);
221 				});
222 
223 				if (GCN.usingSSO) {
224 					// First, try to automatically authenticate via
225 					// Single-SignOn
226 					GCN.loginWithSSO(GCN.onAuthenticated, function () {
227 						// ... if SSO fails, then fallback to requesting user
228 						// credentials: broadcast `authentication-required'
229 						// message.
230 						GCN.authenticate(cancel);
231 					});
232 				} else {
233 					// Trigger the `authentication-required' event to request
234 					// user credentials.
235 					GCN.authenticate(cancel);
236 				}
237 
238 				return;
239 			}
240 
241 			// Append "?sid=..." or "&sid=..." if needed.
242 
243 			var urlFragment = settings.url.substr(
244 				GCN.settings.BACKEND_PATH.length
245 			);
246 			var isSidInUrl = /[\?\&]sid=/.test(urlFragment);
247 			if (!isSidInUrl) {
248 				var isFirstParam = (jQuery.inArray('?',
249 					urlFragment.split('')) === -1);
250 
251 				settings.url += (isFirstParam ? '?' : '&') + 'sid=' + 
252 				                (GCN.sid || '');
253 			}
254 
255 			this._ajax(settings);
256 		},
257 
258 		/**
259 		 * Retrieves a list of all constructs and constructs categories and
260 		 * passes it as the only argument into the `success()' callback.
261 		 *
262 		 * @param {function(Array.<object>)=} success Callback to receive an
263 		 *                                            array of constructs.
264 		 * @param {function(GCNError):boolean=} error Custom error handler.
265 		 * @return undefined
266 		 * @throws INVALID_ARGUMENTS
267 		 */
268 		constructs: function (success, error) {
269 			var that = this;
270 			if (!success) {
271 				GCN.error('INVALID_ARGUMENTS', 'the `constructs()\' method ' +
272 					'requires at least a success callback to be given');
273 			}
274 			if (this._constructs) {
275 				success(this._constructs);
276 			} else {
277 				this._authAjax({
278 					url: GCN.settings.BACKEND_PATH + '/rest/construct/list.json',
279 					type: 'GET',
280 					error: function (xhr, status, msg) {
281 						GCN.handleHttpError(xhr, msg, error);
282 					},
283 					success: function (response) {
284 						if (GCN.getResponseCode(response) === 'OK') {
285 							that._constructs = mapConstructs(response.constructs);
286 							success(that._constructs);
287 						} else {
288 							GCN.handleResponseError(response, error);
289 						}
290 					}
291 				});
292 			}
293 		},
294 
295 		/**
296 		 * Helper method that will load the constructs of this node.
297 		 *
298 		 * @private
299 		 * @this {AdminAPI}
300 		 * @param {function(Array.<object>)} success callback
301 		 * @param {function(GCNError):boolean=} error callback
302 		 */
303 		constructCategories: function (success, error) {
304 			if (this._constructCategories) {
305 				success(this._constructCategories);
306 			} else {
307 				var that = this;
308 				this.constructs(function (constructs) {
309 					that._constructCategories = mapConstructCategories(constructs);
310 					success(that._constructCategories);
311 				}, error);
312 			}
313 		},
314 
315 		/**
316 		 * Internal method, to fetch this object's data from the server.
317 		 *
318 		 * @private
319 		 * @override
320 		 * @param {function(ContentObjectAPI)=} success Optional callback that
321 		 *                                              receives this object as
322 		 *                                              its only argument.
323 		 * @param {function(GCNError):boolean=} error Optional customer error
324 		 *                                            handler.
325 		 */
326 		'!_read': function (success, error) {
327 			success(this);
328 		}
329 	});
330 
331 	/**
332 	 * Expose an instance of the AdminAPI.
333 	 */
334 	GCN.Admin = new AdminAPI();
335 
336 }(GCN));
337