1 // The Antville Project 2 // http://code.google.com/p/antville 3 // 4 // Copyright 2001-2007 by The Antville People 5 // 6 // Licensed under the Apache License, Version 2.0 (the ``License''); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an ``AS IS'' BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 // 18 // $Revision$ 19 // $LastChangedBy$ 20 // $LastChangedDate$ 21 // $URL$ 22 // 23 24 /** 25 * @fileOverview Defines the Skin prototype 26 */ 27 28 markgettext("Skin"); 29 markgettext("skin"); 30 31 /** 32 * 33 * @param {String} group 34 * @param {String} name 35 * @returns {Skin} 36 */ 37 Skin.getByName = function(group, name) { 38 var skinSet = (res.handlers.layout || path.layout).skins.get(group); 39 if (skinSet) { 40 var skin = skinSet.get(name); 41 if (skin) { 42 return skin; 43 } 44 } 45 return null; 46 } 47 48 /** 49 * @param {String} prototype 50 * @param {String} name 51 * @param {Layout} layout 52 */ 53 Skin.add = function(prototype, name, layout) { 54 var skin = new Skin(prototype, name); 55 layout.skins.add(skin); 56 return skin; 57 } 58 59 /** 60 * 61 * @param {Skin} skin 62 */ 63 Skin.remove = function() { 64 if (this.constructor === Skin) { 65 this.setSource(this.source); 66 this.source = null; 67 this.remove(); 68 } 69 return; 70 } 71 72 /** 73 * @returns {String[]} 74 */ 75 Skin.getPrototypeOptions = function() { 76 var prototypes = []; 77 var content, file; 78 var skinFiles = app.getSkinfilesInPath(res.skinpath); 79 for (var name in skinFiles) { 80 // Include root skins only for root site 81 if (name === root.constructor.name && res.handlers.site !== root) { 82 continue; 83 } 84 if (skinFiles[name][name]) { 85 prototypes.push({value: name, display: name}); 86 } 87 } 88 return prototypes.sort(new String.Sorter("display")); 89 } 90 91 /** 92 * @name Skin 93 * @constructor 94 * @param {String} prototype 95 * @param {String} name 96 * @property {Date} created 97 * @property {User} creator 98 * @property {Layout} layout 99 * @property {Date} modified 100 * @property {User} modifier 101 * @property {String} prototype 102 * @property {String} source 103 * @extends HopObject 104 */ 105 Skin.prototype.constructor = function(prototype, name) { 106 HopObject.confirmConstructor(this); 107 this.prototype = prototype || String.EMPTY; 108 this.name = name || String.EMPTY; 109 this.creator = this.modifier = session.user; 110 this.created = this.modified = new Date; 111 return this; 112 } 113 114 /** 115 * 116 * @param {String} action 117 * @returns {Boolean} 118 */ 119 Skin.prototype.getPermission = function(action) { 120 switch (action) { 121 case ".": 122 case "main": 123 return true; 124 } 125 return res.handlers.skins.getPermission("main"); 126 } 127 128 /** 129 * 130 * @param {String} action 131 * @returns {String} 132 */ 133 Skin.prototype.href = function(action) { 134 res.push(); 135 res.write(res.handlers.layout.skins.href()); 136 res.write(this.prototype); 137 res.write("/"); 138 res.write(this.name); 139 res.write("/"); 140 action && (res.write(action)); 141 return res.pop(); 142 } 143 144 Skin.prototype.main_action = function() { 145 if (res.handlers.site === root) { 146 res.contentType = "text/plain"; 147 res.write(this.getSource()); 148 return; 149 } 150 res.redirect(this.href("edit")); 151 return; 152 } 153 154 Skin.prototype.edit_action = function() { 155 if (req.postParams.save) { 156 try { 157 var url = this.href(req.action); 158 this.update(req.postParams); 159 res.message = gettext("The changes were saved successfully."); 160 if (req.postParams.save == 1) { 161 res.redirect(url); 162 } else { 163 res.redirect(res.handlers.layout.skins.href("modified")); 164 } 165 } catch (ex) { 166 res.message = ex; 167 app.log(ex); 168 } 169 } 170 res.data.action = this.href(req.action); 171 res.data.title = gettext('Edit Skin: {0}.{1}', this.prototype, this.name); 172 res.data.body = this.renderSkinAsString("$Skin#edit"); 173 res.handlers.skins.renderSkin("$Skins#page"); 174 return; 175 } 176 177 /** 178 * 179 * @param {Object} data 180 */ 181 Skin.prototype.update = function(data) { 182 if (this.isTransient()) { 183 res.handlers.layout.skins.add(this); 184 this.source = this.getSource(); // Copies the skin file source to database 185 } 186 if (this.prototype === "Site" && this.name === "page") { 187 var macro = "response.body"; 188 if (!createSkin(data.source).containsMacro(macro)) { 189 var macro = ["<code><%", macro, "%></code>"].join(String.EMPTY); 190 throw Error(gettext("The {0} macro is missing. It is essential for accessing the site and must be present in this skin.", macro)); 191 } 192 } 193 this.setSource(data.source); 194 this.touch(); 195 return; 196 } 197 198 Skin.prototype.reset_action = function() { 199 if (req.postParams.proceed) { 200 try { 201 Skin.remove.call(this); 202 res.message = gettext("{0} was successfully reset.", gettext("Skin")); 203 res.redirect(res.handlers.layout.skins.href("modified")); 204 } catch(ex) { 205 res.message = ex; 206 app.log(ex); 207 } 208 } 209 210 res.data.action = this.href(req.action); 211 res.data.title = gettext("Confirm Reset"); 212 res.data.body = this.renderSkinAsString("$HopObject#confirm", { 213 text: this.getConfirmText() 214 }); 215 res.handlers.site.renderSkin("Site#page"); 216 return; 217 } 218 219 Skin.prototype.compare_action = function() { 220 var originalSkin = this.source || String.EMPTY; 221 var diff = this.getSource().diff(originalSkin); 222 if (!diff) { 223 res.message = gettext("No differences were found."); 224 } else { 225 res.push(); 226 var param = {}, leftLineNumber = rightLineNumber = 0; 227 for each (let line in diff) { 228 if (line.deleted) { 229 param.right = encode(line.value); 230 param.leftStatus = "added"; 231 param.rightStatus = ''; 232 for (let i=0; i<line.deleted.length; i++) { 233 leftLineNumber += 1; 234 param.leftLineNumber = leftLineNumber; 235 param.rightLineNumber = ''; 236 param.left = encode(line.deleted[i]); 237 param.right = ''; 238 this.renderSkin("$Skin#difference", param); 239 } 240 } 241 if (line.inserted) { 242 param.left = encode(line.value); 243 param.leftStatus = ''; 244 param.rightStatus = 'removed'; 245 for (let i=0; i<line.inserted.length; i++) { 246 rightLineNumber += 1; 247 param.leftLineNumber = ''; 248 param.rightLineNumber = rightLineNumber; 249 param.left = ''; 250 param.right = encode(line.inserted[i]); 251 this.renderSkin("$Skin#difference", param); 252 } 253 } 254 if (line.value !== null) { 255 leftLineNumber += 1; 256 rightLineNumber += 1; 257 param.leftLineNumber = leftLineNumber; 258 param.rightLineNumber = rightLineNumber; 259 param.leftStatus = param.rightStatus = ''; 260 param.left = encode(line.value); 261 param.right = param.left; 262 this.renderSkin("$Skin#difference", param); 263 } 264 } 265 res.data.diff = res.pop(); 266 } 267 268 res.data.title = gettext("Compare Skin: {0}", this.getTitle()); 269 res.data.body = this.renderSkinAsString("$Skin#compare"); 270 res.handlers.skins.renderSkin("$Skins#page"); 271 return; 272 } 273 274 /** 275 * 276 * @return {String} 277 */ 278 Skin.prototype.getTitle = function() { 279 return this.prototype + '.' + this.name; 280 } 281 282 /** 283 * 284 * @param {String} name 285 * @return {Object} 286 */ 287 Skin.prototype.getFormOptions = function(name) { 288 switch (name) { 289 case "prototype": 290 return Skin.getPrototypeOptions(); 291 } 292 } 293 294 Skin.prototype.getFormValue = function(name) { 295 switch (name) { 296 case "source": 297 return req.data.source || this.getSource(); 298 } 299 return HopObject.prototype.getFormValue.apply(this, arguments); 300 } 301 302 /** 303 * @returns {String} 304 */ 305 Skin.prototype.getSource = function() { 306 var skin; 307 // FIXME: Maintain skin inheritance by checking if we target the Site skin of root 308 if (res.handlers.site === root && this.prototype === "Site") { 309 skin = this.getSubskin("Root"); 310 if (skin) { 311 return skin.getSource(); 312 } 313 } 314 skin = this.getSubskin(); 315 if (skin) { 316 return skin.getSource(); 317 } 318 return null; 319 } 320 321 /** 322 * 323 * @param {String} source 324 */ 325 Skin.prototype.setSource = function(source) { 326 // FIXME: Maintain skin inheritance by checking if we target the Site skin of root 327 var prototype = (res.handlers.site === root && this.prototype === "Site") ? "Root" : this.prototype; 328 var skin = this.getMainSkin(prototype); 329 if (!skin) { 330 return; 331 } 332 333 res.push(); 334 if (source != null) { 335 res.writeln("<% #" + this.name + " %>"); 336 res.writeln(source.trim().replace(/(<%\s*)#/g, "$1// #")); 337 } 338 var subskins = skin.getSubskinNames(); 339 for (var i in subskins) { 340 if (subskins[i] !== this.name) { 341 res.writeln("<% #" + subskins[i] + " %>"); 342 source = skin.getSubskin(subskins[i]).source; 343 source && res.writeln(source.trim()); 344 } 345 } 346 source = res.pop(); 347 348 var file = this.getStaticFile(); 349 if (!file.exists()) { 350 file.getParentFile().mkdirs(); 351 file.createNewFile(); 352 } 353 var fos = new java.io.FileOutputStream(file); 354 var bos = new java.io.BufferedOutputStream(fos); 355 var writer = new java.io.OutputStreamWriter(bos, "UTF-8"); 356 writer.write(source); 357 writer.close(); 358 bos.close(); 359 fos.close(); 360 361 this.clearCache(); 362 return; 363 } 364 365 /** 366 * 367 * @returns {java.io.File} 368 */ 369 Skin.prototype.getStaticFile = function() { 370 // FIXME: Maintain skin inheritance by checking if we target the Site skin of root 371 var prototype = (res.handlers.site === root && this.prototype === "Site") ? "Root" : this.prototype; 372 return new java.io.File(res.skinpath[0], prototype + "/" + this.prototype + ".skin"); 373 } 374 375 /** 376 * @param {String} prototype 377 * @returns {Skin} 378 */ 379 Skin.prototype.getMainSkin = function(prototype) { 380 var source, skinSet = app.getSkinfilesInPath(res.skinpath)[prototype || this.prototype]; 381 if (skinSet) { 382 source = skinSet[this.prototype]; 383 if (source !== null) { 384 return createSkin(source); 385 } 386 } 387 return null; 388 } 389 390 /** 391 * 392 * @param prototype 393 * @param name 394 * @returns {Skin} 395 */ 396 Skin.prototype.getSubskin = function(prototype, name) { 397 var mainSkin = this.getMainSkin(prototype); 398 if (mainSkin) { 399 return mainSkin.getSubskin(name || this.name); 400 } 401 return null; 402 } 403 404 /** 405 * 406 */ 407 Skin.prototype.render = function() { 408 return renderSkin(createSkin(this.getSource())); 409 } 410 411 /** 412 * 413 * @param {String} source 414 * @returns {Boolean} 415 */ 416 Skin.prototype.equals = function(source) { 417 // FIXME: The removal of linebreaks is necessary but it's not very nice 418 var re = /\r|\n/g; 419 var normalize = function(str) { 420 return str.replace(re, String.EMPTY); 421 } 422 return normalize(source) === normalize(this.getSource()); 423 } 424 425 /** 426 * @returns {String} 427 */ 428 Skin.prototype.getConfirmText = function() { 429 return gettext("You are about to reset the skin {0}.{1}.", 430 this.prototype, this.name); 431 } 432 433 /** 434 * @returns {String} 435 */ 436 Skin.prototype.toString = function() { 437 return "Skin #" + this._id + ": " + this.prototype + "." + this.name; 438 } 439 440 /** 441 * 442 */ 443 Skin.prototype.status_macro = function() { 444 return this.isTransient() ? "inherited" : "modified"; 445 } 446 447 /** 448 * 449 */ 450 Skin.prototype.content_macro = function() { 451 return res.write(this.getSource()); 452 } 453