Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -761,9 +761,13 @@ private function registerEventListeners(IRegistrationContext $context): void
*/
public function boot(IBootContext $context): void
{
// Deep link registration is deferred to avoid circular DI resolution.
// DeepLinkRegistryService depends on RegisterMapper/SchemaMapper which
// trigger circular resolution chains when resolved during boot.
// Consuming apps register their patterns lazily on first use instead.
// Dispatch the deep link registration event so consuming apps
// (Procest, Pipelinq, etc.) can register their URL patterns.
// DeepLinkRegistryService uses ContainerInterface for lazy mapper
// resolution, so no circular DI issues during registration.
$server = $context->getServerContainer();
$dispatcher = $server->get(IEventDispatcher::class);
$registry = $server->get(DeepLinkRegistryService::class);
$dispatcher->dispatchTyped(new DeepLinkRegistrationEvent(registry: $registry));
}//end boot()
}//end class
7 changes: 6 additions & 1 deletion lib/Controller/TasksController.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,13 @@ public function index(
} catch (DoesNotExistException $e) {
return new JSONResponse(data: ['error' => 'Object not found'], statusCode: 404);
} catch (Exception $e) {
// No VTODO calendar = no tasks; return empty for listing.
if (str_contains($e->getMessage(), 'No VTODO-supporting calendar') === true) {
return new JSONResponse(data: ['results' => [], 'total' => 0]);
}

return new JSONResponse(data: ['error' => $e->getMessage()], statusCode: 500);
}
}//end try
}//end index()

/**
Expand Down
22 changes: 18 additions & 4 deletions lib/Db/MagicMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -1272,11 +1272,13 @@
// Columns that don't exist use NULL::text AS placeholder.
// Cast to text ensures type compatibility across schemas in UNION.
// (e.g., one schema has 'type' as text, another as jsonb).
$existingColumns = [];
foreach (array_keys($allPropertyColumns) as $columnName) {
$quotedCol = $this->quoteIdentifier(name: $columnName, isPostgres: $isPostgres);
$colExpr = "NULL::text AS {$quotedCol}";
if ($this->columnExistsInTable(tableName: $tableName, columnName: $columnName) === true) {
$colExpr = "{$quotedCol}::text AS {$quotedCol}";
$colExpr = "{$quotedCol}::text AS {$quotedCol}";
$existingColumns[] = $columnName;
}

$selectColumns[] = $colExpr;
Expand All @@ -1301,7 +1303,14 @@
$type = $propDef['type'] ?? 'string';
if (in_array($type, ['string', 'text'], true) === true) {
$columnName = $this->sanitizeColumnName(name: $propName);
$quotedCol = $this->quoteIdentifier(name: $columnName, isPostgres: $isPostgres);
// Only score columns that actually exist in this table.
// Columns from other schemas are aliased as NULL in the SELECT
// and cannot be referenced by similarity()/ILIKE expressions.
if ($this->columnExistsInTable(tableName: $tableName, columnName: $columnName) === false) {
continue;
}

$quotedCol = $this->quoteIdentifier(name: $columnName, isPostgres: $isPostgres);
// Fallback: use CASE with ILIKE for basic relevance scoring.
$likePattern = "'%".trim($quotedTerm, "'")."%'";
$scoreExpr = "CASE WHEN {$quotedCol}::text ILIKE {$likePattern} THEN 1 ELSE 0 END";
Expand All @@ -1312,7 +1321,7 @@

$searchColumns[] = $scoreExpr;
}
}
}//end foreach

$selectColumns[] = '0 AS _search_score';
if (empty($searchColumns) === false) {
Expand All @@ -1329,7 +1338,12 @@

// Build WHERE conditions using shared method (single source of truth for filters).
// This ensures search, count, and facets all use the same filter logic.
$whereClauses = $this->searchHandler->buildWhereConditionsSql(query: $query, schema: $schema);
// Pass existing columns so property-based search only targets columns in this table.
$whereClauses = $this->searchHandler->buildWhereConditionsSql(
query: $query,
schema: $schema,
existingColumns: $existingColumns
);

if (empty($whereClauses) === false) {
$selectSql .= ' WHERE '.implode(' AND ', $whereClauses);
Expand Down Expand Up @@ -7081,7 +7095,7 @@
* @param array|null $ids Specific IDs
* @param string|null $uses Uses filter
*
* @return array<int, ObjectEntity>|int

Check failure on line 7098 in lib/Db/MagicMapper.php

View workflow job for this annotation

GitHub Actions / quality / PHP Quality (psalm)

LessSpecificImplementedReturnType

lib/Db/MagicMapper.php:7098:16: LessSpecificImplementedReturnType: The inherited return type 'int|list<OCA\OpenRegister\Db\ObjectEntity>' for OCA\OpenRegister\Db\AbstractObjectMapper::searchObjects is more specific than the implemented return type for OCA\OpenRegister\Db\MagicMapper::searchobjects 'array<int, OCA\OpenRegister\Db\ObjectEntity>|int' (see https://psalm.dev/166)
*
* @SuppressWarnings(PHPMD.BooleanArgumentFlag) Flags control security filtering behavior
*/
Expand Down
29 changes: 19 additions & 10 deletions lib/Db/MagicMapper/MagicSearchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,13 @@ function ($key) {
* Includes RBAC filtering when enabled (default). Values are quoted inline
* (not parameterized) for UNION query compatibility.
*
* @param array $query Search parameters including filters.
* @param Schema $schema The schema for property filtering.
* @param array $query Search parameters including filters.
* @param Schema $schema The schema for property filtering.
* @param array|null $existingColumns Optional list of existing column names.
*
* @return string[] Array of SQL WHERE conditions (without leading AND/WHERE).
*/
public function buildWhereConditionsSql(array $query, Schema $schema): array
public function buildWhereConditionsSql(array $query, Schema $schema, ?array $existingColumns=null): array
{
$conditions = [];
// Get connection for value quoting through QueryBuilder.
Expand Down Expand Up @@ -391,7 +392,8 @@ public function buildWhereConditionsSql(array $query, Schema $schema): array
search: trim($search),
schema: $schema,
query: $query,
connection: $connection
connection: $connection,
existingColumns: $existingColumns
);
if ($searchCondition !== null) {
$conditions[] = $searchCondition;
Expand Down Expand Up @@ -443,18 +445,20 @@ private function buildRbacConditionSql(Schema $schema): ?string
* Without _fuzzy=true: ~140ms (ILIKE only)
* With _fuzzy=true: ~160ms (ILIKE + similarity on _name)
*
* @param string $search Trimmed search term
* @param Schema $schema Schema for determining searchable columns
* @param array $query Full query array for extracting _fuzzy param
* @param object $connection Database connection for value quoting
* @param string $search Trimmed search term
* @param Schema $schema Schema for determining searchable columns
* @param array $query Full query array for extracting _fuzzy param
* @param object $connection Database connection for value quoting
* @param array|null $existingColumns Optional list of existing column names.
*
* @return string|null SQL condition or null if no search conditions generated
*/
private function buildSearchConditionSql(
string $search,
Schema $schema,
array $query,
object $connection
object $connection,
?array $existingColumns=null
): ?string {
$searchConditions = [];
$likePattern = $connection->quote('%'.$search.'%');
Expand All @@ -468,7 +472,12 @@ private function buildSearchConditionSql(
foreach ($properties as $propName => $propDef) {
$type = $propDef['type'] ?? 'string';
if ($type === 'string') {
$columnName = $this->sanitizeColumnName(name: $propName);
$columnName = $this->sanitizeColumnName(name: $propName);
// In UNION contexts, only search columns that actually exist in this table.
if ($existingColumns !== null && in_array($columnName, $existingColumns, true) === false) {
continue;
}

$searchConditions[] = "{$columnName}::text ILIKE {$likePattern}";
}
}
Expand Down
1 change: 1 addition & 0 deletions lib/Service/NoteService.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public function createNote(string $objectUuid, string $message): array
);

$comment->setMessage($message);
$comment->setVerb('comment');
$this->commentsManager->save($comment);

return $this->commentToArray(comment: $comment);
Expand Down
Loading