View | Details | Raw Unified | Return to bug 37389
Collapse All | Expand All

(-)a/Koha/ILL/Requests.pm (-1 / +15 lines)
Lines 22-29 use Modern::Perl; Link Here
22
use Koha::Database;
22
use Koha::Database;
23
use Koha::ILL::Request;
23
use Koha::ILL::Request;
24
use Koha::ILL::Request::Config;
24
use Koha::ILL::Request::Config;
25
use Koha::Objects::Mixin::ExtendedAttributes;
25
26
26
use base qw(Koha::Objects);
27
use base qw(Koha::Objects::Mixin::ExtendedAttributes Koha::Objects);
27
28
28
=head1 NAME
29
=head1 NAME
29
30
Lines 106-111 sub search_incomplete { Link Here
106
    } );
107
    } );
107
}
108
}
108
109
110
=head3 extended_attributes_config
111
112
=cut
113
114
sub extended_attributes_config {
115
    my ( $self ) = @_;
116
    return {
117
        'id_field'     => { 'foreign' => 'illrequest_id', 'self' => 'illrequest_id' },
118
        'key_field'    => 'type',
119
        'schema_class' => 'Koha::Schema::Result::Illrequestattribute',
120
    };
121
}
122
109
=head2 Internal methods
123
=head2 Internal methods
110
124
111
=head3 _type
125
=head3 _type
(-)a/Koha/Objects/Mixin/ExtendedAttributes.pm (+234 lines)
Line 0 Link Here
1
package Koha::Objects::Mixin::ExtendedAttributes;
2
3
# Copyright 2024 PTFS Europe Ltd
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Scalar::Util qw( reftype );
22
23
=head1 NAME
24
25
Koha::Objects::Mixin::ExtendedAttributes
26
27
=head2 Class methods
28
29
30
=head3 search
31
32
    Overwrites the search method to include extended attributes rewrite and dynamic relation accessors
33
34
=cut
35
36
sub search {
37
    my ( $self, $params, $attributes ) = @_;
38
39
    my $class = ref($self) ? ref($self) : $self;
40
41
    $self->handle_query_extended_attributes(
42
        {
43
            attributes      => $attributes,
44
            filtered_params => $params,
45
        }
46
    );
47
48
    my $rs = $self->_resultset()->search( $params, $attributes );
49
    return $class->_new_from_dbic($rs);
50
}
51
52
=head3 handle_query_extended_attributes
53
54
    Checks for the presence of extended_attributes in a query
55
    If present, it builds the dynamic extended attributes relations and rewrites the query to include the extended_attributes relation
56
57
=cut
58
59
sub handle_query_extended_attributes {
60
    my ( $self, $args ) = @_;
61
62
    my $attributes      = $args->{attributes};
63
    my $filtered_params = $args->{filtered_params};
64
65
    if (   reftype( $attributes->{prefetch} )
66
        && reftype( $attributes->{prefetch} ) eq 'ARRAY'
67
        && grep ( /extended_attributes/, @{ $attributes->{prefetch} } ) )
68
    {
69
70
        my @array = $self->_get_extended_attributes_entries($filtered_params);
71
72
        # Calling our private method to build the extended attributes relations
73
        my @joins = $self->_build_extended_attributes_relations( \@array );
74
        push @{ $attributes->{join} }, @joins;
75
76
    }
77
}
78
79
=head3 _get_extended_attributes_entries
80
81
    $self->_get_extended_attributes_entries( $filtered_params, 0 )
82
83
Recursive function that returns the rewritten extended_attributes query entries.
84
85
Given:
86
[
87
    '-and',
88
    [
89
        {
90
            'extended_attributes.code'      => 'CODE_1',
91
            'extended_attributes.attribute' => { 'like' => '%Bar%' }
92
        },
93
        {
94
            'extended_attributes.attribute' => { 'like' => '%Bar%' },
95
            'extended_attributes.code'      => 'CODE_2'
96
        }
97
    ]
98
];
99
100
Returns :
101
102
[
103
    'CODE_1',
104
    'CODE_2'
105
]
106
107
=cut
108
109
sub _get_extended_attributes_entries {
110
    my ( $self, $params, @array ) = @_;
111
112
    if ( reftype($params) && reftype($params) eq 'HASH' ) {
113
114
        # rewrite additional_field_values table query params
115
        @array = _rewrite_related_metadata_query( $params, 'field_id', 'value', @array )
116
            if $params->{'extended_attributes.field_id'};
117
118
        # rewrite borrower_attributes table query params
119
        @array = _rewrite_related_metadata_query( $params, 'code', 'attribute', @array )
120
            if $params->{'extended_attributes.code'};
121
122
        # rewrite illrequestattributes table query params
123
        @array = _rewrite_related_metadata_query( $params, 'type', 'value', @array )
124
            if $params->{'extended_attributes.type'};
125
126
        foreach my $key ( keys %{$params} ) {
127
            return $self->_get_extended_attributes_entries( $params->{$key}, @array );
128
        }
129
    } elsif ( reftype($params) && reftype($params) eq 'ARRAY' ) {
130
        foreach my $ea_instance (@$params) {
131
            @array = $self->_get_extended_attributes_entries( $ea_instance, @array );
132
        }
133
        return @array;
134
    } else {
135
        return @array;
136
    }
137
}
138
139
=head3 _rewrite_related_metadata_query
140
141
        $extended_attributes_entries =
142
            _rewrite_related_metadata_query( $params, 'field_id', 'value', @array )
143
144
Helper function that rewrites all subsequent extended_attributes queries to match the alias generated by the dbic self left join
145
Take the below example (patrons):
146
        [
147
            {
148
                "extended_attributes.attribute":{"like":"%123%"},
149
                "extended_attributes.code":"CODE_1"
150
            }
151
        ],
152
        [
153
            {
154
                "extended_attributes.attribute":{"like":"%abc%" },
155
                "extended_attributes.code":"CODE_2"
156
            }
157
        ]
158
159
It'll be rewritten as:
160
        [
161
            {
162
                'extended_attributes_CODE_1.attribute' => { 'like' => '%123%' },
163
                'extended_attributes_CODE_1.code' => 'CODE_1'
164
            }
165
        ],
166
            [
167
            {
168
                'extended_attributes_CODE_2.attribute' => { 'like' => '%abc%' },
169
                'extended_attributes_CODE_2.code' => 'CODE_2'
170
            }
171
        ]
172
173
=cut
174
175
sub _rewrite_related_metadata_query {
176
    my ( $params, $key, $value, @array ) = @_;
177
178
    if ( ref \$params->{ 'extended_attributes.' . $key } eq 'SCALAR' ) {
179
        my $old_key_value = delete $params->{ 'extended_attributes.' . $key };
180
        my $new_key_value = "extended_attributes_$old_key_value" . "." . $key;
181
        $params->{$new_key_value} = $old_key_value;
182
183
        my $old_value_value = delete $params->{ 'extended_attributes.' . $value };
184
        my $new_value_value = "extended_attributes_$old_key_value" . "." . $value;
185
        $params->{$new_value_value} = $old_value_value;
186
        push @array, $old_key_value;
187
    }
188
189
    return @array;
190
}
191
192
=head3 _build_extended_attributes_relations
193
194
    Method to dynamically add has_many relations for Koha classes that support extended_attributes.
195
196
    Returns a list of relation accessor names.
197
198
=cut
199
200
sub _build_extended_attributes_relations {
201
    my ( $self, $types ) = @_;
202
203
    return if( !grep ( /extended_attributes/, keys %{$self->_resultset->result_source->_relationships} ) );
204
205
    my $ea_config = $self->extended_attributes_config;
206
207
    my $result_source = $self->_resultset->result_source;
208
    for my $type ( @{$types} ) {
209
        $result_source->add_relationship(
210
            "extended_attributes_$type",
211
            "$ea_config->{schema_class}",
212
            sub {
213
                my $args = shift;
214
215
                return {
216
                    "$args->{foreign_alias}.$ea_config->{id_field}->{foreign}" =>
217
                        { -ident => "$args->{self_alias}.$ea_config->{id_field}->{self}" },
218
                    "$args->{foreign_alias}.$ea_config->{key_field}" => { '=', $type },
219
                };
220
            },
221
            {
222
                accessor       => 'multi',
223
                join_type      => 'LEFT',
224
                cascade_copy   => 0,
225
                cascade_delete => 0,
226
                is_depends_on  => 0
227
            },
228
        );
229
230
    }
231
    return map { 'extended_attributes_' . $_ } @{$types};
232
}
233
234
1;
(-)a/Koha/Patrons.pm (-1 / +14 lines)
Lines 29-35 use Koha::Patron; Link Here
29
use Koha::Exceptions::Patron;
29
use Koha::Exceptions::Patron;
30
use Koha::Patron::Categories;
30
use Koha::Patron::Categories;
31
31
32
use base qw(Koha::Objects);
32
use base qw(Koha::Objects::Mixin::ExtendedAttributes Koha::Objects);
33
33
34
=head1 NAME
34
=head1 NAME
35
35
Lines 566-571 sub filter_by_have_permission { Link Here
566
    );
566
    );
567
}
567
}
568
568
569
=head3 extended_attributes_config
570
571
=cut
572
573
sub extended_attributes_config {
574
    my ( $self ) = @_;
575
    return {
576
        'id_field'     => { 'foreign' => 'borrowernumber', 'self' => 'borrowernumber' },
577
        'key_field'    => 'code',
578
        'schema_class' => 'Koha::Schema::Result::BorrowerAttribute',
579
    };
580
}
581
569
=head3 _type
582
=head3 _type
570
583
571
=cut
584
=cut
(-)a/Koha/REST/Plugin/Objects.pm (-8 lines)
Lines 287-300 controller, and thus shouldn't be called twice in it. Link Here
287
            $c->stash('koha.pagination.base_total'   => $result_set->count);
287
            $c->stash('koha.pagination.base_total'   => $result_set->count);
288
            $c->stash('koha.pagination.query_params' => $args);
288
            $c->stash('koha.pagination.query_params' => $args);
289
289
290
            # Check and handle related metadata table joins
291
            $c->dbic_extended_attributes_join(
292
                {
293
                    attributes      => $attributes,
294
                    filtered_params => $filtered_params
295
                }
296
            );
297
298
            $c->dbic_validate_operators( { filtered_params => $filtered_params } );
290
            $c->dbic_validate_operators( { filtered_params => $filtered_params } );
299
291
300
            # Generate the resultset
292
            # Generate the resultset
(-)a/Koha/REST/Plugin/Query.pm (-154 lines)
Lines 160-193 Generates the DBIC prefetch attribute based on embedded relations, and merges in Link Here
160
        }
160
        }
161
    );
161
    );
162
162
163
=head3 dbic_extended_attributes_join
164
165
    $attributes = $c->dbic_extended_attributes_join({ attributes => $attributes, result_set => $result_set });
166
167
Generates the DBIC join attribute based on extended_attributes query entries, and merges into I<$attributes>.
168
169
=cut
170
171
    $app->helper(
172
        'dbic_extended_attributes_join' => sub {
173
            my ( $c, $args ) = @_;
174
175
            my $attributes      = $args->{attributes};
176
            my $filtered_params = $args->{filtered_params};
177
178
            if (   reftype( $attributes->{prefetch} )
179
                && reftype( $attributes->{prefetch} ) eq 'ARRAY'
180
                && grep ( /extended_attributes/, @{ $attributes->{prefetch} } ) )
181
            {
182
                my $ea_entries = $self->_get_extended_attributes_entries( $filtered_params, 0 );
183
                while ( $ea_entries > 0 ) {
184
                    push( @{ $attributes->{join} }, 'extended_attributes' );
185
                    $ea_entries--;
186
                }
187
            }
188
        }
189
    );
190
191
=head3 dbic_validate_operators
163
=head3 dbic_validate_operators
192
164
193
    $attributes = $c->dbic_validate_operators( { filtered_params => $filtered_params } );
165
    $attributes = $c->dbic_validate_operators( { filtered_params => $filtered_params } );
Lines 532-662 sub _parse_dbic_query { Link Here
532
    }
504
    }
533
}
505
}
534
506
535
=head3 _get_extended_attributes_entries
536
537
    $self->_get_extended_attributes_entries( $filtered_params, 0 )
538
539
Recursive function that returns the number of extended_attributes entries present in a query.
540
Example: Returns 2 if given a $filtered_params containing the below:
541
542
{
543
    "-and":[
544
        [
545
            {
546
                "extended_attributes.value":{
547
                    "like":"abc%"
548
                },
549
                "extended_attributes.code":[
550
                    [
551
                        "arbitrary_attr_code",
552
                        "another_attr_code"
553
                    ]
554
                ]
555
            }
556
        ],
557
        [
558
            {
559
                "extended_attributes.value":{
560
                    "like":"123%"
561
                },
562
                "extended_attributes.code":[
563
                    [
564
                        "arbitrary_attr_code",
565
                        "another_attr_code"
566
                    ]
567
                ]
568
            }
569
        ]
570
    ]
571
}
572
573
=cut
574
575
sub _get_extended_attributes_entries {
576
    my ( $self, $params, $extended_attributes_entries ) = @_;
577
578
    if ( reftype($params) && reftype($params) eq 'HASH' ) {
579
580
        # rewrite additional_field_values table query params
581
        $extended_attributes_entries =
582
            _rewrite_related_metadata_query( $params, $extended_attributes_entries, 'field_id', 'value' )
583
            if $params->{'extended_attributes.field_id'};
584
585
        # rewrite borrower_attributes table query params
586
        $extended_attributes_entries =
587
            _rewrite_related_metadata_query( $params, $extended_attributes_entries, 'code', 'attribute' )
588
            if $params->{'extended_attributes.code'};
589
590
        # rewrite illrequestattributes table query params
591
        $extended_attributes_entries =
592
            _rewrite_related_metadata_query( $params, $extended_attributes_entries, 'type', 'value' )
593
            if $params->{'extended_attributes.type'};
594
595
        foreach my $key ( keys %{$params} ) {
596
            return $self->_get_extended_attributes_entries( $params->{$key}, $extended_attributes_entries );
597
        }
598
    } elsif ( reftype($params) && reftype($params) eq 'ARRAY' ) {
599
        foreach my $ea_instance (@$params) {
600
            $extended_attributes_entries =
601
                +$self->_get_extended_attributes_entries( $ea_instance, $extended_attributes_entries );
602
        }
603
        return $extended_attributes_entries;
604
    } else {
605
        return $extended_attributes_entries;
606
    }
607
}
608
609
=head3 _rewrite_related_metadata_query
610
611
        $extended_attributes_entries =
612
            _rewrite_related_metadata_query( $params, $extended_attributes_entries, 'field_id', 'value' )
613
614
Helper function that rewrites all subsequent extended_attributes queries to match the alias generated by the dbic self left join
615
Take the below example (patrons), assuming it is the third instance of an extended_attributes query:
616
{
617
    "extended_attributes.value":{
618
        "like":"123%"
619
    },
620
    "extended_attributes.code":[
621
        [
622
            "arbitrary_attr_code",
623
            "another_attr_code"
624
        ]
625
    ]
626
}
627
628
It'll be rewritten as:
629
{
630
    "extended_attributes_3.value":{
631
        "like":"123%"
632
    },
633
    "extended_attributes_3.code":[
634
        [
635
            "arbitrary_attr_code",
636
            "another_attr_code"
637
        ]
638
    ]
639
}
640
641
=cut
642
643
sub _rewrite_related_metadata_query {
644
    my ( $params, $extended_attributes_entries, $key, $value ) = @_;
645
646
    $extended_attributes_entries++;
647
    if ( $extended_attributes_entries > 1 ) {
648
        my $old_key_value = delete $params->{ 'extended_attributes.' . $key };
649
        my $new_key_value = "extended_attributes_$extended_attributes_entries" . "." . $key;
650
        $params->{$new_key_value} = $old_key_value;
651
652
        my $old_value_value = delete $params->{ 'extended_attributes.' . $value };
653
        my $new_value_value = "extended_attributes_$extended_attributes_entries" . "." . $value;
654
        $params->{$new_value_value} = $old_value_value;
655
    }
656
657
    return $extended_attributes_entries;
658
}
659
660
=head3 _validate_operator
507
=head3 _validate_operator
661
508
662
=cut
509
=cut
663
- 

Return to bug 37389