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/CoreAdminHome/vue/src/BrandingSettings/BrandingSettings.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>
  <ContentBlock
    :content-title="translate('CoreAdminHome_BrandingSettings')"
    anchor="brandingSettings"
  >
    <div v-form>
      <p>{{ translate('CoreAdminHome_CustomLogoHelpText') }}</p>

      <Field
        name="useCustomLogo"
        uicontrol="checkbox"
        :model-value="enabled"
        @update:model-value="onUseCustomLogoChange($event)"
        :title="translate('CoreAdminHome_UseCustomLogo')"
        :inline-help="help"
      />

      <div id="logoSettings" v-show="enabled">
        <form
          id="logoUploadForm"
          ref="logoUploadForm"
          method="post"
          enctype="multipart/form-data"
          action="index.php?module=CoreAdminHome&format=json&action=uploadCustomLogo"
        >
          <div v-if="fileUploadEnabled">
            <input type="hidden" name="token_auth" :value="tokenAuth"/>
            <input type="hidden" name="force_api_session" value="1"/>

            <div v-if="logosWriteable">
              <transition name="fade-out">
                <div class="alert alert-warning uploaderror" v-if="showUploadError">
                  {{ translate('CoreAdminHome_LogoUploadFailed') }}
                </div>
              </transition>

              <Field
                uicontrol="file"
                name="customLogo"
                :model-value="customLogo"
                @update:model-value="onCustomLogoChange($event)"
                :title="translate('CoreAdminHome_LogoUpload')"
                :inline-help="translate('CoreAdminHome_LogoUploadHelp', 'JPG / PNG / GIF', '110')"
              />

              <div class="row">
                <div class="col s12">
                  <img
                    :src="pathUserLogoSrc"
                    id="currentLogo"
                    style="max-height: 150px"
                    ref="currentLogo"
                  />
                </div>
              </div>

              <Field
                uicontrol="file"
                name="customFavicon"
                :model-value="customFavicon"
                @update:model-value="onFaviconChange($event)"
                :title="translate('CoreAdminHome_FaviconUpload')"
                :inline-help="translate('CoreAdminHome_LogoUploadHelp', 'JPG / PNG / GIF', '16')"
              />

              <div class="row">
                <div class="col s12">
                  <img
                    :src="pathUserFaviconSrc"
                    id="currentFavicon"
                    width="16"
                    height="16"
                    ref="currentFavicon"
                  />
                </div>
              </div>
            </div>

            <div v-if="!logosWriteable">
              <div class="alert alert-warning" v-html="$sanitize(logosNotWriteableWarning)"/>
            </div>
          </div>
          <div v-if="!fileUploadEnabled">
            <div class="alert alert-warning">
              {{ translate('CoreAdminHome_FileUploadDisabled', "file_uploads=1") }}
            </div>
          </div>
        </form>
      </div>

      <SaveButton
        @confirm="save()"
        :saving="isLoading"
      />
    </div>
  </ContentBlock>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import {
  ContentBlock,
  translate,
  NotificationsStore,
  AjaxHelper,
  Matomo,
} from 'CoreHome';
import {
  SaveButton,
  Form,
  Field,
} from 'CorePluginsAdmin';

const { $ } = window;

interface BrandingSettingsState {
  isLoading: boolean;
  enabled: boolean;
  customLogo: string|File;
  customFavicon: string|File;
  showUploadError: boolean;
  currentLogoSrcExists: boolean;
  currentFaviconSrcExists: boolean;
  newLogoBase64Src: string;
  newFaviconBase64Src: string;
}

export default defineComponent({
  props: {
    fileUploadEnabled: {
      type: Boolean,
      required: true,
    },
    logosWriteable: {
      type: Boolean,
      required: true,
    },
    useCustomLogo: {
      type: Boolean,
      required: true,
    },
    pathUserLogoDirectory: {
      type: String,
      required: true,
    },
    pathUserLogo: {
      type: String,
      required: true,
    },
    pathUserLogoSmall: {
      type: String,
      required: true,
    },
    pathUserLogoSvg: {
      type: String,
      required: true,
    },
    hasUserLogo: {
      type: Boolean,
      required: true,
    },
    pathUserFavicon: {
      type: String,
      required: true,
    },
    hasUserFavicon: {
      type: Boolean,
      required: true,
    },
    isPluginsAdminEnabled: {
      type: Boolean,
      required: true,
    },
  },
  components: {
    Field,
    ContentBlock,
    SaveButton,
  },
  directives: {
    Form,
  },
  data(): BrandingSettingsState {
    return {
      isLoading: false,
      enabled: this.useCustomLogo,
      customLogo: this.pathUserLogo,
      customFavicon: this.pathUserFavicon,
      showUploadError: false,
      currentLogoSrcExists: this.hasUserLogo,
      currentFaviconSrcExists: this.hasUserFavicon,
      newLogoBase64Src: '',
      newFaviconBase64Src: '',
    };
  },
  computed: {
    tokenAuth() {
      return Matomo.token_auth;
    },
    logosNotWriteableWarning() {
      return translate(
        'CoreAdminHome_LogoNotWriteableInstruction',
        `<code>${this.pathUserLogoDirectory}</code><br/>`,
        `${this.pathUserLogo}, ${this.pathUserLogoSmall}, ${this.pathUserLogoSvg}`,
      );
    },
    help() {
      if (!this.isPluginsAdminEnabled) {
        return undefined;
      }

      const giveUsFeedbackText = `"${translate('General_GiveUsYourFeedback')}"`;
      const linkStart = '<a href="?module=CorePluginsAdmin&action=plugins" '
        + 'rel="noreferrer noopener" target="_blank">';
      return translate(
        'CoreAdminHome_CustomLogoFeedbackInfo',
        giveUsFeedbackText,
        linkStart,
        '</a>',
      );
    },
    pathUserLogoSrc() {
      if (this.newLogoBase64Src) {
        return `data:image/png;base64, ${this.newLogoBase64Src}`;
      }
      if (this.currentLogoSrcExists && this.pathUserLogo) {
        return this.pathUserLogo;
      }

      return '';
    },
    pathUserFaviconSrc() {
      if (this.newFaviconBase64Src) {
        return `data:image/png;base64, ${this.newFaviconBase64Src}`;
      }

      if (this.currentFaviconSrcExists && this.pathUserFavicon) {
        return this.pathUserFavicon;
      }

      return '';
    },
  },
  methods: {
    onUseCustomLogoChange(newValue: boolean) {
      this.enabled = newValue;
    },
    onCustomLogoChange(newValue: File) {
      this.customLogo = newValue;
      this.updateLogo();
    },
    onFaviconChange(newValue: File) {
      this.customFavicon = newValue;
      this.updateLogo();
    },
    save() {
      this.isLoading = true;
      AjaxHelper.post({
        module: 'API',
        method: 'CoreAdminHome.setBrandingSettings',
      },
      {
        useCustomLogo: this.enabled ? '1' : '0',
        hasCustomLogo: (this.newLogoBase64Src.length > 0 || this.customLogo) ? '1' : '0',
        hasCustomFavicon: (this.newFaviconBase64Src.length > 0 || this.customFavicon) ? '1' : '0',
      }).then((response) => {
        this.enabled = !!response.useCustomLogo;
        if (response.customLogoPath) {
          this.customLogo = response.customLogoPath;
        }
        if (response.customFaviconPath) {
          this.customFavicon = response.customFaviconPath;
        }

        const notificationInstanceId = NotificationsStore.show({
          message: translate('CoreAdminHome_SettingsSaveSuccess'),
          type: 'transient',
          id: 'generalSettings',
          context: 'success',
        });
        NotificationsStore.scrollToNotification(notificationInstanceId);
      }).finally(() => {
        if (!this.enabled) {
          // reset fields so that values are not visible when setting is enabled without page reload
          this.currentLogoSrcExists = false;
          this.currentFaviconSrcExists = false;
          this.customLogo = '';
          this.customFavicon = '';
          this.newFaviconBase64Src = '';
          this.newLogoBase64Src = '';
        }
        this.isLoading = false;
      });
    },
    updateLogo() {
      const isSubmittingLogo = !!this.customLogo;
      const isSubmittingFavicon = !!this.customFavicon;

      if (!isSubmittingLogo && !isSubmittingFavicon) {
        return;
      }

      this.showUploadError = false;

      const frameName = `upload${(new Date()).getTime()}`;
      const uploadFrame = $(`<iframe name="${frameName}" />`);
      uploadFrame.css('display', 'none');
      uploadFrame.on('load', () => {
        setTimeout(() => {
          let frameContent = '';
          let frameContentJSON: Record<string, string> = {};
          try {
            frameContent = ($(uploadFrame.contents()).find('body').text() || '').trim();
            frameContentJSON = JSON.parse(frameContent as string) as Record<string, string>;
          } catch (e) {
            // invalid JSON
          }

          if (frameContent && Object.keys(frameContentJSON).length === 0) {
            this.showUploadError = true;
          } else {
            // Upload succeed, so we update the images availability
            // according to what have been uploaded
            if (isSubmittingLogo && frameContentJSON.logo) {
              this.newLogoBase64Src = frameContentJSON.logo;
            }
            if (isSubmittingFavicon && frameContentJSON.favicon) {
              this.newFaviconBase64Src = frameContentJSON.favicon;
            }
          }

          if (frameContent) {
            uploadFrame.remove();
          }
        }, 1000);
      });
      $('body:first').append(uploadFrame);

      const submittingForm = $(this.$refs.logoUploadForm as HTMLElement);
      submittingForm.attr('target', frameName);
      submittingForm.submit();

      this.customLogo = '';
      this.customFavicon = '';
    },
  },
});
</script>