HEX
Server: Microsoft-IIS/10.0
System: Windows NT ITPWINWEBSVR22 10.0 build 20348 (Windows Server 2022) AMD64
User: www.conferencesearch.co.uk (0)
PHP: 8.3.30
Disabled: NONE
Upload Files
File: D:/web/matomo.vdk/plugins/CoreHome/vue/src/EntityDuplicator/EntityDuplicatorModal.vue
<!--
  Matomo - free/libre analytics platform

  @link    https://matomo.org
  @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
-->
<template>
  <div
    :class="{
      'modal': true,
      'entity-duplicator-modal': true,
      'slot-configured': $slots.default
    }"
    ref="root">
    <div class="main-duplicator-modal-content" v-show="isModalVisible">
      <div class="modal-header">
        <span class="btn-close modal-close"><i class="icon-close"></i></span>
        <h2>
          {{ getModalTitle }}
        </h2>
      </div>

      <template v-if="isLoading">
        <div class="modal-sub-header">
          <MatomoLoader />
          <span class="loading-message">{{ translate('General_Loading') }}</span>
        </div>
      </template>

      <template v-else>
        <div class="modal-sub-header" v-if="!hideSiteSelector">
          <p>
            {{ getDuplicateDescription }}
            <span v-if="descriptionLearnMoreLink" v-html="$sanitize(getLearnMoreLink)"></span>
          </p>
          <Field
            uicontrol="site"
            name="siteSelector"
            :title="translate('CoreHome_ChooseWebsite')"
            v-model="destinationSite"
            :ui-control-attributes="{
              onlySitesWithAtLeastWriteAccess: true,
              siteTypesToExclude: ['rollup'],
            }"
          />
        </div>
        <div class="modal-content">
          <div v-form class="modal-inputs">
            <slot></slot>
          </div>
        </div>
        <div class="modal-sub-footer">
          <div
            :class="{
              'alert': true,
              'alert-danger': true,
              'error-list': duplicationErrors.length > 1
            }"
            v-if="duplicationErrors.length > 0">
            <ul>
              <li
                v-for="(duplicationError, index) in duplicationErrors"
                :key="index"
                v-html="$sanitize(duplicationError)"
              />
            </ul>
          </div>
          <p class="note-text" v-html="$sanitize(getNoteText)"/>
        </div>
        <div class="modal-footer">
          <MatomoLoader v-show="hasBeenSubmitted" />
          <button
            class="btn"
            :disabled="!getIsValid || hasBeenSubmitted"
            @click="submitRequest()"
          >{{ translate('General_Copy') }}</button>
        </div>
      </template>
    </div>
  </div>
</template>

<script lang="ts">
import {
  defineComponent,
  watch,
  PropType,
} from 'vue';
import useExternalPluginComponent from '../useExternalPluginComponent';
import SiteRef from '../SiteSelector/SiteRef';
import { translate } from '../translate';
import { externalLink } from '../externalLink';
import { EntityDuplicatorStore } from './EntityDuplicatorStore';
import {
  DuplicateRequestResponse,
  ValidationResult,
} from './EntityDuplicatorAdapter';
import MatomoLoader from '../MatomoLoader/MatomoLoader';

// async since we're referencing a recursive component
const Field = useExternalPluginComponent('CorePluginsAdmin', 'Field');
const Form = useExternalPluginComponent('CorePluginsAdmin', 'Form');

const { $ } = window;

interface EntityDuplicatorState {
  isLoading: boolean;
  isValidated: boolean;
  duplicationErrors: string[];
  destinationSite: SiteRef|null;
  hasBeenSubmitted: boolean;
}

export default defineComponent({
  directives: {
    Form,
  },
  components: {
    Field,
    MatomoLoader,
  },
  props: {
    /**
     * The reactive class for controlling the settings of the modal from multiple components.
     */
    modalStore: {
      type: Object as PropType<EntityDuplicatorStore>,
      required: true,
    },
    /**
     * Option to hide the site selector when it's not needed.
     */
    hideSiteSelector: {
      type: Boolean,
      default: false,
    },
    /**
     * Optional "Learn more." link to append to the end of the description text if provided.
     */
    descriptionLearnMoreLink: {
      type: String,
      default: '',
    },
  },
  data(): EntityDuplicatorState {
    return {
      isLoading: true,
      isValidated: false,
      duplicationErrors: [],
      destinationSite: null,
      hasBeenSubmitted: false,
    };
  },
  watch: {
    isModalVisible(newValue) {
      if (!newValue) {
        return;
      }

      // Call adapter's beforeShowModal if defined
      let beforeShowModal: void | Promise<void>;
      if (this.modalStore.adapter.beforeShowModal) {
        beforeShowModal = this.modalStore.adapter.beforeShowModal();
      }

      // If a promise was returned, leave as loading until the promise is resolved
      if (!beforeShowModal || typeof beforeShowModal === 'undefined') {
        beforeShowModal = new Promise<void>((resolve) => resolve());
      }

      this.showModal();

      // If a promise was returned, use that to set the state at the right time
      beforeShowModal.then(() => { this.isLoading = false; });
    },
    destinationSite() {
      // Reset flag since the data has changed since validation
      this.isValidated = false;
    },
  },
  methods: {
    closeModal() {
      const root = this.$refs.root as HTMLElement;
      const $root = $(root);
      $root.modal('close');
    },
    resetModal() {
      this.modalStore.hideModal();
      this.destinationSite = null;
      this.isLoading = true;
      this.isValidated = false;
      this.duplicationErrors = [];
      this.hasBeenSubmitted = false;
    },
    showModal() {
      const root = this.$refs.root as HTMLElement;
      const $root = $(root);
      $root.modal({
        dismissible: true,
        onCloseEnd: () => {
          this.resetModal();
        },
      }).modal('open');
    },
    submitRequest() {
      this.hasBeenSubmitted = true;

      // Make sure the validation passes before making the server request
      this.getValidationResultPromise().then((validationResult: ValidationResult) => {
        if (!validationResult.isValid && validationResult.errorMessages.length > 0) {
          this.isValidated = true;
          this.hasBeenSubmitted = false;
          this.duplicationErrors = validationResult.errorMessages;
          return;
        }

        // Use adapter to prepare API parameters
        const params = this.modalStore.adapter.prepareApiParams(
          this.modalStore.getFormValues(this.destinationSite?.id),
        );

        // Use adapter to submit the request
        this.modalStore.adapter.submitRequest(params).then((response: DuplicateRequestResponse) => {
          if (!response || !response.success) {
            this.setErrorMessages(response);
            return;
          }

          // Call adapter's onSuccess if defined
          if (this.modalStore.adapter.onSuccess) {
            this.modalStore.adapter.onSuccess(response);
          }

          this.closeModal();
        }).catch((error) => {
          this.setErrorMessages();
          // Call adapter's onFailure if defined
          if (this.modalStore.adapter.onFailure) {
            this.modalStore.adapter.onFailure(error);
          }

          console.log('Unexpected server error during request.', error);
        }).finally(() => {
          this.hasBeenSubmitted = false;
        });
      });
    },
    getValidationResultPromise(): Promise<ValidationResult> {
      this.duplicationErrors = [];

      // Use adapter for validation
      const validationResultPromise = this.modalStore.adapter.validateFormFields(
        this.modalStore.getFormValues(this.destinationSite?.id),
      );
      // If a promise wasn't returned wrap the result with a promise for consistent processing
      return 'isValid' in validationResultPromise
        ? new Promise((resolve) => resolve(validationResultPromise))
        : validationResultPromise;
    },
    setErrorMessages(response: null|DuplicateRequestResponse = null) {
      let message = response?.message || '';

      // If the error message wasn't set, set it to a generic error message
      if (!message || message.length === 0) {
        message = translate('General_ErrorRequest', '', '');
      }

      this.duplicationErrors = [];
      this.duplicationErrors.push(message);
    },
  },
  mounted() {
    // Watch the formData object for any property changes to know whether current data was validated
    watch(
      () => this.modalStore.state.entityFormData,
      () => {
        this.isValidated = false;
      },
      { deep: true },
    );
  },
  computed: {
    isModalVisible(): boolean {
      return this.modalStore.state.isModalVisible ?? false;
    },
    getModalTitle(): string {
      return translate('CoreHome_CopyX', this.modalStore.getEntityTypeTranslation);
    },
    getNoteText(): string {
      const noteText = translate(
        'CoreHome_CopyModalNote',
        '<strong>',
        '</strong>',
        this.modalStore.getEntityTypeTranslation,
      );

      return `${noteText}`;
    },
    getDuplicateDescription(): string {
      return translate('CoreHome_CopyXDescription', this.modalStore.getEntityTypeTranslation);
    },
    getLearnMoreLink() {
      if (!this.descriptionLearnMoreLink) {
        return '';
      }

      const linkString = externalLink(this.descriptionLearnMoreLink);
      return translate('CoreHome_LearnMoreFullStop', linkString, '</a>');
    },
    getIsValid(): boolean {
      // Show as valid until validation has actually been checked
      if (!this.isValidated) {
        return true;
      }

      return Array.isArray(this.duplicationErrors) && this.duplicationErrors.length === 0;
    },
  },
});
</script>