Name

sn_ace.ACEAppBuilderUtilV2

Description

No description available

Script

var ACEAppBuilderUtilV2 = Class.create();
ACEAppBuilderUtilV2.prototype = {
  initialize: function() {
      this.CONSTANTS = new ACEAppBuilderMetaData();
      this.TABLE = this.CONSTANTS.TABLES;
      this.UXF_TABLE = this.CONSTANTS.UXF_TABLES;
      this.GLOBAL = new global.ACEAppBuilderGlobalScopedUtil();
      this.CREATE_DEMO_DATA = false;
      this.demoUtils = new ACEDemoDataUtils();
      this.AIS = this.CONSTANTS.AIS;
      this.EXPERIENCE_ID = null;
      this.VIRTUAL_GR = null;
      this.gr_counter = 0;
      this.CAN_READ = this.hasReadAccess();
      this.DEFAULT_FETCH_MODE = 'dfs';
      this.SUPPORTED_FETCH_MODES = ['dfs', 'bfs'];
      this.ACE_DEFAULT_APP_CONFIG = "f0d91c8a87650110a4a2c077f6cb0b3e";
      this.ACE_CONTROL_TYPE = "c70afc5453db5110c733ddeeff7b1281";
      this.GENERIC_CONTENT_TYPE = "4cb9f85453db5110c733ddeeff7b12b4";
      this.REUSABLE_CONTENT_TYPE = "27ea3c9453db5110c733ddeeff7b126a";
      this.REUSABLE_DEFINTION_CONTENT_TYPE = "5db4a12153975110c733ddeeff7b1277";
      this.MODAL_CONTAINER_TYPE = "6aee588753931110c733ddeeff7b126a";
      this.CONTENT_SLOT_TYPE = "306b292d53006110c733ddeeff7b1237";
      this.DATA_BLOCK_CONTENT_TYPE = 'a886aedfc3dde11044104fb9c840ddb7';
      this.BLOCK_CONTEXT_AS_LABEL = "$(blockcontext";
      this.TRANSLATED_TEXT_PREFIX = 'TRT: ';
  },

  upsertReusableBlock: function(blockName, blockParameters, appConfigId) {
      var reusableBlocKGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      var blockId = null;
      reusableBlocKGr.addQuery("reusable_block", true);
      reusableBlocKGr.addQuery("name", blockName);
      reusableBlocKGr.query();
      if (reusableBlocKGr.next()) {
          reusableBlocKGr.variable["block_variables"] = JSON.stringify(blockParameters);
          blockId = reusableBlocKGr.getUniqueValue();
          reusableBlocKGr.update();
      } else {
          reusableBlocKGr.reusable_block = true;
          reusableBlocKGr.name = blockName;
          reusableBlocKGr.type = this.REUSABLE_DEFINTION_CONTENT_TYPE;
          reusableBlocKGr.document_table = this.TABLE.APP_CONFIG;
          reusableBlocKGr.document_id = appConfigId || this.TABLE.ACE_DEFAULT_APP_CONFIG;
          reusableBlocKGr.active = true;
          reusableBlocKGr.visibility = "answer=true;"
          reusableBlocKGr.variable["block_variables"] = JSON.stringify(blockParameters);
          blockId = reusableBlocKGr.insert();
      }
      return blockId;
  },

  _updateACEContentBlocks: function(contentTree, documentTable, documentId) {
      var resuableBlockInstanceToDefBlockIdMap = this._getActualBlockIdMap(contentTree);
      var contentBlocksResp = this._getAllContentBlockData(contentTree);
      var allContentBlockData = contentBlocksResp.blockData;
      var reusableBlockIds = contentBlocksResp.reusableBlockIds;
      var allContentBlockIds = Object.keys(allContentBlockData);

      /* Remove all content blocks that are existing with in the documentId (acepage) and not in the input block ids */
      var contentBlocksTobeDeletedGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      contentBlocksTobeDeletedGr.addEncodedQuery('document_id=' + documentId +
          '^document_table=' + documentTable +
          '^NQdocument_table=sn_ace_content_block^document_idIN' + reusableBlockIds.join(','));
      contentBlocksTobeDeletedGr.addQuery("sys_id", 'NOT IN', allContentBlockIds);
      contentBlocksTobeDeletedGr.query();
      var resuableBlockInstancesToDelete = [];
      while (contentBlocksTobeDeletedGr.next()) {
          if (contentBlocksTobeDeletedGr.getValue("type") == this.REUSABLE_CONTENT_TYPE) {
              resuableBlockInstancesToDelete.push(contentBlocksTobeDeletedGr.getUniqueValue());
          }
      }
      contentBlocksTobeDeletedGr.query();
      contentBlocksTobeDeletedGr.deleteMultiple();
      if (resuableBlockInstancesToDelete && resuableBlockInstancesToDelete.length > 0) {
          var resuableBlockInstancesToDeleteGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          resuableBlockInstancesToDeleteGr.addQuery("document_table", this.TABLE.CONTENT_BLOCK);
          resuableBlockInstancesToDeleteGr.addQuery("document_id", "IN", resuableBlockInstancesToDelete.join(","));
          resuableBlockInstancesToDeleteGr.query();
          resuableBlockInstancesToDeleteGr.deleteMultiple();
      }

      /* Upsert the remaining content blocks based on the input content block data */
      var visitedBlockIds = [],
          blockData = {};

      var allContentBlocksGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      allContentBlocksGr.addEncodedQuery('document_id=' + documentId +
          '^document_table=' + documentTable +
          '^NQdocument_table=sn_ace_content_block^document_idIN' + reusableBlockIds.join(','));
      allContentBlocksGr.query();
      while (allContentBlocksGr.next()) {
          visitedBlockIds.push(allContentBlocksGr.getUniqueValue());
      }

      /* Create new Block Ids that are not available in the above query */
      var remainingBlockIds = [];
      allContentBlockIds.forEach(function(cbId) {
          if (visitedBlockIds.indexOf(cbId) == -1) {
              remainingBlockIds.push(cbId);
          }
      });
      var _self = this;
      remainingBlockIds.forEach(function(cbId) {
          var cbGr = new GlideRecord(_self.TABLE.CONTENT_BLOCK);
          cbGr.document_id = documentId;
          cbGr.document_table = documentTable;
          cbGr.name = allContentBlockData[cbId].name;
          cbGr.label = allContentBlockData[cbId].label || "";
          cbGr.active = allContentBlockData[cbId].active;
          cbGr.title = allContentBlockData[cbId].title;
          cbGr.subtitle = allContentBlockData[cbId].subtitle;
          cbGr.post_script = allContentBlockData[cbId].post_script;
          cbGr.lazy_script = allContentBlockData[cbId].lazy_script;
          cbGr.lazy_load = allContentBlockData[cbId].lazy_load;
          cbGr.visibility = allContentBlockData[cbId].visibility;
          cbGr.order = allContentBlockData[cbId].order;
          cbGr.style = JSON.stringify(allContentBlockData[cbId].style);
          cbGr.element_id = allContentBlockData[cbId].elementId;
          cbGr.slotname = allContentBlockData[cbId].slotname;
          if (allContentBlockData[cbId].hasOwnProperty('enableLazyLoad'))
              cbGr.lazy_load = allContentBlockData[cbId].enableLazyLoad;

          cbGr.props_overrides = JSON.stringify(allContentBlockData[cbId].props_overrides);
          if (allContentBlockData[cbId].reusableBlockId) {
              var persistedBlockId = allContentBlockData[allContentBlockData[cbId].reusableBlockId].newBlockId || allContentBlockData[cbId].reusableBlockId;
              cbGr.document_id = persistedBlockId;
              cbGr.document_table = _self.TABLE.CONTENT_BLOCK;
          }
          if (!allContentBlockData[cbId].type.id) {
              switch (allContentBlockData[cbId].type.route) {
                  case "ace-control":
                      cbGr.type = _self.ACE_CONTROL_TYPE;
                      break;
                  case "generic":
                      cbGr.type = _self.GENERIC_CONTENT_TYPE;
                      break;
                  case "reusable":
                      cbGr.type = _self.REUSABLE_CONTENT_TYPE;
                      break;
                  case "modal-container":
                      cbGr.type = _self.MODAL_CONTAINER_TYPE;
                      break;
                  case "content-slot":
                      cbGr.type = _self.CONTENT_SLOT_TYPE;
                      break;
              }
          } else {
              cbGr.type = allContentBlockData[cbId].type.id;
          }

          var parentBlockId = resuableBlockInstanceToDefBlockIdMap[allContentBlockData[cbId].parent_block_id] || allContentBlockData[cbId].parent_block_id;
          cbGr.parent = parentBlockId
          if (allContentBlockData[cbId].props_details) {
              for (var gVariable in allContentBlockData[cbId].props_details) {
                  cbGr.variable[gVariable] = (gVariable == 'component_properties' ||
                          gVariable == 'block_parameters' || gVariable == "aria_properties" ||
                          gVariable == 'items' || gVariable == 'output_map') &&
                      typeof allContentBlockData[cbId].props_details[gVariable] != 'string' ?
                      JSON.stringify(allContentBlockData[cbId].props_details[gVariable]) : allContentBlockData[cbId].props_details[gVariable];
              }
          }
          var grId = cbGr.insert();
          allContentBlockData[cbId].newBlockId = grId;
          allContentBlockData[grId] = allContentBlockData[cbId];
      });

      /*Again query all the blocks for the given document and update the blocks along with the new parent block ids where necessary */
      var translatableStrings = [];
      var allComponentProperties = this.GLOBAL.getCachedMacroponentCBProperties() || '';
      if (allComponentProperties && allComponentProperties.componentProperties) {
          allComponentProperties = allComponentProperties.componentProperties;
          var componentKeys = Object.keys(allComponentProperties);
          if (componentKeys && componentKeys.length > 0) {
              var firstComponentProp = allComponentProperties[componentKeys[0]];
              if (!firstComponentProp.translatableStringMap) {
                  allComponentProperties = this.getCachedProperties(true).componentProperties;
              }
          }
      } else {
          allComponentProperties = this.getCachedProperties(true).componentProperties;
      }
      allContentBlocksGr.query();
      while (allContentBlocksGr.next()) {
          var blockId = allContentBlocksGr.getUniqueValue();
          blockData = allContentBlockData[blockId];
          allContentBlocksGr.name = blockData.name;
          allContentBlocksGr.active = blockData.active;
          allContentBlocksGr.order = blockData.order;
          allContentBlocksGr.label = blockData.label || "";
          allContentBlocksGr.title = blockData.title;
          allContentBlocksGr.subtitle = blockData.subtitle;
          allContentBlocksGr.post_script = blockData.post_script;
          allContentBlocksGr.lazy_script = blockData.lazy_script;
          allContentBlocksGr.lazy_load = blockData.lazy_load;
          allContentBlocksGr.visibility = blockData.visibility;
          allContentBlocksGr.style = JSON.stringify(blockData.style);
          allContentBlocksGr.element_id = blockData.elementId;
          allContentBlocksGr.slotname = blockData.slotname;
          if (blockData.hasOwnProperty('enableLazyLoad'))
              allContentBlocksGr.lazy_load = blockData.enableLazyLoad;

          allContentBlocksGr.props_overrides = JSON.stringify(blockData.props_overrides);
          if (blockData.parent_block_id && allContentBlockData[blockData.parent_block_id] && allContentBlockData[blockData.parent_block_id].newBlockId) {
              blockData.parent_block_id = allContentBlockData[blockData.parent_block_id].newBlockId;
          }
          if (blockData.type) {
              if (!blockData.type.id) {
                  switch (blockData.type.route) {
                      case "ace-control":
                          allContentBlocksGr.type = this.ACE_CONTROL_TYPE;
                          break;
                      case "generic":
                          allContentBlocksGr.type = this.GENERIC_CONTENT_TYPE;
                          break;
                      case "reusable":
                          allContentBlocksGr.type = this.REUSABLE_CONTENT_TYPE;
                          break;
                      case "modal-container":
                          allContentBlocksGr.type = this.MODAL_CONTAINER_TYPE;
                          break;
                      case "content-slot":
                          allContentBlocksGr.type = this.CONTENT_SLOT_TYPE;
                          break;
                  }
              } else {
                  allContentBlocksGr.type = blockData.type.id;
              }
          }
          var parentBlockId = resuableBlockInstanceToDefBlockIdMap[blockData.parent_block_id] || blockData.parent_block_id;
          allContentBlocksGr.parent = parentBlockId;

          // Change For RB translation 
          if (blockData.type.route == 'reusable') {
              var reusableBlockData = this.getReusableBlockDataForTranslations(blockData.props_details.block_id);
              if (reusableBlockData.block_variables && blockData.props_details.block_parameters) {
                  var parsedComponentProps;
                  if (typeof blockData.props_details.block_parameters == 'object') {
                      parsedComponentProps = blockData.props_details.block_parameters;
                  } else {
                      parsedComponentProps = JSON.parse(blockData.props_details.block_parameters);
                  }
                  var translatableStringMap = this.getReusableBlockTranslatableFields(reusableBlockData.block_variables);
                  var translatableStringKeys = Object.keys(translatableStringMap);
                  if (translatableStringKeys && translatableStringKeys.length > 0) {
                      var propTranslatableStrings = this.fetchTranslatableStringsFromProps(translatableStringKeys, parsedComponentProps, translatableStringMap);
                      this.mergeTranslatableStrings(translatableStrings, propTranslatableStrings);
                  }
              }

              if (reusableBlockData && reusableBlockData.required_translation) {
                  var reusableBlockTranslatableString = JSON.parse(reusableBlockData.required_translation) || [];
                  if (reusableBlockTranslatableString.length > 0) {
                      for (var key in reusableBlockTranslatableString) {
                          if (translatableStrings.indexOf(reusableBlockTranslatableString[key]) === -1)
                              translatableStrings.push(reusableBlockTranslatableString[key]);
                      }
                  }

              }
          }

          if (blockData.title && translatableStrings.indexOf(blockData.title) === -1) {
              blockData.title = this.removeTranslationPrefix(blockData.title);
              allContentBlocksGr.title = blockData.title;
              translatableStrings.push(blockData.title);
          }
          if (blockData.subtitle && translatableStrings.indexOf(blockData.subtitle) === -1) {
              blockData.subtitle = this.removeTranslationPrefix(blockData.subtitle);
              allContentBlocksGr.subtitle = blockData.subtitle;
              translatableStrings.push(blockData.subtitle);
          }

          if (blockData && blockData.type && blockData.type.props && blockData.props_details) {
              blockData.type.props.forEach(function(prop) {
                  if (prop.type == 'translated_text') {
                      if (translatableStrings.indexOf(blockData.props_details[prop.element]) === -1) {
                          blockData.props_details[prop.element] = _self.removeTranslationPrefix(blockData.props_details[prop.element])
                          translatableStrings.push(blockData.props_details[prop.element]);

                      }
                  }
              })
          }
          if (blockData.props_details) {
              for (var gVariable in blockData.props_details) {
                  allContentBlocksGr.variable[gVariable] = (gVariable == 'component_properties' ||
                          gVariable == 'block_parameters' ||
                          gVariable == "aria_properties" ||
                          gVariable == "items" ||
                          gVariable == 'output_map') &&
                      typeof blockData.props_details[gVariable] != 'string' ?
                      JSON.stringify(blockData.props_details[gVariable]) : blockData.props_details[gVariable];
              }

              if (blockData.props_details.component_name) {
                  if (allComponentProperties && allComponentProperties[blockData.props_details.component_name] && allComponentProperties[blockData.props_details.component_name].translatableStringMap) {
                      var translatableProps = allComponentProperties[blockData.props_details.component_name].translatableStringMap;
                      if (translatableProps) {
                          var parsedComponentProps = '';
                          if (typeof blockData.props_details.component_properties == 'object') {
                              parsedComponentProps = blockData.props_details.component_properties;
                          } else {
                              parsedComponentProps = JSON.parse(blockData.props_details.component_properties);
                          }
                          var translatableStringKeys = Object.keys(translatableProps);
                          if (translatableStringKeys && translatableStringKeys.length > 0) {
                              var propTranslatableStrings = this.fetchTranslatableStringsFromProps(translatableStringKeys, parsedComponentProps, translatableProps);
                              this.mergeTranslatableStrings(translatableStrings, propTranslatableStrings);
                          }
                      }
                  }


              }
          }

          if (blockData.reusableBlockId) {
              var persistedBlockId = allContentBlockData[blockData.reusableBlockId].newBlockId || blockData.reusableBlockId;
              allContentBlocksGr.document_id = persistedBlockId;
              allContentBlocksGr.document_table = _self.TABLE.CONTENT_BLOCK;
          }

          allContentBlocksGr.update();
      }
      if (translatableStrings && translatableStrings.length > 0) {
          var pageMacroponentTranslationUpdateInfo = {};
          if (documentTable == "sn_ace_page") {
              pageMacroponentTranslationUpdateInfo = this.GLOBAL.updatePageMacroponentTransLation(allContentBlocksGr.document_id, translatableStrings)
          }
          this.updateReusableBlockAndPageTranslations(translatableStrings, documentId, documentTable, pageMacroponentTranslationUpdateInfo);
      }
      var pageId = documentTable == "sn_ace_page" ? documentId : "";
      var blockId = documentTable == "sn_ace_page" ? "" : documentId;
      return this._getContentBlockMetaData(pageId, blockId);
  },
  updateReusableBlockAndPageTranslations: function(translatableStrings, documentId, documentTable, pageMacroponentTranslationUpdateInfo) {
      if (documentTable == this.TABLE.CONTENT_BLOCK) {
          var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          gr.get(documentId);
          gr.required_translations = JSON.stringify(translatableStrings);
          gr.update();
      } else if (pageMacroponentTranslationUpdateInfo && pageMacroponentTranslationUpdateInfo.requireUpdate) {
          var cbGr = new GlideRecord('sn_ace_page');
          cbGr.get(documentId);
          cbGr.required_translations = pageMacroponentTranslationUpdateInfo.required_translations;
          cbGr.update();
      }

  },

  getReusableBlockDataForTranslations: function(blockId) {
      var result = {};
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      gr.get('sys_id', blockId);
      result.block_variables = JSON.parse(gr.variable["block_variables"])
      result.required_translation = gr.getValue('required_translations')
      return result;

  },
  getReusableBlockTranslatableFields: function(block_variables) {
      var _self = this;
      var translatableStringMap = {};
      if (block_variables && block_variables.length) {
          block_variables.forEach(function(block_variable, index) {
              if (block_variable.typeMetadata && block_variable.fieldType == 'json') {
                  var translatableProp = _self.getTranslatableProp(block_variable.typeMetadata, 'translatable', '');
                  if (translatableProp && translatableProp.length > 0) {
                      translatableStringMap[block_variable.name] = translatableProp;
                  }

              } else if (block_variable && block_variable.typeMetadata && block_variable.typeMetadata.translatable) {
                  translatableStringMap[block_variable.name] = block_variable.defaultValue;
              }
          });
      }
      return translatableStringMap;
  },
  fetchTranslatableStringsFromProps: function(translatableStringKeys, parsedComponentProps, translatableProps) {
      var translatableStrings = [];
      var _self = this;
      translatableStringKeys.forEach(function(translatableStringKey) {
          if (translatableStringKey in parsedComponentProps) {
              if (typeof parsedComponentProps[translatableStringKey] == 'object' &&
                  typeof translatableProps[translatableStringKey] == 'object') {
                  var allKeys = translatableProps[translatableStringKey];
                  if (allKeys && parsedComponentProps[translatableStringKey] && Array.isArray(parsedComponentProps[translatableStringKey])) {
                      parsedComponentProps[translatableStringKey].forEach(function(props) {
                          allKeys.forEach(function(key) {
                              if (key in props) {
                                  if (translatableStrings.indexOf(props[key]) === -1 && !props[key].includes(_self.BLOCK_CONTEXT_AS_LABEL)) {
                                      translatableStrings.push(props[key]);
                                  }
                              }
                          })
                      });
                  } else {
                      allKeys.forEach(function(key) {
                          if (key in parsedComponentProps[translatableStringKey]) {
                              if (translatableStrings.indexOf(parsedComponentProps[translatableStringKey][key]) === -1 && !parsedComponentProps[translatableStringKey][key].includes(_self.BLOCK_CONTEXT_AS_LABEL)) {
                                  translatableStrings.push(parsedComponentProps[translatableStringKey][key]);
                              }
                          }
                      })
                  }


              } else if (translatableStrings.indexOf(parsedComponentProps[translatableStringKey]) === -1 && !parsedComponentProps[translatableStringKey].includes(_self.BLOCK_CONTEXT_AS_LABEL)) {
                  translatableStrings.push(parsedComponentProps[translatableStringKey]);
              }
          }
      });
      return translatableStrings;
  },
  mergeTranslatableStrings: function(translatableStrings, propTranslatableStrings) {
      if (propTranslatableStrings && propTranslatableStrings.length > 0) {
          propTranslatableStrings.forEach(function(param) {
              if (translatableStrings.indexOf(param) === -1) {
                  translatableStrings.push(param);
              }

          });
      }
      return translatableStrings;
  },

  _getContentSlottedNodes: function(contentTree, contentSlottedNodes, reusableBlockId) {
      var self = this;
      if (contentTree.children) {
          contentTree.children.forEach(function(node) {
              if (node.type && node.type.route == "content-slot" && node.children) {
                  node.children.forEach(function(childItem) {
                      childItem.reusableBlockId = reusableBlockId;
                      contentSlottedNodes.push(childItem);
                  });
              } else {
                  self._getContentSlottedNodes(node, contentSlottedNodes, reusableBlockId);
              }
          });
      }
  },

  _getActualBlockIdMap: function(contentTree) {
      var result = {};
      var children = contentTree;
      while (children && children.length > 0) {
          var nextChildren = [];
          children.forEach(function(childItem) {
              result[childItem.block_id] = childItem.actual_block_id || childItem.block_id;
              nextChildren = nextChildren.concat(childItem.children);
          });
          children = nextChildren;
      }
      return result;
  },
  _getReusableBlockChildren: function(contentTree, existingChildren) {
      var children = existingChildren || [];
      if (contentTree.props_details && contentTree.props_details.clone + "" == "true") {
          children = children.concat(contentTree.children);
      } else {
          var contentSlottedNodes = [];
          this._getContentSlottedNodes(contentTree, contentSlottedNodes, contentTree.block_id);
          children = children.concat(contentSlottedNodes);
      }
      return children;
  },

  updateBlockData: function(childItem, blockData) {
      cbData = {};
      Object.keys(childItem).forEach(function(cbKey) {
          if (cbKey != "children") {
              cbData[cbKey] = childItem[cbKey];
          }
      });
      blockData[childItem.block_id] = cbData;
      if (childItem.reusableBlockId) {
          blockData[childItem.block_id].reusableBlockId = childItem.reusableBlockId;
      }
  },

  _getAllContentBlockData: function(contentTree, updatedBlocks) {
      var blockData = {};
      var contentTreeArr = [];
      var children = [],
          nextlevelChildren = [],
          reusableBlockIds = [];
      var self = this;
      cbData = {};

      if (!contentTree) {
          return {
              "blockData": {},
              "reusableBlockIds": []
          };
      }

      if (!Array.isArray(contentTree))
          contentTreeArr.push(contentTree);
      else
          contentTreeArr = contentTree;

      contentTreeArr.forEach(function(cb) {
          if ((!cb.type || cb.type.route != "reusable")) {
              children = children.concat(cb.children);
          } else if (cb.type && cb.type.route == "reusable") {
              children = self._getReusableBlockChildren(cb, children);
              reusableBlockIds.push(cb.block_id);
          }
          self.updateBlockData(cb, blockData);
      });

      while (children && children.length > 0) {
          nextlevelChildren = [];
          children.forEach(function(childItem) {
              if (updatedBlocks && updatedBlocks[childItem.block_id]) {
                  blockData[childItem.block_id] = updatedBlocks[childItem.block_id];
              } else {
                  self.updateBlockData(childItem, blockData);
              }
              if (childItem.children) {
                  if (childItem.type && childItem.type.route && childItem.type.route == "reusable") {
                      reusableBlockIds.push(childItem.block_id);
                      var reusbaleBlockChildren = self._getReusableBlockChildren(childItem, []);
                      if (reusbaleBlockChildren && reusbaleBlockChildren.length)
                          nextlevelChildren = nextlevelChildren.concat(reusbaleBlockChildren);
                  } else if (childItem.reusableBlockId) {
                      childItem.children.forEach(function(chItem) {
                          chItem.reusableBlockId = childItem.reusableBlockId;
                      });
                      nextlevelChildren = nextlevelChildren.concat(childItem.children);
                  } else {
                      nextlevelChildren = nextlevelChildren.concat(childItem.children);
                  }
              }
          });
          children = nextlevelChildren;
      }

      return {
          "blockData": blockData,
          "reusableBlockIds": reusableBlockIds
      };
  },

  _getVirtualGrInstance: function(cbId) {
      if (!cbId) return null;
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (gr.get(cbId)) {
          return gr;
      }
      return null;
  },
  _cachedScriptEvaluator: function(grInstance, scriptField, scriptValue, context) {
      try {
          if (!grInstance || !scriptValue) return context ? context : true;
          grInstance.setValue(scriptField, scriptValue);
          var evaluator = new GlideScopedEvaluator();
          if (context) {
              evaluator.putVariable('context', context);
              evaluator.evaluateScript(grInstance, scriptField, null);
          } else {
              evaluator.putVariable('answer', null);
              evaluator.putVariable('current', grInstance);
              evaluator.evaluateScript(grInstance, scriptField, null);
              return evaluator.getVariable('answer');
          }
      } catch (e) {
          gs.log('ACEAppBuilder: Exception executing script of cached content block, ' + e);
      }
  },
  _scriptEvaluator: function(grInstance, context, fieldName, pageParams, blockcontext, dataParams) {
      try {
          if (!fieldName || !grInstance.getValue(fieldName))
              return context;

          var evaluator = new GlideScopedEvaluator();
          evaluator.putVariable('context', context);
          evaluator.putVariable('pageParams', pageParams);
          evaluator.putVariable('blockcontext', blockcontext);
          evaluator.putVariable('dataParams', dataParams);
          return evaluator.evaluateScript(grInstance, fieldName, null);
      } catch (e) {
          gs.error('ACEAppBuilder: Exception executing script of content block, ' + e.toString());
      }
  },
  _evaluatePostScript: function(grInstance, context, pageParams, blockcontext, dataParams) {
      this._scriptEvaluator(grInstance, context, 'post_script', pageParams, blockcontext, dataParams);
  },
  _evaluateLazyScript: function(grInstance, context, pageParams, blockcontext, dataParams) {
      return this._scriptEvaluator(grInstance, context, 'lazy_script', pageParams, blockcontext, dataParams);
  },
  _visibilityEvaluator: function(grInstance, pageParams, blockcontext, dataParams) {
      try {
          if (!grInstance.getValue('visibility'))
              return true;

          var evaluator = new GlideScopedEvaluator();
          evaluator.putVariable('answer', null);
          evaluator.putVariable('current', grInstance);
          evaluator.putVariable('pageParams', pageParams);
          evaluator.putVariable('blockcontext', blockcontext);
          evaluator.putVariable('dataParams', dataParams);
          evaluator.evaluateScript(grInstance, 'visibility', null);
          return evaluator.getVariable('answer');
      } catch (e) {
          gs.error('ACEAppBuilder: Exception executing script of content block, ' + e.toString());
          return false;
      }
  },
  _getValuePairsFromClonedRecord: function(table, sysId, fields) {
      try {
          if (!table || !sysId || !this.CAN_READ) return {};
          var valuePairs = {};
          var gr = new GlideRecord(table);
          if (gr.get(sysId)) {
              fields.forEach(function(field) {
                  valuePairs[field] = gr.getValue(field);
              });
              return valuePairs;
          }
          return {};
      } catch (e) {
          return {};
      }
  },
  _grRecordCreator: function(table, valuePairs, cloneInfo) {
      try {
          var clonedValuePairs = {},
              field;
          if (cloneInfo && cloneInfo.sys_id && Array.isArray(cloneInfo.fields) && cloneInfo.fields.length > 0) {
              clonedValuePairs = this._getValuePairsFromClonedRecord(table, cloneInfo.sys_id, cloneInfo.fields);
          }
          var gr = new GlideRecord(table);
          gr.initialize();
          gr.sys_scope = gs.getCurrentApplicationId();
          for (field in clonedValuePairs) {
              gr[field] = clonedValuePairs[field];
          }
          for (field in valuePairs) {
              if (field == 'props_details' && table == this.TABLE.CONTENT_BLOCK) {
                  for (var i in valuePairs.props_details) {
                      gr.variable[i] = valuePairs.props_details[i];
                  }
              } else
                  gr[field] = valuePairs[field];
          }

          return gr.insert();
      } catch (e) {
          gs.error("ACEAppBuilderUtil: Error in Glide Record Creation: " + e.toString());
          return null;
      }

  },
  _updateMacroponentProp: function(macroponentId, field, element, inputValueProp, newValue) {
      try {
          var _self = this;
          var gr = new GlideRecord(this.UXF_TABLE.MACROPONENT);
          if (gr.get(macroponentId)) {
              var fieldData = gr.getValue(field);
              fieldData = JSON.parse(fieldData);
              if (Array.isArray(fieldData)) {
                  for (var i = 0; i < fieldData.length; i++) {
                      var dataBroker = fieldData[i];
                      if (dataBroker.elementId == element && dataBroker.inputValues && dataBroker.inputValues[inputValueProp]) {
                          dataBroker.inputValues[inputValueProp].value = newValue.toString();

                          var input = {};
                          input[field] = JSON.stringify(fieldData, null, 1);
                          _self.GLOBAL.updateRecord(_self.UXF_TABLE.MACROPONENT, gr.getUniqueValue(), input);
                      }
                  }
              }
              return true;
          }
      } catch (e) {
          return null;
      }
  },
  createPage: function(appConfigId, name, aceAppSysId, template, pageId) {
      var screenTypeId = this.GLOBAL._grRecordCreator(this.UXF_TABLE.SCREEN_TYPE, {
          'name': name
      }, {
          'sys_id': this._getConstantsForPage(template).SCREEN_TYPE,
          'fields': this.CONSTANTS.CLONE_FIELDS.SCREEN_TYPE
      });
      var macroponentId = this._createMacroponent(name, template);


      var screenId = this._createScreen(macroponentId, appConfigId, screenTypeId, name, template);
      var appRouteId = this._createRoute(appConfigId, screenTypeId, name, template);
      // TODO: Have segregation between methods that create UXF artifacts and ACE artifacts
      if (pageId) {
          if (!this._updateMacroponentProp(macroponentId, 'data', this.CONSTANTS.BASE_UXF_RECORDS.SECTIONS_DATA_BROKER_NAME, 'page_route', pageId)) {
              gs.addErrorMessage(gs.getMessage('Please update the macroponent prop value via UIB'));
          }
          if (!this._updateMacroponentProp(macroponentId, 'data', this.CONSTANTS.BASE_UXF_RECORDS.ACE_PAGE_INIT_DATA_BROKER_NAME, 'page_route', pageId)) {
              gs.addErrorMessage(gs.getMessage('Please update the macroponent prop value via UIB'));
          }
          // FIXME: Remove references to hardcoded template names
          if (template != 'Home')
              this._createContentBlocks(pageId, this.CONSTANTS.DEFAULT_CONTENT_BLOCKS);

      }

      return {
          route: appRouteId,
          screenType: screenTypeId,
          screen: screenId,
          macroponent: macroponentId
      };
  },
  _createRoute: function(appConfig, screenType, name, template) {
      var cloneInfo = {
          'sys_id': this._getConstantsForPage(template).ROUTE,
          'fields': this.CONSTANTS.CLONE_FIELDS.APP_ROUTE
      };

      var payload = {
          'name': name,
          'app_config': appConfig,
          'screen_type': screenType,
          'route_type': name.toLowerCase()
      };

      return this.GLOBAL._grRecordCreator(this.UXF_TABLE.APP_ROUTE, payload, cloneInfo);
  },
  _cloneClientScripts: function(baseMacroponentId, newMacroponentId) {
      var clientScriptMaps = [];
      try {
          var _self = this;
          var gr = new GlideRecord(_self.UXF_TABLE.CLIENT_SCRIPT);
          gr.addQuery('macroponent', baseMacroponentId);
          gr.query();
          while (gr.next()) {
              var newClientScriptId = this.GLOBAL._grRecordCreator(_self.UXF_TABLE.CLIENT_SCRIPT, {
                  'macroponent': newMacroponentId,
                  'name': gr.getValue('name'),
                  'type': gr.getValue('type'),
                  'script': gr.getValue('script'),
              });
              clientScriptMaps.push({
                  'findId': gr.getValue('sys_id'),
                  'replaceWithId': newClientScriptId
              });
          }
          return clientScriptMaps;
      } catch (e) {
          return clientScriptMaps;
      }

  },
  _replaceAll: function(str, find, replace) {
      return str.replace(new RegExp(find, 'g'), replace);
  },
  _createMacroponent: function(name, template) {
      var _self = this;
      var cloneInfo = {
          'sys_id': _self._getConstantsForPage(template).MACROPONENT,
          'fields': _self.CONSTANTS.CLONE_FIELDS.MACROPONENT
      };
      var macroponentId = this.GLOBAL._grRecordCreator(this.UXF_TABLE.MACROPONENT, {
          'name': name
      }, cloneInfo);
      var clientScriptMaps = _self._cloneClientScripts(this._getConstantsForPage(template).MACROPONENT, macroponentId);
      //postCleanUp
      var gr = new GlideRecord(_self.UXF_TABLE.MACROPONENT);
      if (gr.get(macroponentId)) {
          var postUpdateComposition = {
              'composition': gr.getValue('composition'),
              'data': gr.getValue('data'),
              'state_properties': gr.getValue('state_properties'),
              'internal_event_mappings': gr.getValue('internal_event_mappings'),
          };
          clientScriptMaps.forEach(function(clientScriptMap) {
              postUpdateComposition.composition = _self._replaceAll(postUpdateComposition.composition, clientScriptMap.findId, clientScriptMap.replaceWithId);
              postUpdateComposition.data = _self._replaceAll(postUpdateComposition.data, clientScriptMap.findId, clientScriptMap.replaceWithId);
              postUpdateComposition.state_properties = _self._replaceAll(postUpdateComposition.state_properties, clientScriptMap.findId, clientScriptMap.replaceWithId);
              postUpdateComposition.internal_event_mappings = _self._replaceAll(postUpdateComposition.internal_event_mappings, clientScriptMap.findId, clientScriptMap.replaceWithId);
          });
          //gr.update();
          _self.GLOBAL.updateRecord(_self.UXF_TABLE.MACROPONENT, macroponentId, postUpdateComposition);
      }

      return macroponentId;
  },
  _createScreen: function(macroponent, appConfig, screenType, name, template) {
      var cloneInfo = {
          'sys_id': this._getConstantsForPage(template).SCREEN,
          'fields': this.CONSTANTS.CLONE_FIELDS.UX_SCREEN
      };
      var payload = {
          'name': name,
          'macroponent': macroponent,
          'app_config': appConfig,
          'screen_type': screenType
      };

      return this.GLOBAL._grRecordCreator(this.UXF_TABLE.SCREEN, payload, cloneInfo);
  },
  _getConstantsForPage: function(name) {
      switch (name) {
          case 'Home':
              return this.CONSTANTS.BASE_UXF_RECORDS.HOME_PAGE;
          case 'Settings':
              return this.CONSTANTS.BASE_UXF_RECORDS.SETTINGS_PAGE;
          case 'Search':
              return this.CONSTANTS.BASE_UXF_RECORDS.SEARCH_PAGE;
          case 'List':
              return this.CONSTANTS.BASE_UXF_RECORDS.LIST_PAGE;
          case 'Record':
              return this.CONSTANTS.BASE_UXF_RECORDS.RECORD_PAGE;
          case 'Content Block':
              return this.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK;
          default:
              return this.CONSTANTS.BASE_UXF_RECORDS.BLANK_PAGE;
      }
  },
  _createContentBlocks: function(pageId, contentBlocks) {
      var _self = this;

      function createContentBlock(pageId, block, parent) {
          try {
              var payload = JSON.parse(JSON.stringify(block));
              payload.type = block.type.id;
              payload.parent = parent;
              payload.document_table = _self.TABLE.PAGE;
              payload.document_id = pageId;
              var blockId = _self._grRecordCreator(_self.TABLE.CONTENT_BLOCK, payload);

              block.children && block.children.forEach(function(child) {
                  createContentBlock(pageId, child, blockId);
              });
          } catch (e) {
              gs.error('ACEAppBuilderUtil: error creating content blocks: ' + e.toString());
          }
      }

      contentBlocks.forEach(function(block) {
          createContentBlock(pageId, block, null);
      });
  },
  addPageToExperience: function(pageName, pageTemplate, path, aceAppSysId) {
      var pageId = this._grRecordCreator(this.TABLE.PAGE, {
          'name': pageName,
          'ace_app': aceAppSysId,
          'template': pageTemplate,
          'path': path
      });
      return pageId;
  },
  createExperience: function(name, urlPath, landingPath, aceAppSysId, createDemoData, current) {
      var _self = this;
      if (createDemoData) this.CREATE_DEMO_DATA = true;
      var appConfig = this.GLOBAL._grRecordCreator(_self.UXF_TABLE.APP_CONFIG, {
          'name': name,
          'landing_path': landingPath
      });
      var experience = this.GLOBAL._grRecordCreator(_self.UXF_TABLE.PAGE_REGISTRY, {
          'title': name,
          'admin_panel_table': _self.UXF_TABLE.APP_CONFIG,
          'admin_panel': appConfig,
          'path': urlPath,
          'parent_app': _self.CONSTANTS.BASE_UXF_RECORDS.PARENT_APP,
          'root_macroponent': _self.CONSTANTS.BASE_UXF_RECORDS.APP_SHELL_UI
      });
      var pagePropertyPayload = {
          'page': experience,
          'name': 'ace_application_id',
          'type': 'string',
          'value': aceAppSysId,
          'description': 'ACE Record id for this Experience'
      };
      var chromeHeaderPagePropertyPayload = {
          'page': experience,
          'name': 'chrome_header',
          'type': 'json',
          'value': JSON.stringify(_self.CONSTANTS.CHROME_HEADER_VALUE),
      };
      this.GLOBAL._grRecordCreator(_self.UXF_TABLE.PAGE_PROPERTY, pagePropertyPayload);
      this.GLOBAL._grRecordCreator(_self.UXF_TABLE.PAGE_PROPERTY, chromeHeaderPagePropertyPayload);

      // Update the current variable directly here with required info
      var context = this._createFactories(appConfig, aceAppSysId, name);
      if (current) {
          current.app_config = appConfig;
          current.context = JSON.stringify(context);
          current.update();
      }

      var shippedPages = _self.CONSTANTS.DEFAULT_PAGES;
      shippedPages.forEach(function(pageInfo) {
          _self.addPageToExperience(pageInfo.name, pageInfo.template, pageInfo.path, aceAppSysId);
      });
      return appConfig;
  },

  constructNavMenuItems: function(menuItems, parentId) {
      var _self = this;
      menuItems.forEach(function(menuItem) {
          menuItem.label = menuItem.title;
          menuItem.id = menuItem.block_id;
          _self.constructNavMenuItems(menuItem.children);
      });
  },
  /*_getPageIdFromRoute: function(aceAppSysId, pageRoute) {
      if (!aceAppSysId || !pageRoute) return null;
      var gr = new GlideRecord(this.TABLE.PAGE);
      gr.addQuery('name', pageRoute);
      gr.addQuery('ace_app', aceAppSysId);
      gr.query();
      if (gr.next()) {
          return gr.getValue('sys_id');
      }
      return null;
  },*/
  _buildLink: function(gr) {
      var link = '';
      switch (gr.getValue('link_type')) {
          case 'guided_setup':
              link = '/$guided_setup.do#/content/' + gr.getValue('guided_setup_id');
              var focus_id = gr.getValue('foucus_id');
              if (focus_id != '') {
                  link += '?focus=' + focus_id;
              }
              break;
          case 'record':
              link = '/' + gr.getValue('list_table') + '.do?sys_id=' + gr.getValue('record');
              break;
          case 'list':
              link = '/' + gr.getValue('list_table') + '_list.do?sysparm_query=' + gr.getValue('list_query');
              break;
          case 'custom':
          default:
              link = gr.getValue('link_url');
      }
      return link;
  },
  _getPluginsInfo: function(plugins) {
      if (!plugins) return [];

      var pluginList = plugins.split(',');
      return pluginList.map(function(plugin) {
          var grPlugin = new GlideRecord('v_plugin');
          grPlugin.get(plugin);

          return {
              "id": plugin,
              "label": grPlugin.getValue('name'),
              "selected": true,
              "type": "plugin",
              "use_demo_data": false
          };
      });
  },
  _areAllPluginsActive: function(plugins) {
      if (!plugins) return false;

      var pluginList = plugins.split(',');
      return !pluginList.some(function(plugin) {
          var grPlugin = new GlideRecord('v_plugin');
          grPlugin.get(plugin);
          return grPlugin.getValue('active') !== 'active';
      });
  },
  _mapCards: function(registry) {
      var mappedCards = [];
      try {
          registry.card_order.forEach(function(parentCardId) {
              var screens = [];
              screens.push(registry.definitions[parentCardId]);
              registry.maps[parentCardId].forEach(function(childCardId) {
                  screens.push(registry.definitions[childCardId]);
              });
              mappedCards.push(screens);
          });

          return mappedCards;
      } catch (e) {
          return mappedCards;
      }
  },
  _parseSysProperties: function(propsSysIdList) {
      var props = [];
      try {
          var gr = new GlideRecord('sys_properties');
          gr.addQuery('sys_id', 'IN', propsSysIdList);
          gr.query();
          while (gr.next()) {
              props.push({
                  'name': gr.getValue('name'),
                  'value': gr.getValue('value') == 'true',
                  'description': gr.getValue('description')
              });
          }
          return props;
      } catch (e) {
          return props;
      }
  },
  syncSysProperties: function(propsList) {
      var _self = this;
      if (!propsList) return false;
      if (!Array.isArray(propsList)) return false;
      if (propsList.length == 0) return false;
      try {
          propsList.forEach(function(property) {
              if (_self.GLOBAL._getSysProperty(property.name) != property.value.toString())
                  _self.GLOBAL._updateSysProperty(property.name, property.value);
          });
          return true;
      } catch (e) {
          return false;
      }
  },
  _getChartConfig: function(gr) {
      try {
          var config = JSON.parse(gr.getValue('chart_config'));
          config.chart_type = gr.getValue('chart_type');
          return config;
      } catch (e) {
          return {
              "chart_type": "donut",
              "title": "Basic Chart",
              "table": "problem",
              "query": "active=true"
          };
      }
  },
  _isParentChildTypeEqual: function(gr) {
      if (!gr.parent) return false;
      return gr.parent.category.toString() == gr.category.toString() &&
          gr.parent.type.toString() == gr.type.toString();
  },
  _getGlideVarsForType: function(type) {
      var variables = [];
      var gr = new GlideRecord(this.TABLE.VARIABLE);
      gr.addQuery('model', type);
      gr.query();
      while (gr.next()) {
          variables.push({
              'element': gr.getValue('element'),
              'type': gr.getValue('internal_type')
          });
      }
      return variables;
  },
  _setGlideVars: function(table, recordId, glideVariableObject) {
      try {
          var gr = new GlideRecord(table);
          if (gr.get(recordId)) {
              for (var gVariable in glideVariableObject) {
                  gr.variable[gVariable] = glideVariableObject[gVariable];
              }
              return gr.update();
          }
          return false;
      } catch (e) {
          return false;
      }
  },
  _getContentBlocksRowCountOnly: function(customQuery) {
      try {
          return this._getContentBlocks('', customQuery).length;
      } catch (e) {
          return 0;
      }
  },
  getAllContentBlockTypes: function(table, experienceMetadata) {
      var types = {};
      var _self = this;
      var gr = new GlideRecord(table);
      gr.query();
      while (gr.next()) {
          var typeId = gr.getUniqueValue();
          var route = gr.route.toString();
          var props = (experienceMetadata && experienceMetadata['typeVarsMap'] && experienceMetadata['typeVarsMap'][typeId]) ? experienceMetadata['typeVarsMap'][typeId] : _self._getGlideVarsForType(typeId);
          types[typeId] = {
              'group': gr.category.toString(),
              'name': gr.name.toString(),
              'id': typeId,
              'route': route,
              'props': props
          };
          experienceMetadata && experienceMetadata[route] && (types[typeId]['macroponent'] = experienceMetadata[route].macroponent);
      }
      return types;
  },
  _getDetailedType: function(typeSysId, experienceMetadata) {
      return this.getAllContentBlockTypes(this.TABLE.TYPE, experienceMetadata)[typeSysId];
  },
  _variableScriptEvaluator: function(gr, cbScriptVariables, cbData) {
      var _self = this;
      try {
          var evaluator = new GlideScopedEvaluator();
          evaluator.putVariable('pageId', cbScriptVariables.pageId);
          evaluator.putVariable('pageParams', cbScriptVariables.pageParams);
          evaluator.putVariable('blockcontext', cbScriptVariables.blockcontext);
          var aceVarGr = new GlideRecord(this.TABLE.VARIABLE);
          aceVarGr.addQuery('model', cbData.type.id);
          aceVarGr.query();
          if (aceVarGr.next()) {
              var sysVarGr = new GlideRecord('sys_variable_value');
              sysVarGr.addQuery('document_key', cbData.block_id);
              sysVarGr.addQuery('variable', aceVarGr.getValue('sys_id'));
              sysVarGr.query();
              try {
                  if (sysVarGr.next())
                      return evaluator.evaluateScript(sysVarGr, 'value', null);
              } catch (e) {
                  gs.error('Error while evaluating Script: ' + e.toString());
              }
          }
      } catch (e) {
          gs.error('Error while evaluating Script: ' + e.toString());
      }
      return [];
  },
  _evaluateCBScriptVariables: function(gr, cbData, cbScriptVariables) {
      var _self = this;
      var typeObject = cbData.type;
      if (typeObject && typeObject.props && Array.isArray(typeObject.props) > 0) {
          typeObject.props.forEach(function(propItem) {
              if (propItem.type === "script")
                  cbData.props_details[propItem.element] = _self._variableScriptEvaluator(gr, cbScriptVariables, cbData);
          });
      }
  },
  _sanitize: function(value, parseBoolean) {
      if (value == null || value == undefined) return '';
      if (parseBoolean) {
          if (value.toString() == 'true') return '1';
          if (value.toString() == 'false') return '0';
      }
      return value.toString();
  },

  _sanitizeScript: function(scriptValue) {
      if (scriptValue == null || scriptValue == undefined ||
          (typeof scriptValue === 'object' && JSON.stringify(scriptValue) === '{}')) {
          return '';
      }
      return scriptValue.toString();
  },

  /* dataSource =  GlideRecord instance [OR] Cached Data*/
  _getContentBlockInfo: function(dataSource, cbId, pageParams, experienceMetadata, fetchOnyMetaData) {
      var _self = this;
      try {
          var typeObject = _self._getDetailedType(_self._sanitize(dataSource.type), experienceMetadata);
          //id and label property is required for content tree component
          var style;
          try {
              style = JSON.parse(dataSource.style);
          } catch (err) {
              style = {};
          }
          var propsOverrides = {};
          try {
              propsOverrides = dataSource.props_overrides ? JSON.parse(dataSource.props_overrides) : {};
          } catch (err) {

          }
          var parentBlock = dataSource.parent;
          var parentBlockRoute = parentBlock && parentBlock.type && parentBlock.type.route ?
              parentBlock.type.route.toString() : '';
          var contentBlockInfo = {
              'block_id': cbId,
              'id': cbId,
              'parent_block_id': parentBlock.toString(),
              'parent_block_route': parentBlockRoute,
              'active': dataSource.active.toString() === 'true' ? true : false,
              'name': _self._sanitize(dataSource.name),
              'label': _self._sanitize(dataSource.name),
              'title': _self._sanitize(dataSource.title),
              'subtitle': _self._sanitize(dataSource.subtitle),
              'order': _self._sanitize(dataSource.order),
              'type': typeObject,
              'lazyLoad': _self._sanitize(dataSource.lazy_load, true),
              'children': [],
              'props_details': {},
              'props_details_display': {},
              'props_overrides': propsOverrides,
              'pageParams': pageParams,
              'style': style,
              'elementId': _self._sanitize(dataSource.element_id),
              'slotname': _self._sanitize(dataSource.slotname),
              'post_script': _self._sanitizeScript(dataSource.getValue('post_script')),
              'visibility': _self._sanitizeScript(dataSource.getValue('visibility')),
              'lazy_script': _self._sanitizeScript(dataSource.getValue('lazy_script')),
              'scope': _self._sanitize(dataSource.sys_scope),
              'package': _self._sanitize(dataSource.sys_package)
          };
          if (typeObject && typeObject.props && Array.isArray(typeObject.props) > 0) {
              typeObject.props.forEach(function(propItem) {
                  contentBlockInfo.props_details[propItem.element] = dataSource.variable[propItem.element].toString();
                  if (propItem.type == 'reference') {
                      var dictionary = new GlideRecord('sys_dictionary');
                      dictionary.addQuery('sys_id', _self._sanitize(dataSource.variable[propItem.element]));
                      dictionary.query();
                      if (dictionary.next()) {
                          contentBlockInfo.props_details_display[propItem.element] = dictionary.getValue('element');
                      }
                  }
              });
          }
          if (contentBlockInfo.type && contentBlockInfo.type.route && contentBlockInfo.type.route == 'reusable') {
              if (contentBlockInfo.props_details && contentBlockInfo.props_details.block_id)
                  var reusableBlockInfo = this.getReusableBlockDataForTranslations(contentBlockInfo.props_details.block_id);
              if (reusableBlockInfo && reusableBlockInfo.block_variables && reusableBlockInfo.block_variables.length > 0)
                  contentBlockInfo.block_variables = reusableBlockInfo.block_variables || {};

          }
          if (fetchOnyMetaData) {
              contentBlockInfo['post_script'] = dataSource.getValue("post_script");
              contentBlockInfo['visibility'] = dataSource.getValue("visibility");
              contentBlockInfo['lazy_script'] = dataSource.getValue("lazy_script");
          }
          return contentBlockInfo;
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error while fetching content block info: ' + e.toString());
          return contentBlockInfo;
      }
  },
  _getExperienceIdFromCBId: function(cbId) {
      if (!cbId) return null;
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (gr.get(cbId)) {
          return gr.getValue('ace_config');
      }
      return null;
  },
  _getExperienceIdFromCBQuery: function(cbQuery) {
      if (!cbQuery || !cbQuery.document_table || !cbQuery.document_id) return null;
      var _self = this;
      switch (cbQuery.document_table) {
          case _self.TABLE.PAGE:
              var gr = new GlideRecord(_self.TABLE.PAGE);
              if (gr.get(cbQuery.document_id)) {
                  return gr.getValue('ace_app');
              }
              return null;
          case _self.TABLE.APP_CONFIG:
              return cbQuery.document_id;
          case _self.TABLE.CONTENT_BLOCK:
              return cbQuery.document_id;
          default:
              return null;
      }
  },
  _buildCachedContentBlocks: function(experienceId, cbQuery, parentId, cbScriptVariables) {
      var cachedContentBlocks = [],
          _self = this;

      //check for user roles access sn_ace.ace_user
      if (!this.CAN_READ) return cachedContentBlocks;

      //attach the parent query to the existing query. NOTE: For cached content, parent == '' IS_NOT_EQUAL parent == null
      if (!cbQuery) cbQuery = {};
      cbQuery['parent'] = parentId;
      cbQuery['active'] = '1';
      var cachedCBs = this.GLOBAL.getContentBlocksFromCache(experienceId, cbQuery);
      if (!cachedCBs) return null;

      //Try creating a Virtual GlideRecord instance using the parentId required for GlideScopeEvaluator. Re-use this for evaluation to reduce queries.
      var virtualGr = _self.VIRTUAL_GR || _self._getVirtualGrInstance(parentId);
      if (!_self.VIRTUAL_GR && virtualGr) _self.VIRTUAL_GR = virtualGr;

      cachedCBs.forEach(function(cachedCB) {
          var cbId = cachedCB.sys_id;

          //Re-try the Virtual GR creation again with cbId (since parentId is null) and register it in app level
          virtualGr = _self.VIRTUAL_GR || _self._getVirtualGrInstance(cbId);
          if (!_self.VIRTUAL_GR && virtualGr) _self.VIRTUAL_GR = virtualGr;

          if (_self._cachedScriptEvaluator(virtualGr, 'visibility', cachedCB.visibility, null)) {
              var cachedCBData = _self._getContentBlockInfo(cachedCB, cbId);
              _self._evaluateCBScriptVariables(virtualGr, cachedCBData, cbScriptVariables);
              cachedCBData.children = _self._buildCachedContentBlocks(experienceId, cbQuery, cbId, cbScriptVariables);
              _self._cachedScriptEvaluator(virtualGr, 'post_script', cachedCB.post_script, cachedCBData);
              cachedContentBlocks.push(cachedCBData);
          }
      });
      return cachedContentBlocks;
  },
  _getContentBlocksFromCache: function(parentId, cbQuery, cbScriptVariables) {
      try {
          //get and register the experience id in app level to reduce queries
          var experienceId = this.EXPERIENCE_ID || this._getExperienceIdFromCBId(parentId) || this._getExperienceIdFromCBQuery(cbQuery);
          if (!experienceId) return null;
          if (!this.EXPERIENCE_ID) this.EXPERIENCE_ID = experienceId;

          //try to fetch content blocks from cache
          var cachedContentBlocks = this._buildCachedContentBlocks(experienceId, cbQuery, parentId, cbScriptVariables);

          //if not present, try to build the cache for this experience
          if (!cachedContentBlocks || (Array.isArray(cachedContentBlocks) && cachedContentBlocks.length == 0)) {
              this.GLOBAL.buildExperienceCache(experienceId);

              //retry fetching the content blocks from the recenly built cache
              cachedContentBlocks = this._buildCachedContentBlocks(experienceId, cbQuery, parentId, cbScriptVariables);
          }

          return cachedContentBlocks;
      } catch (e) {
          return null;
      }
  },
  _getContentBlocks: function(parentId, customQuery, cbScriptVariables, experienceMetadata, includeFilterOnEmptyParent, skipScriptEvaluation) {
      var contentBlocks = [];
      cbScriptVariables = cbScriptVariables || {};

      var _self = this;
      try {
          this.gr_counter += 1;
          var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          if (!includeFilterOnEmptyParent) {
              gr.addQuery('parent', parentId);
          }
          gr.addQuery('active', true);
          for (var q in customQuery) {
              gr.addQuery(q, customQuery[q]);
          }
          gr.addQuery('type', '!=', _self.DATA_BLOCK_CONTENT_TYPE);
          gr.orderBy('order');
          gr.query();
          while (gr.next()) {
              var cbId = gr.getUniqueValue().toString();
              if (gr.getValue("type") == _self.REUSABLE_CONTENT_TYPE) {
                  //todo		
                  var reusableContentBlocks = null;
                  var cbData = _self._getContentBlockInfo(gr, cbId, cbScriptVariables.pageParams, experienceMetadata, false);
                  var blockParams = {};
                  try {
                      blockParams = gr.variable && gr.variable.block_parameters ?
                          JSON.parse(gr.variable.block_parameters + '') : {};
                  } catch (err) {
                      gs.error('ACEAppBuilderUtil._getContentBlocks - error in parsing blockParams')
                  }
                  reusableContentBlocks = _self.getContentBlocksByBlockId(gr.variable.block_id, blockParams, cbId, false, skipScriptEvaluation);
                  cbData.children = reusableContentBlocks;
                  contentBlocks.push(cbData);
              } else {
                  if (!skipScriptEvaluation && !_self._visibilityEvaluator(gr, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams)) continue;
                  var cbData = _self._getContentBlockInfo(gr, cbId, cbScriptVariables.pageParams, experienceMetadata);

                  // content block variable script evaluator  
                  _self._evaluateCBScriptVariables(gr, cbData, cbScriptVariables);
                  cbData.children = _self._getContentBlocks(cbId, customQuery, cbScriptVariables, experienceMetadata);

                  if (!skipScriptEvaluation) {
                      //post script evaluator 
                      _self._evaluatePostScript(gr, cbData, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams);
                  }

                  contentBlocks.push(cbData);
              }
          }
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error while fetching content blocks: ' + e.toString());
      }
      return contentBlocks;
  },
  _getFetchMode: function(cbQuery) {

      //Experience level System Property value (ace.content_blocks.fetch.mode.<EXPERIENCE ID>)

      var experienceId = this._getExperienceIdFromCBQuery(cbQuery);
      if (!experienceId) return this.DEFAULT_FETCH_MODE;
      var mode = gs.getProperty('ace.content_blocks.fetch.mode.' + experienceId, 'dfs');
      mode = mode.toLowerCase();
      if (this.SUPPORTED_FETCH_MODES.indexOf(mode) != -1) return mode;
      return this.DEFAULT_FETCH_MODE;

  },

  // 	getContentBlocksById: function(blockId, pageId, pageParams, reloadCache) {
  //         var _self = this;
  //         if (!this.CAN_READ) return [];
  //         var cbQuery = {
  //             'parent': blockId,
  //             'or': {
  //                 'sys_id': blockId
  //             }
  //         };
  //         var cbPageParams = {
  //             'pageId': pageId,
  //             'pageParams': pageParams
  //         };
  // 		var contentBlockTypeMetadata = this.GLOBAL.getCachedContentTypeMetaData(reloadCache);
  //         var experienceMetadata = {};
  //         if (contentBlockTypeMetadata) {
  //             experienceMetadata = contentBlockTypeMetadata[appConfig];
  //             experienceMetadata['typeVarsMap'] = contentBlockTypeMetadata['typeVarsMap'];
  //         }

  //         return this._getContentBlocksBFS(null, cbQuery, cbPageParams, experienceMetadata);
  //     },

  getContentBlocksByPageId: function(pageId, pageParams, reloadCache) {
      var appConfig = "";
      var pageGr = new GlideRecord(this.TABLE.PAGE);
      pageGr.get(pageId);
      if (pageGr.isValidRecord()) {
          appConfig = pageGr.ace_app.app_config.toString();
      }
      return this.getContentBlocks(pageId, pageParams, appConfig, reloadCache, false);
  },
  getContentBlocksByBlockId: function(defBlockId, params, instanceBlockId, reloadCache, skipScriptEvaluation) {
      var self = this;
      var appConfig = "",
          aceAppConfigGr;
      var blockGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (instanceBlockId) {
          blockGr.addQuery("sys_id", "IN", defBlockId + "," + instanceBlockId);
          blockGr.query();
      } else {
          blockGr.get(defBlockId);
      }

      var defContentTree = [];
      var instanceContentTree = [];

      while (blockGr.next()) {
          if (blockGr.getUniqueValue() == defBlockId) {
              aceAppConfigGr = new GlideRecord(this.TABLE.APP_CONFIG);
              aceAppConfigGr.get(blockGr.document_id.toString());
              if (aceAppConfigGr.isValidRecord()) {
                  appConfig = aceAppConfigGr.app_config.toString();
              }
              defContentTree = this.getContentBlocks(defBlockId, params, appConfig, reloadCache, true, null, skipScriptEvaluation);
          } else {
              var pageId = blockGr.getValue('document_id');
              var pageGr = new GlideRecord(this.TABLE.PAGE);
              if (pageGr.get(pageId)) {
                  appConfig = pageGr.ace_app.app_config.toString();
              }
              instanceContentTree = this.getContentBlocks(instanceBlockId, params, appConfig, reloadCache, true, true, skipScriptEvaluation);
          }
      }

      self._mergeReusableBlocks(defContentTree, instanceContentTree);
      return defContentTree;
  },
  _mergeReusableBlocks: function(defContentTree, instanceContentTree) {
      var self = this;
      var children = defContentTree;
      while (children && children.length > 0) {
          var nextlevelChildren = [];
          children.forEach(function(node) {
              if (node.type && node.type.route == "content-slot") {
                  node.children = self._findNodesHavingParent(instanceContentTree, node.block_id);
              }
              nextlevelChildren = nextlevelChildren.concat(node.children);
          });
          children = nextlevelChildren;
      }
  },
  _findNodesHavingParent: function(contents, parentBlockId) {
      var children = contents;
      var result = [];
      while (children && children.length > 0) {
          var nextlevelChildren = [];
          children.forEach(function(node) {
              if (node.parent_block_id == parentBlockId) {
                  result = result.concat(node);
              } else {
                  nextlevelChildren = nextlevelChildren.concat(node.children);
              }
          });
          children = nextlevelChildren;
      }
      return result;
  },

  getExperienceMetadata: function(reloadCache, appConfig) {
      var contentBlockTypeMetadata = this.GLOBAL.getCachedContentTypeMetaData(reloadCache);
      var baseExpConfigId = this.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG;
      var experienceMetadata = {};
      if (!appConfig) {
          experienceMetadata = contentBlockTypeMetadata[baseExpConfigId];
          experienceMetadata['typeVarsMap'] = contentBlockTypeMetadata['typeVarsMap'];
          return experienceMetadata;
      }

      if (!contentBlockTypeMetadata[appConfig])
          contentBlockTypeMetadata = this.GLOBAL.getCachedContentTypeMetaData(true);
      experienceMetadata = contentBlockTypeMetadata[appConfig] || contentBlockTypeMetadata[baseExpConfigId];
      experienceMetadata['typeVarsMap'] = contentBlockTypeMetadata['typeVarsMap'];
      return experienceMetadata;
  },

  getContentBlocks: function(artifactId, pageParams, appConfig, reloadCache, isBlock, includeParent, skipScriptEvaluation) {
      var _self = this;
      if (!this.CAN_READ) return [];
      var experienceMetadata = this.getExperienceMetadata(reloadCache, appConfig);

      var cbQuery = {
          'document_table': isBlock ? _self.TABLE.CONTENT_BLOCK : _self.TABLE.PAGE,
          'document_id': artifactId
      };

      var cbPageParams = {
          'pageId': isBlock ? "" : artifactId,
          'pageParams': pageParams
      };

      // Confirm the content blocks are being fetched for a page and evaluate data blocks.
      if (cbPageParams.pageId != "") {
          // TODO: get the data blocks for this page.
          var dataBlockGr = new GlideRecord('sn_ace_content_block');
          dataBlockGr.addQuery('type', _self.DATA_BLOCK_CONTENT_TYPE);
          dataBlockGr.query();

          var dataBlocks = [];
          while (dataBlockGr.next()) {
              var cbData = _self._getContentBlockInfo(dataBlockGr, dataBlockGr.getUniqueValue().toString(), cbPageParams.pageParams, experienceMetadata);
              dataBlocks.push(cbData);
          }

          dataBlocks = _self.eagerlyEvaluateDataBlocks(dataBlocks, cbPageParams);
          cbPageParams = _self.addDataBlockParamsToScriptVariables(dataBlocks, cbPageParams);
      }

      var fetchMode = this._getFetchMode(cbQuery);
      if (fetchMode === this.DEFAULT_FETCH_MODE) {
          return this._getContentBlocks(null, cbQuery, cbPageParams, experienceMetadata, includeParent, skipScriptEvaluation).concat(dataBlocks);
      }
      return this._getContentBlocksBFS(null, cbQuery, cbPageParams, experienceMetadata, includeParent, skipScriptEvaluation).concat(dataBlocks);
  },

  getAllContentBlocksBFS: function() {
      return this._getContentBlocksBFS(null, {}, {});
  },

  _appendContentBlockschildren: function(contentBlocksMap, cbId, cbContextObj) {
      if (!contentBlocksMap[cbId]) return [];
      var result = [];
      contentBlocksMap[cbId].forEach(function(cbChildId) {
          if (cbContextObj[cbChildId]) {
              result.push(JSON.parse(JSON.stringify(cbContextObj[cbChildId])));
              //delete cbContextObj[cbChildId];   
          }
      });
      return result;
  },
  _processContentBlocksFromIds: function(contentBlocksMap, contentBlockParentIds, cbContextObj, cbScriptVariables, cbLevel, nextCBLevel, experienceMetadata, fetchOnlyMetaData) {
      var _self = this;
      this.gr_counter += 1;
      if (!contentBlockParentIds && (!cbLevel || cbLevel[nextCBLevel].length == 0)) return;

      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (contentBlockParentIds) {
          gr.addQuery('parent', 'IN', contentBlockParentIds.toString());
      } else {
          gr.addQuery('sys_id', "IN", cbLevel[nextCBLevel].join(","));
      }

      gr.orderBy('order');
      gr.query();
      while (gr.next()) {
          var cbId = gr.getUniqueValue().toString();
          if (cbLevel[nextCBLevel] && cbLevel[nextCBLevel].indexOf(cbId) == -1) continue;
          if (gr.getValue("type") == _self.REUSABLE_CONTENT_TYPE) {
              //todo		
              var reusableContentBlocks = null;
              var cbData = _self._getContentBlockInfo(gr, cbId, cbScriptVariables.pageParams, experienceMetadata, fetchOnlyMetaData);
              if (!fetchOnlyMetaData) {
                  var blockParams = gr.variable.block_parameters;
                  if (typeof blockParams == "string") {
                      try {
                          blockParams = JSON.parse(blockParams);
                      } catch (err) {}
                  }
                  reusableContentBlocks = _self.getContentBlocksByBlockId(gr.variable.block_id, blockParams, cbId, false)
              } else {
                  reusableContentBlocks = _self._getContentBlockMetaData("", gr.variable.block_id, cbId, false, gr.variable.clone + "" == "true");
                  reusableContentBlocks = reusableContentBlocks ? reusableContentBlocks.contentBlocks : [];
              }
              cbData.children = reusableContentBlocks;
              cbContextObj[cbId] = cbData;
          } else {
              var cbData = _self._getContentBlockInfo(gr, cbId, cbScriptVariables.pageParams, experienceMetadata, fetchOnlyMetaData);
              if (fetchOnlyMetaData) {
                  //append children   
                  cbData.children = _self._appendContentBlockschildren(contentBlocksMap, cbId, cbContextObj);
              } else {
                  // content block variable script evaluator  
                  _self._evaluateCBScriptVariables(gr, cbData, cbScriptVariables);
                  //append children   
                  cbData.children = _self._appendContentBlockschildren(contentBlocksMap, cbId, cbContextObj);
                  //post script evaluator 
                  _self._evaluatePostScript(gr, cbData, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams);
              }
              cbContextObj[cbId] = cbData;
          }
      }
  },
  _buildContentBlockTreeFromLevels: function(contentBlocksMap, cbLevel, cbScriptVariables, experienceMetadata, fetchOnlyMetaData) {
      //loop through last-but-one level to 0  
      var _self = this;
      var cbContextObj = {};
      var contentBlocks = [];
      for (var level = (cbLevel.length - 2); level >= 0; level--) {
          var contentBlockParentIds = cbLevel[level];
          _self._processContentBlocksFromIds(contentBlocksMap, contentBlockParentIds, cbContextObj, cbScriptVariables, cbLevel, (level + 1), experienceMetadata, fetchOnlyMetaData);
      }
      _self._processContentBlocksFromIds(contentBlocksMap, '', cbContextObj, cbScriptVariables, cbLevel, 0, experienceMetadata, fetchOnlyMetaData);
      //if only root level content blocks are present 
      if (!contentBlockParentIds) contentBlockParentIds = cbLevel[0];
      contentBlockParentIds.forEach(function(contentBlockParentId) {
          contentBlocks.push(cbContextObj[contentBlockParentId]);
      });
      return contentBlocks;
  },
  _buildContentBlockLevels: function(cbMap, cbIds, cbLevelObject, level) {
      if (!cbLevelObject[level]) cbLevelObject[level] = [];
      var _self = this;
      cbIds.forEach(function(blockId) {
          cbLevelObject[level].push(blockId);
          if (cbMap[blockId] && cbMap[blockId].length > 0) {
              _self._buildContentBlockLevels(cbMap, cbMap[blockId], cbLevelObject, (level + 1));
          }
      });
  },
  _getValidCBIds: function(base, blacklist) {
      var validIds = [];
      base.forEach(function(cbId) {
          if (blacklist.indexOf(cbId) == -1) validIds.push(cbId);
      });
      return validIds;
  },
  _visibilityCheck: function(cbLevels, cbLevelIndex, blacklistedCBIds, cbScriptVariables) {
      if (cbLevelIndex == cbLevels.length && cbLevelIndex >= 0) {
          return cbLevels;
      }
      this.gr_counter += 1;
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      gr.addQuery('active', true);
      if (cbLevelIndex < 0) {
          gr.addQuery('sys_id', 'IN', cbLevels[0]);
      } else {
          var validIds = this._getValidCBIds(cbLevels[cbLevelIndex], blacklistedCBIds);
          if (validIds.length === 0) return cbLevels;
          gr.addQuery('parent', 'IN', validIds.toString());
      }
      gr.orderBy('order');
      gr.query();
      var cbs = [];
      while (gr.next()) {
          var cbId = gr.getUniqueValue().toString();
          if (!this._visibilityEvaluator(gr, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams)) {
              blacklistedCBIds.push(cbId);
          } else {
              cbs.push(cbId);
          }
      }
      if (cbs.length)
          cbLevels[cbLevelIndex + 1] = cbs;
      return this._visibilityCheck(cbLevels, (cbLevelIndex + 1), blacklistedCBIds, cbScriptVariables);
  },
  /** 
   * BFS Approach:    
   * Step 1: Get ALL the content blocks of a page orderby 'order' 
   * Step 2: Build the parent-child sys_id relationship map. Example: 
   *      {   
   *          '_root': ['cb_id#1', 'cb_id#2', 'cb_id#3'], 
   *          'cb_id#1': ['cb_id#1.child#1', 'cb_id#1.child#2', 'cb_id#1.child#3'],   
   *          'cb_id#2': ['cb_id#2.child#1', 'cb_id#2.child#2'],  
   *          'cb_id#3': ['cb_id#3.child#1', 'cb_id#3.child#2', 'cb_id#3.child#3', 'cb_id#3.child#4'],    
   *          'cb_id#1.child#1': ['cb_id#1.child#1.child#1', ...],    
   *          ... 
   *      }   
   * Step 3: Build the step-level hierarchy of the map. Example:  
   *      [   
   *          ['cb_id#1', 'cb_id#2', 'cb_id#3'],  
   *          ['cb_id#1.child#1', 'cb_id#1.child#2', 'cb_id#1.child#3', 'cb_id#2.child#1', 'cb_id#2.child#2', 'cb_id#3.child#1', 'cb_id#3.child#2', 'cb_id#3.child#3', 'cb_id#3.child#4'],    
   *          ['cb_id#1.child#1.child#1', ...],   
   *          ... 
   *      ]   
   * Step 4: Using a top-down approach evaluate the visibility script and modify the step-level hierarchy.    
   *      Example: For, 'cb_id#2' = false 
   *      [   
   *          ['cb_id#1', 'cb_id#3'], 
   *          ['cb_id#1.child#1', 'cb_id#1.child#2', 'cb_id#1.child#3', 'cb_id#3.child#1', 'cb_id#3.child#2', 'cb_id#3.child#3', 'cb_id#3.child#4'],  
   *          ['cb_id#1.child#1.child#1', ...],   
   *          ... 
   *      ]   
   * Step 5: Using a bottom-up approach evaluate the post script and build the Content Block contract 
   */
  _getContentBlocksBFS: function(parentId, customQuery, cbScriptVariables, experienceMetadata, includeFilterOnEmptyParent) {
      try {
          var contentBlocksMap = {
              '_root': []
          };
          this.gr_counter += 1;
          //Step 1,2: get all content blocks and build the parent-child relationship map of their sys_ids 
          var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          for (var q in customQuery) {
              gr.addQuery(q, customQuery[q]);
          }

          if (parentId || includeFilterOnEmptyParent) {
              gr.addQuery('parent', parentId);
          }
          gr.addQuery('type', '!=', this.DATA_BLOCK_CONTENT_TYPE);
          gr.orderBy('order');
          gr.query();
          while (gr.next()) {
              var blockId = gr.getUniqueValue();
              var parentBlockId = gr.getValue('parent');
              if (!parentBlockId) contentBlocksMap._root.push(blockId);
              else {
                  if (!contentBlocksMap[parentBlockId]) contentBlocksMap[parentBlockId] = [];
                  contentBlocksMap[parentBlockId].push(blockId);
              }
          }
          var contentBlocksLevel = [];
          //Step 3: build a 2D array of the step-level hierarchy  
          this._buildContentBlockLevels(contentBlocksMap, contentBlocksMap._root, contentBlocksLevel, 0);
          //Step 4: modify the same 2D array of the step-level hierarchy after removing visible false items   
          contentBlocksLevel = this._visibilityCheck(contentBlocksLevel, -1, [], cbScriptVariables);
          //Step 5: build the content block contract  
          var contentBlocks = this._buildContentBlockTreeFromLevels(contentBlocksMap, contentBlocksLevel, cbScriptVariables, experienceMetadata);
          return contentBlocks;
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error while fetching content blocks: ' + e.toString());
          return [];
      }
  },
  getContentBlockPayload: function(cbId, pageParams, includeChildBlocks) {
      var cbData = {};
      var cbScriptVariables = {
          'pageParams': pageParams
      };
      if (!this.CAN_READ) return cbData;

      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (cbId && gr.get(cbId)) {
          // TODO: Should we check for visibility in lazy load again?
          var app_config = gr.ace_config.app_config.toString();
          var experienceMetadata = this.getExperienceMetadata(false, app_config);

          cbData = this._getContentBlockInfo(gr, cbId, pageParams, experienceMetadata);
          this._evaluateCBScriptVariables(gr, cbData, cbScriptVariables);

          if (includeChildBlocks) {
              // content block variable script evaluator
              cbData.children = this._getContentBlocks(cbId, {}, cbScriptVariables, experienceMetadata);

              //post script evaluator
              _self._evaluatePostScript(gr, cbData, pageParams);
          }

          this._evaluateLazyScript(gr, cbData, pageParams);
      }
      return cbData;
  },
  _getUrlPath: function(urlPath, route) {
      return '/now' + '/' + urlPath + '/' + route.toLowerCase();
  },
  getMenuItems: function(aceAppId, moduleSysId, routeParams) {
      var _self = this;
      var menuItems = [];
      try {
          menuItems = _self._getContentBlocks(moduleSysId, {
              'document_table': _self.TABLE.APP_CONFIG,
              'document_id': aceAppId,
              'type': _self.CONSTANTS.CONTENT_BLOCK_TYPES.NAV
          }, {
              pageParams: routeParams
          });

          _self.constructNavMenuItems(menuItems);
          return menuItems;
      } catch (e) {
          return menuItems;
      }
  },

  getFieldsAndParams: function(routeId) {
      var routeGr = new GlideRecord('sys_ux_app_route');
      var result = {
          fields: [],
          params: []
      };
      if (routeGr.get(routeId)) {
          var fields = routeGr.getValue('fields') || '';
          result.fields = fields.split(',').filter(function(field) {
              return field;
          }).map(function(field) {
              return {
                  name: field
              };
          });
          var params = routeGr.getValue('optional_parameters') || '';
          result.params = params.split(',').filter(function(param) {
              return param;
          }).map(function(param) {
              return {
                  name: param
              };
          });
      }
      return result;
  },

  getModulePath: function(urlPath, modulePath, routeId, page_parameters) {
      var _self = this;
      var path = _self._getUrlPath(urlPath, modulePath);
      var uxfPageParams = _self.getFieldsAndParams(routeId);
      if (page_parameters) {
          page_parameters = JSON.parse(page_parameters);
          uxfPageParams.fields.concat(uxfPageParams.params).forEach(function(param) {
              var name = param.name;
              if (page_parameters[name])
                  path = path + '/' + page_parameters[name].toString();
          });
      }
      return path;
  },

  getModules: function(aceAppId, urlPath, routeParams) {
      var _self = this;
      if (!this.CAN_READ) return [];
      var modules = this._getContentBlocks(null, {
          'document_table': this.TABLE.APP_CONFIG,
          'document_id': aceAppId,
          'parent': null,
          'type': this.CONSTANTS.CONTENT_BLOCK_TYPES.MODULE
      });

      //massage modules blocks to support UI
      //TODO: Make UI in sync with content_blocks contract
      modules.forEach(function(module) {
          module.isActive = false;
          var containedPages = module.props_details.contained_pages;
          module.props_details.contained_pages = containedPages ? _self.getContainedPagesRoutes(containedPages) : containedPages;
          module.menu = _self.getMenuItems(aceAppId, module.block_id, routeParams);
          // Module Page Route Should be atken from prop details
          var pageId = module.props_details.ace_page;
          var gr = new GlideRecord(_self.TABLE.PAGE);
          if (pageId && gr.get(pageId)) {
              module.page_route = gr.getValue('path');
              module.path = _self.getModulePath(urlPath, module.page_route, gr.route.toString(), module.props_details.page_parameters);
          } else if (module.props_details.link) module.path = module.props_details.link;
          else gs.addErrorMessage(gs.getMessage("Please provide input for either acepage or link fields"));
      });

      return modules;

  },

  getContainedPagesRoutes: function(containedPages) {
      var _self = this;
      var contained_pages_routes = [];
      var ace_page_gr = new GlideRecord(_self.TABLE.PAGE);
      ace_page_gr.addQuery('sys_id', 'IN', containedPages);
      ace_page_gr.query();
      while (ace_page_gr.next()) {
          contained_pages_routes.push(ace_page_gr.getValue('path'));
      }
      return contained_pages_routes;
  },

  getURLPath: function(uxAppConfigId) {
      var url_path = '';
      var sys_ux_page_registry_gr = new GlideRecord(this.CONSTANTS.UXF_TABLES.PAGE_REGISTRY);
      sys_ux_page_registry_gr.addQuery('admin_panel', uxAppConfigId);
      sys_ux_page_registry_gr.query();
      if (sys_ux_page_registry_gr.next()) {
          url_path = sys_ux_page_registry_gr.getValue('path');
      }
      return url_path;
  },

  getAceAppData: function(aceAppConfig, routeParams) {
      var _self = this;
      var onLoadData = {
          'modules': []
      };
      try {
          var gr = new GlideRecord(this.TABLE.APP_CONFIG);
          if (gr.get(aceAppConfig)) {
              onLoadData.modules = _self.getModules(aceAppConfig, _self.getURLPath(gr.getValue('app_config')), routeParams);
              onLoadData.isSearchConfigured = _self.fetchSearchConfig(aceAppConfig) ? true : false;
          }
          return onLoadData;
      } catch (e) {
          return onLoadData;
      }
  },
  _createDemoBlocksInPage: function(pageId) {
      var _self = this;
      this._createContentBlocks(pageId, this.demoUtils.getDemoTemplate());
  },
  _createDemoData: function(pageName, pageId, moduleId) {
      // TODO: Add this demo data while creating the app
      if (!this.CREATE_DEMO_DATA) return;
  },

  _createFactories: function(appConfigId, aceAppSysId, appName) {
      var contentOutput = this.createPage(appConfigId, appName + ' - ACE Content Factory', aceAppSysId, 'Content Factory');
      var containerOutput = this.createPage(appConfigId, appName + ' - ACE Container Factory', aceAppSysId, 'Container Factory');

      return this._patchFactories(contentOutput.macroponent, containerOutput.macroponent);
  },

  _deleteUXFPage: function(routeId) {
      // TODO: Replace this method with UXF apis in app builder
      var routeGr = new GlideRecord(this.UXF_TABLE.APP_ROUTE);
      if (!routeGr.get(routeId)) return;

      var screenTypeGr = new GlideRecord(this.UXF_TABLE.SCREEN_TYPE);
      if (!screenTypeGr.get(routeGr.getValue('screen_type'))) return;

      var screenGr = new GlideRecord(this.UXF_TABLE.SCREEN);
      screenGr.addQuery('screen_type', routeGr.getValue('screen_type'));
      screenGr.query();
      while (screenGr.next()) {
          this.GLOBAL.deleteRecord(this.UXF_TABLE.SCREEN, screenGr.getUniqueValue());
      }

      var macroponentGr = new GlideRecord(this.UXF_TABLE.MACROPONENT);
      if (!macroponentGr.get(screenGr.getValue('macroponent'))) return;

      this.GLOBAL.deleteRecord(this.UXF_TABLE.APP_ROUTE, routeGr.getUniqueValue());
      this.GLOBAL.deleteRecord(this.UXF_TABLE.SCREEN_TYPE, screenTypeGr.getUniqueValue());
      this.GLOBAL.deleteRecord(this.UXF_TABLE.MACROPONENT, macroponentGr.getUniqueValue());
  },
  _createContentBlock: function(configId, inputDocId, templateId, documentTable, searchPageId) {
      var _self = this;
      var cbGr = new GlideRecord(_self.TABLE.CONTENT_BLOCK);
      cbGr.addQuery('document_id', templateId);
      cbGr.addQuery('document_table', documentTable);
      cbGr.query();
      while (cbGr.next()) {
          var props_details = {};
          var props = _self._getGlideVarsForType(cbGr.type.toString());
          if (props && Array.isArray(props) > 0) {
              props.forEach(function(propItem) {
                  props_details[propItem.element] = cbGr.variable[propItem.element].toString();
              });
          }
          var cbId = _self._grRecordCreator(_self.TABLE.CONTENT_BLOCK, {
              name: cbGr.getValue('name'),
              title: cbGr.getValue('title'),
              subtitle: cbGr.getValue('subtitle'),
              order: cbGr.getValue('order'),
              type: cbGr.type,
              parent: cbGr.parent,
              post_script: cbGr.getValue('post_script'),
              visibility: cbGr.getValue('visibility'),
              active: cbGr.getValue('active'),
              ace_config: configId,
              document_id: inputDocId,
              document_table: documentTable
          });
          if (searchPageId && cbGr.getValue('name') === _self.CONSTANTS.SEARCH_CONTENT_BLOCK) {
              props_details['page'] = searchPageId;
          }
          _self._setGlideVars(_self.TABLE.CONTENT_BLOCK, cbId, props_details);
      }
  },
  createContentBlock: function(configId, pageId) {
      var _self = this;
      if (!this.CAN_READ) return;
      var baseGr = new GlideRecord(_self.TABLE.PAGE);
      if (pageId && baseGr.get(pageId)) {
          var templatePageId = baseGr.getValue('template');
          _self._createContentBlock(configId, pageId, templatePageId, _self.TABLE.PAGE);
      }
  },

  _patchPage: function(aceAppId, pageId, macroponentId, screenType, sourceMacroponentId, isNewPage) {
      if (!this.CAN_READ) return null;

      if (!this._updateMacroponentProp(macroponentId, 'data', this.CONSTANTS.BASE_UXF_RECORDS.SECTIONS_DATA_BROKER_NAME, 'page_route', pageId)) {
          //code logic
      }
      if (!this._updateMacroponentProp(macroponentId, 'data', this.CONSTANTS.BASE_UXF_RECORDS.ACE_PAGE_INIT_DATA_BROKER_NAME, 'page_route', pageId)) {
          //code logic
      }

      try {
          // Update route field on ace_page record if screenType is supplied
          if (screenType) {
              var routeGr = new GlideRecord(this.UXF_TABLE.APP_ROUTE);
              routeGr.addQuery('screen_type', screenType);
              routeGr.query();

              if (routeGr.next()) {
                  var pageGr = new GlideRecord(this.TABLE.PAGE);
                  if (pageGr.get(pageId) && pageGr.canWrite()) {
                      pageGr.setValue('route', routeGr.getUniqueValue());
                      pageGr.update();
                  }
              }
          }

          // FIXME: These record updates maynot work from a different scope. Try to use Global update script include
          if (isNewPage) {
              this.fixClientScriptBindings(sourceMacroponentId, macroponentId);
          }
      } catch (e) {
          gs.error(e);
          return null;
      }
  },

  fixClientScriptBindings: function(sourceMacroponentId, targetMacroponentId) {
      var targetMacroponentGr = new GlideRecord(this.UXF_TABLE.MACROPONENT);
      targetMacroponentGr.get(targetMacroponentId);

      var clientScriptSrcTargetMap = {};

      var sourceClientScriptsGr = new GlideRecord("sys_ux_client_script");
      sourceClientScriptsGr.addQuery("macroponent", sourceMacroponentId);
      sourceClientScriptsGr.query();
      while (sourceClientScriptsGr.next()) {
          clientScriptSrcTargetMap[sourceClientScriptsGr.getValue("name")] = clientScriptSrcTargetMap[sourceClientScriptsGr.getValue("name")] || {};
          clientScriptSrcTargetMap[sourceClientScriptsGr.getValue("name")].srcId = sourceClientScriptsGr.getUniqueValue();
      }

      var targetClientScriptsGr = new GlideRecord("sys_ux_client_script");
      targetClientScriptsGr.addQuery("macroponent", targetMacroponentId);
      targetClientScriptsGr.query();
      while (targetClientScriptsGr.next()) {
          clientScriptSrcTargetMap[targetClientScriptsGr.getValue("name")] = clientScriptSrcTargetMap[targetClientScriptsGr.getValue("name")] || {};
          clientScriptSrcTargetMap[targetClientScriptsGr.getValue("name")].targetId = targetClientScriptsGr.getUniqueValue();
      }

      var targetMacroponentUpdatedProps = {};
      for (var clientScriptName in clientScriptSrcTargetMap) {
          if (clientScriptSrcTargetMap[clientScriptName].srcId && clientScriptSrcTargetMap[clientScriptName].targetId) {
              if (targetMacroponentGr.getValue("composition")) {
                  targetMacroponentUpdatedProps.composition = targetMacroponentUpdatedProps.composition || targetMacroponentGr.getValue("composition") || "";
                  targetMacroponentUpdatedProps.composition = targetMacroponentUpdatedProps.composition.replace(new RegExp(clientScriptSrcTargetMap[clientScriptName].srcId, 'g'), clientScriptSrcTargetMap[clientScriptName].targetId);
              }
              if (targetMacroponentGr.getValue("internal_event_mappings")) {
                  targetMacroponentUpdatedProps.internal_event_mappings = targetMacroponentUpdatedProps.internal_event_mappings || targetMacroponentGr.getValue("internal_event_mappings") || "";
                  targetMacroponentUpdatedProps.internal_event_mappings = targetMacroponentUpdatedProps.internal_event_mappings.replace(new RegExp(clientScriptSrcTargetMap[clientScriptName].srcId, 'g'), clientScriptSrcTargetMap[clientScriptName].targetId);
              }
              if (targetMacroponentGr.getValue("data")) {
                  targetMacroponentUpdatedProps.data = targetMacroponentUpdatedProps.data || targetMacroponentGr.getValue("data") || "";
                  targetMacroponentUpdatedProps.data = targetMacroponentUpdatedProps.data.replace(new RegExp(clientScriptSrcTargetMap[clientScriptName].srcId, 'g'), clientScriptSrcTargetMap[clientScriptName].targetId);
              }
          }
      }

      this.GLOBAL.updateRecord(this.UXF_TABLE.MACROPONENT, targetMacroponentId, targetMacroponentUpdatedProps);
  },

  updateAppConfigForSubPagesRoutes: function(screenType, contentBlockId) {
      var _self = this;
      var screen = new GlideRecord(this.UXF_TABLE.SCREEN);
      screen.get('screen_type', screenType);
      var appConfig = screen.app_config;

      var route_gr = new GlideRecord(this.UXF_TABLE.APP_ROUTE);
      route_gr.addQuery('parent_macroponent', contentBlockId);
      route_gr.addQuery('app_config', this.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG);
      route_gr.query();
      while (route_gr.next()) {
          var route_id = route_gr.sys_id;
          this.GLOBAL.updateRecord(_self.UXF_TABLE.APP_ROUTE, route_id, {
              'app_config': appConfig
          });
      }
  },

  getContentBlockSubPages: function(appName) {
      var self = this;
      var sub_pages_list = [];
      var clone_macroponent_routes = self.CONSTANTS.CLONE_ROUTES;
      var content_block_routes_gr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
      content_block_routes_gr.addQuery('app_config', self.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG);
      content_block_routes_gr.addQuery('parent_macroponent', self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT);
      content_block_routes_gr.query();
      while (content_block_routes_gr.next()) {
          var cloneMacroponent = clone_macroponent_routes.some(function(route) {
              return route === content_block_routes_gr.getValue('route_type');
          });
          var screen_gr = new GlideRecord(self.UXF_TABLE.SCREEN);
          screen_gr.get('screen_type', content_block_routes_gr.getValue('screen_type'));
          sub_pages_list.push({
              MACROPONENT: screen_gr.getValue('macroponent'),
              SCREEN: screen_gr.getValue('sys_id'),
              SCREEN_TYPE: content_block_routes_gr.getValue('screen_type'),
              NAME: appName + " - " + content_block_routes_gr.getValue('name'),
              ROUTE: content_block_routes_gr.getValue('route_type'),
              CLONE_MACROPONENT: cloneMacroponent
          });
      }
      return sub_pages_list;
  },

  _cloneMacroponentForSubpage: function(baseMacroponentId, subPageName, appContentBlockId) {
      var self = this;
      var content_block = self._getConstantsForPage('Content Block');
      var macroponentCloneInfo = {
          'sys_id': baseMacroponentId,
          'fields': self.CONSTANTS.CLONE_FIELDS.MACROPONENT
      };

      // create new subpage macroponent
      var newMacroponentId = self.GLOBAL._grRecordCreator(self.UXF_TABLE.MACROPONENT, {
          'name': subPageName
      }, macroponentCloneInfo);
      var macroponentGr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
      if (!macroponentGr.get(newMacroponentId)) {
          return;
      }

      // patch it with base composition
      self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, newMacroponentId, {
          'composition': self._replaceAll(macroponentGr.getValue('composition'), content_block.MACROPONENT, appContentBlockId)
      });

      // ensure success
      macroponentGr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
      if (!macroponentGr.get(newMacroponentId)) {
          return;
      }

      return macroponentGr;
  },

  _updateNewMacroponentWithScripts: function(newMacroponentGr, clientScriptMaps, newMacroponentId) {
      var self = this;
      var postUpdate = {
          'composition': newMacroponentGr.getValue('composition'),
          'data': newMacroponentGr.getValue('data'),
          'state_properties': newMacroponentGr.getValue('state_properties'),
          'internal_event_mappings': newMacroponentGr.getValue('internal_event_mappings'),
      };

      clientScriptMaps.forEach(function(clientScriptMap) {
          postUpdate.composition = self._replaceAll(
              postUpdate.composition,
              clientScriptMap.findId,
              clientScriptMap.replaceWithId
          );
          postUpdate.data = self._replaceAll(
              postUpdate.data,
              clientScriptMap.findId,
              clientScriptMap.replaceWithId
          );
          postUpdate.state_properties = self._replaceAll(
              postUpdate.state_properties,
              clientScriptMap.findId,
              clientScriptMap.replaceWithId
          );
          postUpdate.internal_event_mappings = self._replaceAll(
              postUpdate.internal_event_mappings,
              clientScriptMap.findId,
              clientScriptMap.replaceWithId
          );
      });

      self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, newMacroponentId, postUpdate);
  },

  _createScreenTypeForSubPage: function(subPage) {
      var self = this;
      var screenTypeCloneInfo = {
          'sys_id': subPage.SCREEN_TYPE,
          'fields': self.CONSTANTS.CLONE_FIELDS.SCREEN_TYPE
      };
      var newScreenTypeId = self.GLOBAL._grRecordCreator(self.UXF_TABLE.SCREEN_TYPE, {
              'name': subPage.NAME
          },
          screenTypeCloneInfo);

      return newScreenTypeId;
  },

  _createScreenForSubPage: function(subPage, newMacroponentId, appConfig, newScreenTypeId, appContentBlockId) {
      var self = this;
      var screenCloneInfo = {
          'sys_id': subPage.SCREEN,
          'fields': self.CONSTANTS.CLONE_FIELDS.UX_SCREEN
      };

      var screenPayload = {
          'name': subPage.NAME,
          'macroponent': newMacroponentId,
          'app_config': appConfig,
          'screen_type': newScreenTypeId,
          'parent_macroponent': appContentBlockId
      };

      self.GLOBAL._grRecordCreator(self.UXF_TABLE.SCREEN, screenPayload, screenCloneInfo);
  },

  _patchScreenCollectionToRoute: function(subPage, appContentBlockId, appConfig, newScreenTypeId) {
      var self = this;
      var route_gr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
      route_gr.addQuery('route_type', subPage.ROUTE);
      route_gr.addQuery('parent_macroponent', appContentBlockId);
      route_gr.addQuery('app_config', appConfig);
      route_gr.query();
      if (route_gr.next()) {
          var route_id = route_gr.sys_id;
          self.GLOBAL.updateRecord(self.UXF_TABLE.APP_ROUTE, route_id, {
              'screen_type': newScreenTypeId
          });
      }
  },

  _cloneMacroponentAndScripts: function(baseMacroponent, subPageName, appContentBlockId) {
      var self = this;

      // MACROPONENT CREATION
      var newMacroponentGr = self._cloneMacroponentForSubpage(baseMacroponent, subPageName, appContentBlockId);
      if (!newMacroponentGr) {
          return;
      }
      var newMacroponentSysId = newMacroponentGr.getValue('sys_id');

      // CLONE CLIENT SCRIPTS
      var clientScriptMaps = self._cloneClientScripts(baseMacroponent, newMacroponentSysId);

      // POST UPDATE MACROPONENT
      self._updateNewMacroponentWithScripts(newMacroponentGr, clientScriptMaps, newMacroponentSysId);

      return newMacroponentSysId;
  },

  cloneSubPages: function(appName, appConfig, appContentBlockId, subPages) {
      // TODO: appName is not used in this method but do not remove it here unless you are going to refactor the
      // various callers using this method signature through UIB/UX tranform script, etc.
      var self = this;
      try {
          subPages.forEach(function(subPage) {
              if (!subPage) {
                  return;
              }

              // OPTIONAL MACROPONENT CREATION
              var newMacroponentId = subPage.MACROPONENT; // initialize to base macroponent
              if (subPage.CLONE_MACROPONENT) {
                  newMacroponentId = self._cloneMacroponentAndScripts(subPage.MACROPONENT, subPage.NAME, appContentBlockId);
              }

              //SCREEN_TYPE CREATION
              var newScreenTypeId = self._createScreenTypeForSubPage(subPage);

              //SCREEN CREATION
              self._createScreenForSubPage(subPage, newMacroponentId, appConfig, newScreenTypeId, appContentBlockId);

              // PATCH SCREEN COLLECTION TO ROUTE
              self._patchScreenCollectionToRoute(subPage, appContentBlockId, appConfig, newScreenTypeId);
          });
      } catch (e) {
          gs.error("ACEAppBuilderUtil: Failed to create content block subpages: " + e.toString());
      }
  },

  fixMissingSubPages: function(aceBaseCbSubpages, appCbSubpages, appContentBlockId, appConfigSysId) {
      try {

          var self = this;
          var appSubpagesMap = {};
          for (var i = 0; i < appCbSubpages.length; i++) {
              appSubpagesMap[appCbSubpages[i].ROUTE] = appCbSubpages[i];
          }

          var subpagesWithMissingMacroponent = aceBaseCbSubpages.filter(function(baseSubpage) {
              var appSubpage = appSubpagesMap[baseSubpage.ROUTE];
              if (!appSubpage) {
                  return false;
              }

              // for subpages that are flagged to have their macroponents cloned,
              // if both base and app subpage point to the same macroponent, then the macroponent is missing
              return appSubpage.CLONE_MACROPONENT && baseSubpage.MACROPONENT === appSubpage.MACROPONENT;
          });

          subpagesWithMissingMacroponent.forEach(function(baseSubpage) {
              var newMacroponentId = self._cloneMacroponentAndScripts(baseSubpage.MACROPONENT, baseSubpage.NAME, appContentBlockId);
              var appSubpage = appSubpagesMap[baseSubpage.ROUTE];
              appSubpage.MACROPONENT = newMacroponentId;
              self.syncScreenWithBase(baseSubpage, appSubpage, appConfigSysId);
          });
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Failed to fix missing content block subpages: ' + e.toString());
      }
  },

  getPropertiesPayload: function(propertyIds) {
      var supportedTypes = this.CONSTANTS.SUPPORTED_PROPERTIES_TYPES;
      return this.GLOBAL.getPropertiesPayload(propertyIds, supportedTypes);
  },
  getControlCardQuery: function(table) {
      //Example query: name=incident^internal_type=string^ORinternal_type=string_full_utf8^ORinternal_type=integer^ORinternal_type=choice^ORinternal_type=boolean
      var query = 'name=' + table + '^';
      var supportedFields = this.CONSTANTS.SUPPORTED_PROPERTIES_TYPES;
      query += supportedFields.map(function(f, index) {
          return (index > 0 ? '^OR' : '') + 'internal_type=' + f;
      }).join('');
      return query;
  },
  getAnalyticsCardQuery: function(tableName) {

      var query = 'name=' + tableName + '^';
      var tableGr = new GlideRecord('sys_db_object');
      tableGr.addQuery('name', tableName);
      tableGr.query();
      if (tableGr.next()) {
          var superClass = tableGr.getValue('super_class');
          if (superClass) {
              var superClassTableGr = new GlideRecord('sys_db_object');
              if (superClassTableGr.get(superClass)) {
                  query += 'OR' + 'name=' + superClassTableGr.getValue('name') + '^';
              }
          }
      }
      query += 'internal_type!=collection^ORinternal_type=NULL';
      return query;

  },

  getControl: function(contentBlockPayload) {
      var result = [];
      var _self = this;
      try {
          contentBlockPayload.forEach(function(control) {
              if (!control.props_details) {
                  return;
              }

              var controlType = control.props_details.control_type;

              if (controlType === 'button') {
                  var buttonLabel = control.props_details.button_label;
                  var buttonEventName = control.props_details.button_event_name;

                  var payload = {
                      'title': control.title,
                      'subtitle': control.subtitle,
                      'type': control.props_details.control_type,
                      'button_data': {
                          label: buttonLabel,
                          event_name: buttonEventName
                      }
                  };

                  result.push(payload);
              } else if (controlType === 'record') {
                  if (control.props_details.table && control.props_details.record && control.props_details.field) {
                      var table = control.props_details.table;
                      var recordId = control.props_details.record;
                      var fieldName = control.props_details.field;
                      var helperText = control.props_details.helper_text;
                      var placeHolderText = control.props_details.placeholder_text || '';
                      var isDisabled = (control.props_details.disabled === true);
                      var payload = {
                          'title': control.title,
                          'subtitle': control.subtitle,
                          'type': control.props_details.control_type,
                          'record_data': {}
                      };

                      if (fieldName) {
                          payload.record_data.field_name = fieldName;
                          var recordDetails = _self.GLOBAL.getRecord(table, recordId, [payload.record_data.field_name]);
                          if (recordDetails[payload.record_data.field_name] != undefined) {
                              payload.record_data.field_type = recordDetails.__internal_type;
                              payload.record_data.sys_id = recordId;
                              payload.record_data.table = table;
                              payload.record_data.old_value = recordDetails[payload.record_data.field_name];
                              payload.record_data.read_only = isDisabled || !(recordDetails.__canWrite && recordDetails.__isInSelectedScope);
                              if (helperText) {
                                  payload.record_data.helper_text = helperText;
                              }

                              if (placeHolderText) {
                                  payload.record_data.placeholder_text = placeHolderText;
                              }

                              if (recordDetails.__choices)
                                  payload.record_data.choices = recordDetails.__choices;
                          }
                      }
                      result.push(payload);
                  }
              }
          });
          return result;
      } catch (e) {
          return result;
      }
  },

  _updateRecord: function(table, recordId, input) {
      try {
          return this.GLOBAL.updateRecord(table, recordId, input);
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error: ' + e.toString());
          return false;
      }
  },
  setControl: function(payload) {
      try {
          var updated = true,
              _self = this;
          for (var recordId in payload) {
              var record = payload[recordId];
              if (record.new_value != record.old_value) {
                  var input = {};
                  input[record.field_name] = record.new_value;
                  updated = _self._updateRecord(record.table, recordId, input) && updated;
              }
          }
          return updated;
      } catch (e) {
          return false;
      }
  },
  createAiSearchConfig: function(appConfigId, searchConfigBlockId) {
      var searchSourceId, searchProfileId, searchApplicationId, appName, searchContextContentBlockId;
      //fetching app name
      if (!this.CAN_READ) return 3;
      var gr = new GlideRecord(this.TABLE.APP_CONFIG);
      if (gr.get(appConfigId)) {
          appName = gr.getValue('name');
      }

      if (this.GLOBAL._isAisEnabled()) {
          var dataSource = this.GLOBAL._grGetKeyDataFromTable('ais_datasource', this.AIS.searchIndex, 'source');

          if (!this.GLOBAL.AISAlreadyExists(appName)) {
              if (dataSource != null) {
                  searchSourceId = this.GLOBAL._grRecordCreator('ais_search_source', {
                      'name': appName + this.AIS.source,
                      'datasource': dataSource,
                      'condition': 'ace_config=' + appConfigId + ''
                  }, null, true);
              }
              if (searchSourceId != null) {
                  searchProfileId = this.GLOBAL._grRecordCreator('ais_search_profile', {
                      'label': appName + this.AIS.profile
                  }, null, true);

                  //map search source to profile
                  this.GLOBAL._grRecordCreator('ais_search_profile_ais_search_source_m2m', {
                      'profile': searchProfileId,
                      'search_source': searchSourceId
                  });

                  //publishing search profile
                  var searchProfileName = this.GLOBAL._grGetKeyDataFromTable('ais_search_profile', searchProfileId, 'name');
                  this.GLOBAL._aisPublishProfile(searchProfileName);
              }

              //creating search application
              if (searchProfileId != null) {
                  searchApplicationId = this.GLOBAL._grRecordCreator('sys_search_context_config', {
                      'name': appName + this.AIS.application,
                      'search_profile': searchProfileId,
                      'genius_results_limit': 3
                  }, null, true);
              }
              // creating search context block
              if (searchApplicationId != null) {
                  searchContextContentBlockId = this._grRecordCreator(this.TABLE.CONTENT_BLOCK, {
                      'type': this.CONSTANTS.CONTENT_BLOCK_TYPES.SEARCH_CONTEXT,
                      'name': appName + ' - Default Search Context',
                      'ace_config': appConfigId,
                      'parent': searchConfigBlockId,
                      'order': 10,
                      'document_id': appConfigId,
                      'document_table': this.TABLE.APP_CONFIG
                  });
                  this._setGlideVars(this.TABLE.CONTENT_BLOCK, searchContextContentBlockId, {
                      'search_application': searchApplicationId,
                      'evam_configuration': this.AIS.evam_config
                  });
              }

              if (searchContextContentBlockId != null) {
                  return 2;
              }

          } else {
              return 1;
          }

      } else {
          //zing search configuration
          return 0;
      }
      return 3;
  },

  fetchRouteSearchResult: function(aceAppId, contentBlockId) {
      var _self = this;
      if (!this.CAN_READ) return null;
      var contentBlockGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      if (contentBlockGr.get(contentBlockId)) {
          if (contentBlockGr.getValue('document_table') == this.TABLE.PAGE) {
              var pageId = contentBlockGr.getValue('document_id');
              var gr = new GlideRecord(this.TABLE.PAGE);
              if (gr.get(pageId)) {
                  var routeId = gr.getValue('route');
                  var route = this.GLOBAL._grGetKeyDataFromTable(_self.UXF_TABLE.APP_ROUTE, routeId, 'route_type');
                  return getURL(aceAppId, route);
              }
          } else {
              return getURL(aceAppId, '');
          }
      }

      function getURL(aceAppId, route) {
          var gr = new GlideRecord(_self.TABLE.APP_CONFIG);
          var url = '';
          if (gr.get(aceAppId)) {
              url = '/now/' + _self.getURLPath(gr.getValue('app_config')) + '/' + route;
          }

          return url;
      }
  },

  fetchSearchConfig: function(aceAppId) {
      var _self = this;

      function getURL(route, contentBlockId) {
          var gr = new GlideRecord(_self.TABLE.APP_CONFIG);
          var url = '';
          if (gr.get(aceAppId)) {
              url = '/now/' + _self.getURLPath(gr.getValue('app_config')) + '/' + route + '/' + contentBlockId;
          }

          return url;
      }

      var result = {};
      if (!this.CAN_READ) return result;
      var searchConfigBlocks = this._getContentBlocks('', {
          'document_table': this.TABLE.APP_CONFIG,
          'document_id': aceAppId,
          'parent': '',
          'type': this.CONSTANTS.CONTENT_BLOCK_TYPES.SEARCH_CONFIG
      });
      if (searchConfigBlocks.length > 0) {
          var pageId = searchConfigBlocks[0].props_details.page;
          var gr = new GlideRecord(this.TABLE.PAGE);
          if (gr.get(pageId)) {
              var routeId = gr.getValue('route');
              var route = this.GLOBAL._grGetKeyDataFromTable(_self.UXF_TABLE.APP_ROUTE, routeId, 'route_type');
              result = {
                  page_route: route,
                  mode: searchConfigBlocks[0].props_details.mode,
                  sys_id: searchConfigBlocks[0].block_id,
                  url: getURL(route, searchConfigBlocks[0].block_id)
              };
          }
          return result;
      }
  },
  getSearchContexts: function(aceAppId, searchConfigContentBlockId) {
      var result = [];
      if (!this.CAN_READ) return result;
      var searchContexts = this._getContentBlocks(searchConfigContentBlockId, {
          'ace_config': aceAppId,
          'type': this.CONSTANTS.CONTENT_BLOCK_TYPES.SEARCH_CONTEXT
      });
      searchContexts.forEach(function(context) {
          if (context.props_details.evam_configuration && context.props_details.search_application) {
              result.push({
                  'search_config': context.props_details.search_application,
                  'evam_config': context.props_details.evam_configuration
              });
          }
      });
      return result;
  },
  activateSearchConfig: function(aceAppId, searchConfigContentBlockId) {
      var result = this.getSearchContexts(aceAppId, searchConfigContentBlockId);
      if (result.length > 0) {
          var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          if (gr.get(searchConfigContentBlockId) && gr.canWrite()) {
              gr.setValue('active', true);
              gr.update();
              return true;
          }
      }
      return false;
  },


  getSubPageRoutes: function(appConfig, contentBlockId, appName) {

      var self = this;
      var routes = [];
      var clone_macroponent_routes = self.CONSTANTS.CLONE_ROUTES;
      var contentBlockGr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
      contentBlockGr.addQuery('app_config', appConfig);
      contentBlockGr.addQuery('parent_macroponent', contentBlockId);
      contentBlockGr.query();
      while (contentBlockGr.next()) {
          var cloneMacroponent = clone_macroponent_routes.some(function(route) {
              return route === contentBlockGr.getValue('route_type');
          });
          var screen_gr = new GlideRecord(self.UXF_TABLE.SCREEN);
          screen_gr.addQuery('screen_type', contentBlockGr.getValue('screen_type'));
          screen_gr.orderBy('sys_created_on');
          screen_gr.setLimit(1);
          screen_gr.query();
          while (screen_gr.next()) {
              var screen_id = screen_gr.getValue('sys_id');
          }
          screen_gr.get('screen_type', contentBlockGr.getValue('screen_type'));
          routes.push({
              MACROPONENT: screen_gr.getValue('macroponent'),
              SCREEN: screen_id,
              SCREEN_TYPE: contentBlockGr.getValue('screen_type'),
              NAME: appName + " - " + contentBlockGr.getValue('name'),
              ROUTE: contentBlockGr.getValue('route_type'),
              ROUTE_ID: contentBlockGr.getValue('sys_id'),
              VIEWPORT_ID: contentBlockGr.getValue('parent_macroponent_composition_element_id'),
              CLONE_MACROPONENT: cloneMacroponent
          });
      }
      return routes;

  },


  getClientScriptDetails: function(macroponentSysId) {

      var client_scripts = [];
      var client_script_gr = new GlideRecord('sys_ux_client_script');
      client_script_gr.addQuery('macroponent', macroponentSysId);
      client_script_gr.query();
      while (client_script_gr.next()) {
          client_scripts.push({
              sys_id: client_script_gr.getValue('sys_id').toString(),
              name: client_script_gr.getValue('name').toString(),
              script: client_script_gr.getValue('script').toString(),
              macroponent_id: macroponentSysId
          });
      }
      return client_scripts;

  },

  getAppConfigDetails: function(aceAppConfig) {
      var self = this;
      if (!this.CAN_READ) return {};
      var app_config_gr = new GlideRecord(self.TABLE.APP_CONFIG);
      if (app_config_gr.get(aceAppConfig)) {
          return {
              app_config: app_config_gr.getValue('app_config').toString(),
              context: JSON.parse(app_config_gr.getValue('context')),
              app_name: app_config_gr.getValue('name'),
              version: app_config_gr.getValue('version'),
              compatibility: app_config_gr.getValue('compatibility'),
              scope: app_config_gr.getValue('sys_scope')
          };
      }
  },

  updateAppConfigWithVersion: function(aceAppConfig, version, compatibleVersion) {
      var self = this;
      var app_config_gr = new GlideRecord(self.TABLE.APP_CONFIG);
      if (app_config_gr.get(aceAppConfig)) {
          app_config_gr.setValue('version', version);
          app_config_gr.setValue('compatibility', compatibleVersion);
      }
      app_config_gr.update();
  },

  syncNewSubPages: function(appName, aceBaseCbRoutes, appCbRoutes, appConfig) {

      var self = this;
      var content_block_id = appConfig.context.content_block_id;
      var routes_diff = aceBaseCbRoutes.filter(function(acebase_route) {
          return !appCbRoutes.some(function(route) {
              return acebase_route.ROUTE === route.ROUTE;
          });
      });

      routes_diff.forEach(function(route) {
          var cloneInfo = {
              'sys_id': route.ROUTE_ID,
              'fields': self.CONSTANTS.CLONE_FIELDS.APP_ROUTE
          };
          var payload = {
              'name': route.NAME,
              'app_config': appConfig.app_config,
              'parent_macroponent': content_block_id,
              'parent_macroponent_composition_element_id': route.VIEWPORT_ID

          };
          self.GLOBAL._grRecordCreator(self.UXF_TABLE.APP_ROUTE, payload, cloneInfo);
      });

      self.cloneSubPages(appName, appConfig.app_config, content_block_id, routes_diff);

  },

  syncClientScriptsWithBase: function(aceBaseCbSubpage, appCbSubpage) {

      var self = this;
      var ace_base_client_scripts = self.getClientScriptDetails(aceBaseCbSubpage.MACROPONENT);
      var app_client_scripts = self.getClientScriptDetails(appCbSubpage.MACROPONENT);

      app_client_scripts.forEach(function(app_client_script) {
          var ace_base_client_script = ace_base_client_scripts.filter(function(ace_base_client_script) {
              return ace_base_client_script.name === app_client_script.name;
          });
          if (ace_base_client_script.length > 0) {
              self.GLOBAL.updateRecord(self.UXF_TABLE.CLIENT_SCRIPT, app_client_script.sys_id, {
                  'script': ace_base_client_script[0].script
              });
          }
      });

      //Fetch client scripts that are part of a subpage in ace base content block and do not exist in app's content block su page
      var client_script_diff = ace_base_client_scripts.filter(function(ace_base_client_script) {
          return !app_client_scripts.some(function(app_client_script) {
              return ace_base_client_script.name === app_client_script.name;
          });
      });

      if (client_script_diff.length > 0) {
          var macroponent = {};
          var macroponent_gr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
          if (macroponent_gr.get(appCbSubpage.MACROPONENT)) {
              macroponent.composition = macroponent_gr.getValue('composition'),
                  macroponent.data = macroponent_gr.getValue('data'),
                  macroponent.state_properties = macroponent_gr.getValue('state_properties'),
                  macroponent.internal_event_mappings = macroponent_gr.getValue('internal_event_mappings');
          }
          client_script_diff.forEach(function(client_script) {
              var client_script_id = self.GLOBAL._grRecordCreator(self.UXF_TABLE.CLIENT_SCRIPT, {
                  'macroponent': appCbSubpage.MACROPONENT,
                  'name': client_script.name,
                  'script': client_script.script,
              });
              macroponent.composition = self._replaceAll(macroponent.composition, client_script.sys_id, client_script_id);
              macroponent.data = self._replaceAll(macroponent.data, client_script.sys_id, client_script_id);
              macroponent.state_properties = self._replaceAll(macroponent.state_properties, client_script.sys_id, client_script_id);
              macroponent.internal_event_mappings = self._replaceAll(macroponent.internal_event_mappings, client_script.sys_id, client_script_id);
          });
          self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, appCbSubpage.MACROPONENT, {
              'composition': macroponent.composition,
              'data': macroponent.data,
              'state_properties': macroponent.state_properties,
              'internal_event_mappings': macroponent.internal_event_mappings,
          });
      }

  },

  syncMacroponentWithBase: function(aceBaseCbSubpage, appCbSubpage, appConfig) {


      var self = this;
      var macroponent = {};
      var ace_base_macroponent_gr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
      if (ace_base_macroponent_gr.get(aceBaseCbSubpage.MACROPONENT)) {
          macroponent.composition = ace_base_macroponent_gr.getValue('composition'),
              macroponent.data = ace_base_macroponent_gr.getValue('data'),
              macroponent.layout = ace_base_macroponent_gr.getValue('layout'),
              macroponent.properties = ace_base_macroponent_gr.getValue('props'),
              macroponent.state_properties = ace_base_macroponent_gr.getValue('state_properties'),
              macroponent.internal_event_mappings = ace_base_macroponent_gr.getValue('internal_event_mappings'),
              macroponent.dispatched_events = ace_base_macroponent_gr.getValue('dispatched_events'),
              macroponent.handled_events = ace_base_macroponent_gr.getValue('handled_events');
      }

      macroponent.composition = self._replaceAll(macroponent.composition, self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT, appConfig.context.content_block_id);

      var ace_base_client_scripts = self.getClientScriptDetails(aceBaseCbSubpage.MACROPONENT);
      var app_client_scripts = self.getClientScriptDetails(appCbSubpage.MACROPONENT);

      app_client_scripts.forEach(function(app_client_script) {
          var client_script = ace_base_client_scripts.filter(function(ace_base_client_script) {
              return app_client_script.name === ace_base_client_script.name;
          });

          if (client_script.length > 0) {
              macroponent.composition = self._replaceAll(
                  macroponent.composition,
                  client_script[0].sys_id,
                  app_client_script.sys_id
              );
              macroponent.data = self._replaceAll(
                  macroponent.data,
                  client_script[0].sys_id,
                  app_client_script.sys_id
              );
              macroponent.state_properties = self._replaceAll(
                  macroponent.state_properties,
                  client_script[0].sys_id,
                  app_client_script.sys_id
              );
              macroponent.internal_event_mappings = self._replaceAll(
                  macroponent.internal_event_mappings,
                  client_script[0].sys_id,
                  app_client_script.sys_id
              );
          }
      });

      self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, appCbSubpage.MACROPONENT, {
          'composition': macroponent.composition,
          'data': macroponent.data,
          'layout': macroponent.layout,
          'props': macroponent.properties,
          'state_properties': macroponent.state_properties,
          'internal_event_mappings': macroponent.internal_event_mappings,
          'dispatched_events': macroponent.dispatched_events,
          'handled_events': macroponent.handled_events
      });

  },

  syncRouteWithBase: function(aceBaseCbSubpage, appCbSubpage, appConfig) {

      var self = this;
      var route = {};
      var ace_base_route_gr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
      if (ace_base_route_gr.get(aceBaseCbSubpage.ROUTE_ID)) {
          route.name = ace_base_route_gr.getValue('name'),
              route.fields = ace_base_route_gr.getValue('fields'),
              route.optional_parameters = ace_base_route_gr.getValue('optional_parameters'),
              route.parent_macroponent_composition_element_id = ace_base_route_gr.getValue('parent_macroponent_composition_element_id');
      }


      self.GLOBAL.updateRecord(self.UXF_TABLE.APP_ROUTE, appCbSubpage.ROUTE_ID, {
          'name': appConfig.app_name + "-" + route.name,
          'parent_macroponent': appConfig.context.content_block_id,
          'fields': route.fields,
          'optional_parameters': route.optional_parameters,
          'parent_macroponent_composition_element_id': route.parent_macroponent_composition_element_id,
          'screen_type': appCbSubpage.SCREEN_TYPE,
          'app_config': appConfig.app_config,

      });
  },

  syncScreenWithBase: function(aceBaseCbSubpage, appCbSubpage, appConfig) {

      var self = this;
      var screen = {};
      var ace_base_screen_gr = new GlideRecord(self.UXF_TABLE.SCREEN);
      if (ace_base_screen_gr.get(aceBaseCbSubpage.SCREEN)) {
          var content_block_routes_gr = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
          content_block_routes_gr.addQuery('app_config', self.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG);
          content_block_routes_gr.addQuery('parent_macroponent', self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT);
          content_block_routes_gr.addQuery('screen_type', ace_base_screen_gr.getValue('screen_type'));
          content_block_routes_gr.query();
          if (!content_block_routes_gr.next())
              return;

          screen.name = appConfig.app_name + ' - ' + content_block_routes_gr.getValue('name');
          screen.macroponent_config = ace_base_screen_gr.getValue('macroponent_config');
          screen.event_mappings = ace_base_screen_gr.getValue('event_mappings');
          screen.page_definition = (appCbSubpage.CLONE_MACROPONENT) ? appCbSubpage.MACROPONENT : aceBaseCbSubpage.MACROPONENT;
      }

      self.GLOBAL.updateRecord(self.UXF_TABLE.SCREEN, appCbSubpage.SCREEN, {
          'name': screen.name,
          'screen_type': appCbSubpage.SCREEN_TYPE,
          'macroponent': screen.page_definition,
          'parent_macroponent': appConfig.context.content_block_id,
          'macroponent_config': screen.macroponent_config,
          'event_mappings': screen.event_mappings,
          'app_config': appConfig.app_config

      });
  },

  hardSyncWithBase: function(aceAppConfig) {

      var self = this;
      var app_config = self.getAppConfigDetails(aceAppConfig);
      var app_name = app_config.app_name;
      var app_content_block_id = app_config.context.content_block_id;

      var ace_base_cb_subpages = self.getSubPageRoutes(self.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG, self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT, app_name);
      var app_cb_subpages = self.getSubPageRoutes(app_config.app_config, app_content_block_id, app_name);

      self.syncNewSubPages(app_name, ace_base_cb_subpages, app_cb_subpages, app_config);

      app_cb_subpages.forEach(function(subpage) {
          var ace_base_cb_subpage = ace_base_cb_subpages.filter(function(acebase_subpage) {
              return acebase_subpage.ROUTE === subpage.ROUTE;
          });

          if (ace_base_cb_subpage.length > 0) {
              if (subpage.CLONE_MACROPONENT) {
                  self.syncMacroponentWithBase(ace_base_cb_subpage[0], subpage, app_config);
                  self.syncClientScriptsWithBase(ace_base_cb_subpage[0], subpage);
              }
              self.syncRouteWithBase(ace_base_cb_subpage[0], subpage, app_config);
              self.syncScreenWithBase(ace_base_cb_subpage[0], subpage, app_config);

          }
      });

      self.fixMissingSubPages(ace_base_cb_subpages, app_cb_subpages, app_content_block_id, app_config.app_config);

      //sync content block macroponent and client scripts
      self.syncMacroponentWithBase({
          MACROPONENT: self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT
      }, {
          MACROPONENT: app_content_block_id
      }, app_config);
      self.syncClientScriptsWithBase({
          MACROPONENT: self.CONSTANTS.BASE_UXF_RECORDS.CONTENT_BLOCK.MACROPONENT
      }, {
          MACROPONENT: app_content_block_id
      });
  },

  hasReadAccess: function() {
      return gs.hasRole('sn_ace.ace_user');
  },

  getRoutesForAcePage: function(aceAppConfig) {
      var routeSysIds = [];
      var sn_ace_page_gr = new GlideRecord(this.TABLE.PAGE);
      sn_ace_page_gr.addQuery('ace_app', aceAppConfig);
      sn_ace_page_gr.query();
      while (sn_ace_page_gr.next()) {
          routeSysIds.push(sn_ace_page_gr.getValue('route'));
      }
      return routeSysIds;
  },
  getMacroponentsFromRoute: function(routes) {
      var self = this;
      var macroponentSysIds = [];
      routes.forEach(function(route) {
          var gr_route = new GlideRecord(self.UXF_TABLE.APP_ROUTE);
          gr_route.get(route);
          gr_route.query();
          if (gr_route.next()) {
              var gr_screen = new GlideRecord(self.UXF_TABLE.SCREEN);
              gr_screen.addQuery('screen_type', gr_route.screen_type);
              gr_screen.query();
              if (gr_screen.next()) {
                  macroponentSysIds.push(gr_screen.macroponent.toString());
              }
          }
      })
      return macroponentSysIds;
  },
  appendAceContentTreeProperty: function(composition, aceRenderer) {
      var self = this;
      if (!composition.length) {
          return;
      }
      composition.forEach(function(child) {
          if (child.definition && child.definition.id == aceRenderer && child.propertyValues.payload) {
              child.propertyValues['contentTree'] = child.propertyValues.payload;
              delete child.propertyValues.payload;
          }
          if (child.overrides && child.overrides.composition) {
              self.appendAceContentTreeProperty(child.overrides.composition, aceRenderer);
          }
      });
  },
  updateMacroponentComposition: function(macroponents, contentBlockId, aceRendererId) {
      var self = this;
      macroponents.forEach(function(macroponentId) {
          var macroGr = new GlideRecord(self.UXF_TABLE.MACROPONENT);
          if (macroGr.get(macroponentId)) {
              var composition = macroGr.getValue('composition');
              var newComposition = JSON.parse((self._replaceAll(composition, contentBlockId, aceRendererId)));
              self.appendAceContentTreeProperty(newComposition, aceRendererId);
              self.GLOBAL.updateRecord(self.UXF_TABLE.MACROPONENT, macroponentId, {
                  composition: JSON.stringify(newComposition)
              });
          }
      });
  },

  migrateToAceRenderer: function(aceAppConfig) {
      var self = this;
      var app_config = self.getAppConfigDetails(aceAppConfig);
      var migrationStatus = gs.getMessage("Experience {0} migration to ACE v2 initiated", [app_config.app_name]);
      if (gs.getCurrentApplicationId() != app_config.scope) {
          migrationStatus = gs.getMessage("Cross Scope Alert! Failed to migrate experience {0} to ACE v2", [app_config.app_name]);;
          return migrationStatus;
      }
      if (app_config.version != 'v2' && app_config.context && app_config.context.content_block_id) {
          var app_content_block_id = app_config.context.content_block_id;
          var routeSysIds = self.getRoutesForAcePage(aceAppConfig);
          var macroponents = self.getMacroponentsFromRoute(routeSysIds);
          try {
              self.updateMacroponentComposition(macroponents, app_content_block_id, self.CONSTANTS.ACE_CONTENT_MANAGER_ID);
              self.updateAppConfigWithVersion(aceAppConfig, 'v2', 'v1');
              migrationStatus = gs.getMessage("Experience {0} has been migrated to ACE v2 successfully", [app_config.app_name]);
          } catch (e) {
              migrationStatus = gs.getMessage("Failed to migrate experience {0} to ACE v2", [app_config.app_name]);
              gs.error("Failed to migrate experience to ACE v2" + e.toString());
          }
      } else {
          migrationStatus = gs.getMessage("Experience {0} cannot be migrated to ACE v2", [app_config.app_name]);
      }
      return migrationStatus;
  },
  _getContentBlockMetaData: function(pageId, defBlockId, instanceBlockId, reloadCache, isClonedBlock) {
      try {
          var blockParameters = [];
          this.gr_counter += 1;
          var encodedQuery = "";
          var contentBlocks = [];
          var appConfig = ""; //Default App Config of ACE
          var screenType = "";
          var aceAppConfigGr;
          var pageMacroponentId = "";
          var experienceMetadata = {};

          if (pageId) {
              var pageGr = new GlideRecord('sn_ace_page');
              pageGr.get(pageId);
              if (pageGr.isValidRecord()) {
                  appConfig = pageGr.ace_app.app_config.toString();
                  screenType = pageGr.route.screen_type.toString();
              }
              experienceMetadata = this.getExperienceMetadata(reloadCache, appConfig);

              var screenGr = new GlideRecord("sys_ux_screen");
              screenGr.addQuery("screen_type", screenType);
              screenGr.query();
              if (screenGr.next()) {
                  pageMacroponentId = screenGr.macroponent.toString();
              }

              encodedQuery += "document_table=sn_ace_page^document_id=" + pageId;
              contentBlocks = this._getContentBlockMetaDataForBlockId(pageId, encodedQuery, experienceMetadata);
          } else if (defBlockId) {
              var blockGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);

              if (instanceBlockId) {
                  blockGr.addQuery("sys_id", "IN", [defBlockId, instanceBlockId].join(","));
                  blockGr.query();

                  var defContentBlocks = [],
                      instanceContentBlocks = [];
                  while (blockGr.next()) {
                      if (blockGr.getUniqueValue() == defBlockId) {
                          aceAppConfigGr = new GlideRecord(this.TABLE.APP_CONFIG);
                          aceAppConfigGr.get(blockGr.document_id.toString());
                          if (aceAppConfigGr.isValidRecord()) {
                              appConfig = aceAppConfigGr.app_config.toString();
                          }
                          if (blockGr.variable["block_variables"]) {
                              blockParameters = JSON.parse(blockGr.variable["block_variables"]);
                          }
                          experienceMetadata = this.getExperienceMetadata(reloadCache, appConfig);
                          encodedQuery = "document_table=sn_ace_content_block^document_id=" + defBlockId;
                          defContentBlocks = isClonedBlock ? [] : this._getContentBlockMetaDataForBlockId(defBlockId, encodedQuery, experienceMetadata);
                      } else {
                          var parentPageId = blockGr.getValue('document_id');
                          var parentPageGr = new GlideRecord(this.TABLE.PAGE);
                          if (parentPageGr.get(parentPageId)) {
                              appConfig = parentPageGr.ace_app.app_config.toString();
                          }
                          experienceMetadata = this.getExperienceMetadata(reloadCache, appConfig);
                          encodedQuery = "document_table=sn_ace_content_block^document_id=" + instanceBlockId;
                          instanceContentBlocks = this._getContentBlockMetaDataForBlockId(instanceBlockId, encodedQuery, experienceMetadata);
                      }
                  }
                  if (isClonedBlock) {
                      contentBlocks = instanceContentBlocks;
                  } else {
                      this._mergeReusableBlocks(defContentBlocks, instanceContentBlocks);
                      contentBlocks = defContentBlocks;
                  }
              } else {
                  blockGr.get(defBlockId);
                  if (blockGr.isValidRecord()) {
                      aceAppConfigGr = new GlideRecord(this.TABLE.APP_CONFIG);
                      aceAppConfigGr.get(blockGr.document_id.toString());
                      if (aceAppConfigGr.isValidRecord()) {
                          appConfig = aceAppConfigGr.app_config.toString();
                      }
                      if (blockGr.variable["block_variables"]) {
                          blockParameters = JSON.parse(blockGr.variable["block_variables"]);
                      }
                  }
                  experienceMetadata = this.getExperienceMetadata(reloadCache, appConfig);
                  encodedQuery += "document_table=sn_ace_content_block^document_id=" + defBlockId;
                  contentBlocks = this._getContentBlockMetaDataForBlockId(defBlockId, encodedQuery, experienceMetadata);
              }
          }

          return {
              contentBlocks: contentBlocks,
              blockParameters: blockParameters,
              pageMacroponentId: pageMacroponentId
          };
      } catch (e) {
          gs.error('ACEAppBuilderUtil: Error while fetching content blocks: ' + e.toString());
          return {
              contentBlocks: []
          };
      }
  },
  _getContentBlockMetaDataForBlockId: function(artifactId, encodedQuery, experienceMetadata) {
      var contentBlocksMap = {
          '_root': []
      };
      //Step 1,2: get all content blocks and build the parent-child relationship map of their sys_ids 			
      var gr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
      gr.addEncodedQuery(encodedQuery);
      gr.orderBy('order');
      gr.query();
      var blockIdsPartOfQuery = [];
      while (gr.next()) {
          var blockId = gr.getUniqueValue();
          blockIdsPartOfQuery.push(blockId);
          var blockParentId = gr.getValue('parent');
          var parentBlockId = (artifactId != blockId) ? blockParentId : null;

          if (!parentBlockId) {
              contentBlocksMap._root.push(blockId);
          } else {
              contentBlocksMap[parentBlockId] = contentBlocksMap[parentBlockId] || [];
              contentBlocksMap[parentBlockId].push(blockId);
          }
      }
      var parentBlockIds = Object.keys(contentBlocksMap);
      parentBlockIds.forEach(function(parentId) {
          if (parentId != "_root" && blockIdsPartOfQuery.indexOf(parentId) == -1) {
              //parent block is not part of the instance of reusable block's content slot
              if (contentBlocksMap[parentId] && contentBlocksMap[parentId].length > 0) {
                  contentBlocksMap._root.push(contentBlocksMap[parentId][0]);
                  delete contentBlocksMap[parentId];
              }
          }
      });

      var contentBlocksLevel = [];
      //Step 3: build a 2D array of the step-level hierarchy  
      this._buildContentBlockLevels(contentBlocksMap, contentBlocksMap._root, contentBlocksLevel, 0);
      //Step 4: build the content block contract  
      return this._buildContentBlockTreeFromLevels(contentBlocksMap, contentBlocksLevel, {}, experienceMetadata, true);
  },
  getDefaultAppConfigId: function() {
      var gr = new GlideRecord('sn_ace_app_config');
      gr.get(this.ACE_DEFAULT_APP_CONFIG);
      return gr.getValue('app_config');
  },

  getAppConfigId: function(inputParam) {
      var blockId = inputParam.blockId;
      var pageId = inputParam.pageId;
      var result = "";
      if (pageId) {
          var gr = new GlideRecord('sn_ace_page');
          result = gr.get(pageId) ? gr.ace_app.app_config + '' : this.getDefaultAppConfigId();
      } else if (blockId) {
          var blockGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          blockGr.get(blockId);
          if (blockGr.isValidRecord()) {
              var aceAppConfigGr = new GlideRecord(this.TABLE.APP_CONFIG);
              aceAppConfigGr.get(blockGr.document_id.toString());
              if (aceAppConfigGr.isValidRecord()) {
                  result = aceAppConfigGr.app_config.toString();
              }
          }
      }
      return result;
  },

  aceEvent: function() {
      return {
          name: "ACE_ACTION_PERFORMED",
          label: "ACE EVENT",
          properties: [{
                  "name": "eventName",
                  "label": "Event Name",
                  "fieldType": "string",
                  "typeMetadata": {
                      "schema": {
                          "type": "string"
                      }
                  },
                  "valueType": "string"
              },
              {
                  "name": "eventPayload",
                  "label": "Event Payload",
                  "fieldType": "json"
              }
          ]
      };
  },

  getVariableChoices: function() {
      var choiceGr = new GlideRecord('sys_choice');
      choiceGr.addEncodedQuery('nameSTARTSWITHvar__m_sn_ace_variable_');
      choiceGr.query();

      var choices = {};
      while (choiceGr.next()) {
          var choice = {};
          choice.language = choiceGr.getValue('language');
          choice.label = choiceGr.getValue('label');
          choice.value = choiceGr.getValue('value');
          choice.id = choiceGr.getValue('value');
          choice.sequence = choiceGr.getValue('sequence');
          choice.element = choiceGr.getValue('element');
          var tableName = choiceGr.getValue('name');
          if (!choices.hasOwnProperty(tableName))
              choices[tableName] = [];
          choices[tableName].push(choice);
      }
      return choices;
  },

  getVariables: function() {
      var choices = this.getVariableChoices();
      var variableGr = new GlideRecord(this.TABLE.VARIABLE);
      variableGr.orderBy('order');
      variableGr.query();
      var varObj = {};
      while (variableGr.next()) {
          var _variable = {};
          _variable.label = variableGr.getValue('label');
          _variable.name = variableGr.getValue('element');
          _variable.fieldType = variableGr.internal_type.name + '';
          var defaultValue = variableGr.getValue('default_value');
          _variable.defaultValue = defaultValue == null ? "" : defaultValue;

          var fieldChoices = choices[variableGr.getValue('name')];
          if (fieldChoices && fieldChoices.length) {
              var newChoices = fieldChoices.filter(function(choice) {
                  return variableGr.getValue('element') == choice.element;
              });
              if (newChoices.length) {
                  _variable.typeMetadata = {};
                  var enumArr = [];
                  _variable.typeMetadata.choices = newChoices;
                  newChoices.forEach(function(choice) {
                      enumArr.push(choice.value);
                  });
                  _variable.typeMetadata.schema = {
                      "enum": enumArr,
                      "type": "string"
                  };
              }
          }

          var modelId = variableGr.getValue('model');
          if (varObj.hasOwnProperty(modelId))
              varObj[modelId].push(_variable);
          else
              varObj[modelId] = [_variable];
      }
      return varObj;
  },

  getContentBlockProperties: function(inputParam) {
      var configId = this.getAppConfigId(inputParam);
      var varObj = this.getVariables();
      var cbTypes = {};
      var contentTypes = this.GLOBAL.getCachedContentTypeMetaData();
      var cbTypeGr = new GlideRecord(this.TABLE.TYPE);
      cbTypeGr.addEncodedQuery('route!=NULL');
      cbTypeGr.orderBy('name');
      cbTypeGr.query();
      while (cbTypeGr.next()) {
          var cbType = {};
          cbType.variables = varObj[cbTypeGr.getUniqueValue()];
          cbType.typeId = cbTypeGr.getUniqueValue();
          cbType.name = cbTypeGr.getValue('name');
          var route = cbTypeGr.getValue('route');
          cbType.nested = cbTypeGr.getValue('nested') == 1 ? true : false;
          cbType.macroponent = "";
          if (configId && contentTypes[configId] && contentTypes[configId][route]) {
              var macroponent = contentTypes[configId][route]['macroponent'];
              cbType.macroponent = !macroponent ? "" : macroponent;
          } else {
              // Fallback to ACE Base Experience if Macroponent ID is not available
              // for a route in an app config / experience.
              var baseExpConfigId = this.CONSTANTS.BASE_UXF_RECORDS.ACE_BASE_EXPERIENCE_APP_CONFIG;
              if (contentTypes[baseExpConfigId] && contentTypes[baseExpConfigId][route]) {
                  var macroponentId = contentTypes[baseExpConfigId][route]['macroponent'];
                  cbType.macroponent = !macroponentId ? "" : macroponentId;
              }
          }
          var allowedBlocks = cbTypeGr.getValue('allowed_blocks');
          cbType.allowed_blocks = !allowedBlocks ? "" : allowedBlocks;
          cbType.category = cbTypeGr.getValue('category');
          cbType.events = [];
          cbType.icon = cbTypeGr.getValue('icon');
          cbType.events.push(this.aceEvent());
          cbType.sysId = cbTypeGr.getUniqueValue();
          cbType.slots = [];
          var slots = cbTypeGr.getValue('slots') || "";
          if (slots.length) {
              cbType.slots = slots.split(',');
          }
          if (cbTypes.hasOwnProperty(route)) {
              var sysId = cbTypeGr.getUniqueValue();
              cbTypes[route + '||' + sysId] = cbType;
          } else {
              cbTypes[route] = cbType;
          }
      }
      return cbTypes;
  },

  tryParse: function(obj) {
      var resp = {};
      try {
          resp.value = JSON.parse(obj);
          resp.status = true;
      } catch (e) {
          resp.status = false;
          resp.message = "Error in parsing the object"
      }
      return resp;
  },

  uxEvents: function() {
      var eventGr = new GlideRecord('sys_ux_event');
      eventGr.query();
      var events = {};
      while (eventGr.next()) {
          var event = {};
          event.label = eventGr.getValue('label');
          event.name = eventGr.getValue('event_name');
          var eventPropValues = eventGr.getValue('props') ? eventGr.getValue('props').replace(/\n/g, " ") : "[]";
          try {
              event.properties = JSON.parse(eventPropValues);
          } catch (e) {
              event.properties = {};
              gs.error("ACEAppBuilderUtilV2: Unable to parse event prop values, exception: " + e);
          }
          events[eventGr.getUniqueValue()] = event;
      }
      return events;
  },

  getMacroponentIcons: function() {
      var icons = {};
      var toolboxCompGr = new GlideRecord('sys_uib_toolbox_component');
      toolboxCompGr.query();
      while (toolboxCompGr.next()) {
          icons[toolboxCompGr.getValue('macroponent')] = toolboxCompGr.getValue('icon');
      }
      return icons;
  },

  getMacroponentProperties: function() {
      var events = this.uxEvents();
      var icons = this.getMacroponentIcons();
      var compGr = new GlideRecord('sys_ux_macroponent');
      compGr.addEncodedQuery('root_component!=NULL^category=component^ORcategory=primitives^ORcategory=shared^ORcategory=viewport');
      compGr.orderBy('name');
      compGr.query();
      var components = {};
      var _this = this;
      while (compGr.next()) {
          var translatableStringMap = {};
          var comp = {};
          var propValues = compGr.getValue('props') ? compGr.getValue('props').replace(/\n/g, " ") : "[]";
          var props = JSON.parse(propValues);
          if (props && props.length) {
              props.forEach(function(prop, index) {
                  if (prop.fieldType == "json" && prop.hasOwnProperty('defaultValue') && prop.defaultValue) {
                      var defaultValue = props[index]['defaultValue'];
                      var resp = _this.tryParse(defaultValue);
                      if (resp.status)
                          props[index]['defaultValue'] = resp.value;
                  } else if (prop.fieldType == "choice") {
                      var choices = prop.typeMetadata ? prop.typeMetadata.choices : [];
                      choices && choices.map(function(choice) {
                          choice.id = choice.value;
                      });
                  }
                  if (prop.typeMetadata && prop.fieldType == 'json') {
                      var translatableProp = _this.getTranslatableProp(prop.typeMetadata, 'translatable', '');
                      if (translatableProp && translatableProp.length > 0) {
                          translatableStringMap[prop.name] = translatableProp;
                      }

                  } else if (prop && prop.typeMetadata && prop.typeMetadata.translatable) {
                      translatableStringMap[prop.name] = prop.defaultValue;
                  }
              });
          }
          comp.translatableStringMap = translatableStringMap;
          comp.componentName = compGr.getValue('name');
          comp.componentTag = compGr.getDisplayValue('root_component');
          comp.properties = props;
          comp.root_element_metadata = JSON.parse(compGr.getValue('root_component_definition'));
          comp.events = [];
          comp.icon = icons[compGr.getUniqueValue()];
          var dispatchedEvents = compGr.getValue('dispatched_events');
          if (dispatchedEvents) {
              eventList = dispatchedEvents.split(',');
              eventList.forEach(function(eventId) {
                  comp.events.push(events[eventId]);
              });
          }
          var rootElementMetadata = compGr.getValue('root_component_definition');
          if (rootElementMetadata && rootElementMetadata.length) {
              rootElementMetadata = JSON.parse(rootElementMetadata);
              comp.slots = rootElementMetadata.availableSlots || [];
          }
          components[comp.componentTag] = comp;
      }
      return components;
  },

  getCachedMetadataProperties: function(inputParam, reload) {
      if (reload)
          this.GLOBAL.flushCachedMacroponentCBProperties();

      var aceMetaData = this.GLOBAL.getCachedMacroponentCBProperties();
      if (aceMetaData)
          return aceMetaData;

      var metadata = {};
      metadata.contentTypes = this.getContentBlockProperties(inputParam);
      metadata.componentProperties = this.getMacroponentProperties();
      this.GLOBAL.putCachedMacroponentCBProperties(metadata);
      return metadata;
  },

  /**
   * Util method to process visibility and post scripts the content blocks within the given content tree.
   * 
   * @param {Array} contentTree - The content tree to process.
   * @param {GlideRecord} contentBlockGr - A gliderecord from sn-ace-content-block table to evaluate the scripts.
   * @param {Object} cbScriptVariables
   * 
   * @returns {Array} Returns the content blocks after processing visibility and post scripts.
   */
  processCachedContentBlocksUtil: function(contentTree, contentBlockGr, cbScriptVariables) {
      var _self = this;
      var outputBlocks = [];
      contentTree.forEach(function(contentBlock) {
          /**
           * Set the contentBlockGr scope and package to the contentBlock's scope and package.
           * This ensures that there wont be any scope related issues in evaluating scripts.
           */
          contentBlockGr.sys_scope = contentBlock.scope;
          contentBlockGr.sys_package = contentBlock.package;

          /**
           * Set the contentBlockGr's visibility to the contentBlock's visibility.
           */
          var visibilityScript = contentBlock.visibility;
          contentBlockGr.visibility = visibilityScript;

          /**
           * Only process the curret block if the block's visibility script evaluates to true.
           */
          if (_self._visibilityEvaluator(contentBlockGr, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams)) {
              // First process the child blocks of the current block.
              if (contentBlock.children && contentBlock.children.length) {
                  var childBlocks = _self.processCachedContentBlocksUtil(contentBlock.children, contentBlockGr, cbScriptVariables);
                  contentBlock.children = childBlocks;
              }

              _self._evaluateCBScriptVariables(contentBlockGr, contentBlock, cbScriptVariables);

              if (contentBlock.post_script && contentBlock.post_script.length) {
                  contentBlockGr.post_script = contentBlock.post_script;
                  _self._evaluatePostScript(contentBlockGr, contentBlock, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams);
              }

              outputBlocks.push(contentBlock);
          }
      });
      return outputBlocks;
  },

  /**
   * As part of ACE performance improvement, we are storing the page bundle in the sn_ace_page record.
   * This bundle will contain the structure of the content blocks with all the required metadata to render the ACE page.
   * 
   * @param {Array} contentTree - The content tree to process.
   * @param {Object} cbScriptVariables
   * 
   * @returns {Array} Content blocks with evaluated post script and visibility script.
   */
  processCachedContentBlocks: function(contentTree, cbScriptVariables) {
      /**
       * Evaluation of content block visibility and post scripts requires a real GlideRecord object that is
       * in the DB. We can reuse just one content block record and change its visibility and post scripts
       * to process all the blocks in the content tree.
       * Here we check if the content tree is not empty and pick the first block from the content tree.
       */
      if (contentTree && contentTree.length) {
          var contentBlockGrId = contentTree[0].block_id;
          var contentBlockGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          contentBlockGr.get(contentBlockGrId);

          return this.processCachedContentBlocksUtil(contentTree, contentBlockGr, cbScriptVariables);
      }

      return [];
  },

  /**
   * Utility method to refresh a content block. Refreshing a block executes the given block's scripts again
   * to re-fetch data.
   * 
   * @param {Object} block - Data of the block that needs to be refreshed.
   * @param {Object} cbScriptVariables - An object containing current pageParams, blockcontext.
   * @param {boolean} shouldRefreshChildBlocks - A flag used to decide whether the refresh operation should be executed
   * for the given block's children.
   * @param {GlideRecord} contentBlockGr
   */
  refreshContentBlock: function(block, cbScriptVariables, shouldRefreshChildBlocks, contentBlockGr) {
      var _self = this;
      var refreshedBlock = JSON.parse(JSON.stringify(block));
      if (refreshedBlock) {
          if (!contentBlockGr) {
              /**
               * One time operation to get a persisted content block record.
               * This single record can be used to evaluate scripts for all the nested content blocks.
               * For subsequent recursive calls of refreshContentBlock method, the contentBlockGr value will be non-null.
               */
              contentBlockGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
              contentBlockGr.get(refreshedBlock.block_id);
          }

          /**
           * Only refresh the child content blocks if shouldRefreshChildBlocks is true. Bottom-up approach.
           */
          if (shouldRefreshChildBlocks && refreshedBlock.children && refreshedBlock.children.length) {
              refreshedBlock.children = refreshedBlock.children.filter(function(childBlock) {
                  var refreshedChildBlock = refreshContentBlock(childBlock, cbScriptVariables, shouldRefreshChildBlocks, contentBlockGr);
                  if (refreshedChildBlock) {
                      return refreshedChildBlock;
                  }
              });
          }

          /**
           * Set the contentBlockGr scope and package to the contentBlock's scope and package.
           * This ensures that there wont be any scope related issues in evaluating scripts.
           */
          contentBlockGr.sys_scope = refreshedBlock.scope;
          contentBlockGr.sys_package = refreshedBlock.package;

          /**
           * Set the contentBlockGr's visibility to the contentBlock's visibility.
           */
          contentBlockGr.visibility = refreshedBlock.visibility;

          /**
           * Only process the current block if the visibility script evaluates to true.
           */
          if (_self._visibilityEvaluator(contentBlockGr, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams)) {
              _self._evaluateCBScriptVariables(contentBlockGr, refreshedBlock, cbScriptVariables);

              // Evaluate post script first.
              if (refreshedBlock.post_script && refreshedBlock.post_script.length) {
                  contentBlockGr.post_script = refreshedBlock.post_script;
                  _self._evaluatePostScript(contentBlockGr, refreshedBlock, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams);
              }

              // Evaluate lazy script.
              if (refreshedBlock.lazy_script && refreshedBlock.lazy_script.length) {
                  contentBlockGr.lazy_script = refreshedBlock.lazy_script;
                  _self._evaluateLazyScript(contentBlockGr, refreshedBlock, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams);
              }

              return refreshedBlock;
          }
      }
      return;
  },
  getCachedProperties: function(reload) {
      if (reload) {
          this.GLOBAL.flushCachedMacroponentCBProperties();
      }
      var cachedProperties = this.GLOBAL.getCachedMacroponentCBProperties();
      if (cachedProperties) {
          return cachedProperties;
      } else {
          var inputParam = {};
          return this.getCachedMetadataProperties(inputParam);

      }
  },
  /**
   * Utility method to find a key in nested json.
   *
   * @param {Object} obj - json object.
   * @param {string} key -  key that will be search in object .
   */
  getTranslatableProp: function(obj, key, prev) {
      if (!prev) {
          prev = '';
      }
      var result = [];
      for (var prop in obj) {
          var path = prev + (prev ? '.' : '') + prop;
          if (prop == key) {
              result.push(path.replace('.' + key, '').split(".").pop());
          } else if (typeof obj[prop] == 'object') {
              var resp = this.getTranslatableProp(obj[prop], key, path);
              if (resp.length > 0) {
                  Array.prototype.push.apply(result, resp);

              }

          }
      }
      return result
  },

  evaluateDataBlocks: function(dataBlocks, cbScriptVariables) {
      var executedDataBlockOutput = {};
      if (dataBlocks && dataBlocks.length) {
          var dataBlockIds = dataBlocks.map(function(block) {
              return block.block_id;
          });
          var blockIdToBlockMap = {};
          dataBlocks.forEach(function(dataBlock) {
              blockIdToBlockMap[dataBlock.block_id] = dataBlock;
          });
          var dataBlockGr = new GlideRecord(this.TABLE.CONTENT_BLOCK);
          dataBlockGr.addEncodedQuery('sys_idIN', dataBlockIds.join(','));
          dataBlockGr.query();
          while (dataBlockGr.next()) {
              var evaluationOutput = this._evaluateLazyScript(dataBlockGr, blockIdToBlockMap[dataBlockGr.getUniqueValue()], cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams);
              executedDataBlockOutput[dataBlockGr.getUniqueValue()] = evaluationOutput;
          }
      }
      return executedDataBlockOutput;
  },

  _evaluateDataBlock: function(dataBlockGr, dataBlockObject, cbScriptVariables) {
      var dataBlockEvaluationOutput;
      if (dataBlockObject && dataBlockObject.props_details) {
          var evaluationSource = dataBlockObject.props_details.evaluation_source;
          if (evaluationSource == 'script') {
              dataBlockEvaluationOutput = this._evaluateLazyScript(dataBlockGr, dataBlockObject, cbScriptVariables.pageParams, cbScriptVariables.blockcontext, cbScriptVariables.dataParams);
          } else if (evaluationSource == 'flow_action') {
              // TODO: Implement flow action way of evaluating data blocks.
          }
      }
      return dataBlockEvaluationOutput;
  },

  eagerlyEvaluateDataBlocks: function(contentBlocks, cbScriptVariables) {
      var executedDataBlockOutput = {};
      var _self = this;
      if (contentBlocks && contentBlocks.length) {
          var dataBlocks = [];
          var dataBlockIds = [];
          contentBlocks.forEach(function(contentBlock) {
              if (contentBlock.type && contentBlock.type.id == _self.DATA_BLOCK_CONTENT_TYPE && contentBlock.props_details &&
                  contentBlock.props_details.evaluation_mode == 'eager') {
                  dataBlocks.push(contentBlock);
                  dataBlockIds.push(contentBlock.block_id);
              }
          });

          var blockIdToBlockMap = {};
          dataBlocks.forEach(function(dataBlock) {
              blockIdToBlockMap[dataBlock.block_id] = dataBlock;
          });

          var dataBlockGr = new GlideRecord('sn_ace_content_block');
          dataBlockGr.addEncodedQuery('sys_idIN' + dataBlockIds.join(','));
          dataBlockGr.query();
          while (dataBlockGr.next()) {
              var evaluationOutput = this._evaluateDataBlock(dataBlockGr, blockIdToBlockMap[dataBlockGr.getUniqueValue()], cbScriptVariables);
              executedDataBlockOutput[dataBlockGr.getUniqueValue()] = evaluationOutput;
          }
      }

      contentBlocks.forEach(function(contentBlock) {
          if (executedDataBlockOutput[contentBlock.block_id]) {
              contentBlock.props_details.data_block_output = executedDataBlockOutput[contentBlock.block_id];
          }
      });

      return contentBlocks;
  },

  addDataBlockParamsToScriptVariables: function(contentBlocks, scriptVariables) {
      var _self = this;
      var modifiedScriptVariables = scriptVariables;
      var dataParams = {};
      contentBlocks.forEach(function(contentBlock) {
          if (contentBlock.type.id === _self.DATA_BLOCK_CONTENT_TYPE && contentBlock.elementId) {
              dataParams[contentBlock.elementId] = contentBlock.props_details.data_block_output;
          };
      });
      modifiedScriptVariables['dataParams'] = dataParams;
      return modifiedScriptVariables;
  },
  removeTranslationPrefix: function(prop) {
      prop = prop.includes(this.TRANSLATED_TEXT_PREFIX) ? prop.replaceAll(this.TRANSLATED_TEXT_PREFIX, "") : prop;
      return prop;
  },

  type: 'ACEAppBuilderUtilV2'
};

Sys ID

290c6147c306211094d388c7c840dd4e

Offical Documentation

Official Docs: