<commit_before>// GGX from from http://www.filmicworlds.com/images/ggx-opt/optimized-ggx.hlsl
// PBR adapted from from https://www.shadertoy.com/view/XsfXWX

import LightShader from "./LightShader.js";
import RayShader from "./RayShader.js";
import CameraShader from "./CameraShader.js";
import Shader from "../3d/Shader.js";

export default class PBRShader extends Shader {
  static get PhysicallyBasedMaterial() {
    return `
    struct PhysicallyBasedMaterial
    {
      vec4 baseColor;
      float metallic;
      float roughness;
      float reflectance;
    };
    `;
  }

  static ggx() {
    return `
    #define PI 3.1415926535897932384626433832795

    float G1V(float dotNV, float k)
    {
      return 1. / (dotNV * (1. - k) + k);
    }

    float ggx(vec3 N, vec3 V, vec3 L, float roughness, float F0)
    {
      roughness = .01 + roughness * .99;

      float alpha = roughness * roughness;

      vec3 H = normalize(V + L);

      float dotNL = clamp(dot(N, L), 0., 1.);
      float dotNV = clamp(dot(N, V), 0., 1.);
      float dotNH = clamp(dot(N, H), 0., 1.);
      float dotLH = clamp(dot(L, H), 0., 1.);

      float F, D, vis;

      // D
      float alphaSqr = alpha * alpha;
      float denom = dotNH * dotNH * (alphaSqr - 1.) + 1.;
      D = alphaSqr / (PI * denom * denom);

      // F
      float dotLH5 = pow(1. - dotLH, 5.);
      F = F0 + (1. - F0) * dotLH5;

      // V
      float k = alpha / 2.;
      vis = G1V(dotNL, k) * G1V(dotNV, k);

      float specular = dotNL * D * F * vis;
      return specular;
    }
    `;
  }

  static computeGGXLighting() {
    return `
    vec3 computeGGXLighting (
      Ray ray,
      Light light,
      vec3 normal,
      PhysicallyBasedMaterial material
    ) {
      vec3 specular = light.color * ggx(normal, -ray.direction, -light.direction, material.roughness, material.reflectance);
      return specular;
    }
    `;
  }

  static computeSimplePBRColor({
    pbrReflectionFromRay = `
      ray.direction *= 1. / max(roughness, .0001);
      ray.direction = clamp(ray.direction, vec3(-1.), vec3(1.));
      vec4 color = vec4(vec3(ray.direction * .5 + .5), 1.);
      float grey = (color.r + color.g + color.b) / 3.;
      return vec4(vec3(grey), 1.);
    `,
  } = {}) {
    return `
    vec4 pbrReflectionFromRay(
      Ray ray,
      float roughness
    ) {
      ${pbrReflectionFromRay}
    }

    vec4 computeSimplePBRColor (
      Ray ray,
      Light light,
      vec3 position,
      vec3 normal,
      PhysicallyBasedMaterial material
    ) {
      light.color *= light.intensity;

      // fresnel
      float fresnel = 1. - dot(mix(normal, -ray.direction, material.roughness), -ray.direction);

      // reflection
      vec4 reflection = pbrReflectionFromRay(Ray(position, normalize(reflect(ray.direction, normal))), material.roughness);

      // diffuse
      vec4 color = mix(material.baseColor, material.baseColor * reflection, material.metallic);
      color = mix(color, reflection, fresnel);
      color.rgb *= light.color;

      color.rgb += computeGGXLighting(ray, light, normal, material);
      color.rgb = clamp(vec3(0.), vec3(1.), color.rgb);

      return color;
    }
    `;
  }

  static computePBRColor({
    pbrReflectionFromRay = `
      ray.direction *= 1. / roughness;
      ray.direction = clamp(ray.direction, vec3(-1.), vec3(1.));
      vec3 color = ray.direction * .5 + .5;
      float grey = (color.r + color.g + color.b) / 3.;
      return vec3(grey);
    `,
    pbrDiffuseLightFromRay = `
      vec3 color = ray.direction * .5 + .5;
      float grey = (color.r + color.g + color.b) / 3.;
      return vec3(grey);
    `,
  } = {}) {
    return `
    vec3 pbrReflectionFromRay(
      Ray ray,
      float roughness
    ) {
      ${pbrReflectionFromRay}
    }

    vec3 pbrDiffuseLightFromRay(
      Ray ray
    ) {
      ${pbrDiffuseLightFromRay}
    }

    // From https://github.com/KhronosGroup/glTF-WebGL-PBR

    // This fragment shader defines a reference implementation for Physically Based Shading of
    // a microfacet surface material defined by a glTF model.
    //
    // References:
    // [1] Real Shading in Unreal Engine 4
    //     http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
    // [2] Physically Based Shading at Disney
    //     http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
    // [3] README.md - Environment Maps
    //     https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps
    // [4] "An Inexpensive BRDF Model for Physically based Rendering" by Christophe Schlick
    //     https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf

    // Encapsulate the various inputs used by the various functions in the shading equation
    // We store values in this struct to simplify the integration of alternative implementations
    // of the shading terms, outlined in the Readme.MD Appendix.
    struct PBRInfo
    {
        float NdotL;                  // cos angle between normal and light direction
        float NdotV;                  // cos angle between normal and view direction
        float NdotH;                  // cos angle between normal and half vector
        float LdotH;                  // cos angle between light direction and half vector
        float VdotH;                  // cos angle between view direction and half vector
        float perceptualRoughness;    // roughness value, as authored by the model creator (input to shader)
        float metalness;              // metallic value at the surface
        vec3 reflectance0;            // full reflectance color (normal incidence angle)
        vec3 reflectance90;           // reflectance color at grazing angle
        float alphaRoughness;         // roughness mapped to a more linear change in the roughness (proposed by [2])
        vec3 diffuseColor;            // color contribution from diffuse lighting
        vec3 specularColor;           // color contribution from specular lighting
    };

    const float M_PI = 3.141592653589793;
    const float c_MinRoughness = 0.04;

    // Calculation of the lighting contribution from an optional Image Based Light source.
    // Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1].
    // See our README.md on Environment Maps [3] for additional discussion.
    vec3 getIBLContribution(PBRInfo pbrInputs, vec3 n, vec3 reflection, vec3 position)
    {
      vec3 diffuseLight = pbrDiffuseLightFromRay(Ray(position, n));

      // Fake BRDF Lookup
      vec2 brdfPosition = vec2(pbrInputs.NdotV, pbrInputs.perceptualRoughness);
      float brdfLength = length(brdfPosition);
      vec2 brdf = vec2(1. - smoothstep(0., 2., brdfLength), 1. - smoothstep(0., .3, brdfLength));
      brdf.x *= 1. - brdf.y;

      vec3 specularLight = pbrReflectionFromRay(Ray(position, reflection), pbrInputs.alphaRoughness);

      vec3 diffuse = diffuseLight * pbrInputs.diffuseColor;
      vec3 specular = specularLight * (pbrInputs.specularColor * brdf.x + brdf.y);

      return diffuse + specular;
    }

    // Basic Lambertian diffuse
    // Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog
    // See also [1], Equation 1
    vec3 diffuse(PBRInfo pbrInputs)
    {
        return pbrInputs.diffuseColor / M_PI;
    }

    // The following equation models the Fresnel reflectance term of the spec equation (aka F())
    // Implementation of fresnel from [4], Equation 15
    vec3 specularReflection(PBRInfo pbrInputs)
    {
        return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0);
    }

    // This calculates the specular geometric attenuation (aka G()),
    // where rougher material will reflect less light back to the viewer.
    // This implementation is based on [1] Equation 4, and we adopt their modifications to
    // alphaRoughness as input as originally proposed in [2].
    float geometricOcclusion(PBRInfo pbrInputs)
    {
        float NdotL = pbrInputs.NdotL;
        float NdotV = pbrInputs.NdotV;
        float r = pbrInputs.alphaRoughness;

        float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL)));
        float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV)));
        return attenuationL * attenuationV;
    }

    // The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
    // Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
    // Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
    float microfacetDistribution(PBRInfo pbrInputs)
    {
        float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
        float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0;
        return roughnessSq / (M_PI * f * f);
    }

    vec4 computePBRColor(
      vec3 viewDirection,
      Light light,
      vec3 position,
      vec3 normal,
      PhysicallyBasedMaterial material
    )
    {
        // Metallic and Roughness material properties are packed together
        // In glTF, these factors can be specified by fixed scalar values
        // or from a metallic-roughness map
        float perceptualRoughness = material.roughness;
        float metallic = material.metallic;

        perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0);
        metallic = clamp(metallic, 0.0, 1.0);
        // Roughness is authored as perceptual roughness; as is convention,
        // convert to material roughness by squaring the perceptual roughness [2].
        float alphaRoughness = perceptualRoughness * perceptualRoughness;

        vec4 baseColor = material.baseColor;

        vec3 f0 = vec3(0.04);
        vec3 diffuseColor = baseColor.rgb * (vec3(1.0) - f0);
        diffuseColor *= 1.0 - metallic;
        vec3 specularColor = mix(f0, baseColor.rgb, metallic);

        // Compute reflectance.
        float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);

        // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect.
        // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%.
        float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
        vec3 specularEnvironmentR0 = specularColor.rgb;
        vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90;

        vec3 n = normal;                                  // normal at surface point
        vec3 v = -viewDirection;                          // Vector from surface point to camera
        vec3 l = normalize(-light.direction);             // Vector from surface point to light
        vec3 h = normalize(l+v);                          // Half vector between both l and v
        vec3 reflection = -normalize(reflect(v, n));

        float NdotL = clamp(dot(n, l), 0.001, 1.0);
        float NdotV = clamp(abs(dot(n, v)), 0.001, 1.0);
        float NdotH = clamp(dot(n, h), 0.0, 1.0);
        float LdotH = clamp(dot(l, h), 0.0, 1.0);
        float VdotH = clamp(dot(v, h), 0.0, 1.0);

        PBRInfo pbrInputs = PBRInfo(
            NdotL,
            NdotV,
            NdotH,
            LdotH,
            VdotH,
            perceptualRoughness,
            metallic,
            specularEnvironmentR0,
            specularEnvironmentR90,
            alphaRoughness,
            diffuseColor,
            specularColor
        );

        // Calculate the shading terms for the microfacet specular shading model
        vec3 F = specularReflection(pbrInputs);
        float G = geometricOcclusion(pbrInputs);
        float D = microfacetDistribution(pbrInputs);

        // Calculation of analytical lighting contribution
        vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs);
        vec3 specContrib = F * G * D / (4.0 * NdotL * NdotV);
        // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law)
        vec3 color = NdotL * light.color * light.intensity * (diffuseContrib + specContrib);

        // Calculate lighting contribution from image based lighting source (IBL)
        color += getIBLContribution(pbrInputs, n, reflection, position);

        return vec4(color, baseColor.a);
      }
    `;
  }

  constructor({
    baseColor = [1, 1, 1, 1],
    metallic = 0,
    roughness = 0,
    reflectance = 1,
    uniforms = {},
    vertexShaderChunks = [],
    fragmentShaderChunks = [],
    uvs = true,
    pbrDiffuseLightFromRay = undefined,
    pbrReflectionFromRay = undefined,
  } = {}) {
    super({
      uniforms: Object.assign({
        material: {
          baseColor,
          metallic,
          roughness,
          reflectance,
        },
      }, uniforms),
      vertexShaderChunks: [
        ["start", `
            ${CameraShader.Camera}
            ${RayShader.Ray}

            uniform Camera camera;
            uniform mat4 projectionView;
            uniform mat4 transform;

            in vec3 position;
            in vec3 normal;
            ${uvs ? "in vec2 uv;" : ""}

            out vec3 vPosition;
            out vec3 vNormal;
            ${uvs ? "out vec2 vUV;" : ""}
            out vec3 vViewDirection;

            ${RayShader.rayFromCamera()}
         `],
        ["end", `
          ${uvs ? "vUV = uv;" : ""}
          gl_Position = camera.projectionView * transform * vec4(position, 1.);
          vPosition = position;
          vNormal = normalize(mat3(transform) * normal);
          Ray ray = rayFromCamera(gl_Position.xy / gl_Position.w, camera);
          vViewDirection = ray.direction;
        `],
        ...vertexShaderChunks,
      ],
      fragmentShaderChunks: [
        ["start", `
          ${LightShader.Light}
          ${RayShader.Ray}
          ${PBRShader.PhysicallyBasedMaterial}

          uniform PhysicallyBasedMaterial material;
          uniform Light light;

          in vec3 vPosition;
          in vec3 vNormal;
          ${uvs ? "in vec2 vUV;" : ""}
          in vec3 vViewDirection;

          ${PBRShader.ggx()}
          ${PBRShader.computeGGXLighting()}
          ${PBRShader.computePBRColor({ pbrDiffuseLightFromRay, pbrReflectionFromRay })}
        `],
        ["end", `
          Light light = Light(vec3(1.), vec3(1.), normalize(vec3(-1.)), 1.);
          fragColor = computePBRColor(vViewDirection, light, vPosition, vNormal, material);
          // fragColor = vec4(vViewDirection, 1.);
        `],
        ...fragmentShaderChunks,
      ],
    });
  }
}
<commit_msg>clean PBRShader<commit_after>import LightShader from "./LightShader.js";
import RayShader from "./RayShader.js";
import CameraShader from "./CameraShader.js";
import Shader from "../3d/Shader.js";

export default class PBRShader extends Shader {
  static get PhysicallyBasedMaterial() {
    return `
    struct PhysicallyBasedMaterial
    {
      vec4 baseColor;
      float metallic;
      float roughness;
      float reflectance;
    };
    `;
  }

  static computePBRColor({
    pbrReflectionFromRay = `
      ray.direction *= 1. / roughness;
      ray.direction = clamp(ray.direction, vec3(-1.), vec3(1.));
      vec3 color = ray.direction * .5 + .5;
      float grey = (color.r + color.g + color.b) / 3.;
      return vec3(grey);
    `,
    pbrDiffuseLightFromRay = `
      vec3 color = ray.direction * .5 + .5;
      float grey = (color.r + color.g + color.b) / 3.;
      return vec3(grey);
    `,
  } = {}) {
    return `
    vec3 pbrReflectionFromRay(
      Ray ray,
      float roughness
    ) {
      ${pbrReflectionFromRay}
    }

    vec3 pbrDiffuseLightFromRay(
      Ray ray
    ) {
      ${pbrDiffuseLightFromRay}
    }

    // From https://github.com/KhronosGroup/glTF-WebGL-PBR

    // This fragment shader defines a reference implementation for Physically Based Shading of
    // a microfacet surface material defined by a glTF model.
    //
    // References:
    // [1] Real Shading in Unreal Engine 4
    //     http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
    // [2] Physically Based Shading at Disney
    //     http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
    // [3] README.md - Environment Maps
    //     https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps
    // [4] "An Inexpensive BRDF Model for Physically based Rendering" by Christophe Schlick
    //     https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf

    // Encapsulate the various inputs used by the various functions in the shading equation
    // We store values in this struct to simplify the integration of alternative implementations
    // of the shading terms, outlined in the Readme.MD Appendix.
    struct PBRInfo
    {
        float NdotL;                  // cos angle between normal and light direction
        float NdotV;                  // cos angle between normal and view direction
        float NdotH;                  // cos angle between normal and half vector
        float LdotH;                  // cos angle between light direction and half vector
        float VdotH;                  // cos angle between view direction and half vector
        float perceptualRoughness;    // roughness value, as authored by the model creator (input to shader)
        float metalness;              // metallic value at the surface
        vec3 reflectance0;            // full reflectance color (normal incidence angle)
        vec3 reflectance90;           // reflectance color at grazing angle
        float alphaRoughness;         // roughness mapped to a more linear change in the roughness (proposed by [2])
        vec3 diffuseColor;            // color contribution from diffuse lighting
        vec3 specularColor;           // color contribution from specular lighting
    };

    const float M_PI = 3.141592653589793;
    const float c_MinRoughness = 0.04;

    // Calculation of the lighting contribution from an optional Image Based Light source.
    // Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1].
    // See our README.md on Environment Maps [3] for additional discussion.
    vec3 getIBLContribution(PBRInfo pbrInputs, vec3 n, vec3 reflection, vec3 position)
    {
      vec3 diffuseLight = pbrDiffuseLightFromRay(Ray(position, n));

      // Fake BRDF Lookup
      vec2 brdfPosition = vec2(pbrInputs.NdotV, pbrInputs.perceptualRoughness);
      float brdfLength = length(brdfPosition);
      vec2 brdf = vec2(1. - smoothstep(0., 2., brdfLength), 1. - smoothstep(0., .3, brdfLength));
      brdf.x *= 1. - brdf.y;

      vec3 specularLight = pbrReflectionFromRay(Ray(position, reflection), pbrInputs.alphaRoughness);

      vec3 diffuse = diffuseLight * pbrInputs.diffuseColor;
      vec3 specular = specularLight * (pbrInputs.specularColor * brdf.x + brdf.y);

      return diffuse + specular;
    }

    // Basic Lambertian diffuse
    // Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog
    // See also [1], Equation 1
    vec3 diffuse(PBRInfo pbrInputs)
    {
        return pbrInputs.diffuseColor / M_PI;
    }

    // The following equation models the Fresnel reflectance term of the spec equation (aka F())
    // Implementation of fresnel from [4], Equation 15
    vec3 specularReflection(PBRInfo pbrInputs)
    {
        return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0);
    }

    // This calculates the specular geometric attenuation (aka G()),
    // where rougher material will reflect less light back to the viewer.
    // This implementation is based on [1] Equation 4, and we adopt their modifications to
    // alphaRoughness as input as originally proposed in [2].
    float geometricOcclusion(PBRInfo pbrInputs)
    {
        float NdotL = pbrInputs.NdotL;
        float NdotV = pbrInputs.NdotV;
        float r = pbrInputs.alphaRoughness;

        float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL)));
        float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV)));
        return attenuationL * attenuationV;
    }

    // The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
    // Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz
    // Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3.
    float microfacetDistribution(PBRInfo pbrInputs)
    {
        float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
        float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0;
        return roughnessSq / (M_PI * f * f);
    }

    vec4 computePBRColor(
      vec3 viewDirection,
      Light light,
      vec3 position,
      vec3 normal,
      PhysicallyBasedMaterial material
    )
    {
        // Metallic and Roughness material properties are packed together
        // In glTF, these factors can be specified by fixed scalar values
        // or from a metallic-roughness map
        float perceptualRoughness = material.roughness;
        float metallic = material.metallic;

        perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0);
        metallic = clamp(metallic, 0.0, 1.0);
        // Roughness is authored as perceptual roughness; as is convention,
        // convert to material roughness by squaring the perceptual roughness [2].
        float alphaRoughness = perceptualRoughness * perceptualRoughness;

        vec4 baseColor = material.baseColor;

        vec3 f0 = vec3(0.04);
        vec3 diffuseColor = baseColor.rgb * (vec3(1.0) - f0);
        diffuseColor *= 1.0 - metallic;
        vec3 specularColor = mix(f0, baseColor.rgb, metallic);

        // Compute reflectance.
        float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);

        // For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect.
        // For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%.
        float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
        vec3 specularEnvironmentR0 = specularColor.rgb;
        vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90;

        vec3 n = normal;                                  // normal at surface point
        vec3 v = -viewDirection;                          // Vector from surface point to camera
        vec3 l = normalize(-light.direction);             // Vector from surface point to light
        vec3 h = normalize(l+v);                          // Half vector between both l and v
        vec3 reflection = -normalize(reflect(v, n));

        float NdotL = clamp(dot(n, l), 0.001, 1.0);
        float NdotV = clamp(abs(dot(n, v)), 0.001, 1.0);
        float NdotH = clamp(dot(n, h), 0.0, 1.0);
        float LdotH = clamp(dot(l, h), 0.0, 1.0);
        float VdotH = clamp(dot(v, h), 0.0, 1.0);

        PBRInfo pbrInputs = PBRInfo(
            NdotL,
            NdotV,
            NdotH,
            LdotH,
            VdotH,
            perceptualRoughness,
            metallic,
            specularEnvironmentR0,
            specularEnvironmentR90,
            alphaRoughness,
            diffuseColor,
            specularColor
        );

        // Calculate the shading terms for the microfacet specular shading model
        vec3 F = specularReflection(pbrInputs);
        float G = geometricOcclusion(pbrInputs);
        float D = microfacetDistribution(pbrInputs);

        // Calculation of analytical lighting contribution
        vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs);
        vec3 specContrib = F * G * D / (4.0 * NdotL * NdotV);
        // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law)
        vec3 color = NdotL * light.color * light.intensity * (diffuseContrib + specContrib);

        // Calculate lighting contribution from image based lighting source (IBL)
        color += getIBLContribution(pbrInputs, n, reflection, position);

        return vec4(color, baseColor.a);
      }
    `;
  }

  constructor({
    baseColor = [1, 1, 1, 1],
    metallic = 0,
    roughness = 0,
    reflectance = 1,
    uniforms = {},
    vertexShaderChunks = [],
    fragmentShaderChunks = [],
    uvs = true,
    pbrDiffuseLightFromRay = undefined,
    pbrReflectionFromRay = undefined,
  } = {}) {
    super({
      uniforms: Object.assign({
        material: {
          baseColor,
          metallic,
          roughness,
          reflectance,
        },
      }, uniforms),
      vertexShaderChunks: [
        ["start", `
            ${CameraShader.Camera}
            ${RayShader.Ray}

            uniform Camera camera;
            uniform mat4 projectionView;
            uniform mat4 transform;

            in vec3 position;
            in vec3 normal;
            ${uvs ? "in vec2 uv;" : ""}

            out vec3 vPosition;
            out vec3 vNormal;
            ${uvs ? "out vec2 vUV;" : ""}
            out vec3 vViewDirection;

            ${RayShader.rayFromCamera()}
         `],
        ["end", `
          ${uvs ? "vUV = uv;" : ""}
          gl_Position = camera.projectionView * transform * vec4(position, 1.);
          vPosition = position;
          vNormal = normalize(mat3(transform) * normal);
          Ray ray = rayFromCamera(gl_Position.xy / gl_Position.w, camera);
          vViewDirection = ray.direction;
        `],
        ...vertexShaderChunks,
      ],
      fragmentShaderChunks: [
        ["start", `
          ${LightShader.Light}
          ${RayShader.Ray}
          ${PBRShader.PhysicallyBasedMaterial}

          uniform PhysicallyBasedMaterial material;
          uniform Light light;

          in vec3 vPosition;
          in vec3 vNormal;
          ${uvs ? "in vec2 vUV;" : ""}
          in vec3 vViewDirection;

          ${PBRShader.computePBRColor({ pbrDiffuseLightFromRay, pbrReflectionFromRay })}
        `],
        ["end", `
          Light light = Light(vec3(1.), vec3(1.), normalize(vec3(-1.)), 1.);
          fragColor = computePBRColor(vViewDirection, light, vPosition, vNormal, material);
        `],
        ...fragmentShaderChunks,
      ],
    });
  }
}
<|endoftext|>


<commit_before>'use strict';

var _        = require('mano').i18n.bind('View: Official')
  , identity = require('es5-ext/function/identity');

module.exports = function (doc) {
	var upload                 = doc.owner
	  , revisionStatus         = upload._status
	  , isPaymentReceiptUpload = eq(upload.constructor.__id__, 'PaymentReceiptUpload');

	// Don't display anything if not reviewed yet.
	return _if(revisionStatus, div({
		class: 'section-secondary'
	}, _if(eq(revisionStatus, 'valid'),
		h4(_("This document was validated")),
		_if(isPaymentReceiptUpload, [
			h4(_("This payment receipt was rejected for the following reason(s)"), ':'),
			p(upload._rejectReasonMemo)
		], [
			h4(_("This document was rejected for the following reason(s)"), ':'),
			_if(eq(upload.rejectReasons._size, 1), p(upload.rejectReasons._first),
				ul(upload.rejectReasons, identity))
		]))));

};
<commit_msg>Issue #1312. Removed 'validated' message from doc.<commit_after>'use strict';

var _        = require('mano').i18n.bind('View: Official')
  , identity = require('es5-ext/function/identity');

module.exports = function (doc) {
	var upload                 = doc.owner
	  , revisionStatus         = upload._status
	  , isPaymentReceiptUpload = eq(upload.constructor.__id__, 'PaymentReceiptUpload');

	// Don't display anything if not rejected.
	return _if(eq(revisionStatus, 'invalid'), div({
		class: 'section-secondary'
	}, _if(isPaymentReceiptUpload, [
		h4(_("This payment receipt was rejected for the following reason(s)"), ':'),
		p(upload._rejectReasonMemo)
	], [
		h4(_("This document was rejected for the following reason(s)"), ':'),
		_if(eq(upload.rejectReasons._size, 1), p(upload.rejectReasons._first),
			ul(upload.rejectReasons, identity))
	])));

};
<|endoftext|>


<commit_before>/**
 * Checkout.com
 * Authorized and regulated as an electronic money institution
 * by the UK Financial Conduct Authority (FCA) under number 900816.
 *
 * PHP version 7
 *
 * @category  Magento2
 * @package   Checkout.com
 * @author    Platforms Development Team <platforms@checkout.com>
 * @copyright 2010-2019 Checkout.com
 * @license   https://opensource.org/licenses/mit-license.html MIT License
 * @link      https://docs.checkout.com/
 */

define(
    [
        'jquery',
        'CheckoutCom_Magento2/js/view/payment/config-loader',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/checkout-data',
        'mage/url',
        'Magento_Checkout/js/action/redirect-on-success',
        'Magento_Checkout/js/model/full-screen-loader',
        'Magento_Customer/js/model/customer',
        'mage/translate',
        'mage/cookies'
    ],
    function ($, Config, Quote, CheckoutData, Url, RedirectOnSuccessAction, FullScreenLoader, Customer, __) {
        'use strict';

        const KEY_CONFIG = 'checkoutcom_configuration';
        const KEY_DATA = 'checkoutcom_data';

        return {
            /**
             * Gets a field value.
             *
             * @param  {string}  methodId The method id
             * @param  {string}  field    The field
             * @param  {bool}  strict     The strict value
             * @return {mixed}            The value
             */
            getValue: function (methodId, field, strict) {
                var val = null;
                strict = (strict === undefined ? false : strict);
                if (methodId && Config.hasOwnProperty(methodId) && Config[methodId].hasOwnProperty(field)) {
                    val = Config[methodId][field]
                } else if (Config.hasOwnProperty(KEY_CONFIG) && Config[KEY_CONFIG].hasOwnProperty(field) && !strict) {
                    val = Config[KEY_CONFIG][field];
                }

                return val;
            },

            /**
             * Load a CSS file
             *
             * @return {void}
             */
            loadCss: function (fileName, folderPath) {
                // Prepare the folder path
                folderPath = (folderPath) ? '/' + folderPath : '';

                // Get the CSS config parameters
                var useMinCss = window.checkoutConfig.payment.checkoutcom_magento2.checkoutcom_data.use_minified_css;
                var ext = (useMinCss == '1') ? '.min.css' : '.css';

                // Build the payment form CSS path
                var cssPath = window.checkoutConfig.payment.checkoutcom_magento2.checkoutcom_data.css_path;
                cssPath += folderPath + '/' + fileName + ext;

                // Append the CSS file
                $('head').append('<link rel="stylesheet" href="' + cssPath + '" type="text/css"/>');
            },

            /**
             * Load a remote JS file
             *
             * @return {void}
             */
            loadRemoteJs: function (jsUrl) {
                // Append the CSS file
                $('head').append('');
            },

            /**
             * Get the store name.
             *
             * @return {string}  The store name.
             */
            getStoreName: function () {
                return Config[KEY_DATA].store.name;
            },

            /**
             * Get the quote value.
             *
             * @return {float}  The quote value.
             */
            getQuoteValue: function () {
                var data = this.getRestQuoteData();
                var amount = parseFloat(data.totals.base_grand_total);

                return amount.toFixed(2);
            },

            /**
             * Get the updated quote data from the core REST API.
             *
             * @return {object}  The quote data.
             */
            getRestQuoteData: function () {
                // Prepare the required parameters
                var self = this;
                var result = null;
                var restUrl = window.BASE_URL;

                // Build the rest URL
                if (Customer.isLoggedIn()) {
                    restUrl += 'rest/default/V1/';
                    restUrl += 'carts/mine/payment-information';
                    restUrl += '?form_key=' + window.checkoutConfig.formKey;
                }
                else {
                    restUrl += 'rest/all/V1/guest-carts/' + window.checkoutConfig.quoteData.entity_id + '/payment-information';
                    restUrl += '?form_key=' + window.checkoutConfig.formKey;
                }

                // Set the event to update data on any button click
                $('button[type="submit"]')
                .off('click', self.getRestQuoteData)
                .on('click', self.getRestQuoteData);

                    // Send the AJAX request
                    $.ajax({
                        url: restUrl,
                        type: 'GET',
                        contentType: "application/json",
                        dataType: "json",
                        async: false,
                        showLoader: true,
                        success: function (data, status, xhr) {
                            result = data;
                        },
                        error: function (request, status, error) {
                            self.log(error);
                        }
                    });

                return result;
            },

            /**
             * Get the quote currency.
             *
             * @return {string}  The quote currency.
             */
            getQuoteCurrency: function () {
                return Config[KEY_DATA].quote.currency;
            },

            /**
             * Check if the payment method should be enabled by default.
             *
             * @return {void}
             */
            checkDefaultEnabled: function (methodId) {
                var userData = this.getValue('checkoutcom_data', 'user');

                // Select the previous payment method it's available.
                if (userData['previous_method'] == methodId && $('input#' + userData['previous_method']).length) {
                    $('input#' + userData['previous_method']).trigger('click');
                } else if (this.getValue(null, 'default_method') == methodId) {
                    $('input#' + methodId).trigger('click');
                }
            },

            /**
             * Check if APM should be auto selected.
             *
             * @return {void}
             */
            checkLastPaymentMethod: function () {
                var userData = this.getValue('checkoutcom_data', 'user');

                if (userData['previous_method'] == 'checkoutcom_apm'
                    && $('.cko-apm#' + userData['previous_source']).length) {
                    // If available auto select the previously used apm.
                    $('.cko-apm#' + userData['previous_source']).trigger('click');
                } else if(userData['previous_method'] == 'checkoutcom_apm') {
                    // Select default payment method if the previous apm is no longer available.
                    if ($('input#' + this.getValue(null, 'default_method')).length) {
                        $('input#' + this.getValue(null, 'default_method')).trigger('click');
                    }
                }
            },

            /**
             * Check if stored card should be auto selected.
             *
             * @return {void}
             */
            checkStoredCard: function () {
                var userData = this.getValue('checkoutcom_data', 'user');
                if (userData['previous_method'] == 'checkoutcom_vault'
                    && $('input[name=\'publicHash\'][value=\''+userData['previous_source']+'\']').length) {
                    $('input[name=\'publicHash\'][value=\''+userData['previous_source']+'\']').trigger('click');
                }
            },

            /**
             * Checks if user has saved cards.
             *
             * @return {bool}
             */
            userHasCards: function () {
                return Config[KEY_DATA].user.has_cards;
            },

            /**
             * Gets the card form language fallback.
             *
             * @return {string}
             */
            getLangageFallback: function () {
                return Config[KEY_DATA].user.language_fallback;
            },

            /**
             * Get the card form shop language
             *
             * @return {string}
             */
            getShopLanguage: function() {
                let mageShopLanguage = Config[KEY_DATA].store.language;
                let framesLanguage;
                switch(mageShopLanguage) {
                    case 'nl_NL':
                        framesLanguage = 'NL-NL';
                        break;
                    case 'en_GB':
                         framesLanguage = 'EN-GB';
                        break;
                    case 'fr_FR':
                         framesLanguage = 'FR-FR';
                        break;
                    case 'de_DE':
                        framesLanguage = 'DE-DE';
                        break;
                    case 'it_IT':
                        framesLanguage = 'IT-IT';
                        break;
                    case 'ko_KR':
                        framesLanguage = 'KR-KR';
                        break;
                    case 'es_ES':
                        framesLanguage = 'ES-ES';
                        break;
                    default:
                      framesLanguage = this.getLangageFallback().toUpperCase().split("_").join('-');
                }

                return framesLanguage;
            },

            /**
             * Get the supported cards.
             *
             * @return {array}
             */
            getSupportedCards: function () {
                return Config[KEY_DATA].cards;
            },

            /**
             * Builds the controller URL.
             *
             * @param  {string}  path  The path
             * @return {string}
             */
            getUrl: function (path) {
                return Url.build('checkout_com/' + path);
            },

            /**
             * Customer name.
             *
             * @param  {bool} return in object format.
             * @return {mixed}  The billing address.
             */
            getCustomerName: function () {
                var billingAddress = Quote.billingAddress();
                var customerName = '';
                if (billingAddress) {
                    customerName += billingAddress.firstname;
                    customerName += ' ' + billingAddress.lastname;
                }

                return customerName;
            },

            /**
             * Billing address.
             *
             * @return {object}  The billing address.
             */
            getBillingAddress: function () {
                return Quote.billingAddress();
            },

            /**
             * @return {string}
             */
            getEmail: function () {
                var emailCookieName = this.getValue(null, 'email_cookie_name');
                return window.checkoutConfig.customerData.email
                || Quote.guestEmail
                || CheckoutData.getValidatedEmailValue()
                || $.cookie(emailCookieName);
            },

            /**
             * @return {void}
             */
            setEmail: function () {
                var userEmail = this.getEmail();
                var emailCookieName = this.getValue(null, 'email_cookie_name');
                $.cookie(emailCookieName, userEmail);

                // If no email found, observe the core email field
                if (!userEmail) {
                    $('#customer-email').off('change').on('change', function () {
                        userEmail = Quote.guestEmail || CheckoutData.getValidatedEmailValue();
                        $.cookie(emailCookieName, userEmail);
                    });
                }
            },

            /**
             * @return {object}
             */
            getPhone: function () {
                var billingAddress = Quote.billingAddress();

                return {
                    number: billingAddress.telephone
                };
            },

            /**
             * Handle error logging.
             */
            log: function (val) {
                if (this.getValue(null, 'debug')
                    && this.getValue(null, 'console_logging')
                ) {
                    console.log(val);
                }
            },

            /**
             * Show a message.
             */
            showMessage: function (type, message, methodId) {
                this.clearMessages(methodId);
                var messageContainer = this.getMethodContainer(methodId).find('.message-cko');
                messageContainer.addClass('message-' + type + ' ' + type);
                messageContainer.append('
' + __(message) + '
');
                messageContainer.show();
            },

            /**
             * Show debug message.
             */
            showDebugMessage: function (type, message, methodId) {
                var messageContainer = this.getMethodContainer(methodId).find('.debug-message');
                messageContainer.addClass('message-' + type + ' ' + type);
                messageContainer.append('

' + __(message) + '

');
                messageContainer.show();
            },

            /**
             * Show response code.
             */
            showResponseCode: function (type, message, methodId) {
                var messageContainer = this.getMethodContainer(methodId).find('.message-response-code');
                messageContainer.addClass('message-' + type + ' ' + type);
                messageContainer.append('
Response Code: ' + __(message) + '
');
                messageContainer.show();
            },

            /**
             * Clear all messages.
             */
            clearMessages: function (methodId) {
                var messageContainer = this.getMethodContainer(methodId).find('.message');
                messageContainer.hide();
                messageContainer.empty();
            },

            /**
             * Get a payment method container instance.
             */
            getMethodContainer: function (methodId) {
                return $('#' + methodId + '_container');
            },

            /**
             * Check if an URL is valid.
             */
            isUrl: function (str) {
                var pattern = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/;
                return pattern.test(str);
            },

            /**
             * Handle the place order button state.
             */
            allowPlaceOrder: function (buttonId, yesNo) {
                $('#' + buttonId).prop('disabled', !yesNo);
            },

            /**
             * Check if a payment option is active.
             */
            methodIsSelected: function (idSelector) {
                var id = idSelector.replace('#', '');
                var selected = CheckoutData.getSelectedPaymentMethod();
                return id == selected || selected == null;
            },

            /**
             * Place a new order.
             *
             * @return {void}
             */
            placeOrder: function (payload, methodId) {
                var self = this;

                // Start the loader
                FullScreenLoader.startLoader();

                // Send the request
                $.ajax({
                    type: 'POST',
                    url: self.getUrl('payment/placeorder'),
                    data: payload,
                    success: function (data) {
                        if (!data.success) {
                            FullScreenLoader.stopLoader();
                            self.showMessage('error', data.message, methodId);
                            if (data.debugMessage) {
                                self.showDebugMessage('error', data.debugMessage, methodId);
                            }
                            if (data.responseCode) {
                                self.showResponseCode('error', data.responseCode, methodId);
                            }
                            self.allowPlaceOrder(methodId + '_btn', false)
                        } else if (data.success && data.url) {
                            // Handle 3DS redirection
                            window.location.href = data.url;
                        } else {
                            // Normal redirection
                            RedirectOnSuccessAction.execute();
                        }
                    },
                    error: function (request, status, error) {
                        self.showMessage('error', error, methodId);
                        FullScreenLoader.stopLoader();
                    }
                });
            }
        };
    }
);<commit_msg>Code refactor<commit_after>/**
 * Checkout.com
 * Authorized and regulated as an electronic money institution
 * by the UK Financial Conduct Authority (FCA) under number 900816.
 *
 * PHP version 7
 *
 * @category  Magento2
 * @package   Checkout.com
 * @author    Platforms Development Team <platforms@checkout.com>
 * @copyright 2010-2019 Checkout.com
 * @license   https://opensource.org/licenses/mit-license.html MIT License
 * @link      https://docs.checkout.com/
 */

define(
    [
        'jquery',
        'CheckoutCom_Magento2/js/view/payment/config-loader',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/checkout-data',
        'mage/url',
        'Magento_Checkout/js/action/redirect-on-success',
        'Magento_Checkout/js/model/full-screen-loader',
        'Magento_Customer/js/model/customer',
        'mage/translate',
        'mage/cookies'
    ],
    function ($, Config, Quote, CheckoutData, Url, RedirectOnSuccessAction, FullScreenLoader, Customer, __) {
        'use strict';

        const KEY_CONFIG = 'checkoutcom_configuration';
        const KEY_DATA = 'checkoutcom_data';

        return {
            /**
             * Gets a field value.
             *
             * @param  {string}  methodId The method id
             * @param  {string}  field    The field
             * @param  {bool}  strict     The strict value
             * @return {mixed}            The value
             */
            getValue: function (methodId, field, strict) {
                var val = null;
                strict = (strict === undefined ? false : strict);
                if (methodId && Config.hasOwnProperty(methodId) && Config[methodId].hasOwnProperty(field)) {
                    val = Config[methodId][field]
                } else if (Config.hasOwnProperty(KEY_CONFIG) && Config[KEY_CONFIG].hasOwnProperty(field) && !strict) {
                    val = Config[KEY_CONFIG][field];
                }

                return val;
            },

            /**
             * Load a CSS file
             *
             * @return {void}
             */
            loadCss: function (fileName, folderPath) {
                // Prepare the folder path
                folderPath = (folderPath) ? '/' + folderPath : '';

                // Get the CSS config parameters
                var useMinCss = window.checkoutConfig.payment.checkoutcom_magento2.checkoutcom_data.use_minified_css;
                var ext = (useMinCss == '1') ? '.min.css' : '.css';

                // Build the payment form CSS path
                var cssPath = window.checkoutConfig.payment.checkoutcom_magento2.checkoutcom_data.css_path;
                cssPath += folderPath + '/' + fileName + ext;

                // Append the CSS file
                $('head').append('<link rel="stylesheet" href="' + cssPath + '" type="text/css"/>');
            },

            /**
             * Load a remote JS file
             *
             * @return {void}
             */
            loadRemoteJs: function (jsUrl) {
                // Append the CSS file
                $('head').append('');
            },

            /**
             * Get the store name.
             *
             * @return {string}  The store name.
             */
            getStoreName: function () {
                return Config[KEY_DATA].store.name;
            },

            /**
             * Get the quote value.
             *
             * @return {float}  The quote value.
             */
            getQuoteValue: function () {
                var data = this.getRestQuoteData();
                var amount = parseFloat(data.totals.base_grand_total);

                return amount.toFixed(2);
            },

            /**
             * Get the updated quote data from the core REST API.
             *
             * @return {object}  The quote data.
             */
            getRestQuoteData: function () {
                // Prepare the required parameters
                var self = this;
                var result = null;
                var restUrl = window.BASE_URL;

                // Build the rest URL
                if (Customer.isLoggedIn()) {
                    restUrl += 'rest/default/V1/';
                    restUrl += 'carts/mine/payment-information';
                    restUrl += '?form_key=' + window.checkoutConfig.formKey;
                }
                else {
                    restUrl += 'rest/all/V1/guest-carts/' + window.checkoutConfig.quoteData.entity_id + '/payment-information';
                    restUrl += '?form_key=' + window.checkoutConfig.formKey;
                }

                // Set the event to update data on any button click
                $('button[type="submit"]')
                .off('click', self.getRestQuoteData)
                .on('click', self.getRestQuoteData);

                    // Send the AJAX request
                    $.ajax({
                        url: restUrl,
                        type: 'GET',
                        contentType: "application/json",
                        dataType: "json",
                        async: false,
                        showLoader: true,
                        success: function (data, status, xhr) {
                            result = data;
                        },
                        error: function (request, status, error) {
                            self.log(error);
                        }
                    });

                return result;
            },

            /**
             * Get the quote currency.
             *
             * @return {string}  The quote currency.
             */
            getQuoteCurrency: function () {
                return Config[KEY_DATA].quote.currency;
            },

            /**
             * Check if the payment method should be enabled by default.
             *
             * @return {void}
             */
            checkDefaultEnabled: function (methodId) {
                var userData = this.getValue('checkoutcom_data', 'user');

                // Select the previous payment method it's available.
                if (userData['previous_method'] == methodId && $('input#' + userData['previous_method']).length) {
                    $('input#' + userData['previous_method']).trigger('click');
                } else if (this.getValue(null, 'default_method') == methodId) {
                    $('input#' + methodId).trigger('click');
                }
            },

            /**
             * Check if APM should be auto selected.
             *
             * @return {void}
             */
            checkLastPaymentMethod: function () {
                var userData = this.getValue('checkoutcom_data', 'user');

                if (userData['previous_method'] == 'checkoutcom_apm'
                && $('.cko-apm#' + userData['previous_source']).length) {
                    // If available auto select the previously used apm.
                    $('.cko-apm#' + userData['previous_source']).trigger('click');
                } else if(userData['previous_method'] == 'checkoutcom_apm'
                && $('input#' + userData['previous_method']).length) {
                    // Select default payment method if apms are unavailable.
                    if ($('input#' + this.getValue(null, 'default_method')).length) {
                        $('input#' + this.getValue(null, 'default_method')).trigger('click');
                    }
                }
            },

            /**
             * Check if stored card should be auto selected.
             *
             * @return {void}
             */
            checkStoredCard: function () {
                var userData = this.getValue('checkoutcom_data', 'user');
                if (userData['previous_method'] == 'checkoutcom_vault'
                && $('input[name=\'publicHash\'][value=\''+userData['previous_source']+'\']').length) {
                    $('input[name=\'publicHash\'][value=\''+userData['previous_source']+'\']').trigger('click');
                }
            },

            /**
             * Checks if user has saved cards.
             *
             * @return {bool}
             */
            userHasCards: function () {
                return Config[KEY_DATA].user.has_cards;
            },

            /**
             * Gets the card form language fallback.
             *
             * @return {string}
             */
            getLangageFallback: function () {
                return Config[KEY_DATA].user.language_fallback;
            },

            /**
             * Get the card form shop language
             *
             * @return {string}
             */
            getShopLanguage: function() {
                let mageShopLanguage = Config[KEY_DATA].store.language;
                let framesLanguage;
                switch(mageShopLanguage) {
                    case 'nl_NL':
                        framesLanguage = 'NL-NL';
                        break;
                    case 'en_GB':
                         framesLanguage = 'EN-GB';
                        break;
                    case 'fr_FR':
                         framesLanguage = 'FR-FR';
                        break;
                    case 'de_DE':
                        framesLanguage = 'DE-DE';
                        break;
                    case 'it_IT':
                        framesLanguage = 'IT-IT';
                        break;
                    case 'ko_KR':
                        framesLanguage = 'KR-KR';
                        break;
                    case 'es_ES':
                        framesLanguage = 'ES-ES';
                        break;
                    default:
                      framesLanguage = this.getLangageFallback().toUpperCase().split("_").join('-');
                }

                return framesLanguage;
            },

            /**
             * Get the supported cards.
             *
             * @return {array}
             */
            getSupportedCards: function () {
                return Config[KEY_DATA].cards;
            },

            /**
             * Builds the controller URL.
             *
             * @param  {string}  path  The path
             * @return {string}
             */
            getUrl: function (path) {
                return Url.build('checkout_com/' + path);
            },

            /**
             * Customer name.
             *
             * @param  {bool} return in object format.
             * @return {mixed}  The billing address.
             */
            getCustomerName: function () {
                var billingAddress = Quote.billingAddress();
                var customerName = '';
                if (billingAddress) {
                    customerName += billingAddress.firstname;
                    customerName += ' ' + billingAddress.lastname;
                }

                return customerName;
            },

            /**
             * Billing address.
             *
             * @return {object}  The billing address.
             */
            getBillingAddress: function () {
                return Quote.billingAddress();
            },

            /**
             * @return {string}
             */
            getEmail: function () {
                var emailCookieName = this.getValue(null, 'email_cookie_name');
                return window.checkoutConfig.customerData.email
                || Quote.guestEmail
                || CheckoutData.getValidatedEmailValue()
                || $.cookie(emailCookieName);
            },

            /**
             * @return {void}
             */
            setEmail: function () {
                var userEmail = this.getEmail();
                var emailCookieName = this.getValue(null, 'email_cookie_name');
                $.cookie(emailCookieName, userEmail);

                // If no email found, observe the core email field
                if (!userEmail) {
                    $('#customer-email').off('change').on('change', function () {
                        userEmail = Quote.guestEmail || CheckoutData.getValidatedEmailValue();
                        $.cookie(emailCookieName, userEmail);
                    });
                }
            },

            /**
             * @return {object}
             */
            getPhone: function () {
                var billingAddress = Quote.billingAddress();

                return {
                    number: billingAddress.telephone
                };
            },

            /**
             * Handle error logging.
             */
            log: function (val) {
                if (this.getValue(null, 'debug')
                    && this.getValue(null, 'console_logging')
                ) {
                    console.log(val);
                }
            },

            /**
             * Show a message.
             */
            showMessage: function (type, message, methodId) {
                this.clearMessages(methodId);
                var messageContainer = this.getMethodContainer(methodId).find('.message-cko');
                messageContainer.addClass('message-' + type + ' ' + type);
                messageContainer.append('
' + __(message) + '
');
                messageContainer.show();
            },

            /**
             * Show debug message.
             */
            showDebugMessage: function (type, message, methodId) {
                var messageContainer = this.getMethodContainer(methodId).find('.debug-message');
                messageContainer.addClass('message-' + type + ' ' + type);
                messageContainer.append('

' + __(message) + '

');
                messageContainer.show();
            },

            /**
             * Show response code.
             */
            showResponseCode: function (type, message, methodId) {
                var messageContainer = this.getMethodContainer(methodId).find('.message-response-code');
                messageContainer.addClass('message-' + type + ' ' + type);
                messageContainer.append('
Response Code: ' + __(message) + '
');
                messageContainer.show();
            },

            /**
             * Clear all messages.
             */
            clearMessages: function (methodId) {
                var messageContainer = this.getMethodContainer(methodId).find('.message');
                messageContainer.hide();
                messageContainer.empty();
            },

            /**
             * Get a payment method container instance.
             */
            getMethodContainer: function (methodId) {
                return $('#' + methodId + '_container');
            },

            /**
             * Check if an URL is valid.
             */
            isUrl: function (str) {
                var pattern = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/;
                return pattern.test(str);
            },

            /**
             * Handle the place order button state.
             */
            allowPlaceOrder: function (buttonId, yesNo) {
                $('#' + buttonId).prop('disabled', !yesNo);
            },

            /**
             * Check if a payment option is active.
             */
            methodIsSelected: function (idSelector) {
                var id = idSelector.replace('#', '');
                var selected = CheckoutData.getSelectedPaymentMethod();
                return id == selected || selected == null;
            },

            /**
             * Place a new order.
             *
             * @return {void}
             */
            placeOrder: function (payload, methodId) {
                var self = this;

                // Start the loader
                FullScreenLoader.startLoader();

                // Send the request
                $.ajax({
                    type: 'POST',
                    url: self.getUrl('payment/placeorder'),
                    data: payload,
                    success: function (data) {
                        if (!data.success) {
                            FullScreenLoader.stopLoader();
                            self.showMessage('error', data.message, methodId);
                            if (data.debugMessage) {
                                self.showDebugMessage('error', data.debugMessage, methodId);
                            }
                            if (data.responseCode) {
                                self.showResponseCode('error', data.responseCode, methodId);
                            }
                            self.allowPlaceOrder(methodId + '_btn', false)
                        } else if (data.success && data.url) {
                            // Handle 3DS redirection
                            window.location.href = data.url;
                        } else {
                            // Normal redirection
                            RedirectOnSuccessAction.execute();
                        }
                    },
                    error: function (request, status, error) {
                        self.showMessage('error', error, methodId);
                        FullScreenLoader.stopLoader();
                    }
                });
            }
        };
    }
);<|endoftext|>


<commit_before>var express = require('express');
var app = express();

// Use the built-in express middleware for serving static files from './public'
app.use('/', express.static('build'));

// Start the server
var server = app.listen(process.env.PORT || '8080', function () {
  console.log('App listening on port %s', server.address().port);
});
<commit_msg>redirect static requests for gmas to root<commit_after>var express = require('express');
var app = express();

// Use the built-in express middleware for serving static files from './public'
app.use('/', express.static('build'));

// redirect to static react app
app.get('/gmas', (req, res) => {
  res.redirect('/');
});

// Start the server
var server = app.listen(process.env.PORT || '8080', function () {
  console.log('App listening on port %s', server.address().port);
});
<|endoftext|>


<commit_before>'use strict'

const fs = require('fs')
const path = require('path')

const config = require('../config.json')
const validate = require('./validate')

module.exports.render = function (type, data, callback) {
  let response = buildResponse(500, 'An error has occurred.', data)
  switch (type) {
    case 'honeypot':
      response = buildResponse(422, config.MSG_HONEYPOT || 'You shall not pass.', data)
      break
    case 'no-admin-email':
      response = buildResponse(422, config.MSG_NO_ADMIN_EMAIL || 'Form not sent, the admin has not set up a forwarding email address.', data)
      break
    case 'bad-admin-email':
      response = buildResponse(422, config.MSG_BAD_ADMIN_EMAIL || 'Form not sent, the admin email address is not valid.', data)
      break
    case 'error':
      response = buildResponse(500, config.MSG_ERROR || 'Form not sent, there was an error adding it to the database.', data)
      break
    case 'success':
      if (validate.hasRedirect(data)) {
        response = buildResponse(302, config.MSG_SUCCESS || 'Form submission successfully made.', data)
      } else {
        response = buildResponse(200, config.MSG_SUCCESS || 'Form submission successfully made.', data)
      }
      break
  }
  callback(null, response)
}

function buildResponse (statusCode, message, data) {
  if (validate.isJsonResponse(data)) {
    return buildJsonResponse(statusCode, message, data)
  }
  return buildHtmlResponse(statusCode, message, data)
}

function buildJsonResponse (statusCode, message, data) {
  let response = {
    statusCode: statusCode,
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      statusCode: statusCode,
      message: message
    })
  }
  return response
}

function buildHtmlResponse (statusCode, message, data) {
  let response = {
    statusCode: statusCode,
    headers: {
      'Content-Type': 'text/html'
    },
    body: generateView(message)
  }
  if (data['_redirect'] !== undefined) {
    response.headers.Location = data['_redirect']
  }
  return response
}

function generateView (message) {
  let template = fs.readFileSync(path.resolve(__dirname, 'template.html')).toString()
  if (!template) {
    return message
  }
  return template.replace('{{ message }}', message)
}
<commit_msg>tidy success response redirect code<commit_after>'use strict'

const fs = require('fs')
const path = require('path')

const config = require('../config.json')
const validate = require('./validate')

module.exports.render = function (type, data, callback) {
  let response = buildResponse(500, 'An error has occurred.', data)
  switch (type) {
    case 'honeypot':
      response = buildResponse(422, config.MSG_HONEYPOT || 'You shall not pass.', data)
      break
    case 'no-admin-email':
      response = buildResponse(422, config.MSG_NO_ADMIN_EMAIL || 'Form not sent, the admin has not set up a forwarding email address.', data)
      break
    case 'bad-admin-email':
      response = buildResponse(422, config.MSG_BAD_ADMIN_EMAIL || 'Form not sent, the admin email address is not valid.', data)
      break
    case 'error':
      response = buildResponse(500, config.MSG_ERROR || 'Form not sent, there was an error adding it to the database.', data)
      break
    case 'success':
      let successStatusCode = validate.hasRedirect(data) ? 302 : 200
      response = buildResponse(successStatusCode, config.MSG_SUCCESS || 'Form submission successfully made.', data)
      break
  }
  callback(null, response)
}

function buildResponse (statusCode, message, data) {
  if (validate.isJsonResponse(data)) {
    return buildJsonResponse(statusCode, message, data)
  }
  return buildHtmlResponse(statusCode, message, data)
}

function buildJsonResponse (statusCode, message, data) {
  let response = {
    statusCode: statusCode,
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      statusCode: statusCode,
      message: message
    })
  }
  return response
}

function buildHtmlResponse (statusCode, message, data) {
  let response = {
    statusCode: statusCode,
    headers: {
      'Content-Type': 'text/html'
    },
    body: generateView(message)
  }
  if (statusCode === 302) {
    response.headers.Location = data['_redirect']
  }
  return response
}

function generateView (message) {
  let template = fs.readFileSync(path.resolve(__dirname, 'template.html')).toString()
  if (!template) {
    return message
  }
  return template.replace('{{ message }}', message)
}
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var url = require('url');
var parxer = require('parxer').parxer;
var parxerPlugins = require('parxer/Plugins');
var _ = require('lodash');
var utils = require('../utils');
var htmlEntities = new (require('html-entities').AllHtmlEntities)();

var errorTemplate = _.template('
Error: <%= err %>
');
var debugScript = '';

function hasCxAttr(node, name) {
  return name in node.attribs || ('data-' + name in node.attribs);
}

function getCxAttr(node, name, defaultAttr) {
  var value = node.attribs[name] || node.attribs['data-' + name];

  if (value) {
    return htmlEntities.decode(value);
  }

  return defaultAttr === undefined ? false : defaultAttr;
}

function getNoCacheAttr(fragment) {
  /* jslint evil: true */
  var explicitNoCacheAttr = getCxAttr(fragment, 'cx-no-cache');
  return explicitNoCacheAttr ? eval(explicitNoCacheAttr) : false;
}

function getMiddleware(config, reliableGet, eventHandler, optionsTransformer) {
  function getCdnHost() {
    if (config.cdn.host) {
      return config.cdn.host;
    }
  }

  function getCdnUrl() {
    if (config.cdn.url) {
      return config.cdn.url;
    }
  }

  return function parserMiddleware(req, res, cb) {
    var fragmentId = 0;
    var contentId = 0;
    var templateVars = req.templateVars;
    var commonState = {}; // common between all parser
    var fragmentTimings = [];
    var defaultedToNoCache = false;
    var noCacheFragments = [];
    var responseStatusCode = 200;

    function getContent(fragment, next) {
      if (!(config.content && config.content.server)) {
        return next(null);
      }

      var tag = getCxAttr(fragment, 'cx-content');
      var url = config.content.server + '/' + tag;
      var cacheKeyAttr = getCxAttr(fragment, 'cx-cache-key');

      var opts = {
        url: url,
        timeout: config.content.timeout || 5000,
        cacheKey: cacheKeyAttr,
        statsdKey: 'content',
        statsdTags: ['application:drupal'],
        cacheTTL: utils.timeToMillis(getCxAttr(fragment, 'cx-cache-ttl', '1m'))
      };

      var logEvents = utils.isDebugEnabled(req) && utils.attachEventLogger(opts);
      reliableGet.get(opts, function (err, response) {
        if (err) {
          return next(errorTemplate({ err: err.message }));
        }

        var contentVars = {};
        try {
          contentVars = JSON.parse(response.content);
        } catch (e) {
          // Ignore parse error
        }
        var debugTag = utils.isDebugEnabled(req) ? utils.delimitContent('', response, opts, logEvents, 'content', contentId) : '';

        contentId++;
        /* server timing: content from drupal */
        if(utils.isDebugEnabled(req)) {
          utils.appendServerTimings(res, utils.getServerTimingName('content:' + contentId, response), response.realTiming);
        }
        _.each(contentVars, function (value, key) {
          templateVars['content:' + tag + ':' + key] = value;
          templateVars['content:' + tag + ':' + key + ':encoded'] = encodeURI(value);
        });

        next(null, '' + debugTag);
      });
    }

    function getSlot(fragment, next) {
      var slotName = getCxAttr(fragment, 'cx-define-slot');
      var content = templateVars.slots[slotName];
      parxer(getParxerOpts(0), content, function (parxerErr, fragmentCount, newContent) {
        if (parxerErr && parxerErr.content) {
          return next(parxerErr, content);
        }
        return next(null, newContent);
      });
    }

    function getCookie() {
      if (req.cookies && req.headers.cookie) {
        var whitelist = config.cookies && config.cookies.whitelist;
        return whitelist ? utils.filterCookies(whitelist, req.cookies) : req.headers.cookie;
      }
    }

    function setResponseHeaders(headers, fragment) {
      if (res.headersSent) {
        return; // ignore late joiners
      }

      // A fragment is telling us to not cache it. We force the entire backend/composed page to not be cached.
      var cacheControlHeader = headers['cache-control'];
      if (cacheControlHeader && (cacheControlHeader.indexOf('no-cache') !== -1 || cacheControlHeader.indexOf('no-store') !== -1)) {
        // Use the fragment's cache-control header only if we don't already have one from the backend, or
        // it does not have no-cache or no-store.
        var backendCacheControl = res.getHeader('cache-control');
        if (!backendCacheControl || (backendCacheControl.indexOf('no-cache') === -1 && backendCacheControl.indexOf('no-store') === -1)) {
          var defaultCacheControl = config.cache.defaultNoCacheHeaders && config.cache.defaultNoCacheHeaders['cache-control'];
          res.setHeader('cache-control', defaultCacheControl || 'private, no-cache, max-age=0, must-revalidate, no-store');
          defaultedToNoCache = true;
        }
        if (defaultedToNoCache) {
          // Push into list so we can add a debug header later
          noCacheFragments.push(getCxAttr(fragment, 'cx-url-raw'));
        }
      }

      if (headers['set-cookie']) {
        var existingResponseCookies = res.getHeader('set-cookie') || [];
        res.setHeader('set-cookie', _.union(existingResponseCookies, headers['set-cookie']));
      }
    }

    function logError(err, message, ignoreError) {
      var logLevel = (err.statusCode === 404 || ignoreError) ? 'warn' : 'error';

      if (typeof eventHandler.logger === 'function') {
        eventHandler.logger(logLevel, message, { tracer: req.tracer });
      }
    }

    function getParxerOpts(depth) {
      function getCx(fragment, next) {
        var start = Date.now();
        var parseMeTag = hasCxAttr(fragment, 'cx-parse-me');
        var cxUrl = getCxAttr(fragment, 'cx-url');
        var cacheKeyAttr = getCxAttr(fragment, 'cx-cache-key');
        var ignoreError = getCxAttr(fragment, 'cx-ignore-error');

        function responseCallback(err, content, headers) {
          if (headers) {
            var newTemplateVars = utils.formatTemplateVariables(headers);
            _.assign(templateVars, newTemplateVars);
            setResponseHeaders(headers, fragment);
          }

          if (err || !content) {
            return next(err, content, headers);
          }

          if (depth > (config.fragmentDepth || 5)) {
            return next(err, content, headers);
          }

          if (!parseMeTag && (!headers || !headers['cx-parse-me'])) {
            return next(err, content, headers);
          }

          parxer(getParxerOpts(depth + 1), content, function (parxerErr, fragmentCount, newContent) {
            if (parxerErr && parxerErr.content) {
              return next(parxerErr, content, headers);
            }
            return next(null, newContent, headers);
          });
        }

        function onErrorHandler(err, oldCacheData, transformedOptions) {
          var errorMsg;
          var elapsed = Date.now() - req.timerStart;
          var timing = Date.now() - start;

          // Check to see if we are just ignoring errors completely for this fragment
          if (ignoreError && (ignoreError === 'true' || _.includes(ignoreError.split(','), String(err.statusCode)))) {
            errorMsg = _.template('IGNORE <%= statusCode %> for Service <%= url %> cache <%= cacheKey %>.');
            logError(err, errorMsg({ url: transformedOptions.url, cacheKey: transformedOptions.cacheKey, statusCode: err.statusCode }), true);
            return responseCallback(errorTemplate({ err: err.message }));
          }

          // Check to see if we have any statusCode handlers defined
          if (err.statusCode && config.statusCodeHandlers && config.statusCodeHandlers[err.statusCode]) {
            var handlerDefn = config.statusCodeHandlers[err.statusCode];
            var handlerFn = config.functions && config.functions[handlerDefn.fn];

            if (handlerFn) {
              return handlerFn(req, res, req.templateVars, handlerDefn.data, transformedOptions, err, responseCallback);
            }
          }

          if (err.statusCode === 404 && !transformedOptions.ignore404) {
            errorMsg = _.template('404 Service <%= url %> cache <%= cacheKey %> returned 404.');
            logError(err, errorMsg({ url: transformedOptions.url, cacheKey: transformedOptions.cacheKey }));

            if (res.headersSent) {
              return;
            }

            res.writeHead(404, { 'Content-Type': 'text/html' });
            return res.end(errorMsg(transformedOptions));
          }

          if (oldCacheData && oldCacheData.content) {
            responseCallback(errorTemplate({ err: err.message }), oldCacheData.content, oldCacheData.headers);
            errorMsg = _.template('STALE <%= url %> cache <%= cacheKey %> failed but serving stale content.');
            logError(err, errorMsg(transformedOptions));
          } else {
            responseCallback(errorTemplate({ err: err.message }));
          }

          if (typeof eventHandler.stats === 'function') {
            eventHandler.stats('increment', transformedOptions.statsdKey + '.errors', transformedOptions.statsdTags);
          }

          errorMsg = _.template('FAIL <%= url %> did not respond in <%= timing%>, elapsed <%= elapsed %>. Reason: ' + err.message);
          logError(err, errorMsg({ url: transformedOptions.url, timing: timing, elapsed: elapsed }));
        }

        var options = {
          url: cxUrl,
          timeout: utils.timeToMillis(getCxAttr(fragment, 'cx-timeout', '5s')),
          cacheKey: cacheKeyAttr,
          cacheTTL: utils.timeToMillis(getCxAttr(fragment, 'cx-cache-ttl', '1m')),
          explicitNoCache: req.explicitNoCache || getNoCacheAttr(fragment),
          ignore404: getCxAttr(fragment, 'cx-ignore-404', 'true') === 'true',
          type: 'fragment',
          tracer: req.tracer,
          statsdKey: 'fragment',
          statsdTags: ['application:' + utils.getServiceNameFromUrl(cxUrl)],
          headers: {
            accept: getCxAttr(fragment, 'cx-accept', 'text/html'),
            cookie: getCookie(),
            'cx-page-url': templateVars['url:href'],
            'x-tracer': req.tracer,
            'x-cdn-host': getCdnHost(),
            'x-cdn-url': getCdnUrl(),
            'x-site-country': req.headers['x-site-country'],
            'x-site-language': req.headers['x-site-language']
          }
        };

        function transformerCallback(err, transformedOptions) {
          if (err) {
            return onErrorHandler(err, {}, transformedOptions);
          }
          var logEvents = utils.isDebugEnabled(req) && utils.attachEventLogger(transformedOptions);

          reliableGet.get(transformedOptions, function getCallback(getErr, response) {
            if (getErr) {
              return onErrorHandler(getErr, response, transformedOptions);
            }
            fragmentId++;

            fragmentTimings.push({ url: cxUrl, status: response.statusCode, timing: response.timing });

            var content = utils.isDebugEnabled(req) ? utils.delimitContent(response.content, response, options, logEvents, 'fragment', fragmentId) : response.content;
            /* server timing: fragment */
            if(utils.isDebugEnabled(req)) {
              utils.appendServerTimings(res, utils.getServerTimingName('fragment:' + fragmentId, response), response.realTiming);
            }
            responseCallback(null, content, response.headers);
          });
        }

        optionsTransformer(req, options, transformerCallback);
      }

      var baseURL = config.getBaseURL ? config.getBaseURL(req) : url.format({ protocol: req.protocol, host: req.get('host') });

      return {
        environment: config.environment,
        baseURL: config.useRelativeURL ? baseURL : undefined,
        cdn: config.cdn,
        minified: config.minified,
        showErrors: !req.backend.quietFailure,
        timeout: utils.timeToMillis(req.backend.timeout || '5000'),
        parserTimeout: utils.timeToMillis(req.backend.parserTimeout || '5000'),
        plugins: [
          parxerPlugins.Test,
          parxerPlugins.If,
          parxerPlugins.Url(getCx),
          parxerPlugins.Image(),
          parxerPlugins.Bundle(getCx),
          parxerPlugins.Content(getContent),
          parxerPlugins.ContentItem,
          parxerPlugins.DefineSlot(getSlot),
          parxerPlugins.Library(getCx)
        ],
        variables: templateVars,
        commonState: commonState
      };
    }

    function parseCallback(err, fragmentIndex, content) {
      if (err) {
        // Overall errors
        if (!res.headersSent && err.content) {
          res.writeHead(err.statusCode || 500, { 'Content-Type': 'text/html' });
          return res.end(err.content);
        }

        if (err.fragmentErrors) {
          // TODO: Notify fragment errors to debugger in future
        }

        if (err.statistics && config.functions && config.functions.statisticsHandler) {
          // Send stats to the stats handler if it is defined
          config.functions.statisticsHandler(req.backend, err.statistics);
        }
      }

      if (!res.headersSent) {
        if (noCacheFragments.length) {
          commonState.additionalHeaders['cx-notice'] = 'cache-control defaulted due to fragment nocache: ' + noCacheFragments.join(', ');
        }
        res.writeHead(responseStatusCode, _.assign({ 'Content-Type': 'text/html' }, commonState.additionalHeaders || {}));
        if (utils.isDebugEnabled(req)) {
          return res.end(content.replace('', res.debugInfo + debugScript + ''));
        }

        return res.end(content);
      }
    }

    res.parse = function parse(data, statusCode) {
      responseStatusCode = statusCode || responseStatusCode;
      parxer(getParxerOpts(1), data, parseCallback);
    };

    cb();
  };
}

module.exports = {
  getMiddleware: getMiddleware
};
<commit_msg>Catch TypeError with set header when invalid characters<commit_after>var fs = require('fs');
var path = require('path');
var url = require('url');
var parxer = require('parxer').parxer;
var parxerPlugins = require('parxer/Plugins');
var _ = require('lodash');
var utils = require('../utils');
var htmlEntities = new (require('html-entities').AllHtmlEntities)();

var errorTemplate = _.template('
Error: <%= err %>
');
var debugScript = '';

function hasCxAttr(node, name) {
  return name in node.attribs || ('data-' + name in node.attribs);
}

function getCxAttr(node, name, defaultAttr) {
  var value = node.attribs[name] || node.attribs['data-' + name];

  if (value) {
    return htmlEntities.decode(value);
  }

  return defaultAttr === undefined ? false : defaultAttr;
}

function getNoCacheAttr(fragment) {
  /* jslint evil: true */
  var explicitNoCacheAttr = getCxAttr(fragment, 'cx-no-cache');
  return explicitNoCacheAttr ? eval(explicitNoCacheAttr) : false;
}

function getMiddleware(config, reliableGet, eventHandler, optionsTransformer) {
  function getCdnHost() {
    if (config.cdn.host) {
      return config.cdn.host;
    }
  }

  function getCdnUrl() {
    if (config.cdn.url) {
      return config.cdn.url;
    }
  }

  return function parserMiddleware(req, res, cb) {
    var fragmentId = 0;
    var contentId = 0;
    var templateVars = req.templateVars;
    var commonState = {}; // common between all parser
    var fragmentTimings = [];
    var defaultedToNoCache = false;
    var noCacheFragments = [];
    var responseStatusCode = 200;

    function getContent(fragment, next) {
      if (!(config.content && config.content.server)) {
        return next(null);
      }

      var tag = getCxAttr(fragment, 'cx-content');
      var url = config.content.server + '/' + tag;
      var cacheKeyAttr = getCxAttr(fragment, 'cx-cache-key');

      var opts = {
        url: url,
        timeout: config.content.timeout || 5000,
        cacheKey: cacheKeyAttr,
        statsdKey: 'content',
        statsdTags: ['application:drupal'],
        cacheTTL: utils.timeToMillis(getCxAttr(fragment, 'cx-cache-ttl', '1m'))
      };

      var logEvents = utils.isDebugEnabled(req) && utils.attachEventLogger(opts);
      reliableGet.get(opts, function (err, response) {
        if (err) {
          return next(errorTemplate({ err: err.message }));
        }

        var contentVars = {};
        try {
          contentVars = JSON.parse(response.content);
        } catch (e) {
          // Ignore parse error
        }
        var debugTag = utils.isDebugEnabled(req) ? utils.delimitContent('', response, opts, logEvents, 'content', contentId) : '';

        contentId++;
        /* server timing: content from drupal */
        if(utils.isDebugEnabled(req)) {
          utils.appendServerTimings(res, utils.getServerTimingName('content:' + contentId, response), response.realTiming);
        }
        _.each(contentVars, function (value, key) {
          templateVars['content:' + tag + ':' + key] = value;
          templateVars['content:' + tag + ':' + key + ':encoded'] = encodeURI(value);
        });

        next(null, '' + debugTag);
      });
    }

    function getSlot(fragment, next) {
      var slotName = getCxAttr(fragment, 'cx-define-slot');
      var content = templateVars.slots[slotName];
      parxer(getParxerOpts(0), content, function (parxerErr, fragmentCount, newContent) {
        if (parxerErr && parxerErr.content) {
          return next(parxerErr, content);
        }
        return next(null, newContent);
      });
    }

    function getCookie() {
      if (req.cookies && req.headers.cookie) {
        var whitelist = config.cookies && config.cookies.whitelist;
        return whitelist ? utils.filterCookies(whitelist, req.cookies) : req.headers.cookie;
      }
    }

    function setResponseHeaders(headers, fragment) {
      if (res.headersSent) {
        return; // ignore late joiners
      }

      // A fragment is telling us to not cache it. We force the entire backend/composed page to not be cached.
      var cacheControlHeader = headers['cache-control'];
      if (cacheControlHeader && (cacheControlHeader.indexOf('no-cache') !== -1 || cacheControlHeader.indexOf('no-store') !== -1)) {
        // Use the fragment's cache-control header only if we don't already have one from the backend, or
        // it does not have no-cache or no-store.
        var backendCacheControl = res.getHeader('cache-control');
        if (!backendCacheControl || (backendCacheControl.indexOf('no-cache') === -1 && backendCacheControl.indexOf('no-store') === -1)) {
          var defaultCacheControl = config.cache.defaultNoCacheHeaders && config.cache.defaultNoCacheHeaders['cache-control'];
          res.setHeader('cache-control', defaultCacheControl || 'private, no-cache, max-age=0, must-revalidate, no-store');
          defaultedToNoCache = true;
        }
        if (defaultedToNoCache) {
          // Push into list so we can add a debug header later
          noCacheFragments.push(getCxAttr(fragment, 'cx-url-raw'));
        }
      }

      if (headers['set-cookie']) {
        var existingResponseCookies = res.getHeader('set-cookie') || [];
        res.setHeader('set-cookie', _.union(existingResponseCookies, headers['set-cookie']));
      }
    }

    function logError(err, message, ignoreError) {
      var logLevel = (err.statusCode === 404 || ignoreError) ? 'warn' : 'error';

      if (typeof eventHandler.logger === 'function') {
        eventHandler.logger(logLevel, message, { tracer: req.tracer });
      }
    }

    function getParxerOpts(depth) {
      function getCx(fragment, next) {
        var start = Date.now();
        var parseMeTag = hasCxAttr(fragment, 'cx-parse-me');
        var cxUrl = getCxAttr(fragment, 'cx-url');
        var cacheKeyAttr = getCxAttr(fragment, 'cx-cache-key');
        var ignoreError = getCxAttr(fragment, 'cx-ignore-error');

        function responseCallback(err, content, headers) {
          if (headers) {
            var newTemplateVars = utils.formatTemplateVariables(headers);
            _.assign(templateVars, newTemplateVars);
            setResponseHeaders(headers, fragment);
          }

          if (err || !content) {
            return next(err, content, headers);
          }

          if (depth > (config.fragmentDepth || 5)) {
            return next(err, content, headers);
          }

          if (!parseMeTag && (!headers || !headers['cx-parse-me'])) {
            return next(err, content, headers);
          }

          parxer(getParxerOpts(depth + 1), content, function (parxerErr, fragmentCount, newContent) {
            if (parxerErr && parxerErr.content) {
              return next(parxerErr, content, headers);
            }
            return next(null, newContent, headers);
          });
        }

        function onErrorHandler(err, oldCacheData, transformedOptions) {
          var errorMsg;
          var elapsed = Date.now() - req.timerStart;
          var timing = Date.now() - start;

          // Check to see if we are just ignoring errors completely for this fragment
          if (ignoreError && (ignoreError === 'true' || _.includes(ignoreError.split(','), String(err.statusCode)))) {
            errorMsg = _.template('IGNORE <%= statusCode %> for Service <%= url %> cache <%= cacheKey %>.');
            logError(err, errorMsg({ url: transformedOptions.url, cacheKey: transformedOptions.cacheKey, statusCode: err.statusCode }), true);
            return responseCallback(errorTemplate({ err: err.message }));
          }

          // Check to see if we have any statusCode handlers defined
          if (err.statusCode && config.statusCodeHandlers && config.statusCodeHandlers[err.statusCode]) {
            var handlerDefn = config.statusCodeHandlers[err.statusCode];
            var handlerFn = config.functions && config.functions[handlerDefn.fn];

            if (handlerFn) {
              return handlerFn(req, res, req.templateVars, handlerDefn.data, transformedOptions, err, responseCallback);
            }
          }

          if (err.statusCode === 404 && !transformedOptions.ignore404) {
            errorMsg = _.template('404 Service <%= url %> cache <%= cacheKey %> returned 404.');
            logError(err, errorMsg({ url: transformedOptions.url, cacheKey: transformedOptions.cacheKey }));

            if (res.headersSent) {
              return;
            }

            res.writeHead(404, { 'Content-Type': 'text/html' });
            return res.end(errorMsg(transformedOptions));
          }

          if (oldCacheData && oldCacheData.content) {
            responseCallback(errorTemplate({ err: err.message }), oldCacheData.content, oldCacheData.headers);
            errorMsg = _.template('STALE <%= url %> cache <%= cacheKey %> failed but serving stale content.');
            logError(err, errorMsg(transformedOptions));
          } else {
            responseCallback(errorTemplate({ err: err.message }));
          }

          if (typeof eventHandler.stats === 'function') {
            eventHandler.stats('increment', transformedOptions.statsdKey + '.errors', transformedOptions.statsdTags);
          }

          errorMsg = _.template('FAIL <%= url %> did not respond in <%= timing%>, elapsed <%= elapsed %>. Reason: ' + err.message);
          logError(err, errorMsg({ url: transformedOptions.url, timing: timing, elapsed: elapsed }));
        }

        var options = {
          url: cxUrl,
          timeout: utils.timeToMillis(getCxAttr(fragment, 'cx-timeout', '5s')),
          cacheKey: cacheKeyAttr,
          cacheTTL: utils.timeToMillis(getCxAttr(fragment, 'cx-cache-ttl', '1m')),
          explicitNoCache: req.explicitNoCache || getNoCacheAttr(fragment),
          ignore404: getCxAttr(fragment, 'cx-ignore-404', 'true') === 'true',
          type: 'fragment',
          tracer: req.tracer,
          statsdKey: 'fragment',
          statsdTags: ['application:' + utils.getServiceNameFromUrl(cxUrl)],
          headers: {
            accept: getCxAttr(fragment, 'cx-accept', 'text/html'),
            cookie: getCookie(),
            'cx-page-url': templateVars['url:href'],
            'x-tracer': req.tracer,
            'x-cdn-host': getCdnHost(),
            'x-cdn-url': getCdnUrl(),
            'x-site-country': req.headers['x-site-country'],
            'x-site-language': req.headers['x-site-language']
          }
        };

        function transformerCallback(err, transformedOptions) {
          if (err) {
            return onErrorHandler(err, {}, transformedOptions);
          }
          var logEvents = utils.isDebugEnabled(req) && utils.attachEventLogger(transformedOptions);

          reliableGet.get(transformedOptions, function getCallback(getErr, response) {
            if (getErr) {
              return onErrorHandler(getErr, response, transformedOptions);
            }
            fragmentId++;

            fragmentTimings.push({ url: cxUrl, status: response.statusCode, timing: response.timing });

            var content = utils.isDebugEnabled(req) ? utils.delimitContent(response.content, response, options, logEvents, 'fragment', fragmentId) : response.content;
            /* server timing: fragment */
            if(utils.isDebugEnabled(req)) {
              utils.appendServerTimings(res, utils.getServerTimingName('fragment:' + fragmentId, response), response.realTiming);
            }
            responseCallback(null, content, response.headers);
          });
        }

        optionsTransformer(req, options, transformerCallback);
      }

      var baseURL = config.getBaseURL ? config.getBaseURL(req) : url.format({ protocol: req.protocol, host: req.get('host') });

      return {
        environment: config.environment,
        baseURL: config.useRelativeURL ? baseURL : undefined,
        cdn: config.cdn,
        minified: config.minified,
        showErrors: !req.backend.quietFailure,
        timeout: utils.timeToMillis(req.backend.timeout || '5000'),
        parserTimeout: utils.timeToMillis(req.backend.parserTimeout || '5000'),
        plugins: [
          parxerPlugins.Test,
          parxerPlugins.If,
          parxerPlugins.Url(getCx),
          parxerPlugins.Image(),
          parxerPlugins.Bundle(getCx),
          parxerPlugins.Content(getContent),
          parxerPlugins.ContentItem,
          parxerPlugins.DefineSlot(getSlot),
          parxerPlugins.Library(getCx)
        ],
        variables: templateVars,
        commonState: commonState
      };
    }

    function parseCallback(err, fragmentIndex, content) {
      if (err) {
        // Overall errors
        if (!res.headersSent && err.content) {
          res.writeHead(err.statusCode || 500, { 'Content-Type': 'text/html' });
          return res.end(err.content);
        }

        if (err.fragmentErrors) {
          // TODO: Notify fragment errors to debugger in future
        }

        if (err.statistics && config.functions && config.functions.statisticsHandler) {
          // Send stats to the stats handler if it is defined
          config.functions.statisticsHandler(req.backend, err.statistics);
        }
      }

      if (!res.headersSent) {
        if (noCacheFragments.length) {
          commonState.additionalHeaders['cx-notice'] = 'cache-control defaulted due to fragment nocache: ' + noCacheFragments.join(', ');
        }
        try {
          res.writeHead(responseStatusCode, _.assign({ 'Content-Type': 'text/html' }, commonState.additionalHeaders || {}));
        } catch(ex) {
          eventHandler.logger('error', 'TypeError, unable to set response headers due to invalid character: ' + JSON.stringify(commonState.additionalHeaders));
        }
        if (utils.isDebugEnabled(req)) {
          return res.end(content.replace('', res.debugInfo + debugScript + ''));
        }
        return res.end(content);
      }
    }

    res.parse = function parse(data, statusCode) {
      responseStatusCode = statusCode || responseStatusCode;
      parxer(getParxerOpts(1), data, parseCallback);
    };

    cb();
  };
}

module.exports = {
  getMiddleware: getMiddleware
};
<|endoftext|>


<commit_before>"use strict";

let mongoose = require("mongoose");
let util = require("util");

exports.configure = services => {

  services.registerFactory("database", () => {
    let config = services.get("config");
    let host = config.get("storage.mongodb.host", "localhost");
    let database = config.get("storage.mongodb.database");
    mongoose.connect(util.format("mongodb://%s/%s", host, database));
    return mongoose;
  });

  services.get("event.manager").on("app.ready", () => {
    Object.keys(mongoose.models).forEach(model_id => {
      services.get("param.converter").set(model_id, value => {
        let Model = mongoose.model(model_id);
        if (value === null) {
          return Promise.accept(new Model);
        } else {
          return Model.findById(value);
        }
      });
    })
  });
};
<commit_msg>Utility class for mapping search form data into a Mongo query<commit_after>"use strict";

let mongoose = require("mongoose");
let util = require("util");

/**
 * Helper class for compiling a search query (form data) into a Mongo query.
 */
class SearchCompiler {
  constructor(rules, defaults) {
    if (!rules) {
      throw new Error("Must define compiler rules");
    }

    this.defaults = defaults || {};

    if (Array.isArray(rules)) {
      this.rules = new Map(rules);
    } else if (typeof rules == "object") {
      this.rules = new Map(Object.keys(rules).map(key => [key, rules[key]]));
    }
  }

  compile(params) {
    let query = typeof this.defaults == "function" ? this.defaults() : this.defaults;
    this.rules.forEach((callback, field) => {
      if (params.hasOwnProperty(field)) {
        let partial = callback(params[field], params);
        Object.keys(partial).forEach(key => {
          query[key] = partial[key];
        });
      }
    });
    return query;
  }
}

exports.SearchCompiler = SearchCompiler;

exports.configure = services => {

  services.registerFactory("database", () => {
    let config = services.get("config");
    let host = config.get("storage.mongodb.host", "localhost");
    let database = config.get("storage.mongodb.database");
    mongoose.connect(util.format("mongodb://%s/%s", host, database));
    return mongoose;
  });

  services.get("event.manager").on("app.ready", () => {
    Object.keys(mongoose.models).forEach(model_id => {
      services.get("param.converter").set(model_id, value => {
        let Model = mongoose.model(model_id);
        if (value === null) {
          return Promise.accept(new Model);
        } else {
          return Model.findById(value);
        }
      });
    })
  });
};
<|endoftext|>