#################### 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;