Adding new cached pagetree stuff.

This commit is contained in:
Martin Kamerbeek 2004-02-03 20:49:53 +00:00
parent 019c28e63e
commit 97dfbcbb01
2 changed files with 439 additions and 115 deletions

View file

@ -45,38 +45,36 @@ sub _changeWobjectPrivileges {
}
}
}
#-------------------------------------------------------------------
sub _recursivelyChangePrivileges {
my ($sth, $pageId);
$sth = WebGUI::SQL->read("select pageId from page where parentId=$_[0]");
_changeWobjectPrivileges($_[0]) unless $session{form}{wobjectPrivileges};
while (($pageId) = $sth->array) {
if (WebGUI::Privilege::canEditPage($pageId)) {
WebGUI::SQL->write("update page set startDate=".WebGUI::FormProcessor::dateTime("startDate").",
endDate=".WebGUI::FormProcessor::dateTime("endDate").",
ownerId=$session{form}{ownerId}, groupIdView=$session{form}{groupIdView},
groupIdEdit=$session{form}{groupIdEdit}, wobjectPrivileges=$session{form}{wobjectPrivileges} where pageId=$pageId");
_recursivelyChangePrivileges($pageId);
}
}
$sth->finish;
}
#-------------------------------------------------------------------
sub _recursivelyChangeStyle {
my ($sth, $pageId);
$sth = WebGUI::SQL->read("select pageId from page where parentId=$_[0]");
while (($pageId) = $sth->array) {
if (WebGUI::Privilege::canEditPage($pageId)) {
WebGUI::SQL->write("update page set styleId=$session{form}{styleId}, printableStyleId=$session{form}{printableStyleId} where pageId=$pageId");
_recursivelyChangeStyle($pageId);
# This combines _recusivelyChangePrivileges and _recusivelyChangeStyle, since there's no use in walking down a tree twice.
sub _recursivelyChangeProperties {
my $page = shift;
_changeWobjectPrivileges($page->get("pageId")) unless $session{form}{wobjectPrivileges};
$page->walk_down({
callback => sub {
if (WebGUI::Privilege::canEditPage($_[0]->get('pageId'))) {
$_[0]->setWithoutRecache({
startDate => WebGUI::FormProcessor::dateTime("startDate"),
endDate => WebGUI::FormProcessor::dateTime("endDate"),
ownerId => $session{form}{ownerId},
groupIdView => $session{form}{groupIdView},
groupIdEdit => $session{form}{groupIdEdit}
}) if ($session{form}{recursePrivs});
$_[0]->setWithoutRecache({
styleId => $session{form}{styleId}
}) if ($session{form}{recurseStyle});
}
return 1;
}
}
$sth->finish;
});
WebGUI::Page->recachePageTree;
}
#-------------------------------------------------------------------
-------------------------------------------------------------------
sub _reorderPages {
my ($sth, $i, $pid);
$sth = WebGUI::SQL->read("select pageId from page where parentId=$_[0] order by sequenceNumber");
@ -192,16 +190,13 @@ sub _traversePageTree {
#-------------------------------------------------------------------
sub www_cutPage {
my ($page);
if ($session{page}{pageId} < 26 && $session{page}{pageId} >= 0) {
return WebGUI::Privilege::vitalComponent();
} elsif (WebGUI::Privilege::canEditPage()) {
WebGUI::SQL->write("update page set parentId=2, "
."bufferUserId=".$session{user}{userId}.", "
."bufferDate=".time().", "
."bufferPrevId=".$session{page}{parentId}." "
."where pageId=".$session{page}{pageId});
_reorderPages($session{page}{parentId});
WebGUI::Session::refreshPageInfo($session{page}{parentId});
$page = WebGUI::Page->getPage($session{page}{pageId});
$page->cut;
return "";
} else {
return WebGUI::Privilege::insufficient();
@ -484,51 +479,54 @@ sub www_editPage {
#-------------------------------------------------------------------
sub www_editPageSave {
my ($nextSeq, $pageId);
my ($pageId, $currentPage, $page);
if ($session{form}{pageId} eq "new") {
$pageId = $session{form}{parentId};
} else {
$page = WebGUI::Page->getPage($session{form}{pageId});
$pageId = $session{form}{pageId};
}
return WebGUI::Privilege::insufficient() unless (WebGUI::Privilege::canEditPage($pageId));
return WebGUI::Privilege::insufficient() unless (WebGUI::Privilege::canEditPage($pageId));
if ($session{form}{pageId} eq "new") {
($nextSeq) = WebGUI::SQL->quickArray("select max(sequenceNumber) from page where parentId=$session{form}{parentId}");
$nextSeq++;
$session{form}{pageId} = getNextId("pageId");
WebGUI::SQL->write("insert into page (pageId,sequenceNumber,parentId)
values ($session{form}{pageId},$nextSeq,$session{form}{parentId})");
$currentPage = WebGUI::Page->getPage($session{page}{pageId});
$page = $currentPage->add;
}
$session{form}{title} = "no title" if ($session{form}{title} eq "");
$session{form}{menuTitle} = $session{form}{title} if ($session{form}{menuTitle} eq "");
$session{form}{urlizedTitle} = $session{form}{menuTitle} if ($session{form}{urlizedTitle} eq "");
$session{form}{urlizedTitle} = WebGUI::Page::makeUnique(WebGUI::URL::urlize($session{form}{urlizedTitle}),$session{form}{pageId});
WebGUI::SQL->write("update page set
title=".quote($session{form}{title}).",
styleId=$session{form}{styleId},
printableStyleId=$session{form}{printableStyleId},
ownerId=$session{form}{ownerId},
groupIdView=$session{form}{groupIdView},
groupIdEdit=$session{form}{groupIdEdit},
newWindow=$session{form}{newWindow},
wobjectPrivileges=$session{form}{wobjectPrivileges},
hideFromNavigation=$session{form}{hideFromNavigation},
startDate=".WebGUI::FormProcessor::dateTime("startDate").",
endDate=".WebGUI::FormProcessor::dateTime("endDate").",
cacheTimeout=".WebGUI::FormProcessor::interval("cacheTimeout").",
cacheTimeoutVisitor=".WebGUI::FormProcessor::interval("cacheTimeoutVisitor").",
metaTags=".quote($session{form}{metaTags}).",
urlizedTitle='$session{form}{urlizedTitle}',
redirectURL='$session{form}{redirectURL}',
languageId='$session{form}{languageId}',
defaultMetaTags='$session{form}{defaultMetaTags}',
templateId='$session{form}{templateId}',
menuTitle=".quote($session{form}{menuTitle}).",
synopsis=".quote($session{form}{synopsis})."
where pageId=$session{form}{pageId}");
WebGUI::SQL->write("update wobject set templatePosition=1 where pageId=$session{form}{pageId}
and templatePosition>".WebGUI::Page::countTemplatePositions($session{form}{templateId}));
_recursivelyChangeStyle($session{form}{pageId}) if ($session{form}{recurseStyle});
_recursivelyChangePrivileges($session{form}{pageId}) if ($session{form}{recursePrivs});
$page->set({
title => $session{form}{title},
styleId => $session{form}{styleId},
printableStyleId => $session{form}{printableStyleId},
ownerId => $session{form}{ownerId},
groupIdView => $session{form}{groupIdView},
groupIdEdit => $session{form}{groupIdEdit},
newWindow => $session{form}{newWindow},
wobjectPrivileges => $session{form}{wobjectPrivileges},
hideFromNavigation => $session{form}{hideFromNavigation},
startDate => WebGUI::FormProcessor::dateTime("startDate"),
endDate => WebGUI::FormProcessor::dateTime("endDate"),
cacheTimeout => WebGUI::FormProcessor::interval("cacheTimeout"),
cacheTimeoutVisitor => WebGUI::FormProcessor::interval("cacheTimeoutVisitor"),
metaTags => $session{form}{metaTags},
urlizedTitle => $session{form}{urlizedTitle},
redirectURL => $session{form}{redirectURL},
languageId => $session{form}{languageId},
defaultMetaTags => $session{form}{defaultMetaTags},
templateId => $session{form}{templateId},
menuTitle => $session{form}{menuTitle},
synopsis => $session{form}{synopsis}
});
unless ($session{form}{pageId} == 'new') {
WebGUI::SQL->write("update wobject set templatePosition=1 where pageId=$session{form}{pageId}
and templatePosition>".WebGUI::Page::countTemplatePositions($session{form}{templateId}));
}
_recursivelyChangeProperties($page) if ($session{form}{recursePrivs} || $session{form}{recurseStyle});
if ($session{form}{proceed} eq "gotoNewPage") {
WebGUI::Session::refreshPageInfo($session{form}{pageId});
} elsif ($session{form}{pageId} == $session{page}{pageId}) {
@ -599,15 +597,11 @@ sub www_moveTreePageRight {
#-------------------------------------------------------------------
sub www_pastePage {
return "" if ($session{page}{pageId} == $session{form}{pageId}); # don't let it paste to itself
my ($output, $nextSeq);
($nextSeq) = WebGUI::SQL->quickArray("select max(sequenceNumber) from page where parentId=$session{page}{pageId}");
$nextSeq += 1;
my ($currentPage, $pageToPaste);
if (WebGUI::Privilege::canEditPage()) {
WebGUI::SQL->write("update page set parentId=$session{page}{pageId}, sequenceNumber='$nextSeq', "
."bufferUserId=NULL, bufferDate=NULL, bufferPrevId=NULL "
."where pageId=$session{form}{pageId}");
_reorderPages($session{page}{pageId});
$currentPage = WebGUI::Page->getPage($session{page}{pageId});
$pageToPaste = WebGUI::Page->getPage($session{form}{pageId});
$pageToPaste->paste($currentPage);
return "";
} else {
return WebGUI::Privilege::insufficient();
@ -624,4 +618,3 @@ sub www_viewPageTree {
}
1;

View file

@ -26,6 +26,8 @@ use WebGUI::Session;
use WebGUI::SQL;
use WebGUI::Template;
use WebGUI::Utility;
use WebGUI::DateTime;
use Data::Serializer;
our @ISA = qw(WebGUI::Persistent::Tree);
@ -35,10 +37,15 @@ Package WebGUI::Page
=head1 DESCRIPTION
This package provides utility functions for WebGUI's page system.
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.
=head1 SYNOPSIS
Non OO functions
use WebGUI::Page;
$integer = WebGUI::Page::countTemplatePositions($templateId);
$html = WebGUI::Page::drawTemplate($templateId);
@ -48,6 +55,20 @@ This package provides utility functions for WebGUI's page system.
$hashRef = WebGUI::Page::getTemplatePositions($templateId);
$url = WebGUI::Page::makeUnique($url,$pageId);
OO style methods
use WebGUI::Page;
$page = WebGUI::Page->getPage($pageId);
$newMother = WebGUI::Page->getPage($anotherPageId);
$page->cut;
$page->paste($newMother);
$page->set; # this automatically recaches the pagetree
$page->setWithoutRecache;
WebGUI::Page->recachePageTree # here we've got to recache manually
=head1 METHODS
These functions are available from this package:
@ -57,36 +78,82 @@ These functions are available from this package:
#-------------------------------------------------------------------
sub classSettings {
return {
properties => {
pageId => { key => 1 },
parentId => { defaultValue => 0 },
title => { quote => 1 },
styleId => { defaultValue => 0 },
ownerId => { defaultValue => 0 },
sequenceNumber => { defaultValue => 1 },
metaTags => { quote => 1 },
urlizedTitle => { quote => 1 },
defaultMetaTags => { defaultValue => 0 },
menuTitle => { quote => 1 },
synopsis => { quote => 1 },
templateId => { defaultValue => 1 },
startDate => { defaultValue => 946710000 },
endDate => { defaultValue => 2082783600 },
redirectURL => { quote => 1 },
userDefined1 => { quote => 1 },
userDefined2 => { quote => 1 },
userDefined3 => { quote => 1 },
userDefined4 => { quote => 1 },
userDefined5 => { quote => 1 },
languageId => { defaultValue => 1 },
groupIdView => { defaultValue => 3 },
groupIdEdit => { defaultValue => 3 },
hideFromNavigation => { defaultValue => 0 },
},
useDummyRoot => 1,
table => 'page'
}
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 => 1 },
defaultMetaTags => { defaultValue => 0 },
templateId => { defaultValue => 1 },
menuTitle => { quote => 1 },
synopsis => { quote => 1 },
# 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.
=over
=item page
A WebGUI::Page instance to be added to the children of the current object.
=back
=cut
sub add {
my ($self, $page, @daughters, $newSequenceNumber);
($self) = @_;
@daughters = $self->daughters;
$newSequenceNumber = 1;
$newSequenceNumber = (pop(@daughters))[0]->get('sequenceNumber') + 1 if (@daughters);
$page = WebGUI::Page->new(-properties => {
parentId => $self->get("pageId"),
sequenceNumber => $newSequenceNumber
});
$self->add_daughter($page);
$page->set;
return $page;
}
#-------------------------------------------------------------------
@ -116,10 +183,38 @@ sub countTemplatePositions {
}
#-------------------------------------------------------------------
=head2 cut
Cuts the this Page object and places it on the clipboard.
=back
=cut
sub cut {
my ($self, $clipboard, $parentId);
$self = shift;
$parentId = $self->get("parentId");
# Place page in clipboard (pageId 2)
$clipboard = WebGUI::Page->getPage(2);
$self->get("pageId");
if ($self->move($clipboard)) {
$self->set({
bufferUserId => $session{user}{userId},
bufferDate => time,
bufferPrevId => $parentId,
});
}
return $self;
}
#-------------------------------------------------------------------
=head2 deCache ( [ pageId ] )
Deletes the cached version of a specified page.
Deletes the cached version of a specified page. Note that this is something else than the
cached page tree. This funtion should be invoked in a non-OO context;
=over
@ -138,7 +233,6 @@ sub deCache {
}
#-------------------------------------------------------------------
=head2 drawTemplate ( templateId )
Returns an HTML string containing a small representation of the page template.
@ -243,7 +337,6 @@ sub generate {
#-------------------------------------------------------------------
=head2 getTemplateList
Returns a hash reference containing template ids and template titles for all the page templates available in the system.
@ -255,7 +348,6 @@ sub getTemplateList {
}
#-------------------------------------------------------------------
=head2 getTemplate ( [ templateId ] )
Returns an HTML template.
@ -276,7 +368,6 @@ sub getTemplate {
}
#-------------------------------------------------------------------
=head2 getTemplatePositions ( templateId )
Returns a hash reference containing the positions available in the specified page template.
@ -301,7 +392,6 @@ sub getTemplatePositions {
}
#-------------------------------------------------------------------
=head2 makeUnique ( pageURL, pageId )
Returns a unique page URL.
@ -334,5 +424,246 @@ sub makeUnique {
return $url;
}
1;
#-------------------------------------------------------------------
=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 ($serializer, $cache, $pageLookup, $node, $self, $class, $pageId, $tree);
($class, $pageId) = @_;
$pageId ||= $session{page}{pageId};
WebGUI::ErrorHandler::fatalError("Illegal pageId: '$pageId'") unless ($pageId =~ /^-?\d+$/);
# Only fetch from cache if cache is enabled in the config file
if ($session{config}{usePageCache}) {
# Fetch the correct pagetree from cache
$cache = WebGUI::Cache->new('pageLookup', 'PageTree-'.$session{config}{configFile});
$pageLookup = $cache->getDataStructure;
$cache = WebGUI::Cache->new('root-'.$pageLookup->{$pageId},'PageTree-'.$session{config}{configFile});
$tree = $cache->getDataStructure;
unless (defined $tree) {
#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;
$tree = WebGUI::Page->getTree()->{0};
}
# No caching
} else {
# Do it the diehard way. Just build a complete tree from the database. This will most definately work, but
# not too fast. A more elegant aproach would be overlaoding every Tree::DAG_Node method WebGUI::Page inherits
# and dynamically load the data from the db.
$tree = WebGUI::Page->getTree()->{0};
# Caching the tree in the session hash might be a good idea, to reduce tree building to only once per session.
# $session{page}{tree} = $tree;
}
# Select the correct node from the tree
$tree->walk_down({
callback => sub {
if ($_[0]->get('pageId') == $pageId) {
$node = $_[0];
return 0;
}
return 1;
}});
return $node;
}
#-------------------------------------------------------------------
=head2 move( newMother )
Moves a page to another page (ie. makes the page you execute this method on a child of newMother).
Returns 1 if the move was succesfull, 0 otherwise.
=over
=item newMother
The page under which the current page should be moved. This should be an WebGUI::Page object.
=back
=cut
sub move{
my ($self, $clipboard, $parentId, $newSequenceNumber, @newSisters, $newMother);
$self = shift;
$newMother = shift;
# 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));
# Make sure a page is not moved to itself.
return 0 if ($self->get("pageId") == $newMother->get("pageId"));
$parentId = $self->get("parentId");
# Lower the sequence numbers of the following sisters
foreach ($self->right_sisters) {
$_->setWithoutRecache({
sequenceNumber => $_->get('sequenceNumber') - 1
});
}
# 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,
});
return 1;
}
#-------------------------------------------------------------------
=head2 paste( newMother )
Pastes a page under newMother.
=over
=item newMother
The page under which the current page should be pasted. This should be an WebGUI::Page object.
=back
=cut
sub paste{
my ($self, $newMother);
$self = shift;
$newMother = shift;
return $self if ($self->get("pageId") == $newMother->get("pageId"));
return WebGUI::ErrorHandler::fatalError("You cannot paste a page that's not on the clipboard.") unless ($self->get("parentId") == 2);
# Place page in clipboard (pageId 2)
if ($self->move($newMother)) {
$self->set({
bufferUserId => 'NULL',
bufferDate => 'NULL',
bufferPrevId => 'NULL'
});
}
return $self;
}
#-------------------------------------------------------------------
=head2 recachePageTree
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.
It should only be nescesarry to call this method when setWithoutRecache is used.
=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(".*");
# Fetch the complete forrest, which is actually all the pagetrees connected by a dummy root.
$forrest = WebGUI::Page->getTree();
@pageRoots = $forrest->{0}->daughters;
$serializer = Data::Serializer->new(serializer => 'Storable');
foreach $currentTree (@pageRoots) {
# Disconnect the tree from the dummy root.
$currentTree->unlink_from_mother;
# Cache forrest per tree.
$cache = WebGUI::Cache->new('root-'.$currentTree->get('pageId'),'PageTree-'.$session{config}{configFile});
$cache->setDataStructure($currentTree);
# Create URL lookup table.
$currentTree->walk_down({
callback => sub {
$_[1]->{_pageLookup}->{$_[0]->get('pageId')} = $_[1]->{_root};
return 1;
},
_root => $currentTree->get('pageId'),
_pageLookup => \%pageLookup
});
}
# Put the lookup table into cache
$cache = WebGUI::Cache->new('pageLookup','PageTree-'.$session{config}{configFile});
$cache->setDataStructure(\%pageLookup);
return "";
}
#-------------------------------------------------------------------
=head2 set ( [ data ] )
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.
=over
=item data
The properties you want to set. This parameter is optional and should be a hashref of the form {propertyA => valueA, propertyB => valueB, etc...}
=back
=cut
sub set {
my $self = shift;
my $output = $self->SUPER::set(@_);
# 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;
}
#-------------------------------------------------------------------
=head2 setWithoutRecache ([data])
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
The properties you want to set. This parameter is optional and should be a hashref of the form {propertyA => valueA, propertyB => valueB, etc...}
=back
=cut
sub setWithoutRecache {
my $self = shift;
return $self->SUPER::set(@_);
}
1;