youtube_cookies-2.0.x-dev/js/youtube-cookies.js

js/youtube-cookies.js
/**
 * @file
 * Do not let videos be played until user accept cookies.
 */

/**
 * Manage YouTube cookies on the page played videos.
 *
 * @param {string} action
 *   Block YouTube videos until accept cookies / use youtube-nocookie domain.
 * @param {string} thumbnailLabel
 *   Message for the button that open the consent popup.
 * @param {string} popupMessage
 *   Message that explains the cookies tracking.
 * @param {string} manageButton
 *   Allow managing cookies.
 * @param {string} okButton
 *   Accept YouTube's tracking.
 * @param {string} exitButton
 *   Remove the button (only appears in popup).
 * @param {string} thumbnailMarkup
 *   HTML markup for the YouTube cookies thumbnail.
 *
 * @constructor
 */
function YoutubeCookies(
  action,
  thumbnailLabel,
  popupMessage,
  manageButton,
  okButton,
  exitButton,
  thumbnailMarkup
) {
  this.checkYoutubeNoCookie = action !== "no_cookies_domain";
  this.action = action;
  this.youtubeCookiesAccepted = false;
  this.manageCookiesCallback = null;
  this.acceptedCookiesCallback = null;
  this.thumbnailLabel = thumbnailLabel;
  this.popupMessage = popupMessage;
  this.thumbnailMarkup = thumbnailMarkup;
  this.showThirdPartyButton = true;
  this.manageButton = manageButton;
  this.okButton = okButton;
  this.exitButton = exitButton;
  this.currentVideo = null;
  this.interceptVideo = true;
  this.setup();
}

const youtubeCookiesDisabled = new Event("youtubeCookiesDisabled");
const youtubeCookiesEnabled = new Event("youtubeCookiesEnabled");

/**
 * Decide whether the manage cookies button should be shown.
 *
 * @param {boolean} value
 *   If false, the 'Manage cookies' button won't appear.
 */
YoutubeCookies.prototype.setShowThirdPartyButton = value => {
  this.showThirdPartyButton = value;
};

/**
 * Callback that allow third parties show the manage cookies form.
 *
 * @param {function} callback
 *   Callback that show the form.
 */
YoutubeCookies.prototype.setManageCookiesCallback = function (callback) {
  this.manageCookiesCallback = callback;
};

/**
 * Check that cookies are accepted.
 *
 * It allows to use a custom callback to determine that.
 *
 * @return {boolean|*}
 *   TRUE when the cookies are accepted.
 */
YoutubeCookies.prototype.areCookiesAccepted = () => {
  if (this.acceptedCookiesCallback !== null) {
    return this.acceptedCookiesCallback();
  }
  return this.youtubeCookiesAccepted;
};

/**
 * Set up the YouTube videos.
 *
 * Show / hide videos whether the
 * cookies have been accepted.
 *
 * @param {boolean} youtubeCookiesAccepted
 *   True or false according the provider value.
 */
YoutubeCookies.prototype.setAcceptedCookies = youtubeCookiesAccepted => {
  if (youtubeCookiesAccepted) {
    document
      .querySelectorAll("iframe[data-yc-iframe-processed]")
      .forEach(element => {
        Drupal.youtubeCookies.destroyYoutuebeCookiesIframe(element.parentNode);
      });
    Drupal.youtubeCookies.removePopup();
    if (Drupal.youtubeCookies.currentVideo !== null) {
      // Propagate the click event to the oembed-lazy original button.
      if (Drupal.youtubeCookies.isOembedLazyload(Drupal.youtubeCookies.currentVideo)) {
        Drupal.youtubeCookies.currentVideo.querySelector(".oembed-lazyload__button").click();
      }
      Drupal.youtubeCookies.interceptVideo = true;
      Drupal.youtubeCookies.currentVideo = null;
    }
    document.dispatchEvent(youtubeCookiesDisabled);
  } else {
    Drupal.youtubeCookies.processYoutubeIframes(document);
    document.dispatchEvent(youtubeCookiesEnabled);
  }
  Drupal.youtubeCookies.youtubeCookiesAccepted = youtubeCookiesAccepted;
};

/**
 * Set the callback that will be called when the cookies are not accepted.
 *
 * @param {function} callback
 *   Funcgtion to execute.
 */
YoutubeCookies.prototype.onNonAcceptedCookies = callback => {
  this.onNonAcceptedCookiesCallback = callback;
};

/**
 * Set up the cookies' management.
 */
YoutubeCookies.prototype.setup = function() {
  document.addEventListener(
    "DOMContentLoaded",
    this.onContentLoaded.bind(this)
  );
};

/**
 * Adds the YouTube cookies popup.
 *
 * @param {object} element
 *   Element that will have the popup.
 * @param {boolean} showExitButton
 *   Weather to show the exit button or not.
 *
 * @return {HTMLDivElement}
 *   Popup HTML
 */
YoutubeCookies.prototype.addPopup = function(element, showExitButton) {
  const popupContainer = document.createElement("div");
  popupContainer.classList.add("youtube-cookies-popup");
  const popupContainerBox = document.createElement("div");
  popupContainerBox.classList.add("youtube-cookies-popup-box");
  const popupContainerBoxInfo = document.createElement("div");
  popupContainerBoxInfo.classList.add("youtube-cookies-popup-box-info");
  popupContainerBoxInfo.insertAdjacentHTML("beforeend", this.popupMessage);
  const popupContainerBoxButtons = document.createElement("div");
  popupContainerBoxButtons.classList.add("youtube-cookies-popup-box-buttons");
  if (this.showThirdPartyButton) {
    popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.manageButton);
  }
  popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.okButton);
  if (showExitButton) {
    popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.exitButton);
  }
  popupContainerBoxInfo.appendChild(popupContainerBoxButtons);
  popupContainerBox.appendChild(popupContainerBoxInfo);
  popupContainer.appendChild(popupContainerBox);
  element.append(popupContainer);
  popupContainer
    .querySelector(".youtube-cookies-button--accept")
    .addEventListener("click", this.onAcceptClick.bind(this));
  if (showExitButton) {
    popupContainer
      .querySelector(".youtube-cookies-button--reject")
      .addEventListener("click", this.onRejectClick.bind(this));
  }
  const manageCookiesButton = popupContainer.querySelector(
    ".youtube-cookies-button--manage"
  );
  if (manageCookiesButton != null) {
    manageCookiesButton.addEventListener(
      "click",
      this.onManageCookiesClick.bind(this)
    );
  }
  return popupContainer;
};

/**
 * Actions to do when the fake/yc thumbnail is clicked.
 *
 * @param {object} event
 *   Event.
 */
YoutubeCookies.prototype.onThumbnailClick = function(event) {
  if (!this.interceptVideo) {
    // @TODO: Check if this condition is needed.
    return;
  }

  event.preventDefault();
  this.addPopup(document.body, true);
  this.currentVideo = event.currentTarget.parentNode;
  this.interceptVideo = false;
};

/**
 * Remove the fake thumbnail added by yc.
 *
 * @param {object} iframeWrapper
 *   The iframe container.
 */
YoutubeCookies.prototype.removeFakeThumbnail = function(iframeWrapper) {
  iframeWrapper.querySelector("[data-yc-fake-button]").remove();
  if (this.isOembedLazyload(iframeWrapper)) {
    const button = iframeWrapper.querySelector(
      "[data-yc-original-oembed-button]"
    );
    button.style.display = "";
    button.removeAttribute("data-yc-original-oembed-button");
  }
};

/**
 * Dispatch event to allow other modules to add
 * their specific behaviours when the thumbnail
 * button is clicked.
 *
 * @param {object} button
 *   Element that received the click.
 */
YoutubeCookies.prototype.eventDispatch = function(button) {
  const iframeWrapper = button.currentTarget.parentNode;
  const event = new Event("youtube-cookies-thumbnail-click");
  iframeWrapper.querySelector("iframe").dispatchEvent(event);
};

/**
 * Remove the popup.
 */
YoutubeCookies.prototype.removePopup = function() {
  const popup = document.querySelector(".youtube-cookies-popup");
  if (popup !== null) {
    popup.remove();
  }
};

/**
 * When manage cookies is clicked show the manage cookies button.
 */
YoutubeCookies.prototype.onManageCookiesClick = function() {
  if (this.manageCookiesCallback !== null) {
    this.manageCookiesCallback();
  }
  this.interceptVideo = true;
};

/**
 * When cookies are accepted videos can be played.
 *
 * It only works in the current page , after refreshing
 * consent will be needed again. Consent will only stop
 * after managing third party cookies.
 *
 * @param {object} event
 *   Event.
 */
YoutubeCookies.prototype.onAcceptClick = function(event) {
  this.removePopup();

  if (this.currentVideo !== null) {
    this.destroyYoutuebeCookiesIframe(this.currentVideo);

    // Propagate the click event to the oembed-lazy original button.
    if (this.isOembedLazyload(this.currentVideo)) {
      this.currentVideo.querySelector(".oembed-lazyload__button").click();
    }
    this.interceptVideo = true;
    this.currentVideo = null;
  }
};

/**
 * When cookies are rejected the popup is removed.
 *
 * @param {object} event
 *   Event.
 */
YoutubeCookies.prototype.onRejectClick = function(event) {
  this.removePopup();
  this.currentVideo = null;
  this.interceptVideo = true;
};

/**
 * Add a wrapper div to the YouTube iframe to easily add the thumbnail.
 *
 * @param {object} iframe
 *   Iframe.
 */
YoutubeCookies.prototype.setIframeWrapper = iframe => {
  const wrapper = document.createElement("div");
  wrapper.classList.add("youtube-cookies__iframe-container");
  iframe.parentNode.insertBefore(wrapper, iframe);
  wrapper.appendChild(iframe);
};

/**
 * Remove a wrapper div to the YouTube iframe to easily add the thumbnail.
 *
 * @param {object} iframe
 *   Iframe.
 */
YoutubeCookies.prototype.removeIframeWrapper = iframe => {
  const wrapper = iframe.parentNode;
  wrapper.after(iframe);
  wrapper.remove();
};

/**
 * Replace a YouTube iframe with a YouTube thumbnail.
 *
 * If tracking is accepted the video will be loaded.
 *
 * @param {object} iframe
 *   Iframe.
 * @param {string} iframeSrc
 *   A YouTube URL.
 */
YoutubeCookies.prototype.setYoutubeThumbnail = function(iframe, iframeSrc) {
  const videoId = this.getYoutubeIdFromUrl(iframeSrc);
  const button = document.createElement("button");
  button.classList.add("youtube-cookies__thumbnail");
  button.addEventListener("click", this.onThumbnailClick.bind(this));
  button.setAttribute("data-yc-fake-button", "1");
  button.setAttribute("aria-label", this.thumbnailLabel);
  button.innerHTML = this.thumbnailMarkup.replace(/videoId/g, videoId);
  iframe.parentNode.prepend(button);
};

/**
 * Check that an url is from YouTube.
 *
 * @param {string} source
 *   YouTube url.
 *
 * @return {boolean}
 *   True if URL is from YouTube.
 */
YoutubeCookies.prototype.isYoutubeSource = function(source) {
  if (source.length === 0) {
    return false;
  }

  const regExps = [/youtu\.be/, /youtube\.com/];
  if (this.checkYoutubeNoCookie) {
    regExps.push(/youtube-nocookie\.com/);
  }

  for (const i in regExps) {
    if (source.match(regExps[i])) {
      return true;
    }
  }
  return false;
};

/**
 * Check that an iframe is oembed-lazyload.
 *
 * @param {object} element
 *   YouTube url.
 *
 * @return {boolean}
 *   True if URL is oembed-lazyload.
 */
YoutubeCookies.prototype.isOembedLazyload = function(element) {
  return !!(
    element.classList.contains("oembed-lazyload") ||
    element.classList.contains("oembed-lazyload__iframe")
  );
};

/**
 * Stop an iframe.
 *
 * @param {object} iframe
 *   Iframe object.
 */
YoutubeCookies.prototype.stopIframe = function(iframe) {
  const iframeSrc = iframe.src;

  if (iframeSrc.length > 0 && this.isYoutubeSource(iframeSrc)) {
    iframe.setAttribute("data-src", iframeSrc);
    iframe.src = '';
  }
};

/**
 * Given a youtube video URL get its ID.
 *
 * @param {string} url
 *   Youtube video URL.
 *
 * @return {null|*}
 *   Video id, if exists.
 */
YoutubeCookies.prototype.getYoutubeIdFromUrl = function(url) {
  const youtubeIdRegexp = "[^\\s&?/#]{11}";
  let videoIdMatches = [];
  if (url.match(`(youtube\.com\/embed\/)(${youtubeIdRegexp})`)) {
    videoIdMatches = url.match(`(youtube\.com\/embed\/)(${youtubeIdRegexp})`);
  }
  if (url.match(`(youtu\.be\/)(${youtubeIdRegexp})`)) {
    videoIdMatches = url.match(
      `(youtube.com/embed)|(youtu.be/)(${youtubeIdRegexp})`
    );
  } else if (url.match(`(youtube.com/watch%3Fv%3D)(${youtubeIdRegexp})`)) {
    videoIdMatches = url.match(
      `(youtube.com/watch%3Fv%3D)(${youtubeIdRegexp})`
    );
  }
  if (videoIdMatches !== null && videoIdMatches.length > 0) {
    return videoIdMatches[videoIdMatches.length - 1];
  }
  return null;
};

/**
 * Replace an element YouTube URL by youtube-nocookie alternative.
 *
 * @param {object} element
 *   Element.
 * @param {string} url
 *   Video URL.
 */
YoutubeCookies.prototype.setYoutubeNoCookiesSrc = function(element, url) {
  const attribute =
    typeof element.src === "string" && element.src.length > 0
      ? "src"
      : "data-src";
  const videoId = this.getYoutubeIdFromUrl(url);
  if (videoId != null) {
    element.setAttribute("data-original-src", url);
    element.setAttribute("data-original-src-attribute", attribute);
    // @TODO: Improve, the domain replace should change only the domain not the
    //  entire URL. This changes the formatter default behaviour and must not.
    element.setAttribute(
      attribute,
      `https://www.youtube-nocookie.com/embed/${videoId}?enablejsapi=1`
    );
  }
};

/**
 * Set the YouTube original URL to the iframe.
 *
 * Only works with videos that haven't been played yet.
 *
 * @param {object} element
 *   Iframe.
 */
YoutubeCookies.prototype.setYoutubeOriginalUrl = function(element) {
  const attribute = element.getAttribute("data-original-src-attribute");
  element.setAttribute(attribute, element.getAttribute("data-original-src"));
  element.removeAttribute("data-original-src-attribute");
  element.removeAttribute("data-original-src");
};

/**
 * Set up the iframes so cookies can be managed.
 *
 * @param {object} event
 *   Event objetc.
 */
YoutubeCookies.prototype.onContentLoaded = function(event) {
  if (!this.youtubeCookiesAccepted) {
    this.processYoutubeIframes(event.target);
  }
};

/**
 * Process YouTube iframes.
 *
 * @param {object} container
 *   Element that contains the iframe.
 */
YoutubeCookies.prototype.processYoutubeIframes = function(container) {
  if (this.youtubeCookiesAccepted) {
    return;
  }

  let newButton;
  const iframes = container.getElementsByTagName("iframe");
  for (const iframeIndex in iframes) {
    const iframe = iframes[iframeIndex];
    if (typeof iframe !== "object") {
      return;
    }

    if (iframe.hasAttribute("data-yc-iframe-processed")) {
      continue;
    }

    this.stopIframe(iframe);

    const iframeSrc = iframe.hasAttribute("data-src")
      ? iframe.getAttribute("data-src")
      : iframe.src;
    if (
      typeof iframeSrc === "string" &&
      !this.isYoutubeSource(iframeSrc)
    ) {
      continue;
    }

    const iframeWrapper = iframe.parentNode;
    if (this.isOembedLazyload(iframeWrapper)) {
      iframeWrapper.setAttribute(
        "data-original-strategy",
        iframeWrapper.getAttribute("data-strategy")
      );
      iframeWrapper.removeAttribute("data-strategy");
    }

    switch (this.action) {
      case "no_cookies_domain":
        this.setYoutubeNoCookiesSrc(iframe, iframeSrc);
        break;

      case "popup":
        if (this.isOembedLazyload(iframeWrapper)) {
          const originalButton = iframeWrapper.querySelector("button");
          if (typeof originalButton === "object") {
            iframe.parentNode.insertAdjacentHTML(
              "afterbegin",
              originalButton.outerHTML
            );
            originalButton.setAttribute("data-yc-original-oembed-button", 1);
            originalButton.style.display = "none";

            // @TODO: Review, for the scope of this module launch an event on
            // thumbnail click is trivial. At this point the modules do not
            // perform a relevant action.
            originalButton.addEventListener(
              "click",
              this.eventDispatch.bind(this)
            );

            newButton = iframe.parentNode.querySelector(
              "button:not([data-yc-original-oembed-button])"
            );

            newButton.addEventListener("click", this.onThumbnailClick.bind(this));
            newButton.setAttribute("data-yc-fake-button", "1");
          }
        } else {
          // Set up the wrapper.
          if (iframe.classList.contains("youtube-cookies__iframe--wysiwyg")) {
            this.setIframeWrapper(iframe);
          }
          if (iframe.classList.contains("youtube-cookies__iframe--oembed")) {
            iframe.parentNode.classList.add("youtube-cookies__iframe-container");
          }
          // Add the thumbnails.
          this.setYoutubeThumbnail(iframe, iframeSrc);
        }
        break;
    }

    iframe.setAttribute("data-yc-iframe-index", iframeIndex);
    iframe.setAttribute("data-yc-iframe-processed", "1");
    iframe.dispatchEvent(youtubeCookiesEnabled);
  }
};

/**
 * Set the YouTube iframe/container to his initial state.
 *
 * @param {object} iframeWrapper
 *    Iframe container object.
 */
YoutubeCookies.prototype.destroyYoutuebeCookiesIframe = function(
  iframeWrapper
) {
  const iframe = iframeWrapper.querySelector("iframe");

  switch (this.action) {
    case "no_cookies_domain":
      this.setYoutubeOriginalUrl(iframe);
      break;

    case "popup":
      this.removeFakeThumbnail(iframeWrapper);
      if (this.isOembedLazyload(iframeWrapper)) {
        iframeWrapper.setAttribute(
          "data-strategy",
          iframeWrapper.getAttribute("data-original-strategy")
        );
        iframeWrapper.removeAttribute("data-original-strategy");
      } else {
        if (iframe.classList.contains("youtube-cookies__iframe--wysiwyg")) {
          this.removeIframeWrapper(iframe);
        }
        if (iframe.classList.contains("youtube-cookies__iframe--oembed")) {
          iframe.parentNode.classList.remove("youtube-cookies__iframe-container");
        }
        iframe.setAttribute("src", iframe.getAttribute("data-src"));
        iframe.removeAttribute("data-src");
      }
      break;
  }
  iframe.removeAttribute("data-yc-iframe-processed");
  iframe.removeAttribute("data-yc-iframe-index");
  iframe.dispatchEvent(youtubeCookiesDisabled);
};

/**
 * Set the callback that checks cookies have been accepted.
 *
 * @param {function} callback
 *   Callback that determine if cookies have been accepted.
 */
YoutubeCookies.prototype.setAcceptedCookiesCallback = function(callback) {
  this.acceptedCookiesCallback = callback;
};

/**
 * Initialize YouTube cookies.
 */
(function(Drupal, drupalSettings) {
  if (typeof drupalSettings.youtubeCookies === "object") {
    Drupal.youtubeCookies = new YoutubeCookies(
      drupalSettings.youtubeCookies.action,
      drupalSettings.youtubeCookies.thumbnailLabel,
      drupalSettings.youtubeCookies.popupMessage,
      drupalSettings.youtubeCookies.manageButton,
      drupalSettings.youtubeCookies.okButton,
      drupalSettings.youtubeCookies.exitButton,
      drupalSettings.youtubeCookies.thumbnailMarkup
    );
  }

  Drupal.behaviors.youtubeCookies = {
    attach() {
      // Force attachment of oembed lazyload behaviour before removing the oembed attributes.
      if (
        typeof Drupal.behaviors.oembedLazyload === "object" &&
        typeof Drupal.behaviors.oembedLazyload.attach === "function"
      ) {
        Drupal.behaviors.oembedLazyload.attach(document);
      }
      Drupal.youtubeCookies.processYoutubeIframes(document);
      document.dispatchEvent(youtubeCookiesEnabled);
    }
  };
})(Drupal, drupalSettings);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc