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