| 1 | # Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd. |
|---|
| 2 | # This program is distributed under the terms of the |
|---|
| 3 | # GNU General Public License, version 2. |
|---|
| 4 | # |
|---|
| 5 | # $Id$ |
|---|
| 6 | |
|---|
| 7 | package MT::Template::Context::Search; |
|---|
| 8 | |
|---|
| 9 | use strict; |
|---|
| 10 | use base qw( MT::Template::Context ); |
|---|
| 11 | use MT::Util qw( encode_url decode_html ); |
|---|
| 12 | |
|---|
| 13 | sub load_core_tags { |
|---|
| 14 | require MT::Template::Context; |
|---|
| 15 | return { |
|---|
| 16 | function => { |
|---|
| 17 | SearchString => \&_hdlr_search_string, |
|---|
| 18 | SearchResultCount => \&_hdlr_result_count, |
|---|
| 19 | MaxResults => \&_hdlr_max_results, |
|---|
| 20 | SearchIncludeBlogs => \&_hdlr_include_blogs, |
|---|
| 21 | SearchTemplateID => \&_hdlr_template_id, |
|---|
| 22 | }, |
|---|
| 23 | block => { |
|---|
| 24 | SearchResults => \&_hdlr_results, |
|---|
| 25 | 'IfTagSearch?' => sub { MT->instance->mode eq 'tag' }, |
|---|
| 26 | 'IfStraightSearch?' => sub { MT->instance->mode eq 'default' }, |
|---|
| 27 | 'NoSearchResults?' => sub { $_[0]->stash('count') ? 0 : 1; }, |
|---|
| 28 | 'NoSearch?' => sub { ( $_[0]->stash('search_string') && |
|---|
| 29 | $_[0]->stash('search_string') =~ /\S/ ) ? 0 : 1 }, |
|---|
| 30 | SearchResultsHeader => \&MT::Template::Context::_hdlr_pass_tokens, |
|---|
| 31 | SearchResultsFooter => \&MT::Template::Context::_hdlr_pass_tokens, |
|---|
| 32 | BlogResultHeader => \&MT::Template::Context::_hdlr_pass_tokens, |
|---|
| 33 | BlogResultFooter => \&MT::Template::Context::_hdlr_pass_tokens, |
|---|
| 34 | 'IfMaxResultsCutoff?' => \&MT::Template::Context::_hdlr_pass_tokens, |
|---|
| 35 | }, |
|---|
| 36 | }; |
|---|
| 37 | } |
|---|
| 38 | |
|---|
| 39 | ########################################################################### |
|---|
| 40 | |
|---|
| 41 | =head2 IfStraightSearch |
|---|
| 42 | |
|---|
| 43 | A conditional block which outputs its contents if the search in progress |
|---|
| 44 | is a regular (or "straight") search. |
|---|
| 45 | |
|---|
| 46 | =for tags search |
|---|
| 47 | |
|---|
| 48 | =cut |
|---|
| 49 | |
|---|
| 50 | ########################################################################### |
|---|
| 51 | |
|---|
| 52 | =head2 IfTagSearch |
|---|
| 53 | |
|---|
| 54 | A conditional block which outputs its contents if the search in progress |
|---|
| 55 | is a search of entries by tag. |
|---|
| 56 | |
|---|
| 57 | =for tags search |
|---|
| 58 | |
|---|
| 59 | =cut |
|---|
| 60 | |
|---|
| 61 | ########################################################################### |
|---|
| 62 | |
|---|
| 63 | =head2 NoSearch |
|---|
| 64 | |
|---|
| 65 | A container tag whose contents are displayed only if there is no search |
|---|
| 66 | performed. |
|---|
| 67 | |
|---|
| 68 | This tag is only recognized in search templates. |
|---|
| 69 | |
|---|
| 70 | =for tags search |
|---|
| 71 | |
|---|
| 72 | =cut |
|---|
| 73 | |
|---|
| 74 | ########################################################################### |
|---|
| 75 | |
|---|
| 76 | =head2 NoSearchResults |
|---|
| 77 | |
|---|
| 78 | A container tag whose contents are displayed if a search is performed |
|---|
| 79 | but no results are found. |
|---|
| 80 | |
|---|
| 81 | This tag is only recognized in search templates. |
|---|
| 82 | |
|---|
| 83 | =for tags search |
|---|
| 84 | |
|---|
| 85 | =cut |
|---|
| 86 | |
|---|
| 87 | ########################################################################### |
|---|
| 88 | |
|---|
| 89 | =head2 SearchResultsHeader |
|---|
| 90 | |
|---|
| 91 | The content of this block tag is rendered when the item in context |
|---|
| 92 | from search results are the first item of the result set. You can |
|---|
| 93 | use the block to render headings and titles of the result table, |
|---|
| 94 | for example. |
|---|
| 95 | |
|---|
| 96 | This tag is only recognized in SearchResults block. |
|---|
| 97 | |
|---|
| 98 | B<Example:> |
|---|
| 99 | |
|---|
| 100 | <mt:SearchResultsHeader> |
|---|
| 101 | <h3>Look what we found!</h3> |
|---|
| 102 | </mt:SearchResultsHeader> |
|---|
| 103 | |
|---|
| 104 | =for tags search |
|---|
| 105 | |
|---|
| 106 | =cut |
|---|
| 107 | |
|---|
| 108 | ########################################################################### |
|---|
| 109 | |
|---|
| 110 | =head2 SearchResultsFooter |
|---|
| 111 | |
|---|
| 112 | The content of this block tag is rendered when the item in context |
|---|
| 113 | from search results are the last item of the result set. You can |
|---|
| 114 | use the block to render closeing tags of a HTML element, for example. |
|---|
| 115 | |
|---|
| 116 | This tag is only recognized in SearchResults block. |
|---|
| 117 | |
|---|
| 118 | B<Example:> |
|---|
| 119 | |
|---|
| 120 | <mt:SearchResultsFooter> |
|---|
| 121 | <p>If you didn't find what you were looking for, you can also peruse |
|---|
| 122 | the <a href="<mt:Link identifier="archive_index">">site archives</a>.</p> |
|---|
| 123 | </mt:SearchResultsFooter> |
|---|
| 124 | |
|---|
| 125 | =for tags search |
|---|
| 126 | |
|---|
| 127 | =cut |
|---|
| 128 | |
|---|
| 129 | ########################################################################### |
|---|
| 130 | |
|---|
| 131 | =head2 BlogResultHeader |
|---|
| 132 | |
|---|
| 133 | The contents of this container tag will be displayed when the blog id of |
|---|
| 134 | the entry in context from search results differs from the previous entry's |
|---|
| 135 | blog id. |
|---|
| 136 | |
|---|
| 137 | This tag is only recognized in search templates. |
|---|
| 138 | |
|---|
| 139 | =for tags search |
|---|
| 140 | |
|---|
| 141 | =cut |
|---|
| 142 | |
|---|
| 143 | ########################################################################### |
|---|
| 144 | |
|---|
| 145 | =head2 BlogResultFooter |
|---|
| 146 | |
|---|
| 147 | The contents of this container tag will be displayed when the blog id of |
|---|
| 148 | the entry in context from search results differs from the next entry's |
|---|
| 149 | blog id. |
|---|
| 150 | |
|---|
| 151 | This tag is only recognized in search templates. |
|---|
| 152 | |
|---|
| 153 | =for tags search |
|---|
| 154 | |
|---|
| 155 | =cut |
|---|
| 156 | |
|---|
| 157 | ########################################################################### |
|---|
| 158 | |
|---|
| 159 | =head2 IfMaxResultsCutOff |
|---|
| 160 | |
|---|
| 161 | NOTE: this tag only applies if you are using older Movable Type than |
|---|
| 162 | version 4.15, or you set up your search script so it instantiates |
|---|
| 163 | MT::App::Search::Legacy, the older search script. Under the default |
|---|
| 164 | search script in Movable Type, this tag will never be evaluated as true and |
|---|
| 165 | therefore the contents will never be rendered. |
|---|
| 166 | |
|---|
| 167 | A conditional tag that returns true when the number of search results |
|---|
| 168 | per blog exceeds the maximium limit specified in MaxResults configuration |
|---|
| 169 | directive. |
|---|
| 170 | |
|---|
| 171 | This tag is only recognized in search templates. |
|---|
| 172 | |
|---|
| 173 | =for tags search |
|---|
| 174 | |
|---|
| 175 | =cut |
|---|
| 176 | |
|---|
| 177 | ########################################################################### |
|---|
| 178 | |
|---|
| 179 | =head2 MaxResults |
|---|
| 180 | |
|---|
| 181 | Returns the value of SearchMaxResults, specified either in configuration |
|---|
| 182 | (via C<SearchMaxResults> configuration directive) or in the search query |
|---|
| 183 | parameter in the URL. |
|---|
| 184 | |
|---|
| 185 | This tag is only recognized in search templates. |
|---|
| 186 | |
|---|
| 187 | =cut |
|---|
| 188 | |
|---|
| 189 | ########################################################################### |
|---|
| 190 | |
|---|
| 191 | =head2 SearchIncludeBlogs |
|---|
| 192 | |
|---|
| 193 | Used in the search result template to pass the IncludeBlogs parameters |
|---|
| 194 | through from the search form keeping the context of any followup search |
|---|
| 195 | the same as the initial search. |
|---|
| 196 | |
|---|
| 197 | B<Example:> |
|---|
| 198 | |
|---|
| 199 | <input type="hidden" name="IncludeBlogs" value="<$mt:SearchIncludeBlogs$>" /> |
|---|
| 200 | |
|---|
| 201 | =cut |
|---|
| 202 | |
|---|
| 203 | sub _hdlr_include_blogs { $_[0]->stash('include_blogs') || '' } |
|---|
| 204 | |
|---|
| 205 | ########################################################################### |
|---|
| 206 | |
|---|
| 207 | =head2 SearchString |
|---|
| 208 | |
|---|
| 209 | An HTML-encoded search query. This tag is only recognized in search templates. |
|---|
| 210 | |
|---|
| 211 | B<Example:> |
|---|
| 212 | |
|---|
| 213 | <$mt:SearchString$> |
|---|
| 214 | |
|---|
| 215 | =for tags search |
|---|
| 216 | |
|---|
| 217 | =cut |
|---|
| 218 | |
|---|
| 219 | sub _hdlr_search_string { $_[0]->stash('search_string') || '' } |
|---|
| 220 | |
|---|
| 221 | ########################################################################### |
|---|
| 222 | |
|---|
| 223 | =head2 SearchTemplateID |
|---|
| 224 | |
|---|
| 225 | Returns the identifier of the search template (ie, "feed" or |
|---|
| 226 | "nomorepizzaplease"). |
|---|
| 227 | |
|---|
| 228 | B<Example:> |
|---|
| 229 | |
|---|
| 230 | <$mt:SearchTemplateID$> |
|---|
| 231 | |
|---|
| 232 | =for tags search |
|---|
| 233 | |
|---|
| 234 | =cut |
|---|
| 235 | |
|---|
| 236 | sub _hdlr_template_id { $_[0]->stash('template_id') || '' } |
|---|
| 237 | sub _hdlr_max_results { $_[0]->stash('maxresults') || '' } |
|---|
| 238 | |
|---|
| 239 | ########################################################################### |
|---|
| 240 | |
|---|
| 241 | =head2 SearchResultCount |
|---|
| 242 | |
|---|
| 243 | The number of results found across all of the blogs searched. This tag |
|---|
| 244 | is only recognized in search templates. |
|---|
| 245 | |
|---|
| 246 | B<Example:> |
|---|
| 247 | |
|---|
| 248 | <$mt:SearchResultCount$> |
|---|
| 249 | |
|---|
| 250 | =for tags search, count |
|---|
| 251 | |
|---|
| 252 | =cut |
|---|
| 253 | |
|---|
| 254 | sub _hdlr_result_count { |
|---|
| 255 | my $results = $_[0]->stash('count'); |
|---|
| 256 | $results ? $results : 0; |
|---|
| 257 | } |
|---|
| 258 | |
|---|
| 259 | ########################################################################### |
|---|
| 260 | |
|---|
| 261 | =head2 SearchResults |
|---|
| 262 | |
|---|
| 263 | A container tag that creates a list of search results. This tag |
|---|
| 264 | creates an entry and blog context that all Entry* and Blog* tags |
|---|
| 265 | can be used. |
|---|
| 266 | |
|---|
| 267 | This tag is only recognized in search templates. |
|---|
| 268 | |
|---|
| 269 | =for tags search |
|---|
| 270 | |
|---|
| 271 | =cut |
|---|
| 272 | |
|---|
| 273 | sub _hdlr_results { |
|---|
| 274 | my($ctx, $args, $cond) = @_; |
|---|
| 275 | |
|---|
| 276 | ## If there are no results, return the empty string, knowing |
|---|
| 277 | ## that the handler for <MTNoSearchResults> will fill in the |
|---|
| 278 | ## no results message. |
|---|
| 279 | my $iter = $ctx->stash('results') or return ''; |
|---|
| 280 | my $count = $ctx->stash('count') or return ''; |
|---|
| 281 | my $max = $ctx->stash('maxresults'); |
|---|
| 282 | my $stash_key = $ctx->stash('stash_key') || 'entry'; |
|---|
| 283 | |
|---|
| 284 | my $output = ''; |
|---|
| 285 | my $build = $ctx->stash('builder'); |
|---|
| 286 | my $tokens = $ctx->stash('tokens'); |
|---|
| 287 | my $blog_header = 1; |
|---|
| 288 | my $blog_footer = 0; |
|---|
| 289 | my $footer = 0; |
|---|
| 290 | my $count_per_blog = 0; |
|---|
| 291 | my $max_reached = 0; |
|---|
| 292 | my ( $this_object, $next_object ); |
|---|
| 293 | $this_object = $iter->(); |
|---|
| 294 | return '' unless $this_object; |
|---|
| 295 | for ( my $i = 0; $i < $count; $i++) { |
|---|
| 296 | $count_per_blog++; |
|---|
| 297 | $ctx->stash($stash_key, $this_object); |
|---|
| 298 | local $ctx->{__stash}{blog} = $this_object->blog |
|---|
| 299 | if $this_object->can('blog'); |
|---|
| 300 | my $ts; |
|---|
| 301 | if ( $this_object->isa('MT::Entry') ) { |
|---|
| 302 | $ts = $this_object->authored_on; |
|---|
| 303 | } |
|---|
| 304 | elsif ( $this_object->properties->{audit} ) { |
|---|
| 305 | $ts = $this_object->created_on; |
|---|
| 306 | } |
|---|
| 307 | local $ctx->{current_timestamp} = $ts; |
|---|
| 308 | |
|---|
| 309 | # TODO: per blog max objects? |
|---|
| 310 | #if ( $count_per_blog >= $max ) { |
|---|
| 311 | # while (1) { |
|---|
| 312 | # if ( $count > $i + 1 ) { |
|---|
| 313 | # $next_object = $results->[$i + 1]; |
|---|
| 314 | # if ( $next_object->blog_id ne $this_object->blog_id ) { |
|---|
| 315 | # $blog_footer = 1; |
|---|
| 316 | # last; |
|---|
| 317 | # } |
|---|
| 318 | # else { |
|---|
| 319 | # $max_reached = 1; |
|---|
| 320 | # } |
|---|
| 321 | # } |
|---|
| 322 | # else { |
|---|
| 323 | # $next_object = undef; |
|---|
| 324 | # $blog_footer = 1; |
|---|
| 325 | # last; |
|---|
| 326 | # } |
|---|
| 327 | # $i++; |
|---|
| 328 | # } |
|---|
| 329 | #} |
|---|
| 330 | #elsif ( $count > $i + 1 ) { |
|---|
| 331 | # $next_object = $results->[$i + 1]; |
|---|
| 332 | # $blog_footer = $next_object->blog_id ne $this_object->blog_id ? 1 : 0; |
|---|
| 333 | #} |
|---|
| 334 | #else { |
|---|
| 335 | # $blog_footer = 1; |
|---|
| 336 | #} |
|---|
| 337 | if ( $next_object = $iter->() ) { |
|---|
| 338 | $blog_footer = $next_object->blog_id ne $this_object->blog_id ? 1 : 0; |
|---|
| 339 | } |
|---|
| 340 | else { |
|---|
| 341 | $blog_footer = 1; |
|---|
| 342 | $footer = 1; |
|---|
| 343 | } |
|---|
| 344 | |
|---|
| 345 | defined(my $out = $build->build($ctx, $tokens, |
|---|
| 346 | { %$cond, |
|---|
| 347 | SearchResultsHeader => $i == 0, |
|---|
| 348 | SearchResultsFooter => $footer, |
|---|
| 349 | BlogResultHeader => $blog_header, |
|---|
| 350 | BlogResultFooter => $blog_footer, |
|---|
| 351 | IfMaxResultsCutoff => $max_reached, |
|---|
| 352 | } |
|---|
| 353 | )) or return $ctx->error( $build->errstr ); |
|---|
| 354 | $output .= $out; |
|---|
| 355 | |
|---|
| 356 | $this_object = $next_object; |
|---|
| 357 | last unless $this_object; |
|---|
| 358 | $blog_header = $blog_footer ? 1 : 0; |
|---|
| 359 | } |
|---|
| 360 | $output; |
|---|
| 361 | } |
|---|
| 362 | |
|---|
| 363 | sub context_script { |
|---|
| 364 | my ( $ctx, $args, $cond ) = @_; |
|---|
| 365 | |
|---|
| 366 | my $search_string = decode_html( $ctx->stash('search_string') ) ; |
|---|
| 367 | my $cgipath = $ctx->_hdlr_cgi_path($args); |
|---|
| 368 | my $script = $ctx->{config}->SearchScript; |
|---|
| 369 | my $link = $cgipath.$script . '?search=' . encode_url( $search_string ); |
|---|
| 370 | if ( my $mode = $ctx->stash('mode') ) { |
|---|
| 371 | $mode = encode_url($mode); |
|---|
| 372 | $link .= "&__mode=$mode"; |
|---|
| 373 | } |
|---|
| 374 | if ( my $type = $ctx->stash('type') ) { |
|---|
| 375 | $type = encode_url($type); |
|---|
| 376 | $link .= "&type=$type"; |
|---|
| 377 | } |
|---|
| 378 | if ( my $include_blogs = $ctx->stash('include_blogs') ) { |
|---|
| 379 | $link .= "&IncludeBlogs=$include_blogs"; |
|---|
| 380 | } |
|---|
| 381 | elsif ( my $blog_id = $ctx->stash('blog_id') ) { |
|---|
| 382 | $link .= "&blog_id=$blog_id"; |
|---|
| 383 | } |
|---|
| 384 | if ( my $format = $ctx->stash('format') ) { |
|---|
| 385 | $link .= '&format=' . encode_url($format); |
|---|
| 386 | } |
|---|
| 387 | $link; |
|---|
| 388 | } |
|---|
| 389 | |
|---|
| 390 | 1; |
|---|
| 391 | __END__ |
|---|
| 392 | |
|---|