Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 03d1f858 authored by Anna's avatar Anna Committed by GitHub
Browse files

Merge pull request #5510 from nextcloud/backport/5498/stable4.5

parents d61e132c 2ee631c1
Loading
Loading
Loading
Loading
+0 −7
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ declare(strict_types=1);
namespace OCA\Calendar\Controller;

use OCA\Calendar\Service\Appointments\AppointmentConfigService;
use OCA\Calendar\Service\CategoriesService;
use OCP\App\IAppManager;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\FileDisplayResponse;
@@ -46,9 +45,6 @@ class ViewController extends Controller {
	/** @var AppointmentConfigService */
	private $appointmentConfigService;

	/** @var CategoriesService */
	private $categoriesService;

	/** @var IInitialState */
	private $initialStateService;

@@ -64,7 +60,6 @@ class ViewController extends Controller {
		IRequest $request,
		IConfig $config,
		AppointmentConfigService $appointmentConfigService,
		CategoriesService $categoriesService,
		IInitialState $initialStateService,
		IAppManager $appManager,
		?string $userId,
@@ -72,7 +67,6 @@ class ViewController extends Controller {
		parent::__construct($appName, $request);
		$this->config = $config;
		$this->appointmentConfigService = $appointmentConfigService;
		$this->categoriesService = $categoriesService;
		$this->initialStateService = $initialStateService;
		$this->appManager = $appManager;
		$this->userId = $userId;
@@ -143,7 +137,6 @@ class ViewController extends Controller {
		$this->initialStateService->provideInitialState('appointmentConfigs', $this->appointmentConfigService->getAllAppointmentConfigurations($this->userId));
		$this->initialStateService->provideInitialState('disable_appointments', $disableAppointments);
		$this->initialStateService->provideInitialState('can_subscribe_link', $canSubscribeLink);
		$this->initialStateService->provideInitialState('categories', $this->categoriesService->getCategories($this->userId));
		$this->initialStateService->provideInitialState('show_resources', $showResources);

		return new TemplateResponse($this->appName, 'main');

lib/Service/CategoriesService.php

deleted100644 → 0
+0 −144
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

/**
 * Calendar App
 *
 * @copyright 2023 Claus-Justus Heine <himself@claus-justus-heine.de>
 *
 * @author Claus-Justus Heine <himself@claus-justus-heine.de>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

namespace OCA\Calendar\Service;

use OCP\Calendar\ICalendarQuery;
use OCP\Calendar\IManager as ICalendarManager;
use OCP\IL10N;
use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager;

/**
 * @psalm-type Category = array{label: string, value: string}
 * @psalm-type CategoryGroup = array{group: string, options: array<int, Category>}
 */
class CategoriesService {
	/** @var ICalendarManager */
	private $calendarManager;

	/** @var ISystemTagManager */
	private $systemTagManager;

	/** @var IL10N */
	private $l;

	public function __construct(ICalendarManager $calendarManager,
		ISystemTagManager $systemTagManager,
		IL10N $l10n) {
		$this->calendarManager = $calendarManager;
		$this->systemTagManager = $systemTagManager;
		$this->l = $l10n;
	}

	/**
	 * This is a simplistic brute-force extraction of all already used
	 * categories from all events accessible to the given user.
	 *
	 * @return array
	 */
	private function getUsedCategories(string $userId): array {
		$categories = [];
		$principalUri = 'principals/users/' . $userId;
		$query = $this->calendarManager->newQuery($principalUri);
		$query->addSearchProperty(ICalendarQuery::SEARCH_PROPERTY_CATEGORIES);
		$calendarObjects = $this->calendarManager->searchForPrincipal($query);
		foreach ($calendarObjects as $objectInfo) {
			foreach ($objectInfo['objects'] as $calendarObject) {
				if (isset($calendarObject['CATEGORIES'])) {
					$categories[] = explode(',', $calendarObject['CATEGORIES'][0][0]);
				}
			}
		}

		// Avoid injecting "broken" categories into the UI (avoid empty
		// categories and categories surrounded by spaces)
		$categories = array_filter(array_map(fn ($label) => trim($label), array_unique(array_merge(...$categories))));

		return $categories;
	}

	/**
	 * Return a grouped array with all previously used categories, all system
	 * tags and all categories found in the iCalendar RFC.
	 *
	 * @return CategoryGroup[]
	 */
	public function getCategories(string $userId): array {
		$systemTags = $this->systemTagManager->getAllTags(true);

		$systemTagCategoryLabels = [];
		/** @var ISystemTag $systemTag */
		foreach ($systemTags as $systemTag) {
			if (!$systemTag->isUserAssignable() || !$systemTag->isUserVisible()) {
				continue;
			}
			$systemTagCategoryLabels[] = $systemTag->getName();
		}
		sort($systemTagCategoryLabels);
		$systemTagCategoryLabels = array_values(array_filter(array_unique($systemTagCategoryLabels)));

		$rfcCategoryLabels = [
			$this->l->t('Anniversary'),
			$this->l->t('Appointment'),
			$this->l->t('Business'),
			$this->l->t('Education'),
			$this->l->t('Holiday'),
			$this->l->t('Meeting'),
			$this->l->t('Miscellaneous'),
			$this->l->t('Non-working hours'),
			$this->l->t('Not in office'),
			$this->l->t('Personal'),
			$this->l->t('Phone call'),
			$this->l->t('Sick day'),
			$this->l->t('Special occasion'),
			$this->l->t('Travel'),
			$this->l->t('Vacation'),
		];
		sort($rfcCategoryLabels);
		$rfcCategoryLabels = array_values(array_filter(array_unique($rfcCategoryLabels)));

		$standardCategories = array_merge($systemTagCategoryLabels, $rfcCategoryLabels);
		$customCategoryLabels = array_values(array_filter($this->getUsedCategories($userId), fn ($label) => !in_array($label, $standardCategories)));

		$categories = [
			[
				'group' => $this->l->t('Custom Categories'),
				'options' => array_map(fn (string $label) => [ 'label' => $label, 'value' => $label ], $customCategoryLabels),
			],
			[
				'group' => $this->l->t('Collaborative Tags'),
				'options' => array_map(fn (string $label) => [ 'label' => $label, 'value' => $label ], $systemTagCategoryLabels),
			],
			[
				'group' => $this->l->t('Standard Categories'),
				'options' => array_map(fn (string $label) => [ 'label' => $label, 'value' => $label ], $rfcCategoryLabels),
			],
		];

		return $categories;
	}
}
+10 −37
Original line number Diff line number Diff line
@@ -42,9 +42,6 @@
				:multiple="true"
				:taggable="true"
				track-by="label"
				group-values="options"
				group-label="group"
				:group-select="false"
				label="label"
				@select="selectValue"
				@tag="tag"
@@ -102,10 +99,6 @@ export default {
			type: Boolean,
			default: false,
		},
		customLabelHeading: {
			type: String,
			default: 'Custom Categories',
		},
	},
	data() {
		return {
@@ -118,56 +111,45 @@ export default {
		},
		options() {
			const options = this.propModel.options.slice()
			let customOptions = options.find((optionGroup) => optionGroup.group === this.customLabelHeading)
			if (!customOptions) {
				customOptions = {
					group: this.customLabelHeading,
					options: [],
				}
				options.unshift(customOptions)
			}
			for (const category of (this.selectionData ?? [])) {
				if (this.findOption(category, options)) {
				if (options.find(option => option.value === category.value)) {
					continue
				}

				// Add pseudo options for unknown values
				customOptions.options.push({
				options.push({
					value: category.value,
					label: category.label,
				})
			}

			for (const category of this.value) {
				const categoryOption = { value: category, label: category }
				if (!this.findOption(categoryOption, options)) {
					customOptions.options.push(categoryOption)
				if (!options.find(option => option.value === category)) {
					options.push({ value: category, label: category })
				}
			}

			if (this.customLabelBuffer) {
				for (const category of this.customLabelBuffer) {
					if (!this.findOption(category, options)) {
						customOptions.options.push(category)
					if (!options.find(option => option.value === category.value)) {
						options.push(category)
					}
				}
			}

			for (const optionGroup of options) {
				optionGroup.options = optionGroup.options.sort((a, b) => {
			return options
				.sort((a, b) => {
					return a.label.localeCompare(
						b.label,
						getLocale().replace('_', '-'),
						{ sensitivity: 'base' },
					)
				})
			}
			return options
		},
	},
	created() {
		for (const category of this.value) {
			const option = this.findOption({ value: category }, this.options)
			const option = this.options.find(option => option.value === category)
			if (option) {
				this.selectionData.push(option)
			}
@@ -190,7 +172,7 @@ export default {

			// store removed custom options to keep it in the option list
			const options = this.propModel.options.slice()
			if (!this.findOption(value, options)) {
			if (!options.find(option => option.value === value.value)) {
				if (!this.customLabelBuffer) {
					this.customLabelBuffer = []
				}
@@ -205,15 +187,6 @@ export default {
			this.selectionData.push({ value, label: value })
			this.$emit('add-single-value', value)
		},
		findOption(value, availableOptions) {
			for (const optionGroup of availableOptions) {
				const option = optionGroup.options.find(option => option.value === value.value)
				if (option) {
					return option
				}
			}
			return undefined
		},
	},
}
</script>
+2 −5
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@

<template>
	<span class="property-select-multiple-colored-tag">
		<div v-if="!isGroupLabel" class="property-select-multiple-colored-tag__color-indicator" :style="{ 'background-color': color }" />
		<div class="property-select-multiple-colored-tag__color-indicator" :style="{ 'background-color': color}" />
		<span class="property-select-multiple-colored-tag__label">{{ label }}</span>
	</span>
</template>
@@ -41,9 +41,6 @@ export default {
		},
	},
	computed: {
		isGroupLabel() {
			return this.option.$isLabel && this.option.$groupLabel
		},
		label() {
			const option = this.option
			logger.debug('Option render', { option })
@@ -51,7 +48,7 @@ export default {
				return this.option
			}

			return this.option.$groupLabel ? this.option.$groupLabel : this.option.label
			return this.option.label
		},
		colorObject() {
			return uidToColor(this.label)
+0 −6
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import {
} from 'vuex'
import { translate as t } from '@nextcloud/l10n'
import { removeMailtoPrefix } from '../utils/attendee.js'
import { loadState } from '@nextcloud/initial-state'

/**
 * This is a mixin for the editor. It contains common Vue stuff, that is
@@ -315,11 +314,6 @@ export default {
		rfcProps() {
			return getRFCProperties()
		},
		categoryOptions() {
			const categories = { ...this.rfcProps.categories }
			categories.options = loadState('calendar', 'categories', [])
			return categories
		},
		/**
		 * Returns whether or not this event can be downloaded from the server
		 *
Loading