diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt
index b30a99b1b..e051655cb 100644
--- a/docs/changelog/7.x.x.txt
+++ b/docs/changelog/7.x.x.txt
@@ -10,6 +10,7 @@
- ensure proper XML encoding for ITransact messages
- fixed: fatal error duplicate keywords added to a wiki page
- rfe: added the ability to choose whether assets should be added to the front or end of the first content position of the page (Dept of State)
+ - Added Thingy asset
7.5.5
- fixed: Several typos in the new Calendar help documentation.
diff --git a/docs/upgrades/packages-7.5.6/root_import_thingy-templates.wgpkg b/docs/upgrades/packages-7.5.6/root_import_thingy-templates.wgpkg
new file mode 100644
index 000000000..38add7e1c
Binary files /dev/null and b/docs/upgrades/packages-7.5.6/root_import_thingy-templates.wgpkg differ
diff --git a/docs/upgrades/upgrade_7.5.5-7.5.6.pl b/docs/upgrades/upgrade_7.5.5-7.5.6.pl
index 0cb31a533..03629cb8e 100644
--- a/docs/upgrades/upgrade_7.5.5-7.5.6.pl
+++ b/docs/upgrades/upgrade_7.5.5-7.5.6.pl
@@ -24,6 +24,7 @@ my $session = start(); # this line required
convertCacheToBinary($session);
addLayoutOrderSetting( $session );
+installThingyAsset($session);
finish($session); # this line required
@@ -58,7 +59,86 @@ sub addLayoutOrderSetting {
print "DONE!\n" unless $quiet;
}
+#----------------------------------------------------------------------------
+# Install the Thingy asset
+sub installThingyAsset {
+ my $session = shift;
+ print "\tInstalling Thingy asset..." unless $quiet;
+ $session->db->write(<<'ENDSQL');
+create table if not exists Thingy (
+ assetId varchar(22) binary not null,
+ revisionDate bigint not null,
+ templateId varchar(22) not null,
+ defaultThingId varchar(22),
+ primary key (assetId, revisionDate)
+ )
+ENDSQL
+
+ $session->db->write(<<'ENDSQL');
+create table if not exists Thingy_things (
+ assetId varchar(22) binary not null,
+ thingId varchar(22) binary not null,
+ label varchar(255) not null,
+ editScreenTitle varchar(255) not null,
+ editInstructions text,
+ groupIdAdd varchar(22) not null,
+ groupIdEdit varchar(22) not null,
+ saveButtonLabel varchar(255) not null,
+ afterSave varchar(255) not null,
+ editTemplateId varchar(22) not null,
+ onAddWorkflowId varchar(22),
+ onEditWorkflowId varchar(22),
+ onDeleteWorkflowId varchar(22),
+ groupIdView varchar(22) not null,
+ viewTemplateId varchar(22) not null,
+ defaultView varchar(255) not null,
+ searchScreenTitle varchar(255) not null,
+ searchDescription text,
+ groupIdSearch varchar(22) not null,
+ groupIdImport varchar(22) not null,
+ groupIdExport varchar(22) not null,
+ searchTemplateId varchar(22) not null,
+ thingsPerPage int(11) not null default 25,
+ sortBy varchar(22),
+ display int(11),
+ primary key (thingId)
+ )
+ENDSQL
+
+ $session->db->write(<<'ENDSQL');
+create table if not exists Thingy_fields (
+ assetId varchar(22) binary not null,
+ thingId varchar(22) binary not null,
+ fieldId varchar(22) not null,
+ sequenceNumber int(11) not null,
+ dateCreated bigint(20) not null,
+ createdBy varchar(22) not null,
+ dateUpdated bigint(20) not null,
+ updatedBy varchar(22) not null,
+ label varchar(255) not null,
+ fieldType varchar(255) not null,
+ defaultValue varchar(255),
+ possibleValues varchar(255),
+ subText varchar(255),
+ status varchar(255) not null,
+ width int(11),
+ height int(11),
+ vertical smallint(1),
+ extras varchar(255),
+ display int(11),
+ viewScreenTitle int(11),
+ displayInSearch int(11),
+ searchIn int(11),
+ fieldInOtherThingId varchar(22),
+ primary key (fieldId, thingId, assetId)
+ )
+ENDSQL
+
+ $session->config->addToArray("assets","WebGUI::Asset::Wobject::Thingy");
+
+ print "DONE!\n" unless $quiet;
+}
# --------------- DO NOT EDIT BELOW THIS LINE --------------------------------
#----------------------------------------------------------------------------
diff --git a/etc/WebGUI.conf.original b/etc/WebGUI.conf.original
index da9ca791d..0cd05b2c9 100644
--- a/etc/WebGUI.conf.original
+++ b/etc/WebGUI.conf.original
@@ -235,8 +235,9 @@
"WebGUI::Asset::Wobject::InOutBoard",
"WebGUI::Asset::File::ZipArchive",
"WebGUI::Asset::Wobject::WSClient",
- "WebGUI::Asset::Wobject::SQLForm"
- ],
+ "WebGUI::Asset::Wobject::SQLForm",
+ "WebGUI::Asset::Wobject::Thingy"
+ ],
# Specify the list assets that are used for utility purposes only
# and are not typically used as a normal part of content
diff --git a/lib/WebGUI/Asset/Wobject/Thingy.pm b/lib/WebGUI/Asset/Wobject/Thingy.pm
new file mode 100644
index 000000000..e0197f4da
--- /dev/null
+++ b/lib/WebGUI/Asset/Wobject/Thingy.pm
@@ -0,0 +1,2544 @@
+package WebGUI::Asset::Wobject::Thingy;
+
+#-------------------------------------------------------------------
+# WebGUI is Copyright 2001-2006 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
+#-------------------------------------------------------------------
+
+use strict;
+use warnings;
+use version;
+use Tie::IxHash;
+use WebGUI::International;
+use WebGUI::Utility;
+use WebGUI::Text;
+use WebGUI::Form::File;
+use base 'WebGUI::Asset::Wobject';
+
+our $VERSION = "1.0.0";
+
+#-------------------------------------------------------------------
+
+=head2 addField ( field, retainIds )
+
+Adds a new field.
+
+=head3
+
+A hashref containing the properties of the new field.
+
+=head3
+
+If retainIds is true the new field will keep the fieldId and assetId in the properties hashref. The thingId is
+always taken from the field hashref.
+
+=cut
+
+sub addField {
+
+ my $self = shift;
+ my $field = shift;
+ my $retainIds = shift;
+ my $dbDataType = shift || $self->_getDbDataType($field->{fieldType});
+ my $db = $self->session->db;
+ my $error = $self->session->errorHandler;
+ my ($oldFieldId, $newFieldId,$useAssetId,$useSequence);
+
+ $error->info("Adding Field, label: ".$field->{label}.", fieldId: ".$field->{fieldId}.",thingId: ".$field->{thingId});
+
+ if ($retainIds){
+ $oldFieldId = $field->{fieldId};
+ }
+ else {
+ $useAssetId = 1;
+ #$useSequence = 1;
+ }
+
+ $field->{fieldId} = "new";
+ $newFieldId = $self->setCollateral("Thingy_fields","fieldId",$field,1,$useAssetId);
+
+ if ($retainIds){
+ $db->write("update Thingy_fields set fieldId = ".$db->quote($oldFieldId)
+ ." where fieldId = ".$db->quote($newFieldId));
+ $newFieldId = $oldFieldId;
+ }
+
+ my $thingyTableName = "Thingy_".$field->{thingId};
+ my $columnName = "field_".$newFieldId;
+ $db->write(
+ "ALTER TABLE ".$db->dbh->quote_identifier($thingyTableName)
+ ." ADD ".$db->dbh->quote_identifier($columnName)." ". $dbDataType
+ );
+
+ return $newFieldId;
+}
+
+#-------------------------------------------------------------------
+
+=head2 addThing ( thing, retainIds )
+
+Adds a new thing.
+
+=head3
+
+A hashref containing the properties of the new thing.
+
+=head3
+
+If retainIds is true the new thing will keep the thingId and assetId in the properties hashref.
+
+=cut
+
+sub addThing {
+
+ my $self = shift;
+ my $thing = shift;
+ my $retainIds = shift;
+ my $db = $self->session->db;
+ my $error = $self->session->errorHandler;
+ my ($oldThingId, $newThingId,$useAssetId);
+
+ $error->info("Adding Thing, label: ".$thing->{label}.", id: ".$thing->{thingId});
+
+ if ($retainIds){
+ $oldThingId = $thing->{thingId};
+ }
+ else{
+ $useAssetId = 1;
+ }
+
+ $thing->{thingId} = "new";
+ $newThingId = $self->setCollateral("Thingy_things","thingId",$thing,0,$useAssetId);
+
+ if ($retainIds){
+ $db->write("update Thingy_things set thingId = ".$db->quote($oldThingId)
+ ." where thingId = ".$db->quote($newThingId));
+ $newThingId = $oldThingId;
+ }
+
+ $db->write("create table ".$db->dbh->quote_identifier("Thingy_".$newThingId)."(
+ thingDataId varchar(22) binary not null,
+ dateCreated int not null,
+ createdById varchar(22) not null,
+ updatedById varchar(22) not null,
+ updatedByName varchar(255) not null,
+ lastUpdated int not null,
+ ipAddress varchar(255),
+ primary key (thingDataId)
+ )");
+
+ return $newThingId;
+}
+
+#-------------------------------------------------------------------
+
+=head2 definition ( )
+
+defines wobject properties for Thingy instances. If you choose to "autoGenerateForms", the
+getEditForm method is unnecessary/redundant/useless.
+
+=cut
+
+sub definition {
+ my $class = shift;
+ my $session = shift;
+ my $definition = shift;
+ my $i18n = WebGUI::International->new($session, 'Asset_Thingy');
+ my %properties;
+ tie %properties, 'Tie::IxHash';
+
+ %properties = (
+ templateId =>{
+ fieldType=>"template",
+ defaultValue=>'ThingyTmpl000000000001',
+ tab=>"display",
+ noFormPost=>0,
+ namespace=>"Thingy",
+ hoverHelp=>$i18n->get('templateId label description'),
+ label=>$i18n->get('templateId label'),
+ },
+ defaultThingId => {
+ autoGenerate => 0,
+ default=>undef,
+ fieldType=>"selectBox",
+ },
+ );
+ push(@{$definition}, {
+ assetName=>$i18n->get('assetName'),
+ icon=>'Thingy.gif',
+ autoGenerateForms=>1,
+ tableName=>'Thingy',
+ className=>'WebGUI::Asset::Wobject::Thingy',
+ properties=>\%properties
+ });
+ return $class->SUPER::definition($session, $definition);
+}
+
+
+#-------------------------------------------------------------------
+
+=head2 duplicate ( )
+
+Duplicates a Thingy, including the definitions of the Things in this Thingy and their fields.
+
+=cut
+
+sub duplicate {
+ my $self = shift;
+ my $options = shift;
+ my $newAsset = $self->SUPER::duplicate($options);
+ my $db = $self->session->db;
+ my $assetId = $self->get("assetId");
+ my $fields;
+
+ my $things = $db->buildArrayRefOfHashRefs('select * from Thingy_things where assetId = ?',[$assetId]);
+ foreach my $thing (@$things) {
+ my $oldThingId = $thing->{thingId};
+ my $newThingId = $newAsset->addThing($thing,0);
+ $fields = $db->buildArrayRefOfHashRefs('select * from Thingy_fields where assetId=? and thingId=?'
+ ,[$assetId,$oldThingId]);
+ foreach my $field (@$fields) {
+ # set thingId to newly created thing's id.
+ $field->{thingId} = $newThingId;
+
+ $newAsset->addField($field,0);
+ }
+ }
+ return $newAsset;
+}
+
+#-------------------------------------------------------------------
+
+=head2 exportAssetData ( )
+
+See WebGUI::AssetPackage::exportAssetData() for details.
+
+=cut
+
+sub exportAssetData {
+ my $self = shift;
+ my $data = $self->SUPER::exportAssetData;
+ my $db = $self->session->db;
+ my $assetId = $self->get("assetId");
+
+ $data->{things} = $db->buildArrayRefOfHashRefs('select * from Thingy_things where assetId = ?',[$assetId]);
+ $data->{fields} = $db->buildArrayRefOfHashRefs('select * from Thingy_fields where assetId = ?',[$assetId]);
+
+ return $data;
+}
+#-------------------------------------------------------------------
+
+=head2 deleteField ( fieldId , thingId )
+
+Deletes a field from Collateral and drops the fields column in the thingy table.
+
+=head3 fieldId
+
+The id of the field that should be deleted.
+
+=head3 thingId
+
+The id of the thing to which the field to be deleted belongs.
+
+=cut
+
+sub deleteField {
+
+ my $self = shift;
+ my $fieldId = shift;
+ my $thingId = shift;
+ my $db = $self->session->db;
+ my $error = $self->session->errorHandler;
+
+ my ($deletedSequenceNumber) = $db->quickArray("select sequenceNumber from Thingy_fields where fieldId = ?",[$fieldId]);
+ $self->deleteCollateral("Thingy_fields","fieldId",$fieldId);
+ $db->write("update Thingy_fields set sequenceNumber = sequenceNumber -1 where sequenceNumber > ?",[$deletedSequenceNumber]);
+
+ my ($columnExists) = $db->quickArray("show columns from ".$db->dbh->quote_identifier("Thingy_".$thingId)
+ ." like ".$db->quote("field_".$fieldId));
+ if ($columnExists){
+ $db->write("ALTER TABLE ".$db->dbh->quote_identifier("Thingy_".$thingId)." DROP "
+ .$db->dbh->quote_identifier("field_".$fieldId));
+ }
+ $error->info("Deleted field: $fieldId in thing: $thingId.");
+ return undef;
+}
+
+#-------------------------------------------------------------------
+
+=head2 deleteThing ( thingId )
+
+Deletes a Thing and its fields from Collateral and drops the things table.
+
+=head3 thingId
+
+The id of the Thing that should be deleted.
+
+=cut
+
+sub deleteThing {
+
+ my $self = shift;
+ my $thingId = shift;
+ my $session = $self->session;
+ my $error = $session->errorHandler;
+
+ $self->deleteCollateral("Thingy_things","thingId",$thingId);
+ $self->deleteCollateral("Thingy_fields","thingId",$thingId);
+ $self->session->db->write("drop table if exists ".$session->db->dbh->quote_identifier("Thingy_".$thingId));
+
+ $error->info("Deleted thing: $thingId.");
+ return undef;
+}
+
+#-------------------------------------------------------------------
+
+=head2 _getDbDataType ( fieldType )
+
+returns the database data type for a field based on the fieldType.
+
+=head3 fieldType
+
+The fieldType for which the database data type should be returned.
+
+=cut
+
+sub _getDbDataType {
+
+ my $self = shift;
+ my $fieldType = shift;
+ my $session = $self->session;
+
+ my ($dbDataType, $formClass);
+
+ if ($fieldType =~ m/^otherThing/x){
+ $dbDataType = "varchar(22)";
+ }
+ else{
+ $formClass = 'WebGUI::Form::' . ucfirst $fieldType;
+ my $formElement = eval { WebGUI::Pluggable::instanciate($formClass, "new", [$session]) };
+ $dbDataType = $formElement->get("dbDataType");
+ }
+ return $dbDataType;
+
+}
+
+#-------------------------------------------------------------------
+
+=head2 getEditFieldForm ( )
+
+returns the form that will be used in the edit dialog for Thingy_fields.
+
+=cut
+
+sub getEditFieldForm {
+
+ my $self = shift;
+ my $field = shift;
+ my (%fieldStatus, $f, %fieldTypes, $things);
+ my $fieldId = $field->{fieldId} || "new";
+ my $i18n = WebGUI::International->new($self->session, 'Asset_Thingy');
+ my $defaultValue;
+ tie %fieldStatus, 'Tie::IxHash';
+ tie %fieldTypes, 'Tie::IxHash';
+
+ %fieldStatus = (
+ "hidden" => $i18n->get('fieldstatus hidden label'),
+ "visible" => $i18n->get('fieldstatus visible label'),
+ "editable" => $i18n->get('fieldstatus editable label'),
+ "required" => $i18n->get('fieldstatus required label'),
+ );
+
+ %fieldTypes = (
+ "dateTime" => "dateTime",
+ "TimeField" => "TimeField",
+ "float" => "float",
+ "zipcode" => "zipcode",
+ "text" => "text",
+ "textarea" => "textarea",
+ "HTMLArea" => "HTMLArea",
+ "url" => "url",
+ "date" => "date",
+ "email" => "email",
+ "phone" => "phone",
+ "integer" => "integer",
+ "yesNo" => "yesNo",
+ "selectList" => "select",
+ "radioList" => "radioList",
+ "checkList" => "checkList",
+ "selectBox" => "selectBox",
+ "file" => "file",
+ );
+
+ $things = $self->session->db->read('select thingId, label from Thingy_things left join asset using(assetId)
+where asset.state = "published"');
+ while (my $thing = $things->hashRef) {
+ my $fieldType = "otherThing_".$thing->{thingId};
+ $fieldTypes{$fieldType} = $thing->{label};
+ }
+
+ my $dialogPrefix;
+ if ($fieldId eq "new"){
+ $dialogPrefix = "addDialog";
+ }
+ else{
+ $dialogPrefix = "edit_".$fieldId."_Dialog";
+ }
+
+ $f = WebGUI::HTMLForm->new($self->session,{
+ action=>$self->getUrl,
+ tableExtras=>' cellpadding="0" cellspacing="0"'
+ });
+ $f->hidden(
+ -name => "fieldId",
+ -value => $fieldId,
+ );
+ $f->hidden(
+ -name => "thingId",
+ -value => $field->{thingId},
+ );
+ $f->hidden(
+ -name => "func",
+ -value => "editFieldSave"
+ );
+ $f->text(
+ -name=>"label",
+ -label=>$i18n->get('field label label'),
+ -hoverHelp=>$i18n->get('field label description'),
+ -value=>$field->{label}
+ );
+ $f->selectBox(
+ -name=>"fieldType",
+ -label=>$i18n->get('field type label'),
+ -hoverHelp=>$i18n->get('field type description'),
+ -value=>$field->{fieldType} || "text",
+ -options=>\%fieldTypes,
+ -id=>$dialogPrefix."_fieldType_formId",
+ );
+ $f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_fieldInThing_module"));
+
+ $f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_defaultFieldInThing_module"));
+ unless ($field->{fieldType} =~ m/^otherThing/x){
+ $defaultValue = $field->{defaultValue};
+ }
+ my $defaultValueForm = WebGUI::Form::Textarea($self->session, {
+ name=>"defaultValue",
+ label=>$i18n->get('default value label'),
+ hoverHelp=>$i18n->get('default value description'),
+ value=>$defaultValue,
+ subtext=>' '.$i18n->get(85),
+ width=>200,
+ height=>60,
+ resizable=>0,
+ });
+ $f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_defaultValue_module",$defaultValueForm,$i18n->get('default value label')));
+
+ $f->text(
+ -name=>"subtext",
+ -value=>$field->{subtext},
+ -label=>$i18n->get('subtext label'),
+ -hoverHelp=>$i18n->get('subtext description'),
+ );
+ $f->selectBox(
+ -name=>"status",
+ -options=>\%fieldStatus,
+ -label=>$i18n->get('field status label'),
+ -hoverHelp=>$i18n->get('field status description'),
+ -value=> [ $field->{status} || "editable" ] ,
+ );
+
+ my $widthForm = WebGUI::Form::Integer($self->session, {
+ name=>"width",
+ hoverHelp=>$i18n->get('width description'),
+ value=>($field->{width} || 250),
+ size=>10,
+ });
+ $f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_width_module",$widthForm,$i18n->get('width label')));
+
+ my $sizeForm = WebGUI::Form::Integer($self->session, {
+ name=>"size",
+ hoverHelp=>$i18n->get('size description'),
+ value=>($field->{size} || 25),
+ size=>10,
+ });
+ $f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_size_module",$sizeForm,$i18n->get('size label')));
+
+ my $heightForm = WebGUI::Form::Integer($self->session, {
+ name=>"height",
+ value=>$field->{height} || 40,
+ label=>$i18n->get('height label'),
+ hoverHelp=>$i18n->get('height description'),
+ size=>10,
+ });
+ $f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_height_module",$heightForm,$i18n->get('height label')));
+
+ my $verticalForm = WebGUI::Form::YesNo($self->session, {
+ name=>"vertical",
+ value=>$field->{vertical},
+ label=>$i18n->get('vertical label'),
+ hoverHelp=>$i18n->get('vertical description'),
+ });
+ $f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_vertical_module",$verticalForm,$i18n->get('vertical label')));
+
+ my $valuesForm = WebGUI::Form::Textarea($self->session, {
+ name=>"possibleValues",
+ hoverHelp=>$i18n->get('possible values description'),
+ value=>$field->{possibleValues},
+ width=>200,
+ height=>60,
+ resizable=>0,
+ });
+ $f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_values_module",$valuesForm,$i18n->get('possible values label')));
+ $f->text(
+ -name=>"extras",
+ -value=>$field->{extras},
+ -label=>$i18n->get('extras label'),
+ -hoverHelp=>$i18n->get('extras description'),
+ );
+ return $f;
+}
+#-------------------------------------------------------------------
+
+=head2 getEditForm ( )
+
+Returns the tabform object that will be used in generating the edit page for Thingy's.
+Adds the defaultThingId selectBox to the tabform object, because the options for this selectBox depends on already
+existing Things. The rest of the form is auto-generated.
+
+=cut
+
+sub getEditForm {
+
+ my $self = shift;
+ my $i18n = WebGUI::International->new($self->session, 'Asset_Thingy');
+ my $tabform = $self->SUPER::getEditForm();
+
+ my $things = $self->session->db->buildHashRef('select thingId, label from Thingy_things where assetId = ?',[$self->get("assetId")]);
+
+ $tabform->getTab("display")->selectBox(
+ -name=>"defaultThingId",
+ -value=>$self->get("defaultThingId"),
+ -label=>$i18n->get("default thing label"),
+ -options=>$things,
+ );
+
+ return $tabform;
+}
+#-------------------------------------------------------------------
+
+=head2 getFieldValue ( value, field )
+
+Processes the field value for date(Time) fields and Other Thing fields.
+
+=head3 value
+
+The value as stored in the database.
+
+=head field
+
+A reference to a hash containing the fields properties.
+
+=cut
+
+sub getFieldValue {
+
+ my $self = shift;
+ my $value = shift;
+ my $field = shift;
+ my $dateFormat = shift || "%z";
+ my $dateTimeFormat = shift;
+ my $processedValue = $value;
+ my $dbh = $self->session->db->dbh;
+
+ if ($field->{fieldType} eq "date"){
+ $processedValue = $self->session->datetime->epochToHuman($value,$dateFormat);
+ }
+ elsif ($field->{fieldType} eq "dateTime"){
+ $processedValue = $self->session->datetime->epochToHuman($value,$dateTimeFormat);
+ }
+ elsif ($field->{fieldType} =~ m/^otherThing/x) {
+ my $otherThingId = $field->{fieldType};
+ $otherThingId =~ s/^otherThing_//x;
+ ($processedValue) = $self->session->db->quickArray('select '
+ .$dbh->quote_identifier('field_'.$field->{fieldInOtherThingId})
+ .' from '.$dbh->quote_identifier('Thingy_'.$otherThingId)
+ .' where thingDataId = ?',[$value]);
+ }
+ elsif ($field->{fieldType} eq "file") {
+ $processedValue = WebGUI::Form::File->new($self->session,{value=>$value})->displayValue();
+ }
+
+ return $processedValue;
+
+}
+
+#-------------------------------------------------------------------
+
+=head2 getFormElement ( data )
+
+Returns the form element tied to this field.
+
+=head3 data
+
+A hashref containing the properties of this field.
+
+=cut
+
+sub getFormElement {
+
+ my $self = shift;
+ my $data = shift;
+ my %param;
+ my $dbh = $self->session->db->dbh;
+
+ $param{name} = "field_".$data->{fieldId};
+ my $name = $param{name};
+ $name =~ s/\^.*?\;//xgs ; # remove macro's from user input
+ $param{value} = $data->{value} || $data->{defaultValue};
+ $param{size} = $data->{size};
+ $param{height} = $data->{height};
+ $param{width} = $data->{width};
+ $param{extras} = $data->{extras};
+ $param{vertical} = $data->{vertical};
+ $param{fieldType} = $data->{fieldType};
+
+ if ($data->{fieldType} eq "checkbox") {
+ $param{value} = ($data->{defaultValue} =~ /checked/xi) ? 1 : "";
+ }
+ if (WebGUI::Utility::isIn($data->{fieldType},qw(selectList checkList selectBox))) {
+ my @defaultValues;
+ if ($self->session->form->param($name)) {
+ @defaultValues = $self->session->form->selectList($name);
+ } else {
+ foreach (split(/\n/x, $data->{value})) {
+ s/\s+$//x; # remove trailing spaces
+ push(@defaultValues, $_);
+ }
+ }
+ $param{value} = \@defaultValues;
+ }
+ if (WebGUI::Utility::isIn($data->{fieldType},qw(selectList selectBox checkList radioList))) {
+ delete $param{size};
+ my %options;
+ tie %options, 'Tie::IxHash';
+ foreach (split(/\n/x, $data->{possibleValues})) {
+ s/\s+$//x; # remove trailing spaces
+ $options{$_} = $_;
+ }
+ $param{options} = \%options;
+ }
+ if ($data->{fieldType} eq "yesNo") {
+ if ($data->{defaultValue} =~ /yes/xi) {
+ $param{value} = 1;
+ } elsif ($data->{defaultValue} =~ /no/xi) {
+ $param{value} = 0;
+ }
+ }
+ if ($data->{fieldType} =~ m/^otherThing/x){
+ my $otherThingId = $data->{fieldType};
+ $otherThingId =~ s/^otherThing_(.*)/$1/x;
+ $param{fieldType} = "SelectList";
+
+ my $options = $self->session->db->buildHashRef('select thingDataId, '
+ .$dbh->quote_identifier('field_'.$data->{fieldInOtherThingId})
+ .' from '.$dbh->quote_identifier('Thingy_'.$otherThingId));
+
+ my $value = $data->{value} || $data->{defaultValue};
+ ($param{value}) = $self->session->db->quickArray('select '
+ .$dbh->quote_identifier('field_'.$data->{fieldInOtherThingId})
+ .' from '.$dbh->quote_identifier('Thingy_'.$otherThingId)
+ .' where thingDataId = ?',[$value]);
+ $param{size} = 1;
+ $param{multiple} = 0;
+ $param{options} = $options;
+ $param{value} = $data->{value} || $data->{defaultValue};
+ }
+
+ my $formElement = eval { WebGUI::Pluggable::instanciate("WebGUI::Form::". ucfirst $param{fieldType}, "new", [$self->session, \%param ])};
+ return $formElement->toHtml();
+
+}
+
+#-------------------------------------------------------------------
+
+=head2 getHtmlWithModuleWrapper ( id , formElement, formDescription )
+
+Returns a table row containing a form element in a yui module.
+
+=head3 id
+
+An id for the module div.
+
+=head3 formElement
+
+The from element rendered as html.
+
+=head3 formDescription
+
+The description of the from element.
+
+=cut
+
+sub getHtmlWithModuleWrapper {
+
+ my $self = shift;
+ my $id = shift;
+ my $formElement = shift;
+ my $formDescription = shift;
+
+ my $html = "\n
\n";
+ $html .= "\t
\n";
+ $html .= "\t
\n";
+ $html .= "\t
\n";
+ $html .= "\t
";
+ $html .= $formDescription."
";
+ $html .= "
";
+ $html .= $formElement."
";
+ $html .= "\t\n
\n";
+ $html .= "\t
";
+ $html .= "\t\n
\t\n
\n";
+ $html .= "
";
+
+ return $html;
+
+}
+
+#-------------------------------------------------------------------
+
+=head2 hasPrivileges ( groupId )
+
+Checks if the current user has a certain privilege on a Thing.
+A user that can edit a Thingy asset has all rights on every Thing by definition.
+
+=head3 groupId
+
+The id of the group that has the privileges that are to be checked.
+
+=cut
+
+sub hasPrivileges {
+
+ my $self = shift;
+ my $privilegedGroupId = shift;
+ return ($self->canEdit || $self->session->user->isInGroup($privilegedGroupId));
+
+}
+#-------------------------------------------------------------------
+
+=head2 importAssetCollateralData ( data )
+
+Imports Things and fields that where exported with a Thingy asset.
+
+=head3 data
+
+Hashref containing the Thingy's exported data.
+
+=cut
+
+sub importAssetCollateralData {
+
+ my $self = shift;
+ my $session = $self->session;
+ my $error = $session->errorHandler;
+ my $data = shift;
+ my $id = $data->{properties}{assetId};
+ my $class = $data->{properties}{className};
+ my $version = $data->{properties}{revisionDate};
+ my $assetExists = WebGUI::Asset->assetExists($self->session, $id, $class, $version);
+
+ $error->info("Importing Things for Thingy ".$data->{properties}{title});
+ my @importThings;
+ foreach my $thing (@{$data->{things}}) {
+ push(@importThings,$thing->{thingId});
+ my ($thingIdExists) = $session->db->quickArray("select thingId from Thingy_things where thingId = ".
+ $session->db->quote($thing->{thingId}));
+ if ($assetExists && $thingIdExists){
+ # update existing thing
+ $error->info("Updating Thing, label: ".$thing->{label}.", id: ".$thing->{thingId});
+ $self->setCollateral("Thingy_things","thingId",$thing,0,0);
+ }
+ else{
+ # add new thing
+ $self->addThing($thing,1);
+ }
+ }
+ # delete deleted things
+ my $thingsInDatabase = $session->db->read('select thingId from Thingy_things where assetId=?',[$self->get("assetId")]);
+ while (my $thingInDataBase = $thingsInDatabase->hashRef) {
+ if (!WebGUI::Utility::isIn($thingInDataBase->{thingId},@importThings)){
+ # delete thing
+ $self->deleteThing($thingInDataBase->{thingId});
+ }
+ }
+
+ my @importFields;
+ foreach my $field (@{$data->{fields}}) {
+ push(@importFields,$field->{fieldId});
+ my $dbDataType = $self->_getDbDataType($field->{fieldType});
+ my ($fieldIdExists) = $session->db->quickArray("select fieldId from Thingy_fields where fieldId = ? and thingId = ? ",[$field->{fieldId},$field->{thingId}]);
+ if ($assetExists && $fieldIdExists){
+ # update existing field
+ $error->info("Updating Field, label: ".$field->{label}.", id: ".$field->{fieldId});
+ $self->_updateFieldType($field->{fieldType},$field->{fieldId},$field->{thingId},$field->{assetId},$dbDataType);
+ $self->setCollateral("Thingy_fields","fieldId",$field,1,0);
+ }
+ else{
+ # Add field as Collateral, retain fieldId.
+ $self->addField($field,1,$dbDataType);
+ }
+ }
+ # delete deleted fields
+ my $fieldsInDatabase = $session->db->read('select fieldId, thingId from Thingy_fields where assetId = '
+ .$session->db->quote($self->get("assetId")));
+ while (my $fieldInDataBase = $fieldsInDatabase->hashRef) {
+ if (!WebGUI::Utility::isIn($fieldInDataBase->{fieldId},@importFields)){
+ # delete field
+ $self->deleteField($fieldInDataBase->{fieldId},$fieldInDataBase->{thingId});
+ }
+ }
+
+ return undef;
+}
+
+
+#-------------------------------------------------------------------
+
+=head2 prepareView ( )
+
+See WebGUI::Asset::prepareView() for details.
+
+=cut
+
+sub prepareView {
+ my $self = shift;
+ $self->SUPER::prepareView();
+ my $template = WebGUI::Asset::Template->new($self->session, $self->get("templateId"));
+ $template->prepare;
+ $self->{_viewTemplate} = $template;
+ return undef;
+}
+
+
+#-------------------------------------------------------------------
+
+=head2 purge ( )
+
+Removes collateral data and drops tables associated with a Thingy asset when the system
+purges it's data.
+
+=cut
+
+sub purge {
+ my $self = shift;
+ my $session = $self->session;
+
+ my @thingIds = $session->db->buildArray("select thingId from Thingy_things where assetId = "
+ .$session->db->quote($self->getId));
+ foreach my $thingId (@thingIds){
+ $session->db->write("drop table if exists ".$session->db->dbh->quote_identifier("Thingy_".$thingId));
+ }
+ $self->session->db->write("delete from Thingy_things where assetId = ".$self->session->db->quote($self->getId));
+ $self->session->db->write("delete from Thingy_fields where assetId = ".$self->session->db->quote($self->getId));
+
+ return $self->SUPER::purge;
+}
+
+#-------------------------------------------------------------------
+
+=head2 triggerWorkflow ( workflowId, )
+
+Alters a column for a field if the field's fieldType has changed.
+
+=head3 workflowId
+
+The id of the workflow that has to be triggered.
+
+=cut
+
+sub triggerWorkflow {
+
+ my $self = shift;
+ my $workflowId = shift;
+ my $instance = WebGUI::Workflow::Instance->create($self->session, {
+ workflowId=>$workflowId,
+ className=>"WebGUI::Asset::Wobject::Thingy",
+ methodName=>"new",
+ parameters=>$self->getId
+ });
+
+ # deal with realtime
+ if ($instance->getWorkflow->isRealtime) {
+ my $status = $instance->runAll;
+ if ($status eq "done") {
+ $instance->delete;
+ } else {
+ my $errorMessage = "Realtime workflow instance ".$instance->getId." returned status ".$status." where
+ 'done' was expected";
+ $self->session->errorHandler->warn($errorMessage);
+ return $errorMessage;
+ }
+ }
+ return undef;
+}
+
+#-------------------------------------------------------------------
+
+=head2 _updateFieldType ( fieldType, fieldId, thingId, assetId, dbDataType )
+
+Alters a column for a field if the field's fieldType has changed.
+
+=head3 fieldType
+
+The new fieldType for the field that has to be changed.
+
+=head3 fieldId
+
+The id of the field of which should be changed.
+
+=head3 thingId
+
+The id of the Thing to which the field belongs.
+
+=head3 assetId
+
+The id of the Thingy asset to which the field belongs.
+
+=head3 dbDataType
+
+The data type that the field should have in the database.
+
+=cut
+
+sub _updateFieldType {
+
+ my $self = shift;
+ my $session = $self->session;
+ my $error = $session->errorHandler;
+
+ my $newFieldType = shift;
+ my $fieldId = shift;
+ my $thingId = shift;
+ my $assetId = shift;
+ my $dbDataType = shift;
+
+ my ($fieldType) = $self->session->db->quickArray("select fieldType from Thingy_fields where fieldId = "
+ .$self->session->db->quote($fieldId)." and assetId = ".$self->session->db->quote($assetId)
+ ." and thingId = ".$self->session->db->quote($thingId));
+
+ if($newFieldType ne $fieldType){
+ my $thingyTableName = "Thingy_".$thingId;
+ my $columnName = "field_".$fieldId;
+ $error->info("changing column: $columnName, table: $thingyTableName");
+ $self->session->db->write(
+ "ALTER TABLE ".$session->db->dbh->quote_identifier($thingyTableName).
+ " CHANGE ".$self->session->db->dbh->quote_identifier($columnName)." "
+ .$session->db->dbh->quote_identifier($columnName)." ".$dbDataType
+ );
+ }
+ return undef;
+}
+#-------------------------------------------------------------------
+
+=head2 view ( )
+
+method called by the www_view method. Returns a processed template
+to be displayed within the page style.
+
+=cut
+
+sub view {
+ my $self = shift;
+ my $session = $self->session;
+ my $db = $self->session->db;
+ my $i18n = WebGUI::International->new($self->session,"Asset_Thingy");
+ my ($defaultThingId,$defaultView);
+
+ my $var = $self->get;
+ my $url = $self->getUrl;
+
+ $var->{canEditThings} = $self->canEdit;
+ $var->{"addThing_url"} = $session->url->append($url, 'func=editThing;thingId=new');
+ $var->{"manage_url"} = $session->url->append($url, 'func=manage');
+
+ #Get this Thingy's default thing
+ $defaultThingId = $self->get("defaultThingId");
+
+ if ($defaultThingId){
+ # get default view
+ ($defaultView) = $db->quickArray("select defaultView from Thingy_things where thingId=?",[$defaultThingId]);
+ if ($defaultView eq "searchThing"){
+ return $self->www_search($defaultThingId);
+ }
+ elsif ($defaultView eq "addThing"){
+ return $self->www_editThingData($defaultThingId,"new");
+ }
+ }
+ else{
+ return $self->processTemplate($var, undef, $self->{_viewTemplate});
+ }
+}
+#-------------------------------------------------------------------
+
+=head2 www_deleteFieldConfirm ( )
+
+Deletes a field definition. Drops the column of a Thing's table that holds the data of this field.
+
+=cut
+
+sub www_deleteFieldConfirm {
+ my $self = shift;
+ my $session = $self->session;
+ my $fieldId = $session->form->process("fieldId");
+ my $thingId = $session->form->process("thingId");
+ return $session->privilege->insufficient() unless $self->canEdit;
+
+ $self->deleteField($fieldId,$thingId);
+
+ return 1;
+}
+#-------------------------------------------------------------------
+
+=head2 www_deleteThingConfirm ( )
+
+Deletes a Thing, including field definitions and instances of this Thing and drops the table that holds the
+instances of this Thing.
+
+=cut
+
+sub www_deleteThingConfirm {
+ my $self = shift;
+ my $thingId = $self->session->form->process("thingId");
+ return $self->session->privilege->insufficient() unless $self->canEdit;
+
+ $self->deleteThing($thingId);
+
+ return $self->www_manage;
+}
+#-------------------------------------------------------------------
+
+=head2 www_deleteThingDataConfirm ( )
+
+Deletes data in a Thing.
+
+=cut
+
+sub www_deleteThingDataConfirm {
+
+ my $self = shift;
+ my $db = $self->session->db;
+
+ my $thingId = $self->session->form->process("thingId");
+ my $thingDataId = $self->session->form->process('thingDataId');
+
+ my ($groupIdEdit) = $db->quickHash("select groupIdEdit from Thingy_things where thingId=?",[$thingId]);
+ return $self->session->privilege->insufficient() unless $self->hasPrivileges($groupIdEdit);
+
+ $self->deleteCollateral("Thingy_".$thingId,"thingDataId",$thingDataId);
+
+ my ($onDeleteWorkflowId) = $db->quickArray("select onDeleteWorkflowId from Thingy_things where thingId=?"
+ ,[$thingId]);
+ if ($onDeleteWorkflowId){
+ $self->triggerWorkflow($onDeleteWorkflowId);
+ }
+
+ return $self->www_search;
+}
+
+#-------------------------------------------------------------------
+
+=head2 www_editThing ( )
+
+Shows a form to edit or add a Thing. General properties of a Thing are stored when the form is submitted.
+
+When editing fields in a thing some changes are saved immediately. Because of this a table has to be created for a new Thing
+before the form is submitted.
+
+=cut
+
+sub www_editThing {
+ my $self = shift;
+ my $session = $self->session;
+ my ($tabForm, $output, %properties, $tab, %afterSave, %defaultView, $fields);
+ my ($fieldsHTML, $fieldsViewScreen, $fieldsSearchScreen);
+ tie %afterSave, 'Tie::IxHash';
+ return $session->privilege->insufficient() unless $self->canEdit;
+ my $i18n = WebGUI::International->new($session, "Asset_Thingy");
+
+ my $thingId = $session->form->process("thingId");
+ return $self->www_view unless ($thingId);
+
+ if($thingId eq "new"){
+ my $groupIdEdit = $self->get("groupIdEdit");
+ %properties = (
+ thingId=>$thingId,
+ label=>$i18n->get('assetName'),
+ editScreenTitle=>$i18n->get('edit screen title label'),
+ groupIdAdd=>$groupIdEdit,
+ groupIdEdit=>$groupIdEdit,
+ saveButtonLabel=>$i18n->get('default save button label'),
+ afterSave=>'searchThisThing',
+ editTemplateId=>"ThingyTmpl000000000003",
+ groupIdView=>$groupIdEdit,
+ viewTemplateId=>"ThingyTmpl000000000002",
+ defaultView=>'searchThing',
+ searchScreenTitle=>$i18n->get('search screen title label'),
+ groupIdSearch=>$groupIdEdit,
+ groupIdExport=>$groupIdEdit,
+ groupIdImport=>$groupIdEdit,
+ searchTemplateId=>"ThingyTmpl000000000004",
+ thingsPerPage=>25,
+ );
+ $thingId = $self->addThing(\%properties,0);
+ }
+ else{
+ %properties = $self->session->db->quickHash("select * from Thingy_things where thingId=".$self->session->db->quote($thingId));
+ }
+
+ $tabForm = WebGUI::TabForm->new($self->session, undef, undef, $self->getUrl('func=view'));
+ $tabForm->hidden({
+ name => 'func',
+ value => 'editThingSave'
+ });
+ $tabForm->hidden({
+ name => 'thingId',
+ value => $thingId
+ });
+
+ $tabForm->addTab('fields', $i18n->get('fields tab label'));
+
+ $self->session->style->setScript($self->session->url->extras('yui/build/utilities/utilities.js'), {type =>
+ 'text/javascript'});
+ $self->session->style->setScript($self->session->url->extras('yui/build/yahoo-dom-event/yahoo-dom-event.js'), {type=>
+ 'text/javascript'});
+ $self->session->style->setScript($self->session->url->extras('yui/build/connection/connection-min.js'), {type =>
+ 'text/javascript'});
+ $self->session->style->setScript($self->session->url->extras('wobject/Thingy/thingy.js'), {type=>
+ 'text/javascript'});
+ $self->session->style->setLink($self->session->url->extras('wobject/Thingy/thingy.css'), {type
+ =>'text/css', rel=>'stylesheet'});
+
+ $tab = $tabForm->getTab('fields');
+
+ $tab->text(
+ -name => 'label',
+ -label => $i18n->get('thing name label'),
+ -hoverHelp => $i18n->get('thing name description'),
+ -value => $properties{label},
+ -maxlength => 64,
+ );
+
+ $fieldsHTML = "\n"
+ ."\n"
+ ."