Merge commit 'v7.10.18' into 8

Conflicts:
	docs/gotcha.txt
	docs/previousVersion.sql
	docs/templates.txt
	lib/WebGUI.pm
	lib/WebGUI/Asset/File.pm
	lib/WebGUI/Asset/Story.pm
	lib/WebGUI/Asset/Wobject/Calendar.pm
	lib/WebGUI/Asset/Wobject/Thingy.pm
	lib/WebGUI/AssetExportHtml.pm
	lib/WebGUI/Content/AssetManager.pm
	lib/WebGUI/Group.pm
	lib/WebGUI/Macro/AssetProxy.pm
	lib/WebGUI/Shop/PayDriver/PayPal/PayPalStd.pm
	lib/WebGUI/Storage.pm
	t/Asset/AssetExportHtml.t
	t/Asset/Story.t
	t/Shop/TaxDriver/Generic.t
	t/Storage.t
This commit is contained in:
Doug Bell 2011-06-21 16:03:49 -05:00
commit 0c5acb697b
75 changed files with 979 additions and 139 deletions

View file

@ -190,6 +190,12 @@ sub callMethod {
);
return undef;
}
unless ($self->canView) {
my $session = $self->session;
$session->output->print($session->privilege->insufficient);
return undef;
}
#Try to call the method
my $output = eval { $self->$method(@{$args}) };

View file

@ -62,7 +62,7 @@ with 'WebGUI::Role::Asset::SetStoragePermissions';
use WebGUI::Storage;
use WebGUI::SQL;
use WebGUI::Event;
=head1 NAME
@ -219,6 +219,7 @@ sub exportWriteFile {
WebGUI::Error->throw(error => "can't copy " . $self->getStorageLocation->getPath($self->filename)
. ' to ' . $dest->absolute->stringify . ": $!");
}
fire $self->session, 'asset::export' => $dest;
}
#-------------------------------------------------------------------

View file

@ -254,14 +254,18 @@ Extends the base method to handle creating a new subscription group.
=cut
sub duplicate {
my $self = shift;
my $session = $self->session;
my $copy = $self->SUPER::duplicate(@_);
if ($self->get('subscriptionGroupId')) {
my $group = WebGUI::Group->new($session, $self->get('subscriptionGroupId'));
my $copied_group = WebGUI::Group->new($session, 'new');
$copied_group->addUsers($group->getUsers('withoutExpired'));
$copy->update({subscriptionGroupId => $copied_group->getId});
my $self = shift;
my $session = $self->session;
my $copy = $self->SUPER::duplicate(@_);
my $oldGroupId = $self->get('subscriptionGroupId');
if ($oldGroupId) {
my $newGroup = WebGUI::Group->new($session, 'new');
my $oldGroup = WebGUI::Group->new($session, $oldGroupId);
if ($oldGroup) {
$newGroup->addUsers($oldGroup->getUsers('withoutExpired'));
}
$copy->update({subscriptionGroupId => $newGroup->getId});
}
return $copy;
}

View file

@ -73,8 +73,6 @@ with 'WebGUI::Role::Asset::AlwaysHidden';
use WebGUI::International;
use JSON qw/from_json to_json/;
use Storable qw/dclone/;
use Data::Dumper;
=head1 NAME
@ -513,12 +511,10 @@ Returns the photo hash formatted as perl data. See also L<setPhotoData>.
sub getPhotoData {
my $self = shift;
if (!exists $self->{_photoData}) {
my $json = $self->photo;
$json ||= '[]';
$self->{_photoData} = from_json($json);
}
return dclone($self->{_photoData});
my $json = $self->photo;
$json ||= '[]';
my $photoData = from_json($json);
return $photoData;
}
#-------------------------------------------------------------------
@ -600,8 +596,6 @@ sub processEditForm {
my $session = $self->session;
$self->next::method;
my $archive = delete $self->{_parent}; ##Force a new lookup.
#$session->log->warn($self->getParent->get('className'));
#$session->log->warn($self->getParent->getParent->get('className'));
my $form = $session->form;
##Handle old data first, to avoid iterating across a newly added photo.
my $photoData = $self->getPhotoData;
@ -773,7 +767,6 @@ sub setPhotoData {
my $photo = to_json($photoData);
##Update the db.
$self->update({photo => $photo});
delete $self->{_photoData};
return;
}

View file

@ -705,7 +705,8 @@ sub getFeed {
=head2 getFeeds ( )
Gets an arrayref of hashrefs of all the feeds attached to this calendar.
Gets an arrayref of hashrefs of all the feeds attached to this calendar. Since the icalFeeds
property does double duty as JSON and Perl, deserialize from JSON if it's not already perl.
TODO: Format lastUpdated into the user's time zone
@ -713,7 +714,10 @@ TODO: Format lastUpdated into the user's time zone
sub getFeeds {
my $self = shift;
return $self->icalFeeds;
my $feeds = $self->icalFeeds;
return $feeds if (ref $feeds);
$self->session->log->warn('improperly stored icalFeed in calendar assetId:'.$self->getId);
return JSON::from_json($feeds);
}
#----------------------------------------------------------------------------

View file

@ -2075,7 +2075,7 @@ sub www_editThing {
maxEntriesPerUser=>undef,
maxEntriesTotal=>undef,
);
$thingId = $self->addThing(\%properties,0);
$thingId = "new";
}
else{
%properties = %{$self->getThing($thingId)};
@ -2600,13 +2600,14 @@ sub www_editFieldSave {
$defaultValue = $session->form->process("defaultFieldInThing");
}
$thingId = $self->addThing({ thingId => 'new' },0) if $thingId eq 'new';
$fieldId = $session->form->process("fieldId");
%properties = (
fieldId => $fieldId,
thingId => $thingId,
label => $label,
fieldType => $fieldType,
isUnique => $uniqueField,
isUnique => $uniqueField,
defaultValue => $defaultValue,
possibleValues => $session->form->process("possibleValues"),
pretext => $session->form->process("pretext"),
@ -2664,7 +2665,7 @@ sub www_editFieldSave {
# Make sure we send debug information along with the field.
$log->preventDebugOutput;
$session->output->print($newFieldId.$listItemHTML);
$session->output->print($thingId.$newFieldId.$listItemHTML);
return "chunked";
}

View file

@ -25,6 +25,7 @@ use URI::URL ();
use Scope::Guard qw(guard);
use WebGUI::ProgressTree;
use WebGUI::FormBuilder;
use WebGUI::Event;
=head1 NAME
@ -957,6 +958,7 @@ sub exportWriteFile {
$self->session->output->print($contents);
}
$fh->close;
fire $self->session, 'asset::export' => $dest;
}
#-------------------------------------------------------------------

148
lib/WebGUI/Event.pm Normal file
View file

@ -0,0 +1,148 @@
package WebGUI::Event;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2009 Plain Black Corporation.
-------------------------------------------------------------------
Please read the legal notices (docs/legal.txt) and the license
(docs/license.txt) that came with this distribution before using
this software.
-------------------------------------------------------------------
http://www.plainblack.com info@plainblack.com
-------------------------------------------------------------------
=cut
use strict;
use warnings;
use Exporter qw(import);
use WebGUI::Pluggable;
use Try::Tiny;
our @EXPORT = qw(fire);
=head1 NAME
WebGUI::Event
=head1 DESCRIPTION
Run custom code when things happen in WebGUI.
=head1 SUBSCRIBERS
If you're trying to handle an event, this is you.
=head2 CONFIG FILE
The C<events> hash in the config file maps names to lists of event handlers.
They will be run in the order they are defined. Instead of a list, you can
just specify one handler, and it will be treated as a list of one element.
The handlers are subroutines and must be able to be found by
WebGUI::Pluggable::run.
#...
"events" : {
"asset::export" : "My::Events::onExport",
"storage::addFile" : "My::Events::onFile"
},
#...
=head2 PERL CODE
Your code will be called with the arguments that are passed to
WebGUI::Event::Fire by the publisher.
package My::Events;
sub onExport {
my ($session, $name, $asset, $path) = @_;
#...
}
sub onFile {
my ($session, $name, $storage, $filename) = @_;
#...
}
=head1 PUBLISHERS
If you want to let people hook some behavior in the code you're writing, this
is you.
package WebGUI::Something;
use WebGUI::Event;
sub someThing {
#...
fire $session, 'something::happened', $with, $some, $arguments;
#...
}
=head1 SUBROUTINES
These subroutines are available from this package:
=cut
#-------------------------------------------------------------------
=head2 fire($session, $name, ...)
Exported by default. Calls all the subroutines defined in C<$session>'s config
file for C<$name> in order with these same arguments.
=cut
our %cache;
sub fire {
my ($session, $name) = splice @_, 0, 2;
my $config = $session->config;
my $path = $config->getFilePath;
unless (exists $cache{$path}{$name}) {
my $events = $config->get('events') or return;
my $names = $events->{$name} or return;
$names = [ $names ] unless ref $names eq 'ARRAY';
$cache{$path}{$name} = [
grep { $_ } map {
if ($_) {
my ($package, $subname) = /^(.*)::([^:]+)$/;
try {
WebGUI::Pluggable::load($package);
$package->can($subname);
}
catch {
$session->log->error(
"Couldn't load event handler for $name: $_"
);
undef;
};
}
} @$names
];
}
$_->($session, $name, @_) for @{ $cache{$path}{$name} };
}
=head1 RATIONALE
=head2 Why can't I register listeners at runtime? or...
=head2 Why is there no subscribe method? or...
=head2 Why is this in the config file instead of somewhere else?
WebGUI::Events are conceptually per-site things. The code to be called is
static and hopefully controlled someone by with access to the config file.
That being said, you could certainly build something more dynamic on top of
this system. Writing an event handler that publishes messages to a broker
service like DBus or RabbitMQ is entirely possible.
=cut
1;

View file

@ -1634,6 +1634,7 @@ sub resetGroupFields {
##Note, I did assets in SQL instead of using the API because you would have to
##instanciate every version of the asset that used the group. This should be much quicker
ASSET: foreach my $assetClass ($db->buildArray('SELECT DISTINCT className FROM asset')) {
next ASSET unless $db->quickScalar( "SELECT COUNT(*) FROM asset WHERE className=?", [$assetClass] );
my $className = eval { WebGUI::Asset->loadModule($assetClass); };
if (my $e = Exception::Class->caught) {
warn $e->cause;
@ -1685,7 +1686,11 @@ sub resetGroupFields {
push @activities, @{ $wfActivities };
}
foreach my $activity (@activities) {
my $definition = WebGUI::Pluggable::instanciate($activity, 'definition', [$session]);
my $definition = eval { WebGUI::Pluggable::instanciate($activity, 'definition', [$session]) };
if ( $@ ) {
$session->log->warn( "Couldn't instanciate activity class $activity to reset groups, skipping..." );
next;
}
my $sth = $db->prepare('UPDATE WorkflowActivityData set value=3 where name=? and value=?');
SUBDEF: foreach my $subdef (@{ $definition }) {
PROP: while (my ($fieldName, $properties) = each %{ $subdef->{properties} }) {

View file

@ -45,7 +45,7 @@ sub process {
my ($session, $identifier, $type) = @_;
if (!$identifier) {
$session->errorHandler->warn('AssetProxy macro called without an asset to proxy. '
. 'The macro was called through this url: '.$session->asset->get('url'));
. 'The macro was called through this url: '.$session->url->page);
if ($session->isAdminOn) {
my $i18n = WebGUI::International->new($session, 'Macro_AssetProxy');
return $i18n->get('invalid url');
@ -61,9 +61,9 @@ sub process {
else {
$asset = eval { WebGUI::Asset->newByUrl($session,$identifier); };
}
if (Exception::Class->caught()) {
$session->log->warn('AssetProxy macro called invalid asset: '.$identifier
.'. The macro was called through this url: '.$session->asset->get('url'));
if (!defined $asset) {
$session->errorHandler->warn('AssetProxy macro called invalid asset: '.$identifier
.'. The macro was called through this url: '.$session->url->page);
if ($session->isAdminOn) {
my $i18n = WebGUI::International->new($session, 'Macro_AssetProxy');
return $i18n->get('invalid url');
@ -71,15 +71,15 @@ sub process {
}
elsif ($asset->get('state') =~ /^trash/) {
$session->log->warn('AssetProxy macro called on asset in trash: '.$identifier
.'. The macro was called through this url: '.$session->asset->get('url'));
.'. The macro was called through this url: '.$session->url->page);
if ($session->isAdminOn) {
my $i18n = WebGUI::International->new($session, 'Macro_AssetProxy');
return $i18n->get('asset in trash');
}
}
elsif ($asset->get('state') =~ /^clipboard/) {
$session->log->warn('AssetProxy macro called on asset in clipboard: '.$identifier
.'. The macro was called through this url: '.$session->asset->get('url'));
$session->errorHandler->warn('AssetProxy macro called on asset in clipboard: '.$identifier
.'. The macro was called through this url: '.$session->url->page);
if ($session->isAdminOn) {
my $i18n = WebGUI::International->new($session, 'Macro_AssetProxy');
return $i18n->get('asset in clipboard');

View file

@ -41,23 +41,26 @@ sub process {
my ($session, $thingDataUrl, $templateHint ) = @_;
my $i18n = WebGUI::International->new($session, 'Macro_RenderThingData');
return $i18n->get('no template') if !$templateHint;
my $uri = URI->new( $thingDataUrl );
my $gateway = $session->config->get('gateway');
my $uri = URI->new( $thingDataUrl );
my $thingy_url = $uri->path;
$thingy_url =~ s/^$gateway//;
my $urlHash = { $uri->query_form };
my $thingId = $urlHash->{'thingId'};
my $thingDataId = $urlHash->{'thingDataId'};
my $thing = WebGUI::Asset::Wobject::Thingy->newByUrl( $session, $uri->path );
my $thing = WebGUI::Asset::Wobject::Thingy->newByUrl( $session, $thingy_url );
# TODO: i18n
return ( "Bad URL: " . $thingDataUrl ) if !$thing || !$thingId || !$thingDataId;
return ( $i18n->get('bad url') . $thingDataUrl ) if !$thing || !$thingId || !$thingDataId;
# Render
my $output = $thing->www_viewThingData( $thingId, $thingDataId, $templateHint );
# FIX: Temporary solution (broken map due to template rendering <script> tags)
return "RenderThingData: Contained bad tags!" if $output =~ /script>/;
return $i18n->get('bad tags') if $output =~ /script>/;
return $output;
}

View file

@ -513,7 +513,9 @@ A reference to the current session.
sub www_leaveVersionTag {
my $session = shift;
WebGUI::VersionTag->getWorking($session)->leaveTag;
my $tag = $session->scratch()->get(q{versionTag});
WebGUI::VersionTag->getWorking($session)->leaveTag if $tag;
return www_manageVersions($session);
}

View file

@ -286,12 +286,14 @@ sub www_sendToPayPal {
my $url = $session->url;
my $base = $url->getSiteURL . $url->page;
my $i18n = WebGUI::International->new( $self->session, $I18N );
my $returnUrl = URI->new($base);
$returnUrl->query_form( {
shop => 'pay',
method => 'do',
do => 'payPalCallback',
paymentGatewayId => $self->getId,
LOCALECODE => $i18n->getLanguage->{locale},
}
);
@ -310,7 +312,6 @@ sub www_sendToPayPal {
my $testMode = $self->testMode;
my $response = LWP::UserAgent->new->post( $self->apiUrl, $form );
my $params = $self->responseHash($response);
my $i18n = WebGUI::International->new( $self->session, $I18N );
my $error;
if ($params) {

View file

@ -225,6 +225,7 @@ sub paymentVariables {
my $url = $self->session->url;
my $base = $url->getSiteURL . $url->page;
my $cart = $self->getCart;
my $i18n = WebGUI::International->new($self->session);
my $return = URI->new($base);
$return->query_form( {
@ -247,10 +248,11 @@ sub paymentVariables {
return => $return->as_string,
cancel_return => $cancel->as_string,
lc => $i18n->getLanguage->{locale},
handling_cart => $cart->calculateShipping, ##According to https://www.x.com/message/180018#180018
#handling_cart => $cart->calculateShipping, ##According to https://www.x.com/message/180018#180018
tax_cart => $cart->calculateTaxes,
discount_amount_cart => -($cart->calculateShopCreditDeduction),
discount_amount_cart => abs($cart->calculateShopCreditDeduction),
# When we verify that we have a valid transaction ID later on in
# processPayment, we'll make sure it's the cart we think it is.
@ -259,11 +261,18 @@ sub paymentVariables {
my $counter = 0;
foreach my $item (@{ $cart->getItems}) {
my $n = ++$counter;
$params{"amount_$n"} = $item->getSku->getPrice;
$params{"quantity_$n"} = $item->quantity;
$params{"item_name_$n"} = $item->configuredTitle;
$params{"item_number_$n"} = $item->itemId;
++$counter;
$params{"amount_$counter"} = $item->getSku->getPrice;
$params{"quantity_$counter"} = $item->get('quantity');
$params{"item_name_$counter"} = $item->get('configuredTitle');
$params{"item_number_$counter"} = $item->get('itemId');
}
if ($cart->requiresShipping) {
++$counter;
$params{"amount_$counter"} = $cart->calculateShipping;
$params{"quantity_$counter"} = 1;
$params{"item_name_$counter"} = $i18n->get('shipping', 'Shop');
$params{"item_number_$counter"} = 'Shipping';
}
return \%params;

View file

@ -26,6 +26,7 @@ use Image::Magick;
use Path::Class::Dir;
use Storable ();
use WebGUI::Paths;
use WebGUI::Event;
use JSON ();
@ -111,6 +112,19 @@ sub _addError {
#-------------------------------------------------------------------
=head2 _addFile ( $filename )
Emits the storage::addFile event for this storage/filename.
=cut
sub _addFile {
my ($self, $filename) = @_;
fire $self->session, 'storage::addFile', $self, $filename;
}
#-------------------------------------------------------------------
=head2 _cdnAdd ( )
Adds to CDN queue, for any of the add* methods.
@ -338,6 +352,7 @@ sub addFileFromFilesystem {
close $dest;
close $source;
$self->_cdnAdd;
$self->_addFile($filename);
return $filename;
}
@ -390,6 +405,7 @@ sub addFileFromFormPost {
$attachmentCount++;
if (File::Copy::move($upload->path, $filePath)) {
$self->_changeOwner($filePath);
$self->_addFile($filename);
$self->session->log->info("Got ".$upload->filename);
}
else {
@ -426,6 +442,7 @@ sub addFileFromHashref {
Storable::nstore($hashref, $self->getPath($filename))
or $self->_addError("Couldn't create file ".$self->getPath($filename)." because ".$!);
$self->_changeOwner($self->getPath($filename));
$self->_addFile($filename);
$filename and $self->_cdnAdd;
return $filename;
}
@ -455,6 +472,7 @@ sub addFileFromScalar {
print $FILE $content;
close($FILE);
$self->_changeOwner($self->getPath($filename));
$self->_addFile($filename);
$self->_cdnAdd;
}
else {
@ -586,7 +604,12 @@ sub copy {
else {
open my $source, '<:raw', $origFile or next FILE;
open my $dest, '>:raw', $copyFile or next FILE;
File::Copy::copy($source, $dest) or $self->_addError("Couldn't copy file ".$origFile." to ".$copyFile." because ".$!);
if (File::Copy::copy($source, $dest)) {
$newStorage->_addFile($file);
}
else {
$self->_addError("Couldn't copy file $origFile to $copyFile because $!");
}
close $dest;
close $source;
}
@ -619,6 +642,7 @@ sub copyFile {
File::Copy::copy( $self->getPath($filename), $self->getPath($newFilename) )
|| croak "Couldn't copy '$filename' to '$newFilename': $!";
$self->_changeOwner($self->getPath($filename));
$self->_addFile($filename);
$self->_cdnAdd;
return undef;

View file

@ -843,7 +843,7 @@ Example call:
},
'WebGUI::LDAPLink' => sub {
my $link = shift;
$link->session->db->write("delete from ldapLink where ldapLinkId=?", [$link->{ldapLinkId}]);
$link->session->db->write("delete from ldapLink where ldapLinkId=?", [$link->{_ldapLinkId}]);
},
'CODE' => sub {
(shift)->();

View file

@ -169,6 +169,24 @@ our $I18N = {
message => q{required },
lastUpdated => 1225724810,
},
'assetManagerSortDirection label' => {
message => 'Asset Manager Sort Direction',
lastUpdated => 1307982524,
},
'assetManagerSortColumn label' => {
message => 'Asset Manager Sort Column',
lastUpdated => 1307982524,
},
'ascending' => {
message => 'Ascending',
lastUpdated => 1307982524,
context => 'Ascending sort order (lowest to highest)',
},
'descending' => {
message => 'Descending',
lastUpdated => 1307982524,
context => 'Descending sort order (highest to lowest)',
},
};
1;

View file

@ -1809,6 +1809,12 @@ the Collaboration Asset, the user will be notified.|,
lastUpdated => 0,
},
'View this message on the website' => {
message => q{View this message on the website},
context => q{label in the default notification email template},
lastUpdated => 0,
},
};
1;

View file

@ -1150,6 +1150,18 @@ below/after the form element.|,
context => q|Template variable help for getViewThingData|,
},
'unique label' => {
message => q|Unique|,
lastUpdated => 1308006166,
context => q|Label in the edit field screen.|,
},
'unique description' => {
message => q|Is this a unique field? In simple terms, values in unique fields can't have any duplicates in other rows.|,
lastUpdated => 1308006162,
context => q|Hoverhelp for edit field screen|,
},
};
1;

View file

@ -4,13 +4,19 @@ use strict;
our $I18N = {
'bad tags' => {
message => q||,
message => q|RenderThingData: Thingy output contains script tags.|,
lastUpdated => 1306275259,
},
'no template' => {
message => q|RenderThingData: Please specify a template.|,
lastUpdated => 1149177662,
lastUpdated => 1306337467,
},
'bad url' => {
message => q|Bad URL: |,
lastUpdated => 1306337468,
context => q|A URL with missing parameters or one that does not refer to a Thingy asset.|,
},
};