      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