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