Merge commit 'v7.10.21' into WebGUI8. Also, add POD and fix broken tests.

This commit is contained in:
Colin Kuskie 2011-10-27 16:45:19 -07:00
commit 4855816a29
72 changed files with 1357 additions and 82 deletions

View file

@ -1,3 +1,13 @@
7.10.21
- added #9668 extension template variable to attachment loops for the following assets:
Article,Post,Event,File,Form::Attachments,Folder
- added WaitForUserConfirmation workflow activity
- added new setting - Enable Users after Anonymous Registration?
- added the optional WebGUI::Content::PDFGenerator, not enabled by default
(see the module's documentation).
- fixed #12204: Default forum notification template produces invalid HTML
- fixed #12202: JsonTable refers to unexistent YUI file
7.10.20
- fixed: Do not call group methods on an undefined value.
- fixed #12178: random deletion of columns may happen when a schema is saved (Amir Plivatsky)
@ -13,6 +23,7 @@
- fixed #12135: Geo::Coder::Googlev3 needs common sense
- fixed #12183: Posts do not disqualify themselves when purged
- fixed #12189: installClass ignores preload.custom
- fixed #12197: Default date Thingy disables date
7.10.19
- fixed #12169: extras uploads symlink export
@ -4224,3 +4235,4 @@
- Made the Include macro more secure.
- Added Len's patch to fix some caching problems.

View file

@ -31,6 +31,10 @@ save you many hours of grief.
Account Macro template
Admin Toggle Macro template
7.10.21
--------------------------------------------------------------------
* WebGUI now depends on Kwargs.
7.10.17
--------------------------------------------------------------------
* Due to a formatting problem with form variables in the PayPal driver, WebGUI

View file

@ -0,0 +1,146 @@
#!/usr/bin/env perl
#-------------------------------------------------------------------
# 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
#-------------------------------------------------------------------
our ($webguiRoot);
BEGIN {
$webguiRoot = "../..";
unshift (@INC, $webguiRoot."/lib");
}
use strict;
use Getopt::Long;
use WebGUI::Session;
use WebGUI::Storage;
use WebGUI::Asset;
my $toVersion = '7.10.21';
my $quiet; # this line required
my $session = start(); # this line required
addWaitForConfirmationWorkflow($session);
addCreateUsersEnabledSetting($session);
finish($session); # this line required
#----------------------------------------------------------------------------
sub addWaitForConfirmationWorkflow {
my $session = shift;
my $c = $session->config;
my $exists = $c->get('workflowActivities/WebGUI::User');
my $class = 'WebGUI::Workflow::Activity::WaitForUserConfirmation';
unless (grep { $_ eq $class } @$exists) {
print "Adding WaitForUserConfirmation workflow..." unless $quiet;
$c->addToArray('workflowActivities/WebGUI::User' => $class);
print "Done!\n" unless $quiet;
}
}
#----------------------------------------------------------------------------
sub addCreateUsersEnabledSetting {
my $session = shift;
my $s = $session->setting;
my $name = 'enableUsersAfterAnonymousRegistration';
return if $s->has($name);
print "Adding $name setting..." unless $quiet;
$s->add($name => 1);
print "Done!\n" unless $quiet;
}
#----------------------------------------------------------------------------
# Describe what our function does
#sub exampleFunction {
# my $session = shift;
# print "\tWe're doing some stuff here that you should know about... " unless $quiet;
# # and here's our code
# print "DONE!\n" unless $quiet;
#}
# -------------- DO NOT EDIT BELOW THIS LINE --------------------------------
#----------------------------------------------------------------------------
# Add a package to the import node
sub addPackage {
my $session = shift;
my $file = shift;
print "\tUpgrading package $file\n" unless $quiet;
# Make a storage location for the package
my $storage = WebGUI::Storage->createTemp( $session );
$storage->addFileFromFilesystem( $file );
# Import the package into the import node
my $package = eval {
my $node = WebGUI::Asset->getImportNode($session);
$node->importPackage( $storage, {
overwriteLatest => 1,
clearPackageFlag => 1,
setDefaultTemplate => 1,
} );
};
if ($package eq 'corrupt') {
die "Corrupt package found in $file. Stopping upgrade.\n";
}
if ($@ || !defined $package) {
die "Error during package import on $file: $@\nStopping upgrade\n.";
}
return;
}
#-------------------------------------------------
sub start {
my $configFile;
$|=1; #disable output buffering
GetOptions(
'configFile=s'=>\$configFile,
'quiet'=>\$quiet
);
my $session = WebGUI::Session->open($webguiRoot,$configFile);
$session->user({userId=>3});
my $versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->set({name=>"Upgrade to ".$toVersion});
return $session;
}
#-------------------------------------------------
sub finish {
my $session = shift;
updateTemplates($session);
my $versionTag = WebGUI::VersionTag->getWorking($session);
$versionTag->commit;
$session->db->write("insert into webguiVersion values (".$session->db->quote($toVersion).",'upgrade',".time().")");
$session->close();
}
#-------------------------------------------------
sub updateTemplates {
my $session = shift;
return undef unless (-d "packages-".$toVersion);
print "\tUpdating packages.\n" unless ($quiet);
opendir(DIR,"packages-".$toVersion);
my @files = readdir(DIR);
closedir(DIR);
my $newFolder = undef;
foreach my $file (@files) {
next unless ($file =~ /\.wgpkg$/);
# Fix the filename to include a path
$file = "packages-" . $toVersion . "/" . $file;
addPackage( $session, $file );
}
}
#vim:ft=perl

View file

@ -945,6 +945,7 @@
"WebGUI::Workflow::Activity::NotifyAboutUser",
"WebGUI::Workflow::Activity::ActivateUser",
"WebGUI::Workflow::Activity::DeactivateUser",
"WebGUI::Workflow::Activity::WaitForUserConfirmation",
"WebGUI::Workflow::Activity::DeleteUser"
],
"WebGUI::VersionTag" : [

View file

@ -1126,10 +1126,12 @@ sub getEditForm {
###
# Properties
my $overrides = $session->config->get("assets/".$self->className) || {};
foreach my $property ( $self->getProperties ) {
my $fieldHash = $self->getFieldData( $property );
next if $fieldHash->{noFormPost};
$fieldHash = $self->setupFormField($property, $fieldHash, $overrides);
# Create tabs to have labels added later
if ( !$f->getTab( $fieldHash->{tab} ) ) {
$f->addTab( name => $fieldHash->{tab}, label => $fieldHash->{tab} );
@ -1167,22 +1169,37 @@ sub getEditForm {
return $f;
} ## end sub getEditForm
=head2 setupFormField ( $fieldName, $fieldHash, $overrides )
Applies overrides from the WebGUI config file to a set of field data. The overridden
and updated field data is returned.
=head3 $fieldName
The name of the field.
=head3 $fieldHash
A hash reference of field data for $fieldName.
=head3 $overrides
A hash reference of overrides from the config file. This is passed in instead of
looking it up each time as a speed optimization.
=cut
sub setupFormField {
my ( $self, $tabform, $fieldName, $extraFields, $overrides ) = @_;
my %params = %{ $extraFields->{$fieldName} };
my $tab = delete $params{tab};
my ( $self, $fieldName, $fieldHash, $overrides ) = @_;
if ( exists $overrides->{fields}{$fieldName} ) {
my %overrideParams = %{ $overrides->{fields}{$fieldName} };
my $overrideTab = delete $overrideParams{tab};
$tab = $overrideTab if defined $overrideTab;
foreach my $key ( keys %overrideParams ) {
$params{"-$key"} = $overrideParams{$key};
}
return $fieldHash unless exists $overrides->{fields}->{$fieldName};
my %overrideParams = %{ $overrides->{fields}->{$fieldName} };
foreach my $key ( keys %overrideParams ) {
(my $canon = $key) =~ s/^-//;
$fieldHash->{$canon} = $overrideParams{$key};
}
return $fieldHash;
$tab ||= 'properties';
return $tabform->getTab($tab)->addField( delete $params{fieldType}, %params);
} ## end sub setupFormField
#-------------------------------------------------------------------

View file

@ -1289,6 +1289,7 @@ sub getTemplateVars {
url => $storage->getUrl($filename),
icon => $storage->getFileIconUrl($filename),
filename => $filename,
extension => WebGUI::Storage->getFileExtension($filename),
thumbnail => $storage->getThumbnailUrl($filename),
isImage => $storage->isImage($filename),
};

View file

@ -622,6 +622,7 @@ sub view {
$var{fileUrl} = $self->getFileUrl;
$var{fileIcon} = $self->getFileIconUrl;
$var{fileSize} = Number::Format::format_bytes($self->get("assetSize"));
$var{extension} = WebGUI::Storage->getFileExtension( $self->get("filename"));
my $out = $self->processTemplate(\%var,undef,$self->{_viewTemplate});
if (!$self->session->isAdminOn && $self->get("cacheTimeout") > 10) {
$self->session->cache->set($self->getViewCacheKey, $out, $self->get("cacheTimeout"));

View file

@ -1092,6 +1092,7 @@ sub getTemplateVars {
url => $fileUrl,
icon => $storage->getFileIconUrl($filename),
filename => $filename,
extension => WebGUI::Storage->getFileExtension($filename),
thumbnail => $isImage ? $storage->getThumbnailUrl($filename) : '',
isImage => $isImage,
});

View file

@ -340,6 +340,7 @@ sub view {
}
push(@{$var{attachment_loop}}, {
filename => $file,
extension => WebGUI::Storage->getFileExtension($file),
isImage => $storage->isImage($file),
url=> $storage->getUrl($file),
thumbnailUrl => $storage->getThumbnailUrl($file),

View file

@ -296,6 +296,7 @@ sub view {
"icon.small" => $child->getIcon(1),
"icon.big" => $child->getIcon,
type => $child->getName,
extension => WebGUI::Storage->getFileExtension( $child->get("filename")),
url => $child->getUrl,
canEdit => $child->canEdit,
controls => $child->getToolbar,

View file

@ -635,6 +635,10 @@ sub editThingDataSave {
if ($self->field_isa($fieldType, 'WebGUI::Form::File')) {
$field->{ defaultValue } = $thingData{ "field_" . $field->{ fieldId } };
}
elsif ($fieldType eq 'Date' or $fieldType eq 'DateTime') { ##Must be in epoch format to be stored in the db.
my $wdt = WebGUI::DateTime->new($session, $field->{defaultValue})->cloneToUserTimeZone;
$field->{defaultValue} = $wdt->epoch;
}
$fieldValue = $thingData->{$fieldName} || $session->form->process($fieldName,$fieldType,$field->{defaultValue},$field);
}
if ($field->{status} eq "required" && ($fieldValue =~ /^\s$/x || $fieldValue eq "" || !(defined $fieldValue))) {
@ -1042,12 +1046,12 @@ sub getFieldValue {
my $fieldType = lc $field->{fieldType};
if ($fieldType eq "date"){
my $dt = WebGUI::DateTime->new($session, $value);
$processedValue = $dt->webguiDate($dateFormat);
my $wdt = WebGUI::DateTime->new($session, $value);
$processedValue = $wdt->cloneToUserTimeZone->webguiDate($dateFormat);
}
elsif ($fieldType eq "datetime"){
my $dt = WebGUI::DateTime->new($session, $value);
$processedValue = $dt->webguiDate($dateTimeFormat);
my $wdt = WebGUI::DateTime->new($session, $value);
$processedValue = $wdt->cloneToUserTimeZone->webguiDate($dateTimeFormat);
}
# TODO: The otherThing field type is probably also handled by getFormPlugin, so the elsif below can probably be
# safely removed. However, this requires more testing than I can provide right now, so for now this stays the
@ -3069,7 +3073,6 @@ sub www_exportThing {
### Loop through the returned structure and put it through Text::CSV
# Column heads
$self->session->log->warn("field labels: ". join ' ', @fieldLabels);
my $csv_filename = 'export_'.$thingProperties->{label}.'.csv';
open my $CSV, '>', $tempStorage->getPath($csv_filename);
print $CSV WebGUI::Text::joinCSV( @fieldLabels );

View file

@ -23,6 +23,7 @@ use WebGUI::User;
use WebGUI::Form::Captcha;
use WebGUI::Macro;
use WebGUI::Deprecate;
use Scope::Guard qw(guard);
use Encode ();
use Tie::IxHash;
@ -679,12 +680,28 @@ sub www_createAccountSave {
$properties->{ identifier } = $self->hashPassword($password);
$properties->{ passwordLastUpdated } = time();
$properties->{ passwordTimeout } = $setting->get("webguiPasswordTimeout");
$properties->{ status } = 'Deactivated' if ($setting->get("webguiValidateEmail"));
my $afterCreateMessage = $self->SUPER::createAccountSave($username,$properties,$password,$profile);
my $sendEmail = $setting->get('webguiValidateEmail');
# We need to deactivate the user and log him out if there are additional
# things that need to be done before he should be logged in.
my $cleanupUser;
if ($sendEmail || !$setting->get('enableUsersAfterAnonymousRegistration')) {
$cleanupUser = guard {
$self->user->status('Deactivated');
$session->var->end($session->var->get('sessionId'));
$session->var->start(1, $session->getId);
my $u = WebGUI::User->new($session, 1);
$self->{user} = $u;
$self->logout;
};
}
# Send validation e-mail if required
if ($setting->get("webguiValidateEmail")) {
if ($sendEmail) {
my $key = $session->id->generate;
$self->update(emailValidationKey=>$key);
my $mail = WebGUI::Mail::Send->create($self->session, {
@ -700,12 +717,6 @@ WebGUI::Asset::Template->newById($self->session,$self->getSetting('accountActiva
$mail->addText($text);
$mail->addFooter;
$mail->queue;
$self->user->status("Deactivated");
$session->end();
$session->start(1, $session->getId);
my $u = WebGUI::User->new($session, 1);
$self->{user} = $u;
$self->logout;
return $self->www_displayLogin($i18n->get('check email for validation','AuthWebGUI'));
}
return $afterCreateMessage;

View file

@ -0,0 +1,145 @@
package WebGUI::Content::PDFGenerator;
use warnings;
use strict;
use List::Util qw(first);
use Scope::Guard qw(guard);
use WebGUI::Session;
use WebGUI::Content::Asset;
=head1 NAME
WebGUI::Content::PDFGenerator
=head1 DESCRIPTION
Generates a PDF of the requested URL when op=generatePdf.
=head1 PREREQUISITES
This handler depends on wkpdftohtml, which does not ship with WebGUI and is,
as of this writing, still in active development. This handler was written for
version 0.9.9. It is available from http://code.google.com/p/wkhtmltopdf/.
Compiling is rather difficult, but static binaries are available for the most
popular platforms.
=head1 INSTALLATION
Enable this content handler in your WebGUI config file, placing it somewhere
before WebGUI::Content::Operation, and add a pdfGen section to your config
file at the top level. This must contain the path to your wkhtmltopdf
executable, a cache timeout (how many seconds to cache the pdf), and
optionally the userId of a user to view the page as (defaults to Visitor). It
can also contain additional command line arguments to pass to wkhtmltopdf.
"pdfGen" : {
"exe" : "/usr/local/bin/wkhtmltopdf",
"args" : "--orientation Landscape",
"userId" : "_f7d61hs6djh0fjnxqw21",
"cacheTimeout" : 3600 # 1 hour cache timeout
},
"contentHandlers" : [
#...
"WebGUI::Content::PDFGenerator",
#...
"WebGUI::Content::Operation",
#...
"WebGUI::Content::NotFound"
],
=cut
#-------------------------------------------------------------------
# Return the cached pdf, generating if necessary.
=head2 cache ($asset)
Returns the cached PDF for an asset, if necessary
=cut
sub cache {
my $asset = shift;
my $session = $asset->session;
my $key = join '', 'PDFGen', $session->url->getRequestedUrl, $asset->get('revisionDate');
my $cache = $session->cache();
my $content = $cache->get($key);
unless ($content) {
$content = generate($asset);
$cache->set($key, $content, $session->config->get('pdfGen/cacheTimeout'));
}
return $content;
}
#-------------------------------------------------------------------
# Generate the pdf unconditionally and return it as a string.
=head2 generate ($asset)
Generate the pdf unconditionally and return it as a string.
=cut
sub generate {
my $asset = shift;
my $session = $asset->session;
my $url = $session->url;
my $c = $session->config;
my $o = $c->get('pdfGen');
my $login = WebGUI::Session->open($c->getWebguiRoot, $c->getFilename);
my $guard = guard { $login->var->end; $login->close };
$login->user({ userId => $o->{userId} || 1 });
my @args = (
$o->{exe}, @{$o->{args} || []},
'--cookie', $c->get('cookieName'), $login->getId,
$url->getSiteURL . $url->gateway($url->getRequestedUrl),
'-'
);
# We're using backticks because trying to run external programs from a
# mod_perl process is extremely tricky any other way, but TODO: figure out
# use a real call and pass an array of args, as that would be safer.
my $cmd = join ' ', @args;
`$cmd`;
}
#-------------------------------------------------------------------
=head2 cache ($asset)
Figure out which asset we need to check permissions for
=cut
sub getRequestedAsset {
my $session = shift;
my $assetUrl = $session->url->getRequestedUrl;
my $perms = WebGUI::Content::Asset::getUrlPermutations($assetUrl);
foreach my $url (@$perms) {
if (my $asset = WebGUI::Content::Asset::getAsset($session, $url)) {
return $asset;
}
}
}
#-------------------------------------------------------------------
# Top-level handler.
=head2 handler ($session)
Top-level handler
=cut
sub handler {
my $session = shift;
my $op = $session->form->get('op');
return undef unless $op && $op eq 'generatePdf';
my $asset = getRequestedAsset($session);
return $session->privilege->noAccess unless $asset->canView;
$session->http->setMimeType('application/pdf');
return cache($asset);
}
1;

View file

@ -300,6 +300,7 @@ sub www_upload {
title => $filename,
url => "attachments/".$filename,
filename => $filename,
extension => WebGUI::Storage->getFileExtension($filename),
ownerUserId => $owner,
groupIdEdit => "3",
groupIdView => "7",

View file

@ -383,6 +383,7 @@ sub prepare {
$style->setCss( $url->extras( 'yui/build/button/assets/skins/sam/button.css'));
$style->setCss( $url->extras( 'yui/build/calendar/assets/skins/sam/calendar.css'));
$style->setCss( $url->extras('yui/build/container/assets/skins/sam/container.css'));
$style->setCss( $url->extras('yui-webgui/build/form/datatable.css'));
$style->setScript( $url->extras('yui/build/container/container-min.js') );
$style->setScript( $url->extras('yui/build/button/button-min.js') );
$style->setScript( $url->extras('yui/build/calendar/calendar-min.js') );

View file

@ -137,9 +137,9 @@ Send JS required for this plugin.
sub headTags {
my $self = shift;
my ( $url, $style ) = $self->session->quick(qw( url style ));
$style->setScript( $url->extras('yui/build/connection/connection-min.js') );
$style->setScript( $url->extras('yui/build/yahoo-dom-event/yahoo-dom-event.js'));
$style->setScript( $url->extras('yui/build/json/json-min.js'));
$style->setScript( $url->extras('yui/build/connect/connect-min.js') );
$style->setScript( $url->extras('yui-webgui/build/i18n/i18n.js') );
$style->setScript( $url->extras('yui-webgui/build/form/jsontable.js'));
}

View file

@ -201,6 +201,7 @@ sub getOperations {
'ajaxDeleteUser' => 'User',
'ajaxUpdateUser' => 'User',
'becomeUser' => 'User',
'confirmUserEmail' => 'User',
'deleteUser' => 'User',
'editUser' => 'User',
'editUserSave' => 'User',

View file

@ -399,6 +399,15 @@ sub definition {
hoverHelp=>$i18n->get('118 description'),
defaultValue=>$setting->get("anonymousRegistration")
});
push(@fields, {
tab => 'user',
fieldType => 'yesNo',
name => 'enableUsersAfterAnonymousRegistration',
label => $i18n->get('Enable Users after Anonymous Registration?'),
hoverHelp => $i18n->get('enableUsersAfterAnonymousRegistration help'),
defaultValue => $setting->get('enableUsersAfterAnonymousRegistration')
}
);
push(@fields, {
tab=>"user",
fieldType=>"yesNo",

View file

@ -415,6 +415,38 @@ sub www_ajaxCreateUser {
#-------------------------------------------------------------------
=head2 www_confirmUserEmail ( )
Process links clicked from mails sent out by the WaitForUserConfmration
workflow activity.
=cut
sub www_confirmUserEmail {
my $session = shift;
my $f = $session->form;
my $instanceId = $f->get('instanceId');
my $token = $f->get('token');
my $actId = $f->get('activityId');
my $activity = WebGUI::Workflow::Activity->new($session, $actId)
or die;
my $instance = WebGUI::Workflow::Instance->new($session, $instanceId)
or die;
if ($activity->confirm($instance, $token)) {
my $msg = $activity->get('okMessage');
unless ($msg) {
my $i18n = WebGUI::International->new($session, 'WebGUI');
$msg = $i18n->get('ok');
}
return $session->style->userStyle($msg);
}
else {
return $session->privilege->noAccess;
}
}
#-------------------------------------------------------------------
=head2 www_ajaxDeleteUser ( )
Delete a user using a web service.

View file

@ -19,6 +19,14 @@ Package WebGUI::Test::MockAsset
=head1 DESCRIPTION
Creates fake WebGUI::Asset objects and sets them up to be returned by WebGUI::Asset's normal constructors.
Most of the time, you'll be mocking templates to read which variables are sent to their
templates. Here's how to do that:
my $mockAsset = WebGUI::Test::MockAsset->new('WebGUI::Asset::Template');
$mockAsset->mock_id($template_id_to_mock);
my $templateVars;
$templateMock->mock('process', sub { $templateVars = $_[1]; } );
$templateMock->set_true('prepare', sub { } );
=head1 METHODS

View file

@ -76,6 +76,29 @@ These methods are available from this class:
#-------------------------------------------------------------------
=head2 changeWorkflow ( $workflowId, $instance, $skipDelete )
Kicks a new workflow in a new instance with the same object the current
instance has, deleting the old instance unless you say otherwise.
=cut
sub changeWorkflow {
my ($self, $workflowId, $instance, $skipDelete) = @_;
WebGUI::Workflow::Instance->create(
$self->session, {
workflowId => $workflowId,
methodName => $instance->get('methodName'),
className => $instance->get('className'),
parameters => $instance->get('parameters'),
priority => $instance->get('priority'),
}
)->start(1);
$instance->delete() unless $skipDelete;
}
#-------------------------------------------------------------------
=head2 cleanup ( )
Override this activity to add a cleanup routine to be run if an instance

View file

@ -0,0 +1,270 @@
package WebGUI::Workflow::Activity::WaitForUserConfirmation;
use warnings;
use strict;
use base 'WebGUI::Workflow::Activity';
use WebGUI::Asset::Template;
use WebGUI::International;
use WebGUI::Inbox::Message;
use WebGUI::Macro;
use Kwargs;
use Tie::IxHash;
#-----------------------------------------------------------------
=head2 confirm ( $instance, $token )
Returns true (and sets the workflow as done) if the token matches the one we
generated for the email.
=cut
sub confirm {
my ($self, $instance, $token) = @_;
my $id = $self->getId;
return 0 unless $token eq $instance->getScratch("$id-token");
$instance->setScratch("$id-status", 'done');
return 1;
}
#-----------------------------------------------------------------
=head2 definition ( )
See WebGUI::Workflow::Activity::definition for details.
=cut
sub definition {
my ($class, $session, $def) = @_;
my $i18n = WebGUI::International->new(
$session, 'Activity_WaitForUserConfirmation'
);
tie my %props, 'Tie::IxHash', (
emailFrom => {
fieldType => 'user',
defaultValue => 3,
},
emailSubject => {
fieldType => 'text',
defaultValue => 'Confirmation Email',
},
template => {
fieldType => 'textarea',
defaultValue => $i18n->get('your template goes here'),
},
templateParser => {
fieldType => 'templateParser',
defaultValue => $session->config->get('defaultTemplateParser'),
},
okMessage => {
fieldType => 'HTMLArea',
},
waitBetween => {
fieldType => 'interval',
defaultValue => 60*5
},
expireAfter => {
fieldType => 'interval',
defaultValue => 60*60*24*7,
},
doOnExpire => {
fieldType => 'workflow',
type => 'WebGUI::User',
none => 1,
}
);
for my $key (keys %props) {
$props{$key}{label} = $i18n->get("$key label");
$props{$key}{hoverHelp} = $i18n->get("$key hoverHelp");
}
push @$def, {
name => $i18n->get('topicName'),
properties => \%props,
};
return $class->SUPER::definition( $session, $def );
}
#-----------------------------------------------------------------
=head2 execute ( )
See WebGUI::Workflow::Activity::execute for details.
=cut
sub execute {
my ($self, $object, $instance) = @_;
my $id = $self->getId;
my $statk = "$id-status";
my $start = "$id-started";
my $status = $instance->getScratch($statk);
my $subject = $self->get('emailSubject');
my $parser = $self->get('templateParser');
WebGUI::Macro::process(\$subject);
my $body = WebGUI::Asset::Template->processRaw(
$self->session,
$self->get('template'),
$self->getTemplateVariables($object, $instance),
$parser,
);
WebGUI::Macro::process(\$body);
unless ($status) {
$instance->setScratch($start => $self->now);
$self->sendEmail(
from => $self->get('emailFrom'),
to => $object->userId,
subject => $subject,
body => $body,
);
$instance->setScratch($statk => 'waiting');
return $self->wait;
}
return $self->COMPLETE if $status eq 'done' || $status eq 'expired';
if ($status eq 'waiting') {
my $end = $instance->getScratch($start) + $self->get('expireAfter');
if ($self->now > $end) {
$self->expire($instance);
$instance->setScratch($statk => 'expired');
return $self->COMPLETE;
}
return $self->wait;
}
$self->session->log->error("Unknown status: $status");
return $self->ERROR;
}
#-----------------------------------------------------------------
=head2 expire ( $instance )
Deletes the workflow instance and kicks off a configured workflow if there is
one.
=cut
sub expire {
my ($self, $instance) = @_;
if (my $id = $self->get('doOnExpire')) {
$self->changeWorkflow($id, $instance);
}
else {
$instance->delete();
}
}
#-----------------------------------------------------------------
=head2 getTemplateVariables ( $object, $instance )
Returns the variables to be used in rendering the email template.
=cut
sub getTemplateVariables {
my ($self, $object, $instance) = @_;
my $user = $object->get;
# Kill all humans. I means references. Currently there seems to be a bug
# in _rewriteVars in some of the template plugins that disallows us from
# using arrayrefs with just strings in them, which is a common occurrence
# in profile fields. When that bug gets fixed, we can (and should) take
# this out.
delete @{$user}{grep {ref $user->{$_} } keys %$user};
return {
user => $user,
link => $self->link($instance),
}
}
#-----------------------------------------------------------------
=head2 link ( $instance )
Returns the URL that needs to be visited by the user.
=cut
sub link {
my ($self, $instance) = @_;
my $url = $self->session->url;
my $aid = $self->getId;
my $iid = $instance->getId;
my $token = $instance->getScratch("$aid-token");
$instance->setScratch("$aid-token", $token = $self->token) unless $token;
my $path = $url->page(
"op=confirmUserEmail;instanceId=$iid;token=$token;activityId=$aid"
);
return $url->getSiteURL . $url->gateway($path);
}
#-----------------------------------------------------------------
=head2 now ( )
Just returns the current time, nice for testing.
=cut
sub now { time }
#-----------------------------------------------------------------
=head2 sendEmail ( { from, to, subject, body } )
Takes a user to send email from, to, with a subject and a body all as
keywords. Mostly here for testing, it just calls
WebGUI::Inbox::Message->create() with proper arguments. 'from' and 'to' are
userIds, not user objects.
=cut
sub sendEmail {
my ($self, $from, $to, $subject, $body) = kwn @_, 1,
qw(from to subject body);
WebGUI::Inbox::Message->create(
$self->session, {
message => $body,
subject => $subject,
status => 'pending',
userId => $to,
sentBy => $from,
}
);
}
#-----------------------------------------------------------------
=head2 token ( )
Returns a random string to use as a token in the confirmation link
=cut
sub token {
my $self = shift;
$self->session->id->generate;
}
#-----------------------------------------------------------------
=head2 wait ( )
Waits for the configured waitBetween interval.
=cut
sub wait {
my $self = shift;
return $self->WAITING($self->get('waitBetween'));
}
1;

View file

@ -0,0 +1,74 @@
package WebGUI::i18n::English::Activity_WaitForUserConfirmation;
use strict;
our $I18N = {
'doOnExpire hoverHelp' => {
message => q{Workflow to run after the waiting period has expired.},
lastUpdated => 1311365415,
},
'doOnExpire label' => {
message => q{Do On Expire},
lastUpdated => 1311365395,
},
'emailFrom hoverHelp' => {
message => q{Which user should the confirmation email be from?},
lastUpdated => 1311363981,
},
'emailFrom label' => {
message => q{Email From},
lastUpdated => 1311363958,
},
'emailSubject label' => {
message => q{Email Subject},
lastUpdated => 1311363994,
},
'expireAfter hoverHelp' => {
message => q{How long should we wait for the user to respond?},
lastUpdated => 1311363900,
},
'expireAfter label' => {
message => q{Expire After},
lastUpdated => 1311363885,
},
'okMessage label' => {
message => q{Confirmation Message},
lastUpdated => 1311612584,
},
'okMessage hoverHelp' => {
message => q{Message to display to the user when he clicks the confirm link},
lastUpdated => 1311612632,
},
'template hoverHelp' => {
message => q{Raw template code for the body of the email goes here.},
lastUpdated => 1311364201,
},
'template label' => {
message => q{Template},
lastUpdated => 1311364181,
},
'templateParser label' => {
message => q{Template Parser},
lastUpdated => 1311364015,
},
'topicName' => {
message => q{Wait For User Confirmation},
lastUpdated => 1311364913,
},
'waitBetween hoverHelp' => {
message => q{How long should we wait in between checks to see if the user has clicked the link?},
lastUpdated => 1311363934,
},
'waitBetween label' => {
message => q{Wait Interval},
lastUpdated => 1311363920,
},
'your template goes here' => {
message => q{Your template goes here!},
lastUpdated => 1311365274,
},
};
1;
#vim:ft=perl

View file

@ -74,6 +74,18 @@ our $I18N = {
context => q{Format for a column with a date},
},
"format textarea" => {
message => q{Textarea},
lastUpdated => 0,
context => q{Format for a textarea column},
},
"format htmlarea" => {
message => q{HTMLarea},
lastUpdated => 0,
context => q{Format for an HTMLarea column},
},
"add column" => {
message => q{Add Column},
lastUpdated => 0,
@ -152,6 +164,23 @@ our $I18N = {
context => q{The name of a newly added value to a column},
},
"data error" => {
message => q{Data error.},
lastUpdated => 0,
context => q{Message to display when DataTable has data error},
},
"sort ascending" => {
message => q{Click to sort ascending},
lastUpdated => 0,
context => q{Message to display in tooltip to sort Column in ascending order},
},
"sort descending" => {
message => q{Click to sort descending},
lastUpdated => 0,
context => q{Message to display in tooltip to sort Column in descending order},
},
};
1;

View file

@ -4776,6 +4776,18 @@ Users may override this setting in their profile.
context => 'Choose, as in to select from a set of options',
},
'Enable Users after Anonymous Registration?' => {
message => 'Enable Users after Anonymous Registration?',
lastUpdated => 1311618346,
},
'enableUsersAfterAnonymousRegistration help' => {
message => 'If this is off, '
. 'users must be manually activated by a workflow or an admin.',
lastUpdated => 1311618419,
},
};
1;

View file

@ -176,6 +176,7 @@ checkModule('Starman', '0.2010', 2);
checkModule('App::Cmd', '0.311' );
checkModule('Devel::StackTrace', '1.27' );
checkModule('Devel::StackTrace::WithLexicals', '0.03' );
checkModule('Kwargs', );
checkModule('Data::ICal', '0.16' );
checkModule('common::sense', '3.2' );
checkModule('Geo::Coder::Googlev3', '0.07' );

File diff suppressed because one or more lines are too long

View file

@ -162,6 +162,11 @@ $storage->addFileFromFilesystem(WebGUI::Test->getTestCollateralPath('lamp.jpg'))
$storage->addFileFromFilesystem(WebGUI::Test->getTestCollateralPath('littleTextFile'));
my $attachment_loop = $post1->getTemplateVars()->{attachment_loop};
use Data::Dumper;
diag Dumper($attachment_loop);
my @extensions = map { [ $_->{filename}, $_->{extension} ] } @{ $attachment_loop };
cmp_bag(
$attachment_loop,
[
@ -170,6 +175,7 @@ cmp_bag(
url => $storage->getUrl('gooey.jpg'),
icon => $session->url->extras('fileIcons/jpg.gif'),
thumbnail => $storage->getThumbnailUrl('gooey.jpg'),
extension => 'jpg',
isImage => bool(1),
},
{
@ -177,6 +183,7 @@ cmp_bag(
url => $storage->getUrl('lamp.jpg'),
icon => $session->url->extras('fileIcons/jpg.gif'),
thumbnail => $storage->getThumbnailUrl('lamp.jpg'),
extension => 'jpg',
isImage => bool(1),
},
{
@ -184,6 +191,7 @@ cmp_bag(
url => $storage->getUrl('littleTextFile'),
icon => $session->url->extras('fileIcons/unknown.gif'),
thumbnail => '',
extension => undef,
isImage => bool(0),
},
],

View file

@ -14,8 +14,11 @@ use File::Spec;
##The goal of this test is to test the creation of Article Wobjects.
use WebGUI::Test;
use WebGUI::Test::MockAsset;
use WebGUI::Session;
use Test::More tests => 23; # increment this value for each test you create
use Test::More tests => 24; # increment this value for each test you create
use Test::Deep;
use Data::Dumper;
use WebGUI::Asset::Wobject::Article;
my $session = WebGUI::Test->session;
@ -61,9 +64,10 @@ foreach my $newSetting (keys %{$newArticleSettings}) {
}
# Test the duplicate method... not for assets, just the extended duplicate functionality of the article wobject
my $filename = "page_title.jpg";
my $filename = "extensions.tar";
my $pathedFile = WebGUI::Test->getTestCollateralPath($filename);
# Use some test collateral to create a storage location and assign it to our article
my $storage = WebGUI::Storage->create($session);
WebGUI::Test->addToCleanup($storage);
@ -75,6 +79,10 @@ diag(join("\n", @{ $storage->getErrors })) unless $filenameOK;
$article->update({storageId=>$storage->getId});
my $storageOK = is($article->get('storageId'), $storage->getId, 'correct storage id stored');
SKIP: {
skip 'storage test setup problem', 3 unless $filenameOK and $storageOK;
@ -117,6 +125,55 @@ $cachedOutput = $session->cache->get('view_'.$article->getId); # Check cache po
isnt ($output, $cachedOutput, 'purgeCache method deletes cache');
# lets test that our new template variable for the fileloop in the main view method returns the
# right values for the new field in the attached files loop: <tmpl_var extension>
# first we create a new template with only the <tmpl_var extension> field in it
# --------------------------------------------------------------------------------------------------
my $templateId = 'DUMMY_TEMPLATE________';
my $templateMock = WebGUI::Test::MockAsset->new('WebGUI::Asset::Template');
$templateMock->mock_id($templateId);
my $templateVars;
$templateMock->set_true('prepare', sub { } );
$templateMock->mock('process', sub { $templateVars = $_[1]; } );
my @extTestFiles = ("rotation_test.png","littleTextFile","jquery.js","tooWide.gif");
foreach my $f (@extTestFiles) {
my $pathedFile = WebGUI::Test->getTestCollateralPath($f);
my $storedFilename = $storage->addFileFromFilesystem($pathedFile);
}
$article->update({templateId=>$templateId});
$article->prepareView;
$article->view;
cmp_bag(
$templateVars->{attachment_loop},
[
superhashof({
filename => 'rotation_test.png',
extension => 'png',
}),
superhashof({
filename => 'littleTextFile',
extension => undef,
}),
superhashof({
filename => 'jquery.js',
extension => 'js',
}),
superhashof({
filename => 'tooWide.gif',
extension => 'gif',
}),
superhashof({
filename => 'extensions.tar',
extension => 'tar',
}),
],
) or diag Dumper($templateVars->{attachment_loop});
TODO: {
local $TODO = "Tests to make later";
ok(0, 'Test exportAssetData method');
@ -126,3 +183,7 @@ TODO: {
ok(0, 'Test www_deleteFile method');
ok(0, 'Test www_view method... maybe?');
}

View file

@ -81,4 +81,4 @@ my $field1 = $thingy->getFields($thingId)->hashRef;
note 'getFieldValue';
is $thingy->getFieldValue(WebGUI::Test->webguiBirthday, $field1), '8/16/2001', 'with epoch as default';
is $thingy->getFieldValue('2011-07-04', $field1), '7/4/2011', 'with mysql date as default';
is $thingy->getFieldValue('2011-07-04', $field1), '7/3/2011', 'with mysql date as default';

View file

@ -0,0 +1,43 @@
use warnings;
use strict;
use FindBin;
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/../t/lib";
use WebGUI::Test;
use WebGUI::Asset;
use Test::More tests => 2;
use Monkey::Patch qw(patch_class);
my $session = WebGUI::Test->session;
WebGUI::Test->originalConfig('assets/WebGUI::Asset::Wobject::Layout');
my $asset = WebGUI::Asset->getTempspace($session)->addChild(
{
className => 'WebGUI::Asset::Wobject::Layout',
}
);
WebGUI::Test->addToCleanup($asset);
sub capture {
my $save;
my $patch = patch_class 'WebGUI::Form::Control' => new => sub {
my $orig = shift;
my $self = $orig->(@_);
my $name = $self->get('name');
$save = $self if $name && $name eq 'assetsToHide';
return $self;
};
$asset->getEditForm;
#use Data::Dumper::Concise;
#print STDERR '# ' . Dumper $save->{_params};
return $save;
}
my $config = $session->config;
my $pfx = 'assets/WebGUI::Asset::Wobject::Layout/fields/assetsToHide';
$config->set("$pfx/uiLevel", 1);
is capture->get('uiLevel'), 1, 'uiLevel override to 1';
$config->set("$pfx/uiLevel", "2");
is capture->get('uiLevel'), 2, 'uiLEvel override to 2';

View file

@ -42,7 +42,7 @@ sub setSiteVersionTagMode {
sub setUserVersionTagMode {
my ($user, $newMode) = @_;
$user->profileField(q{versionTagMode}, $newMode);
$user->update(versionTagMode => $newMode);
return;
} #setUserVersionTagMode
@ -158,10 +158,16 @@ ok(!defined $tagAgain2, 'nonexistent tag cannot be instantiated');
$tag2->rollback;
($tag, $tagAgain1, $tag2, $tagAgain2) = ();
my $master_tag = WebGUI::VersionTag->getWorking($session);
my $node = WebGUI::Test->asset;
$master_tag->commit;
$node = $node->cloneFromDb;
WebGUI::Test->addToCleanup($master_tag);
my $tag3 = WebGUI::VersionTag->create($session, {});
$tag3->setWorking;
my $asset1 = WebGUI::Test->asset->addChild({ className => 'WebGUI::Asset::Snippet', });
my $asset2 = WebGUI::Test->asset->addChild({ className => 'WebGUI::Asset::Snippet', });
my $asset1 = $node->addChild({ className => 'WebGUI::Asset::Snippet', });
my $asset2 = $node->addChild({ className => 'WebGUI::Asset::Snippet', });
is($tag3->getAssetCount, 2, 'tag with two assets');
is($tag3->getRevisionCount, 2, 'tag with two revisions');
$asset1 = $asset1->addRevision({ title => 'revised once', }, time+10);
@ -171,7 +177,7 @@ is($tag3->getRevisionCount, 5, 'tag with five revisions');
my $tag4 = WebGUI::VersionTag->create($session, {});
$tag4->setWorking;
my $asset3 = WebGUI::Test->asset->addChild({ className => 'WebGUI::Asset::Snippet', });
my $asset3 = $node->addChild({ className => 'WebGUI::Asset::Snippet', });
is($tag4->getAssetCount, 1, 'other tag with one asset');
is($tag4->getRevisionCount, 1, 'other tag with one revision');
$asset3->addRevision({ title => 'again revised once', }, time+40);
@ -186,7 +192,7 @@ $tag4->rollback;
#Test commitAsUser
my $tag5 = WebGUI::VersionTag->create($session, {});
$tag5->setWorking;
my $asset5 = WebGUI::Test->asset->addChild({ className => 'WebGUI::Asset::Snippet', });
my $asset5 = $node->addChild({ className => 'WebGUI::Asset::Snippet', });
is($tag5->get("createdBy"),1,'tag created by visitor');
$tag5->commitAsUser(3);
$tag5 = WebGUI::VersionTag->new($session, $tag5->getId); #Get the tag again - properties have changed
@ -197,7 +203,7 @@ $tag5->rollback;
#Test commitAsUser with options
my $tag6 = WebGUI::VersionTag->create($session, {});
$tag6->setWorking;
my $asset6 = WebGUI::Test->asset->addChild({ className => 'WebGUI::Asset::Snippet', });
my $asset6 = $node->addChild({ className => 'WebGUI::Asset::Snippet', });
$tag6->commitAsUser(3, { commitNow => "yes" });
$tag6 = WebGUI::VersionTag->new($session, $tag6->getId); #Get the tag again - properties have changed
is($tag6->get("committedBy"),3,'tag committed by admin again');
@ -253,7 +259,7 @@ ok($siteWideTag->getId() ne $userTagId, 'versionTagMode siteWide: siteWide tag h
$siteWideTag->clearWorking();
my $asset4 = WebGUI::Test->asset->addChild({ className => 'WebGUI::Asset::Snippet' });
my $asset4 = $node->addChild({ className => 'WebGUI::Asset::Snippet' });
ok(defined ($siteWideTag = getWorking(1)), 'versionTagMode siteWide: reclaim version tag after clearWorking and addding new asset');
@ -321,7 +327,7 @@ $siteWideTag->rollback();
setUserVersionTagMode($user, q{singlePerUser});
my $tag = WebGUI::VersionTag->create($session, {});
$tag->setWorking;
my $asset = WebGUI::Test->asset->addChild({ className => 'WebGUI::Asset::Snippet', });
my $asset = $node->addChild({ className => 'WebGUI::Asset::Snippet', });
is($tag->getAssetCount, 1, qq{$test_prefix [singlePerUser] tag with 1 asset});
# create admin session
@ -372,7 +378,7 @@ $siteWideTag->rollback();
setUserVersionTagMode($user, q{siteWide});
$tag = WebGUI::VersionTag->create($session, {});
$tag->setWorking;
$asset = WebGUI::Test->asset->addChild({ className => 'WebGUI::Asset::Snippet', });
$asset = $node->addChild({ className => 'WebGUI::Asset::Snippet', });
is($tag->getAssetCount, 1, qq{$test_prefix [siteWide] tag with 1 asset});
# create admin session

View file

@ -0,0 +1,110 @@
use warnings;
use strict;
use FindBin;
use lib "$FindBin::Bin/../../lib";
use lib "$FindBin::Bin/../../t/lib";
use WebGUI::Test;
use Test::More tests => 28;
use Test::MockObject;
use Test::MockObject::Extends;
use WebGUI::Workflow::Activity;
use Kwargs;
use URI;
my $session = WebGUI::Test->session;
my $act = WebGUI::Workflow::Activity->newByPropertyHashRef(
$session, {
className => 'WebGUI::Workflow::Activity::WaitForUserConfirmation',
activityId => 'test-activity',
expireAfter => 60*60*24,
waitBetween => 60*5,
emailFrom => 3,
emailSubject => 'Confirmation Email',
templateParser => 'WebGUI::Asset::Template::TemplateToolkit',
template => 'Hey [% user.firstName %] [% user.lastName %], '
. 'click $link!',
}
);
is $act->wait, $act->WAITING(60*5), 'wait helper method';
$act = Test::MockObject::Extends->new($act);
my (%scratch, %profile);
%profile = (
email => 'target@test.com',
firstName => 'Target',
lastName => 'Targetson',
);
my $user = Test::MockObject->new
->mock(get => sub { \%profile })
->mock(userId => sub { 'test-user-id' });
my $workflow = Test::MockObject->new
->mock(setScratch => sub { $scratch{$_[1]} = $_[2] })
->mock(getScratch => sub { $scratch{$_[1]} })
->mock(getId => sub { 'test-workflow' });
my ($expired, $sent) = (0,0);
$act->mock(sendEmail => sub { $sent++ })
->mock(expire => sub { $expired++ })
->mock(now => sub { 100 })
->mock(token => sub { 'test-token' });
my $st = 'test-activity-status';
sub ex { $act->execute($user, $workflow) }
sub clr {
delete @scratch{'test-activity-started', $st};
$sent = 0;
$expired = 0;
}
is ex, $act->wait, 'from scratch returns waiting';
is $sent, 1, 'one email sent';
is $scratch{$st}, 'waiting', 'scratch is waiting';
is $scratch{'test-activity-started'}, 100, 'started at mocked time';
is ex, $act->wait, 'still waiting';
is $sent, 1, 'did not send second email';
is $scratch{$st}, 'waiting', 'scratch still waiting';
$scratch{$st} = 'done';
is ex, $act->COMPLETE, 'returns complete after done';
is ex, $act->COMPLETE, 'forever';
is $expired, 0, 'not expired though';
clr;
is $act->execute($user, $workflow), $act->wait, 'waiting after clear';
is $sent, 1, 'one email sent';
$act->mock(now => sub { 60*60*24+101 });
is ex, $act->COMPLETE, 'complete after expired';
is $scratch{$st}, 'expired', 'expired status';
is $expired, 1, 'expire called';
clr;
my ($self, $to, $from, $subject, $body);
$act->mock(
sendEmail => sub {
($self, $to, $from, $subject, $body) = kwn @_, 1,
qw(to from subject body);
}
);
ex;
is $to, $user->userId, 'to';
is $from, 3, 'from';
is $subject, 'Confirmation Email', 'subject';
my $link = URI->new($act->link($workflow));
my %p = $link->query_form;
is $body, "Hey Target Targetson, click $link!", 'body';
is $p{token}, 'test-token', 'token in link';
is $p{instanceId}, 'test-workflow', 'instance id in link';
is $p{activityId}, 'test-activity', 'activity id in link';
$act->unmock('token');
is $act->link($workflow), $link, 'token only generated once';
ok !$act->confirm($workflow, 'not-the-token'), 'bad token';
is $scratch{$st}, 'waiting', 'wait after bad';
ok $act->confirm($workflow, 'test-token'), 'good token';
is $scratch{$st}, 'done', 'done after good';

View file

@ -0,0 +1,60 @@
/**
* wg-datatable-html.css
* CSS rules for DataTable assets
*
* Add as attachment to Default DetaTable template (HTML)
*/
/* start content at top of cells */
.dataTable table>tbody>tr>td {
vertical-align: 0;
}
.dataTable>table>tbody>tr>td>p:first-child { /* for htmlarea, but really applies to the starting p of any cell */
margin-top: 0;
}
/* padding in all data cells */
.dataTable table>tbody>tr>td {
padding-left: 10px;
padding-right: 10px;
}
/* examples of further styles */
/* limited textarea/htmlarea cells, show vertical scrollbar if needed */
/*
.dataTable table>tbody>tr>td {
height: 70px;
width: 200px;
overflow-y: auto;
}
*/
/* styling a particular DataTable in the page, by assetId */
/*
#dataTablesrvp2vk8QJqY5E0imSRQag.dataTable table>tbody>tr>td {
color:red;
height:auto;
width:150px;
overflow-y: visible
}
*/
/* minimum row height */
/*
.dataTable table>tbody>tr>td {
min-height:70px;
height:auto !important;
height:70px;
}
*/
/* styling a particular column (e.g column 2), by assetId */
/*
#dataTablesvE67R6JQfmEI__T9pIAkQ.dataTable table>tbody>tr>td:first-child+td {
color:blue;
}
*/

View file

@ -0,0 +1,59 @@
/**
* wg-datatable-yui.css
* CSS rules for DataTable assets
*
* Add as attachment to Default DetaTable template (YUI)
*/
/* start content at top of cells */
.yui-dt-editable {
vertical-align: 0;
}
.wg-dt-htmlarea>p:first-child {
margin-top: 0;
}
/* padding in all data cells */
td.yui-dt-editable .yui-dt-liner {
padding-left: 10px;
padding-right: 10px;
}
/* examples of further styles */
/* limited textarea/htmlarea cells, show vertical scrollbar if needed */
/*
.wg-dt-textarea, .wg-dt-htmlarea {
height: 70px;
width: 200px;
overflow-y: auto;
}
*/
/* styling a particular DataTable in the page */
/*
#dataTablesrvp2vk8QJqY5E0imSRQag .wg-dt-textarea, #dataTablesrvp2vk8QJqY5E0imSRQag .wg-dt-htmlarea {
color:red;
height:auto;
width:150px;
overflow:visible;
}
*/
/* minimum row height */
/*
td.yui-dt-editable .yui-dt-liner {
min-height:70px;
height:auto !important;
height:70px;
}
*/
/* styling a particular column */
/*
#dataTablesrvp2vk8QJqY5E0imSRQag .yui-dt-col-col1 .yui-dt-liner {
color:blue;
}
*/

View file

@ -0,0 +1,21 @@
/**
* datatable.css
* CSS rules for Edit DataTable
*/
/* prevent collapsing of empty rows */
.yui-dt-editable .yui-dt-liner {
min-height: 10px;
}
/* displaying the initial blank line in HTMLarea is not useful */
.wg-dt-htmlarea>p:first-child {
margin-top: 0;
}
/* size of Textarea/HTMLarea cell */
.wg-dt-textarea, .wg-dt-htmlarea {
height: 50px;
width: 150px;
overflow-y: auto;
}

View file

@ -1,4 +1,3 @@
/*global WebGUI*/
// Initialize namespace
if (typeof WebGUI == "undefined") {
@ -8,7 +7,6 @@ if (typeof WebGUI.Form == "undefined") {
WebGUI.Form = {};
}
/**
* This object contains scripts for the DataTable form control
*/
@ -39,6 +37,21 @@ WebGUI.Form.DataTable
this.options = options;
this.schemaDialog = undefined;
/************************************************************************
* editorByFormat ( event, data )
* Return the DataTable editor type that matches the given format
*/
this.editorByFormat
= function ( format ) {
switch( format ) {
case "text":
case "number":
case "link":
return "textbox";
}
return format;
};
/************************************************************************
* addRow ( event, data )
* Add a row to the bottom of the table
@ -59,11 +72,18 @@ WebGUI.Form.DataTable
/************************************************************************
* deleteSelectedRows ( )
* Delete the selected rows after confirming
* If there is an editor in the deleted row, cancel it
*/
this.deleteSelectedRows
= function ( ) {
if ( confirm( this.i18n.get( "Form_DataTable", "delete confirm" ) ) ) {
var rows = this.dataTable.getSelectedRows();
// Cancel editor if present
if ( this.dataTable.getCellEditor() ) {
this.dataTable.cancelCellEditor();
}
for ( var i = 0; i < rows.length; i++ ) {
this.dataTable.deleteRow( this.dataTable.getRecord( rows[i] ) );
}
@ -98,16 +118,21 @@ WebGUI.Form.DataTable
/************************************************************************
* handleEditorKeyEvent ( obj )
* Handle a keypress when the Cell Editor is open
* Enter will close the editor and move down
* Not implemented: Enter will close the editor and move down
* Tab will close the editor and move right.
* Use the handleTableKeyEvent() to handle the moving
* Open a new cell editor on the newly focused cell
*/
this.handleEditorKeyEvent
= function ( obj ) {
// 9 = tab, 13 = enter
var e = obj.event;
if ( e.keyCode == 9 || e.keyCode == 13 ) {
// Avoid terminating the editor on enter
if ( e.keyCode == 13) {
return false;
}
if ( e.keyCode == 9) {
var cell = this.dataTable.getCellEditor().getTdEl();
var nextCell = this.dataTable.getNextTdEl( cell );
this.dataTable.saveCellEditor();
@ -121,8 +146,6 @@ WebGUI.Form.DataTable
// No next cell, make a new row and open the editor for that one
this.dataTable.addRow( {} );
}
// BUG: If pressing Enter, editor gets hidden right away due to YUI default event
// putting e.preventDefault() and return false here makes no difference
}
};
@ -152,7 +175,10 @@ WebGUI.Form.DataTable
* handleTableKeyEvent ( obj )
* Handle a keypress inside the DataTable
* Space will open the cell editor
* Note: it doesn't currently work: getSelectedTdEls() always returns [] when selectionMode is "standard"
* Commented out for now.
*/
/*
this.handleTableKeyEvent
= function ( obj ) {
// 9 = tab, 13 = enter, 32 = space
@ -164,6 +190,7 @@ WebGUI.Form.DataTable
}
}
};
*/
/************************************************************************
* hideSchemaDialog ( )
@ -204,7 +231,13 @@ WebGUI.Form.DataTable
}
var dataTableOptions = {
dateOptions : { format : this.options.dateFormat }
dateOptions : {
format : this.options.dateFormat,
MSG_LOADING : this.i18n.get( "WebGUI", "Loading..." ),
MSG_ERROR : this.i18n.get( "Form_DataTable", "data error" ),
MSG_SORTASC : this.i18n.get( "Form_DataTable", "sort ascending" ),
MSG_SORTDESC : this.i18n.get( "Form_DataTable", "sort descending" )
}
};
if ( this.options.showEdit ) {
@ -215,6 +248,40 @@ WebGUI.Form.DataTable
dataTableOptions.initialRequest = "";
}
for ( var i = 0; i < this.columns.length; i++ ) {
this.columns[ i ].editor = this.editorByFormat( this.columns[ i ].formatter );
}
var widget = YAHOO.widget,
DT = widget.DataTable;
// Dynamically add HTMLarea field type
// HTMLAreaCellEditor is like TextareaCellEditor, but with an additional property "htmlarea" which is true
var HTMLAreaCellEditor = function(a) {
widget.TextareaCellEditor.superclass.constructor.call(this, a);
};
YAHOO.lang.extend( HTMLAreaCellEditor, widget.TextareaCellEditor, {
htmlarea : true
} );
// Extend the static arrays of editors and formatters
DT.Editors[ "htmlarea" ] = HTMLAreaCellEditor;
// Define classes "wg-dt-textarea" and "wg-dt-htmlarea" that can be overided by a stylesheet
// (e.g. in the extraHeadTags of the asset).
var formatter = function ( type ) {
var fmt = function( el, oRecord, oColumn, oData ) {
var value = YAHOO.lang.isValue(oData) ? oData : "";
el.innerHTML = "<div class='wg-dt-" + type + "'>" + value + "</div>";
};
return fmt;
};
DT.Formatter[ "textarea" ] = formatter( "textarea" );
DT.Formatter[ "htmlarea" ] = formatter( "htmlarea" );
// XXX need to do it with YUI API
widget.BaseCellEditor.prototype.LABEL_SAVE = this.i18n.get( "Form_DataTable", "save" );
widget.BaseCellEditor.prototype.LABEL_CANCEL = this.i18n.get( "Form_DataTable", "cancel" );
this.dataTable = new YAHOO.widget.DataTable(
this.containerId,
this.columns,
@ -223,12 +290,52 @@ WebGUI.Form.DataTable
);
if ( this.options.showEdit ) {
var tinymceEdit = "tinymce-edit";
var saveThis = this;
this.dataTable.doBeforeShowCellEditor = function( oCellEditor ) {
if ( !oCellEditor.htmlarea ) {
return true;
}
oCellEditor.getInputValue = function() {
return tinyMCE.activeEditor.getContent();
};
oCellEditor.textarea.setAttribute( 'id', tinymceEdit );
tinyMCE.execCommand( 'mceAddControl', false, tinymceEdit );
setTimeout(function(){ tinyMCE.execCommand( 'mceFocus',false, tinymceEdit ); }, 0);
// watch hitting tab, which should save the current cell and open an editor on the next
tinyMCE.activeEditor.onKeyDown.add(
function( eh, t ) {
return function(ed, e) { // ed unused
eh.call( t, { event: e } );
};
}( saveThis.handleEditorKeyEvent, saveThis )
);
return true;
};
// Remove TinyMCE on save or cancel
var mceRemoveControl = function ( oArgs ) {
var oCellEditor = oArgs.editor;
if ( oCellEditor.htmlarea ) {
tinyMCE.execCommand( 'mceRemoveControl', false, tinymceEdit );
oCellEditor.textarea.removeAttribute( 'id' );
}
};
this.dataTable.subscribe( "editorSaveEvent", mceRemoveControl );
this.dataTable.subscribe( "editorCancelEvent", mceRemoveControl );
// Add the class so our editors get the right skin
YAHOO.util.Dom.addClass( document.body, "yui-skin-sam" );
this.dataTable.subscribe( "cellDblclickEvent", this.dataTable.onEventShowCellEditor );
this.dataTable.subscribe( "rowClickEvent", this.dataTable.onEventSelectRow );
this.dataTable.subscribe( "tableKeyEvent", this.handleTableKeyEvent, this, true );
/* this.handleTableKeyEvent() is commented out, see there for the reason */
/* this.dataTable.subscribe( "tableKeyEvent", this.handleTableKeyEvent, this, true ); */
this.dataTable.subscribe( "editorKeydownEvent", this.handleEditorKeyEvent, this, true );
this.dataTable.subscribe( "editorShowEvent", this.handleEditorShowEvent, this, true );
this.dataTable.subscribe( "rowAddEvent", this.handleRowAdd, this, true );
@ -281,7 +388,6 @@ WebGUI.Form.DataTable
scope : this
}
} );
// This data table will be submitted async
if ( this.options.ajaxSaveUrl ) {
var save = new YAHOO.widget.Button( {
@ -333,6 +439,8 @@ WebGUI.Form.DataTable
"format link",
"format number",
"format date",
"format textarea",
"format htmlarea",
"add column",
"cancel",
"ok",
@ -342,7 +450,13 @@ WebGUI.Form.DataTable
"help select row",
"help add row",
"help default sort",
"help reorder column"
"help reorder column",
"data error",
"sort ascending",
"sort descending"
],
'WebGUI' : [
"Loading..."
]
},
onpreload : {
@ -365,7 +479,7 @@ WebGUI.Form.DataTable
var helpDialog = new YAHOO.widget.Panel( "helpWindow", {
modal : false,
draggable : true,
zIndex : 1000
zIndex : 10000
} );
helpDialog.setHeader( "DataTable Help" );
helpDialog.setBody(
@ -404,28 +518,16 @@ WebGUI.Form.DataTable
};
var buttonLabel = this.i18n.get( "Form_DataTable", "delete column" );
var availableFormats = [
{
"value" : "text",
"label" : this.i18n.get( "Form_DataTable", "format text" )
},
{
"value" : "number",
"label" : this.i18n.get( "Form_DataTable", "format number" )
},
{
"value" : "email",
"label" : this.i18n.get( "Form_DataTable", "format email" )
},
{
"value" : "link",
"label" : this.i18n.get( "Form_DataTable", "format link" )
},
{
"value" : "date",
"label" : this.i18n.get( "Form_DataTable", "format date" )
}
];
var availableFormats = [];
var formatType = [ "text", "number", "email", "link", "date", "textarea", "htmlarea" ];
for ( var fti = 0; fti < formatType.length; fti++) {
availableFormats.push(
{
"value" : formatType[fti],
"label" : this.i18n.get( "Form_DataTable", "format " + formatType[fti] )
}
);
}
// function for creating new database columns to the table schema
var createTableColumn = function(i,cols) {
@ -595,7 +697,7 @@ WebGUI.Form.DataTable
this.updateSchema
= function () {
var data = this.schemaDialog.getData();
// First delete columns
var deleteCols = YAHOO.lang.JSON.parse( data[ "deleteCols" ] );
for ( var x = 0; x < deleteCols.length; x++ ) {
@ -623,7 +725,7 @@ WebGUI.Form.DataTable
var rows = this.dataTable.getRecordSet().getRecords();
for ( var r = 0; r < rows.length; r++ ) {
rows[ r ].setData( newKey, rows[ r ].getData( oldKey ) );
rows[ r ].setData( oldKey, undefined );
rows[ r ].setData( oldKey, undefined );
}
}
@ -633,7 +735,7 @@ WebGUI.Form.DataTable
formatter : format,
resizeable : ( col ? col.resizeable : 1 ),
sortable : ( col ? col.sortable : 1 ),
editor : ( format == "date" ? "date" : "textbox")
editor : this.editorByFormat( format )
};
if ( format == "date" ) {
newCol["dateOptions"] = { format : this.options.dateFormat };
@ -664,9 +766,9 @@ WebGUI.Form.DataTable
}
}
}
i++;
}
this.dataTable.render();
this.schemaDialog.cancel();
};