Source: src/js/models/SolrResult.js

define(["jquery", "underscore", "backbone"], function ($, _, Backbone) {
  /**
   * @class SolrResult
   * @classdesc A single result from the Solr search service
   * @classcategory Models
   * @extends Backbone.Model
   */
  var SolrResult = Backbone.Model.extend(
    /** @lends SolrResult.prototype */ {
      // This model contains all of the attributes found in the SOLR 'docs' field inside of the SOLR response element
      defaults: {
        abstract: null,
        entityName: null,
        indexed: true,
        archived: false,
        origin: "",
        keywords: "",
        title: "",
        pubDate: "",
        eastBoundCoord: "",
        westBoundCoord: "",
        northBoundCoord: "",
        southBoundCoord: "",
        attributeName: "",
        beginDate: "",
        endDate: "",
        pubDate: "",
        id: "",
        seriesId: null,
        resourceMap: null,
        downloads: null,
        citations: 0,
        selected: false,
        formatId: null,
        formatType: null,
        fileName: null,
        datasource: null,
        rightsHolder: null,
        size: 0,
        type: "",
        url: null,
        obsoletedBy: null,
        geohash_9: null,
        read_count_i: 0,
        reads: 0,
        isDocumentedBy: null,
        isPublic: null,
        isService: false,
        serviceDescription: null,
        serviceTitle: null,
        serviceEndpoint: null,
        serviceOutput: null,
        notFound: false,
        newestVersion: null,
        //@type {string} - The system metadata XML as a string
        systemMetadata: null,
        provSources: [],
        provDerivations: [],
        //Provenance index fields
        prov_generated: null,
        prov_generatedByDataONEDN: null,
        prov_generatedByExecution: null,
        prov_generatedByFoafName: null,
        prov_generatedByOrcid: null,
        prov_generatedByProgram: null,
        prov_generatedByUser: null,
        prov_hasDerivations: null,
        prov_hasSources: null,
        prov_instanceOfClass: null,
        prov_used: null,
        prov_usedByDataONEDN: null,
        prov_usedByExecution: null,
        prov_usedByFoafName: null,
        prov_usedByOrcid: null,
        prov_usedByProgram: null,
        prov_usedByUser: null,
        prov_wasDerivedFrom: null,
        prov_wasExecutedByExecution: null,
        prov_wasExecutedByUser: null,
        prov_wasGeneratedBy: null,
        prov_wasInformedBy: null,
      },

      initialize: function () {
        this.setURL();
        this.on("change:id", this.setURL);

        this.set("type", this.getType());
        this.on("change:read_count_i", function () {
          this.set("reads", this.get("read_count_i"));
        });
      },

      type: "SolrResult",

      // Toggle the `selected` state of the result
      toggle: function () {
        this.selected = !this.get("selected");
      },

      /**
       * Returns a plain-english version of the general format - either image, program, metadata, PDF, annotation or data
       * @return {string}
       */
      getType: function () {
        //The list of formatIds that are images
        var imageIds = [
          "image/gif",
          "image/jp2",
          "image/jpeg",
          "image/png",
          "image/svg xml",
          "image/svg+xml",
          "image/bmp",
        ];
        //The list of formatIds that are images
        var pdfIds = ["application/pdf"];
        var annotationIds = [
          "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html",
        ];
        var collectionIds = [
          "https://purl.dataone.org/collections-1.0.0",
          "https://purl.dataone.org/collections-1.1.0",
        ];
        var portalIds = [
          "https://purl.dataone.org/portals-1.0.0",
          "https://purl.dataone.org/portals-1.1.0",
        ];

        //Determine the type via provONE
        var instanceOfClass = this.get("prov_instanceOfClass");
        if (typeof instanceOfClass !== "undefined") {
          var programClass = _.filter(instanceOfClass, function (className) {
            return className.indexOf("#Program") > -1;
          });
          if (typeof programClass !== "undefined" && programClass.length)
            return "program";
        } else {
          if (this.get("prov_generated") || this.get("prov_used"))
            return "program";
        }

        //Determine the type via file format
        if (_.contains(collectionIds, this.get("formatId")))
          return "collection";
        if (_.contains(portalIds, this.get("formatId"))) return "portal";
        if (this.get("formatType") == "METADATA") return "metadata";
        if (_.contains(imageIds, this.get("formatId"))) return "image";
        if (_.contains(pdfIds, this.get("formatId"))) return "PDF";
        if (_.contains(annotationIds, this.get("formatId")))
          return "annotation";
        else return "data";
      },

      //Returns a plain-english version of the specific format ID (for selected ids)
      getFormat: function () {
        var formatMap = {
          "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
            "Microsoft Excel OpenXML",
          "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
            "Microsoft Word OpenXML",
          "application/vnd.ms-excel.sheet.binary.macroEnabled.12":
            "Microsoft Office Excel 2007 binary workbooks",
          "application/vnd.openxmlformats-officedocument.presentationml.presentation":
            "Microsoft Office OpenXML Presentation",
          "application/vnd.ms-excel": "Microsoft Excel",
          "application/msword": "Microsoft Word",
          "application/vnd.ms-powerpoint": "Microsoft Powerpoint",
          "text/html": "HTML",
          "text/plain": "plain text (.txt)",
          "video/avi": "Microsoft AVI file",
          "video/x-ms-wmv": "Windows Media Video (.wmv)",
          "audio/x-ms-wma": "Windows Media Audio (.wma)",
          "application/vnd.google-earth.kml xml":
            "Google Earth Keyhole Markup Language (KML)",
          "http://docs.annotatorjs.org/en/v1.2.x/annotation-format.html":
            "annotation",
          "application/mathematica": "Mathematica Notebook",
          "application/postscript": "Postscript",
          "application/rtf": "Rich Text Format (RTF)",
          "application/xml": "XML Application",
          "text/xml": "XML",
          "application/x-fasta": "FASTA sequence file",
          "nexus/1997": "NEXUS File Format for Systematic Information",
          "anvl/erc-v02":
            "Kernel Metadata and Electronic Resource Citations (ERCs), 2010.05.13",
          "http://purl.org/dryad/terms/":
            "Dryad Metadata Application Profile Version 3.0",
          "http://datadryad.org/profile/v3.1":
            "Dryad Metadata Application Profile Version 3.1",
          "application/pdf": "PDF",
          "application/zip": "ZIP file",
          "http://www.w3.org/TR/rdf-syntax-grammar": "RDF/XML",
          "http://www.w3.org/TR/rdfa-syntax": "RDFa",
          "application/rdf xml": "RDF",
          "text/turtle": "TURTLE",
          "text/n3": "N3",
          "application/x-gzip": "GZIP Format",
          "application/x-python": "Python script",
          "http://www.w3.org/2005/Atom": "ATOM-1.0",
          "application/octet-stream": "octet stream (application file)",
          "http://digir.net/schema/conceptual/darwin/2003/1.0/darwin2.xsd":
            "Darwin Core, v2.0",
          "http://rs.tdwg.org/dwc/xsd/simpledarwincore/": "Simple Darwin Core",
          "eml://ecoinformatics.org/eml-2.1.0": "EML v2.1.0",
          "eml://ecoinformatics.org/eml-2.1.1": "EML v2.1.1",
          "eml://ecoinformatics.org/eml-2.0.1": "EML v2.0.1",
          "eml://ecoinformatics.org/eml-2.0.0": "EML v2.0.0",
          "https://eml.ecoinformatics.org/eml-2.2.0": "EML v2.2.0",
        };

        return formatMap[this.get("formatId")] || this.get("formatId");
      },

      setURL: function () {
        if (MetacatUI.appModel.get("objectServiceUrl"))
          this.set(
            "url",
            MetacatUI.appModel.get("objectServiceUrl") +
              encodeURIComponent(this.get("id")),
          );
        else if (MetacatUI.appModel.get("resolveServiceUrl"))
          this.set(
            "url",
            MetacatUI.appModel.get("resolveServiceUrl") +
              encodeURIComponent(this.get("id")),
          );
      },

      /**
       * Checks if the pid or sid or given string is a DOI
       *
       * @param {string} customString - Optional. An identifier string to check instead of the id and seriesId attributes on the model
       * @returns {boolean} True if it is a DOI
       */
      isDOI: function (customString) {
        return (
          MetacatUI.appModel.isDOI(customString) ||
          MetacatUI.appModel.isDOI(this.get("id")) ||
          MetacatUI.appModel.isDOI(this.get("seriesId"))
        );
      },

      /*
       * Checks if the currently-logged-in user is authorized to change
       * permissions (or other action if set as parameter) on this doc
       * @param {string} [action=changePermission] - The action (read, write, or changePermission) to check
       * if the current user has authorization to perform. By default checks for the highest level of permission.
       */
      checkAuthority: function (action = "changePermission") {
        var authServiceUrl = MetacatUI.appModel.get("authServiceUrl");
        if (!authServiceUrl) return false;

        var model = this;

        var requestSettings = {
          url:
            authServiceUrl +
            encodeURIComponent(this.get("id")) +
            "?action=" +
            action,
          type: "GET",
          success: function (data, textStatus, xhr) {
            model.set("isAuthorized_" + action, true);
            model.set("isAuthorized", true);
            model.trigger("change:isAuthorized");
          },
          error: function (xhr, textStatus, errorThrown) {
            model.set("isAuthorized_" + action, false);
            model.set("isAuthorized", false);
          },
        };
        $.ajax(
          _.extend(
            requestSettings,
            MetacatUI.appUserModel.createAjaxSettings(),
          ),
        );
      },

      /*
       * This method will download this object while sending the user's auth token in the request.
       */
      downloadWithCredentials: function () {
        //if(this.get("isPublic")) return;

        //Get info about this object
        var url = this.get("url"),
          model = this;

        //Create an XHR
        var xhr = new XMLHttpRequest();

        //Open and send the request with the user's auth token
        xhr.open("GET", url);

        if (MetacatUI.appUserModel.get("loggedIn")) xhr.withCredentials = true;

        //When the XHR is ready, create a link with the raw data (Blob) and click the link to download
        xhr.onload = function () {
          if (this.status == 404) {
            this.onerror.call(this);
            return;
          }

          //Get the file name to save this file as
          var filename = xhr.getResponseHeader("Content-Disposition");

          if (!filename) {
            filename =
              model.get("fileName") ||
              model.get("title") ||
              model.get("id") ||
              "download";
          } else
            filename = filename
              .substring(filename.indexOf("filename=") + 9)
              .replace(/"/g, "");

          //Replace any whitespaces
          filename = filename.trim().replace(/ /g, "_");

          //For IE, we need to use the navigator API
          if (navigator && navigator.msSaveOrOpenBlob) {
            navigator.msSaveOrOpenBlob(xhr.response, filename);
          }
          //Other browsers can download it via a link
          else {
            var a = document.createElement("a");
            a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob

            // Set the file name.
            a.download = filename;

            a.style.display = "none";
            document.body.appendChild(a);
            a.click();
            a.remove();
          }

          model.trigger("downloadComplete");

          // Track this event
          MetacatUI.analytics?.trackEvent(
            "download",
            "Download DataONEObject",
            model.get("id"),
          );
        };

        xhr.onerror = function (e) {
          model.trigger("downloadError");

          // Track the error
          MetacatUI.analytics?.trackException(
            `Download DataONEObject error: ${e || ""}`,
            model.get("id"),
            true,
          );
        };

        xhr.onprogress = function (e) {
          if (e.lengthComputable) {
            var percent = (e.loaded / e.total) * 100;
            model.set("downloadPercent", percent);
          }
        };

        xhr.responseType = "blob";

        if (MetacatUI.appUserModel.get("loggedIn"))
          xhr.setRequestHeader(
            "Authorization",
            "Bearer " + MetacatUI.appUserModel.get("token"),
          );

        xhr.send();
      },

      getInfo: function (fields) {
        var model = this;

        if (!fields)
          var fields =
            "abstract,id,seriesId,fileName,resourceMap,formatType,formatId,obsoletedBy,isDocumentedBy,documents,title,origin,keywords,attributeName,pubDate,eastBoundCoord,westBoundCoord,northBoundCoord,southBoundCoord,beginDate,endDate,dateUploaded,archived,datasource,replicaMN,isAuthorized,isPublic,size,read_count_i,isService,serviceTitle,serviceEndpoint,serviceOutput,serviceDescription,serviceType,project,dateModified";

        var escapeSpecialChar = MetacatUI.appSearchModel.escapeSpecialChar;

        var query = "q=";

        //If there is no seriesId set, then search for pid or sid
        if (!this.get("seriesId"))
          query +=
            '(id:"' +
            escapeSpecialChar(encodeURIComponent(this.get("id"))) +
            '" OR seriesId:"' +
            escapeSpecialChar(encodeURIComponent(this.get("id"))) +
            '")';
        //If a seriesId is specified, then search for that
        else if (this.get("seriesId") && this.get("id").length > 0)
          query +=
            '(seriesId:"' +
            escapeSpecialChar(encodeURIComponent(this.get("seriesId"))) +
            '" AND id:"' +
            escapeSpecialChar(encodeURIComponent(this.get("id"))) +
            '")';
        //If only a seriesId is specified, then just search for the most recent version
        else if (this.get("seriesId") && !this.get("id"))
          query +=
            'seriesId:"' +
            escapeSpecialChar(encodeURIComponent(this.get("id"))) +
            '" -obsoletedBy:*';

        query +=
          "&fl=" +
          fields + //Specify the fields to return
          "&wt=json&rows=1000" + //Get the results in JSON format and get 1000 rows
          "&archived=archived:*"; //Get archived or unarchived content

        var requestSettings = {
          url: MetacatUI.appModel.get("queryServiceUrl") + query,
          type: "GET",
          success: function (data, response, xhr) {
            //If the Solr response was not as expected, trigger and error and exit
            if (!data || typeof data.response == "undefined") {
              model.set("indexed", false);
              model.trigger("getInfoError");
              return;
            }

            var docs = data.response.docs;

            if (docs.length == 1) {
              docs[0].resourceMap = model.parseResourceMapField(docs[0]);
              model.set(docs[0]);
              model.trigger("sync");
            }
            //If we searched by seriesId, then let's find the most recent version in the series
            else if (docs.length > 1) {
              //Filter out docs that are obsoleted
              var mostRecent = _.reject(docs, function (doc) {
                return typeof doc.obsoletedBy !== "undefined";
              });

              //If there is only one doc that is not obsoleted (the most recent), then
              // set this doc's values on this model
              if (mostRecent.length == 1) {
                mostRecent[0].resourceMap = model.parseResourceMapField(
                  mostRecent[0],
                );
                model.set(mostRecent[0]);
                model.trigger("sync");
              } else {
                //If there are multiple docs without an obsoletedBy statement, then
                // retreive the head of the series via the system metadata
                var sysMetaRequestSettings = {
                  url:
                    MetacatUI.appModel.get("metaServiceUrl") +
                    encodeURIComponent(docs[0].seriesId),
                  type: "GET",
                  success: function (sysMetaData) {
                    //Get the identifier node from the system metadata
                    var seriesHeadID = $(sysMetaData).find("identifier").text();
                    //Get the doc from the Solr results with that identifier
                    var seriesHead = _.findWhere(docs, { id: seriesHeadID });

                    //If there is a doc in the Solr results list that matches the series head id
                    if (seriesHead) {
                      seriesHead.resourceMap =
                        model.parseResourceMapField(seriesHead);
                      //Set those values on this model
                      model.set(seriesHead);
                    }
                    //Otherwise, just fall back on the first doc in the list
                    else if (mostRecent.length) {
                      mostRecent[0].resourceMap = model.parseResourceMapField(
                        mostRecent[0],
                      );
                      model.set(mostRecent[0]);
                    } else {
                      docs[0].resourceMap = model.parseResourceMapField(
                        docs[0],
                      );
                      model.set(docs[0]);
                    }

                    model.trigger("sync");
                  },
                  error: function (xhr, textStatus, errorThrown) {
                    // Fall back on the first doc in the list
                    if (mostRecent.length) {
                      model.set(mostRecent[0]);
                    } else {
                      model.set(docs[0]);
                    }

                    model.trigger("sync");
                  },
                };

                $.ajax(
                  _.extend(
                    sysMetaRequestSettings,
                    MetacatUI.appUserModel.createAjaxSettings(),
                  ),
                );
              }
            } else {
              model.set("indexed", false);
              //Try getting the system metadata as a backup
              model.getSysMeta();
            }
          },
          error: function (xhr, textStatus, errorThrown) {
            model.set("indexed", false);
            model.trigger("getInfoError");
          },
        };

        $.ajax(
          _.extend(
            requestSettings,
            MetacatUI.appUserModel.createAjaxSettings(),
          ),
        );
      },

      getCitationInfo: function () {
        this.getInfo(
          "id,seriesId,origin,pubDate,dateUploaded,title,datasource,project",
        );
      },

      /*
       * Get the system metadata for this object
       */
      getSysMeta: function () {
        var url =
            MetacatUI.appModel.get("metaServiceUrl") +
            encodeURIComponent(this.get("id")),
          model = this;

        var requestSettings = {
          url: url,
          type: "GET",
          dataType: "text",
          success: function (data, response, xhr) {
            if (data && data.length) {
              model.set("systemMetadata", data);
            }

            //Check if this is archvied
            var archived = $(data).find("archived").text() == "true";
            model.set("archived", archived);

            //Get the file size
            model.set("size", $(data).find("size").text() || "");

            //Get the entity name
            model.set("filename", $(data).find("filename").text() || "");

            //Check if this is a metadata doc
            var formatId = $(data).find("formatid").text() || "",
              formatType;
            model.set("formatId", formatId);
            if (
              formatId.indexOf("ecoinformatics.org") > -1 ||
              formatId.indexOf("FGDC") > -1 ||
              formatId.indexOf("INCITS") > -1 ||
              formatId.indexOf("namespaces/netcdf") > -1 ||
              formatId.indexOf("waterML") > -1 ||
              formatId.indexOf("darwin") > -1 ||
              formatId.indexOf("dryad") > -1 ||
              formatId.indexOf("http://www.loc.gov/METS") > -1 ||
              formatId.indexOf("ddi:codebook:2_5") > -1 ||
              formatId.indexOf("http://www.icpsr.umich.edu/DDI") > -1 ||
              formatId.indexOf(
                "http://purl.org/ornl/schema/mercury/terms/v1.0",
              ) > -1 ||
              formatId.indexOf("datacite") > -1 ||
              formatId.indexOf("isotc211") > -1 ||
              formatId.indexOf("metadata") > -1
            )
              model.set("formatType", "METADATA");

            //Trigger the sync event so the app knows we found the model info
            model.trigger("sync");
          },
          error: function (response) {
            //When the user is unauthorized to access this object, trigger a 401 error
            if (response.status == 401) {
              model.set("notFound", true);
              model.trigger("401");
            }
            //When the object doesn't exist, trigger a 404 error
            else if (response.status == 404) {
              model.set("notFound", true);
              model.trigger("404");
            }
            //Other error codes trigger a generic error
            else {
              model.trigger("error");
            }
          },
        };

        $.ajax(
          _.extend(
            requestSettings,
            MetacatUI.appUserModel.createAjaxSettings(),
          ),
        );
      },

      //Transgresses the obsolence chain until it finds the newest version that this user is authorized to read
      findLatestVersion: function (newestVersion, possiblyNewer) {
        // Make sure we have the /meta service configured
        if (!MetacatUI.appModel.get("metaServiceUrl")) return;

        //If no pid was supplied, use this model's id
        if (!newestVersion) {
          var newestVersion = this.get("id");
          var possiblyNewer = this.get("obsoletedBy");
        }

        //If this isn't obsoleted by anything, then there is no newer version
        if (!possiblyNewer) {
          this.set("newestVersion", newestVersion);
          return;
        }

        var model = this;

        //Get the system metadata for the possibly newer version
        var requestSettings = {
          url:
            MetacatUI.appModel.get("metaServiceUrl") +
            encodeURIComponent(possiblyNewer),
          type: "GET",
          success: function (data) {
            // the response may have an obsoletedBy element
            var obsoletedBy = $(data).find("obsoletedBy").text();

            //If there is an even newer version, then get it and rerun this function
            if (obsoletedBy)
              model.findLatestVersion(possiblyNewer, obsoletedBy);
            //If there isn't a newer version, then this is it
            else model.set("newestVersion", possiblyNewer);
          },
          error: function (xhr) {
            //If this newer version isn't found or accessible, then save the last
            // accessible id as the newest version
            if (
              xhr.status == 401 ||
              xhr.status == 404 ||
              xhr.status == "401" ||
              xhr.status == "404"
            ) {
              model.set("newestVersion", newestVersion);
            }
          },
        };

        $.ajax(
          _.extend(
            requestSettings,
            MetacatUI.appUserModel.createAjaxSettings(),
          ),
        );
      },

      /**** Provenance-related functions ****/
      /*
       * Returns true if this provenance field points to a source of this data or metadata object
       */
      isSourceField: function (field) {
        if (typeof field == "undefined" || !field) return false;
        if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field))
          return false;

        if (
          field == "prov_generatedByExecution" ||
          field == "prov_generatedByProgram" ||
          field == "prov_used" ||
          field == "prov_wasDerivedFrom" ||
          field == "prov_wasInformedBy"
        )
          return true;
        else return false;
      },

      /*
       * Returns true if this provenance field points to a derivation of this data or metadata object
       */
      isDerivationField: function (field) {
        if (typeof field == "undefined" || !field) return false;
        if (!_.contains(MetacatUI.appSearchModel.getProvFields(), field))
          return false;

        if (
          field == "prov_usedByExecution" ||
          field == "prov_usedByProgram" ||
          field == "prov_hasDerivations" ||
          field == "prov_generated"
        )
          return true;
        else return false;
      },

      /*
       * Returns true if this SolrResult has a provenance trace (i.e. has either sources or derivations)
       */
      hasProvTrace: function () {
        if (this.get("formatType") == "METADATA") {
          if (this.get("prov_hasSources") || this.get("prov_hasDerivations"))
            return true;
        }

        var fieldNames = MetacatUI.appSearchModel.getProvFields(),
          currentField = "";

        for (var i = 0; i < fieldNames.length; i++) {
          currentField = fieldNames[i];
          if (this.has(currentField)) return true;
        }

        return false;
      },

      /*
       * Returns an array of all the IDs of objects that are sources of this object
       */
      getSources: function () {
        var sources = new Array(),
          model = this,
          //Get the prov fields but leave out references to executions which are not used in the UI yet
          fields = _.reject(
            MetacatUI.appSearchModel.getProvFields(),
            function (f) {
              return f.indexOf("xecution") > -1;
            },
          ); //Leave out the first e in execution so we don't have to worry about case sensitivity

        _.each(fields, function (provField, i) {
          if (model.isSourceField(provField) && model.has(provField))
            sources.push(model.get(provField));
        });

        return _.uniq(_.flatten(sources));
      },

      /*
       * Returns an array of all the IDs of objects that are derivations of this object
       */
      getDerivations: function () {
        var derivations = new Array(),
          model = this,
          //Get the prov fields but leave out references to executions which are not used in the UI yet
          fields = _.reject(
            MetacatUI.appSearchModel.getProvFields(),
            function (f) {
              return f.indexOf("xecution") > -1;
            },
          ); //Leave out the first e in execution so we don't have to worry about case sensitivity

        _.each(fields, function (provField, i) {
          if (model.isDerivationField(provField) && model.has(provField))
            derivations.push(model.get(provField));
        });

        return _.uniq(_.flatten(derivations));
      },

      getInputs: function () {
        return this.get("prov_used");
      },

      getOutputs: function () {
        return this.get("prov_generated");
      },

      /*
       * Uses the app configuration to check if this model's metrics should be hidden in the display
       *
       * @return {boolean}
       */
      hideMetrics: function () {
        //If the AppModel is configured with cases of where to hide metrics,
        if (
          typeof MetacatUI.appModel.get("hideMetricsWhen") == "object" &&
          MetacatUI.appModel.get("hideMetricsWhen")
        ) {
          //Check for at least one match
          return _.some(
            MetacatUI.appModel.get("hideMetricsWhen"),
            function (value, modelProperty) {
              //Get the value of this property from this model
              var modelValue = this.get(modelProperty);

              //Check for the presence of this model's value in the AppModel value
              if (Array.isArray(value) && typeof modelValue == "string") {
                return _.contains(value, modelValue);
              }
              //Check for the presence of the AppModel's value in this model's value
              else if (typeof value == "string" && Array.isArray(modelValue)) {
                return _.contains(modelValue, value);
              }
              //Check for overlap of two arrays
              else if (Array.isArray(value) && Array.isArray(modelValue)) {
                return _.intersection(value, modelValue).length > 0;
              }
              //If the AppModel value is a function, execute it
              else if (typeof value == "function") {
                return value(modelValue);
              }
              //Otherwise, just check for equality
              else {
                return value === modelValue;
              }
            },
            this,
          );
        } else {
          return false;
        }
      },

      /**
       * Creates a URL for viewing more information about this metadata
       * @return {string}
       */
      createViewURL: function () {
        return this.getType() == "portal" || this.getType() == "collection"
          ? MetacatUI.root +
              "/" +
              MetacatUI.appModel.get("portalTermPlural") +
              "/" +
              encodeURIComponent(
                this.get("label") || this.get("seriesId") || this.get("id"),
              )
          : MetacatUI.root +
              "/view/" +
              encodeURIComponent(this.get("seriesId") || this.get("id"));
      },

      parseResourceMapField: function (json) {
        if (typeof json.resourceMap == "string") {
          return json.resourceMap.trim();
        } else if (Array.isArray(json.resourceMap)) {
          let newResourceMapIds = [];
          _.each(json.resourceMap, function (rMapId) {
            if (typeof rMapId == "string") {
              newResourceMapIds.push(rMapId.trim());
            }
          });
          return newResourceMapIds;
        }

        //If nothing works so far, return an empty array
        return [];
      },

      /****************************/

      /**
       * Convert number of bytes into human readable format
       *
       * @param integer bytes     Number of bytes to convert
       * @param integer precision Number of digits after the decimal separator
       * @return string
       */
      bytesToSize: function (bytes, precision) {
        var kibibyte = 1024;
        var mebibyte = kibibyte * 1024;
        var gibibyte = mebibyte * 1024;
        var tebibyte = gibibyte * 1024;

        if (typeof bytes === "undefined") var bytes = this.get("size");

        if (bytes >= 0 && bytes < kibibyte) {
          return bytes + " B";
        } else if (bytes >= kibibyte && bytes < mebibyte) {
          return (bytes / kibibyte).toFixed(precision) + " KiB";
        } else if (bytes >= mebibyte && bytes < gibibyte) {
          return (bytes / mebibyte).toFixed(precision) + " MiB";
        } else if (bytes >= gibibyte && bytes < tebibyte) {
          return (bytes / gibibyte).toFixed(precision) + " GiB";
        } else if (bytes >= tebibyte) {
          return (bytes / tebibyte).toFixed(precision) + " TiB";
        } else {
          return bytes + " B";
        }
      },
    },
  );
  return SolrResult;
});