If you find a bug in Unifi we may issue a hotfix so you can get the features you need without having to upgrade.

Unifi has a Script Include called hotfix. Simply replace the script in the hotfix Script Include with the one shown below and you will instantly have access to the fixes.

These hotfixes will be shipped as real fixes with the next version of Unifi, so make sure you have the correct hotfix for your version.

/**
 * Executes a child function corresponding to the object's type property.
 * The object is passed to the child function so methods and properties can be overridden.
 *
 * @param  {Object} obj The full class object to be patched.
 * @version 2.0.7.6
 */
function hotfix(obj) {
  var type = typeof obj === 'function' ? obj.prototype.type : obj.type;
  if (type && typeof hotfix[type] === 'function') {
    hotfix[type](obj);
  }
}

/**
 * Fix an issue where ServiceNow AV updates an attachment after it's been put on
 * a ticket by an inbound request. The AV update causes the attachment to be sent
 * outbound to the same integration which is incorrect.
 * 
 */
hotfix.AttachmentHandler = function (AttachmentHandler) {

  AttachmentHandler.createBondAttachment = function (attachment) {
    var send = false,
        gr;

    function processIntegration(integration) {
      utils.forEach(integration.getBonds(attachment.table_sys_id, attachment.table_name), processBond);
    }
    function processBond(bond) {
      if (AttachmentHandler.addToBond([attachment], bond)) {
        send = true;
      }
    }

    utils.forEach(Process.getForTable(attachment.table_name), function (process) {
      utils.forEach(process.getIntegrations(), processIntegration);
    });

    if (send) {
      gr = new GlideRecord(attachment.table_name);
      gr.get(attachment.table_sys_id);
      if (gr.isValidRecord()) {
        Message.processOutbound(gr, {
          attachment_added: true, // only match messages with "Attachment added" condition
          parent_tables: true // search table hierarchy
        });
      } else {
        gs.error(t('Attachment %0 does not have a valid record. Cannot process outbound.', attachment));
      }
    }
  };

  AttachmentHandler.createBondAttachments = function (attachments, target, source_bond, direction) {
    var send = false,
        bond_gr,
        bond,
        i;

    bond = new Bond(target.getConfig());
    bond_gr = Bond.getAllForTarget(target);
    while (bond_gr.next()) {
      if (bond_gr.getValue('sys_id') != source_bond) {
        bond.setRecord(bond_gr);
        if (AttachmentHandler.addToBond(attachments, bond, target, direction)) {
          send = true;
        }
      }
    }

    if (send) {
      Message.processOutbound(target.getRecord(), {
        attachment_added: true,
        parent_tables: true
      });
    }

  };

  AttachmentHandler.addToBond = function (attachments, bond, target, direction) {
    var bonded = [];
    var count = 0;
    var gr = new GlideRecord(bond.getConfig('table_attachment'));
    target = target || bond.getTarget();

    gr.addQuery('bond', bond.getUniqueValue());
    ws_console.logQuery(gr);

    while (gr.next()) {
      bonded.push(gr.attachment + '');
    }

    utils.forEach(attachments, function (attachment) {
      var attachment_id;
      if (attachment && typeof attachment.getUniqueValue === 'function') {
        attachment_id = attachment.getUniqueValue();
      } else {
        attachment_id = attachment + '';
      }

      // don't do anything if we've already processed this attachment
      if (bonded.indexOf(attachment_id) > -1) return;

      gr.newRecord();
      gr.attachment = attachment_id;
      gr.table      = target.getTableName();
      gr.document   = target.getValue('sys_id');
      gr.bond       = bond.getValue('sys_id');
      gr.state      = 'Ready';
      gr.direction  = direction || 'Outbound';
      gr.sys_domain = bond.getValue('sys_domain');
      gr.insert();
      count++;
    });

    return count;
  };

};

hotfix.Bond = function (Bond) {
  
  Bond.prototype.setOk = function setOk(note) {
    this.setValue('status', 'OK');
    if (note) this.addHistory(note, 'info');
  };

  Bond.prototype.setAwaiting = function setAwaiting(note) {
    this.setValue('status', 'Awaiting');
    if (note) this.addHistory(note, 'info');
  };

  Bond.prototype.setError = function setError(note) {
    this.setValue('status', 'Error');
    if (note) this.addHistory(note, 'error');
  };
}

hotfix.Field = function (Field) {

  Field.generateFieldChoices = function generateFieldChoices(field_gr) {
    var tables = [],
        choice_gr;

    tables = new GlideTableHierarchy(field_gr.table).getTables();

    /*
      We do not use GlideRecordSecure here as this needs system access.
    */
    choice_gr = new GlideRecord('sys_choice');

    for (var i = 0; i < tables.length && !choice_gr.hasNext(); i++) {
      choice_gr.initialize();
      choice_gr.addQuery('name',     '=', tables[i]);
      choice_gr.addQuery('element',  '=', field_gr.element);
      choice_gr.addQuery('language', '=', gs.getProperty('glide.sys.language', 'en'));
      choice_gr.addQuery('inactive', '=', false);
      ws_console.logQuery(choice_gr);
    }
    
    while (choice_gr.next()) {     
      FieldChoice.createFromChoice(field_gr, choice_gr, 'Inbound'); 
      FieldChoice.createFromChoice(field_gr, choice_gr, 'Outbound');
    }
  };

};

hotfix.Message = function (Message) {

  Message.prototype.evaluateScript = function evaluateScript(record, element, vars) {
    vars.message = this.getRecord();
    vars.variables = this.getIntegration().getActiveConnection().getVariables();
    if (vars.stage) {
      vars.$stage = vars.stage.$stage;
    }
    return utils.evaluateScript(record, element, vars);
  };

};

hotfix.Transaction = function (Transaction) {

  // Fix transaction replay so it works with 'system' user
  Transaction.replay = function replay(current, user) {
    var integration = Integration.createNoConfig(current.integration.getRefRecord()),
        config      = integration.getConfig(),
        transaction = new Transaction(config, current),
        message_id,
        is_inbound,
        user_name,
        user_gr,
        stage,
        script;

    function getLatestMessageId(message_id) {
      var query = 'message_idSTARTSWITH' + message_id + '^ORDERBYDESCmessage_id';
      var t = Transaction.getTransaction(config, query);
      return t.isValidRecord() ? t.getValue('message_id') : message_id;
    }

    if (!transaction.isValidRecord()) {
      ws_console.error('Cannot replay invalid transaction %0', [current]);
      return;
    }

    // if the user is system, then just set the user_name - no user record lookup
    if (user == 'system') { 
      user_name = user;
    } else {

      // if we haven't been given a user, get it from the record
      if (!user) {
        user = current.sys_created_by;
      }

      // validate the user record so we can run as that user
      user_gr = utils.getUserRecord(user);
      if (!user_gr.isValidRecord()) {
        ws_console.error('Cannot replay transaction with invalid user %0', [user]);
        return;
      }
      user_name = user_gr.user_name;
    }

    is_inbound = transaction.getValue('direction') == 'Inbound';
    if (is_inbound) {
      transaction.setQueued();
    } else {
      stage = Stage.getOutbound(transaction);
      if (!stage.cloneRecord()) {
        ws_console.error('Stage record not found for transaction %0', [transaction.getDisplayValue()]);
      }
    }

    message_id = transaction.getValue('message_id');
    message_id = getLatestMessageId(message_id);
    message_id = utils.partialIncrement(message_id, 1, 1);

    if (transaction.isPending()) {
      transaction.setIgnored();
    }
    transaction.commit(); // update the original transaction

    transaction.cloneRecord();
    transaction.setQueued();
    transaction.setValue('message_id', message_id);
    transaction.setValue('process_state', '');
    transaction.setValue('error', '');
    transaction.setValue('process_error', '');

    if (is_inbound) {
      transaction.setValue('error', '');
    } else {
      if (stage && stage.isValid()) {
          stage.setValue('transaction', transaction.getValue('sys_id'));
          stage.commit();
        } else {
          transaction.setError('Stage record not found.');
        }
    }
    transaction.getBond().addHistory(
      t('[!0] Replaying transaction !1: !2', integration.getDisplayValue(), message_id, current.message.message_name)
    );
    transaction.commit(); // insert the new transaction

    ActivityLog.setDocument(transaction.getRecord());

    if (!transaction.getValue('error')) {

      if (is_inbound) {
        HttpRequest.replayFromTransaction(current, transaction);
      } else {
        script = 'var gr = new GlideRecord("' + transaction.getTableName() + '");\n' +
                 'if (gr.get("' + transaction.getValue('sys_id') + '")) {\n' +
                 '  x_snd_eb.ActivityLog.setDocument(gr);\n' +
                 '  gr.transaction_state = "Sending";\n' +
                 '  gr.sys_created_by = "' + user_name + '";' +
                 '  gr.update();\n' +
                 '}';

        // user_gr is empty is user_name is "system"
        ScheduledScript.executeNow('Transaction.replay', script, user_gr);
      }
    }

    return transaction;
  };

  Transaction.prototype.findNext = function findNext(options) {
    var transaction,
        message_id,
        created,
        gr;

    options = options || {};
    transaction = new Transaction(this.getConfig());

    // Cannot find next transaction if document not set
    if (!this.nil('document')) {

      // If the transaction GlideRecord has not been committed then created_on is
      // not populated - use current time instead
      created = this.getValue('created_ms') || new Date().getTime().toString();

      gr = new GlideRecord(this._table);
      gr.addQuery('document', '=', this.getValue('document'));
      gr.addQuery('table', '=', this.getValue('table'));
      gr.addQuery('bond', '=', this.getValue('bond'));
      gr.addQuery('sys_id', '!=', this.getValue('sys_id'));
      if (options.match_direction) {
        gr.addQuery('direction', this.getValue('direction'));
      }

      // if we are replaying, we need to ensure we filter out all previous transactions
      // which have the same created_ms value as this transaction.
      // If we don't do this, we'll return the original transaction that we are replaying.
      message_id = this.getValue('message_id') + '';
      if (message_id.indexOf('.') > -1) {
        gr.addQuery('message_id', 'DOES NOT CONTAIN', message_id.split('.')[0]);
      }

      gr.addQuery('created_ms', '>=', created);
      gr.orderBy('created_ms');
      gr.setLimit(1);
      ws_console.logQuery(gr);

      if (gr.next()) {
        transaction.setRecord(gr);
        transaction.setRefs(this.getRefs());
      }
    }

    return transaction;
  };
};

hotfix.WebServiceInterface = function (WebServiceInterface) {
  
  WebServiceInterface.prototype.setMidServer = function (name, timeout) {
    this.service.setMIDServer(name);
    this.service.setEccParameter('skip_sensor', 'true');
    this.mid_name = name;
    this.mid_timeout = timeout || 60;
  };

  WebServiceInterface.prototype.setRequestBody = function setRequestBody(payload) {
    if (/^sys_attachment:[a-f0-9]{32}$/.test(payload)) {
      this.service.setRequestBodyFromAttachment(payload.split(':')[1]);
    } else {
      this.service.setRequestBody(payload);
    }
  };

};

hotfix.ws_console = function (ws_console) {
  ws_console.DEFAULTS.SYSTEM_LOGGING = function () {
    var result = '' + gs.getProperty(ws_console.APP_SCOPE + '.logging.verbosity', 'error');
    if (!(result in {off:1,trace:1,debug:1,info:1,warn:1,error:1})) {
        throw new Error('Unknown property value for ' + ws_console.APP_SCOPE +
                        '.logging.verbosity. Expects off, trace, debug, info, warn or error.');
    }
    return result;
  };
};