Line # Revision Author
1 8 ahitrov@rambler.ru package SQL::CommonFilters;
2
3 use strict;
4 use SQL::Common;
5 use Contenido::Globals;
6
7 ########### FILTERS DESCRIPTION ####################################################################################
8 sub _status_filter {
9 my ($self, %opts)=@_;
10 return undef unless ( exists($opts{status}) );
11 return &SQL::Common::_generic_int_filter('d.status', $opts{status});
12 }
13
14 sub _class_filter {
15 my ($self, %opts)=@_;
16 return undef unless ( exists($opts{class}) );
17 return &SQL::Common::_generic_text_filter('d.class', $opts{class});
18 }
19
20 sub _in_id_filter {
21 my ($self, %opts)=@_;
22 return undef unless ( exists($opts{in_id}) );
23 return &SQL::Common::_generic_int_filter('d.id', $opts{in_id});
24 }
25
26 sub _id_filter {
27 my ($self, %opts)=@_;
28 return undef unless ( exists($opts{id}) );
29 return &SQL::Common::_generic_int_filter('d.id', $opts{id});
30 }
31
32 sub _sfilter_filter {
33 my ($self, %opts)=@_;
34 return undef unless ( exists($opts{sfilter}) );
35 return &SQL::Common::_generic_intarray_filter('d.sections', $opts{sfilter});
36 }
37
38 sub _name_filter {
39 my ($self, %opts)=@_;
40 return undef unless ( exists($opts{name}) );
41 return &SQL::Common::_generic_name_filter('d.name', $opts{name}, 0, \%opts);
42 }
43
44
45 sub _datetime_filter {
46 my ($self, %opts)=@_;
47 return undef unless ( exists($opts{datetime}) );
48 if ($opts{datetime} eq 'future') {
49 return " ($opts{usedtime} >= CURRENT_TIMESTAMP) ";
50 } elsif ($opts{datetime} eq 'past') {
51 return " ($opts{usedtime} <= CURRENT_TIMESTAMP) ";
52 } elsif ($opts{datetime} eq 'today') {
53 return " ($opts{usedtime}>=CURRENT_DATE AND $opts{usedtime}<CURRENT_DATE+'1 day'::INTERVAL) ";
54 } elsif (ref $opts{'datetime'} && ref $opts{'datetime'} eq 'HASH') {
55 my ($type, $time) = %{$opts{'datetime'}};
56 if ($time =~ /^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}(?:\.\d+)?$/ && $type eq 'after') {
57 return " ($opts{usedtime} >= '$time') ";
58 } elsif ($time =~ /^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}(?:\.\d+)?$/ && $type eq 'before') {
59 return " ($opts{usedtime} <= '$time') ";
60 } else {
61 warn "Contenido Warning: Неверно задан фильтр datetime='$opts{datetime}' допустимые значения: 'future','past','today', {['after' || 'before'] => [timestamp]}\n";
62 return ' FALSE ';
63 }
64 return " ($opts{usedtime}>=CURRENT_DATE AND $opts{usedtime}<CURRENT_DATE+'1 day'::INTERVAL) ";
65
66 } else {
67 warn "Contenido Warning: Неверно задан фильтр datetime='$opts{datetime}' допустимые значения: 'future','past','today', {['after' || 'before'] => [timestamp]}\n";
68 return ' FALSE ';
69 }
70 }
71
72 sub _date_equal_filter {
73 my ($self, %opts)=@_;
74 return undef unless ( exists($opts{date_equal}) );
75
76 # - запрос на совпадение даты / YYYY-MM-DD
77 if ( $opts{date_equal} =~ /^\d{4}-\d{2}-\d{2}$/) {
78 return " ($opts{usedtime}>=?::TIMESTAMP AND $opts{usedtime}<?::TIMESTAMP+'1 day'::INTERVAL) ", [$opts{date_equal},$opts{date_equal}];
79 } else {
80 warn "Contenido Warning: Неверно задан формат даты '$opts{date_equal}'. Правильный формат - YYYY-MM-DD.";
81 return ' FALSE ';
82 }
83 }
84
85 sub _date_filter {
86 my ($self, %opts)=@_;
87 return undef unless ( exists($opts{date}) );
88 # - запрос на извлечение документов из интервала дат...
89 if ( ref($opts{date}) eq 'ARRAY' and scalar(@{$opts{date}})==2 ) {
90 #pure dates
91 if ( ($opts{date}->[0] =~ /^\d{4}-\d{2}-\d{2}$/) and ($opts{date}->[1] =~ /^\d{4}-\d{2}-\d{2}$/) ) {
92 return " ($opts{usedtime}>=?::TIMESTAMP AND $opts{usedtime}<?::TIMESTAMP+'1 day'::INTERVAL) ", [$opts{date}->[0],$opts{date}->[1]];
93 #datetimes
94 } elsif ( ($opts{date}->[0] =~ /^\d{4}-\d{2}-\d{2}/) && ($opts{date}->[1] =~ /^\d{4}-\d{2}-\d{2}/) ) {
95 return " ($opts{usedtime}>=? AND $opts{usedtime}<=?) ", [$opts{date}->[0],$opts{date}->[1]];
96 } else {
97 warn "Contenido Warning: Неверно задан формат даты для параметра date. Это должен быть массив с двумя элементами типа YYYY-MM-DD или YYYY-MM-DD HH:MM:SS. ['$opts{date}->[0]', '$opts{date}->[1]']\n";
98 return ' FALSE ';
99 }
100 } else {
101 warn "Contenido Warning: Неверно задан формат даты для параметра date. Это должен быть массив с двумя элементами типа YYYY-MM-DD или YYYY-MM-DD HH:MM:SS.";
102 return ' FALSE ';
103 }
104 }
105
106 sub _class_excludes_filter {
107 my ($self, %opts)=@_;
108 return undef unless ( exists($opts{class_excludes}) );
109 return &SQL::Common::_generic_text_filter('d.class', $opts{class_excludes}, 'NEGATION');
110 }
111
112 sub _s_filter {
113 my ($self, %opts) = @_;
114 return undef unless ( exists($opts{s}) );
115
116 if ($opts{dive}) {
117 my @all_childs = @{$opts{all_childs}};
118 # - если задан параметр include_parent, то включим в выборку корень...
119 # По умолчанию выключен...
120 push (@all_childs, $opts{s}) if ($opts{include_parent});
121
122 return &SQL::Common::_generic_intarray_filter('d.sections', \@all_childs, \%opts);
123 } else {
124 return &SQL::Common::_generic_intarray_filter('d.sections', $opts{s}, \%opts);
125 }
126 }
127
128
129 sub _previous_days_filter {
130 my ($self, %opts)=@_;
131 return undef unless ( exists($opts{previous_days}) );
132 # - запрос на все данных из заданных последних дней...
133 if ($opts{previous_days} =~ /^\d+$/) {
134 return " $opts{usedtime} >= (CURRENT_DATE - ?::interval)::TIMESTAMP ", "$opts{previous_days} DAYS";
135 } else {
136 warn "Contenido Warning: Неверно задан формат дней для параметра previous_days. Это должно быть число дней\n";
137 return ' FALSE ';
138 }
139 }
140
141 sub _previous_time_filter {
142 my ($self, %opts)=@_;
143 return undef unless exists $opts{previous_time};
144 if ($opts{previous_time} =~ /\D/) {
145 warn "Contenido Warning: Неверно задано кол-во для параметра previous_time.\n";
146 return ' FALSE ';
147 } else {
148 return "$opts{usedtime} >= (NOW() - ?::interval)::TIMESTAMP ", ($opts{previous_time} . ' ' . ($opts{previous_time_units} || 'HOURS'));
149 }
150 }
151
152 sub _prev_to_filter {
153 my ($self, %opts)=@_;
154 670 ahitrov return undef unless ( exists $opts{prevto} || exists $opts{prev_to} );
155 my $prevto = exists $opts{prevto} ? 'prevto' : 'prev_to';
156 8 ahitrov@rambler.ru my ($wheres, $values) = ([],[]);
157 my $field = exists $opts{use_id} ? 'id' : exists $opts{use_ctime} ? 'ctime' : exists $opts{use_mtime} ? 'mtime' : 'dtime';
158 push @$wheres, " d.$field < ? ";
159 670 ahitrov if ( ref $opts{$prevto} ) {
160 my $ctime = $opts{$prevto};
161 8 ahitrov@rambler.ru push @$values, $ctime->ymd('-').' '.$ctime->hms.'.'.$ctime->nanosecond;
162 } else {
163 670 ahitrov push @$values, $opts{$prevto};
164 8 ahitrov@rambler.ru }
165 return ($wheres, $values);
166 }
167
168 sub _next_to_filter {
169 my ($self, %opts)=@_;
170 670 ahitrov return undef unless ( exists $opts{nextto} || exists $opts{next_to} );
171 my $nextto = exists $opts{nextto} ? 'nextto' : 'next_to';
172 8 ahitrov@rambler.ru my ($wheres, $values) = ([],[]);
173 my $field = exists $opts{use_id} ? 'id' : exists $opts{use_ctime} ? 'ctime' : exists $opts{use_mtime} ? 'mtime' : 'dtime';
174 push @$wheres, " d.$field > ? ";
175 670 ahitrov if ( ref $opts{$nextto} ) {
176 my $ctime = $opts{$nextto};
177 8 ahitrov@rambler.ru push @$values, $ctime->ymd('-').' '.$ctime->hms.'.'.$ctime->nanosecond;
178 } else {
179 670 ahitrov push @$values, $opts{$nextto};
180 8 ahitrov@rambler.ru }
181 return ($wheres, $values);
182 }
183
184 sub _excludes_filter {
185 my ($self,%opts)=@_;
186 return undef unless ( exists($opts{excludes}) );
187 return &SQL::Common::_generic_int_filter('d.id', $opts{excludes}, 'NEGATION');
188 }
189
190 sub _auto_filter {
191 my ( $self, %opts ) = @_;
192 my @exclude = qw( order_by limit offset no_limit count ids light hash_by return_mode );
193 my $sql;
194
195 my @wheres;
196 my @binds;
197 my @joins;
198
199 my ( $struct, $query_table );
200
201 if ( $opts{join} ) {
202
203 if ( !$opts{join}->can('class_table') || !$opts{join}->class_table()->can('db_table') ) {
204 warn "Contenido Warning (_auto_filter): Не могу получить имя таблицы для \"склейки\"!\n";
205 return (undef);
206 }
207 $query_table = $opts{join}->class_table()->db_table();
208 $struct = $opts{join}->class_table()->required_hash();
209
210 if ( $opts{field} && !$struct->{ $opts{field} } ) {
211 warn "Contenido Warning (_auto_filter): В таблице $query_table нет поля ".$opts{field}."!\n";
212 return (undef);
213 }
214
215 push @joins, ' join '.$query_table.' on '.$query_table.'.'.( $opts{field} ? $opts{field} : 'id' ).' = '.$opts{__join_table}.'.'.$opts{__join_field}.' ';
216
217 } else {
218 $struct = $self->required_hash();
219 $query_table = 'd';
220 }
221
222 foreach my $param ( keys %opts ) {
223 next if ( grep { $param eq $_ } @exclude ) || !$struct->{$param} || !exists($opts{$param}) || $struct->{$param}->{_no_autofilter};
224 next if $opts{$param} eq SQL::Common::NIL();
225 if ( uc($opts{$param}) eq 'NULL' || uc($opts{$param}) eq 'NOT NULL' || !defined($opts{$param}) ) {
226 $opts{$param} = 'NULL' unless defined $opts{$param};
227 my ($where, $values) = &SQL::Common::_generic_null_filter($query_table.'.'.$param, $opts{$param});
228 push (@wheres, $where);
229 push (@binds, $values) if (defined $values);
230 next;
231 } elsif ( $struct->{$param}->{db_type} eq 'integer' or $struct->{$param}->{type} eq 'checkbox') {
232 my ($where, $values);
233 if ( ref( $opts{$param} ) eq 'HASH') {
234 if ( $opts{$param}->{join} ) {
235 my ( $sub_wheres, $sub_binds, $joins ) = $self->_auto_filter( %{ $opts{$param} }, __join_table => $query_table, __join_field => $param );
236 push (@wheres, ref($sub_wheres) eq 'ARRAY' ? @{ $sub_wheres } : $sub_wheres ) if defined $sub_wheres;
237 push (@binds, ref($sub_binds) eq 'ARRAY' ? @{ $sub_binds } : $sub_binds ) if defined $sub_binds;
238 push (@joins, ref($joins) eq 'ARRAY' ? @{ $joins } : $joins ) if defined $joins;
239
240 } else {
241 foreach my $condition ( keys %{ $opts{$param} } ) {
242 ($where, $values) = &SQL::Common::_generic_composite_filter($query_table.'.'.$param, $condition, $opts{$param}->{$condition});
243 push (@wheres, $where);
244 push (@binds, ref($values) ? @$values : $values) if (defined $values);
245 }
246 }
247 } else {
248 if ( !ref($opts{$param}) && $opts{$param} !~ /^\-?\d+$/ ) {
249 warn "Contenido Warning (_auto_filter): Неверно задано значение для типа integer! ($opts{$param})\n";
250 next;
251 }
252 ($where, $values) = &SQL::Common::_generic_int_filter($query_table.'.'.$param, $opts{$param});
253 push (@wheres, $where);
254 push (@binds, ref($values) ? @$values : $values) if (defined $values);
255
256 }
257 } elsif ( $struct->{$param}->{db_type} eq 'integer[]' ) {
258 my ($where, $values) = &SQL::Common::_generic_intarray_filter($query_table.'.'.$param, $opts{$param});
259 push (@wheres, ref $where ? @$where : $where);
260 push (@binds, ref($values) ? @$values : $values) if (defined $values);
261 } elsif ( $struct->{$param}->{db_type} =~ /char/ || $struct->{$param}->{db_type} eq 'text' ) {
262 my ($where, $values);
263 if ( ref( $opts{$param} ) eq 'HASH') {
264 foreach my $condition ( keys %{ $opts{$param} } ) {
265 ($where, $values) = &SQL::Common::_generic_composite_filter($query_table.'.'.$param, $condition, $opts{$param}->{$condition}, 'text');
266 push (@wheres, $where);
267 push (@binds, ref($values) ? @$values : $values) if (defined $values);
268 }
269 } elsif ($param eq 'name') {
270 # Исключение: для параметра name используем специальный фильтр
271 ($where, $values) = &SQL::Common::_generic_name_filter($query_table.'.'.$param, $opts{$param}, undef, \%opts);
272 push (@wheres, $where);
273 push (@binds, ref($values) ? @$values : $values) if (defined $values);
274 } else {
275 ($where, $values) = &SQL::Common::_generic_text_filter($query_table.'.'.$param, $opts{$param}, $opts{"${param}_negative"});
276 push (@wheres, $where);
277 push (@binds, ref($values) ? @$values : $values) if (defined $values);
278 }
279 } elsif ( $struct->{$param}->{type} eq 'datetime' or $struct->{$param}->{type} eq 'date' ) {
280 my ($where, $values);
281 if ( ref( $opts{$param} ) eq 'HASH') {
282 foreach my $condition ( keys %{ $opts{$param} } ) {
283 ($where, $values) = &SQL::Common::_composite_date_filter($query_table.'.'.$param, $condition, $opts{$param}->{$condition});
284 push (@wheres, $where);
285 push (@binds, ref($values) ? @$values : $values) if (defined $values);
286 }
287 } elsif ( $opts{$param} && !ref( $opts{$param} ) ) {
288 ($where, $values) = &SQL::Common::_generic_date_filter($query_table.'.'.$param, $opts{$param});
289 push (@wheres, $where);
290 push (@binds, ref($values) ? @$values : $values) if (defined $values);
291 } else {
292 warn "Contenido Warning (_auto_filter): Неверно задано значение для поля datetime, ожидается строка в формате 'YYYY-MM-DD[ HH24:MI]', дата в формате unixtime или ссылка на хэш!\n";
293 next;
294 }
295 } elsif ( $struct->{$param}->{db_type} eq 'real' ) {
296 my ($where, $values);
297 if ( !ref($opts{$param}) && $opts{$param} !~ /^\-?[\d\.]+$/ ) {
298 warn "Contenido Warning (_auto_filter): Неверно задано значение для вещественного поля!\n";
299 next;
300 }
301 if ( ref( $opts{$param} ) eq 'HASH') {
302 foreach my $condition ( keys %{ $opts{$param} } ) {
303 ($where, $values) = &SQL::Common::_generic_composite_filter($query_table.'.'.$param, $condition, $opts{$param}->{$condition});
304 push (@wheres, $where);
305 push (@binds, ref($values) ? @$values : $values) if (defined $values);
306 }
307 } else {
308 ($where, $values) = &SQL::Common::_generic_text_filter($query_table.'.'.$param, $opts{$param});
309 push (@wheres, $where);
310 push (@binds, ref($values) ? @$values : $values) if (defined $values);
311 }
312
313 }
314 }
315 return (\@wheres, \@binds, \@joins);
316
317 }
318
319 sub _link_filter {
320 my ($self,%opts)=@_;
321
322 my @wheres=();
323 my @binds=();
324
325 # Связь определенного класса
326 if (exists($opts{lclass})) {
327 my ($where, $values) = SQL::Common::_generic_text_filter('l.class', $opts{lclass});
328 push (@wheres, $where);
329 push (@binds, ref($values) ? @$values:$values) if (defined $values);
330 }
331
332 my $lclass = $opts{lclass} || 'Contenido::Link';
333 # my $link_table = $lclass->class_table->db_table();
334 my $link_table = $lclass->_get_table->db_table();
335
336 # Ограничение по статусу связи
337 if ( exists $opts{lstatus} ) {
338 my ($where, $values) = SQL::Common::_generic_int_filter('l.status', $opts{lstatus});
339 push (@wheres, $where);
340 push (@binds, ref($values) ? @$values:$values) if (defined $values);
341 }
342
343 # Связь с определенным документ(ом/тами) по цели линка
344 if ( exists $opts{ldest} ) {
345 my ($where, $values) = SQL::Common::_generic_int_filter('l.dest_id', $opts{ldest});
346 push (@wheres, $where);
347 push (@binds, ref($values) ? @$values:$values) if (defined $values);
348 if ($self->_single_class) {
349 return (\@wheres, \@binds, " join $link_table as l on l.source_id=d.id");
350 } else {
351 return (\@wheres, \@binds, " join $link_table as l on l.source_id=d.id and l.source_class=d.class");
352 }
353 }
354
355 # Связь с определенным документ(ом/тами) по источнику линка
356 if ( exists $opts{lsource} ) {
357 my ($where, $values) = SQL::Common::_generic_int_filter('l.source_id', $opts{lsource});
358 push (@wheres, $where);
359 push (@binds, ref($values) ? @$values:$values) if (defined $values);
360 if ($self->_single_class) {
361 return (\@wheres, \@binds, " join $link_table as l on l.dest_id=d.id");
362 } else {
363 return (\@wheres, \@binds, " join $link_table as l on l.dest_id=d.id and l.dest_class=d.class");
364 }
365 }
366
367 return (undef);
368 }
369
370 1;
371