<commit_before>/**
 * Copyright (C) 2013 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.fb

import org.orbeon.oxf.fr.FormRunner._
import org.orbeon.oxf.util.{IndentedLogger, Logging, NetUtils, UserAgent}
import org.orbeon.oxf.xforms.XFormsConstants.COMPONENT_SEPARATOR
import org.orbeon.oxf.xforms.XFormsProperties
import org.orbeon.oxf.xforms.action.XFormsAPI._
import org.orbeon.oxf.xforms.model.{XFormsInstance, XFormsModel}
import org.orbeon.oxf.xml.TransformerUtils
import org.orbeon.saxon.function.Property
import org.orbeon.saxon.om.{DocumentInfo, NodeInfo}
import org.orbeon.scaxon.Implicits._
import org.orbeon.scaxon.SimplePath._
import spray.json.DefaultJsonProtocol._
import spray.json._

import scala.collection.immutable

trait BaseOps extends Logging {

  implicit def logger: IndentedLogger = inScopeContainingDocument.getIndentedLogger("form-builder")

  // Minimal version of IE supported
  val MinimalIEVersion = 11

  // Id of the xxf:dynamic control holding the edited form
  val DynamicControlId = "fb"

  // Find the form document being edited
  def getFormDoc: DocumentInfo =
    topLevelModel("fr-form-model").get.unsafeGetVariableAsNodeInfo("model").getDocumentRoot

  // All xbl:binding elements available
  def componentBindings: Seq[NodeInfo] =
    asScalaSeq(topLevelModel("fr-form-model").get.getVariable("component-bindings")).asInstanceOf[Seq[NodeInfo]]

  // Return fb-form-instance
  def fbFormInstance: XFormsInstance =
    topLevelInstance("fr-form-model", "fb-form-instance").get

  // Find the top-level form model of the form being edited
  def getFormModel: XFormsModel =
    inScopeContainingDocument.getObjectByEffectiveId(DynamicControlId + COMPONENT_SEPARATOR + "fr-form-model")
      .asInstanceOf[XFormsModel] ensuring (_ ne null, "did not find fb$fr-form-model")

  def formResourcesRoot: NodeInfo =
    topLevelModel("fr-form-model").get.unsafeGetVariableAsNodeInfo("resources")

  def templateRoot(inDoc: NodeInfo, repeatName: String): Option[NodeInfo] =
    inlineInstanceRootElem(inDoc, templateId(repeatName))

  // Find the next available id for a given token
  def nextId(inDoc: NodeInfo, token: String): String =
    nextIds(inDoc, token, 1).head

  // Find a series of next available ids for a given token
  // Return ids of the form "foo-123-foo", where "foo" is the token
  def nextIds(inDoc: NodeInfo, token: String, count: Int): immutable.IndexedSeq[String] = {

    val prefix = token + "-"
    val suffix = "-" + token

    def findAllIds = {
      val root = inDoc.getDocumentRoot

      // Use id index when possible, otherwise use plain XPath
      val fbInstance = fbFormInstance

      def elementIdsFromIndex = fbInstance.idsIterator filter (_.endsWith(suffix))
      def elementIdsFromXPath = (root descendant *).ids filter (_.endsWith(suffix)) iterator

      def canUseIndex = fbInstance.documentInfo == root

      val elementIds  = if (canUseIndex) elementIdsFromIndex else elementIdsFromXPath
      val instanceIds = formInstanceRoot(root) descendant * map (_.localname + suffix)

      elementIds ++ instanceIds
    }

    val allIds = collection.mutable.Set() ++ findAllIds
    var guess = allIds.size + 1

    def nextId = {
      def buildId(i: Int) = prefix + i + suffix

      while (allIds(buildId(guess)))
        guess += 1

      val result = buildId(guess)
      allIds += result
      result
    }

    for (_ ← 1 to count)
      yield nextId
  }

  def makeInstanceExpression(name: String): String = "instance('" + name + "')"

  // Whether the browser is supported
  // Concretely, we only return false if the browser is an "old" version of IE
  def isBrowserSupported: Boolean = {
    val request = NetUtils.getExternalContext.getRequest
    ! UserAgent.isUserAgentIE(request) || UserAgent.getMSIEVersion(request) >= MinimalIEVersion
  }

  def debugDumpDocumentForGrids(message: String, inDoc: NodeInfo): Unit =
    if (XFormsProperties.getDebugLogging.contains("form-builder-grid"))
      debugDumpDocument(message, inDoc)

  def debugDumpDocument(message: String, inDoc: NodeInfo): Unit =
    debug(message, Seq("doc" → TransformerUtils.tinyTreeToString(inDoc.getDocumentRoot)))

  def insertElementsImposeOrder(into: Seq[NodeInfo], origin: Seq[NodeInfo], order: Seq[String]): Seq[NodeInfo] = {
    val name            = origin.head.localname
    val namesUntil      = (order takeWhile (_ != name)) :+ name toSet
    val elementsBefore  = into child * filter (e ⇒ namesUntil(e.localname))

    insert(into = into, after = elementsBefore, origin = origin)
  }

  //@XPathExpression
  def alwaysShowRoles: List[String] = {
    val rolesJsonOpt = Property.propertyAsString("oxf.fb.permissions.role.always-show")
    rolesJsonOpt.to[List].flatMap(_.parseJson.convertTo[List[String]])
  }
}
<commit_msg>FB: Make minimum IE version 12 (AKA Edge)<commit_after>/**
 * Copyright (C) 2013 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.fb

import org.orbeon.oxf.fr.FormRunner._
import org.orbeon.oxf.util.{IndentedLogger, Logging, NetUtils, UserAgent}
import org.orbeon.oxf.xforms.XFormsConstants.COMPONENT_SEPARATOR
import org.orbeon.oxf.xforms.XFormsProperties
import org.orbeon.oxf.xforms.action.XFormsAPI._
import org.orbeon.oxf.xforms.model.{XFormsInstance, XFormsModel}
import org.orbeon.oxf.xml.TransformerUtils
import org.orbeon.saxon.function.Property
import org.orbeon.saxon.om.{DocumentInfo, NodeInfo}
import org.orbeon.scaxon.Implicits._
import org.orbeon.scaxon.SimplePath._
import spray.json.DefaultJsonProtocol._
import spray.json._

import scala.collection.immutable

trait BaseOps extends Logging {

  implicit def logger: IndentedLogger = inScopeContainingDocument.getIndentedLogger("form-builder")

  // Minimal version of IE supported for Form Builder
  // 2017-10-06: Starting Orbeon Forms 2017.2, we don't support IE11 anymore and require Edge.
  //@XPathExpression
  val MinimalIEVersion = 12

  // Id of the xxf:dynamic control holding the edited form
  val DynamicControlId = "fb"

  // Find the form document being edited
  def getFormDoc: DocumentInfo =
    topLevelModel("fr-form-model").get.unsafeGetVariableAsNodeInfo("model").getDocumentRoot

  // All xbl:binding elements available
  def componentBindings: Seq[NodeInfo] =
    asScalaSeq(topLevelModel("fr-form-model").get.getVariable("component-bindings")).asInstanceOf[Seq[NodeInfo]]

  // Return fb-form-instance
  def fbFormInstance: XFormsInstance =
    topLevelInstance("fr-form-model", "fb-form-instance").get

  // Find the top-level form model of the form being edited
  def getFormModel: XFormsModel =
    inScopeContainingDocument.getObjectByEffectiveId(DynamicControlId + COMPONENT_SEPARATOR + "fr-form-model")
      .asInstanceOf[XFormsModel] ensuring (_ ne null, "did not find fb$fr-form-model")

  def formResourcesRoot: NodeInfo =
    topLevelModel("fr-form-model").get.unsafeGetVariableAsNodeInfo("resources")

  def templateRoot(inDoc: NodeInfo, repeatName: String): Option[NodeInfo] =
    inlineInstanceRootElem(inDoc, templateId(repeatName))

  // Find the next available id for a given token
  def nextId(inDoc: NodeInfo, token: String): String =
    nextIds(inDoc, token, 1).head

  // Find a series of next available ids for a given token
  // Return ids of the form "foo-123-foo", where "foo" is the token
  def nextIds(inDoc: NodeInfo, token: String, count: Int): immutable.IndexedSeq[String] = {

    val prefix = token + "-"
    val suffix = "-" + token

    def findAllIds = {
      val root = inDoc.getDocumentRoot

      // Use id index when possible, otherwise use plain XPath
      val fbInstance = fbFormInstance

      def elementIdsFromIndex = fbInstance.idsIterator filter (_.endsWith(suffix))
      def elementIdsFromXPath = (root descendant *).ids filter (_.endsWith(suffix)) iterator

      def canUseIndex = fbInstance.documentInfo == root

      val elementIds  = if (canUseIndex) elementIdsFromIndex else elementIdsFromXPath
      val instanceIds = formInstanceRoot(root) descendant * map (_.localname + suffix)

      elementIds ++ instanceIds
    }

    val allIds = collection.mutable.Set() ++ findAllIds
    var guess = allIds.size + 1

    def nextId = {
      def buildId(i: Int) = prefix + i + suffix

      while (allIds(buildId(guess)))
        guess += 1

      val result = buildId(guess)
      allIds += result
      result
    }

    for (_ ← 1 to count)
      yield nextId
  }

  def makeInstanceExpression(name: String): String = "instance('" + name + "')"

  // Whether the browser is supported
  // Concretely, we only return false if the browser is an "old" version of IE
  def isBrowserSupported: Boolean = {
    val request = NetUtils.getExternalContext.getRequest
    ! UserAgent.isUserAgentIE(request) || UserAgent.getMSIEVersion(request) >= MinimalIEVersion
  }

  def debugDumpDocumentForGrids(message: String, inDoc: NodeInfo): Unit =
    if (XFormsProperties.getDebugLogging.contains("form-builder-grid"))
      debugDumpDocument(message, inDoc)

  def debugDumpDocument(message: String, inDoc: NodeInfo): Unit =
    debug(message, Seq("doc" → TransformerUtils.tinyTreeToString(inDoc.getDocumentRoot)))

  def insertElementsImposeOrder(into: Seq[NodeInfo], origin: Seq[NodeInfo], order: Seq[String]): Seq[NodeInfo] = {
    val name            = origin.head.localname
    val namesUntil      = (order takeWhile (_ != name)) :+ name toSet
    val elementsBefore  = into child * filter (e ⇒ namesUntil(e.localname))

    insert(into = into, after = elementsBefore, origin = origin)
  }

  //@XPathExpression
  def alwaysShowRoles: List[String] = {
    val rolesJsonOpt = Property.propertyAsString("oxf.fb.permissions.role.always-show")
    rolesJsonOpt.to[List].flatMap(_.parseJson.convertTo[List[String]])
  }
}

{'fraction_non_alphanumeric': 0.09847307305476821, 'fraction_numerical': 0.004297339306939746, 'mean_word_length': 4.012832263978002, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4822942', 'n_tokens_mistral': 3378, 'n_tokens_neox': 3259, 'n_words': 930}