#################### main pod documentation begin ###################
=head1 NAME
Zymonic::Table::SystemDefinition - Zymonic Workflow System Table module
=head1 SYNOPSIS
use Zymonic::Table::SystemDefinition;
Table class that maps Zymonic::Schema::Config field defs
into records that can then be used in table_sync calls, or
in a Form.
=head1 DESCRIPTION
Table class that maps Zymonic::Schema::Config field defs
into records that can then be used in table_sync calls, or
in a Form.
For each record in the table will lookup the config schema for the record's
type and autocreate fields for that type. Data from the fields will be
assembled into a XML Definition to store in the DB. This will then be used by
regular SystemDefintion to become part of the system.
=head1 USAGE
This is really only needs by the core as a means to have user generation
XML definitions. It should not need to be used elsewhere.
...
...
...
...
...
...
...
...
...
...
=for config "schema" begin
Field
AutoCreateMaintenanceProcess
AutoCreateForm
AutoCreateFilter
DefaultProcessZName
Key
RelationshipPermissions
RevisionHistoryLimit
SaveExtras
DeleteExtras
UnDeleteExtras
Lock
db
is_a_permission_dependency
ZName of the Field containing the definition's type.
ZName of the Field containing the definition's class.
ZName of the Field which validations type and class combinations, ideally a Choice field.
ZName of the Field containing the definition's zname.
ZName of the Field containing the definition's display name.
ZName of the Field containing the definition's maintainer.
ZName of the Field containing the definition's source.
ZName of the Field containing the definition's build time.
ZName of the Field containing the definition itself, as raw xml.
=for config "schema" end
=head1 BUGS
NONE
=head1 SUPPORT
As in the license, Zymonic is provided without warranty or support
unless purchased separately, however... If you email
zymonic-support@zednax.com your issue will be noted and may
receive a response.
For security issues, please contact zymonic-security@zednax.com and
someone will respond within 8 working hours.
=head1 AUTHOR
Alex Masidlover et al.a
CPAN ID: MODAUTHOR
Zednax Limited
alex.masidlover@zednax.com
http://www.zednax.com
=head1 COPYRIGHT
This program is free software licensed under the...
Zymonic Public License 1.0
The full text of the license can be found in the
LICENSE file included with this module.
Other licenses may be acceptable if including
parts of Zymonic in larger projects, please
contact Zednax for details.
=head1 SEE ALSO
Zymonic::Field
Zymonic
perl(1).
=cut
#################### main pod documentation end ###################
package Zymonic::Table::SystemDefinition;
use strict;
BEGIN
{
use Exporter ();
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
$VERSION = '0.01';
@ISA = qw(Exporter);
#Give a hoot don't pollute, do not export more than needed by default
@EXPORT = qw();
@EXPORT_OK = qw();
%EXPORT_TAGS = ();
}
use base "Zymonic::Table";
use XML::Simple;
use Zymonic::Schema::Config;
use Zymonic::Utils qw(debug get_pod_schema xml_unescape);
#################### subroutine header begin ####################
=head2 init
Usage : N/A
Purpose : The init method is called by the object constructor
to initialise the object field. In this case it retrieves the
form definition from the config module and then loads all the
fields as objects.
Returns : nothing
Argument : nothing
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub init
{
my $self = shift;
$self->SUPER::init();
# add quick lookup of field znames from the classoptions
$self->{type_field_zname} = $self->get_class_option( ['TypeFieldZName'] );
$self->{class_field_zname} = $self->get_class_option( ['ClassFieldZName'] );
$self->{valid_type_class_field_zname} = $self->get_class_option( ['ValidTypeClassFieldZName'] );
$self->{zname_field_zname} = $self->get_class_option( ['ZNameFieldZName'] );
$self->{display_name_field_zname} = $self->get_class_option( ['DisplayNameFieldZName'] );
$self->{maintainer_field_zname} = $self->get_class_option( ['MaintainerFieldZName'] );
$self->{source_field_zname} = $self->get_class_option( ['SourceFieldZName'] );
$self->{build_time_field_zname} = $self->get_class_option( ['BuildTimeFieldZName'] );
$self->{xml_def_field_zname} = $self->get_class_option( ['XMLDefinitionFieldZName'] );
# set metadata
$self->{maintainer} = $self->{auth}->{user};
$self->{source} = ref($self);
# clear caches
$self->{config_schemas} = {};
}
#################### subroutine header begin ####################
=head2 config_schema
Usage : $table->config_schema()
Purpose : Builds the config schema object.
Returns : Zymonic::Schema::Config
Argument : optional schema xml to use
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub config_schema
{
my $self = shift;
my $schema_xml = shift || '';
unless ( $self->{config_schema}->{$schema_xml} )
{
$self->{config_schema}->{$schema_xml} = Zymonic::Schema::Config->new(
parent => $self,
config => $self->{config},
maintainer => $self->{maintainer},
source => $self->{source},
# use incoming schema, otherwise you system schema
(
$schema_xml
? ( schema_xml => $schema_xml )
: ( schema_file => $self->{config}->sys_opt('xml_schema_location') ),
),
);
}
return $self->{config_schema}->{$schema_xml};
}
#################### subroutine header begin ####################
=head2 load_record_fields
Usage : $self->load_record_fields($record);
Purpose : For the incoming table record, sets up all
the field refs for it.
Returns : nothing
Argument : nothing
Throws : nothing
Comment : adds per schema fields to the record depending on its
type
See Also :
=cut
#################### subroutine header end ####################
sub load_record_fields
{
my $self = shift;
my $record = shift;
my $extras = shift || {};
my $link_parent_form_fields = shift;
# load the normal fields
$self->SUPER::load_record_fields( $record, $extras, $link_parent_form_fields );
# grab the type and class, and do nothing if there is no type
my $type = $self->get_field_value( $self->{type_field_zname}, $record );
my $class = $self->get_field_value( $self->{class_field_zname}, $record );
return unless $type;
# get the field defs for this type and class
my @field_defs = $self->get_type_field_defs( $type, $class );
# add each field def as a field ref on the record
my $parent_for_hkey = ( $self->{form_fields} ? $self->ancestor('Zymonic::Form') : $self );
my %record_field_refs = ();
foreach my $field_def (@field_defs)
{
my $field_ref = $self->store_field_ref(
$field_def->{ZName}->{content},
{
on_form => 'Y',
form_only => 'Y',
parent_for_hkey => $parent_for_hkey,
table => $self,
# set correct ident
ident => $record->{ident},
# set the value record so we get set value on this field later
record => $record,
db_value_record => $record,
# send through xmldef as its dynamic and shouldn't be loaded
xmldef => $field_def,
# send through flag as to whether to get user values for this field
get_user_values => $self->{get_user_values} || '',
# pass through record locking flags, for use by field subclasses
records_need_locking => $self->{records_need_locking} || '',
no_locking => $self->{no_locking} || '',
show_deleted => $self->{show_deleted} || '',
# flag this as an xmldef field
xmldef_field_def => $field_def,
# extras
%{ $self->{field_ref_extras} || {} },
}
);
$record_field_refs{ $field_ref->{zname} } = $field_ref if $field_ref;
}
# grab the xmldef and set values on the field refs
# no need to do anything explicit for any subclass schema here
# as xml_to_fields only uses the incoming field defs and nothing on $self
my $config_schema = $self->config_schema();
my $xmldef = $self->get_field_value( $self->{xml_def_field_zname}, $record );
$config_schema->xml_to_fields( $xmldef, \@field_defs, \%record_field_refs );
}
#################### subroutine header begin ####################
=head2 record_output
Usage : $self->record_output($record)
Purpose : Returns a hashref suitable for output as XML.
Returns : hashref of field output.
Argument : record and Flag to indicate non-inclusion of display attributes
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub record_output
{
my $self = shift;
my $record = shift;
my $no_display_attributes = shift || 0;
# ensure all non-field def record fields are hidden for output
# leave type and class visible at all times
foreach my $field_ref ( values %{ $self->get_fields($record) } )
{
if ( !$field_ref->{xmldef_field_def}
&& $field_ref->{zname} ne $self->{type_field_zname}
&& $field_ref->{zname} ne $self->{class_field_zname} )
{
$self->get_field_object($field_ref)->set_hidden('true');
}
}
# ensure xmldef field is up to date
$self->set_xmldef_from_record($record);
# call super to get any output
return $self->SUPER::record_output( $record, $no_display_attributes );
}
#################### subroutine header begin ####################
=head2 set_xmldef_from_record
Usage : $self->set_xmldef_from_record($record)
Purpose : Sets xmldata field from the schema fields.
Returns : nothing
Argument : record
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub set_xmldef_from_record
{
my $self = shift;
my $record = shift;
# assemble all the schema field refs
my %xmldef_field_refs = ();
my @xmldef_field_defs = ();
my $xmldef_field_ref;
foreach my $field_ref ( values %{ $self->get_fields($record) } )
{
if ( $field_ref->{xmldef_field_def} )
{
$xmldef_field_refs{ $field_ref->{zname} } = $field_ref;
push( @xmldef_field_defs, $field_ref->{xmldef_field_def} );
}
elsif ( $field_ref->{zname} ne $self->{type_field_zname} && $field_ref->{zname} ne $self->{class_field_zname} )
{
$self->get_field_object($field_ref)->set_hidden('true');
}
}
# ensure xmldef and zname fields are up to date
# also make sure class is set on the xmldef if set on field
my $config_schema = $self->config_schema();
my $class = $self->get_field_value( $self->{class_field_zname}, $record );
my $xmldef_details = $config_schema->fields_to_xml( \@xmldef_field_defs, \%xmldef_field_refs, $class );
if ($xmldef_details)
{
# no need to do anything explicit for any subclass schema here
# as fields_to_xml only uses the incoming field defs and nothing on $self
$self->set_record_field_value( $self->{xml_def_field_zname}, $record, $xmldef_details->{xmldef} )
if $xmldef_details->{xmldef};
# set displayname if found on xmldef
if ( $xmldef_details->{xmldef_hash} && $xmldef_details->{xmldef_hash}->{DisplayName} )
{
$self->set_record_field_value( $self->{display_name_field_zname},
$record, xml_unescape( $xmldef_details->{xmldef_hash}->{DisplayName}->{content} ) );
}
}
}
#################### subroutine header begin ####################
=head2 set_metadata_fields
Usage : $self->set_metadata_fields($record)
Purpose : Sets metadata fields on the record.
Returns : nothing
Argument : record
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub set_metadata_fields
{
my $self = shift;
my $record = shift;
# maintainer is the user
$self->set_record_field_value( $self->{maintainer_field_zname}, $record, $self->{maintainer} );
# source is the this object?
$self->set_record_field_value( $self->{source_field_zname}, $record, $self->{source} );
}
#################### subroutine header begin ####################
=head2 before_add
Usage : $self->before_add;
Purpose : This is a stub method for future use, most checks
will be at field level.
Returns : nothing
Argument : nothing
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub before_add
{
my $self = shift;
my $noperms = shift || '';
$self->SUPER::before_add();
# set metadata and xmldef on all placeholders
my @placeholders = values %{ $self->{placeholders} };
foreach my $record (@placeholders)
{
$self->set_metadata_fields($record);
$self->set_xmldef_from_record($record);
}
}
#################### subroutine header begin ####################
=head2 before_update
Usage : $self->before_update;
Purpose : This is a stub method for before updating.
Returns : nothing
Argument : nothing
Throws : Zymonic::Exception::Table::Record_Update
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub before_update
{
my $self = shift;
my $record = shift;
my $noperms = shift || '';
my $autocreated = shift || '';
$self->SUPER::before_update( $record, $noperms, $autocreated );
# set metadata and xmldef on incoming record
$self->set_metadata_fields($record);
$self->set_xmldef_from_record($record);
}
#################### subroutine header begin ####################
=head2 after_add
Usage : $self->after_add;
Purpose : This is a stub method for any after add checks.
Returns : nothing
Argument : record just updated, optional process_id
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub after_add
{
my $self = shift;
my $record = shift;
my $process_id = shift || 0;
$self->SUPER::after_add( $record, $process_id );
# register the def in the sysdef, updating it if it already exists
$self->register_record_xmldef($record);
# after save go through and update all the fields from the xmldef
my @xmldef_fields = grep { $_->{xmldef_field_def} } values %{ $self->get_fields($record) };
$self->field_factory()->post_db_write( $self, $record, \@xmldef_fields );
}
#################### subroutine header begin ####################
=head2 after_update
Usage : $self->after_update;
Purpose : This is a stub method for routines to run after
update.
Returns : nothing
Argument : the record just updated, optional process_id
Throws : nothing
Comment : process_id defaults to 0 if not specified, used in save_revision
See Also :
=cut
#################### subroutine header end ####################
sub after_update
{
my $self = shift;
my $record = shift;
my $process_id = shift || 0;
$self->SUPER::after_update( $record, $process_id );
# register the def in the sysdef, updating it if it already exists
$self->register_record_xmldef($record);
# after save go through and update all the fields from the xmldef
my @xmldef_fields = grep { $_->{xmldef_field_def} } values %{ $self->get_fields($record) };
$self->field_factory()->post_db_write( $self, $record, \@xmldef_fields );
}
#################### subroutine header begin ####################
=head2 delete_record
Usage : $self->delete_record;
Purpose : This method deletes the record
Returns : nothing
Argument : record - record to delete
noperms - flag to set to ignore permissions when doing the delete
actual_delete - flag to set to delete the record, as opposed to
marking it as deleted
Throws : Zymonic::Exception::Table::Record_Delete
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub delete_record
{
my $self = shift;
my $record = shift;
my $noperms = shift || '';
my $actual_delete = shift || '';
my $clear_ppid = shift || '';
# need to deregister the xml def for this record
# do this before delete so record is available to get
# details
$self->remove_record_xmldef($record);
# call super to do the delete
$self->SUPER::delete_record( $record, $noperms, $actual_delete, $clear_ppid );
}
#################### subroutine header begin ####################
=head2 register_record_xmldef
Usage : $self->register_record_xmldef;
Purpose : Looks up the xmldef for the incoming record and registerd
it into the system defintion
Returns : nothing
Argument : id of the record just updated
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub register_record_xmldef
{
my $self = shift;
my $record = shift;
# lookup fields needed to register the def
my $type = $self->get_field_value( $self->{type_field_zname}, $record );
my $xmldef = $self->get_field_value( $self->{xml_def_field_zname}, $record );
if ( $type && $xmldef )
{
# convert to xmldef from string
my $xmldef_hash = XMLin( $xmldef, ForceContent => 1, KeyAttr => [], KeepRoot => 1 );
# assemble metadata
my $metadata = {
maintainer => $self->{maintainer} || ( $self->{auth} ? $self->{auth}->{user} : '' ),
sourcefile => $self->{source} || ref($self),
timestamp => time(),
buildtime => 0
};
# register the def in the sysdef, overwriting it if it already exists
# don't send through the save flag since the add/update call will already have saved it
$self->{config}->register_def( $type, $xmldef_hash->{$type}, $metadata, '', 'overwrite_existing' );
}
}
#################### subroutine header begin ####################
=head2 remove_record_xmldef
Usage : $self->remove_record_xmldef;
Purpose : Looks up the xmldef for the incoming record and removes
it from the system defintion
Returns : nothing
Argument : id of the record just updated
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub remove_record_xmldef
{
my $self = shift;
my $record = shift;
# lookup fields needed to register the def
my $type = $self->get_field_value( $self->{type_field_zname}, $record );
my $zname = $self->get_field_value( $self->{zname_field_zname}, $record );
if ( $type && $zname )
{
# register the def in the sysdef, overwriting it if it already exists
# don't send through the delete flag since the delete call will already have removed it
$self->{config}->remove_def( $type, $zname, '' );
}
}
#################### subroutine header begin ####################
=head2 get_type_field_defs
Usage : $self->get_type_field_defs($type);
Purpose : Looks up the field defs for the incoming type
Returns : list of field defs
Argument : type
Throws : nothing
Comment :
See Also :
=cut
#################### subroutine header end ####################
sub get_type_field_defs
{
my $self = shift;
my $type = shift;
my $class = shift;
# if subclass then get schema for it
my $subclass_config_schema;
if ($class)
{
my $subclass_schema = get_pod_schema("Zymonic::$type::$class");
debug("Getting subclass Zymonic::$type::$class schema:\n$subclass_schema");
$subclass_config_schema = $self->config_schema($subclass_schema);
$self->{subclass_config_schema} = $subclass_config_schema;
}
# get field defs for that type
my $config_schema = $self->config_schema();
$config_schema->set_subclass_schema($subclass_config_schema) if $subclass_config_schema;
my @field_defs = $config_schema->get_field_defs_for_element($type);
# if subclass then get field defs for that subclass class options
if ($subclass_config_schema)
{
# get the field defs for ClassOptions, passing in list of parent element names, e.g. the container type
# need to pass this type in so the xmldef def paths are correct for the whole definition
my @subclass_field_defs = $subclass_config_schema->get_field_defs_for_element( 'ClassOptions', [$type] );
# TODO: do we need to add any special field groups to this
push( @field_defs, @subclass_field_defs );
}
return @field_defs;
}
#################### subroutine header begin ####################
=head2 build_cache
Usage : $self->build_cache()
Purpose : Runs any actions required to set caching.
Returns : A hashref of results for debugging
Argument : nothing
Throws :
Comment : Used to build up the sysdef fields needed to show
the config schema.
See Also :
=cut
#################### subroutine header end ####################
sub build_cache
{
my $self = shift;
# assemble result of what is done for caller
my $result = {};
# assemble list of types and classes
my @type_classs = ();
my $valid_type_class_field = $self->get_field( $self->{valid_type_class_field_zname}, '', 'optional' );
if ( $valid_type_class_field && $valid_type_class_field->isa('Zymonic::Choice') )
{
@type_classs = map { my ( $t, $c ) = split( '::', $_->{Value}, 2 ); { type => $t, class => $c }; }
$valid_type_class_field->options();
}
else
{
# if no field, just hard code some main ones
@type_classs = (
{ type => 'Page', class => '', },
{ type => 'Block', class => '', },
{ type => 'Layout', class => '', },
{ type => 'Filter', class => '', },
{ type => 'Table', class => '', },
{ type => 'Field', class => '', },
{ type => 'Key', class => '', },
{ type => 'FieldGroup', class => '', },
);
}
# TODO: should this clear existing first?
# fetch the field defs for all, this will autocreate all the necessary fields
foreach my $type_class (@type_classs)
{
my $type = $type_class->{type};
my $class = $type_class->{class};
my @field_defs = $self->get_type_field_defs( $type, $class );
$result->{$type} = {} unless $result->{$type};
$result->{$type}->{$class} = {} unless $result->{$type}->{$class};
map { $result->{$type}->{$class}->{ $_->{ZName}->{content} } = 1; } @field_defs;
}
return $result;
}
1;