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 29 /** 30 * 31 * @param {String} group 32 * @param {String} name 33 * @returns {Skin} 34 */ 35 Skin.getByName = function(group, name) { 36 var skinSet = (res.handlers.layout || path.layout).skins.get(group); 37 if (skinSet) { 38 var skin = skinSet.get(name); 39 if (skin) { 40 return skin; 41 } 42 } 43 return null; 44 } 45 46 /** 47 * 48 * @param {Skin} skin 49 */ 50 Skin.remove = function() { 51 if (this.constructor === Skin) { 52 this.setSource(this.source); 53 this.source = null; 54 this.remove(); 55 } 56 return; 57 } 58 59 /** 60 * @returns {String[]} 61 */ 62 Skin.getPrototypeOptions = function() { 63 var prototypes = []; 64 var content, file; 65 var skinFiles = app.getSkinfilesInPath(res.skinpath); 66 for (var name in skinFiles) { 67 // Include root skins only for root site 68 if (name === root.constructor.name && res.handlers.site !== root) { 69 continue; 70 } 71 if (skinFiles[name][name]) { 72 prototypes.push({value: name, display: name}); 73 } 74 } 75 return prototypes.sort(new String.Sorter("display")); 76 } 77 78 /** 79 * @name Skin 80 * @constructor 81 * @param {String} prototype 82 * @param {String} name 83 * @property {Date} created 84 * @property {User} creator 85 * @property {Layout} layout 86 * @property {Date} modified 87 * @property {User} modifier 88 * @property {String} prototype 89 * @property {String} source 90 * @extends HopObject 91 */ 92 Skin.prototype.constructor = function(prototype, name) { 93 this.prototype = prototype || String.EMPTY; 94 this.name = name || String.EMPTY; 95 this.creator = this.modifier = session.user; 96 this.created = this.modified = new Date; 97 return this; 98 } 99 100 /** 101 * 102 * @param {String} action 103 * @returns {Boolean} 104 */ 105 Skin.prototype.getPermission = function(action) { 106 switch (action) { 107 case ".": 108 case "main": 109 res.status = 403; 110 return false; 111 } 112 return res.handlers.skins.getPermission("main"); 113 } 114 115 /** 116 * 117 * @param {String} action 118 * @returns {String} 119 */ 120 Skin.prototype.href = function(action) { 121 res.push(); 122 res.write(res.handlers.layout.skins.href()); 123 res.write(this.prototype); 124 res.write("/"); 125 res.write(this.name); 126 res.write("/"); 127 action && (res.write(action)); 128 return res.pop(); 129 } 130 131 Skin.prototype.main_action = function() { 132 if (res.handlers.site === root) { 133 res.contentType = "text/plain"; 134 res.write(this.getSource()); 135 return; 136 } 137 res.redirect(this.href("edit")); 138 return; 139 } 140 141 Skin.prototype.edit_action = function() { 142 if (req.postParams.save) { 143 try { 144 var url = this.href(req.action); 145 this.update(req.postParams); 146 res.message = gettext("The changes were saved successfully."); 147 if (req.postParams.save == 1) { 148 res.redirect(url); 149 } else { 150 res.redirect(res.handlers.layout.skins.href("modified")); 151 } 152 } catch (ex) { 153 res.message = ex; 154 app.log(ex); 155 } 156 } 157 res.data.action = this.href(req.action); 158 res.data.title = gettext('Edit Skin: {0}.{1}', this.prototype, this.name); 159 res.data.body = this.renderSkinAsString("$Skin#edit"); 160 res.handlers.skins.renderSkin("$Skins#page"); 161 return; 162 } 163 164 /** 165 * 166 * @param {Object} data 167 */ 168 Skin.prototype.update = function(data) { 169 if (this.isTransient()) { 170 res.handlers.layout.skins.add(this); 171 this.source = this.getSource(); // Copies the skin file source to database 172 } 173 if (this.prototype === "Site" && this.name === "page") { 174 var macro = "response.body"; 175 if (!createSkin(data.source).containsMacro(macro)) { 176 var macro = ["<code><%", macro, "%></code>"].join(String.EMPTY); 177 throw Error(gettext("The {0} macro is missing. It is essential for accessing the site and must be present in this skin.", 178 macro)); 179 //data.source = ["<% // ", gettext("The following macro was automatically added to prevent the site from becoming inacessible."), 180 // " %>\n<% ", macro, " %>\n\n", data.source].join(String.EMPTY); 181 } 182 } 183 this.setSource(data.source); 184 this.touch(); 185 return; 186 } 187 188 Skin.prototype.reset_action = function() { 189 if (req.postParams.proceed) { 190 try { 191 var str = this.toString(); 192 Skin.remove.call(this); 193 res.message = gettext("{0} was successfully reset.", str); 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: gettext('You are about to reset {0}.', this) 205 }); 206 res.handlers.site.renderSkin("Site#page"); 207 return; 208 } 209 210 Skin.prototype.compare_action = function() { 211 var originalSkin = this.source || ""; 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 } 272 273 /** 274 * @returns {String} 275 */ 276 Skin.prototype.getSource = function() { 277 var skinSet = app.getSkinfilesInPath(res.skinpath)[this.prototype]; 278 if (skinSet) { 279 var mainSkin = skinSet[this.prototype]; 280 if (mainSkin) { 281 var skin = createSkin(mainSkin).getSubskin(this.name); 282 if (skin) { 283 return skin.getSource(); 284 } 285 } 286 } 287 return null; //String.EMPTY; 288 } 289 290 /** 291 * 292 * @param {String} source 293 */ 294 Skin.prototype.setSource = function(source) { 295 var skin = this.getMainSkin(); 296 if (!skin) { 297 return; 298 } 299 300 res.push(); 301 if (source != null) { 302 res.writeln("<% #" + this.name + " %>"); 303 res.writeln(source.trim().replace(/(<%\s*)#/g, "$1// #")); 304 } 305 var subskins = skin.getSubskinNames(); 306 for (var i in subskins) { 307 if (subskins[i] !== this.name) { 308 res.writeln("<% #" + subskins[i] + " %>"); 309 source = skin.getSubskin(subskins[i]).source; 310 source && res.writeln(source.trim()); 311 } 312 } 313 source = res.pop(); 314 315 var file = this.getStaticFile(); 316 if (!file.exists()) { 317 file.getParentFile().mkdirs(); 318 file.createNewFile(); 319 } 320 var fos = new java.io.FileOutputStream(file); 321 var bos = new java.io.BufferedOutputStream(fos); 322 var writer = new java.io.OutputStreamWriter(bos, "UTF-8"); 323 writer.write(source); 324 writer.close(); 325 bos.close(); 326 fos.close(); 327 328 this.clearCache(); 329 return; 330 } 331 332 /** 333 * 334 * @returns {java.io.File} 335 */ 336 Skin.prototype.getStaticFile = function() { 337 return new java.io.File(res.skinpath[0], this.prototype + "/" + 338 this.prototype + ".skin"); 339 } 340 341 /** 342 * @returns {Skin} 343 */ 344 Skin.prototype.getMainSkin = function() { 345 var skinSet = app.getSkinfilesInPath(res.skinpath)[this.prototype]; 346 if (skinSet && skinSet[this.prototype]) { 347 return createSkin(skinSet[this.prototype]); 348 } 349 return null; 350 } 351 352 /** 353 * 354 */ 355 Skin.prototype.render = function() { 356 return renderSkin(createSkin(this.getSource())); 357 } 358 359 /** 360 * 361 * @param {String} source 362 * @returns {Boolean} 363 */ 364 Skin.prototype.equals = function(source) { 365 // FIXME: The removal of linebreaks is necessary but it's not very nice 366 var re = /\r|\n/g; 367 var normalize = function(str) { 368 return str.replace(re, String.EMPTY); 369 } 370 return normalize(source) === normalize(this.getSource()); 371 } 372 373 /** 374 * @returns {String} 375 */ 376 Skin.prototype.toString = function() { 377 return "Skin #" + this._id + ": " + this.prototype + "." + this.name; 378 } 379 380 /** 381 * 382 */ 383 Skin.prototype.status_macro = function() { 384 return this.isTransient() ? "inherited" : "modified"; 385 } 386 387 /** 388 * 389 */ 390 Skin.prototype.content_macro = function() { 391 return res.write(this.getSource()); 392 } 393