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