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 // Since GCN.handleResponseError can throw an error, 168 // we pass this function to _invoke, so the error is caught, 169 // remembered and thrown in the end. 170 that._invoke(GCN.handleResponseError, [data, onError]); 171 return; 172 } 173 } 174 175 if (onSuccess) { 176 that._invoke(onSuccess, [data]); 177 } 178 }; 179 }(settings.success, settings.error, settings.url)); 180 181 this._queueAjax(settings); 182 }, 183 184 /** 185 * Similar to `_ajax', except that it prefixes the ajax url with the 186 * current session's `sid', and will trigger an 187 * `authentication-required' event if the session is not authenticated. 188 * 189 * @private 190 * @param {object<string, *>} settings Settings for the ajax request. 191 * @throws AUTHENTICATION_FAILED 192 */ 193 _authAjax: function (settings) { 194 var that = this; 195 196 if (GCN.isAuthenticating) { 197 GCN.afterNextAuthentication(function () { 198 that._authAjax(settings); 199 }); 200 201 return; 202 } 203 204 if (!GCN.sid) { 205 var cancel; 206 207 if (settings.error) { 208 /** 209 * @ignore 210 */ 211 cancel = function (error) { 212 GCN.handleError( 213 error || GCN.createError('AUTHENTICATION_FAILED'), 214 settings.error 215 ); 216 }; 217 } else { 218 /** 219 * @ignore 220 */ 221 cancel = function (error) { 222 if (error) { 223 GCN.error(error.code, error.message, error.data); 224 } else { 225 GCN.error('AUTHENTICATION_FAILED'); 226 } 227 }; 228 } 229 230 GCN.afterNextAuthentication(function () { 231 that._authAjax(settings); 232 }); 233 234 if (GCN.usingSSO) { 235 // First, try to automatically authenticate via 236 // Single-SignOn 237 GCN.loginWithSSO(GCN.onAuthenticated, function () { 238 // ... if SSO fails, then fallback to requesting user 239 // credentials: broadcast `authentication-required' 240 // message. 241 GCN.authenticate(cancel); 242 }); 243 } else { 244 // Trigger the `authentication-required' event to request 245 // user credentials. 246 GCN.authenticate(cancel); 247 } 248 249 return; 250 } 251 252 // Append "?sid=..." or "&sid=..." if needed. 253 254 var urlFragment = settings.url.substr( 255 GCN.settings.BACKEND_PATH.length 256 ); 257 var isSidInUrl = /[\?\&]sid=/.test(urlFragment); 258 if (!isSidInUrl) { 259 var isFirstParam = (jQuery.inArray('?', 260 urlFragment.split('')) === -1); 261 262 settings.url += (isFirstParam ? '?' : '&') + 'sid=' 263 + (GCN.sid || ''); 264 } 265 266 this._ajax(settings); 267 }, 268 269 /** 270 * Retrieves a list of all constructs and constructs categories and 271 * passes it as the only argument into the `success()' callback. 272 * 273 * @param {function(Array.<object>)=} success Callback to receive an 274 * array of constructs. 275 * @param {function(GCNError):boolean=} error Custom error handler. 276 * @return undefined 277 * @throws INVALID_ARGUMENTS 278 */ 279 constructs: function (success, error) { 280 var that = this; 281 if (!success) { 282 GCN.error('INVALID_ARGUMENTS', 'the `constructs()\' method ' + 283 'requires at least a success callback to be given'); 284 } 285 if (this._constructs) { 286 this._invoke(success, [this._constructs]); 287 } else { 288 this._authAjax({ 289 url: GCN.settings.BACKEND_PATH + '/rest/construct/list.json', 290 type: 'GET', 291 error: function (xhr, status, msg) { 292 GCN.handleHttpError(xhr, msg, error); 293 }, 294 success: function (response) { 295 if (GCN.getResponseCode(response) === 'OK') { 296 that._constructs = mapConstructs(response.constructs); 297 that._invoke(success, [that._constructs]); 298 } else { 299 GCN.handleResponseError(response, error); 300 } 301 } 302 }); 303 } 304 }, 305 306 /** 307 * Helper method that will load the constructs of this node. 308 * 309 * @private 310 * @this {AdminAPI} 311 * @param {function(Array.<object>)} success callback 312 * @param {function(GCNError):boolean=} error callback 313 */ 314 constructCategories: function (success, error) { 315 if (this._constructCategories) { 316 this._invoke(success, [this._constructCategories]); 317 } else { 318 var that = this; 319 this.constructs(function (constructs) { 320 that._constructCategories = mapConstructCategories(constructs); 321 that._invoke(success, [that._constructCategories]); 322 }, error); 323 } 324 }, 325 326 /** 327 * Internal method, to fetch this object's data from the server. 328 * 329 * @private 330 * @override 331 * @param {function(ContentObjectAPI)=} success Optional callback that 332 * receives this object as 333 * its only argument. 334 * @param {function(GCNError):boolean=} error Optional customer error 335 * handler. 336 */ 337 '!_read': function (success, error) { 338 this._invoke(success, [this]); 339 } 340 }); 341 342 /** 343 * Exposes an instance of the AdminAPI via GCN.Admin. 344 * 345 * @see AdminAPI 346 */ 347 GCN.Admin = new AdminAPI(); 348 349 }(GCN)); 350