3030use TYPO3 \CMS \Backend \RecordList \Event \ModifyRecordListTableActionsEvent ;
3131use TYPO3 \CMS \Backend \Routing \PreviewUriBuilder ;
3232use TYPO3 \CMS \Backend \Routing \UriBuilder ;
33+ use TYPO3 \CMS \Backend \Search \LiveSearch \DatabaseRecordProvider ;
3334use TYPO3 \CMS \Backend \Template \Components \Buttons \ButtonInterface ;
3435use TYPO3 \CMS \Backend \Template \Components \Buttons \GenericButton ;
3536use TYPO3 \CMS \Backend \Tree \Repository \PageTreeRepository ;
5657use TYPO3 \CMS \Core \Schema \Field \DateTimeFieldType ;
5758use TYPO3 \CMS \Core \Schema \Field \NumberFieldType ;
5859use TYPO3 \CMS \Core \Schema \SearchableSchemaFieldsCollector ;
60+ use TYPO3 \CMS \Core \Schema \TcaSchemaFactory ;
5961use TYPO3 \CMS \Core \Service \DependencyOrderingService ;
6062use TYPO3 \CMS \Core \Site \Entity \SiteLanguage ;
6163use TYPO3 \CMS \Core \Type \Bitmask \Permission ;
@@ -412,6 +414,7 @@ public function __construct(
412414 protected readonly BackendViewFactory $ backendViewFactory ,
413415 protected readonly ModuleProvider $ moduleProvider ,
414416 protected readonly SearchableSchemaFieldsCollector $ searchableSchemaFieldsCollector ,
417+ protected readonly TcaSchemaFactory $ tcaSchemaFactory ,
415418 ) {
416419 $ this ->calcPerms = new Permission ();
417420 $ this ->spaceIcon = '<span class="btn btn-default disabled" aria-hidden="true"> ' . $ this ->iconFactory ->getIcon ('empty-empty ' , IconSize::SMALL )->render () . '</span> ' ;
@@ -2599,37 +2602,59 @@ protected function makeSearchString(string $table, int $currentPid, QueryBuilder
25992602 }
26002603
26012604 $ searchableFields = $ this ->searchableSchemaFieldsCollector ->getFields ($ table );
2605+ [$ subSchemaDivisorFieldName , $ fieldsSubSchemaTypes ] = $ this ->getSchemaFieldSubSchemaTypes ($ table );
26022606 // Get fields from ctrl section of TCA first
26032607 if (MathUtility::canBeInterpretedAsInteger ($ this ->searchString )) {
26042608 $ constraints [] = $ expressionBuilder ->eq ('uid ' , (int )$ this ->searchString );
26052609 foreach ($ searchableFields as $ field ) {
26062610 $ fieldConfig = $ field ->getConfiguration ();
2611+ $ searchConstraint = null ;
26072612 if ($ field instanceof NumberFieldType || $ field instanceof DateTimeFieldType) {
26082613 if (!isset ($ fieldConfig ['search ' ]['pidonly ' ])
26092614 || ($ fieldConfig ['search ' ]['pidonly ' ] && $ currentPid > 0 )
26102615 ) {
2611- $ constraints [] = $ expressionBuilder ->and (
2616+ $ searchConstraint = $ expressionBuilder ->and (
26122617 $ expressionBuilder ->eq ($ field ->getName (), (int )$ this ->searchString ),
26132618 $ expressionBuilder ->eq ($ tablePidField , $ currentPid )
26142619 );
2620+ } else {
2621+ continue ;
26152622 }
26162623 } else {
2617- $ constraints [] = $ expressionBuilder ->like (
2624+ $ searchConstraint = $ expressionBuilder ->like (
26182625 $ field ->getName (),
26192626 $ queryBuilder ->quote ('% ' . $ this ->searchString . '% ' )
26202627 );
26212628 }
2629+
2630+ // If this table has subtypes (e.g. tt_content.CType), we want to ensure that only CType that contain
2631+ // e.g. "bodytext" in their list of fields, to search through them. This is important when a field
2632+ // is filled but its type has been changed.
2633+ if ($ subSchemaDivisorFieldName !== ''
2634+ && isset ($ fieldsSubSchemaTypes [$ field ->getName ()])
2635+ && $ fieldsSubSchemaTypes [$ field ->getName ()] !== []
2636+ ) {
2637+ // Using `IN()` with a string-value quoted list is fine for all database systems, even when
2638+ // used on integer-typed fields and no additional work required here to mitigate something.
2639+ $ searchConstraint = $ queryBuilder ->expr ()->and (
2640+ $ searchConstraint ,
2641+ $ queryBuilder ->expr ()->in (
2642+ $ subSchemaDivisorFieldName ,
2643+ $ queryBuilder ->quoteArrayBasedValueListToStringList ($ fieldsSubSchemaTypes [$ field ->getName ()])
2644+ ),
2645+ );
2646+ }
2647+
2648+ $ constraints [] = $ searchConstraint ;
26222649 }
26232650 } elseif ($ searchableFields ->count () > 0 ) {
26242651 $ like = $ queryBuilder ->quote ('% ' . $ queryBuilder ->escapeLikeWildcards ($ this ->searchString ) . '% ' );
26252652 foreach ($ searchableFields as $ field ) {
26262653 $ fieldConfig = $ field ->getConfiguration ();
2627- $ searchConstraint = $ expressionBuilder ->and (
2628- $ expressionBuilder ->comparison (
2629- 'LOWER( ' . $ queryBuilder ->castFieldToTextType ($ field ->getName ()) . ') ' ,
2630- 'LIKE ' ,
2631- 'LOWER( ' . $ like . ') '
2632- )
2654+ $ searchConstraint = $ expressionBuilder ->comparison (
2655+ 'LOWER( ' . $ queryBuilder ->castFieldToTextType ($ field ->getName ()) . ') ' ,
2656+ 'LIKE ' ,
2657+ 'LOWER( ' . $ like . ') '
26332658 );
26342659 if (is_array ($ fieldConfig ['search ' ] ?? null )) {
26352660 $ searchConfig = $ fieldConfig ['search ' ];
@@ -2638,14 +2663,37 @@ protected function makeSearchString(string $table, int $currentPid, QueryBuilder
26382663 $ searchConstraint = $ expressionBuilder ->and ($ expressionBuilder ->like ($ field ->getName (), $ like ));
26392664 }
26402665 if (($ searchConfig ['pidonly ' ] ?? false ) && $ currentPid > 0 ) {
2641- $ searchConstraint = $ searchConstraint ->with ($ expressionBuilder ->eq ($ tablePidField , (int )$ currentPid ));
2666+ $ searchConstraint = $ expressionBuilder ->and (
2667+ $ searchConstraint ,
2668+ $ expressionBuilder ->eq ($ tablePidField , (int )$ currentPid ),
2669+ );
26422670 }
26432671 if ($ searchConfig ['andWhere ' ] ?? false ) {
2644- $ searchConstraint = $ searchConstraint ->with (
2672+ $ searchConstraint = $ expressionBuilder ->and (
2673+ $ searchConstraint ,
26452674 QueryHelper::quoteDatabaseIdentifiers ($ queryBuilder ->getConnection (), QueryHelper::stripLogicalOperatorPrefix ($ fieldConfig ['search ' ]['andWhere ' ]))
26462675 );
26472676 }
26482677 }
2678+
2679+ // If this table has subtypes (e.g. tt_content.CType), we want to ensure that only CType that contain
2680+ // e.g. "bodytext" in their list of fields, to search through them. This is important when a field
2681+ // is filled but its type has been changed.
2682+ if ($ subSchemaDivisorFieldName !== ''
2683+ && isset ($ fieldsSubSchemaTypes [$ field ->getName ()])
2684+ && $ fieldsSubSchemaTypes [$ field ->getName ()] !== []
2685+ ) {
2686+ // Using `IN()` with a string-value quoted list is fine for all database systems, even when
2687+ // used on integer-typed fields and no additional work required here to mitigate something.
2688+ $ searchConstraint = $ queryBuilder ->expr ()->and (
2689+ $ searchConstraint ,
2690+ $ queryBuilder ->expr ()->in (
2691+ $ subSchemaDivisorFieldName ,
2692+ $ queryBuilder ->quoteArrayBasedValueListToStringList ($ fieldsSubSchemaTypes [$ field ->getName ()])
2693+ ),
2694+ );
2695+ }
2696+
26492697 $ constraints [] = $ searchConstraint ;
26502698 }
26512699 }
@@ -3420,4 +3468,42 @@ protected function addDividerToCellGroup(array &$cells): void
34203468 $ this ->addActionToCellGroup ($ cells , '<hr class="dropdown-divider"> ' , 'divider ' );
34213469 }
34223470 }
3471+
3472+ /**
3473+ * Returns table subschema divisor field name and a list of fields not included in all subSchemas along with
3474+ * the list of subSchemas they are included.
3475+ *
3476+ * @param string $tableName
3477+ * @return array{0: string, 1: array<string, list<string>>}
3478+ * @todo Consider to move this to {@see SearchableSchemaFieldsCollector}, a dedicated trait or a shared place to
3479+ * mitigate code duplication (and maintenance in different places).
3480+ * - {@see PageRecordProvider::getSchemaFieldSubSchemaTypes()}
3481+ * - {@see DatabaseRecordProvider::getSchemaFieldSubSchemaTypes()}
3482+ */
3483+ protected function getSchemaFieldSubSchemaTypes (string $ tableName ): array
3484+ {
3485+ $ result = [
3486+ 0 => '' ,
3487+ 1 => [],
3488+ ];
3489+ if (!$ this ->tcaSchemaFactory ->has ($ tableName )) {
3490+ return $ result ;
3491+ }
3492+ $ schema = $ this ->tcaSchemaFactory ->get ($ tableName );
3493+ if ($ schema ->getSubSchemaDivisorField () === null ) {
3494+ return $ result ;
3495+ }
3496+ $ result [0 ] = $ schema ->getSubSchemaDivisorField ()->getName ();
3497+ foreach ($ schema ->getSubSchemata () as $ recordType => $ subSchemata ) {
3498+ foreach ($ subSchemata ->getFields () as $ fieldInSubschema => $ fieldConfig ) {
3499+ $ result [1 ][$ fieldInSubschema ] ??= [];
3500+ $ result [1 ][$ fieldInSubschema ][] = $ recordType ;
3501+ }
3502+ }
3503+ // Remove all fields which are contained in all sub-schemas, determined by
3504+ // comparing each field types count with table types count.
3505+ $ subSchemaCount = count ($ schema ->getSubSchemata ());
3506+ $ result [1 ] = array_filter ($ result [1 ], static fn ($ value ) => count ($ value ) < $ subSchemaCount );
3507+ return $ result ;
3508+ }
34233509}
0 commit comments