Filename | /usr/share/koha/lib/C4/Tags.pm |
Statements | Executed 1787 statements in 48.3ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
25 | 1 | 1 | 6.19ms | 74.6ms | get_tags | C4::Tags::
600 | 1 | 1 | 1.33ms | 1.33ms | CORE:regcomp (opcode) | C4::Tags::
700 | 3 | 1 | 1.05ms | 1.05ms | CORE:match (opcode) | C4::Tags::
100 | 1 | 1 | 142µs | 142µs | CORE:subst (opcode) | C4::Tags::
1 | 1 | 1 | 50µs | 55µs | BEGIN@26 | C4::Tags::
1 | 1 | 1 | 31µs | 41µs | BEGIN@21 | C4::Tags::
1 | 1 | 1 | 27µs | 1.77ms | BEGIN@35 | C4::Tags::
1 | 1 | 1 | 18µs | 45µs | BEGIN@22 | C4::Tags::
1 | 1 | 1 | 18µs | 104µs | BEGIN@30 | C4::Tags::
1 | 1 | 1 | 14µs | 70µs | BEGIN@23 | C4::Tags::
1 | 1 | 1 | 14µs | 104µs | BEGIN@29 | C4::Tags::
1 | 1 | 1 | 14µs | 101µs | BEGIN@32 | C4::Tags::
1 | 1 | 1 | 12µs | 110µs | BEGIN@27 | C4::Tags::
1 | 1 | 1 | 12µs | 32µs | BEGIN@24 | C4::Tags::
1 | 1 | 1 | 11µs | 11µs | INIT | C4::Tags::
0 | 0 | 0 | 0s | 0s | _set_weight | C4::Tags::
0 | 0 | 0 | 0s | 0s | _set_weight_total | C4::Tags::
0 | 0 | 0 | 0s | 0s | add_filter | C4::Tags::
0 | 0 | 0 | 0s | 0s | add_tag | C4::Tags::
0 | 0 | 0 | 0s | 0s | add_tag_approval | C4::Tags::
0 | 0 | 0 | 0s | 0s | add_tag_index | C4::Tags::
0 | 0 | 0 | 0s | 0s | approval_counts | C4::Tags::
0 | 0 | 0 | 0s | 0s | blacklist | C4::Tags::
0 | 0 | 0 | 0s | 0s | decrement_weight | C4::Tags::
0 | 0 | 0 | 0s | 0s | decrement_weight_total | C4::Tags::
0 | 0 | 0 | 0s | 0s | decrement_weights | C4::Tags::
0 | 0 | 0 | 0s | 0s | delete_tag_approval | C4::Tags::
0 | 0 | 0 | 0s | 0s | delete_tag_index | C4::Tags::
0 | 0 | 0 | 0s | 0s | delete_tag_row_by_id | C4::Tags::
0 | 0 | 0 | 0s | 0s | delete_tag_rows_by_ids | C4::Tags::
0 | 0 | 0 | 0s | 0s | get_approval_rows | C4::Tags::
0 | 0 | 0 | 0s | 0s | get_count_by_tag_status | C4::Tags::
0 | 0 | 0 | 0s | 0s | get_filters | C4::Tags::
0 | 0 | 0 | 0s | 0s | get_tag | C4::Tags::
0 | 0 | 0 | 0s | 0s | get_tag_index | C4::Tags::
0 | 0 | 0 | 0s | 0s | get_tag_rows | C4::Tags::
0 | 0 | 0 | 0s | 0s | increment_weight | C4::Tags::
0 | 0 | 0 | 0s | 0s | increment_weight_total | C4::Tags::
0 | 0 | 0 | 0s | 0s | increment_weights | C4::Tags::
0 | 0 | 0 | 0s | 0s | is_approved | C4::Tags::
0 | 0 | 0 | 0s | 0s | mod_tag_approval | C4::Tags::
0 | 0 | 0 | 0s | 0s | remove_filter | C4::Tags::
0 | 0 | 0 | 0s | 0s | remove_tag | C4::Tags::
0 | 0 | 0 | 0s | 0s | stratify_tags | C4::Tags::
0 | 0 | 0 | 0s | 0s | whitelist | C4::Tags::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package C4::Tags; | ||||
2 | |||||
3 | # Copyright Liblime 2008 | ||||
4 | # Parts Copyright ACPL 2011 | ||||
5 | # | ||||
6 | # This file is part of Koha. | ||||
7 | # | ||||
8 | # Koha is free software; you can redistribute it and/or modify it under the | ||||
9 | # terms of the GNU General Public License as published by the Free Software | ||||
10 | # Foundation; either version 2 of the License, or (at your option) any later | ||||
11 | # version. | ||||
12 | # | ||||
13 | # Koha is distributed in the hope that it will be useful, but WITHOUT ANY | ||||
14 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | ||||
15 | # A PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||||
16 | # | ||||
17 | # You should have received a copy of the GNU General Public License along | ||||
18 | # with Koha; if not, write to the Free Software Foundation, Inc., | ||||
19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
20 | |||||
21 | 3 | 59µs | 2 | 51µs | # spent 41µs (31+10) within C4::Tags::BEGIN@21 which was called:
# once (31µs+10µs) by main::BEGIN@51 at line 21 # spent 41µs making 1 call to C4::Tags::BEGIN@21
# spent 10µs making 1 call to strict::import |
22 | 3 | 36µs | 2 | 72µs | # spent 45µs (18+27) within C4::Tags::BEGIN@22 which was called:
# once (18µs+27µs) by main::BEGIN@51 at line 22 # spent 45µs making 1 call to C4::Tags::BEGIN@22
# spent 27µs making 1 call to warnings::import |
23 | 3 | 32µs | 2 | 126µs | # spent 70µs (14+56) within C4::Tags::BEGIN@23 which was called:
# once (14µs+56µs) by main::BEGIN@51 at line 23 # spent 70µs making 1 call to C4::Tags::BEGIN@23
# spent 56µs making 1 call to Exporter::import |
24 | 3 | 30µs | 2 | 52µs | # spent 32µs (12+20) within C4::Tags::BEGIN@24 which was called:
# once (12µs+20µs) by main::BEGIN@51 at line 24 # spent 32µs making 1 call to C4::Tags::BEGIN@24
# spent 20µs making 1 call to Exporter::import |
25 | |||||
26 | 3 | 70µs | 2 | 61µs | # spent 55µs (50+6) within C4::Tags::BEGIN@26 which was called:
# once (50µs+6µs) by main::BEGIN@51 at line 26 # spent 55µs making 1 call to C4::Tags::BEGIN@26
# spent 6µs making 1 call to C4::Context::import |
27 | 3 | 50µs | 2 | 207µs | # spent 110µs (12+97) within C4::Tags::BEGIN@27 which was called:
# once (12µs+97µs) by main::BEGIN@51 at line 27 # spent 110µs making 1 call to C4::Tags::BEGIN@27
# spent 97µs making 1 call to Exporter::import |
28 | #use Data::Dumper; | ||||
29 | 3 | 65µs | 2 | 195µs | # spent 104µs (14+90) within C4::Tags::BEGIN@29 which was called:
# once (14µs+90µs) by main::BEGIN@51 at line 29 # spent 104µs making 1 call to C4::Tags::BEGIN@29
# spent 90µs making 1 call to constant::import |
30 | 3 | 45µs | 3 | 191µs | # spent 104µs (18+86) within C4::Tags::BEGIN@30 which was called:
# once (18µs+86µs) by main::BEGIN@51 at line 30 # spent 104µs making 1 call to C4::Tags::BEGIN@30
# spent 83µs making 1 call to constant::import
# spent 3µs making 1 call to constant::__ANON__[constant.pm:119] |
31 | |||||
32 | 3 | 183µs | 2 | 188µs | # spent 101µs (14+87) within C4::Tags::BEGIN@32 which was called:
# once (14µs+87µs) by main::BEGIN@51 at line 32 # spent 101µs making 1 call to C4::Tags::BEGIN@32
# spent 87µs making 1 call to vars::import |
33 | 1 | 600ns | our $ext_dict; | ||
34 | |||||
35 | # spent 1.77ms (27µs+1.74) within C4::Tags::BEGIN@35 which was called:
# once (27µs+1.74ms) by main::BEGIN@51 at line 64 | ||||
36 | 1 | 2µs | $VERSION = 3.07.00.049; | ||
37 | 1 | 10µs | @ISA = qw(Exporter); | ||
38 | 1 | 3µs | @EXPORT_OK = qw( | ||
39 | &get_tag &get_tags &get_tag_rows | ||||
40 | &add_tags &add_tag | ||||
41 | &delete_tag_row_by_id | ||||
42 | &remove_tag | ||||
43 | &delete_tag_rows_by_ids | ||||
44 | &get_approval_rows | ||||
45 | &blacklist | ||||
46 | &whitelist | ||||
47 | &is_approved | ||||
48 | &approval_counts | ||||
49 | &get_count_by_tag_status | ||||
50 | &get_filters | ||||
51 | stratify_tags | ||||
52 | ); | ||||
53 | # %EXPORT_TAGS = (); | ||||
54 | 1 | 5µs | 1 | 1.74ms | $ext_dict = C4::Context->preference('TagsExternalDictionary'); # spent 1.74ms making 1 call to C4::Context::preference |
55 | 1 | 700ns | if ($debug) { | ||
56 | require Data::Dumper; | ||||
57 | import Data::Dumper qw(:DEFAULT); | ||||
58 | print STDERR __PACKAGE__ . " external dictionary = " . ($ext_dict||'none') . "\n"; | ||||
59 | } | ||||
60 | 1 | 5µs | if ($ext_dict) { | ||
61 | require Lingua::Ispell; | ||||
62 | import Lingua::Ispell qw(spellcheck add_word_lc save_dictionary); | ||||
63 | } | ||||
64 | 1 | 3.38ms | 1 | 1.77ms | } # spent 1.77ms making 1 call to C4::Tags::BEGIN@35 |
65 | |||||
66 | =head1 C4::Tags.pm - Support for user tagging of biblios. | ||||
67 | |||||
- - | |||||
72 | # spent 11µs within C4::Tags::INIT which was called:
# once (11µs+0s) by main::RUNTIME at line 0 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl | ||||
73 | 1 | 900ns | $ext_dict and $Lingua::Ispell::path = $ext_dict; | ||
74 | 1 | 19µs | $debug and print STDERR "\$Lingua::Ispell::path = $Lingua::Ispell::path\n"; | ||
75 | } | ||||
76 | |||||
77 | sub get_filters { | ||||
78 | my $query = "SELECT * FROM tags_filters "; | ||||
79 | my ($sth); | ||||
80 | if (@_) { | ||||
81 | $sth = C4::Context->dbh->prepare($query . " WHERE filter_id = ? "); | ||||
82 | $sth->execute(shift); | ||||
83 | } else { | ||||
84 | $sth = C4::Context->dbh->prepare($query); | ||||
85 | $sth->execute; | ||||
86 | } | ||||
87 | return $sth->fetchall_arrayref({}); | ||||
88 | } | ||||
89 | |||||
90 | # (SELECT count(*) FROM tags_all ) as tags_all, | ||||
91 | # (SELECT count(*) FROM tags_index ) as tags_index, | ||||
92 | |||||
93 | sub approval_counts { | ||||
94 | my $query = "SELECT | ||||
95 | (SELECT count(*) FROM tags_approval WHERE approved= 1) as approved_count, | ||||
96 | (SELECT count(*) FROM tags_approval WHERE approved=-1) as rejected_count, | ||||
97 | (SELECT count(*) FROM tags_approval WHERE approved= 0) as unapproved_count | ||||
98 | "; | ||||
99 | my $sth = C4::Context->dbh->prepare($query); | ||||
100 | $sth->execute; | ||||
101 | my $result = $sth->fetchrow_hashref(); | ||||
102 | $result->{approved_total} = $result->{approved_count} + $result->{rejected_count} + $result->{unapproved_count}; | ||||
103 | $debug and warn "counts returned: " . Dumper $result; | ||||
104 | return $result; | ||||
105 | } | ||||
106 | |||||
107 | =head2 get_count_by_tag_status | ||||
108 | |||||
- - | |||||
115 | sub get_count_by_tag_status { | ||||
116 | my ($status) = @_; | ||||
117 | my $dbh = C4::Context->dbh; | ||||
118 | my $query = | ||||
119 | "SELECT count(*) FROM tags_approval WHERE approved=?"; | ||||
120 | my $sth = $dbh->prepare($query); | ||||
121 | $sth->execute( $status ); | ||||
122 | return $sth->fetchrow; | ||||
123 | } | ||||
124 | |||||
125 | sub remove_tag { | ||||
126 | my $tag_id = shift or return; | ||||
127 | my $user_id = (@_) ? shift : undef; | ||||
128 | my $rows = (defined $user_id) ? | ||||
129 | get_tag_rows({tag_id=>$tag_id, borrowernumber=>$user_id}) : | ||||
130 | get_tag_rows({tag_id=>$tag_id}) ; | ||||
131 | $rows or return 0; | ||||
132 | (scalar(@$rows) == 1) or return; # should never happen (duplicate ids) | ||||
133 | my $row = shift(@$rows); | ||||
134 | ($tag_id == $row->{tag_id}) or return 0; | ||||
135 | my $tags = get_tags({term=>$row->{term}, biblionumber=>$row->{biblionumber}}); | ||||
136 | my $index = shift(@$tags); | ||||
137 | $debug and print STDERR | ||||
138 | sprintf "remove_tag: tag_id=>%s, biblionumber=>%s, weight=>%s, weight_total=>%s\n", | ||||
139 | $row->{tag_id}, $row->{biblionumber}, $index->{weight}, $index->{weight_total}; | ||||
140 | if ($index->{weight} <= 1) { | ||||
141 | delete_tag_index($row->{term},$row->{biblionumber}); | ||||
142 | } else { | ||||
143 | decrement_weight($row->{term},$row->{biblionumber}); | ||||
144 | } | ||||
145 | if ($index->{weight_total} <= 1) { | ||||
146 | delete_tag_approval($row->{term}); | ||||
147 | } else { | ||||
148 | decrement_weight_total($row->{term}); | ||||
149 | } | ||||
150 | delete_tag_row_by_id($tag_id); | ||||
151 | } | ||||
152 | |||||
153 | sub delete_tag_index { | ||||
154 | (@_) or return; | ||||
155 | my $sth = C4::Context->dbh->prepare("DELETE FROM tags_index WHERE term = ? AND biblionumber = ? LIMIT 1"); | ||||
156 | $sth->execute(@_); | ||||
157 | return $sth->rows || 0; | ||||
158 | } | ||||
159 | sub delete_tag_approval { | ||||
160 | (@_) or return; | ||||
161 | my $sth = C4::Context->dbh->prepare("DELETE FROM tags_approval WHERE term = ? LIMIT 1"); | ||||
162 | $sth->execute(shift); | ||||
163 | return $sth->rows || 0; | ||||
164 | } | ||||
165 | sub delete_tag_row_by_id { | ||||
166 | (@_) or return; | ||||
167 | my $sth = C4::Context->dbh->prepare("DELETE FROM tags_all WHERE tag_id = ? LIMIT 1"); | ||||
168 | $sth->execute(shift); | ||||
169 | return $sth->rows || 0; | ||||
170 | } | ||||
171 | sub delete_tag_rows_by_ids { | ||||
172 | (@_) or return; | ||||
173 | my $i=0; | ||||
174 | foreach(@_) { | ||||
175 | $i += delete_tag_row_by_id($_); | ||||
176 | } | ||||
177 | ($i == scalar(@_)) or | ||||
178 | warn sprintf "delete_tag_rows_by_ids tried %s tag_ids, only succeeded on $i", scalar(@_); | ||||
179 | return $i; | ||||
180 | } | ||||
181 | |||||
182 | sub get_tag_rows { | ||||
183 | my $hash = shift || {}; | ||||
184 | my @ok_fields = TAG_FIELDS; | ||||
185 | push @ok_fields, 'limit'; # push the limit! :) | ||||
186 | my $wheres; | ||||
187 | my $limit = ""; | ||||
188 | my @exe_args = (); | ||||
189 | foreach my $key (keys %$hash) { | ||||
190 | $debug and print STDERR "get_tag_rows arg. '$key' = ", $hash->{$key}, "\n"; | ||||
191 | unless (length $key) { | ||||
192 | carp "Empty argument key to get_tag_rows: ignoring!"; | ||||
193 | next; | ||||
194 | } | ||||
195 | unless (1 == scalar grep {/^ $key $/x} @ok_fields) { | ||||
196 | carp "get_tag_rows received unreconized argument key '$key'."; | ||||
197 | next; | ||||
198 | } | ||||
199 | if ($key eq 'limit') { | ||||
200 | my $val = $hash->{$key}; | ||||
201 | unless ($val =~ /^(\d+,)?\d+$/) { | ||||
202 | carp "Non-nuerical limit value '$val' ignored!"; | ||||
203 | next; | ||||
204 | } | ||||
205 | $limit = " LIMIT $val\n"; | ||||
206 | } else { | ||||
207 | $wheres .= ($wheres) ? " AND $key = ?\n" : " WHERE $key = ?\n"; | ||||
208 | push @exe_args, $hash->{$key}; | ||||
209 | } | ||||
210 | } | ||||
211 | my $query = TAG_SELECT . ($wheres||'') . $limit; | ||||
212 | $debug and print STDERR "get_tag_rows query:\n $query\n", | ||||
213 | "get_tag_rows query args: ", join(',', @exe_args), "\n"; | ||||
214 | my $sth = C4::Context->dbh->prepare($query); | ||||
215 | if (@exe_args) { | ||||
216 | $sth->execute(@exe_args); | ||||
217 | } else { | ||||
218 | $sth->execute; | ||||
219 | } | ||||
220 | return $sth->fetchall_arrayref({}); | ||||
221 | } | ||||
222 | |||||
223 | # spent 74.6ms (6.19+68.4) within C4::Tags::get_tags which was called 25 times, avg 2.98ms/call:
# 25 times (6.19ms+68.4ms) by main::RUNTIME at line 581 of /usr/share/koha/opac/cgi-bin/opac/opac-search.pl, avg 2.98ms/call | ||||
224 | 25 | 113µs | my $hash = shift || {}; | ||
225 | 25 | 191µs | my @ok_fields = qw(term biblionumber weight limit sort approved); | ||
226 | 25 | 8µs | my $wheres; | ||
227 | 25 | 22µs | my $limit = ""; | ||
228 | 25 | 12µs | my $order = ""; | ||
229 | 25 | 16µs | my @exe_args = (); | ||
230 | 25 | 248µs | foreach my $key (keys %$hash) { | ||
231 | 100 | 55µs | $debug and print STDERR "get_tags arg. '$key' = ", $hash->{$key}, "\n"; | ||
232 | 100 | 58µs | unless (length $key) { | ||
233 | carp "Empty argument key to get_tags: ignoring!"; | ||||
234 | next; | ||||
235 | } | ||||
236 | 700 | 4.33ms | 1200 | 2.05ms | unless (1 == scalar grep {/^ $key $/x} @ok_fields) { # spent 1.33ms making 600 calls to C4::Tags::CORE:regcomp, avg 2µs/call
# spent 716µs making 600 calls to C4::Tags::CORE:match, avg 1µs/call |
237 | carp "get_tags received unreconized argument key '$key'."; | ||||
238 | next; | ||||
239 | } | ||||
240 | 100 | 189µs | if ($key eq 'limit') { | ||
241 | 25 | 24µs | my $val = $hash->{$key}; | ||
242 | 25 | 163µs | 25 | 101µs | unless ($val =~ /^(\d+,)?\d+$/) { # spent 101µs making 25 calls to C4::Tags::CORE:match, avg 4µs/call |
243 | carp "Non-nuerical limit value '$val' ignored!"; | ||||
244 | next; | ||||
245 | } | ||||
246 | 25 | 36µs | $limit = " LIMIT $val\n"; | ||
247 | } elsif ($key eq 'sort') { | ||||
248 | 25 | 123µs | foreach my $by (split /\,/, $hash->{$key}) { | ||
249 | 25 | 377µs | 75 | 236µs | unless ( # spent 236µs making 75 calls to C4::Tags::CORE:match, avg 3µs/call |
250 | $by =~ /^([-+])?(term)/ or | ||||
251 | $by =~ /^([-+])?(biblionumber)/ or | ||||
252 | $by =~ /^([-+])?(weight)/ | ||||
253 | ) { | ||||
254 | carp "get_tags received illegal sort order '$by'"; | ||||
255 | next; | ||||
256 | } | ||||
257 | 25 | 21µs | if ($order) { | ||
258 | $order .= ", "; | ||||
259 | } else { | ||||
260 | 25 | 25µs | $order = " ORDER BY "; | ||
261 | } | ||||
262 | 25 | 340µs | $order .= $2 . " " . ((!$1) ? '' : $1 eq '-' ? 'DESC' : $1 eq '+' ? 'ASC' : '') . "\n"; | ||
263 | } | ||||
264 | |||||
265 | } else { | ||||
266 | 50 | 54µs | my $whereval = $hash->{$key}; | ||
267 | 50 | 91µs | my $longkey = ($key eq 'term' ) ? 'tags_index.term' : | ||
268 | ($key eq 'approved') ? 'tags_approval.approved' : $key; | ||||
269 | 50 | 413µs | 100 | 142µs | my $op = ($whereval =~ s/^(>=|<=)// or # spent 142µs making 100 calls to C4::Tags::CORE:subst, avg 1µs/call |
270 | $whereval =~ s/^(>|=|<)// ) ? $1 : '='; | ||||
271 | 50 | 83µs | $wheres .= ($wheres) ? " AND $longkey $op ?\n" : " WHERE $longkey $op ?\n"; | ||
272 | 50 | 82µs | push @exe_args, $whereval; | ||
273 | } | ||||
274 | } | ||||
275 | 25 | 53µs | my $query = " | ||
276 | SELECT tags_index.term as term,biblionumber,weight,weight_total | ||||
277 | FROM tags_index | ||||
278 | LEFT JOIN tags_approval | ||||
279 | ON tags_index.term = tags_approval.term | ||||
280 | " . ($wheres||'') . $order . $limit; | ||||
281 | 25 | 11µs | $debug and print STDERR "get_tags query:\n $query\n", | ||
282 | "get_tags query args: ", join(',', @exe_args), "\n"; | ||||
283 | 25 | 690µs | 75 | 30.8ms | my $sth = C4::Context->dbh->prepare($query); # spent 26.3ms making 25 calls to C4::Context::dbh, avg 1.05ms/call
# spent 2.43ms making 25 calls to DBI::db::prepare, avg 97µs/call
# spent 2.05ms making 25 calls to DBD::mysql::db::prepare, avg 82µs/call |
284 | 25 | 34.9ms | 25 | 34.6ms | if (@exe_args) { # spent 34.6ms making 25 calls to DBI::st::execute, avg 1.38ms/call |
285 | $sth->execute(@exe_args); | ||||
286 | } else { | ||||
287 | $sth->execute; | ||||
288 | } | ||||
289 | 25 | 1.65ms | 50 | 3.84ms | return $sth->fetchall_arrayref({}); # spent 2.17ms making 25 calls to DBI::st::fetchall_arrayref, avg 87µs/call
# spent 1.67ms making 25 calls to DBD::_::st::fetchall_arrayref, avg 67µs/call |
290 | } | ||||
291 | |||||
292 | sub get_approval_rows { # i.e., from tags_approval | ||||
293 | my $hash = shift || {}; | ||||
294 | my @ok_fields = qw(term approved date_approved approved_by weight_total limit sort borrowernumber); | ||||
295 | my $wheres; | ||||
296 | my $limit = ""; | ||||
297 | my $order = ""; | ||||
298 | my @exe_args = (); | ||||
299 | foreach my $key (keys %$hash) { | ||||
300 | $debug and print STDERR "get_approval_rows arg. '$key' = ", $hash->{$key}, "\n"; | ||||
301 | unless (length $key) { | ||||
302 | carp "Empty argument key to get_approval_rows: ignoring!"; | ||||
303 | next; | ||||
304 | } | ||||
305 | unless (1 == scalar grep {/^ $key $/x} @ok_fields) { | ||||
306 | carp "get_approval_rows received unreconized argument key '$key'."; | ||||
307 | next; | ||||
308 | } | ||||
309 | if ($key eq 'limit') { | ||||
310 | my $val = $hash->{$key}; | ||||
311 | unless ($val =~ /^(\d+,)?\d+$/) { | ||||
312 | carp "Non-numerical limit value '$val' ignored!"; | ||||
313 | next; | ||||
314 | } | ||||
315 | $limit = " LIMIT $val\n"; | ||||
316 | } elsif ($key eq 'sort') { | ||||
317 | foreach my $by (split /\,/, $hash->{$key}) { | ||||
318 | unless ( | ||||
319 | $by =~ /^([-+])?(term)/ or | ||||
320 | $by =~ /^([-+])?(biblionumber)/ or | ||||
321 | $by =~ /^([-+])?(borrowernumber)/ or | ||||
322 | $by =~ /^([-+])?(weight_total)/ or | ||||
323 | $by =~ /^([-+])?(approved(_by)?)/ or | ||||
324 | $by =~ /^([-+])?(date_approved)/ | ||||
325 | ) { | ||||
326 | carp "get_approval_rows received illegal sort order '$by'"; | ||||
327 | next; | ||||
328 | } | ||||
329 | if ($order) { | ||||
330 | $order .= ", "; | ||||
331 | } else { | ||||
332 | $order = " ORDER BY " unless $order; | ||||
333 | } | ||||
334 | $order .= $2 . " " . ((!$1) ? '' : $1 eq '-' ? 'DESC' : $1 eq '+' ? 'ASC' : '') . "\n"; | ||||
335 | } | ||||
336 | |||||
337 | } else { | ||||
338 | my $whereval = $hash->{$key}; | ||||
339 | my $op = ($whereval =~ s/^(>=|<=)// or | ||||
340 | $whereval =~ s/^(>|=|<)// ) ? $1 : '='; | ||||
341 | $wheres .= ($wheres) ? " AND $key $op ?\n" : " WHERE $key $op ?\n"; | ||||
342 | push @exe_args, $whereval; | ||||
343 | } | ||||
344 | } | ||||
345 | my $query = " | ||||
346 | SELECT tags_approval.term AS term, | ||||
347 | tags_approval.approved AS approved, | ||||
348 | tags_approval.date_approved AS date_approved, | ||||
349 | tags_approval.approved_by AS approved_by, | ||||
350 | tags_approval.weight_total AS weight_total, | ||||
351 | CONCAT(borrowers.surname, ', ', borrowers.firstname) AS approved_by_name | ||||
352 | FROM tags_approval | ||||
353 | LEFT JOIN borrowers | ||||
354 | ON tags_approval.approved_by = borrowers.borrowernumber "; | ||||
355 | $query .= ($wheres||'') . $order . $limit; | ||||
356 | $debug and print STDERR "get_approval_rows query:\n $query\n", | ||||
357 | "get_approval_rows query args: ", join(',', @exe_args), "\n"; | ||||
358 | my $sth = C4::Context->dbh->prepare($query); | ||||
359 | if (@exe_args) { | ||||
360 | $sth->execute(@exe_args); | ||||
361 | } else { | ||||
362 | $sth->execute; | ||||
363 | } | ||||
364 | return $sth->fetchall_arrayref({}); | ||||
365 | } | ||||
366 | |||||
367 | sub is_approved { | ||||
368 | my $term = shift or return; | ||||
369 | my $sth = C4::Context->dbh->prepare("SELECT approved FROM tags_approval WHERE term = ?"); | ||||
370 | $sth->execute($term); | ||||
371 | unless ($sth->rows) { | ||||
372 | $ext_dict and return (spellcheck($term) ? 0 : 1); # spellcheck returns empty on OK word | ||||
373 | return 0; | ||||
374 | } | ||||
375 | return $sth->fetchrow; | ||||
376 | } | ||||
377 | |||||
378 | sub get_tag_index { | ||||
379 | my $term = shift or return; | ||||
380 | my $sth; | ||||
381 | if (@_) { | ||||
382 | $sth = C4::Context->dbh->prepare("SELECT * FROM tags_index WHERE term = ? AND biblionumber = ?"); | ||||
383 | $sth->execute($term,shift); | ||||
384 | } else { | ||||
385 | $sth = C4::Context->dbh->prepare("SELECT * FROM tags_index WHERE term = ?"); | ||||
386 | $sth->execute($term); | ||||
387 | } | ||||
388 | return $sth->fetchrow_hashref; | ||||
389 | } | ||||
390 | |||||
391 | sub whitelist { | ||||
392 | my $operator = shift; | ||||
393 | defined $operator or return; # have to test defined to allow =0 (kohaadmin) | ||||
394 | if ($ext_dict) { | ||||
395 | foreach (@_) { | ||||
396 | spellcheck($_) or next; | ||||
397 | add_word_lc($_); | ||||
398 | } | ||||
399 | } | ||||
400 | foreach (@_) { | ||||
401 | my $aref = get_approval_rows({term=>$_}); | ||||
402 | if ($aref and scalar @$aref) { | ||||
403 | mod_tag_approval($operator,$_,1); | ||||
404 | } else { | ||||
405 | add_tag_approval($_,$operator); | ||||
406 | } | ||||
407 | } | ||||
408 | return scalar @_; | ||||
409 | } | ||||
410 | # note: there is no "unwhitelist" operation because there is no remove for Ispell. | ||||
411 | # The blacklist regexps should operate "in front of" the whitelist, so if you approve | ||||
412 | # a term mistakenly, you can still reverse it. But there is no going back to "neutral". | ||||
413 | sub blacklist { | ||||
414 | my $operator = shift; | ||||
415 | defined $operator or return; # have to test defined to allow =0 (kohaadmin) | ||||
416 | foreach (@_) { | ||||
417 | my $aref = get_approval_rows({term=>$_}); | ||||
418 | if ($aref and scalar @$aref) { | ||||
419 | mod_tag_approval($operator,$_,-1); | ||||
420 | } else { | ||||
421 | add_tag_approval($_,$operator,-1); | ||||
422 | } | ||||
423 | } | ||||
424 | return scalar @_; | ||||
425 | } | ||||
426 | sub add_filter { | ||||
427 | my $operator = shift; | ||||
428 | defined $operator or return; # have to test defined to allow =0 (kohaadmin) | ||||
429 | my $query = "INSERT INTO tags_blacklist (regexp,y,z) VALUES (?,?,?)"; | ||||
430 | # my $sth = C4::Context->dbh->prepare($query); | ||||
431 | return scalar @_; | ||||
432 | } | ||||
433 | sub remove_filter { | ||||
434 | my $operator = shift; | ||||
435 | defined $operator or return; # have to test defined to allow =0 (kohaadmin) | ||||
436 | my $query = "REMOVE FROM tags_blacklist WHERE blacklist_id = ?"; | ||||
437 | # my $sth = C4::Context->dbh->prepare($query); | ||||
438 | # $sth->execute($term); | ||||
439 | return scalar @_; | ||||
440 | } | ||||
441 | |||||
442 | sub add_tag_approval { # or disapproval | ||||
443 | $debug and warn "add_tag_approval(" . join(", ",map {defined($_) ? $_ : 'UNDEF'} @_) . ")"; | ||||
444 | my $term = shift or return; | ||||
445 | my $query = "SELECT * FROM tags_approval WHERE term = ?"; | ||||
446 | my $sth = C4::Context->dbh->prepare($query); | ||||
447 | $sth->execute($term); | ||||
448 | ($sth->rows) and return increment_weight_total($term); | ||||
449 | my $operator = shift || 0; | ||||
450 | my $approval = (@_ ? shift : 0); # default is unapproved | ||||
451 | my @exe_args = ($term); # all 3 queries will use this argument | ||||
452 | if ($operator) { | ||||
453 | $query = "INSERT INTO tags_approval (term,approved_by,approved,date_approved) VALUES (?,?,?,NOW())"; | ||||
454 | push @exe_args, $operator, $approval; | ||||
455 | } elsif ($approval) { | ||||
456 | $query = "INSERT INTO tags_approval (term,approved,date_approved) VALUES (?,?,NOW())"; | ||||
457 | push @exe_args, $approval; | ||||
458 | } else { | ||||
459 | $query = "INSERT INTO tags_approval (term,date_approved) VALUES (?,NOW())"; | ||||
460 | } | ||||
461 | $debug and print STDERR "add_tag_approval query: $query\nadd_tag_approval args: (" . join(", ", @exe_args) . ")\n"; | ||||
462 | $sth = C4::Context->dbh->prepare($query); | ||||
463 | $sth->execute(@exe_args); | ||||
464 | return $sth->rows; | ||||
465 | } | ||||
466 | |||||
467 | sub mod_tag_approval { | ||||
468 | my $operator = shift; | ||||
469 | defined $operator or return; # have to test defined to allow =0 (kohaadmin) | ||||
470 | my $term = shift or return; | ||||
471 | my $approval = (scalar @_ ? shift : 1); # default is to approve | ||||
472 | my $query = "UPDATE tags_approval SET approved_by=?, approved=?, date_approved=NOW() WHERE term = ?"; | ||||
473 | $debug and print STDERR "mod_tag_approval query: $query\nmod_tag_approval args: ($operator,$approval,$term)\n"; | ||||
474 | my $sth = C4::Context->dbh->prepare($query); | ||||
475 | $sth->execute($operator,$approval,$term); | ||||
476 | } | ||||
477 | |||||
478 | sub add_tag_index { | ||||
479 | my $term = shift or return; | ||||
480 | my $biblionumber = shift or return; | ||||
481 | my $query = "SELECT * FROM tags_index WHERE term = ? AND biblionumber = ?"; | ||||
482 | my $sth = C4::Context->dbh->prepare($query); | ||||
483 | $sth->execute($term,$biblionumber); | ||||
484 | ($sth->rows) and return increment_weight($term,$biblionumber); | ||||
485 | $query = "INSERT INTO tags_index (term,biblionumber) VALUES (?,?)"; | ||||
486 | $debug and print STDERR "add_tag_index query: $query\nadd_tag_index args: ($term,$biblionumber)\n"; | ||||
487 | $sth = C4::Context->dbh->prepare($query); | ||||
488 | $sth->execute($term,$biblionumber); | ||||
489 | return $sth->rows; | ||||
490 | } | ||||
491 | |||||
492 | sub get_tag { # by tag_id | ||||
493 | (@_) or return; | ||||
494 | my $sth = C4::Context->dbh->prepare(TAG_SELECT . "WHERE tag_id = ?"); | ||||
495 | $sth->execute(shift); | ||||
496 | return $sth->fetchrow_hashref; | ||||
497 | } | ||||
498 | |||||
499 | sub increment_weights { | ||||
500 | increment_weight(@_); | ||||
501 | increment_weight_total(shift); | ||||
502 | } | ||||
503 | sub decrement_weights { | ||||
504 | decrement_weight(@_); | ||||
505 | decrement_weight_total(shift); | ||||
506 | } | ||||
507 | sub increment_weight_total { | ||||
508 | _set_weight_total('weight_total+1',shift); | ||||
509 | } | ||||
510 | sub increment_weight { | ||||
511 | _set_weight('weight+1',shift,shift); | ||||
512 | } | ||||
513 | sub decrement_weight_total { | ||||
514 | _set_weight_total('weight_total-1',shift); | ||||
515 | } | ||||
516 | sub decrement_weight { | ||||
517 | _set_weight('weight-1',shift,shift); | ||||
518 | } | ||||
519 | sub _set_weight_total { | ||||
520 | my $sth = C4::Context->dbh->prepare(" | ||||
521 | UPDATE tags_approval | ||||
522 | SET weight_total=" . (shift) . " | ||||
523 | WHERE term=? | ||||
524 | "); # note: CANNOT use "?" for weight_total (see the args above). | ||||
525 | $sth->execute(shift); # just the term | ||||
526 | } | ||||
527 | sub _set_weight { | ||||
528 | my $dbh = C4::Context->dbh; | ||||
529 | my $sth = $dbh->prepare(" | ||||
530 | UPDATE tags_index | ||||
531 | SET weight=" . (shift) . " | ||||
532 | WHERE term=? | ||||
533 | AND biblionumber=? | ||||
534 | "); | ||||
535 | $sth->execute(@_); | ||||
536 | } | ||||
537 | |||||
538 | sub add_tag { # biblionumber,term,[borrowernumber,approvernumber] | ||||
539 | my $biblionumber = shift or return; | ||||
540 | my $term = shift or return; | ||||
541 | my $borrowernumber = (@_) ? shift : 0; # the user, default to kohaadmin | ||||
542 | $term =~ s/^\s+//; | ||||
543 | $term =~ s/\s+$//; | ||||
544 | ($term) or return; # must be more than whitespace | ||||
545 | my $rows = get_tag_rows({biblionumber=>$biblionumber, borrowernumber=>$borrowernumber, term=>$term, limit=>1}); | ||||
546 | my $query = "INSERT INTO tags_all | ||||
547 | (borrowernumber,biblionumber,term,date_created) | ||||
548 | VALUES (?,?,?,NOW())"; | ||||
549 | $debug and print STDERR "add_tag query: $query\n", | ||||
550 | "add_tag query args: ($borrowernumber,$biblionumber,$term)\n"; | ||||
551 | if (scalar @$rows) { | ||||
552 | $debug and carp "Duplicate tag detected. Tag not added."; | ||||
553 | return; | ||||
554 | } | ||||
555 | # add to tags_all regardless of approaval | ||||
556 | my $sth = C4::Context->dbh->prepare($query); | ||||
557 | $sth->execute($borrowernumber,$biblionumber,$term); | ||||
558 | |||||
559 | # then | ||||
560 | if (scalar @_) { # if arg remains, it is the borrowernumber of the approver: tag is pre-approved. | ||||
561 | my $approver = shift; | ||||
562 | $debug and print STDERR "term '$term' pre-approved by borrower #$approver\n"; | ||||
563 | add_tag_approval($term,$approver,1); | ||||
564 | add_tag_index($term,$biblionumber,$approver); | ||||
565 | } elsif (is_approved($term) >= 1) { | ||||
566 | $debug and print STDERR "term '$term' approved by whitelist\n"; | ||||
567 | add_tag_approval($term,0,1); | ||||
568 | add_tag_index($term,$biblionumber,1); | ||||
569 | } else { | ||||
570 | $debug and print STDERR "term '$term' NOT approved (yet)\n"; | ||||
571 | add_tag_approval($term); | ||||
572 | add_tag_index($term,$biblionumber); | ||||
573 | } | ||||
574 | } | ||||
575 | |||||
576 | # This takes a set of tags, as returned by C<get_approval_rows> and divides | ||||
577 | # them up into a number of "strata" based on their weight. This is useful | ||||
578 | # to display them in a number of different sizes. | ||||
579 | # | ||||
580 | # Usage: | ||||
581 | # ($min, $max) = stratify_tags($strata, $tags); | ||||
582 | # $stratum: the number of divisions you want | ||||
583 | # $tags: the tags, as provided by get_approval_rows | ||||
584 | # $min: the minumum stratum value | ||||
585 | # $max: the maximum stratum value. This may be the same as $min if there | ||||
586 | # is only one weight. Beware of divide by zeros. | ||||
587 | # This will add a field to the tag called "stratum" containing the calculated | ||||
588 | # value. | ||||
589 | sub stratify_tags { | ||||
590 | my ( $strata, $tags ) = @_; | ||||
591 | |||||
592 | my ( $min, $max ); | ||||
593 | foreach (@$tags) { | ||||
594 | my $w = $_->{weight_total}; | ||||
595 | $min = $w if ( !defined($min) || $min > $w ); | ||||
596 | $max = $w if ( !defined($max) || $max < $w ); | ||||
597 | } | ||||
598 | |||||
599 | # normalise min to zero | ||||
600 | $max = $max - $min; | ||||
601 | my $orig_min = $min; | ||||
602 | $min = 0; | ||||
603 | |||||
604 | # if min and max are the same, just make it 1 | ||||
605 | my $span = ( $strata - 1 ) / ( $max || 1 ); | ||||
606 | foreach (@$tags) { | ||||
607 | my $w = $_->{weight_total}; | ||||
608 | $_->{stratum} = int( ( $w - $orig_min ) * $span ); | ||||
609 | } | ||||
610 | return ( $min, $max ); | ||||
611 | } | ||||
612 | |||||
613 | 1 | 13µs | 1; | ||
614 | __END__ | ||||
sub C4::Tags::CORE:match; # opcode | |||||
# spent 1.33ms within C4::Tags::CORE:regcomp which was called 600 times, avg 2µs/call:
# 600 times (1.33ms+0s) by C4::Tags::get_tags at line 236, avg 2µs/call | |||||
# spent 142µs within C4::Tags::CORE:subst which was called 100 times, avg 1µs/call:
# 100 times (142µs+0s) by C4::Tags::get_tags at line 269, avg 1µs/call |