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

(-)a/Koha/REST/Plugin/Objects.pm (+18 lines)
Lines 19-24 use Modern::Perl; Link Here
19
19
20
use Mojo::Base 'Mojolicious::Plugin';
20
use Mojo::Base 'Mojolicious::Plugin';
21
21
22
use JSON qw(decode_json);
23
22
=head1 NAME
24
=head1 NAME
23
25
24
Koha::REST::Plugin::Objects
26
Koha::REST::Plugin::Objects
Lines 97-102 sub register { Link Here
97
                }
99
                }
98
            }
100
            }
99
101
102
            if( defined $reserved_params->{q} || defined $reserved_params->{query} || defined $reserved_params->{'x-koha-query'}) {
103
                $filtered_params //={};
104
                my @query_params_array;
105
                my $query_params;
106
                push @query_params_array, $reserved_params->{query} if defined $reserved_params->{query};
107
                push @query_params_array, decode_json($reserved_params->{q}) if defined $reserved_params->{q};
108
                push @query_params_array, decode_json($reserved_params->{'x-koha-query'}) if defined $reserved_params->{'x-koha-query'};
109
110
                if(scalar(@query_params_array) > 1) {
111
                    $query_params = {'-and' => \@query_params_array};
112
                } else {
113
                    $query_params = $query_params_array[0];
114
                }
115
116
                $filtered_params = $c->merge_q_params( $filtered_params, $query_params, $result_set );
117
            }
100
            # Perform search
118
            # Perform search
101
            my $objects = $result_set->search( $filtered_params, $attributes );
119
            my $objects = $result_set->search( $filtered_params, $attributes );
102
120
(-)a/Koha/REST/Plugin/Query.pm (-1 / +73 lines)
Lines 20-25 use Modern::Perl; Link Here
20
use Mojo::Base 'Mojolicious::Plugin';
20
use Mojo::Base 'Mojolicious::Plugin';
21
use List::MoreUtils qw(any);
21
use List::MoreUtils qw(any);
22
use Scalar::Util qw(reftype);
22
use Scalar::Util qw(reftype);
23
use JSON qw(decode_json);
23
24
24
use Koha::Exceptions;
25
use Koha::Exceptions;
25
26
Lines 179-184 is raised. Link Here
179
        }
180
        }
180
    );
181
    );
181
182
183
=head3 merge_q_params
184
185
    $c->merge_q_params( $filtered_params, $q_params, $result_set );
186
187
Merges parameters from $q_params into $filtered_params.
188
189
=cut
190
191
    $app->helper(
192
        'merge_q_params' => sub {
193
194
            my ( $c, $filtered_params, $q_params, $result_set ) = @_;
195
196
            $q_params = decode_json($q_params) unless reftype $q_params;
197
198
            my $params = _parse_dbic_query($q_params, $result_set);
199
200
            return $params unless scalar(keys %{$filtered_params});
201
            return {'-and' => [$params, $filtered_params ]};
202
        }
203
    );
204
182
=head3 stash_embed
205
=head3 stash_embed
183
206
184
    $c->stash_embed( $c->match->endpoint->pattern->defaults->{'openapi.op_spec'} );
207
    $c->stash_embed( $c->match->endpoint->pattern->defaults->{'openapi.op_spec'} );
Lines 229-235 is raised. Link Here
229
252
230
sub _reserved_words {
253
sub _reserved_words {
231
254
232
    my @reserved_words = qw( _match _order_by _page _per_page );
255
    my @reserved_words = qw( _match _order_by _page _per_page q query x-koha-query);
233
    return \@reserved_words;
256
    return \@reserved_words;
234
}
257
}
235
258
Lines 346-349 sub _parse_prefetch { Link Here
346
    return $prefetch;
369
    return $prefetch;
347
}
370
}
348
371
372
sub _from_api_param {
373
    my ($key, $result_set) = @_;
374
375
    if($key =~ /\./) {
376
377
        my ($curr, $next) = split /\s*\.\s*/, $key, 2;
378
379
        return $curr.'.'._from_api_param($next, $result_set) if $curr eq 'me';
380
381
        my $ko_class = $result_set->prefetch_whitelist->{$curr};
382
383
        Koha::Exceptions::BadParameter->throw("Cannot find Koha::Object class for $curr")
384
            unless defined $ko_class;
385
386
        $result_set = $ko_class->new;
387
388
        if ($next =~ /\./) {
389
            return _from_api_param($next, $result_set);
390
        } else {
391
            return $curr.'.'.($result_set->from_api_mapping && defined $result_set->from_api_mapping->{$next} ? $result_set->from_api_mapping->{$next}:$next);
392
        }
393
    } else {
394
        return defined $result_set->from_api_mapping->{$key} ? $result_set->from_api_mapping->{$key} : $key;
395
    }
396
}
397
398
sub _parse_dbic_query {
399
    my ($q_params, $result_set) = @_;
400
401
    if(reftype($q_params) && reftype($q_params) eq 'HASH') {
402
        my $parsed_hash;
403
        foreach my $key (keys %{$q_params}) {
404
            if($key =~ /-?(not_?)?bool/i ) {
405
                $parsed_hash->{$key} = _from_api_param($q_params->{$key}, $result_set);
406
                next;
407
            }
408
            my $k = _from_api_param($key, $result_set);
409
            $parsed_hash->{$k} = _parse_dbic_query($q_params->{$key}, $result_set);
410
        }
411
        return $parsed_hash;
412
    } elsif (reftype($q_params) && reftype($q_params) eq 'ARRAY') {
413
        my @mapped = map{ _parse_dbic_query($_, $result_set) } @$q_params;
414
        return \@mapped;
415
    } else {
416
        return $q_params;
417
    }
418
419
}
420
349
1;
421
1;
(-)a/t/Koha/REST/Plugin/Query.t (-1 / +60 lines)
Lines 23-28 use Try::Tiny; Link Here
23
23
24
use Koha::Cities;
24
use Koha::Cities;
25
use Koha::Holds;
25
use Koha::Holds;
26
use Koha::Biblios;
26
27
27
app->log->level('error');
28
app->log->level('error');
28
29
Lines 134-139 get '/dbic_merge_prefetch' => sub { Link Here
134
    $c->render( json => $attributes, status => 200 );
135
    $c->render( json => $attributes, status => 200 );
135
};
136
};
136
137
138
get '/merge_q_params' => sub {
139
  my $c = shift;
140
  my $filtered_params = {'biblio_id' => 1};
141
  my $result_set = Koha::Biblios->new;
142
  $filtered_params = $c->merge_q_params($filtered_params, $c->req->json->{q}, $result_set);
143
144
  $c->render( json => $filtered_params, status => 200 );
145
};
146
137
get '/build_query' => sub {
147
get '/build_query' => sub {
138
    my $c = shift;
148
    my $c = shift;
139
    my ( $filtered_params, $reserved_params ) =
149
    my ( $filtered_params, $reserved_params ) =
Lines 209-215 sub to_model { Link Here
209
219
210
# The tests
220
# The tests
211
221
212
use Test::More tests => 5;
222
use Test::More tests => 6;
213
use Test::Mojo;
223
use Test::Mojo;
214
224
215
subtest 'extract_reserved_params() tests' => sub {
225
subtest 'extract_reserved_params() tests' => sub {
Lines 297-302 subtest '/dbic_merge_prefetch' => sub { Link Here
297
      ->json_like( '/prefetch/1' => qr/item|biblio/ );
307
      ->json_like( '/prefetch/1' => qr/item|biblio/ );
298
};
308
};
299
309
310
subtest '/merge_q_params' => sub {
311
  plan tests => 3;
312
  my $t = Test::Mojo->new;
313
314
  $t->get_ok('/merge_q_params' => json => {
315
    q => {
316
      "-not_bool" => "suggestions.suggester.patron_card_lost",
317
      "-or" => [
318
        {
319
          "creation_date" => {
320
            "!=" => ["fff", "zzz", "xxx"]
321
          }
322
        },
323
        { "suggestions.suggester.housebound_profile.frequency" => "123" },
324
        {
325
          "suggestions.suggester.library_id" => {"like" => "%CPL%"}
326
        }
327
      ]
328
    }
329
  })->status_is(200)
330
    ->json_is( '/-and' => [
331
        {
332
          "-not_bool" => "suggester.lost",
333
          "-or" => [
334
            {
335
              "datecreated" => {
336
                "!=" => [
337
                  "fff",
338
                  "zzz",
339
                  "xxx"
340
                ]
341
              }
342
            },
343
            {
344
              "housebound_profile.frequency" => 123
345
            },
346
            {
347
              "suggester.branchcode" => {
348
                "like" => "\%CPL\%"
349
              }
350
            }
351
          ]
352
        },
353
        {
354
          "biblio_id" => 1
355
        }
356
      ]);
357
};
358
300
subtest '_build_query_params_from_api' => sub {
359
subtest '_build_query_params_from_api' => sub {
301
360
302
    plan tests => 16;
361
    plan tests => 16;
(-)a/t/db_dependent/Koha/REST/Plugin/Objects.t (-2 / +117 lines)
Lines 20-25 use Modern::Perl; Link Here
20
use Koha::Acquisition::Orders;
20
use Koha::Acquisition::Orders;
21
use Koha::Cities;
21
use Koha::Cities;
22
use Koha::Holds;
22
use Koha::Holds;
23
use Koha::Biblios;
23
24
24
# Dummy app for testing the plugin
25
# Dummy app for testing the plugin
25
use Mojolicious::Lite;
26
use Mojolicious::Lite;
Lines 55-62 get '/patrons/:patron_id/holds' => sub { Link Here
55
    $c->render( status => 200, json => {count => scalar(@$holds)} );
56
    $c->render( status => 200, json => {count => scalar(@$holds)} );
56
};
57
};
57
58
59
get '/biblios' => sub {
60
    my $c = shift;
61
    my $output = $c->req->params->to_hash;
62
    $output->{query} = $c->req->json if defined $c->req->json;
63
    my $headers = $c->req->headers->to_hash;
64
    $output->{'x-koha-query'} = $headers->{'x-koha-query'} if defined $headers->{'x-koha-query'};
65
    $c->validation->output($output);
66
    my $biblios_set = Koha::Biblios->new;
67
    $c->stash("koha.embed", {
68
        "suggestions" => {
69
            children => {
70
                "suggester" => {}
71
            }
72
        }
73
    });
74
    my $biblios = $c->objects->search($biblios_set);
75
76
    $c->render( status => 200, json => {count => scalar(@$biblios)} );
77
};
78
58
# The tests
79
# The tests
59
use Test::More tests => 4;
80
use Test::More tests => 8;
60
use Test::Mojo;
81
use Test::Mojo;
61
82
62
use t::lib::TestBuilder;
83
use t::lib::TestBuilder;
Lines 220-222 subtest 'objects.search helper, with path parameters and _match' => sub { Link Here
220
241
221
    $schema->storage->txn_rollback;
242
    $schema->storage->txn_rollback;
222
};
243
};
223
- 
244
245
subtest 'object.search helper with query parameter' => sub {
246
    plan tests => 4;
247
248
    $schema->storage->txn_begin;
249
250
    my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
251
    my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
252
    my $biblio1 = $builder->build_sample_biblio;
253
    my $biblio2 = $builder->build_sample_biblio;
254
    my $biblio3 = $builder->build_sample_biblio;
255
    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
256
    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
257
    my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
258
259
    $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron1->borrowernumber })
260
      ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
261
262
    $t->get_ok('/biblios' => json => {"suggestions.suggester.patron_id" => $patron2->borrowernumber })
263
      ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
264
265
    $schema->storage->txn_rollback;
266
};
267
268
subtest 'object.search helper with q parameter' => sub {
269
    plan tests => 4;
270
271
    $schema->storage->txn_begin;
272
273
    my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
274
    my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
275
    my $biblio1 = $builder->build_sample_biblio;
276
    my $biblio2 = $builder->build_sample_biblio;
277
    my $biblio3 = $builder->build_sample_biblio;
278
    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
279
    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
280
    my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
281
282
    $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}')
283
      ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
284
285
    $t->get_ok('/biblios?q={"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}')
286
      ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
287
288
    $schema->storage->txn_rollback;
289
};
290
291
subtest 'object.search helper with x-koha-query header' => sub {
292
    plan tests => 4;
293
294
    $schema->storage->txn_begin;
295
296
    my $patron1 = $builder->build_object( { class => "Koha::Patrons" } );
297
    my $patron2 = $builder->build_object( { class => "Koha::Patrons" } );
298
    my $biblio1 = $builder->build_sample_biblio;
299
    my $biblio2 = $builder->build_sample_biblio;
300
    my $biblio3 = $builder->build_sample_biblio;
301
    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
302
    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
303
    my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
304
305
    $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}'})
306
      ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
307
308
    $t->get_ok('/biblios' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'})
309
      ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
310
311
    $schema->storage->txn_rollback;
312
};
313
314
subtest 'object.search helper with all query methods' => sub {
315
    plan tests => 6;
316
317
    $schema->storage->txn_begin;
318
319
    my $patron1 = $builder->build_object( { class => "Koha::Patrons" , value => {cardnumber => 'cardpatron1', firstname=>'patron1'} } );
320
    my $patron2 = $builder->build_object( { class => "Koha::Patrons" , value => {cardnumber => 'cardpatron2', firstname=>'patron2'} } );
321
    my $biblio1 = $builder->build_sample_biblio;
322
    my $biblio2 = $builder->build_sample_biblio;
323
    my $biblio3 = $builder->build_sample_biblio;
324
    my $suggestion1 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron1->borrowernumber, biblionumber => $biblio1->biblionumber} } );
325
    my $suggestion2 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio2->biblionumber} } );
326
    my $suggestion3 = $builder->build_object( { class => "Koha::Suggestions", value => { suggestedby => $patron2->borrowernumber, biblionumber => $biblio3->biblionumber} } );
327
328
    $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron1->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron1->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron1->cardnumber})
329
      ->json_is('/count' => 1, 'there should be 1 biblio with suggestions of patron 1');
330
331
    $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron2->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron2->cardnumber})
332
      ->json_is('/count' => 2, 'there should be 2 biblios with suggestions of patron 2');
333
334
    $t->get_ok('/biblios?q={"suggestions.suggester.firstname": "'.$patron1->firstname.'"}' => {'x-koha-query' => '{"suggestions.suggester.patron_id": "'.$patron2->borrowernumber.'"}'} => json => {"suggestions.suggester.cardnumber" => $patron2->cardnumber})
335
      ->json_is('/count' => 0, 'there shouldn\'t be biblios where suggester has patron1 fistname and patron2 id');
336
337
    $schema->storage->txn_rollback;
338
};

Return to bug 24502