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