package WebGUI::Asset::Wobject::Thingy;
#-------------------------------------------------------------------
# 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
#-------------------------------------------------------------------
use strict;
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';
use Data::Dumper;
use PerlIO::eol qw/NATIVE/;
use WebGUI::ProgressBar;
#-------------------------------------------------------------------
=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 $session = $self->session;
my $db = $session->db;
my $error = $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 CHAR(22) binary not null,
dateCreated int not null,
createdById CHAR(22) not null,
updatedById CHAR(22) not null,
updatedByName CHAR(255) not null,
lastUpdated int not null,
ipAddress CHAR(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 badOtherThing ( tableName, fieldName )
Checks that the table and field for the other Thing are okay. Returns 0 if okay,
otherwise, returns an i18n message appropriate for the type of error, like the
table or the field in the table not existing.
=head3 tableName
The table name for the other thing.
=head3 fieldName
The field in the other thing to check for.
=cut
sub badOtherThing {
my ($self, $tableName, $fieldName) = @_;
my $session = $self->session;
my $db = $session->db;
my $i18n = WebGUI::International->new($session, 'Asset_Thingy');
my ($otherThingTableExists) = $db->quickArray('show tables like ?',[$tableName]);
return $i18n->get('other thing missing message') unless $otherThingTableExists;
my ($otherThingFieldExists) = $db->quickArray(
sprintf('show columns from %s like ?', $db->dbh->quote_identifier($tableName)),
[$fieldName]);
return $i18n->get('other thing field missing message') unless $otherThingFieldExists;
return undef;
}
#-------------------------------------------------------------------
=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 deleteThingIndex ( $thingId )
Remove the entry about this Thing from the database, and for any rows for it that are indexed as well.
=head3 $thingId
The GUID of the Thing, used to pick out this record in the database.
=cut
sub deleteThingIndex {
my $self = shift;
my $thingId = shift;
$self->session->db->write(q|delete from assetIndex where assetId=? and subId like CONCAT(?,'%')|,[$self->getId, $thingId]);
}
#-------------------------------------------------------------------
=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 $otherThingFields = $db->buildHashRefOfHashRefs(
"select fieldType, fieldId, right(fieldType,22) as otherThingId, fieldInOtherThingId from Thingy_fields
where fieldType like 'otherThing_%' and assetId = ?",
[$assetId],'fieldInOtherThingId'
);
my $things = $self->getThings;
while ( my $thing = $things->hashRef) {
my $oldSortBy = $thing->{sortBy};
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;
my $originalFieldId = $field->{fieldId};
my $newFieldId = $newAsset->addField($field,0);
if ($originalFieldId eq $oldSortBy){
$self->session->db->write( "update Thingy_things set sortBy = ? where thingId = ?",
[ $newFieldId, $newThingId ] );
}
if ($otherThingFields->{$originalFieldId}){
$otherThingFields->{$originalFieldId}->{newFieldType} = 'otherThing_'.$newThingId;
$otherThingFields->{$originalFieldId}->{newFieldId} = $newFieldId;
}
}
}
foreach my $otherThingField (keys %$otherThingFields){
$db->write('update Thingy_fields set fieldType = ?, fieldInOtherThingId = ?
where fieldInOtherThingId = ? and assetId = ?',
[$otherThingFields->{$otherThingField}->{newFieldType},
$otherThingFields->{$otherThingField}->{newFieldId},
$otherThingFields->{$otherThingField}->{fieldInOtherThingId}, $newAsset->get('assetId')]);
}
return $newAsset;
}
#-------------------------------------------------------------------
=head2 duplicateThing ( thingId )
Duplicates a thing.
=head3 thingId
The id of the Thing that will be duplicated.
=cut
sub duplicateThing {
my $self = shift;
my $oldThingId = shift;
my $db = $self->session->db;
my $thingProperties = $self->getThing($oldThingId);
$thingProperties->{thingId} = 'new';
$thingProperties->{label} = $thingProperties->{label}.' (copy)';
my $newThingId = $self->addThing($thingProperties);
my $fields = $db->buildArrayRefOfHashRefs('select * from Thingy_fields where assetId=? and thingId=?'
,[$self->getId,$oldThingId]);
foreach my $field (@$fields) {
# set thingId to newly created thing's id.
$field->{thingId} = $newThingId;
$self->addField($field,0);
}
$thingProperties->{thingId} = $newThingId;
$self->indexThing($thingProperties);
return $newThingId;
}
#-------------------------------------------------------------------
=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));
}
$self->reindexThings;
$error->info("Deleted field: $fieldId in thing: $thingId.");
return undef;
}
#-------------------------------------------------------------------
=head2 copyThingData ( )
Copies data in a Thing.
=head3 thingId
The id of the Thing that should be copied.
=head3 thingDataId
The id of row of data that should be copied.
=cut
sub copyThingData {
my $self = shift;
my $thingId = shift;
my $thingDataId = shift;
my $session = $self->session;
my $db = $session->db;
return undef unless $self->canEditThingData($thingId, $thingDataId);
my $origCollateral = $self->getCollateral("Thingy_".$thingId, "thingDataId", $thingDataId);
$origCollateral->{thingDataId} = "new";
##Get all fields
my $fields = $db->buildArrayRefOfHashRefs('select * from Thingy_fields where assetId=? and thingId=?'
,[$self->getId,$thingId]);
my @storage_field_ids = ();
##Check to see if any of them are File or Image
foreach my $field (@{ $fields }) {
if ($self->field_isa($field->{fieldType}, 'WebGUI::Form::File')) {
push @storage_field_ids, $field->{fieldId};
}
}
##Instance the storage object and duplicate it
foreach my $fieldId (@storage_field_ids) {
my $currentId = $origCollateral->{"field_". $fieldId};
my $storage = WebGUI::Storage->get($session, $currentId);
my $new_store = $storage->copy;
##Update the copy with the new storageId.
$origCollateral->{"field_". $fieldId} = $new_store->getId;
}
##Update the copy
$self->setCollateral("Thingy_".$thingId, "thingDataId", $origCollateral, 0, 0);
$self->indexThingData($thingId, $origCollateral);
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 $session = $self->session;
my $db = $session->db;
return undef unless $self->canEditThingData($thingId, $thingDataId);
my ($onDeleteWorkflowId) = $db->quickArray("select onDeleteWorkflowId from Thingy_things where thingId=?"
,[$thingId]);
if ($onDeleteWorkflowId){
$self->triggerWorkflow($onDeleteWorkflowId, $thingId,$thingDataId);
}
my $origCollateral = $self->getCollateral("Thingy_".$thingId, "thingDataId", $thingDataId);
$self->deleteCollateral("Thingy_".$thingId,"thingDataId",$thingDataId);
my $fields = $db->buildArrayRefOfHashRefs('select * from Thingy_fields where assetId=? and thingId=?'
,[$self->getId,$thingId]);
my @storage_field_ids = ();
##Check to see if any of them are File or Image
foreach my $field (@{ $fields }) {
if ($self->field_isa($field->{fieldType}, 'WebGUI::Form::File')) {
push @storage_field_ids, $field->{fieldId};
}
}
foreach my $fieldId (@storage_field_ids) {
my $currentId = $origCollateral->{"field_". $fieldId};
my $storage = WebGUI::Storage->get($session, $currentId);
$storage->delete;
}
$self->deleteThingDataIndex($thingDataId);
return undef;
}
#-------------------------------------------------------------------
=head2 deleteThingDataIndex ( $thingDataId )
Remove the entry about this Thing from the database, and for any rows for it that are indexed as well.
=head3 $thingDataId
The GUID of the thingData to remove, used to pick out this record in the database.
=cut
sub deleteThingDataIndex {
my $self = shift;
my $thingDataId = shift;
$self->session->db->write(q|delete from assetIndex where assetId=? and subId like CONCAT('%-',?)|,[$self->getId, $thingDataId]);
}
#-------------------------------------------------------------------
=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.");
$self->deleteThingIndex($thingId);
return undef;
}
#-------------------------------------------------------------------
=head2 editThingDataSave ( )
Saves a row of thing data and triggers the appropriate workflow triggers. Returns the id of the row created in
the database, and an array reference of errors from required fields and other sources. In there are errors, no data
is saved in the database, and the id returned in the empty string.
=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 = $self->getFields($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 ($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))) {
push (@errors,{
"error_message"=>$field->{label}." ".$i18n->get('is required error').".", "field_name"=>$fieldName,
});
}
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);
}
if ($field->{isUnique}) {
unless ( $self->isUniqueEntry($thingId,$fieldName,$fieldValue,$thingDataId)) {
push (@errors,{
"error_message"=>$field->{label}. $i18n->get('needs to be unique error'),"field_name"=>$fieldName,
});
}
}
$thingData{$fieldName} = $fieldValue;
}
if (@errors) {
return ('', \@errors);
}
$newThingDataId = $self->setCollateral("Thingy_".$thingId,"thingDataId",\%thingData,0,0);
$self->indexThingData($thingId, \%thingData);
# trigger workflow
if($thingDataId eq "new"){
my ($onAddWorkflowId) = $session->db->quickArray("select onAddWorkflowId from Thingy_things where thingId=?"
,[$thingId]);
if ($onAddWorkflowId){
$self->triggerWorkflow($onAddWorkflowId,$thingId,$newThingDataId);
}
}else{
my ($onEditWorkflowId) = $session->db->quickArray("select onEditWorkflowId from Thingy_things where thingId=?"
,[$thingId]);
if ($onEditWorkflowId){
$self->triggerWorkflow($onEditWorkflowId,$thingId,$newThingDataId);
}
}
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 field_isa ( $fieldType, $isa )
Builds a form field and does an isa check on it.
=head2 $fieldType
This is the type of a field to build. It will have 'WebGUI::Form' prepended to it to form
a complete classname.
=head2 $isa
This is the class name to check against.
=cut
sub field_isa {
my $self = shift;
my $session = $self->session;
my $fieldType = shift;
my $isa = shift;
$fieldType = ucfirst $fieldType;
my $control = eval { WebGUI::Pluggable::instanciate("WebGUI::Form::".$fieldType, "new", [ $session, () ]) };
return ($control && $control->isa($isa));
}
#-------------------------------------------------------------------
=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 = "CHAR(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 ($field->{oldFieldId}){
$dialogPrefix = "edit_".$field->{oldFieldId}."_Dialog_copy";
}
elsif($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->yesNo(
-name=>'isUnique',
-label=>$i18n->get('unique label'),
-hoverHelp=>$i18n->get('unique description'),
-value=>$field->{isUnique},
-id=>$dialogPrefix."_isUnique_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=>40,
resizable=>0,
});
$f->raw($self->getHtmlWithModuleWrapper($dialogPrefix."_defaultValue_module",$defaultValueForm,
$i18n->get('default value label'),$i18n->get('default value description')));
$f->text(
-name=>"pretext",
-value=>$field->{pretext},
-label=>$i18n->get('pretext label'),
-hoverHelp=>$i18n->get('pretext 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 getFields ( $thingId )
Returns a result set with all the fields in a thing in this Thingy.
=head3 $thingId
The GUID for a thing
=cut
sub getFields {
my ($self, $thingId) = @_;
return $self->session->db->read("select * from Thingy_fields where assetId=? and thingId=? order by sequenceNumber",[$self->getId, $thingId]);
}
#-------------------------------------------------------------------
=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 $session = $self->session;
my $dbh = $session->db->dbh;
my $fieldType = lc $field->{fieldType};
if ($fieldType eq "date"){
my $wdt = WebGUI::DateTime->new($session, $value);
$processedValue = $wdt->cloneToUserTimeZone->webguiDate($dateFormat);
}
elsif ($fieldType eq "datetime"){
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
# way it was.
elsif ($fieldType =~ m/^otherthing/x) {
my $otherThingId = $field->{fieldType};
$otherThingId =~ s/^otherThing_//x;
my $tableName = 'Thingy_'.$otherThingId;
my $fieldName = 'field_'.$field->{fieldInOtherThingId};
my $badThing = $self->badOtherThing($tableName, $fieldName);
if (! $badThing){
($processedValue) = $self->session->db->quickArray('select '
.$dbh->quote_identifier($fieldName)
.' from '.$dbh->quote_identifier($tableName)
.' where thingDataId = ?',[$value]);
}
}
else {
$field->{ value } = $value;
$field->{ defaultValue } = $value;
my $plugin = $self->getFormPlugin( $field );
$processedValue = $plugin->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;
return $self->getFormPlugin( @_, 1 )->toHtml;
}
#-------------------------------------------------------------------
=head2 getFormPlugin ( properties, [ useFormPostData ] )
Returns an instanciated WebGUI::Form::* plugin.
=head3 properties
The properties to configure the form plugin with. The fieldType key should contain the type of the form plugin.
=head3 useFormPostData
If set to true, the value of the form element will be set to the data posted by it if available.
=cut
sub getFormPlugin {
my $self = shift;
my $data = shift;
my $useFormPostData = shift;
my %param;
my $session = $self->session;
my $db = $session->db;
my $dbh = $db->dbh;
my $i18n = WebGUI::International->new($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{value} = $data->{value};
$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) ) ) {
my @values;
if ( $useFormPostData && $session->form->param($name) ) {
$param{ value } = [ $session->form->process( $name, $data->{fieldType} ) ];
}
elsif ( $data->{ value } ) {
foreach (split(/\n/x, $data->{value})) {
s/\s+$//x; # remove trailing spaces
push(@values, $_);
}
$param{value} = \@values;
}
}
elsif ( $useFormPostData && $session->form->param($name) ) {
$param{value} = $session->form->process( $name, $data->{fieldType} );
}
my $class = 'WebGUI::Form::'. ucfirst $data->{fieldType};
eval { WebGUI::Pluggable::load($class) };
if ($class->isa('WebGUI::Form::List')) {
delete $param{size};
my $values = WebGUI::Operation::Shared::secureEval($session,$data->{possibleValues});
$param{options} = $values;
}
if ($data->{fieldType} eq "YesNo") {
if ($data->{defaultValue} =~ /yes/xi) {
$param{defaultValue} = 1;
}
elsif ($data->{defaultValue} =~ /no/xi) {
$param{defaultValue} = 0;
}
}
if ($data->{fieldType} =~ m/^otherThing/x){
my $otherThingId = $data->{fieldType};
$otherThingId =~ s/^otherThing_(.*)/$1/x;
$param{fieldType} = "SelectList";
$class = 'WebGUI::Form::'. $param{fieldType};
my $options = ();
my $tableName = 'Thingy_'.$otherThingId;
my $fieldName = 'field_'.$data->{fieldInOtherThingId};
my $errorMessage = $self->badOtherThing($tableName, $fieldName);
return $errorMessage if $errorMessage;
my $sth = $session->db->read('select thingDataId, '
.$dbh->quote_identifier($fieldName)
.' from '.$dbh->quote_identifier($tableName));
while (my $result = $sth->hashRef){
if ($self->canViewThingData($otherThingId,$result->{thingDataId})){
$options->{$result->{thingDataId}} = $result->{$fieldName}
}
}
my $value = $data->{value} || $data->{defaultValue};
($param{value}) = $db->quickArray('select '
.$dbh->quote_identifier($fieldName)
.' from '.$dbh->quote_identifier($tableName)
.' where thingDataId = ?',[$value]);
$param{size} = 1;
$param{multiple} = 0;
$param{options} = $options;
$param{value} = $data->{value} || $data->{defaultValue};
}
my $formElement = eval { WebGUI::Pluggable::instanciate($class, "new", [$session, \%param ])};
return $formElement;
}
#-------------------------------------------------------------------
=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 getThingUrl ( thingData )
Returns a the URL to view a Thing in this Thingy
=head3 thingData
A hashref of properties for the Thing, as returned by getThing. This is needed to extract the defaultView,
to get the right func, and the thingId.
=cut
sub getThingUrl {
my ($self, $thing) = @_;
if ($thing->{defaultView} eq "addThing") {
return 'func=editThingData;thingId='.$thing->{thingId}.';thingDataId=new';
}
else{
return 'func=search;thingId='.$thing->{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 = $self->getFields($thingId);
while (my %field = $fields->hash) {
next unless ($field{display} eq '1');
my $hidden = ($field{status} eq "hidden" && !$self->session->var->isAdminOn);
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},
"type" => $field{fieldType},
"value" => $value,
"label" => $field{label},
"isHidden" => $hidden,
"url" => $otherThingUrl,
"isVisible" => ($field{status} eq "visible" && !$hidden),
"isRequired" => ($field{status} eq "required" && !$hidden),
"pretext" => $field{pretext},
"subtext" => $field{subtext},
);
push(@viewScreenTitleFields,$value) if ($field{viewScreenTitle});
push(@field_loop, { map {("field_".$_ => $fieldProperties{$_})} keys(%fieldProperties) });
$var->{ $field{label} } = $value;
}
$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 hasEnteredMaxPerUser
Check whether the current user has entered the maximum number of entries allowed for this thing.
=head3 thingId
The unique id of a thing.
=cut
sub hasEnteredMaxPerUser {
my ($self,$thingId) = @_;
my $session = $self->session;
my $db = $session->db;
my $maxEntriesPerUser = $db->quickScalar("select maxEntriesPerUser from Thingy_things where thingId=?",[$thingId]);
return 0 unless $maxEntriesPerUser;
my $numberOfEntries = $session->db->quickScalar("select count(*) "
."from ".$session->db->dbh->quote_identifier("Thingy_".$thingId)." where createdById=?",[$session->user->userId]);
if($numberOfEntries < $maxEntriesPerUser){
return 0;
}
else{
return 1;
}
}
#-------------------------------------------------------------------
=head2 hasEnteredMaxEntries
Check whether the the maximum number of entries allowed for this thing has been reached.
=head3 thingId
The unique id of a thing.
=cut
sub hasEnteredMaxEntries {
my ($self,$thingId) = @_;
my $session = $self->session;
my $db = $session->db;
my $maxEntriesTotal = $db->quickScalar("select maxEntriesTotal from Thingy_things where thingId=?",[$thingId]);
return 0 unless $maxEntriesTotal;
my $numberOfEntries = $session->db->quickScalar("select count(*) "
."from ".$session->db->dbh->quote_identifier("Thingy_".$thingId));
if($numberOfEntries < $maxEntriesTotal){
return 0;
}
else{
return 1;
}
}
#-------------------------------------------------------------------
=head2 isUniqueEntry ( thingId,fieldName,fieldValue, thingDataId )
Checks if the data entered in thingy record is unique
=cut
sub isUniqueEntry {
my ($self,$thingId,$fieldName,$fieldValue,$thingDataId) = @_;
my $session = $self->session;
my $db = $session->db;
my $nrOfEntries = $session->db->quickScalar("select count(*) "
."from ".$session->db->dbh->quote_identifier("Thingy_".$thingId)." where " .
$session->db->dbh->quote_identifier($fieldName) ."=? and thingDataId !=?",[$fieldValue,$thingDataId]);
if ($nrOfEntries > 0) { return 0; }
return 1;
}
#-------------------------------------------------------------------
=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;
$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 ($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);
}
$self->indexThing($thing);
}
# 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 ($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 indexContent ( )
Extend the base class to handle reindexing every Thing, and every row in every Thing. We have
to do a complete rework because the URL may have changed, and that affects everything that has
been inserted into the AssetIndex.
=cut
sub indexContent {
my ($self) = @_;
my $session = $self->session;
$self->SUPER::indexContent();
$self->reindexThings;
}
#-------------------------------------------------------------------
=head2 indexThing ( $thing )
Add an entry about this Thing into the AssetIndex so it can be found for searches.
=head3 $thing
A hashref of Thing properties, as returned by getThing
=cut
sub indexThing {
my ($self, $thing) = @_;
return unless $thing;
my $index = WebGUI::Search::Index->new($self);
$index->addRecord(
groupIdView => $thing->{groupIdView},
title => $thing->{label},
subId => $thing->{thingId},
keywords => join(' ', @{$thing}{qw/label editScreenTitle editInstructions searchScreenTitle searchDescription/}),
url => $self->session->url->append($self->get('url'), $self->getThingUrl($thing)),
);
##Easy update of all thingData fields for this thing. This is in lieu of deleting all records
##And rebuilding them all.
$self->session->db->write(q|update assetIndex set title=?, groupIdView=? where assetId=? and subId like CONCAT(?, '%')|, [$thing->{label}, $thing->{groupIdView}, $self->getId, $thing->{thingId}]);
}
#-------------------------------------------------------------------
=head2 indexThingData ( $thingId, $thingData )
Add an entry about a row from this Thing into the AssetIndex so it can be found for searches.
=head3 $thingId
The GUID for a Thing.
=head3 $thingData
A hashref of ThingData properties
=cut
sub indexThingData {
my ($self, $thingId, $thingData) = @_;
my $session = $self->session;
return unless $thingId;
my $thing = $self->getThing($thingId);
my $index = WebGUI::Search::Index->new($self);
my $keywords;
##Get the Thingy's fields
my $fields = $session->db->read('select * from Thingy_fields where assetId = ? and thingId = ?',
[$self->getId, $thingId]);
##Walk the fields
my @viewScreenTitleFields = ();
FIELD: while (my %field = $fields->hash) {
my $fieldName = 'field_'.$field{fieldId};
$field{value} = $thingData->{$fieldName} || $field{defaultValue};
my $subkeywords = '';
my $value = '';
if ($self->field_isa($field{fieldType}, 'WebGUI::Form::File')) {
my $storage = WebGUI::Storage->get($session, $field{value});
if ($storage) {
foreach my $file (@{$storage->getFiles()}) {
$subkeywords = $index->getKeywordsForFile($storage->getPath($file));
}
}
}
else {
$value = $self->getFieldValue($field{value}, \%field);
##If it's a file type, then get keywords from the file.
##Accumulate keywords
$subkeywords = $value;
}
if ($value && $field{viewScreenTitle}) {
push @viewScreenTitleFields, $value;
}
##We don't index date fields, since they're unix epochs and they'd be different for everyone based on timezone.
next FIELD if $field{fieldType} eq 'date'
|| $field{fieldType} eq 'datetime'
|| $field{fieldType} eq 'time';
##Don't show what shouldn't be shown
next FIELD unless $field{displayInSearch};
$keywords = join ' ', $keywords, $subkeywords;
}
return unless $keywords; ##No sense indexing nothing;
my $title = join('', @viewScreenTitleFields)
|| $thing->{label}
|| $self->getTitle;
$index->addRecord(
assetId => $self->getId,
url => $session->url->append($self->get('url'), 'func=viewThingData;thingId='. $thing->{thingId} . ';thingDataId='. $thingData->{thingDataId}),
groupIdView => $thing->{groupIdView},
title => $title,
subId => $thing->{thingId} . '-' . $thingData->{thingDataId},
keywords => $keywords,
);
}
#-------------------------------------------------------------------
=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"));
if (!$template) {
WebGUI::Error::ObjectNotFound::Template->throw(
error => qq{Template not found},
templateId => $self->get("templateId"),
assetId => $self->getId,
);
}
$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 reindexThings ( )
Reindex every Thing, and every row in every Thing.
=cut
sub reindexThings {
my ($self) = @_;
my $session = $self->session;
my $things = $self->getThings;
THING: while (my $thing = $things->hashRef) {
$self->deleteThingIndex($thing->{thingId});
$self->indexThing($thing);
my $sth = $session->db->read('select * from '. $session->db->dbh->quote_identifier('Thingy_'.$thing->{thingId}));
while (my $thingData = $sth->hashRef) {
$self->indexThingData($thing->{thingId}, $thingData);
}
$sth->finish;
}
}
#-------------------------------------------------------------------
=head2 triggerWorkflow ( workflowId, thingId, thingDataId )
Runs the specified workflow when this Thingy changes.
=head3 workflowId
The id of the workflow that has to be triggered.
=cut
sub triggerWorkflow {
my $self = shift;
my $workflowId = shift;
my $thingId = shift ;
my $thingDataId = shift ;
my $workflowInstance = WebGUI::Workflow::Instance->create($self->session, {
workflowId=>$workflowId,
className=>"WebGUI::Asset::Wobject::Thingy",
methodName=>"new",
parameters=>$self->getId
});
$workflowInstance->setScratch("thingId", $thingId);
$workflowInstance->setScratch("thingDataId",$thingDataId);
$workflowInstance->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 $db = $session->db;
my ($fieldType) = $self->session->db->quickArray("select fieldType from Thingy_fields where fieldId = "
.$db->quote($fieldId)." and assetId = ".$db->quote($assetId)
." and thingId = ".$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 ".$db->dbh->quote_identifier($thingyTableName).
" CHANGE ".$db->dbh->quote_identifier($columnName)." "
.$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_duplicateThing ( )
Duplicates a Thing.
=cut
sub www_duplicateThing {
my $self = shift;
my $session = $self->session;
my $thingId = $session->form->process("thingId");
return $session->privilege->insufficient() unless $self->canEdit;
$self->duplicateThing($thingId);
return $self->www_manage;
}
#-------------------------------------------------------------------
=head2 www_copyThingData( )
Copies data in a Thing.
=cut
sub www_copyThingData{
my $self = shift;
my $thingId = $self->session->form->process("thingId");
my $thingDataId = $self->session->form->process('thingDataId');
return $self->session->privilege->insufficient() unless $self->canEditThingData($thingId, $thingDataId);
$self->copyThingData($thingId,$thingDataId);
return $self->www_search;
}
#-------------------------------------------------------------------
=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 $thingId = $self->session->form->process("thingId");
my $thingDataId = $self->session->form->process('thingDataId');
return $self->session->privilege->insufficient() unless $self->canEditThingData($thingId, $thingDataId);
$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 $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->encode({message => "Can't get thing data without a thingId and a thingDataId."});
}
my $thingProperties = $self->getThing($thingId);
if ($thingProperties->{thingId}){
return $session->privilege->insufficient() unless $self->canEditThingData($thingId, $thingDataId
,$thingProperties);
$self->deleteThingData($thingId,$thingDataId);
$session->http->setMimeType("application/json");
return JSON->new->encode({message => "Data with thingDataId $thingDataId was deleted."});
}
else {
$session->http->setStatus("404", "Not Found");
return JSON->new->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, %editViewOptions);
my ($fieldsHTML, $fieldsViewScreen, $fieldsSearchScreen);
my (@hasHeightWidth,@hasSize,@hasVertical,@hasValues);
tie %afterSave, 'Tie::IxHash';
tie %editViewOptions, '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,
exportMetaData=>undef,
maxEntriesPerUser=>undef,
maxEntriesTotal=>undef,
);
$thingId = "new";
}
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"
."