Source: src/js/models/SolrResult.js

/*global define */
define(['jquery', 'underscore', 'backbone'],
	function($, _, Backbone) {

	/**
  * @class SolrResult
  * @classdesc A single result from the Solr search service
  * @classcategory Models
  */
	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;
});