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  * @param {String} prototype
 50  * @param {String} name
 51  * @param {Layout} layout
 52 */
 53 Skin.add = function(prototype, name, layout) {
 54    var skin = new Skin(prototype, name);
 55    layout.skins.add(skin);
 56    return skin;
 57 }
 58 
 59 /**
 60  * 
 61  * @param {Skin} skin
 62  */
 63 Skin.remove = function() {
 64    if (this.constructor === Skin) {
 65       this.setSource(this.source);
 66       this.source = null;
 67       this.remove();
 68    }
 69    return;
 70 }
 71 
 72 /**
 73  * @returns  {String[]}
 74  */
 75 Skin.getPrototypeOptions = function() {
 76    var prototypes = [];
 77    var content, file;
 78    var skinFiles = app.getSkinfilesInPath(res.skinpath);
 79    for (var name in skinFiles) {
 80       // Include root skins only for root site
 81       if (name === root.constructor.name && res.handlers.site !== root) {
 82          continue;
 83       }
 84       if (skinFiles[name][name]) {
 85          prototypes.push({value: name, display: name});
 86       }
 87    }
 88    return prototypes.sort(new String.Sorter("display"));
 89 }
 90 
 91 /**
 92  * @name Skin
 93  * @constructor
 94  * @param {String} prototype
 95  * @param {String} name
 96  * @property {Date} created
 97  * @property {User} creator
 98  * @property {Layout} layout
 99  * @property {Date} modified
100  * @property {User} modifier
101  * @property {String} prototype
102  * @property {String} source
103  * @extends HopObject
104  */
105 Skin.prototype.constructor = function(prototype, name) {
106    HopObject.confirmConstructor(this);
107    this.prototype = prototype || String.EMPTY;
108    this.name = name || String.EMPTY;
109    this.creator = this.modifier = session.user;
110    this.created = this.modified = new Date;
111    return this;
112 }
113 
114 /**
115  * 
116  * @param {String} action
117  * @returns {Boolean}
118  */
119 Skin.prototype.getPermission = function(action) {
120    switch (action) {
121       case ".":
122       case "main":
123       return true;
124    }
125    return res.handlers.skins.getPermission("main");
126 }
127 
128 /**
129  * 
130  * @param {String} action
131  * @returns {String}
132  */
133 Skin.prototype.href = function(action) {
134    res.push();
135    res.write(res.handlers.layout.skins.href());
136    res.write(this.prototype);
137    res.write("/");
138    res.write(this.name);
139    res.write("/");
140    action && (res.write(action));
141    return res.pop();
142 }
143 
144 Skin.prototype.main_action = function() {
145    if (res.handlers.site === root) {
146       res.contentType = "text/plain";
147       res.write(this.getSource());
148       return;
149    }
150    res.redirect(this.href("edit"));
151    return;
152 }
153 
154 Skin.prototype.edit_action = function() {
155    if (req.postParams.save) {
156       try {
157          var url = this.href(req.action);
158          this.update(req.postParams);
159          res.message = gettext("The changes were saved successfully.");
160          if (req.postParams.save == 1) {
161             res.redirect(url);
162          } else {
163             res.redirect(res.handlers.layout.skins.href("modified"));
164          }
165       } catch (ex) {
166          res.message = ex;
167          app.log(ex);
168       }
169    }
170    res.data.action = this.href(req.action);
171    res.data.title = gettext('Edit Skin: {0}.{1}', this.prototype, this.name);
172    res.data.body = this.renderSkinAsString("$Skin#edit");
173    res.handlers.skins.renderSkin("$Skins#page");
174    return;
175 }
176 
177 /**
178  * 
179  * @param {Object} data
180  */
181 Skin.prototype.update = function(data) {
182    if (this.isTransient()) {
183       res.handlers.layout.skins.add(this);
184       this.source = this.getSource(); // Copies the skin file source to database
185    }
186    if (this.prototype === "Site" && this.name === "page") {
187       var macro = "response.body";
188       if (!createSkin(data.source).containsMacro(macro)) {
189          var macro = ["<code><%", macro, "%></code>"].join(String.EMPTY);
190          throw Error(gettext("The {0} macro is missing. It is essential for accessing the site and must be present in this skin.", macro));
191       }
192    }
193    this.setSource(data.source);   
194    this.touch();
195    return;
196 }
197 
198 Skin.prototype.reset_action = function() {
199    if (req.postParams.proceed) {
200       try {
201          Skin.remove.call(this);
202          res.message = gettext("{0} was successfully reset.", gettext("Skin"));
203          res.redirect(res.handlers.layout.skins.href("modified"));
204       } catch(ex) {
205          res.message = ex;
206          app.log(ex);
207       }
208    }
209 
210    res.data.action = this.href(req.action);
211    res.data.title = gettext("Confirm Reset");
212    res.data.body = this.renderSkinAsString("$HopObject#confirm", {
213       text: this.getConfirmText()
214    });
215    res.handlers.site.renderSkin("Site#page");
216    return;
217 }
218 
219 Skin.prototype.compare_action = function() {
220    var originalSkin = this.source || String.EMPTY;
221    var diff = this.getSource().diff(originalSkin);
222    if (!diff) {
223       res.message = gettext("No differences were found.");
224    } else {
225       res.push();
226       var param = {}, leftLineNumber = rightLineNumber = 0;
227       for each (let line in diff) {
228          if (line.deleted) {
229             param.right = encode(line.value);
230             param.leftStatus = "added";
231             param.rightStatus = '';
232             for (let i=0; i<line.deleted.length; i++) {
233                leftLineNumber += 1;
234                param.leftLineNumber = leftLineNumber;
235                param.rightLineNumber = '';
236                param.left = encode(line.deleted[i]);
237                param.right = '';
238                this.renderSkin("$Skin#difference", param);
239             }
240          }
241          if (line.inserted) {
242             param.left = encode(line.value);
243             param.leftStatus = '';
244             param.rightStatus = 'removed';
245             for (let i=0; i<line.inserted.length; i++) {
246                rightLineNumber += 1;
247                param.leftLineNumber = '';
248                param.rightLineNumber = rightLineNumber;
249                param.left = '';
250                param.right = encode(line.inserted[i]);
251                this.renderSkin("$Skin#difference", param);
252             }
253          }
254          if (line.value !== null) {
255             leftLineNumber += 1;
256             rightLineNumber += 1;
257             param.leftLineNumber = leftLineNumber;
258             param.rightLineNumber = rightLineNumber;
259             param.leftStatus = param.rightStatus = '';
260             param.left = encode(line.value);
261             param.right = param.left;
262             this.renderSkin("$Skin#difference", param);
263          }
264       }
265       res.data.diff = res.pop();
266    }
267 
268    res.data.title = gettext("Compare Skin: {0}", this.getTitle());
269    res.data.body = this.renderSkinAsString("$Skin#compare");
270    res.handlers.skins.renderSkin("$Skins#page");
271    return;
272 }
273 
274 /**
275  *
276  * @return {String}
277  */
278 Skin.prototype.getTitle = function() {
279    return this.prototype + '.' + this.name;
280 }
281 
282 /**
283  * 
284  * @param {String} name
285  * @return {Object}
286  */
287 Skin.prototype.getFormOptions = function(name) {
288    switch (name) {
289       case "prototype":
290       return Skin.getPrototypeOptions();
291    }
292 }
293 
294 Skin.prototype.getFormValue = function(name) {
295    switch (name) {
296       case "source":
297       return req.data.source || this.getSource();
298    }
299    return HopObject.prototype.getFormValue.apply(this, arguments);
300 }
301 
302 /**
303  * @returns {String}
304  */
305 Skin.prototype.getSource = function() {
306    var skin;
307    // FIXME: Maintain skin inheritance by checking if we target the Site skin of root
308    if (res.handlers.site === root && this.prototype === "Site") {
309       skin = this.getSubskin("Root");
310       if (skin) {
311          return skin.getSource();
312       }
313    }
314    skin = this.getSubskin();
315    if (skin) {
316       return skin.getSource();
317    }
318    return null;
319 }
320 
321 /**
322  *
323  * @param {String} source
324  */
325 Skin.prototype.setSource = function(source) {
326    // FIXME: Maintain skin inheritance by checking if we target the Site skin of root
327    var prototype = (res.handlers.site === root && this.prototype === "Site") ? "Root" : this.prototype;
328    var skin = this.getMainSkin(prototype);
329    if (!skin) {
330       return;
331    }
332 
333    res.push();
334    if (source != null) {
335       res.writeln("<% #" + this.name + " %>");
336       res.writeln(source.trim().replace(/(<%\s*)#/g, "$1// #"));
337    }
338    var subskins = skin.getSubskinNames();
339    for (var i in subskins) {
340       if (subskins[i] !== this.name) {
341          res.writeln("<% #" + subskins[i] + " %>");
342          source = skin.getSubskin(subskins[i]).source;
343          source && res.writeln(source.trim());
344       }
345    }
346    source = res.pop();
347 
348    var file = this.getStaticFile();   
349    if (!file.exists()) {
350       file.getParentFile().mkdirs();
351       file.createNewFile();
352    }
353    var fos = new java.io.FileOutputStream(file);
354    var bos = new java.io.BufferedOutputStream(fos);
355    var writer = new java.io.OutputStreamWriter(bos, "UTF-8");
356    writer.write(source);
357    writer.close();
358    bos.close();
359    fos.close();
360 
361    this.clearCache();
362    return;
363 }
364 
365 /**
366  * 
367  * @returns {java.io.File}
368  */
369 Skin.prototype.getStaticFile = function() {
370    // FIXME: Maintain skin inheritance by checking if we target the Site skin of root
371    var prototype = (res.handlers.site === root && this.prototype === "Site") ? "Root" : this.prototype;
372    return new java.io.File(res.skinpath[0], prototype + "/" + this.prototype + ".skin");
373 }
374 
375 /**
376  * @param {String} prototype 
377  * @returns {Skin}
378  */
379 Skin.prototype.getMainSkin = function(prototype) {
380    var source, skinSet = app.getSkinfilesInPath(res.skinpath)[prototype || this.prototype];
381    if (skinSet) {
382       source = skinSet[this.prototype];
383       if (source !== null) {
384          return createSkin(source);
385       }
386    }
387    return null;
388 }
389 
390 /**
391  *
392  * @param prototype
393  * @param name
394  * @returns {Skin}
395  */
396 Skin.prototype.getSubskin = function(prototype, name) {
397    var mainSkin = this.getMainSkin(prototype);
398    if (mainSkin) {
399       return mainSkin.getSubskin(name || this.name);
400    }
401    return null;
402 }
403 
404 /**
405  * 
406  */
407 Skin.prototype.render = function() {
408    return renderSkin(createSkin(this.getSource()));
409 }
410 
411 /**
412  * 
413  * @param {String} source
414  * @returns {Boolean}
415  */
416 Skin.prototype.equals = function(source) {
417    // FIXME: The removal of linebreaks is necessary but it's not very nice
418    var re = /\r|\n/g;
419    var normalize = function(str) {
420       return str.replace(re, String.EMPTY);
421    }
422    return normalize(source) === normalize(this.getSource());
423 }
424 
425 /**
426  * @returns {String}
427  */
428 Skin.prototype.getConfirmText = function() {
429    return gettext("You are about to reset the skin {0}.{1}.", 
430          this.prototype, this.name);
431 }
432 
433 /**
434  * @returns {String}
435  */
436 Skin.prototype.toString = function() {
437    return "Skin #" + this._id + ": " + this.prototype + "." + this.name;
438 }
439 
440 /**
441  * 
442  */
443 Skin.prototype.status_macro = function() {
444    return this.isTransient() ? "inherited" : "modified"; 
445 }
446 
447 /**
448  * 
449  */
450 Skin.prototype.content_macro = function() {
451    return res.write(this.getSource());
452 }
453