package WebGUI::Asset::Wobject::Thingy;
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2008 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 JSON;
use WebGUI::International;
use WebGUI::Utility;
use WebGUI::Text;
use WebGUI::Form::File;
use WebGUI::DateTime;
use base 'WebGUI::Asset::Wobject';
#-------------------------------------------------------------------
=head2 addField ( field, isImport )
Adds a new field.
=head3 field
A hashref containing the properties of the new field.
=head3 isImport
If isImport 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 $isImport = 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 ($isImport){
$oldFieldId = $field->{fieldId};
}
else {
$useAssetId = 1;
#$useSequence = 1;
}
$field->{fieldId} = "new";
$newFieldId = $self->setCollateral("Thingy_fields","fieldId",$field,1,$useAssetId);
if ($isImport){
$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, isImport )
Adds a new thing.
=head3 thing
A hashref containing the properties of the new thing.
=head3 isImport
If isImport 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 $isImport = 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 ($isImport){
$oldThingId = $thing->{thingId};
}
else{
$useAssetId = 1;
}
$thing->{thingId} = "new";
$newThingId = $self->setCollateral("Thingy_things","thingId",$thing,0,$useAssetId);
if ($isImport){
$db->write("update Thingy_things set thingId = ".$db->quote($oldThingId)
." where thingId = ".$db->quote($newThingId));
$newThingId = $oldThingId;
}
else{
# Set this Thingy assets defaultThingId if this is its first Thing.
my ($numberOfThings) = $db->quickArray('select count(*) from Thingy_things where assetId=?'
,[$self->getId]);
if ($numberOfThings == 1){
$self->update({defaultThingId => $newThingId});
}
}
$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)
) ENGINE=MyISAM DEFAULT CHARSET=utf8");
return $newThingId;
}
#-------------------------------------------------------------------
=head2 appendThingsVars ( vars, currentThingId )
Appends the list of things to a set of template vars.
=head3 vars
A hashref containing template variables.
=head3 currentThingId
A thingId. Will set the isCurrent flag in these template variables for the thing that the user is currently working with.
=cut
sub appendThingsVars {
my ($self, $vars, $currentThingId) = @_;
my $things = $self->getThings;
my @thingLoop = ();
while (my $thing = $things->hashRef) {
push @thingLoop, {
name => $thing->{label},
canView => $self->hasPrivileges($thing->{groupIdView}),
search_url => $self->getUrl('func=search;thingId='.$thing->{thingId}),
isCurrent => ($currentThingId eq $thing->{thingId}),
};
}
$vars->{listOfThings} = \@thingLoop;
}
#-------------------------------------------------------------------
=head2 canViewThing ( thingId, [ groupId ] )
Can the current user view the specified thing.
=head3 thingId
The unique id for a thing.
=head3 groupId
Pass in the groupId if you already have the view group for the thing.
=cut
sub canViewThing {
my ($self, $thingId, $groupId) = @_;
if ($groupId eq "") {
$groupId = $self->session->db->quickScalar("select groupIdView from Thingy_things where thingId=?", [$thingId]);
}
return $self->hasPrivileges($groupId);
}
#-------------------------------------------------------------------
=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('thingy template description'),
label=>$i18n->get('thingy template 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 = $self->getThings;
while ( my $thing = $things->hashRef) {
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 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.
=head3 keepSequenceNumbers
Boolean indicating that the sequence numbers should not be changed. This is used by importAssetCollateralData.
=cut
sub deleteField {
my $self = shift;
my $fieldId = shift;
my $thingId = shift;
my $keepSequenceNumbers = shift;
my $db = $self->session->db;
my $error = $self->session->errorHandler;
my $deletedSequenceNumber;
if ($keepSequenceNumbers ne "1"){
($deletedSequenceNumber) = $db->quickArray("select sequenceNumber from Thingy_fields where fieldId = ?"
,[$fieldId]);
}
$self->deleteCollateral("Thingy_fields","fieldId",$fieldId);
if ($keepSequenceNumbers ne "1"){
$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 deleteThingData ( )
Deletes data in a Thing.
=head3 thingId
The id of the Thing that should be deleted.
=head3 thingDataId
The id of row of data that should be deleted.
=cut
sub deleteThingData {
my $self = shift;
my $thingId = shift;
my $thingDataId = shift;
my $db = $self->session->db;
my ($groupIdEdit) = $db->quickArray("select groupIdEdit from Thingy_things where thingId=?",[$thingId]);
return undef 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 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);
$session->db->write("drop table if exists ".$session->db->dbh->quote_identifier("Thingy_".$thingId));
$error->info("Deleted thing: $thingId.");
return undef;
}
#-------------------------------------------------------------------
=head2 editThingDataSave ( )
Saves a row of thing data and triggers the appropriate workflow triggers.
=head3 thingId
The id of the Thing which this row of data is an instance of.
=head3 thingDataId
The id of the row of data. This can be an existing id or 'new'.
=head3 thingData
An optional hashref containing the new data. This will override values passed in by a form post.
Use this hashref for testing purposes.
=cut
sub editThingDataSave {
my $self = shift;
my $thingId = shift;
my $thingDataId = shift;
my $thingData = shift;
my $session = $self->session;
my (%thingData,$fields,@errors,$hadErrors,$newThingDataId);
my $i18n = WebGUI::International->new($session, 'Asset_Thingy');
if ($thingDataId eq "new"){
$thingData{dateCreated} = time();
$thingData{createdById} = $session->user->userId;
$thingData{ipAddress} = $session->env->getIp;
}
else {
%thingData = $session->db->quickHash("select * from ".$session->db->dbh->quote_identifier("Thingy_".$thingId)
." where thingDataId = ?",[$thingDataId]);
}
%thingData = ( %thingData,
thingDataId=>$thingDataId,
updatedById=>$session->user->userId,
updatedByName=>$session->user->username,
lastUpDated=>time(),
);
$fields = $session->db->read('select * from Thingy_fields where assetId = ? and thingId = ? order by sequenceNumber',
[$self->get("assetId"),$thingId]);
while (my $field = $fields->hashRef) {
my $fieldName = 'field_'.$field->{fieldId};
my $fieldValue;
if ($field->{status} eq "required" || $field->{status} eq "editable"){
my $fieldType = $field->{fieldType};
$fieldType = "" if ($fieldType =~ m/^otherThing/x);
# Modify the defaultValue for certain field types. For most types we want to use
# the default in the database, for these we want the last known value for this thingData
if ( $fieldType eq "File" || $fieldType eq "Image" ) {
$field->{ defaultValue } = $thingData{ "field_" . $field->{ fieldId } };
}
$fieldValue = $thingData->{$fieldName} || $session->form->process($fieldName,$fieldType,$field->{defaultValue},$field);
}
if ($field->{status} eq "required" && ($fieldValue =~ /^\s$/x || $fieldValue eq "" || !(defined $fieldValue))) {
push (@errors,{
"error_message"=>$field->{label}." ".$i18n->get('is required error').".",
});
#$hadErrors = 1;
}
if ($field->{status} eq "hidden") {
$fieldValue = $field->{defaultValue};
WebGUI::Macro::process($self->session,\$fieldValue);
}
if ($field->{status} eq "visible") {
$fieldValue = $field->{defaultValue};
#WebGUI::Macro::process($self->session,\$fieldValue);
}
$thingData{$fieldName} = $fieldValue;
}
$newThingDataId = $self->setCollateral("Thingy_".$thingId,"thingDataId",\%thingData,0,0);
# trigger workflow
if($thingDataId eq "new"){
my ($onAddWorkflowId) = $session->db->quickArray("select onAddWorkflowId from Thingy_things where thingId=?"
,[$thingId]);
if ($onAddWorkflowId){
$self->triggerWorkflow($onAddWorkflowId);
}
}else{
my ($onEditWorkflowId) = $session->db->quickArray("select onEditWorkflowId from Thingy_things where thingId=?"
,[$thingId]);
if ($onEditWorkflowId){
$self->triggerWorkflow($onEditWorkflowId);
}
}
return($newThingDataId,\@errors);
}
#-------------------------------------------------------------------
=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 _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->getDatabaseFieldType;
}
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 $session = $self->session;
my $field = shift;
my (%fieldStatus, $f, %fieldTypes, $things);
my $fieldId = $field->{fieldId} || "new";
my $i18n = WebGUI::International->new($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 = %{WebGUI::Form::FieldType->new($session)->getTypes};
%fieldTypes = WebGUI::Utility::sortHash(%fieldTypes);
$things = $self->session->db->read('select thingId, Thingy_things.label, count(*) from Thingy_things '
.'left join Thingy_fields using(thingId) where Thingy_things.assetId = ? and fieldId != "" '
.'group by thingId',[$self->getId]);
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",
value=>$defaultValue,
subtext=>' '.$i18n->get('default value subtext'),
width=>200,
height=>60,
resizable=>0,
});
$f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_defaultValue_module",$defaultValueForm,
$i18n->get('default value label'),$i18n->get('default value description')));
$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",
value=>($field->{width} || 250),
size=>10,
});
$f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_width_module",$widthForm,$i18n->get('width label'),
$i18n->get('width description')));
my $sizeForm = WebGUI::Form::Integer($self->session, {
name=>"size",
value=>($field->{size} || 25),
size=>10,
});
$f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_size_module",$sizeForm,$i18n->get('size label'),
$i18n->get('size description'),));
my $heightForm = WebGUI::Form::Integer($self->session, {
name=>"height",
value=>$field->{height} || 40,
label=>$i18n->get('height label'),
size=>10,
});
$f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_height_module",$heightForm,$i18n->get('height label'),
$i18n->get('height description')));
my $verticalForm = WebGUI::Form::YesNo($self->session, {
name=>"vertical",
value=>$field->{vertical},
label=>$i18n->get('vertical label'),
});
$f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_vertical_module",$verticalForm,$i18n->get('vertical label'),
$i18n->get('vertical description')));
my $valuesForm = WebGUI::Form::Textarea($self->session, {
name=>"possibleValues",
value=>$field->{possibleValues},
width=>200,
height=>60,
resizable=>0,
});
$f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_values_module",$valuesForm,$i18n->get('possible values label'),
$i18n->get('possible values description')));
$f->text(
-name=>"extras",
-value=>$field->{extras},
-label=>$i18n->get('extras label'),
-hoverHelp=>$i18n->get('extras description'),
);
#unless ($dialogPrefix eq "addDialog") {
# $f->raw('');
#}
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")]);
if (scalar(keys(%{$things}))) {
$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.
=head3 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;
my $tableName = 'Thingy_'.$otherThingId;
my ($otherThingTableExists) = $self->session->db->quickArray('show tables like ?',[$tableName]);
if ($otherThingTableExists){
($processedValue) = $self->session->db->quickArray('select '
.$dbh->quote_identifier('field_'.$field->{fieldInOtherThingId})
.' from '.$dbh->quote_identifier($tableName)
.' where thingDataId = ?',[$value]);
}
}
else {
my %fieldProperties = %$field;
$fieldProperties{options} = $field->{possibleValues};
$processedValue
= WebGUI::Form::DynamicField->new( $self->session, %fieldProperties, defaultValue => $value )
->getValueAsHtml;
}
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 $db = $self->session->db;
my $dbh = $db->dbh;
my $i18n = WebGUI::International->new($self->session,"Asset_Thingy");
$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} = 1;
if ($data->{value} == 1){
$param{checked} = 1;
}
}
if (WebGUI::Utility::isIn($data->{fieldType},qw(SelectList CheckList SelectBox Attachments SelectSlider))) {
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 = ();
my $tableName = 'Thingy_'.$otherThingId;
my ($otherThingTableExists) = $db->quickArray('show tables like ?',[$tableName]);
if ($otherThingTableExists){
$options = $db->buildHashRef('select thingDataId, '
.$dbh->quote_identifier('field_'.$data->{fieldInOtherThingId})
.' from '.$dbh->quote_identifier($tableName));
my $value = $data->{value} || $data->{defaultValue};
($param{value}) = $db->quickArray('select '
.$dbh->quote_identifier('field_'.$data->{fieldInOtherThingId})
.' from '.$dbh->quote_identifier($tableName)
.' where thingDataId = ?',[$value]);
}
else{
return $i18n->get('other thing missing message');
}
$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 $hoverHelp = shift;
$hoverHelp &&= '
' . $hoverHelp . '
';
my $html = "\n
\n";
$html .= "\t
\n";
$html .= "\t
\n";
$html .= "\t
\n";
$html .= "\t
";
$html .= $formDescription.$hoverHelp."
";
$html .= "
";
$html .= $formElement."
";
$html .= "\t\n
\n";
$html .= "\t
";
$html .= "\t\n
\t\n
\n";
$html .= "
";
return $html;
}
#-------------------------------------------------------------------
=head2 getThing ( thingId )
Returns a hash reference of the properties of a thing.
=head3 thingId
The unique id of a thing.
=cut
sub getThing {
my ($self, $thingId) = @_;
return $self->session->db->quickHashRef("select * from Thingy_things where thingId=?",[$thingId]);
}
#-------------------------------------------------------------------
=head2 getViewThingVars ( )
Returns the field values of a thing instance and the title for its view screen in a tmpl var hashref.
If a tmpl var hashref is supplied tmpl_var's will be appended to that.
=cut
sub getViewThingVars {
my ($self, $thingId, $thingDataId,$var) = @_;
my $db = $self->session->db;
my (@field_loop, @viewScreenTitleFields, $viewScreenTitle);
return undef unless ($thingId && $thingDataId);
my %thingData = $db->quickHash("select * from ".$db->dbh->quote_identifier("Thingy_".$thingId)
." where thingDataId = ?",[$thingDataId]);
if (%thingData) {
my $fields = $db->read('select * from Thingy_fields where assetId = ? and thingId = ? order by sequenceNumber',
[$self->get('assetId'),$thingId]);
while (my %field = $fields->hash) {
next unless ($field{display} eq '1');
my $hidden = ($field{status} eq "hidden" && !$self->session->var->get("adminOn"));
my $originalValue = $thingData{"field_".$field{fieldId}};
my $value = $self->getFieldValue($originalValue,\%field);
my $otherThingUrl;
if ($field{fieldType} =~ m/^otherThing/x) {
my $otherThingId = $field{fieldType};
$otherThingId =~ s/^otherThing_//x;
if($self->canViewThing($otherThingId)){
$otherThingUrl = $self->session->url->append(
$self->getUrl,
"func=viewThingData;thingId=$otherThingId;thingDataId=$originalValue"
);
}
}
my %fieldProperties = (
"id" => $field{fieldId},
"name" => "field_".$field{fieldId},
"value" => $value,
"label" => $field{label},
"isHidden" => $hidden,
"url" => $otherThingUrl,
);
push(@viewScreenTitleFields,$value) if ($field{viewScreenTitle});
push(@field_loop, { map {("field_".$_ => $fieldProperties{$_})} keys(%fieldProperties) });
}
$var->{viewScreenTitle} = join(" ",@viewScreenTitleFields);
$var->{field_loop} = \@field_loop;
return $var;
}
else{
return undef;
}
}
#-------------------------------------------------------------------
=head2 getThings ( )
Returns a result set with all the things in the database.
=cut
sub getThings {
my ($self) = @_;
return $self->session->db->read("select * from Thingy_things where assetId=?",[$self->getId]);
}
#-------------------------------------------------------------------
=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->session->user->isInGroup($privilegedGroupId) || $self->canEdit);
}
#-------------------------------------------------------------------
=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 = ?",
[$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 = $self->getThings;
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}.",seq :"
.$field->{sequenceNumber});
$self->_updateFieldType($field->{fieldType},$field->{fieldId},$field->{thingId},$field->{assetId},$dbDataType);
$self->setCollateral("Thingy_fields","fieldId",$field,1,0,"","",1);
}
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 = ?',
[$self->get("assetId")]);
while (my $fieldInDataBase = $fieldsInDatabase->hashRef) {
if (!WebGUI::Utility::isIn($fieldInDataBase->{fieldId},@importFields)){
# delete field
$self->deleteField($fieldInDataBase->{fieldId},$fieldInDataBase->{thingId},"1");
}
}
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->getMetaDataAsTemplateVariables);
$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 $db = $self->session->db;
my @thingIds = $db->buildArray("select thingId from Thingy_things where assetId = ?", [$self->getId]);
foreach my $thingId (@thingIds){
$db->write("drop table if exists ".$db->dbh->quote_identifier("Thingy_".$thingId));
}
$db->write("delete from Thingy_things where assetId = ?",[$self->getId]);
$db->write("delete from Thingy_fields where assetId = ?",[$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;
WebGUI::Workflow::Instance->create($self->session, {
workflowId=>$workflowId,
className=>"WebGUI::Asset::Wobject::Thingy",
methodName=>"new",
parameters=>$self->getId
})->start;
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");
$self->appendThingsVars($var, $defaultThingId);
if ($defaultThingId ne ""){
# get default view
($defaultView) = $db->quickArray("select defaultView from Thingy_things where thingId=?",[$defaultThingId]);
my $thingProperties = $self->getThing($defaultThingId);
if ($defaultView eq "searchThing"){
return $i18n->get("no permission to search") if( ! $self->canSearch($defaultThingId, $thingProperties));
return $self->search($defaultThingId,$thingProperties)
}
elsif ($defaultView eq "addThing"){
return $i18n->get("no permission to edit") if( ! $self->canEditThingData($defaultThingId, "new", $thingProperties));
return $self->editThingData($defaultThingId,"new", $thingProperties);
}
else{
return $self->processTemplate($var, undef, $self->{_viewTemplate});
}
}
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->quickArray("select groupIdEdit from Thingy_things where thingId=?",[$thingId]);
return $self->session->privilege->insufficient() unless $self->hasPrivileges($groupIdEdit);
$self->deleteThingData($thingId,$thingDataId);
return $self->www_search;
}
#-------------------------------------------------------------------
=head2 www_deleteThingDataViaAjax ( )
Deletes data in a Thing.
=cut
sub www_deleteThingDataViaAjax {
my $self = shift;
my $session = $self->session;
my $db = $session->db;
my $thingId = $self->session->form->process("thingId");
my $thingDataId = $self->session->form->process('thingDataId');
$session->http->setMimeType("application/json");
unless ($thingId && $thingDataId) {
$session->http->setStatus("400", "Bad Request");
return JSON->new->utf8->encode({message => "Can't get thing data without a thingId and a thingDataId."});
}
my $thingProperties = $self->getThing($thingId);
if ($thingProperties->{thingId}){
my ($groupIdEdit) = $db->quickArray("select groupIdEdit from Thingy_things where thingId=?",[$thingId]);
return $session->privilege->insufficient() unless $self->hasPrivileges($groupIdEdit);
$self->deleteThingData($thingId,$thingDataId);
$session->http->setMimeType("application/json");
return JSON->new->utf8->encode({message => "Data with thingDataId $thingDataId was deleted."});
}
else {
$session->http->setStatus("404", "Not Found");
return JSON->new->utf8->encode({message => "The thingId you specified can not be found."});
}
}
#-------------------------------------------------------------------
=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 $warning = shift;
my $session = $self->session;
my ($tabForm, $output, %properties, $tab, %afterSave, %defaultView, $fields);
my ($fieldsHTML, $fieldsViewScreen, $fieldsSearchScreen);
my (@hasHeightWidth,@hasSize,@hasVertical,@hasValues);
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('thing name label'),
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->getThing($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');
foreach my $fieldType ( keys %{ WebGUI::Form::FieldType->new($session)->getTypes }) {
my $form = eval { WebGUI::Pluggable::instanciate("WebGUI::Form::".$fieldType, "new", [$session]) };
if ($@) {
$session->errorHandler->error($@);
next;
}
my $definition = $form->definition($session);
if ($form->get("height")){
push(@hasHeightWidth, $fieldType);
}
if ($form->get("size")){
push(@hasSize, $fieldType);
}
if (defined $definition->[0]->{vertical}->{defaultValue}){
push(@hasVertical, $fieldType);
}
if ($form->areOptionsSettable){
push(@hasValues, $fieldType);
}
}
$tab->raw("");
$tab->text(
-name => 'label',
-label => $i18n->get('thing name label'),
-hoverHelp => $i18n->get('thing name description'),
-value => $properties{label},
-maxlength => 64,
);
$fieldsHTML = "\n"
."\n"
."