From 83b93e22e8b18e468c8697cfe67983819df8e432 Mon Sep 17 00:00:00 2001 From: Martin Kamerbeek Date: Sun, 11 Jul 2004 15:33:41 +0000 Subject: [PATCH] Adding navigation caching and implementing the nested set model. --- docs/changelog/6.x.x.txt | 9 +- docs/upgrades/upgrade_6.0.3-6.1.0.pl | 48 ++ lib/WebGUI/Navigation.pm | 221 ++--- lib/WebGUI/Operation/Navigation.pm | 11 + lib/WebGUI/Operation/Page.pm | 41 +- lib/WebGUI/Operation/Trash.pm | 15 +- lib/WebGUI/Page.pm | 1149 +++++++++++++++++++++----- 7 files changed, 1150 insertions(+), 344 deletions(-) diff --git a/docs/changelog/6.x.x.txt b/docs/changelog/6.x.x.txt index a72146ce4..94cdb9a01 100644 --- a/docs/changelog/6.x.x.txt +++ b/docs/changelog/6.x.x.txt @@ -73,9 +73,12 @@ - bugfix [ 934410 ] non-compliant HTML (solution). Thanks to Nicklous Roberts. - bugfix [ 963316 ] Field without title in Content Settings - - - + - Converted the page tree system from the ajacency list model to the nested + set model. Thanks to Martin Kamerbeek / Procolix and Dan Collis Puro for + providing the DBIx::Tree::NestedSet module. + - Dropped page tree caching and stepped onto Navigation caching. This helps + scalability a lot. Thanks to Martin Kamerbeek / Procolix + 6.0.3 - Fixed a recursive style change bug. - Bugfix [ 953593 ] perl -MWebGUI -e "" fails diff --git a/docs/upgrades/upgrade_6.0.3-6.1.0.pl b/docs/upgrades/upgrade_6.0.3-6.1.0.pl index b925a85aa..83bf9f495 100644 --- a/docs/upgrades/upgrade_6.0.3-6.1.0.pl +++ b/docs/upgrades/upgrade_6.0.3-6.1.0.pl @@ -75,6 +75,54 @@ foreach my $key (keys %{$langs}) { WebGUI::SQL->write("update page set languageId=".quote($langs->{$key})." where languageId=".$key); } +print "\tConverting page tree to the Nested Set model.\n"; +sub walk_down { + my($pageId, $o) = @_[0,1]; + + my $callback = $o->{callback}; + my $callbackback = $o->{callbackback}; + my $callback_status = 1; + + $callback_status = &{ $callback }( $pageId, $o ) if $callback; + if($callback_status) { + # Keep recursing unless callback returned false... and if there's + # anything to recurse into, of course. + my @daughters = WebGUI::SQL->buildArray("select pageId from page where parentId=$pageId and pageId != 0"); + if(@daughters) { + $o->{'_depth'} += 1; + foreach my $one (@daughters) { + walk_down($one, $o); + } + $o->{'_depth'} -= 1; + } + if($callbackback){ + scalar( &{ $callbackback }( $pageId, $o ) ); + } + } + return; +} + +my $counter = 0; + +WebGUI::SQL->write("alter table page add column (lft int(11))"); +WebGUI::SQL->write("alter table page add column (rgt int(11))"); +WebGUI::SQL->write("alter table page add column (id int(11))"); +WebGUI::SQL->write("alter table page add column (depth int(3))"); +WebGUI::SQL->write("insert into page (pageId, parentId) values (0, -1)"); +WebGUI::SQL->write("update page set id=pageId"); +walk_down(0, { + callback => sub { + WebGUI::SQL->write("update page set depth=".($_[1]->{_depth}-1).", lft=$counter where pageId=".$_[0]); + $counter++; + return 1; + }, + callbackback => sub { + WebGUI::SQL->write("update page set rgt=$counter where pageId=".$_[0]); + $counter++; + return 1; + } +}); + WebGUI::Session::close(); diff --git a/lib/WebGUI/Navigation.pm b/lib/WebGUI/Navigation.pm index 41a7694d5..4f3dd44c6 100644 --- a/lib/WebGUI/Navigation.pm +++ b/lib/WebGUI/Navigation.pm @@ -96,62 +96,43 @@ sub _levels { %levels = ( 'root' => { name => WebGUI::International::get(1,'Navigation'), handler => sub { - return WebGUI::Page->getPage()->root; + return WebGUI::Page->getAnonymousRoot; }, }, 'WebGUIroot' => { name => WebGUI::International::get(2,'Navigation'), handler => sub { - my $p = WebGUI::Page->getPage; - my @ancestors = reverse $p->ancestors; - if(scalar(@ancestors) == 1) { # I am WebGUI root. I have one ancestor, which - return $p; # is nameless root. Return myself - } elsif(scalar(@ancestors) > 1) { # I am a page under WebGUI root. - return $ancestors[1]; # 1st element of ancestors is WebGUI root - } else { - return undef; # huh ? No root ??? - } + return WebGUI::Page->getWebGUIRoot; }, }, 'top' => { name => WebGUI::International::get(3,'Navigation'), handler => sub { - my $p = WebGUI::Page->getPage; - my @ancestors = reverse $p->ancestors; - if(scalar(@ancestors) == 2) { # I am top, my ancestors are nameless root - return $p; # and my WebGUI root. Return myself. - } elsif(scalar(@ancestors) > 2) { # I am a page under top, so return the - return $ancestors[2]; # 2nd element of ancestors is top. - } else { # No top page or I am root. - return ($p->daughters)[0]; # 1st element - } + return WebGUI::Page->getTop; }, }, 'grandmother' => { name => WebGUI::International::get(4,'Navigation'), handler => sub { - my $p = WebGUI::Page->getPage(); - return $p->mother->mother; + return WebGUI::Page->getGrandmother; }, }, 'mother' => { name => WebGUI::International::get(5,'Navigation'), handler => sub { - my $p = WebGUI::Page->getPage(); - return $p->mother; + return WebGUI::Page->getMother; }, }, 'current' => { name => WebGUI::International::get(6,'Navigation'), handler => sub { - return WebGUI::Page->getPage(); + return WebGUI::Page->getPage; }, }, 'daughter' => { name => WebGUI::International::get(7,'Navigation'), handler => sub { - my $p = WebGUI::Page->getPage; - return ($p->daughters)[0]; # 1st daughter + return WebGUI::Page->getFirstDaughter; }, }, ); @@ -241,102 +222,128 @@ sub build { my $var = {'page_loop' => []}; my $p = $self->_getStartPageObject(); my $method = $self->_methods()->{$self->{_method}}{method}; - my @pages = eval $method; - if ($@) { - WebGUI::ErrorHandler::warn("Error in WebGUI::Navigation::build while trying to execute $method".$@); - } - # Store current page properties in template var - my $currentPage = WebGUI::Page->getPage(); - foreach my $property (@interestingPageProperties) { - $var->{'page.current.'.$property} = $currentPage->get($property); - } + my $cache = WebGUI::Cache->new($self->{_identifier}.'-'.$session{page}{pageId}, "Navigation-".$session{config}{configFile}); + my $cacheContent = $cache->get; - if (@pages) { - my $startPageDepth = ($p->ancestors); - my $maxDepth = $startPageDepth + $self->{_depth}; - my $minDepth = $startPageDepth - $self->{_depth}; - - foreach my $page (@pages) { - my $pageData = {}; - - # Initial page info - $pageData->{"page.url"} = WebGUI::URL::gateway($page->get('urlizedTitle')); - $pageData->{"page.absDepth"} = scalar($page->ancestors); - $pageData->{"page.relDepth"} = $pageData->{"page.absDepth"} - $startPageDepth; - $pageData->{"page.isCurrent"} = ($page->get('pageId') == $session{page}{pageId}); - $pageData->{"page.isHidden"} = $page->get('hideFromNavigation'); - $pageData->{"page.isSystem"} = (($page->get('pageId') < 1000 && $page->get('pageId') > 1) || - $page->get('pageId') == 0); - $pageData->{"page.isViewable"} = WebGUI::Page::canView($page->get('pageId')); - - # indent - my $indent = 0; - if ($self->{_method} eq 'pedigree' # reverse traversing - || $self->{_method} eq 'ancestors' # needs another way to calculate - || $self->{_method} eq 'self_and_ancestors') { # the indent - if ($self->{_stopAtLevel} <= $startPageDepth && $self->{_stopAtLevel} > 0) { - $indent = $pageData->{"page.absDepth"} - ($self->{_stopAtLevel} - 1) - 1; - } elsif ($self->{_stopAtLevel} > $startPageDepth && $self->{_stopAtLevel} > 0) { - $indent = 0; - } else { - $indent = $pageData->{"page.absDepth"} - 1; - } - } else { - $indent = $pageData->{"page.absDepth"} - $startPageDepth - 1; - } - $pageData->{"page.indent_loop"} = []; - push(@{$pageData->{"page.indent_loop"}},{'indent'=>$_}) for(1..$indent); - $pageData->{"page.indent"} = "   " x $indent; - - # Check if in depth range - next if ($pageData->{"page.absDepth"} > $maxDepth || $pageData->{"page.absDepth"} < $minDepth); + my @page_loop; - # Check stopAtLevel - next if ($pageData->{"page.absDepth"} < $self->{_stopAtLevel}); + unless (defined $cacheContent) { + # The loop was not cached + my @pages = eval $method; + if ($@) { + WebGUI::ErrorHandler::warn("Error in WebGUI::Navigation::build while trying to execute $method".$@); + } - # Check showSystemPages - next if (! $self->{_showSystemPages} && $pageData->{"page.isSystem"}); + # Store current page properties in template var + my $currentPage = WebGUI::Page->getPage(); + foreach my $property (@interestingPageProperties) { + $var->{'page.current.'.$property} = $currentPage->get($property); + } + + if (@pages) { + my $startPageDepth = ($p->ancestors); + my $maxDepth = $startPageDepth + $self->{_depth}; + my $minDepth = $startPageDepth - $self->{_depth}; + + foreach my $page (@pages) { + my $pageData = {}; + + # Initial page info + $pageData->{"page.url"} = WebGUI::URL::gateway($page->{'urlizedTitle'}); + $pageData->{"page.absDepth"} = $page->{'depth'} + 1; + $pageData->{"page.relDepth"} = $pageData->{"page.absDepth"} - $startPageDepth; + $pageData->{"page.isCurrent"} = ($page->{'pageId'} == $session{page}{pageId}); + $pageData->{"page.isHidden"} = $page->{'hideFromNavigation'}; + $pageData->{"page.isSystem"} = (($page->{'pageId'} < 1000 && $page->{'pageId'} > 1) || + $page->{'pageId'} == 0); + + # indent + my $indent = 0; + if ($self->{_method} eq 'pedigree' # reverse traversing + || $self->{_method} eq 'ancestors' # needs another way to calculate + || $self->{_method} eq 'self_and_ancestors') { # the indent + if ($self->{_stopAtLevel} <= $startPageDepth && $self->{_stopAtLevel} > 0) { + $indent = $pageData->{"page.absDepth"} - ($self->{_stopAtLevel} - 1) - 1; + } elsif ($self->{_stopAtLevel} > $startPageDepth && $self->{_stopAtLevel} > 0) { + $indent = 0; + } else { + $indent = $pageData->{"page.absDepth"} - 1; + } + } else { + $indent = $pageData->{"page.absDepth"} - $startPageDepth - 1; + } + $pageData->{"page.indent_loop"} = []; + push(@{$pageData->{"page.indent_loop"}},{'indent'=>$_}) for(1..$indent); + $pageData->{"page.indent"} = "   " x $indent; + + # Check if in depth range + next if ($pageData->{"page.absDepth"} > $maxDepth || $pageData->{"page.absDepth"} < $minDepth); + + # Check stopAtLevel + next if ($pageData->{"page.absDepth"} < $self->{_stopAtLevel}); + + # Check showSystemPages + next if (! $self->{_showSystemPages} && $pageData->{"page.isSystem"}); - # Check privileges - next if (! $pageData->{"page.isViewable"} && ! $self->{_showUnprivilegedPages}); + # Deal with hidden pages + next if($page->{'hideFromNavigation'} && ! $self->{_showHiddenPages}); - # Deal with hidden pages - next if($page->get('hideFromNavigation') && ! $self->{_showHiddenPages}); + # Put page properties in $pageData hashref + foreach my $property (@interestingPageProperties) { + $pageData->{"page.".$property} = $page->{$property}; + } + $pageData->{"page.isRoot"} = (! $page->{'parentId'}); + $pageData->{"page.isTop"} = ($pageData->{"page.absDepth"} == 2); + $pageData->{"page.hasDaughter"} = ($page->{'rgt'} - $page->{'lft'} > 1); + $pageData->{"page.isMyDaughter"} = ($page->{'parentId'} == + $currentPage->get('pageId')); + $pageData->{"page.isMyMother"} = ($page->{'pageId'} == + $currentPage->get('parentId')); + $pageData->{"page.inCurrentRoot"} = + (($page->{'lft'} > $currentPage->get('lft')) && ($page->{'rgt'} < $currentPage->get('rgt'))) || + (($page->{'lft'} < $currentPage->get('lft')) && ($page->{'rgt'} > $currentPage->get('rgt'))); + # Some information about my mother + if ($page->{parentId} > 0) { + my $mother = WebGUI::Page->getPage($page->{parentId}); + foreach (qw(title urlizedTitle parentId pageId)) { + $pageData->{"page.mother.$_"} = $mother->get($_); + } + } + # Some information about my depth + $pageData->{"page.depthIs".$pageData->{"page.absDepth"}} = 1; + $pageData->{"page.relativeDepthIs".$pageData->{"page.absDepth"}} = 1; - # Put page properties in $pageData hashref - foreach my $property (@interestingPageProperties) { - $pageData->{"page.".$property} = $page->get($property); - } - $pageData->{"page.isRoot"} = (! $page->get('parentId')); - $pageData->{"page.isTop"} = ($pageData->{"page.absDepth"} == 2); - $pageData->{"page.hasDaughter"} = scalar($page->daughters); - $pageData->{"page.isMyDaughter"} = ($page->get('parentId') == - $currentPage->get('pageId')); - $pageData->{"page.isMyMother"} = ($page->get('pageId') == - $currentPage->get('parentId')); - - # Some information about my mother - if(ref($page->mother)) { - foreach (qw(title urlizedTitle parentId pageId)) { - $pageData->{"page.mother.$_"} = $page->mother->get($_); + # Store $pageData in page_loop. Mind the order. + if ($self->{_reverse}) { + unshift(@page_loop, $pageData); + } else { + + push(@page_loop, $pageData); } } - # Some information about my depth - $pageData->{"page.depthIs".$pageData->{"page.absDepth"}} = 1; - $pageData->{"page.relativeDepthIs".$pageData->{"page.absDepth"}} = 1; + } - # Store $pageData in page_loop. Mind the order. - if ($self->{_reverse}) { - unshift(@{$var->{page_loop}}, $pageData); - } else { - push(@{$var->{page_loop}}, $pageData); - } + # We had a cache miss, so let's put the data in cache + $cache->set(\@page_loop, 3600*24); + } else { + # We had a cache hit + @page_loop = @{$cacheContent}; + } + + # Do the user-dependent checks (which cannot be cached globally) + foreach my $pageData (@page_loop) { + $pageData->{"page.isViewable"} = WebGUI::Page::canView($pageData->{'pageId'}); + + # Check privileges + unless (! $pageData->{"page.isViewable"} && ! $self->{_showUnprivilegedPages}) { + push (@{$var->{page_loop}}, $pageData); } } # Configure button $var->{'config.button'} = $self->_getEditButton(); + if ($self->{_template}) { return WebGUI::Template::processRaw($self->{_template}, $var); } else { diff --git a/lib/WebGUI/Operation/Navigation.pm b/lib/WebGUI/Operation/Navigation.pm index adfc28da2..7fee9108d 100644 --- a/lib/WebGUI/Operation/Navigation.pm +++ b/lib/WebGUI/Operation/Navigation.pm @@ -29,6 +29,7 @@ use WebGUI::SQL; use WebGUI::URL; use WebGUI::Utility; use WebGUI::TabForm; +use WebGUI::Cache; our @ISA = qw(Exporter); our @EXPORT = qw(&www_listNavigation &www_editNavigation &www_editNavigationSave &www_copyNavigation @@ -90,6 +91,9 @@ sub www_deleteNavigationConfirm { return WebGUI::Privilege::vitalComponent(); } WebGUI::SQL->write("delete from Navigation where navigationId = $session{form}{navigationId}"); + + # Also delete cache + WebGUI::Cache->new("", "Navigation-".$session{config}{configFile})->deleteByRegex("m/^$session{form}{navigationId}-/"); return www_listNavigation(); } @@ -270,6 +274,9 @@ sub www_editNavigationSave { identifier = ".quote($session{form}{identifier}).", reverse = ".quote($session{form}{'reverse'})." where navigationId = $session{form}{navigationId}"); + # Delete from cache + WebGUI::Cache->new("", "Navigation-".$session{config}{configFile})->deleteByRegex("m/^$session{form}{navigationId}-/"); + return www_listNavigation(); } @@ -336,6 +343,10 @@ sub www_previewNavigation { ) . $nav->build . qq(); + + # Because of the way the system is set up, the preview is cached. So let's remove it again... + WebGUI::Cache->new($nav->{_identifier}."$session{page}{pageId}", "Navigation-".$session{config}{configFile})->delete; + return $output; } diff --git a/lib/WebGUI/Operation/Page.pm b/lib/WebGUI/Operation/Page.pm index 5eb37b3d4..905fba0aa 100644 --- a/lib/WebGUI/Operation/Page.pm +++ b/lib/WebGUI/Operation/Page.pm @@ -81,8 +81,8 @@ sub _recursivelyChangeProperties { _changeWobjectPrivileges($page->get("pageId")) unless $session{form}{wobjectPrivileges}; - $page->walk_down({ - callback => sub { + $page->traversePreOrder( + sub { $currentPage = shift; if (WebGUI::Page::canEdit($currentPage->get('pageId'))) { $currentPage->setWithoutRecache({ @@ -99,9 +99,9 @@ sub _recursivelyChangeProperties { } return 1; } - }); + ); - WebGUI::Page->recachePageTree; + WebGUI::Page->recacheNavigation; } #------------------------------------------------------------------- @@ -194,10 +194,10 @@ sub _traversePageTree { tie %wobject, 'Tie::CPHash'; $spacer = ''; - $page = WebGUI::Page->getPage($top); - $page->walk_down({ - callback => sub { + + $page->traversePreOrder( + sub { ($currentPage, $options) = @_; $currentPageId = $currentPage->get('pageId'); $currentUrlizedTitle = $currentPage->get('urlizedTitle'); @@ -221,9 +221,8 @@ sub _traversePageTree { } $wobjects->finish; } - }, - _depth => $initialDepth || 0 - }); + } + ); return $output; } @@ -614,7 +613,8 @@ Moves page down in the context of it's sisters. =cut sub www_movePageDown { if (WebGUI::Page::canEdit($session{page}{pageId})) { - WebGUI::Page->moveDown($session{page}{pageId}); + my $page = WebGUI::Page->getPage; + $page->moveRight; return ""; } else { return WebGUI::Privilege::insufficient(); @@ -631,7 +631,8 @@ Moves page up in the context of it's sisters. =cut sub www_movePageUp { if (WebGUI::Page::canEdit($session{page}{pageId})) { - WebGUI::Page->moveUp($session{page}{pageId}); + my $page = WebGUI::Page->getPage; + $page->moveLeft; return ""; } else { return WebGUI::Privilege::insufficient(); @@ -647,8 +648,8 @@ Same as www_movePageUp wit this difference that this module returns the www_view =cut sub www_moveTreePageUp { - if (WebGUI::Page::canEdit($session{page}{pageId})) { - WebGUI::Page->moveUp($session{page}{pageId}); + if (WebGUI::Page::canEdit($session{form}{pageId})) { + WebGUI::Page->getPage($session{form}{pageId})->moveLeft; return www_viewPageTree(); } else { return WebGUI::Privilege::insufficient(); @@ -664,8 +665,8 @@ Same as www_movePageDown with this difference that this module returns the www_v =cut sub www_moveTreePageDown { - if (WebGUI::Page::canEdit($session{page}{pageId})) { - WebGUI::Page->moveDown($session{page}{pageId}); + if (WebGUI::Page::canEdit($session{form}{pageId})) { + WebGUI::Page->getPage($session{form}{pageId})->moveRight; return www_viewPageTree(); } else { return WebGUI::Privilege::insufficient(); @@ -682,8 +683,8 @@ Another way to look at is that the mother of the current page becomes the elder =cut sub www_moveTreePageLeft { - if (WebGUI::Page::canEdit($session{page}{pageId})) { - WebGUI::Page->moveLeft($session{page}{pageId}); + if (WebGUI::Page::canEdit($session{form}{pageId})) { + WebGUI::Page->getPage($session{form}{pageId})->moveUp; return www_viewPageTree(); } else { return WebGUI::Privilege::insufficient(); @@ -692,8 +693,8 @@ sub www_moveTreePageLeft { #------------------------------------------------------------------- sub www_moveTreePageRight { - if (WebGUI::Page::canEdit($session{page}{pageId})) { - WebGUI::Page->moveRight($session{page}{pageId}); + if (WebGUI::Page::canEdit($session{form}{pageId})) { + WebGUI::Page->getPage($session{form}{pageId})->moveDown; return www_viewPageTree(); } else { return WebGUI::Privilege::insufficient(); diff --git a/lib/WebGUI/Operation/Trash.pm b/lib/WebGUI/Operation/Trash.pm index 6024c1e37..04d915b99 100644 --- a/lib/WebGUI/Operation/Trash.pm +++ b/lib/WebGUI/Operation/Trash.pm @@ -53,11 +53,11 @@ sub _purgeUserTrash { # Delete pages and all subpages $page = WebGUI::Page->getPage(3); - foreach $currentPage ($page->daughters) { - print "page: ".$currentPage->get('menuTitle')." - UID: ".$currentPage->get('bufferUserId')."\n\n\n
"; + foreach ($page->daughters) { + $currentPage = WebGUI::Page->new($_); if ($currentPage->get('bufferUserId') == $userId) { foreach $currentWobjectPage ($currentPage->self_and_descendants) { - _purgeWobjects($currentWobjectPage->get('pageId')); + _purgeWobjects($currentWobjectPage->{'pageId'}); } $currentPage->purge; } @@ -209,7 +209,7 @@ sub www_deleteTrashItemConfirm { } foreach my $currentPage ($page->self_and_descendants) { - _purgeWobjects($currentPage->get("pageId")); + _purgeWobjects($currentPage->{"pageId"}); } $page->purge; @@ -254,11 +254,12 @@ sub www_emptyTrashConfirm { } if ($allUsers eq "1") { $page = WebGUI::Page->getPage(3); - foreach $currentPage ($page->daughters) { + foreach ($page->daughters) { + $currentPage = WebGUI::Page->new($_); foreach $currentWobjectPage ($currentPage->self_and_descendants) { - _purgeWobjects($currentPage->get("pageId")); + _purgeWobjects($currentWobjectPage->{"pageId"}); } - $page->purge; + $currentPage->purge; } _purgeWobjects(3); WebGUI::ErrorHandler::audit("emptied system trash"); diff --git a/lib/WebGUI/Page.pm b/lib/WebGUI/Page.pm index ecb6d2476..b8d092cdc 100644 --- a/lib/WebGUI/Page.pm +++ b/lib/WebGUI/Page.pm @@ -25,14 +25,14 @@ use WebGUI::HTMLForm; use WebGUI::HTTP; use WebGUI::Icon; use WebGUI::Macro; -use WebGUI::Persistent::Tree; use WebGUI::Session; use WebGUI::SQL; use WebGUI::Style; use WebGUI::Template; use WebGUI::Utility; +use DBIx::Tree::NestedSet; -our @ISA = qw(WebGUI::Persistent::Tree); +our @ISA = qw(DBIx::Tree::NestedSet); =head1 NAME @@ -43,7 +43,7 @@ Package WebGUI::Page This package provides utility functions for WebGUI's page system. Some of these work in a non-object oriented fashion. These are utility functions, not affecting the page tree hiearchy. -The methods that do affect this hiearchy should be called in a object oriented context. +The methods that do affect or report on this hiearchy should be called in a object oriented context. =head1 SYNOPSIS @@ -61,7 +61,7 @@ The methods that do affect this hiearchy should be called in a object oriented c $url = WebGUI::Page::makeUnique($url,$pageId); - OO style methods + Some OO style methods use WebGUI::Page; $page = WebGUI::Page->getPage($pageId); @@ -69,10 +69,10 @@ The methods that do affect this hiearchy should be called in a object oriented c $page->cut; $page->paste($newMother); - $page->set; # this automatically recaches the pagetree + $page->set; # this automatically recaches the pagetree $page->setWithoutRecache; - WebGUI::Page->recachePageTree # here we've got to recache manually + WebGUI::Page->recacheNavigation # here we've got to recache manually =head1 METHODS @@ -81,63 +81,10 @@ These functions are available from this package: =cut #------------------------------------------------------------------- +=head2 add -sub classSettings { - return { - properties => { - # These fields define the place in the pagetree of a page - pageId => { key => 1 }, - parentId => { defaultValue => 0 }, - sequenceNumber => { defaultValue => 1 }, - - # These are the entries that define the privileges and behaviour of a page. - # They're in the same order as they apear in the set-statement in the www_editPageSave method - # of WebGUI::Operation::Page. Please keep them in this order. You will find it to be more - # convient when debugging. - title => { quote => 1 }, - styleId => { defaultValue => 0 }, - printableStyleId => { defaultValue => 0 }, - ownerId => { defaultValue => 0 }, - groupIdView => { defaultValue => 3 }, - groupIdEdit => { defaultValue => 3 }, - newWindow => { defaultValue => 0 }, - wobjectPrivileges => { defaultValue => 0 }, - hideFromNavigation => { defaultValue => 0 }, - startDate => { defaultValue => 946710000 }, - endDate => { defaultValue => 2082783600 }, - cacheTimeout => { defaultValue => 60}, - cacheTimeoutVisitor => { defaultValue => 600}, - metaTags => { quote => 1 }, - urlizedTitle => { quote => 1 }, - redirectURL => { quote => 1 }, - languageId => { defaultValue => "English", quote=>1 }, - defaultMetaTags => { defaultValue => 0 }, - templateId => { defaultValue => 1 }, - menuTitle => { quote => 1 }, - synopsis => { quote => 1 }, - - # The clipboard/trash properties. - bufferUserId => { }, - bufferDate => { }, - bufferPrevId => { }, - - # The userdefined database entries. - userDefined1 => { quote => 1 }, - userDefined2 => { quote => 1 }, - userDefined3 => { quote => 1 }, - userDefined4 => { quote => 1 }, - userDefined5 => { quote => 1 } - }, - useDummyRoot => 1, - table => 'page' - } -} - -#------------------------------------------------------------------- - -=head2 add ( page ) - -Adds page to the children of the object this method is invoked on. +Adds page to the right of the children of the object this method is invoked +on. Returns the new page object. =over @@ -150,25 +97,43 @@ A WebGUI::Page instance to be added to the children of the current object. =cut sub add { - my ($self, $page, @daughters, $newSequenceNumber); - ($self) = @_; - - @daughters = $self->daughters; - $newSequenceNumber = 1; - $newSequenceNumber = (pop(@daughters))[0]->get('sequenceNumber') + 1 if (@daughters); + my ($self, $page, $newPageId); + $self = shift; - $page = WebGUI::Page->new(-properties => { - parentId => $self->get("pageId"), - sequenceNumber => $newSequenceNumber - }); - $self->add_daughter($page); - $page->set; + $newPageId = getNextId('pageId'); + $self->add_child_to_right( + id =>$self->get('pageId'), + pageId =>$newPageId, + parentId=>$self->get('pageId'), + depth =>($self->get('depth') + 1), + ); - return $page; + # Fixup the 'id' column that has the wrong value. + WebGUI::SQL->write("update page set id=pageId where pageId=$newPageId"); + + $self->recacheNavigation; + + return WebGUI::Page->new($newPageId); +} + +#------------------------------------------------------------------- +=head2 ancestors + +Returns an array of hashes containing the properties of the ancestors of the current node. + +=back + +=cut + +sub ancestors { + my ($self); + $self = shift; + return @{$self->get_parents_flat( + id => $self->get('pageId') + )}; } #------------------------------------------------------------------- - =head2 canEdit ( [ pageId ] ) Returns a boolean (0|1) value signifying that the user has the required privileges. @@ -199,6 +164,71 @@ sub canEdit { } } +#------------------------------------------------------------------- +=head2 canMoveDown + +Returns true if the current node can be moved down the tree. Ie. can be made +a child of it's left sister. + +=back + +=cut + +sub canMoveDown { + my ($self) = shift; + return $self->hasLeftSister; +} + +#------------------------------------------------------------------- +=head2 canMoveLeft + +Returns true if the current node can be moved left. Ie. if it can swap places +with it's left sister. + +=back + +=cut + +sub canMoveLeft { + my ($self, $mother); + $self = shift; + $mother = $self->getMother; + + return (($self->get('lft') - $mother->get('lft')) > 1); +} + +#------------------------------------------------------------------- +=head2 canMoveRight + +Returns true if the current node can be moved rightt. Ie. if it can swap places +with it's right sister. + +=back + +=cut + +sub canMoveRight { + my ($self, $mother); + $self = shift; + $mother = $self->getMother; + + return (($mother->get('rgt') - $self->get('rgt')) > 1); +} + +#------------------------------------------------------------------- +=head2 canMoveUp + +Returns true if the current node can be moved up the tree. Ie. if it can be +made a child of it's grandmother. + +=back + +=cut + +sub canMoveUp { + my ($self) = shift; + return ($self->get('depth') > 0); +} #------------------------------------------------------------------- @@ -234,7 +264,6 @@ sub canView { } } - #------------------------------------------------------------------- =head2 countTemplatePositions ( templateId ) @@ -265,7 +294,7 @@ sub countTemplatePositions { =head2 cut -Cuts the this Page object and places it on the clipboard. +Cuts the this page object and places it on the clipboard. =cut @@ -276,7 +305,6 @@ sub cut { # Place page in clipboard (pageId 2) $clipboard = WebGUI::Page->getPage(2); - if ($self->move($clipboard)) { $self->set({ bufferUserId => $session{user}{userId}, @@ -287,7 +315,25 @@ sub cut { return $self; } - + +#------------------------------------------------------------------- +=head2 daughters + +Returns an array of hashes containing the properties of the daughters of the current node. + +=back + +=cut + +sub daughters { + my ($self); + $self = shift; + return @{$self->get_children_flat( + id => $self->get('pageId'), + depth => 1 + )}; +} + #------------------------------------------------------------------- =head2 deCache ( [ pageId ] ) @@ -318,11 +364,6 @@ sub deCache { Deletes this Page object from the tree and places it in the trash. To physically remove pages from the tree and the database you should use the purge method. -This function overloads the delete inherited from WebGUI::Persistent::Tree. This is done -because 'delete' is the right name for the functionality within WebGUI and because the -delete of WG::P::Tree is not very useful: it only works on one page and causes pages with -'zombie' parents. - =cut sub delete { @@ -344,6 +385,23 @@ sub delete { return $self; } +#------------------------------------------------------------------- +=head2 descendants + +Returns an array of hashes containing the properties of the descendants of the current node. + +=back + +=cut + +sub descendants { + my ($self); + $self = shift; + return @{$self->get_children_flat( + id => $self->get('pageId') + )}; +} + #------------------------------------------------------------------- =head2 drawTemplate ( templateId ) @@ -377,7 +435,6 @@ sub drawTemplate { return $template; } - #------------------------------------------------------------------- =head2 generate ( ) @@ -465,6 +522,287 @@ sub generate { return WebGUI::Template::process($session{page}{templateId},"page",\%var); } +#------------------------------------------------------------------- +=head2 generation + +Returns an array of hashes containing the properties of the same generation as the current node. The +current node, being a member of it's own generation, is of course included. A generation consists of +all nodes with the same depth (or level) in the tree. + +=back + +=cut + +sub generation { + my ($self, $sth, %row, @result); + $self = shift; + $sth = WebGUI::SQL->read( + "select a.* + from page as a, + page as b + where a.depth = b.depth and + b.pageId = ".$self->get('pageId'). + " order by lft"); + + while (%row = $sth->hash) { + push(@result, {(%row)}); + } + + return @result; +} + +#------------------------------------------------------------------- +=head2 get( property ) + +Returns the disired page property. + +=over + +=item property + +The name of the property you wanna have + +=back + +=cut + +sub get { + my ($self, $property); + ($self, $property) = @_; + + return $self->{_pageProperties}->{$_[1]}; +} + +#------------------------------------------------------------------- +=head2 getAnonymousRoot + +Returns the 'ueber'-root, the root with pageId 0, the one that holds all WebGUI roots +together, the node that brings the balance back into the force ;) + +Note that this node is only in the database because of design. You cannot put stuff on +it. Well actually you can, but you don't want to. Trust me. Use it to add WebGUI roots +or traverse the whole page tree instead . + +=back + +=cut + +sub getAnonymousRoot { + return WebGUI::Page->new(0); +} + +#------------------------------------------------------------------- +=head2 getFirstDaughter( pageId ) + +Return the first (leftmost) daughter of the current node when called in instance context, +returns the first daughter of 'pageId' when called in class context. + +=over + +=item pageId + +Only required if called in class context. The pageId of the page of which you want the +daughter of. Defaults to the current page. + +=back + +=cut + +sub getFirstDaughter { + my ($self, $pageId, $daughterId); + ($self, $pageId) = @_; + unless (ref($self)) { + $self = WebGUI::Page->new($pageId || $session{page}{pageId}); + } + + $daughterId = ($self->daughters)[0]->{pageId}; + + return WebGUI::Page->new($daughterId); +} + +#------------------------------------------------------------------- +=head2 getGrandMother( pageId ) + +Returns the grandmother of the current node, or, when called in class context, the garndmother +of 'pageId'. + +=over + +=item pageId + +Only required if called in class context. The pageId of the page of which you want the +grandmother of. Defaults to the current page. + +=back + +=cut + +sub getGrandMother { + my ($self, $pageId, $grannyId); + ($self, $pageId) = @_; + unless (ref($self)) { + $self = WebGUI::Page->new($pageId || $session{page}{pageId}); + } + + return undef if ($self->get('depth') < 2); + + # We use self and ancestors here because ancestors strips on the wrong side. + $grannyId = (reverse $self->self_and_ancestors)[2]->{pageId}; + return WebGUI::Page->new($grannyId); +} + +#------------------------------------------------------------------- +=head2 getLeftSister( pageId ) + +Returns the left sister of the current node, or, when called in class context, the left sister +of 'pageId'. + +=over + +=item pageId + +Only required if called in class context. The pageId of the page of which you want the +left sister of. Defaults to the current page. + +=back + +=cut + +sub getLeftSister { + my ($self, $pageId, $leftSisterId); + ($self, $pageId) = @_; + unless (ref($self)) { + $self = WebGUI::Page->new($pageId || $session{page}{pageId}); + } + + ($leftSisterId) = WebGUI::SQL->quickArray("select pageId from page where rgt=".($self->get('lft') - 1)); + return undef unless($leftSisterId); + + return WebGUI::Page->new($leftSisterId); +} + +#------------------------------------------------------------------- +=head2 getMother( pageId ) + +Returns the mother of the current node, or, when called in class context, the left sister +of 'pageId'. + +=over + +=item pageId + +Only required if called in class context. The pageId of the page of which you want the +mother of. Defaults to the current page. + +=back + +=cut + + +sub getMother { + my ($self, $pageId, $mommyId); + ($self, $pageId) = @_; + unless (ref($self)) { + $self = WebGUI::Page->new($pageId || $session{page}{pageId}); + } + + return undef if ($self->get('depth') < 1); + + # We use self and ancestors here because ancestors strips on the wrong side. + $mommyId = (reverse $self->self_and_ancestors)[1]->{pageId}; + return WebGUI::Page->new($mommyId); +} + +#------------------------------------------------------------------- +=head2 getPage( pageId ) + +Returns the page identified by 'pageId'. + +=over + +=item pageId + +The pageId of the page you want. Defaults to the current page. + +=back + +=cut + +sub getPage { + my ($pageId); + $pageId = $session{page}{pageId}; + $pageId = $_[1] if (defined $_[1]); + + return WebGUI::Page->new($pageId); +} + +#------------------------------------------------------------------- +=head2 getRightSister( pageId ) + +Returns the right sister of the current node, or, when called in class context, the right sister +of 'pageId'. + +=over + +=item pageId + +Only required if called in class context. The pageId of the page of which you want the +right sister of. Defaults to the current page. + +=back + +=cut + + +sub getRightSister { + my ($self,$pageId, $rightSisterId); + ($self, $pageId) = @_; + unless (ref($self)) { + $self = WebGUI::Page->new($pageId || $session{page}{pageId}); + } + + ($rightSisterId) = WebGUI::SQL->quickArray("select pageId from page where lft=".($self->get('rgt') + 1)); + return undef unless(defined $rightSisterId); + + return WebGUI::Page->new($rightSisterId); +} + +#------------------------------------------------------------------- +=head2 getTop( pageId ) + +Returns the top page (child of a WebGUI root, depth = 1) of the current node, or, when called in class +context, the top of 'pageId'. + +=over + +=item pageId + +Only required if called in class context. The pageId of the page of which you want the +top page of. Defaults to the current page. + +=back + +=cut + + +sub getTop { + my ($self, $pageId, $topId, @descendants); + ($self, $pageId) = shift; + unless (ref($self)) { + $self= WebGUI::Page->new($pageId || $session{page}{pageId}); + } + + @descendants = $self->descendants; + if (scalar(@descendants) == 2) { + $topId = $self->get('pageId'); #The current page is a top level page + } elsif (scalar(@descendants) > 2) { + $topId = $descendants[2]->{pageId}; + } else { #Either the current page is a root page or there's no top level page. + $topId = ($self->daughters)[0]->{pageId}; + } + + return WebGUI::Page->new($topId); +} #------------------------------------------------------------------- @@ -525,6 +863,86 @@ sub getTemplatePositions { return \%hash; } +#------------------------------------------------------------------- +=head2 getWebGUIRoot( pageId ) + +Returns the WebGUI root (depth = 0) of the current node, or, when called in class +context, the WebGUI root of 'pageId'. + +=over + +=item pageId + +Only required if called in class context. The pageId of the page of which you want the +WebGUI root of. Defaults to the current page. + +=back + +=cut + +sub getWebGUIRoot { + my ($self, $pageId, $node, $rootId, @descendants); + ($self, $pageId) = shift; + unless (ref($self)) { + $self= WebGUI::Page->new($pageId || $session{page}{pageId}); + } + + @descendants = $self->descendants; + if (scalar(@descendants) == 1) { #The current page is a WebGUI root + $rootId = $self->get('pageId'); + } elsif (scalar(@descendants) > 1) { + $rootId = $descendants[1]->{pageId}; + } else { #There's no root, your tree is broken + return undef; + } + + return WebGUI::Page->new($rootId); +} + +#------------------------------------------------------------------- +=head2 hasDaughter + +Returns true if the page has one or more daughters + +=back + +=cut + +sub hasDaughter { + my ($self) = shift; + + return ($self->get('rgt') - $self->get('lft') > 1); +} + +#------------------------------------------------------------------- +=head leaves_under + +Returns an array of hashes containing the properties of all leaves (pages without children) +under the page + +=back + +=cut + +sub leaves_under { + my ($self, $sth, %row, @result); + $self = shift; + $sth = WebGUI::SQL->read( + "select a.* + from page as a, + page as b + where (a.lft between b.lft and b.rgt) and + (a.rgt = a.lft + 1) + b.pageId = ".$self->get('pageId'). + " order by lft"); + + while (%row = $sth->hash) { + push(@result, {(%row)}); + } + + return @result; +} + #------------------------------------------------------------------- =head2 makeUnique ( pageURL, pageId ) @@ -561,57 +979,6 @@ sub makeUnique { #------------------------------------------------------------------- -=head2 getPage ( [pageId] ) - -This method fetches a WebGUI::Page object. If no pageId is given the current page will be used. - -=over - -=item pageId - -The id of the page requested. - -=back - -=cut - -sub getPage { - my ($cache, $pageLookup, $node, $self, $pageId, $tree, $pageInBranche); - ($self, $pageId) = @_; - - unless (defined $pageId) { - $pageId = $session{page}{pageId}; - } - - WebGUI::ErrorHandler::fatalError("Illegal pageId: '$pageId'") unless ($pageId =~ /^-?\d+$/); - - # Fetch the correct pagetree from cache - $cache = WebGUI::Cache->new('root-0','PageTree-'.$session{config}{configFile}); - $tree = $cache->get; - - # If the tree is cached then use it. - if (defined $tree) { - $node = $tree->{$pageId}; - } - # If the tree isn't cached or if the requested page is not in the cached tree, recache. - if (!defined $tree || !$node) { - # Handle cache miss. The way it's done here costs twice the amount of time that's needed to build - # a tree. This shouldn't matter that much, though, since cache-misses should occur almost never. - WebGUI::Page->recachePageTree; - $node = WebGUI::Page->getTree()->{$pageId}; - undef $pageInBranche; - } - - # If there's still no page in node something's really wrong. - if (!$node) { - WebGUI::ErrorHandler::fatalError("Page is not in database! pageId: $pageId"); - } - - return $node; -} - -#------------------------------------------------------------------- - =head2 move( newMother ) Moves a page to another page (ie. makes the page you execute this method on a child of newMother). @@ -627,41 +994,241 @@ The page under which the current page should be moved. This should be an WebGUI: =cut -sub move{ - my ($self, $clipboard, $parentId, $newSequenceNumber, @newSisters, $newMother); +sub move{ + my ($self, $newMother, $parentId, $diff, $diff2, $sql, $depthDiff, $between, $updateRange, $moveNextToMother); ($self, $newMother) = @_; # Avoid cyclic pages. Not doing this will allow people to freeze your computer, by generating infinite loops. - return 0 if (isIn($self->get("pageId"), map {$_->get("pageId")} $newMother->ancestors)); + return 0 if (isIn($self->get('pageId'), map {$_->{pageId}} $newMother->ancestors)); # Make sure a page is not moved to itself. - return 0 if ($self->get("pageId") == $newMother->get("pageId")); + return 0 if ($self->get('pageId') == $newMother->get("pageId")); + # Make sure a page is not moved to it's own mother + return 0 if ($self->get('parentId') == $newMother->get('pageId')); + $parentId = $self->get("parentId"); - # Lower the sequence numbers of the following sisters - foreach ($self->right_sisters) { - $_->setWithoutRecache({ - sequenceNumber => $_->get('sequenceNumber') - 1 - }); + # We move to the right of the children of $newMother. + $depthDiff = $self->get('depth') - $newMother->get('depth') - 1; + + # It is important if the page moves 'up' or 'down' in lft and rgt value + if ($self->get('lft') < $newMother->get('lft')) { + $between = ($self->get('rgt') + 1)." and ".($newMother->get('rgt') - 1); + $updateRange = $self->get('lft')." and ".$newMother->get('rgt'); + $diff = $self->get('rgt') - $self->get('lft') + 1; + $diff2 = $newMother->get('rgt') - $self->get('rgt') - 1; + } else { + $between = $newMother->get('rgt')." and ".($self->get('lft') - 1); + $updateRange = $newMother->get('lft')." and ".($self->get('rgt')+1); + $diff = $self->get('lft') - $self->get('rgt') - 1; + $diff2 = $newMother->get('rgt') - $self->get('lft'); } - - # Derive new sequenceNumber - @newSisters = $newMother->daughters; - $newSequenceNumber = 1; - $newSequenceNumber = $newSisters[scalar(@newSisters)-1]->get('sequenceNumber') +1 if (@newSisters); - # Do the move - $self->unlink_from_mother; - $newMother->add_daughter($self); - $self->set({ - parentId => $newMother->get("pageId"), - sequenceNumber => $newSequenceNumber, - }); + + # Set the new depth + WebGUI::SQL->write("update page set depth=depth - $depthDiff where lft between ".$self->get('lft')." and ".$self->get('rgt')); + + # Do the magic: cast move on tree + $sql = " + update page set + lft = case + when lft between ". $self->get('lft')." and ".$self->get('rgt')." + then lft + $diff2 + when lft between ". $between ." + then lft - $diff + else + lft + end, + rgt = case + when rgt between ". $self->get('lft') ." and ". $self->get('rgt') ." + then rgt + $diff2 + when rgt between ". $between ." + then rgt - $diff + else + rgt + end + where + rgt between $updateRange"; + WebGUI::SQL->write($sql); + # Set the parentId to the right node. + WebGUI::SQL->write("update page set parentId=".$newMother->get('pageId')." where pageId=".$self->get('pageId')); + + WebGUI::Page->recacheNavigation; + return 1; } +#------------------------------------------------------------------- +=head2 moveDown + +Moves the page down the tree. Ie. makes the page a daughter of it's left sister. + +=back + +=cut + +sub moveDown { + my ($self, $leftSister); + $self = shift; + + $leftSister = $self->getLeftSister; + return 0 unless (defined $leftSister); + + $self->move($leftSister); + return 1; +} + +#------------------------------------------------------------------- +=head2 moveLeft + +Move the page to the left. Ie. swaps places with it's left sister. + +=back + +=cut + +sub moveLeft { + my ($self, $leftSister); + $self = shift; + + $leftSister = $self->getLeftSister; + return 0 unless (defined $leftSister); + + $self->swap_nodes( + first_id => $self->get('pageId'), + second_id => $leftSister->get('pageId') + ); + + WebGUI::Page->recacheNavigation; + return 1; +} + +#------------------------------------------------------------------- +=head2 moveRight + +Move the page to the right. Ie. swaps places with it's right sister. + +=back + +=cut + +sub moveRight { + my ($self, $rightSister); + $self = shift; + + $rightSister = $self->getRightSister; + return 0 unless (defined $rightSister); + + $self->swap_nodes( + first_id => $self->get('pageId'), + second_id => $rightSister->get('pageId') + ); + + WebGUI::Page->recacheNavigation; + return 1; +} + +#------------------------------------------------------------------- +=head2 moveUp + +Moves the page up the tree. Ie. makes the page the right sister of it's mother. + +=back + +=cut + +sub moveUp { + my ($self, $mother, $diff, $diff2, $sql); + $self = shift; + + $mother = $self->getMother; + return 0 unless (defined $mother); + + # Update depth, we do this before the move because now we know the rgt range of nodes + # that change in depth. + WebGUI::SQL->write("update page set depth=depth-1 where rgt between ".$self->get('lft')." and ".$self->get('rgt')); + + # Do some movement magic! + $diff = $self->get('rgt') - $self->get('lft') + 1; + $diff2 = $mother->get('rgt') - $self->get('rgt'); + $sql = " + update page set + lft = case + when lft between ". $self->get('lft')." and ".$self->get('rgt')." + then lft + $diff2 + when lft between ". ($self->get('rgt') + 1) ." and ". $mother->get('rgt') ." + then lft - $diff + else + lft + end, + rgt = case + when rgt between ". $self->get('lft') ." and ". $self->get('rgt') ." + then rgt + $diff2 + when rgt between ". ($self->get('rgt') + 1) ." and ". $mother->get('rgt') ." + then rgt - $diff + else + rgt + end, + parentId = case pageId + when ". $self->get('pageId') ." + then ".$mother->get('parentId')." + else + parentId + end + where + rgt between ". $self->get('lft') ." and ". $mother->get('rgt'); + WebGUI::SQL->write($sql); + + WebGUI::Page->recacheNavigation; + + return 1; +} + +#------------------------------------------------------------------- +=head2 new ( pageId || { properties } ) + +Creates a new page object. You can't create pages in the database with this, though. Use add instead. + +If called without arguments it' fetches the current page (the one in $session{page}{pageId}) from the database +and returns an WebGUI::Page object of it. + +You can pass one argument. This can be either a pageId of another page than the current you want, or a hashref +containing page properties. You can use the latter if you already have page properties (returned by ancestors or +something like it for example), and save a (redundant) database query. You can of course also use it to fool the +system with dummy pages and do all kinds of magic that I can't imagine with it. + +=over + +=item pageId || { properties } + +You can pass either a pageId or a properties hashref. See above for an explanation + +=back + +=cut + +sub new { + my ($class, $self, $properties); + ($class, $properties) = @_; + $self = $_[0]->SUPER::new( + table_name => 'page', + left_column_name => 'lft', + right_column_name => 'rgt', + dbh => $session{dbh}, + no_alter_table => 1, + no_locking => 1 + ); + unless (ref($properties)) { + $properties = WebGUI::SQL->quickHashRef("select * from page where pageId=$_[1]"); + } + + return undef unless (defined $properties->{pageId}); + $self->{_pageProperties} = $properties; + return $self; +} + #------------------------------------------------------------------- =head2 paste( newMother ) @@ -681,6 +1248,8 @@ The page under which the current page should be pasted. This should be an WebGUI sub paste{ my ($self, $newMother); ($self, $newMother) = @_; + + # You do not want to paste a page onto itself, believe me. return $self if ($self->get("pageId") == $newMother->get("pageId")); return WebGUI::ErrorHandler::fatalError("You cannot paste a page that's not on the clipboard. parentId:". $self->get("parentId").", pageId:".$self->get("pageId")) unless ($self->get("parentId") == 2); @@ -697,77 +1266,203 @@ sub paste{ return $self; } +#------------------------------------------------------------------- +=head2 pedigree + +Ok, this does something funky. It returns an array of hashes containing page properties of the mothers of +the page and their daughter, the page itself and it's daugthers. It used for the flexmenu. + +=back + +=cut + +sub pedigree { + my ($self, $leftSisters, $currentPage, $rightSisters, @flexMenu, $node); + $self = shift; + + ($leftSisters, $currentPage, $rightSisters) = $self->self_and_sisters_splitted; + @flexMenu = (@{$leftSisters}, {%{$currentPage}}, $self->daughters, @{$rightSisters}); + while (defined($self=$self->getMother) && ref($self)) { + ($leftSisters, $currentPage, $rightSisters) = $self->self_and_sisters_splitted; + @flexMenu = (@{$leftSisters}, {%{$currentPage}}, @flexMenu, @{$rightSisters}); + } + return @flexMenu; +} + #------------------------------------------------------------------- =head2 purge -This purges this object from the tree and the database. +This purges this object and all it's children from the tree and the database. =cut sub purge { - my ($self, $currentPage, @pagesToPurge); + my ($self); $self = shift; - - # Never ever modify the tree hierarchy in a walk_down... - $self->walk_down({ - callback => sub { - $currentPage = shift; - push(@pagesToPurge, $currentPage); - } - }); - # ... do it afterwards. - foreach $currentPage (@pagesToPurge) { - $currentPage->SUPER::delete; - } + $self->delete_self_and_children( + id => $self->get('pageId') + ); + + WebGUI::Page->recacheNavigation; return ""; } #------------------------------------------------------------------- +=head2 recacheNavigation -=head2 recachePageTree +Actually this doesn't recache anything, but it might be in the future. Hence the name. Currently +it purges all Navigation cache objects. You should call it if you changed the pagetree. Note that +the methods in this module that modify the tree already call this. -This method fetches all pageroots from the database, builds their underlying trees and caches them. Additionally -this method creates a lookup table that connects a node to a root. This is done to avoid searching through all -page tree to find a specific node. +If you only change some navigation properties of a navigation element, you should use a more restricted +cache purge. -It should only be nescesarry to call this method when setWithoutRecache is used. +=over =cut -sub recachePageTree { - my ($forrest, @pageRoots, $currentTree, $serializer, $node, $serialized, $cache, %pageLookup); - - # Purge the cached page trees. - $cache = WebGUI::Cache->new("", "PageTree-".$session{config}{configFile}); - $cache->deleteByRegex(".*"); +sub recacheNavigation { + WebGUI::Cache->new("", "Navigation-".$session{config}{configFile})->deleteByRegex(".*"); - # Fetch the complete forrest, which is actually all the pagetrees connected by a dummy root. - $forrest = WebGUI::Page->getTree(); - - # Cache complete forrest. - $cache = WebGUI::Cache->new('root-0','PageTree-'.$session{config}{configFile}); - $cache->set($forrest); - return ""; } + +#------------------------------------------------------------------- +=head2 self_and_ancestors + +Returns an array of hashrefs containing the page properties of this node and it's ancestors. + +=back + +=cut + +sub self_and_ancestors { + my ($self); + $self = shift; + return @{$self->get_self_and_parents_flat( + id => $self->get('pageId') + )}; +} + +#------------------------------------------------------------------- +=head2 self_and_descendants + +Returns an array of hashrefs containing the page properties of this node and it's descendants. + +=back + +=cut + + +sub self_and_descendants { + my ($self); + $self = shift; + return @{$self->get_self_and_children_flat( + id => $self->get('pageId') + )}; +} + +#------------------------------------------------------------------- +=head2 self_and_sisters + +Returns an array of hashrefs containing the page properties of this node and it's sisters. + +=back + +=cut + +sub self_and_sisters { + my ($self, $sth, %row, @result); + $self = shift; + $sth = WebGUI::SQL->read( + "select a.* + from page as a, + page as b + where a.parentId = b.parentId and + b.pageId = ".$self->get('pageId'). + " order by lft"); + while (%row = $sth->hash) { + push(@result, {(%row)}); + } + + return @result; +} + +#------------------------------------------------------------------- +=head2 self_and_sisters_splitted + +Returns an array with the following contents: + + - [ leftSisters ] an arrayref of hashref containing the properties of the left sisters of the page. + - $currentPage an hashref containing the page properties of this node + - [ rightsister ] an arrayref of hashref containing the properties of the right sisters of the page. + +=back + +=cut + +sub self_and_sisters_splitted { + my ($self, $haveAllLeftSisters, $currentPage, @leftSisters, @rightSisters); + $self = shift; + + $haveAllLeftSisters = 0; + foreach ($self->self_and_sisters) { + if ($_->{pageId} == $self->get('pageId')) { + $currentPage = $_; + $haveAllLeftSisters = 1; + } elsif ($haveAllLeftSisters) { + push (@rightSisters, $_); + } else { + push (@leftSisters, $_); + } + } + + return (\@leftSisters, $currentPage, \@rightSisters); +} + +#------------------------------------------------------------------- +=head2 sisters + +Returns an array of hashrefs containing the page properties of this nodes sisters. The node not included. + +=back + +=cut + +sub sisters { + my ($self, $sth, %row, @result); + $self = shift; + $sth = WebGUI::SQL->read( + "select a.* + from page as a, + page as b + where a.pageId !=".$self->get('pageId')." and + a.parentId = b.parentId and b.pageId = ".$self->get('pageId'). + " order by lft"); + while (%row = $sth->hash) { + push(@result, {(%row)}); + } + + return @result; +} + #------------------------------------------------------------------- -=head2 set ( [ data ] ) +=head2 set ( { properties } ) If data is given, invoking this method will set the object to the state given in data. If called without any arguments the state of the tree is saved to the database. -A thing to note here is that the cached version of the pagetree is always refreshed after saving to the database. Refreshing -the cache is pretty expensive, so if you have to do many sets in a row, like when recursively changing style of a (sub)tree, -it would be best to use setWithoutRecache, and call recachePageTree afterwards. +This method purges the Navigation cache. Note that if you have to save a lot of properties in row, it's better to use +setWithoutRecache, and call recacheNavigation manually. This saves some time. =over -=item data +=item properties The properties you want to set. This parameter is optional and should be a hashref of the form {propertyA => valueA, propertyB => valueB, etc...} @@ -776,27 +1471,25 @@ The properties you want to set. This parameter is optional and should be a hashr =cut sub set { - my $self = shift; - my $output = $self->SUPER::set(@_); + my ($self, $properties); + ($self, $properties) = @_; + + $self->setWithoutRecache($properties); + WebGUI::Page->recacheNavigation; - # The very only reason we overload the set method is to make sure that the cache also gets - # updated. So let's do just that ;). This might be overkill for some situations where only - # one tree needs to be updated, but doing all of them, sure is a lot safer. (and easier!) - recachePageTree(); - - return $output; + return ""; } #------------------------------------------------------------------- -=head2 setWithoutRecache ([data]) +=head2 setWithoutRecache ( { properties } ) See set. The only difference with set is that the cached version of the pagetree is not updated. This means that you must update it manually by invoking recachePageTree afterwards. =over -=item data +=item properties The properties you want to set. This parameter is optional and should be a hashref of the form {propertyA => valueA, propertyB => valueB, etc...} @@ -805,8 +1498,50 @@ The properties you want to set. This parameter is optional and should be a hashr =cut sub setWithoutRecache { - my $self = shift; - return $self->SUPER::set(@_); + my ($self, $properties); + ($self, $properties) = @_; + + $properties = $self->{_properties} unless ($properties); + + if (scalar(keys(%{$properties}))) { + WebGUI::SQL->write("update page set ".join(', ', map {"$_=".quote($properties->{$_})} keys %{$properties})." where pageId=".$self->get('pageId')); + } + + return ""; +} + +#------------------------------------------------------------------- +=head2 traversePreOrder ( &mappingFunction ) + +Traverses the tree from this node down in pre-order fashion and excutes (maps) the mapping function +onto each node. Also maps onto this node except if it is the anonymous root. This has some but very limited +compatibility with the callback property of the walk_down method of Tree::DAG_Node. + +=over + +=item mappingFunction + +This should be a coderef pointing to your mapping function. The arguments that are passed to this function are +a page object and a hashref containing only _depth for now. + +=back + +=cut + +sub traversePreOrder { + my ($self, $mappingFunction, $initialDepth, $page, @pages); + ($self, $mappingFunction, $initialDepth) = @_; + + @pages = $self->self_and_descendants; + # The 'ueber'-root contains no data so we do not want to return i! + shift @pages if ($pages[0]->{'pageId'} == 0); + + foreach (@pages) { + $page = WebGUI::Page->new($_->{'pageId'}); + &$mappingFunction($page, {_depth=>$page->get('depth')}); + } + + return @pages; } 1;