SPICEWORKS.plugin.add(     { name:"Portal Tweaks", version:"0.8", description:"A plugin to customize the portal to meet your needs. Make custom fields required, change the portal colors, reverse the comment order.", guid:"p-8207c6f0-cba2-012b-49c6-0016353cc494-1233939542", settings:{"logoutText":"logout","navColor":"","headerColor":"","commentOrder":"normal","bodyColor":"","requiredMessage":"Please complete required fields","navTextColor":"","headerTextColor":"","bodyTextColor":""}, contentAreas: [{"content_type":"text/javascript","updated_at":"2010/01/04 12:38:16 +0000","id":7,"description":null,"content_name":"initialize.js","user_id":null}], initialize:function(plugin){ // ==SPICEWORKS-PLUGIN==
// @name          Portal Tweaks
// @description   A plugin to customize the portal to meet your needs. Make custom fields required, change the portal colors, reverse the comment order.
// @version       0.8
// @guid          p-8207c6f0-cba2-012b-49c6-0016353cc494-1233939542
// ==/SPICEWORKS-PLUGIN==

plugin.configure({
  settingDefinitions:[
    // this is to support other languages
    { name:'requiredMessage', label:'Required Message', type:'string', defaultValue:'Please complete required fields'},
    { name:'logoutText', label:'Logout Text', type:'string', defaultValue:'logout', example: 'only for old portal, use <a href="http://community.spiceworks.com/plugin/22" target="_blank">Internationalization plugin</a> for new portal'},
    { name:'commentOrder', label:'Comment Order', type:'enumeration', defaultValue:'normal', options:[['Normal (oldest first)', 'normal'], ['Reverse (newest first)', 'reverse']], example:'new portal only'},
    { name:'setPriority', label:'Set Priority', type:'checkbox', example:'allow end-users to set the priority of their tickets'},
    { name:'headerColor', label:'Header Color', type:'string', defaultValue:'', example:'color or <a href="http://www.colorpicker.com/" target="_blank">hex with leading #</a>, leave blank for default, only for new portal'},
    { name:'headerTextColor', label:'Header Text Color', type:'string', defaultValue:'', example:'color or <a href="http://www.colorpicker.com/" target="_blank">hex with leading #</a>, leave blank for default, only for new portal'},
    { name:'navColor', label:'Navigation Color', type:'string', defaultValue:'', example:'color or <a href="http://www.colorpicker.com/" target="_blank">hex with leading #</a>, leave blank for default, only for new portal'},
    { name:'navTextColor', label:'Nav Text Color', type:'string', defaultValue:'', example:'color or <a href="http://www.colorpicker.com/" target="_blank">hex with leading #</a>, leave blank for default, only for new portal'},
    { name:'bodyColor', label:'Body Color', type:'string', defaultValue:'', example:'color or <a href="http://www.colorpicker.com/" target="_blank">hex with leading #</a>, leave blank for default, only for new portal'},
    { name:'bodyTextColor', label:'Body Text Color', type:'string', defaultValue:'', example:'color or <a href="http://www.colorpicker.com/" target="_blank">hex with leading #</a>, leave blank for default, only for new portal'}
  ]
});

var Tweaks = {
  App: {
    initialize: function(){
      this.standardTable = $('standard_attributes');
      this.customTable = $('custom_attributes');

      this.config = $A();
      // add the "required" column to the standard attributes and custom attributes tables
      this.addRequiredColumns();

      // load up any config data from the database (not applicable to first-time use)
      plugin.load('fields', this.loadConfig.bind(this));

      // observe the ajax completed event so that we can pick up any new attributes that were added to the custom attributes table
      document.observe('ajax:completed', this.ajaxCallback.bind(this));
    },
    addRequiredColumns: function(){
      // add the extra column to the header of the standard attributes table
      this._addMetaCells(this.standardTable);

      // add the extra column to the body of the standard attributes table
      this._addBodyCells(this.standardTable);

      // add the extra column to the header and footer of the custom attributes table
      this._addMetaCells(this.customTable);

      // add the extra column to the body of the custom attributes table
      this._addBodyCells(this.customTable);
    },
    loadConfig: function(config){
      this.config = $A(config);
      // after we've loaded previously saved config data, we need to update the required checkboxes to reflect this data
      this.updateCheckboxes();
    },

    // called whenever an ajax request completes
    ajaxCallback: function(){
      // add the new column to any new rows added, this method will not add a column to a table that already had the column added
      this._addBodyCells(this.customTable);
    },

    // called after the stored config data is loaded
    updateCheckboxes: function(){
      // for each item in our stored data, find the checkbox and update it's checked status
      this.config.each(function(item){
        var checkbox = $('cfr-' + item);
        // since custom attributes can be deleted without our knowing, we need to make sure the checkbox exists before setting the value
        if (checkbox) checkbox.checked = true;
      });
    },

    // called whenever a "required" checkbox is clicked
    checkboxClicked: function(enabled, fieldName){

      // if the checkbox is checked, push this field name onto the config array, otherwise pull it out of the array
      if (enabled) this.config.push(fieldName);
      else this.config = this.config.without(fieldName);

      // save our changes to our persitant storage
      plugin.store('fields', this.config);
    },

    // called on page load, adds extra column to header and optionally the footer (only for custom attributes table)
    _addMetaCells: function(table){
      var header = table.down('thead tr'), body = table.down('tbody'), footer = table.down('tfoot tr');
      header.insert('<td>Req.</td>');
      if (footer) footer.insert('<td>&nbsp;</td>');
    },

    // called on page load and whenever an ajax request completes
    _addBodyCells: function(table){
      var checkbox, fieldName;

      // for each row in the table body, add the new column if it hasn't already been added
      table.down('tbody').select('tr').each(function(row){

        // if we've already appended our custom table cell to this row, move on to the next one
        if (row.down('td.cfr-plugin')) return;

        // all attribute row IDs have this syntax of using the attribute type as a prefix, we really only care about ticket attributes
        fieldName = row.id.gsub(/(Agreement|Ticket|Device)-/, '')

        // create the new checkbox, make it checked if it's in our config and only enable the checkbox if this is a ticket attribute
        checkbox = new Element('input', {type:'checkbox', id:'cfr-' + fieldName, name:fieldName, checked:this.config.include(fieldName), disabled:!row.id.startsWith('Ticket-')});

        // setup an observer on the new checkbox to capture the click event
        checkbox.observe('click', function(event){
          var checkbox = event.element();
          // call our click method, passing in the checkbox state and name
          this.checkboxClicked(checkbox.checked, checkbox.name);
        }.bind(this));
        // add the newly created cell
        row.insert((new Element('td', {'class':'cfr-plugin'})).update(checkbox));
      }.bind(this));
    }
  },
  Portal : {
    initialize: function(){
      this.switchLogoutText();
      // mixin our helper
      this.checkRequiredFields = Tweaks.helpers.checkRequiredFields;

      this.config = $A();
      // load the config data, after the config is loaded we will setup the page
      plugin.load('fields', this.setupPage.bind(this));
    },
    switchLogoutText: function(){
      if (document.body.hasClassName('reports')) return;
      $$('#is_logged_in a, #new_ticket_fields p.no_label a').invoke('update', plugin.settings.logoutText);
    },
    setupPage: function(config){
      this.config = config;
      // capture the form submit event as opposed to the button click event so we can capture keyboard submits
      $('new_ticket_form').observe('submit', this.checkRequiredFields.bindAsEventListener(this));
    }
  },
  Portalv2: {
    priorities: {
      labels:{
        priority1: 'high',
        priority2: 'medium',
        priority3: 'low'
      },
      styles:{
        priority1: 'color:#fff;background:#fc939c;padding:0 5px;',
        priority2: 'color:#000;background:#ffffd7;padding:0 5px;',
        priority3: 'color:#fff;background:#56bf53;padding:0 5px;'
      }
    },
    initialize: function(){
      this.config = $A();
      // mixin our helper
      this.checkRequiredFields = Tweaks.helpers.checkRequiredFields;

      this.addColors(); // inject custom CSS for the colors that may have been provided
      this.addColorPreferences(); // add the color options to the portal preferences window
      this.orderComments(); // reorder the comments newest first if desired
      this.newTicket(); // do some mods to the new ticket form

      var matches = location.href.match(/view-help-request\/(\d+)/);
      if (matches && matches.length > 1) this.viewingTicket(matches[1]);

      // load the config data, after the config is loaded we will setup the page
      plugin.load('fields', this.setupPage.bind(this));
    },
    setupPage: function(config){
      // our config is passed in to this callback
      this.config = config;

      // attach our validation routine to any new ticket forms on the page
      $$('form.new-ticket').invoke('observe', 'submit', this.checkRequiredFields.bindAsEventListener(this));
    },
    newTicket: function(){
      var newTicket = $$('form.new-ticket');
      if (newTicket && newTicket.length > 0 && plugin.settings.setPriority){
        var desc = newTicket.first().down('p.description'), resetStyle = 'padding-top:0;float:none;width:auto;';
        var priority = '<p class="priority"><label>Priority</label>' +
                       '<input type="radio" name="ticket[priority]" id="ticket_priority_high" value="1" />&nbsp;<label style="' + resetStyle + '" for="ticket_priority_high">High</label>&nbsp;' +
                       '<input type="radio" name="ticket[priority]" id="ticket_priority_med" checked="checked" value="2" />&nbsp;<label style="' + resetStyle + '" for="ticket_priority_med">Medium</label>&nbsp;' +
                       '<input type="radio" name="ticket[priority]" id="ticket_priority_low" value="3" />&nbsp;<label style="' + resetStyle + '" for="ticket_priority_low">Low</label>' +
                       '</p>';
        desc.insert({after:priority});
      }
    },
    viewingTicket: function(ticketID){
      // not implemented yet because fetching data through the API requires a valid admin user and we're in the end-user portal
      return;

      this.ticket = new Ajax.Request('/api/tickets/' + ticketID + '.json', {method:'get', asynchronous:false});
      if (this.ticket.transport.responseText.isJSON()){
        this.ticket = this.ticket.transport.responseText.evalJSON();
        var priority = this.ticket.priority, dueDate = this.ticket.due_date,
            main = $('main'), meta = main.down('.ticket-description .meta');
        var priorityKey = 'priority' + priority, priorityMessage = (new Element('span', {'class':'meta'})).update('Priority: ' + this.priorities.labels[priorityKey]);
        priorityMessage.setStyle(this.priorities.styles[priorityKey]);
        meta.insert('&nbsp;|&nbsp;');
        meta.insert(priorityMessage);
      }
    },
    addColors: function(){
      var injectCSS = '';
      if (plugin.settings.headerColor) injectCSS += 'body.custom-colors #header{ background-color:' + plugin.settings.headerColor + '}';
      if (plugin.settings.headerTextColor) injectCSS += 'body.custom-colors #header, body.custom-colors #header a{ color:' + plugin.settings.headerTextColor + '}';
      if (plugin.settings.navColor) injectCSS += 'body.custom-colors #navigation{ background-color:' + plugin.settings.navColor + '}';
      if (plugin.settings.navTextColor) injectCSS += 'body.custom-colors #navigation ul li a{ color:' + plugin.settings.navTextColor + '}';
      if (plugin.settings.bodyColor) {
        injectCSS += 'body.custom-colors, body.custom-colors #container, body.custom-colors #content div.content-block{ background-color:' + plugin.settings.bodyColor + '}';
        injectCSS += 'body.custom-colors #navigation ul li.current a{ background-color:' + plugin.settings.bodyColor + '}';
      }
      if (plugin.settings.bodyTextColor) injectCSS += 'body.custom-colors div#content{ color:' + plugin.settings.bodyTextColor + '}';

      if (injectCSS != ''){
        $(document.body).addClassName('custom-colors');
        Tweaks.helpers.cssLoader(injectCSS);
      }
    },
    addColorPreferences: function(){
      var preferences = $('preferences');
      if (preferences){
        var insertBefore = preferences.down('p.example'), insertion = '', observationStack = $A();
        $w('headerColor headerTextColor navColor navTextColor bodyColor bodyTextColor').each(function(setting){
          var definition = plugin.settingDefinitions.detect(function(def){ return def.name == setting }), input;
          definition.activeValue = plugin.settings[setting];
          insertion += '<p><label for="plugin_color_' + definition.name + '">' + definition.label.replace(' color', '') + '</label><input type="text" class="text" name="plugin[color][' + definition.name + ']" id="plugin_color_' + definition.name + '" value="' + definition.activeValue + '" /> <em class="example" style="font-size:10px;">color or <a href="http://www.colorpicker.com/" target="_blank">hex code</a></em></p>';
          observationStack.push({id:'plugin_color_' + definition.name, setting:definition.name});
        });
        insertBefore.insert({before:'<div class="custom-colors" style="margin:10px 0;padding:10px 0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;"><h3 style="margin:0 0 5px;padding:0;">Theme Color Overrides</h3>' + insertion + '</div>'})
        observationStack.each(function(def){
          $(def.id).observe('blur', this.colorChanged.curry(def.setting).bindAsEventListener(this));
        }.bind(this))
        preferences.down('form').observe('submit', function(event){
          var form = event.element();
          event.stop();
          plugin.store('settings', plugin.settings, function(){
            form.submit();
          });
        });
      }
    },
    colorChanged: function(setting, event){
      plugin.settings[setting] = $F(event.element())
      this.addColors();
    },
    orderComments: function(){
      if (plugin.settings.commentOrder == 'reverse' && $$('div.view_ticket').size() > 0){
        $$('.ticket-comments').each(function(list){
          $$('div.view_ticket h3').invoke('insert', '&nbsp;(most recent first)');
          list.select('li').collect(function(item){
            return item.remove();
          }).reverse().each(function(item){
            list.insert(item);
          });
        })
      }
    }
  },
  helpers: {
    checkRequiredFields: function(event){
      var withErrors = false

      // for each field in our config, find it and if it exists, and is empty, then raise the error flag
      this.config.each(function(field){
        field = $('ticket_' + field);

        // if the field doesn't exist, move on to the next one. this can happen with attributes that were deleted
        if (!field) return;
        // if the value is empty (works for drop-down boxes too), raise the flag
        if (field.value == '') {
          field.up('p').addClassName('with_error');
          withErrors = true;
        }
      }.bind(this));

      // if we have errors, stop the submit event from bubbling up and set an error message
      if (withErrors){
        event.stop();
        var message;
        if (SPICEWORKS.portalv2 && SPICEWORKS.portalv2.isShowing()){
          var buttonContainer = $$('form.new-ticket p.button').first();
          buttonContainer.down('input').disabled = false;
          message = buttonContainer.down('em.highlight');
        } else {
          message = $('new_ticket_form_status');
        }
        message.update(plugin.settings.requiredMessage);
      }
    },
    cssLoader:function(cssText){
      var styleNode = document.createElement('style');
      styleNode.setAttribute("type", "text/css");
      if (styleNode.styleSheet) { // workaround for IE
        styleNode.styleSheet.cssText = cssText;
      } else if (Prototype.Browser.WebKit) { 
        styleNode.innerText = cssText;
      } else { // DOM
        styleNode.update(cssText);
      }
      $$('head').first().appendChild(styleNode);
    }
  }
};

SPICEWORKS.app.settings.advanced.ready(Tweaks.App.initialize.bind(Tweaks.App));
SPICEWORKS.portal.ready(Tweaks.Portal.initialize.bind(Tweaks.Portal));
if (SPICEWORKS.portalv2) SPICEWORKS.portalv2.ready(Tweaks.Portalv2.initialize.bind(Tweaks.Portalv2));
      }
    }
 );
