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 skinSet = app.getSkinfilesInPath(res.skinpath)[this.prototype]; 279 if (skinSet) { 280 var mainSkin = skinSet[this.prototype]; 281 if (mainSkin) { 282 var skin = createSkin(mainSkin).getSubskin(this.name); 283 if (skin) { 284 return skin.getSource(); 285 } 286 } 287 } 288 return null; //String.EMPTY; 289 } 290 291 /** 292 * 293 * @param {String} source 294 */ 295 Skin.prototype.setSource = function(source) { 296 var skin = this.getMainSkin(); 297 if (!skin) { 298 return; 299 } 300 301 res.push(); 302 if (source != null) { 303 res.writeln("<% #" + this.name + " %>"); 304 res.writeln(source.trim().replace(/(<%\s*)#/g, "$1// #")); 305 } 306 var subskins = skin.getSubskinNames(); 307 for (var i in subskins) { 308 if (subskins[i] !== this.name) { 309 res.writeln("<% #" + subskins[i] + " %>"); 310 source = skin.getSubskin(subskins[i]).source; 311 source && res.writeln(source.trim()); 312 } 313 } 314 source = res.pop(); 315 316 var file = this.getStaticFile(); 317 if (!file.exists()) { 318 file.getParentFile().mkdirs(); 319 file.createNewFile(); 320 } 321 var fos = new java.io.FileOutputStream(file); 322 var bos = new java.io.BufferedOutputStream(fos); 323 var writer = new java.io.OutputStreamWriter(bos, "UTF-8"); 324 writer.write(source); 325 writer.close(); 326 bos.close(); 327 fos.close(); 328 329 this.clearCache(); 330 return; 331 } 332 333 /** 334 * 335 * @returns {java.io.File} 336 */ 337 Skin.prototype.getStaticFile = function() { 338 return new java.io.File(res.skinpath[0], this.prototype + "/" + 339 this.prototype + ".skin"); 340 } 341 342 /** 343 * @returns {Skin} 344 */ 345 Skin.prototype.getMainSkin = function() { 346 var skinSet = app.getSkinfilesInPath(res.skinpath)[this.prototype]; 347 if (skinSet && skinSet[this.prototype]) { 348 return createSkin(skinSet[this.prototype]); 349 } 350 return null; 351 } 352 353 /** 354 * 355 */ 356 Skin.prototype.render = function() { 357 return renderSkin(createSkin(this.getSource())); 358 } 359 360 /** 361 * 362 * @param {String} source 363 * @returns {Boolean} 364 */ 365 Skin.prototype.equals = function(source) { 366 // FIXME: The removal of linebreaks is necessary but it's not very nice 367 var re = /\r|\n/g; 368 var normalize = function(str) { 369 return str.replace(re, String.EMPTY); 370 } 371 return normalize(source) === normalize(this.getSource()); 372 } 373 374 /** 375 * @returns {String} 376 */ 377 Skin.prototype.getConfirmText = function() { 378 return gettext("You are about to reset the skin {0}.{1}.", 379 this.prototype, this.name); 380 } 381 382 /** 383 * @returns {String} 384 */ 385 Skin.prototype.toString = function() { 386 return "Skin #" + this._id + ": " + this.prototype + "." + this.name; 387 } 388 389 /** 390 * 391 */ 392 Skin.prototype.status_macro = function() { 393 return this.isTransient() ? "inherited" : "modified"; 394 } 395 396 /** 397 * 398 */ 399 Skin.prototype.content_macro = function() { 400 return res.write(this.getSource()); 401 } 402