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