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 * 50 * @param {Skin} skin 51 */ 52 Skin.remove = function() { 53 if (this.constructor === Skin) { 54 this.setSource(this.source); 55 this.source = null; 56 this.remove(); 57 } 58 return; 59 } 60 61 /** 62 * @returns {String[]} 63 */ 64 Skin.getPrototypeOptions = function() { 65 var prototypes = []; 66 var content, file; 67 var skinFiles = app.getSkinfilesInPath(res.skinpath); 68 for (var name in skinFiles) { 69 // Include root skins only for root site 70 if (name === root.constructor.name && res.handlers.site !== root) { 71 continue; 72 } 73 if (skinFiles[name][name]) { 74 prototypes.push({value: name, display: name}); 75 } 76 } 77 return prototypes.sort(new String.Sorter("display")); 78 } 79 80 /** 81 * @name Skin 82 * @constructor 83 * @param {String} prototype 84 * @param {String} name 85 * @property {Date} created 86 * @property {User} creator 87 * @property {Layout} layout 88 * @property {Date} modified 89 * @property {User} modifier 90 * @property {String} prototype 91 * @property {String} source 92 * @extends HopObject 93 */ 94 Skin.prototype.constructor = function(prototype, name) { 95 this.prototype = prototype || String.EMPTY; 96 this.name = name || String.EMPTY; 97 this.creator = this.modifier = session.user; 98 this.created = this.modified = new Date; 99 return this; 100 } 101 102 /** 103 * 104 * @param {String} action 105 * @returns {Boolean} 106 */ 107 Skin.prototype.getPermission = function(action) { 108 switch (action) { 109 case ".": 110 case "main": 111 return true; 112 } 113 return res.handlers.skins.getPermission("main"); 114 } 115 116 /** 117 * 118 * @param {String} action 119 * @returns {String} 120 */ 121 Skin.prototype.href = function(action) { 122 res.push(); 123 res.write(res.handlers.layout.skins.href()); 124 res.write(this.prototype); 125 res.write("/"); 126 res.write(this.name); 127 res.write("/"); 128 action && (res.write(action)); 129 return res.pop(); 130 } 131 132 Skin.prototype.main_action = function() { 133 if (res.handlers.site === root) { 134 res.contentType = "text/plain"; 135 res.write(this.getSource()); 136 return; 137 } 138 res.redirect(this.href("edit")); 139 return; 140 } 141 142 Skin.prototype.edit_action = function() { 143 if (req.postParams.save) { 144 try { 145 var url = this.href(req.action); 146 this.update(req.postParams); 147 res.message = gettext("The changes were saved successfully."); 148 if (req.postParams.save == 1) { 149 res.redirect(url); 150 } else { 151 res.redirect(res.handlers.layout.skins.href("modified")); 152 } 153 } catch (ex) { 154 res.message = ex; 155 app.log(ex); 156 } 157 } 158 res.data.action = this.href(req.action); 159 res.data.title = gettext('Edit Skin: {0}.{1}', this.prototype, this.name); 160 res.data.body = this.renderSkinAsString("$Skin#edit"); 161 res.handlers.skins.renderSkin("$Skins#page"); 162 return; 163 } 164 165 /** 166 * 167 * @param {Object} data 168 */ 169 Skin.prototype.update = function(data) { 170 if (this.isTransient()) { 171 res.handlers.layout.skins.add(this); 172 this.source = this.getSource(); // Copies the skin file source to database 173 } 174 if (this.prototype === "Site" && this.name === "page") { 175 var macro = "response.body"; 176 if (!createSkin(data.source).containsMacro(macro)) { 177 var macro = ["<code><%", macro, "%></code>"].join(String.EMPTY); 178 throw Error(gettext("The {0} macro is missing. It is essential for accessing the site and must be present in this skin.", 179 macro)); 180 //data.source = ["<% // ", gettext("The following macro was automatically added to prevent the site from becoming inacessible."), 181 // " %>\n<% ", macro, " %>\n\n", data.source].join(String.EMPTY); 182 } 183 } 184 this.setSource(data.source); 185 this.touch(); 186 return; 187 } 188 189 Skin.prototype.reset_action = function() { 190 if (req.postParams.proceed) { 191 try { 192 Skin.remove.call(this); 193 res.message = gettext("{0} was successfully reset.", gettext("Skin")); 194 res.redirect(res.handlers.layout.skins.href("modified")); 195 } catch(ex) { 196 res.message = ex; 197 app.log(ex); 198 } 199 } 200 201 res.data.action = this.href(req.action); 202 res.data.title = gettext("Confirm Reset"); 203 res.data.body = this.renderSkinAsString("$HopObject#confirm", { 204 text: this.getConfirmText() 205 }); 206 res.handlers.site.renderSkin("Site#page"); 207 return; 208 } 209 210 Skin.prototype.compare_action = function() { 211 var originalSkin = this.source || String.EMPTY; 212 var diff = originalSkin.diff(this.getSource()); 213 if (!diff) { 214 res.data.status = gettext("No differences were found"); 215 } else { 216 res.push(); 217 var sp = new Object(); 218 for (var i in diff) { 219 var line = diff[i]; 220 sp.num = line.num; 221 if (line.deleted) { 222 sp.status = "DEL"; 223 sp["class"] = "removed"; 224 for (var j=0;j<line.deleted.length;j++) { 225 sp.num = line.num + j; 226 sp.line = encode(line.deleted[j]); 227 this.renderSkin("$Skin#difference", sp); 228 } 229 } 230 if (line.inserted) { 231 sp.status = "ADD"; 232 sp["class"] = "added"; 233 for (var j=0;j<line.inserted.length;j++) { 234 sp.num = line.num + j; 235 sp.line = encode(line.inserted[j]); 236 this.renderSkin("$Skin#difference", sp); 237 } 238 } 239 if (line.value != null) { 240 sp.status = " "; 241 sp["class"] = "line"; 242 sp.line = encode(line.value); 243 this.renderSkin("$Skin#difference", sp); 244 } 245 } 246 res.data.diff = res.pop(); 247 } 248 res.data.title = gettext("Compare Skin"); 249 res.data.body = this.renderSkinAsString("$Skin#compare"); 250 res.handlers.skins.renderSkin("$Skins#page"); 251 return; 252 } 253 254 /** 255 * 256 * @param {String} name 257 * @return {Object} 258 */ 259 Skin.prototype.getFormOptions = function(name) { 260 switch (name) { 261 case "prototype": 262 return Skin.getPrototypeOptions(); 263 } 264 } 265 266 Skin.prototype.getFormValue = function(name) { 267 switch (name) { 268 case "source": 269 return req.data.source || this.getSource(); 270 } 271 return HopObject.prototype.getFormValue.apply(this, arguments); 272 } 273 274 /** 275 * @returns {String} 276 */ 277 Skin.prototype.getSource = function() { 278 var skin; 279 // FIXME: Maintain skin inheritance by checking if we target the Site skin of root 280 if (res.handlers.site === root && this.prototype === "Site") { 281 skin = this.getSubskin("Root"); 282 if (skin) { 283 return skin.getSource(); 284 } 285 } 286 skin = this.getSubskin(); 287 if (skin) { 288 return skin.getSource(); 289 } 290 return null; 291 } 292 293 /** 294 * 295 * @param {String} source 296 */ 297 Skin.prototype.setSource = function(source) { 298 // FIXME: Maintain skin inheritance by checking if we target the Site skin of root 299 var prototype = (res.handlers.site === root && this.prototype === "Site") ? "Root" : this.prototype; 300 var skin = this.getMainSkin(prototype); 301 if (!skin) { 302 return; 303 } 304 305 res.push(); 306 if (source != null) { 307 res.writeln("<% #" + this.name + " %>"); 308 res.writeln(source.trim().replace(/(<%\s*)#/g, "$1// #")); 309 } 310 var subskins = skin.getSubskinNames(); 311 for (var i in subskins) { 312 if (subskins[i] !== this.name) { 313 res.writeln("<% #" + subskins[i] + " %>"); 314 source = skin.getSubskin(subskins[i]).source; 315 source && res.writeln(source.trim()); 316 } 317 } 318 source = res.pop(); 319 320 var file = this.getStaticFile(); 321 if (!file.exists()) { 322 file.getParentFile().mkdirs(); 323 file.createNewFile(); 324 } 325 var fos = new java.io.FileOutputStream(file); 326 var bos = new java.io.BufferedOutputStream(fos); 327 var writer = new java.io.OutputStreamWriter(bos, "UTF-8"); 328 writer.write(source); 329 writer.close(); 330 bos.close(); 331 fos.close(); 332 333 this.clearCache(); 334 return; 335 } 336 337 /** 338 * 339 * @returns {java.io.File} 340 */ 341 Skin.prototype.getStaticFile = function() { 342 // FIXME: Maintain skin inheritance by checking if we target the Site skin of root 343 var prototype = (res.handlers.site === root && this.prototype === "Site") ? "Root" : this.prototype; 344 return new java.io.File(res.skinpath[0], prototype + "/" + this.prototype + ".skin"); 345 } 346 347 /** 348 * @param {String} prototype 349 * @returns {Skin} 350 */ 351 Skin.prototype.getMainSkin = function(prototype) { 352 var source, skinSet = app.getSkinfilesInPath(res.skinpath)[prototype || this.prototype]; 353 if (skinSet) { 354 source = skinSet[this.prototype]; 355 if (source !== null) { 356 return createSkin(source); 357 } 358 } 359 return null; 360 } 361 362 /** 363 * 364 * @param prototype 365 * @param name 366 * @returns {Skin} 367 */ 368 Skin.prototype.getSubskin = function(prototype, name) { 369 var mainSkin = this.getMainSkin(prototype); 370 if (mainSkin) { 371 return mainSkin.getSubskin(name || this.name); 372 } 373 return null; 374 } 375 376 /** 377 * 378 */ 379 Skin.prototype.render = function() { 380 return renderSkin(createSkin(this.getSource())); 381 } 382 383 /** 384 * 385 * @param {String} source 386 * @returns {Boolean} 387 */ 388 Skin.prototype.equals = function(source) { 389 // FIXME: The removal of linebreaks is necessary but it's not very nice 390 var re = /\r|\n/g; 391 var normalize = function(str) { 392 return str.replace(re, String.EMPTY); 393 } 394 return normalize(source) === normalize(this.getSource()); 395 } 396 397 /** 398 * @returns {String} 399 */ 400 Skin.prototype.getConfirmText = function() { 401 return gettext("You are about to reset the skin {0}.{1}.", 402 this.prototype, this.name); 403 } 404 405 /** 406 * @returns {String} 407 */ 408 Skin.prototype.toString = function() { 409 return "Skin #" + this._id + ": " + this.prototype + "." + this.name; 410 } 411 412 /** 413 * 414 */ 415 Skin.prototype.status_macro = function() { 416 return this.isTransient() ? "inherited" : "modified"; 417 } 418 419 /** 420 * 421 */ 422 Skin.prototype.content_macro = function() { 423 return res.write(this.getSource()); 424 } 425