Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1f09b91
perf: Don't update etag of folder marked as IReliableETagStorage
CarlSchwan Jul 27, 2026
d80671d
perf(trashbin): Batch propagate file deletions
CarlSchwan Jul 27, 2026
37ac045
perf(version): Batch propagator for multi-write operations
CarlSchwan Jul 27, 2026
f7be338
refactor(Sharing): Move SharingBackend to OC
provokateurin Jul 22, 2026
8c92be9
refactor(Sharing): Always store shares in unified sharing backend and…
provokateurin Jul 21, 2026
f262364
refactor(SharingBackend): Create default values for properties and pe…
provokateurin Jul 28, 2026
fc23c1f
refactor(SharingBackend): Move value initializations to the SharingMa…
provokateurin Jul 28, 2026
ec8f046
fix(Share20): Pass previous exception if exception occurred during sh…
provokateurin Jul 23, 2026
463a1e2
fix(Share20): Only set random token if no token is set
provokateurin Jul 23, 2026
25f698b
fix(Share20): Use correct enum for share type
provokateurin Jul 23, 2026
acec9da
fix(Share20): Use correct enum for share status
provokateurin Jul 23, 2026
5bb204c
fix(Share20): Use correct enum for share permissions
provokateurin Jul 28, 2026
f7ecd25
chore: fix ncu @experimental
icewind1991 Jul 29, 2026
58dcd3d
feat(globalscale): Expose more configs
CarlSchwan Jul 29, 2026
7335eaf
refactor: Use GlobalScale/IConfig everywhere
CarlSchwan Jul 29, 2026
7afb35e
chore: migrate weather status to vue3
julien-nc Jul 28, 2026
557f5f8
fix(caldav): Expand recurring events for principal calendar search
kesselb Apr 17, 2026
b7a5b8f
chore(assets): Recompile assets
nextcloud-command Jul 30, 2026
576d78c
test: resolve potential flaky Playwright tests
susnux Jul 30, 2026
4cf0888
Merge pull request #60766 from nextcloud/bug/noid/expand-unified-search
kesselb Jul 30, 2026
b9e303f
Merge pull request #62625 from nextcloud/enh/noid/vue3-weather
julien-nc Jul 30, 2026
28a7502
Merge pull request #62688 from nextcloud/chore/stableize-tests
susnux Jul 30, 2026
bfd6f75
chore: remove deprecated global libraries
susnux Jul 29, 2026
fbf2d93
chore: compile assets
susnux Jul 30, 2026
14b21d7
Merge pull request #62677 from nextcloud/chore/deprecated-global-deps
susnux Jul 30, 2026
c65d1f9
Merge pull request #62613 from nextcloud/refactor/sharing/files_shari…
provokateurin Jul 30, 2026
0a688f0
Merge pull request #62562 from nextcloud/carl/perf/propagator
CarlSchwan Jul 30, 2026
f5b5225
Merge pull request #62660 from nextcloud/carl/gss-config
CarlSchwan Jul 30, 2026
acf233c
fix(Database): Use real idle-timer to prevent `lastInsertId` being re…
DerDreschner Jul 30, 2026
683e273
Merge pull request #62697 from nextcloud/fix/db-connectivity-check-id…
DerDreschner Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 5 additions & 6 deletions .github/workflows/npm-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,11 @@ jobs:
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'

# This does not work on server as we have some git dependencies that "npm-package-lock-add-resolved" cannot handle
# - name: Validate package-lock.json # See https://github.com/npm/cli/issues/4460
# run: |
# npm i -g npm-package-lock-add-resolved@1.1.4
# npm-package-lock-add-resolved
# git --no-pager diff --exit-code
- name: Validate package-lock.json # See https://github.com/npm/cli/issues/4460
run: |
npm i -g npm-package-lock-add-resolved@1.1.4
npm-package-lock-add-resolved
git --no-pager diff --exit-code

- name: Install dependencies & build
env:
Expand Down
69 changes: 45 additions & 24 deletions apps/dav/lib/CalDAV/CalDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,10 @@ private function searchCalendarObjects(IQueryBuilder $query, ?DateTimeInterface
}

try {
// The time-range filter is hardcoded to VEVENT: Sabre only
// expands VEVENT recurrences (EventIterator is VEVENT-only and
// VTodo::isInTimeRange ignores RRULE), so other component types
// would not be filtered correctly here.
$isValid = $this->validateFilterForObject($row, [
'name' => 'VCALENDAR',
'comp-filters' => [
Expand Down Expand Up @@ -2494,13 +2498,24 @@ private function transformSearchProperty(Property $prop) {
}

/**
* Search calendar objects across a principal's calendars.
*
* This returns the stored calendar objects and does not expand recurring
* events. Callers that need the concrete occurrence for a requested time
* range must expand recurrences from `calendardata` themselves.
*
* Note: when a `timerange` option is given, the precise filtering assumes
* VEVENT components (see searchCalendarObjects()). Passing other component
* types together with a `timerange` would drop all results.
*
* @param string $principalUri
* @param string $pattern
* @param array $componentTypes
* @param array $searchProperties
* @param array $searchParameters
* @param array $options
* @return array
*
* @return list<array{uri: string, calendarid: int, calendartype: int, calendardata: string}>
*/
public function searchPrincipalUri(string $principalUri,
string $pattern,
Expand All @@ -2516,6 +2531,11 @@ public function searchPrincipalUri(string $principalUri,
$calendarOr = [];
$searchOr = [];

$start = null;
$end = null;

// Todo: The retries when $hasLimit && $hasTimeRange from https://github.com/nextcloud/server/pull/45222 should also be applied here to the calendarObjectIdQuery

// Fetch calendars and subscription
$calendars = $this->getCalendarsForUser($principalUri);
$subscriptions = $this->getSubscriptionsForUser($principalUri);
Expand Down Expand Up @@ -2594,19 +2614,21 @@ public function searchPrincipalUri(string $principalUri,
if (isset($options['offset'])) {
$calendarObjectIdQuery->setFirstResult($options['offset']);
}
if (isset($options['timerange'])) {
if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTimeInterface) {
$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->gt(
'lastoccurence',
$calendarObjectIdQuery->createNamedParameter($options['timerange']['start']->getTimeStamp()),
));
}
if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTimeInterface) {
$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->lt(
'firstoccurence',
$calendarObjectIdQuery->createNamedParameter($options['timerange']['end']->getTimeStamp()),
));
}
if (isset($options['timerange']['start']) && $options['timerange']['start'] instanceof DateTimeInterface) {
/** @var DateTimeInterface $start */
$start = $options['timerange']['start'];
$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->gt(
'lastoccurence',
$calendarObjectIdQuery->createNamedParameter($start->getTimestamp()),
));
}
if (isset($options['timerange']['end']) && $options['timerange']['end'] instanceof DateTimeInterface) {
/** @var DateTimeInterface $end */
$end = $options['timerange']['end'];
$calendarObjectIdQuery->andWhere($calendarObjectIdQuery->expr()->lt(
'firstoccurence',
$calendarObjectIdQuery->createNamedParameter($end->getTimestamp()),
));
}

$result = $calendarObjectIdQuery->executeQuery();
Expand All @@ -2621,17 +2643,16 @@ public function searchPrincipalUri(string $principalUri,
->from('calendarobjects')
->where($query->expr()->in('id', $query->createNamedParameter($matches, IQueryBuilder::PARAM_INT_ARRAY)));

$result = $query->executeQuery();
$calendarObjects = [];
while (($array = $result->fetchAssociative()) !== false) {
$array['calendarid'] = (int)$array['calendarid'];
$array['calendartype'] = (int)$array['calendartype'];
$array['calendardata'] = $this->readBlob($array['calendardata']);
$calendarObjects = $this->searchCalendarObjects($query, $start, $end);

$calendarObjects[] = $array;
}
$result->closeCursor();
return $calendarObjects;
return array_values(array_map(function ($event) {
return [
'uri' => (string)$event['uri'],
'calendarid' => (int)$event['calendarid'],
'calendartype' => (int)$event['calendartype'],
'calendardata' => (string)$this->readBlob($event['calendardata']),
];
}, $calendarObjects));
}, $this->db);
}

Expand Down
28 changes: 13 additions & 15 deletions apps/dav/lib/Search/ACalendarSearchProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use OCP\IURLGenerator;
use OCP\Search\IProvider;
use Sabre\VObject\Component;
use Sabre\VObject\Reader;
use Sabre\VObject\Component\VCalendar;

/**
* Class ACalendarSearchProvider
Expand Down Expand Up @@ -76,34 +76,32 @@ protected function getSortedSubscriptions(string $principalUri): array {

/**
* Returns the primary VEvent / VJournal / VTodo component
*
* If it's a component with recurrence-ids, it will return
* the primary component
*
* TODO: It would be a nice enhancement to show recurrence-exceptions
* as individual search-results.
*
* For now we will just display the primary element of a recurrence-set.
*
* @param string $calendarData
* Returns null when the calendar has no component of the requested type.
*
* @param VCalendar $vCalendar
* @param string $componentName
* @return Component
* @return Component|null
*/
protected function getPrimaryComponent(string $calendarData, string $componentName): Component {
$vCalendar = Reader::read($calendarData, Reader::OPTION_FORGIVING);

$components = $vCalendar->select($componentName);
if (count($components) === 1) {
return $components[0];
}

// If it's a recurrence-set, take the primary element
foreach ($components as $component) {
protected function getPrimaryComponent(VCalendar $vCalendar, string $componentName): ?Component {
$first = null;
foreach ($vCalendar->select($componentName) as $component) {
/** @var Component $component */
// Prefer the recurrence-set master (no RECURRENCE-ID); otherwise the first element.
$first ??= $component;
if (!$component->{'RECURRENCE-ID'}) {
return $component;
}
}

// In case of error, just fallback to the first element in the set
return $components[0];
return $first;
}
}
Loading
Loading