Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge release2.4 to master #458

Merged
merged 11 commits into from
Aug 21, 2023
7 changes: 4 additions & 3 deletions lib/Controller/OpenProjectAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,18 @@ public function getNotifications(): DataResponse {
*
* @return DataResponse
*/
public function getSearchedWorkPackages(?string $searchQuery = null, ?int $fileId = null): DataResponse {
public function getSearchedWorkPackages(?string $searchQuery = null, ?int $fileId = null, bool $isSmartPicker = false): DataResponse {
if ($this->accessToken === '') {
return new DataResponse('', Http::STATUS_UNAUTHORIZED);
} elseif (!OpenProjectAPIService::validateURL($this->openprojectUrl)) {
return new DataResponse('', Http::STATUS_BAD_REQUEST);
}

// when the search is done through smart picker we don't want to check if the work package is linkable
$result = $this->openprojectAPIService->searchWorkPackage(
$this->userId,
$searchQuery,
$fileId
$fileId,
!$isSmartPicker
);

if (!isset($result['error'])) {
Expand Down
27 changes: 27 additions & 0 deletions lib/Listener/OpenProjectReferenceListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,48 @@
namespace OCA\OpenProject\Listener;

use OCA\OpenProject\AppInfo\Application;
use OCA\OpenProject\Service\OpenProjectAPIService;
use OCP\AppFramework\Services\IInitialState;
use OCP\Collaboration\Reference\RenderReferenceEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IConfig;
use OCP\Util;

/**
* @template-implements IEventListener<Event>
*/
class OpenProjectReferenceListener implements IEventListener {

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

/**
* @var IConfig
*/
private $config;

public function __construct(
IInitialState $initialStateService,
IConfig $config
) {
$this->initialStateService = $initialStateService;
$this->config = $config;
}
public function handle(Event $event): void {
// @phpstan-ignore-next-line - make phpstan not complain in nextcloud version other than 26
if (!$event instanceof RenderReferenceEvent) {
return;
}

Util::addScript(Application::APP_ID, Application::APP_ID . '-reference');
$this->initialStateService->provideInitialState('admin-config-status', OpenProjectAPIService::isAdminConfigOk($this->config));

$this->initialStateService->provideInitialState(
'openproject-url',
$this->config->getAppValue(Application::APP_ID, 'openproject_instance_url')
);
}
}
32 changes: 16 additions & 16 deletions lib/Reference/WorkPackageReferenceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@

use OCA\OpenProject\Service\OpenProjectAPIService;
use OCP\Collaboration\Reference\ADiscoverableReferenceProvider;
use OCP\Collaboration\Reference\ISearchableReferenceProvider;
use OCP\Collaboration\Reference\Reference;
use OC\Collaboration\Reference\ReferenceManager;
use OCA\OpenProject\AppInfo\Application;
Expand All @@ -34,7 +33,7 @@
use OCP\IL10N;
use OCP\IURLGenerator;

class WorkPackageReferenceProvider extends ADiscoverableReferenceProvider implements ISearchableReferenceProvider {
class WorkPackageReferenceProvider extends ADiscoverableReferenceProvider {
private const RICH_OBJECT_TYPE = Application::APP_ID . '_work_package';

// as we know we are on NC >= 26, we can use Php 8 syntax for class attributes
Expand Down Expand Up @@ -76,29 +75,30 @@ public function getIconUrl(): string {
);
}

/**
* @inheritDoc
*/
public function getSupportedSearchProviderIds(): array {
return ['openproject-search'];
}

/**
* Parse a link to find a work package ID
*
* @param string $referenceText
*
* @return int|null
*/
private function getWorkPackageIdFromUrl(string $referenceText): ?int {
public function getWorkPackageIdFromUrl(string $referenceText): ?int {
$patterns = array(
'\/wp\/([0-9]+)/',
'\/projects\/[^\/\?]+\/(?:work_packages|bcf)(?:\/details)?\/([0-9]+)/',
'\/(?:work_packages|notifications)\/details\/([0-9]+)/',
'\/work_packages\/([0-9]+)/',
'\/projects\/[^\/\?]+\/(?:boards|calendars|team_planners)\/[^\/\?]+\/details\/([0-9]+)/');
// example links
// https://community.openproject.org/projects/nextcloud-integration/work_packages/40070
$openProjectUrl = $this->config->getAppValue(Application::APP_ID, 'openproject_instance_url');
preg_match('/^' . preg_quote($openProjectUrl, '/') . '\/projects\/[^\/\?]+\/work_packages\/([0-9]+)/', $referenceText, $matches);
if (count($matches) > 1) {
return (int) $matches[1];
$openProjectUrl = rtrim($this->config->getAppValue(Application::APP_ID, 'openproject_instance_url'),'/');
foreach ($patterns as $pattern) {
$patternString ='/^' . preg_quote($openProjectUrl, '/') . $pattern;
preg_match($patternString, $referenceText, $patternMatches);
if (count($patternMatches) > 1) {
return (int) $patternMatches[1];
}
}

return null;
}

Expand All @@ -124,7 +124,7 @@ public function matchReference(string $referenceText): bool {
* @inheritDoc
*/
public function resolveReference(string $referenceText): ?IReference {
if ($this->matchReference($referenceText)) {
if ($this->matchReference($referenceText) && OpenProjectAPIService::isAdminConfigOk($this->config)) {
$wpId = $this->getWorkPackageIdFromUrl($referenceText);
if ($wpId !== null) {
$wpInfo = $this->openProjectAPIService->getWorkPackageInfo($this->userId, $wpId);
Expand Down
20 changes: 5 additions & 15 deletions lib/Search/OpenProjectSearchProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,14 @@ public function search(IUser $user, ISearchQuery $query): SearchResult {
$offset = $offset ? intval($offset) : 0;
$openprojectUrl = OpenProjectAPIService::sanitizeUrl($this->config->getAppValue(Application::APP_ID, 'openproject_instance_url'));
$accessToken = $this->config->getUserValue($user->getUID(), Application::APP_ID, 'token');

if ($accessToken === '') {
$searchEnabled = $this->config->getUserValue(
$user->getUID(),
Application::APP_ID, 'search_enabled',
$this->config->getAppValue(Application::APP_ID, 'default_enable_unified_search', '0')) === '1';
if ($accessToken === '' || !$searchEnabled) {
return SearchResult::paginated($this->getName(), [], 0);
}

$routeFrom = $query->getRoute();
$requestedFromSmartPicker = $routeFrom === '' || $routeFrom === 'smart-picker';

if (!$requestedFromSmartPicker) {
$searchEnabled = $this->config->getUserValue(
$user->getUID(),
Application::APP_ID, 'search_enabled',
$this->config->getAppValue(Application::APP_ID, 'default_enable_unified_search', '0')) === '1';
if (!$searchEnabled) {
return SearchResult::paginated($this->getName(), [], 0);
}
}

$searchResults = $this->service->searchWorkPackage($user->getUID(), $term, null, false);
$searchResults = array_slice($searchResults, $offset, $limit);

Expand Down
29 changes: 21 additions & 8 deletions lib/Service/OpenProjectAPIService.php
Original file line number Diff line number Diff line change
Expand Up @@ -1129,8 +1129,12 @@ public function generateAppPasswordTokenForUser(): string {
*/
public function deleteAppPassword(): void {
if ($this->hasAppPassword()) {
$tokenId = $this->tokenProvider->getTokenByUser(Application::OPEN_PROJECT_ENTITIES_NAME)[0]->getId();
$this->tokenProvider->invalidateTokenById(Application::OPEN_PROJECT_ENTITIES_NAME, $tokenId);
$tokens = $this->tokenProvider->getTokenByUser(Application::OPEN_PROJECT_ENTITIES_NAME);
foreach ($tokens as $token) {
if ($token->getName() === Application::OPEN_PROJECT_ENTITIES_NAME) {
$this->tokenProvider->invalidateTokenById(Application::OPEN_PROJECT_ENTITIES_NAME, $token->getId());
}
}
}
}

Expand All @@ -1140,7 +1144,13 @@ public function deleteAppPassword(): void {
* @return bool
*/
public function hasAppPassword(): bool {
return sizeof($this->tokenProvider->getTokenByUser(Application::OPEN_PROJECT_ENTITIES_NAME)) === 1;
$tokens = $this->tokenProvider->getTokenByUser(Application::OPEN_PROJECT_ENTITIES_NAME);
foreach ($tokens as $token) {
if ($token->getName() === Application::OPEN_PROJECT_ENTITIES_NAME) {
return true;
}
}
return false;
}

/**
Expand All @@ -1151,11 +1161,14 @@ public function hasAppPassword(): bool {
*/
public function getWorkPackageInfo(string $userId, int $wpId): array {
$result[] = null;
$searchResult = $this->searchWorkPackage($userId, null, null, false, $wpId);
$result['title'] = $this->getSubline($searchResult[0]);
$result['description'] = $this->getMainText($searchResult[0]);
$result['imageUrl'] = $this->getOpenProjectUserAvatarUrl($searchResult[0]);
$result['entry'] = $searchResult[0];
$accessToken = $this->config->getUserValue($userId, Application::APP_ID, 'token');
if ($accessToken) {
$searchResult = $this->searchWorkPackage($userId, null, null, false, $wpId);
$result['title'] = $this->getSubline($searchResult[0]);
$result['description'] = $this->getMainText($searchResult[0]);
$result['imageUrl'] = $this->getOpenProjectUserAvatarUrl($searchResult[0]);
$result['entry'] = $searchResult[0];
}
return $result;
}

Expand Down
1 change: 1 addition & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ parameters:
reportUnmatchedIgnoredErrors: false
excludePaths:
- './lib/Reference/WorkPackageReferenceProvider.php'
- './tests/lib/Reference/WorkPackageReferenceProviderTest.php'
41 changes: 32 additions & 9 deletions src/components/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
<div class="openproject-server-host">
<FormHeading index="1"
:title="t('integration_openproject', 'OpenProject server')"
:is-complete="isServerHostFormComplete" />
:is-complete="isServerHostFormComplete"
:is-dark-theme="isDarkTheme" />

<FieldValue v-if="isServerHostFormInView"
is-required
class="pb-1"
Expand Down Expand Up @@ -56,7 +58,8 @@
<FormHeading index="2"
:title="t('integration_openproject', 'OpenProject OAuth settings')"
:is-complete="isOPOAuthFormComplete"
:is-disabled="isOPOAuthFormInDisableMode" />
:is-disabled="isOPOAuthFormInDisableMode"
:is-dark-theme="isDarkTheme" />
<div v-if="isServerHostFormComplete">
<FieldValue v-if="isOPOAuthFormInView"
is-required
Expand Down Expand Up @@ -109,7 +112,8 @@
<FormHeading index="3"
:title="t('integration_openproject', 'Nextcloud OAuth client')"
:is-complete="isNcOAuthFormComplete"
:is-disabled="isNcOAuthFormInDisableMode" />
:is-disabled="isNcOAuthFormInDisableMode"
:is-dark-theme="isDarkTheme" />
<div v-if="state.nc_oauth_client">
<TextInput v-if="isNcOAuthFormInEdit"
id="nextcloud-oauth-client-id"
Expand Down Expand Up @@ -159,6 +163,15 @@
</NcButton>
</div>
</div>
<div v-if="!state.nc_oauth_client && isOPOAuthFormComplete && isOPOAuthFormInView && showDefaultManagedProjectFolders">
<NcButton data-test-id="reset-nc-oauth-btn"
@click="resetNcOauthValues">
<template #icon>
<AutoRenewIcon :size="20" />
</template>
{{ t('integration_openproject', 'Create Nextcloud OAuth values') }}
</NcButton>
</div>
</div>
<div class="project-folder-setup">
<FormHeading index="4"
Expand Down Expand Up @@ -239,7 +252,7 @@
</div>
<div v-else class="project-folder-status">
<div class="project-folder-status-value">
<b>Automatic managed folders:</b> {{ opUserAppPassword ? t('integration_openproject', 'Active') : t('integration_openproject', 'Inactive') }}
<b>Automatically managed folders:</b> {{ opUserAppPassword ? t('integration_openproject', 'Active') : t('integration_openproject', 'Inactive') }}
</div>
<ProjectFolderError
v-if="state.app_password_set && !isProjectFolderSetupCorrect"
Expand All @@ -262,7 +275,8 @@
<FormHeading index="5"
:title="t('integration_openproject', 'Project folders application connection')"
:is-complete="isOPUserAppPasswordFormComplete"
:is-disabled="isOPUserAppPasswordInDisableMode" />
:is-disabled="isOPUserAppPasswordInDisableMode"
:is-dark-theme="isDarkTheme" />
<div v-if="state.app_password_set">
<TextInput v-if="isOPUserAppPasswordFormInEdit"
id="openproject-system-password"
Expand Down Expand Up @@ -408,6 +422,7 @@ export default {
textLabelProjectFolderSetupButton: null,
// pointer for which form the request is coming
isFormStep: null,
isDarkTheme: null,
}
},
computed: {
Expand Down Expand Up @@ -528,14 +543,13 @@ export default {
isResetButtonDisabled() {
return !(this.state.openproject_client_id || this.state.openproject_client_secret || this.state.openproject_instance_url)
},
isDarkTheme() {
return !!document.querySelector("body[data-themes*='dark']")

},
},
created() {
this.init()
},
mounted() {
this.isDarkTheme = window.getComputedStyle(this.$el).getPropertyValue('--background-invert-if-dark') === 'invert(100%)'
},
methods: {
init() {
if (this.state) {
Expand Down Expand Up @@ -571,6 +585,15 @@ export default {
this.formMode.ncOauth = F_MODES.VIEW
this.isFormCompleted.ncOauth = true
}
if (!this.state.nc_oauth_client
&& this.state.openproject_instance_url
&& this.state.openproject_client_id
&& this.state.openproject_client_secret
&& this.textLabelProjectFolderSetupButton === 'Keep current setup') {
this.showDefaultManagedProjectFolders = true
this.formMode.projectFolderSetUp = F_MODES.VIEW
this.isFormCompleted.projectFolderSetUp = true
}
if (this.formMode.ncOauth === F_MODES.VIEW) {
this.showDefaultManagedProjectFolders = true
}
Expand Down
18 changes: 12 additions & 6 deletions src/components/admin/FormHeading.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
<div v-else-if="isComplete" class="complete">
<CheckBoldIcon fill-color="#FFFFFF" :size="12" />
</div>
<div v-else class="index">
<div v-else
class="index"
:class="{
'index-dark-mode': isDarkTheme,
'index-light-mode': !isDarkTheme
}">
{{ index }}
</div>
<div class="title"
Expand Down Expand Up @@ -124,7 +129,6 @@ export default {
text-align: center;
border-radius: 50%;
background: var(--color-loading-dark);
color: white;
}
.title {
font-weight: 700;
Expand All @@ -134,10 +138,12 @@ export default {
}
}

body[data-themes*='dark'] {
.index {
color: #000000;
}
.index-dark-mode {
color: #000000;
}

.index-light-mode {
color: #FFFFFF;
}

.form-heading.disabled {
Expand Down
Loading
Loading