File Coverage

blib/lib/WE_Frontend/Plugin/Navigation.pm
Criterion Covered Total %
statement 9 177 5.0
branch 0 78 0.0
condition 0 20 0.0
subroutine 3 29 10.3
pod 24 26 92.3
total 36 330 10.9


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # $Id: Navigation.pm,v 1.20 2004/03/25 11:56:24 eserte Exp $
5             # Author: Slaven Rezic
6             #
7             # Copyright (C) 2002 Online Office Berlin. All rights reserved.
8             # Copyright (C) 2002 Slaven Rezic.
9             # This is free software; you can redistribute it and/or modify it under the
10             # terms of the GNU General Public License, see the file COPYING.
11              
12             #
13             # Mail: slaven@rezic.de
14             # WWW: http://we-framework.sourceforge.net
15             #
16              
17             package WE_Frontend::Plugin::Navigation;
18 1     1   6 use base qw(Template::Plugin);
  1         3  
  1         82  
19              
20 1     1   5 use strict;
  1         1  
  1         30  
21 1     1   5 use vars qw($VERSION);
  1         1  
  1         2331  
22             $VERSION = sprintf("%d.%02d", q$Revision: 1.20 $ =~ /(\d+)\.(\d+)/);
23              
24             require WE_Frontend::Plugin::Navigation::Object;
25              
26             =head1 NAME
27              
28             WE_Frontend::Plugin::Navigation - utilities for navigation creation
29              
30             =head1 SYNOPSIS
31              
32             my $t = Template->new({PLUGIN_BASE => "WE_Frontend::Plugin"});
33              
34             [% USE Navigation %]
35              
36             =head1 DESCRIPTION
37              
38             C is a C plugin to
39             make the creation of navigations based on objects in a C
40             database easier. The C object
41             corresponds somewhat to the C database. Most of the
42             methods described below return
43             L objects, which correspond
44             to L objects.
45              
46             =head2 METHODS
47              
48             =over
49              
50             =item new
51              
52             This method is normally not called directly, but only as part of the
53             C directive:
54              
55             [% USE Navigation %]
56             [% USE n = Navigation %]
57              
58             You can pass the named parameter C to set the object id for
59             this context.
60              
61             [% USE n = Navigation(objid = 10) %]
62              
63             Or you can use the C parameter:
64              
65             [% USE n = Navigation(name = "rootcollection") %]
66              
67             You can pass the named parameter C to set the object id for
68             this context.
69              
70             When calling the process() method of C the value for
71             C (a reference to the L database) should be set or
72             C (a reference to the L database) XXX rootdb prefered if content access.
73             Also, if C is not set in the C directive, it may be
74             supplied to the process() method.
75              
76             my $t = Template->new(
77             {PLUGIN_BASE => "WE_Frontend::Plugin"}
78             );
79             $t->process(
80             \$template,
81             {
82             objdb => $objdb,
83             rootdb => $rootdb,
84             objid => $objid,
85             config => $c,
86             langstring => \&WE::Util::LangString::langstring
87             },
88             \$output
89             );
90              
91             The return value of the C directive (the C variable
92             in the examples above) is a L
93             of the current object supplied with the C key.
94              
95             =cut
96              
97             sub new {
98 0     0 1   my($class, $context, $params) = @_;
99 0   0       $params ||= {};
100 0   0       my $rootdb = $params->{rootdb} || eval { $context->stash->get("rootdb") };
101 0   0       my $objdb = $params->{objdb} || eval { $context->stash->get("objdb") };
102 0 0 0       if (!$objdb && $rootdb) {
103 0           $objdb = $rootdb->ObjDB;
104             }
105 0 0         if (!$objdb) {
106 0 0         if ($rootdb) {
107 0           return $class->error("The required parameter rootdb is defined, but its member ObjDB is not defined");
108             } else {
109 0           return $class->error("The required parameter objdb and/or rootdb is not defined");
110             }
111             }
112 0           my $objid = $params->{objid};
113 0 0         if (!defined $objid) {
114 0 0         if (defined $params->{name}) {
115 0           $objid = $objdb->name_to_objid($params->{name});
116             }
117             }
118 0 0         if (!defined $objid) {
119 0           $objid = eval { $context->stash->get("objid") };
  0            
120             }
121 0           my $self = {
122             Context => $context,
123             RootDB => $rootdb,
124             ObjDB => $objdb,
125             ObjID => $objid,
126             POCache => {}, # for pathobjects_with_cache
127             };
128 0           bless $self, $class;
129             }
130              
131             =item ancestors([[objid = id | name = name], fromlevel => level, tolevel => level, restrict = restrictmethod])
132              
133             Return a list of ancestors of the current object. The oldest
134             (top-most) ancestor is first in the list. If C is given, then
135             return the ancestors for the object with this object identifier. If
136             C and/or C are given, then restrict the ancestor
137             list for these levels. The topmost level is numbered with 1. The root
138             itself is numbered with 0, this can be used for a "home" link on top
139             of the list. The list may be restricted by specifying C. If
140             tolevel is less than fromlevel, then an empty list is returned.
141              
142             =cut
143              
144             sub ancestors {
145 0     0 1   my $self = shift;
146 0 0         my $params = ref($_[$#_]) eq 'HASH' ? pop(@_) : { };
147 0           my @l = $self->{ObjDB}->pathobjects_with_cache($self->current_id($params), undef, $self->{POCache});
148 0           pop @l; # delete last in list (the object itself);
149 0           $self->objify_list(\@l);
150              
151 0 0         if (defined $params->{fromlevel}) {
    0          
152 0 0         if (defined $params->{tolevel}) {
153 0           @l = @l[$params->{fromlevel} .. $params->{tolevel}];
154             } else {
155 0           @l = @l[$params->{fromlevel} .. $#l];
156             }
157             } elsif (defined $params->{tolevel}) {
158 0           @l = @l[0 .. $params->{tolevel}];
159             }
160              
161 0           $self->restrict($params, \@l);
162 0           [@l];
163             }
164              
165             =item parent([[objid = id | name = name]])
166              
167             Return the parent of the current object, or of the object with id
168             C. Note that it is possible in the C database to
169             have more than one parent, nevertheless only one parent is returned.
170              
171             =cut
172              
173             sub parent {
174 0     0 1   my $self = shift;
175 0 0         my $params = ref($_[$#_]) eq 'HASH' ? pop(@_) : { };
176 0           my $obj = $self->current_object($params);
177 0           my $objdb = $self->{ObjDB};
178 0           my(@l) = ($objdb->parents($obj))[0];
179 0           $self->objify_list(\@l);
180 0           $self->restrict($params, \@l);
181 0           $l[0];
182             }
183              
184             =item level([[objid = id | name = name]])
185              
186             Return the level of the current object, or of the object with id
187             C. The root of the site has level = 0.
188              
189             =cut
190              
191             sub level {
192 0     0 1   my $self = shift;
193 0           scalar @{ $self->ancestors(@_) };
  0            
194             }
195              
196             =item toplevel_children([sort = sortmethod, restrict = restrictmethod])
197              
198             Return a list of sorted toplevel children. Normally, the sequence
199             order is used but the sorting can be changed by specifying C.
200             The list may be restricted by specifying C.
201              
202             =cut
203              
204             sub toplevel_children {
205 0     0 1   my $self = shift;
206 0 0         my $params = ref($_[$#_]) eq 'HASH' ? pop(@_) : { };
207 0           $params->{'level'} = 1;
208 0           $self->siblings($params);
209             }
210              
211             =item siblings([[objid = id | name = name], level = level, sort = sortmethod, restrict => restrictmethod])
212              
213             Get the siblings of the current object, or of the object with id
214             C. The siblings are sorted by the sortmethod in C and
215             can be restricted with C.
216              
217             If C is specified, the siblings of the ancestor of the current
218             object in the specified level are returned. The level can also be
219             specified as a negative number; this means how many levels up from the
220             current position should be used.
221              
222             =cut
223              
224             sub siblings {
225 0     0 1   my $self = shift;
226 0 0         my $params = ref($_[$#_]) eq 'HASH' ? pop(@_) : { };
227 0           my $objid = $self->current_id($params);
228 0           my $objdb = $self->{ObjDB};
229 0           my $pid;
230             my @l;
231 0 0         if (defined $params->{level}) {
232 0           my @ancestors = $objdb->pathobjects_with_cache($objid, undef, $self->{POCache});
233 0 0         if ($params->{level} =~ /^\d/) {
    0          
234 0           $pid = $ancestors[$params->{level}-1]; # XXX -1 ???
235             } elsif ($params->{level} =~ /^-(\d+)$/) {
236 0 0         if (-($params->{level}-2) > scalar @ancestors + 1) {
    0          
237 0           return $self->error("Level above root object");
238             } elsif (-($params->{level}-2) == scalar @ancestors + 1) {
239             # the root object itself
240 0           @l = $ancestors[0];
241 0           $self->objify_list(\@l);
242 0           $self->restrict($params, \@l);
243             # no sorting necessary :-)
244 0           return @l;
245             } else {
246 0           $pid = $ancestors[$params->{level}-2]
247             }
248             } else {
249 0           return $self->error("Invalid level specifier: $params->{level}");
250             }
251             } else {
252 0           $pid = ($objdb->parent_ids($objid))[0];
253             }
254 0           @l = $objdb->children($pid);
255 0           $self->objify_list(\@l);
256 0           $self->restrict($params, \@l);
257 0           $self->sort($params, \@l);
258 0           [@l];
259             }
260              
261             =item children([[objid = id | name = name], sort = sortmethod, restrict => restrictmethod])
262              
263             Get the children of the current object, or of the object with id
264             C. The children are sorted by the sortmethod in C and
265             can be restricted with C.
266              
267             =cut
268              
269             sub children {
270 0     0 1   my $self = shift;
271 0 0         my $params = ref($_[$#_]) eq 'HASH' ? pop(@_) : { };
272 0           my $obj = $self->current_object($params);
273 0           my @l = $self->{ObjDB}->children($obj);
274 0           $self->objify_list(\@l);
275 0           $self->restrict($params, \@l);
276 0           $self->sort($params, \@l);
277 0           [@l];
278             }
279              
280             =item siblings_or_children([...]);
281              
282             Often, siblings are used if the object is a document and children are
283             used if the object is a folder. This convenience method uses the
284             appropriate method. The arguments are the same as in C or
285             C.
286              
287             =cut
288              
289             sub siblings_or_children {
290 0     0 1   my $self = shift;
291 0 0         my $params = ref($_[$#_]) eq 'HASH' ? pop(@_) : { };
292 0           my $obj = $self->current_object($params);
293 0 0         if ($obj->is_doc) {
294 0           $self->siblings($params);
295             } else {
296 0           $self->children($params);
297             }
298             }
299              
300             =item restrict($params, $listref)
301              
302             Internal method to restrict the passed array reference according to
303             the C<<$params->{restrict}>> subroutine.
304              
305             The value of the C parameter should be the name of a method
306             in the C class. The object is
307             accepted if the returned value is true. Example for an user-defined
308             method (although subclassing would be the cleaner solution):
309              
310             package WE_Frontend::Plugin::Navigation::Object;
311             sub my_restrict {
312             my $o = shift;
313             # restrict to objects with Id less than 5
314             $o->o->Id < 5;
315             }
316              
317             =cut
318              
319             sub restrict {
320 0     0 1   my($self, $params, $listref) = @_;
321 0           my $sub = $params->{restrict};
322 0 0 0       return if !$sub || !@$listref;
323 0           @$listref = grep { $_->$sub() } @$listref;
  0            
324             }
325              
326             =item sort($params, $listref)
327              
328             Internal method to sort the passed array reference according to
329             the C<<$params->{sort}>> subroutine.
330              
331             The value of the C parameter should be the name of a method in
332             the C class. This method takes to
333             arguments C<$a> and C<$b>, both
334             C objects which should be
335             compared. The returned value should be -1, 0, or 1, just as in the
336             C function. Example for an user-defined method (although
337             subclassing would be the cleaner solution):
338              
339             package WE_Frontend::Plugin::Navigation;
340             sub my_sort {
341             my($self, $a, $b) = @_;
342             # sort by title
343             WE::Util::LangString::langstring($a->o->Title) cmp WE::Util::LangString::langstring($b->o->Title);
344             }
345              
346             =cut
347              
348             sub sort {
349 0     0 1   my($self, $params, $listref) = @_;
350 0           my $sub = $params->{sort};
351 0 0 0       return if !$sub || !@$listref;
352 0           @$listref = sort { $self->$sub($a,$b) } @$listref;
  0            
353             }
354              
355             =item current_object([[objid = id | name = name]])
356              
357             Return the current active object as a C object. See also the
358             C method.
359              
360             =cut
361              
362             sub current_object {
363 0     0 1   my($self, $params) = @_;
364 0           my $id = $self->current_id($params);
365             # XXX check for error?
366 0           my $objdb = $self->{ObjDB};
367 0           my $obj = $objdb->get_object($id);
368 0 0         if (!$obj) {
369 0           return $self->error("Can't get object with id <$id> from database <" . $objdb->DBFile . ">");
370             }
371 0           $obj;
372             }
373              
374             =item current_id([[objid = id | name = name]])
375              
376             Return the current active id. The object is identified in this order:
377              
378             =over
379              
380             =item C in this method call
381              
382             =item C in this method call
383              
384             =item C parameter in the C C directive
385              
386             =item C template variable (as specified in the C<<
387             Template->new >> call)
388              
389             =back
390              
391             =cut
392              
393             sub current_id {
394 0     0 1   my($self, $params) = @_;
395 0           my $id;
396 0 0         if (defined $params->{objid}) {
    0          
    0          
397 0           $id = $params->{objid};
398             } elsif (defined $params->{name}) {
399 0           $id = $self->{ObjDB}->name_to_objid($params->{name});
400             } elsif (defined $self->{ObjID}) {
401 0           $id = $self->{ObjID};
402             }
403 0 0         if (!defined $id) {
404 0           return $self->error("No object id defined. Please define it in either:
405             * as an objid parameter in the current method call
406             * as an name parameter (with an existing name) in the current method call
407             * as an objid parameter in the USE directive
408             * as an objid template variable
409             ");
410             }
411 0           $id;
412             }
413              
414             =item self([[objid = id | name = name]])
415              
416             Return the current active object as a B<...::Navigation::Object> object.
417              
418             =cut
419              
420             sub self {
421 0     0 1   my($self, $params) = @_;
422 0           my $class = $self->Object;
423 0           $class->new($self->current_object($params), $self);
424             }
425              
426             =item get_object([[objid = id | name = name]])
427              
428             This is an alias for B, but uses a more "logical" name if an
429             object is retrieved by id or name.
430              
431             =cut
432              
433             *get_object = \&self;
434              
435             =item is_self([$testobj | $testobjid], [[objid = id | name = name]])
436              
437             Return true if the given C<$testobj> (object) or C<$testobjid> (id) is
438             the current object. You can pass another C instead of the
439             current object. =cut
440              
441             =cut
442              
443             sub is_self {
444 0     0 1   my($self, $id, $params) = @_;
445 0           $self->idify_params($id);
446 0           my $current_id = $self->current_id($params);
447 0           $id eq $current_id;
448             }
449              
450             =item equals([$testobj | $testobjid], [objid = id | name = name])
451              
452             The same as C, only that either C or C are
453             mandatory.
454              
455             Example:
456              
457             [% IF n.equals(testobjid, objid = otherobjid) %]
458              
459             =cut
460              
461             sub equals {
462 0     0 1   my($self, $id, $params) = @_;
463 0 0 0       if (!exists $params->{objid} && !exists $params->{name}) {
464 0           die "Either objid or name are mandatory for the equals method";
465             }
466 0           $self->is_self($id, $params);
467             }
468              
469             =item is_ancestor([$testobj | $testobjid], [objid => id])
470              
471             Return true if the given C<$testobj> (object) or C<$testobjid> (id) is
472             an ancestor of the current object. You can pass another C
473             instead of the current object. The current object is not considered an
474             ancestor of itself.
475              
476             =cut
477              
478             sub is_ancestor {
479 0     0 1   my($self, $objid, $params) = @_;
480 0           $self->idify_params($objid);
481 0           my $current_id = $self->current_id($params);
482 0 0         return 0 if $objid eq $current_id;
483 0           for my $o ($self->{ObjDB}->pathobjects_with_cache($current_id, undef, $self->{POCache})) {
484 0 0         return 1 if ($objid eq $o->Id);
485             }
486 0           0;
487             }
488              
489             =item object_by_name($name)
490              
491             Return an object by C<$name>.
492              
493             =cut
494              
495             sub object_by_name {
496 0     0 1   my($self, $name) = @_;
497 0           my $id = $self->{ObjDB}->name_to_objid($name);
498 0 0         if (defined $id) {
499 0           return $self->self({objid => $id});
500             }
501 0           return $self->error("Can't get object by name $name");
502             }
503              
504             =item Root
505              
506             Return reference to root database.
507              
508             =cut
509              
510 0     0 1   sub Root { $_[0]->{ObjDB}->Root }
511              
512             =item ObjDB
513              
514             Return reference to the object database.
515              
516             =cut
517              
518 0     0 1   sub ObjDB { $_[0]->{ObjDB} }
519              
520             =item Object
521              
522             Return the class name for the navigation objects. This can be
523             overridden in inherited classes.
524              
525             =cut
526              
527             sub Object {
528 0     0 1   "WE_Frontend::Plugin::Navigation::Object";
529             }
530              
531             =back
532              
533             =head2 MEMBERS
534              
535             Remember that there is no visible distinction in the Template-Toolkit
536             between accessing members and methods.
537              
538             =over
539              
540             =item Context
541              
542             The C context.
543              
544             =item ObjDB
545              
546             A reference to the object database (C).
547              
548             =back
549              
550             =head2 INTERNAL METHODS
551              
552             =over
553              
554             =item objify_list($listref)
555              
556             Internal method to create from a list of C objects a list of
557             Navigation objects (see the C method). The passed parameter
558             C<$listref> will be changed.
559              
560             =cut
561              
562             sub objify_list {
563 0     0 1   my($self, $objlistref) = @_;
564 0           my $class = $self->Object;
565 0           @$objlistref = map { $class->new($_, $self) } @$objlistref;
  0            
566             }
567              
568             =item objectify_params($obj_or_id, $obj_or_id, ...)
569              
570             Turn the given arguments from an object id or C object into
571             an C object.
572              
573             =cut
574              
575             sub objectify_params {
576 0     0 1   my $self = shift;
577 0           my $class = $self->Object;
578 0           my $objdb = $self->{ObjDB};
579 0           for (@_) {
580 0 0         if (/^\d+$/) { # treat as object id
    0          
    0          
581 0           $_ = $objdb->get_object($_);
582             } elsif (UNIVERSAL::isa($_, $class)) {
583             # do nothing
584             } elsif (UNIVERSAL::isa($_, "WE::Obj")) {
585 0           $_ = $class->new($_, $self);
586             } else {
587 0           warn "Can't interpret $_ in objectify_params";
588             }
589             }
590             }
591              
592             =item idify_params($obj_or_id, ....)
593              
594             Turn the given arguments from an object id or C object into
595             an object id.
596              
597             =cut
598              
599             sub idify_params {
600 0     0 1   my $self = shift;
601 0           my $class = $self->Object;
602 0           my $objdb = $self->{ObjDB};
603 0           for (@_) {
604 0 0         if (/^\d+$/) { # treat as object id
    0          
    0          
605             # do nothing
606             } elsif (UNIVERSAL::isa($_, $class)) {
607 0           $_ = $_->o->Id;
608             } elsif (UNIVERSAL::isa($_, "WE::Obj")) {
609 0           $_ = $_->Id;
610             } else {
611 0           warn "Can't interpret $_ in idify_params";
612             }
613             }
614             }
615              
616             # hmmm... the default error() method does not throw an exception
617             sub error {
618 0     0 1   require Carp;
619 0           Carp::confess($_[1]);
620             }
621              
622             sub dump {
623 0     0 0   my($self, $extra) = @_;
624 0           my $out = "Dump $self:\n";
625 0           require WE::Util::LangString;
626 0           while(my($k,$v) = each %$self) {
627 0           $out .= "$k => " . WE::Util::LangString::langstring($v) . "\n";
628             }
629 0 0         $out .= "\n$extra" if defined $extra;
630 0           $out .= "\n";
631 0           warn $out;
632 0           "";
633             }
634              
635             # XXX documentation pending
636             sub reset_cache {
637 0     0 0   my $self = shift;
638 0           $self->{POCache} = {};
639             }
640              
641             ## Debugging aid:
642             # sub DESTROY {
643             # my $self = shift;
644             # warn $self->{ObjDB}->{CH} if defined $self->{ObjDB} && defined $self->{ObjDB}->{CH};
645             # }
646              
647             1;
648              
649             __END__