      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