package Bric::Biz::Element::Field; ############################################################################### =head1 NAME Bric::Biz::Element::Field - Data (Field) Element =head1 VERSION $LastChangedRevision$ =cut require Bric; our $VERSION = Bric->VERSION; =head1 DATE $LastChangedDate: 2006-06-12 22:15:48 +0200 (Mon, 12 Jun 2006) $ =head1 SYNOPSIS # Creation of New Objects $data = Bric::Biz::Element::Field->new($params); $data = Bric::Biz::Element::Field->lookup({ id => $id }); @data = Bric::Biz::Element::Field->list($params); # Retrieval of Object IDs @ids = = Bric::Biz::Element::Field->list_ids($params); # Manipulation of Data Field $data = $data->set_value( $data_value ); $data_value = $data->get_value; =head1 DESCRIPTION This class contains the contents of field elements, also known as data elements. These are the objects that hold the values of story element fields. This class inherits from L. =cut #==============================================================================# # Dependencies # #======================================# #--------------------------------------# # Standard Dependencies use strict; #--------------------------------------# # Programatic Dependencies use Bric::Util::DBI qw(:all); use Bric::Util::Time qw(:all); use Bric::Util::Fault qw(throw_gen throw_da); use Bric::Biz::ElementType::Parts::FieldType; #==============================================================================# # Inheritance # #======================================# # The parent module should have a 'use' line if you need to import from it. # use Bric; use base qw(Bric::Biz::Element); #=============================================================================# # Function Prototypes # #======================================# # None #==============================================================================# # Constants # #======================================# use constant DEBUG => 0; use constant S_TABLE => 'story_field'; use constant M_TABLE => 'media_field'; my @SEL_COLS = qw( f.id ft.name ft.key_name ft.description ft.autopopulated ft.multiple ft.max_length ft.sql_type ft.widget_type f.field_type__id f.object_instance_id f.parent_id f.place f.object_order f.hold_val f.date_val f.short_val f.blob_val f.active ); my @SEL_FIELDS = qw( id name key_name description _autopopulated _multiple max_length sql_type widget_type field_type_id object_instance_id parent_id place object_order _hold_val _date_val _short_val _blob_val _active ); my @COLS = qw( field_type__id object_instance_id parent_id place object_order hold_val date_val short_val blob_val active ); my @FIELDS = qw( field_type_id object_instance_id parent_id place object_order _hold_val _date_val _short_val _blob_val _active ); #==============================================================================# # Fields # #======================================# #--------------------------------------# # Public Class Fields # None. #--------------------------------------# # Private Class Fields # None. #--------------------------------------# # Instance Fields # This method of Bricolage will call 'use fields' for you and set some permissions. BEGIN { Bric::register_fields({ field_type_id => Bric::FIELD_RDWR, sql_type => Bric::FIELD_READ, widget_type => Bric::FIELD_READ, max_length => Bric::FIELD_READ, _autopopulated => Bric::FIELD_NONE, _multiple => Bric::FIELD_NONE, _hold_val => Bric::FIELD_NONE, _active => Bric::FIELD_NONE, _date_val => Bric::FIELD_NONE, _short_val => Bric::FIELD_NONE, _blob_val => Bric::FIELD_NONE, _field_type => Bric::FIELD_NONE, }); } #==============================================================================# # Interface Methods # #======================================# =head1 INTERFACE =head2 Constructors =over 4 =item my $data = Bric::Biz::Element::Field->new($init) Construct a new data element object. The supported initial attributes are: =over 4 =item object_type A string identifying the type of document the new data element is associated with. It's value can be "story " or "media". =item object_instance_id The ID of the story or media document the new data element is associated with. =item place The order of this element relative to the other subelements of the parent element. =item field_type =item element_data The Bric::Biz::ElementType::Parts::FieldType object that defines the structure of the new data element. =item field_type_id =item element_data_id The ID of the Bric::Biz::ElementType::Parts::FieldType object that defines the structure of the new data element. =item parent_id The ID of the container element that is the parent of the new data element. =item active A boolean value indicating whether the container element is active or inactive. =back B NONE. B NONE. B NONE. =cut sub new { my ($class, $init) = @_; # check active and object $init->{_active} = !exists $init->{active} ? 1 : delete $init->{active} ? 1 : 0; $init->{_hold_val} = delete $init->{hold_val} ? 1 : 0; $init->{place} ||= 0; if (my $doc = delete $init->{object}) { $init->{object_instance_id} = $doc->get_version_id; $init->{object_type} = $doc->key_name; } else { throw_gen 'Cannot create without object type.' unless $init->{object_type} } my $atd = delete $init->{field_type} || delete $init->{element_data}; if (!$atd && $init->{field_type_id} || $init->{element_data_id}) { $atd = Bric::Biz::ElementType::Parts::FieldType->lookup({ id => $init->{field_type} ||= delete $init->{element_data} }); } if ($atd) { $init->{field_type_id} = $atd->get_id; $init->{name} = $atd->get_name; $init->{key_name} = $atd->get_key_name; $init->{description} = $atd->get_description; $init->{sql_type} = $atd->get_sql_type; $init->{widget_type} = $atd->get_widget_type; $init->{_autopopulated} = $atd->get_autopopulated; $init->{_multiple} = $atd->get_multiple; $init->{max_length} = $atd->get_max_length; $init->{_field_type} = $atd; } my $self = $class->SUPER::new($init); $self->set_value($atd->get_default_val) if $atd; return $self; } ################################################################################ =item my $data = Bric::Biz::Element::Field->lookup($params) Looks up a data element in the database by its ID and returns it. The lookup parameters are: =over 4 =item id The ID of the data element to lookup. Required. =item object A story or media document object with which the data element is associated. Required unless C is specified. =item object_type The type of document object with which the data element is associated. Must be either "media" or "story". Required unless C is specified. =back B =over 4 =item Missing required Parameter 'id'. =item Missing required Parameter 'object_type' or 'object'. =item Improper type of object passed to lookup. =back B NONE. B NONE. =cut sub lookup { my ($class, $param) = @_; my $self = $class->cache_lookup($param); return $self if $self; # Check for the proper args throw_gen 'Missing required parameter "object" or "id" and "object_type"' unless $param->{object} || $param->{obj} || ($param->{id} || $param->{object_type}); my $elems = _do_list($class, $param, undef); # Throw an exception if we looked up more than one site. throw_da "Too many $class objects found" if @$elems > 1; return $elems->[0]; } ################################################################################ =item my @data = Bric::Biz::Element::Field->list($params) Searches for and returns a list or anonymous array of data element objects. The supported parameters that can be searched are: =over 4 =item object A story or media object with which the data elements are associated. Required unless C is specified. =item object_type The type of document with which the data elements are associated. Required unless C is specified. =item object_instance_id The ID of a story or data object with wich the data elements are associated. Can only be used if C is also specified and C is not specified. May use C for a list of possible values. =item name The name of the data elements. Since the SQL C operator is used with this search parameter, SQL wildcards can be used. May use C for a list of possible values. =item key_name The key name of the data elements. Since the SQL C operator is used with this search parameter, SQL wildcards can be used. May use C for a list of possible values. =item parent_id The ID of the container element that is the parent element of the data elements. May use C for a list of possible values. =item field_type_id =item element_data_id The ID of the Bric::Biz::ElementType::Parts::FieldType object that specifies the structure of the data elements. May use C for a list of possible values. =item active A boolean value indicating whether the returned data elements are active or inactive. =back B NONE. B NONE. B NONE. =cut sub list { my ($class, $param) = @_; _do_list($class, $param, undef); } ################################################################################ =back =head2 Destructors =over 4 =item $data->DESTROY Dummy method to prevent wasting time trying to AUTOLOAD DESTROY. =cut sub DESTROY { # This method should be here even if its empty so that we don't waste time # making Bricolage's autoload method try to find it. } ############################################################################## =back =head2 Public Class Methods =over 4 =item my @ids = Bric::Biz::Element::Field->list_ids($params) Returns a list or anonymous array of data element IDs. The search parameters are the same as for C. B NONE. B NONE. B NONE. =cut sub list_ids { my ($class, $param) = @_; _do_list($class, $param, 1); } ################################################################################ =back =head2 Public Instance Methods See also L, from which Bric::Biz::Element::Field inherits. =over 4 =item my $field_type_id = $data->get_field_type_id =item my $field_type_id = $data->get_element_data_id Returns the ID of the Bric::Biz::ElementType::Parts::FieldType object that describes this element. B NONE. B NONE. B NONE. =item $data->set_field_type_id($field_type_id) =item $data->set_element_data_id($field_type_id) Sets the ID of the Bric::Biz::ElementType::Parts::FieldType object that describes this element. B NONE. B NONE. B NONE. =cut # Compatibility accessors. sub get_element_data_id { shift->get_field_type_id } sub set_element_data_id { shift->set_field_type_id(@_) } =item $field_type = $data->get_field_type =item $field_type = $data->get_field_type Returns the Bric::Biz::ElementType::Parts::FieldType object that defines the structure of this field. B NONE. B NONE. B C is the deprecated form. =cut sub get_field_type { my $self = shift; my $atd = $self->_get('_field_type'); return $atd if $atd; my $dirty = $self->_get__dirty; my $atd_id = $self->_get('field_type_id'); $atd = Bric::Biz::ElementType::Parts::FieldType->lookup({id => $atd_id}); $self->_set(['_field_type'], [$atd]); $self->_set__dirty($dirty); return $atd; } sub get_element_data_obj { shift->get_field_type } ################################################################################ =item $name = $data->get_element_name An alias for C<< $data->get_name >>. B NONE. B NONE. B NONE. =cut sub get_element_name { $_[0]->get_name } ################################################################################ =item $key_name = $data->get_element_key_name An alias for C<< $data->get_key_name >>. B NONE. B NONE. B NONE. =cut sub get_element_key_name { $_[0]->get_key_name } ################################################################################ =item $data->set_value($value) =item $data->set_data($value) Sets the value of the field. Use C if multiple values are allowed on the field (because the field type's C attribute is true). B NONE. B NONE. B C is the deprecated form of this method. =cut sub set_value { my ($self, $value) = @_; # XXX Add code to validate values against options? # OK this is just an attribute my $sql_type = $self->get_sql_type; $value = db_date($value) if $sql_type eq 'date'; my $old_val = $self->_get("_$sql_type\_val"); return $self unless (defined $value && not defined $old_val) || (not defined $value && defined $old_val) || ($value ne $old_val); return $self->_set(["_$sql_type\_val"] => [$value]); } sub set_data { shift->set_value(@_) } ############################################################################## =item $data->set_values(@values) Sets multiple values for the field. Useful for setting the values for "multiple" fields such as multiple select lists. B NONE. B NONE. B NONE. =cut sub set_values { shift->set_value(join '__OPT__', @_) } ################################################################################ =item my $value = $value->get_value =item my $value = $value->get_value($format) =item my $value = $data->get_data =item my $value = $data->get_data($format) Returns the value of this data element. If the SQL type of the data object is "date", then C<$format>, if it is passed, will be used to format the date. Otherwise, the format set in the preferences will be used. B NONE. B NONE. B C is the deprecated form of this method. For fields that can contain multiple values, call C, instead. =cut sub get_value { my ($self, $format) = @_; my $sql_type = $self->get_sql_type or return undef; return $sql_type eq 'date' ? local_date(scalar $self->_get('_date_val'), $format) : scalar $self->_get("_$sql_type\_val"); } sub get_data { shift->get_value(@_) } ################################################################################ =item $field->get_values() my @values = $field->get_values; @values = $field->get_values($format); my $fields_aref = $field->get_values; $fields_aref = $field->get_values($format); Returns a list or array reference of all of the values associated with a field. This method should be called in preference to C for fields that can contain multiple values (i.e., C returns a true value. B NONE. B NONE. B NONE. =cut sub get_values { my $vals = shift->get_value(@_); return wantarray ? split /__OPT__/, $vals : [ split /__OPT__/, $vals ] ; } ############################################################################## =item my $is_autopopulated = $data->is_autopopulated Returns true if the field's value is autopopulate, and false if it is not. B NONE. B NONE. B NONE. =cut sub is_autopopulated { my $self = shift; return $self if $self->_get('_autopopulated'); return; } ############################################################################## =item my $is_multiple = $data->is_multiple Returns true if the field is allowed to have multiple values and false if it is not. B NONE. B NONE. B NONE. =cut sub is_multiple { my $self = shift; return $self if $self->_get('_multiple'); return; } ############################################################################## =item my $get_max_length = $data->get_max_length Returns the maximum allowed length of the field's value. B NONE. B NONE. B NONE. =cut sub get_max_length { my $self = shift; return $self if $self->_get('max_length'); return; } ############################################################################## =item my $sql_type = $data->get_sql_type Returns the SQL type of the field. This value corresponds to the C attribute of the Bric::Biz::ElementType::Parts::FieldType object on which the field is based. B NONE. B NONE. B NONE. =cut ############################################################################## =item my $widget_type = $data->get_widget_type Returns the string indicating the widget to use to display the field. This value corresponds to the C attribute of the Bric::Biz::ElementType::Parts::FieldType object on which the field is based. B NONE. B NONE. B NONE. =cut ################################################################################ =item $data->prepare_clone Prepares the data element to be cloned, such as when a new version of a document is created, or when a document itself is cloned. B NONE. B NONE. B NONE. =cut sub prepare_clone { shift->_set(['id'], [undef]); } ################################################################################ =item my $is_container = $data->is_container Returns false, since data elements are not container elements. B NONE. B NONE. B NONE. =cut sub is_container { return } ############################################################################### =item $data->lock_val For autopopulated data elements, this method prevents the value from being autopopulated. B NONE. B NONE. B NONE. =cut sub lock_val { shift->_set(['_hold_val'], [1]); } ############################################################################### =item $data = $data->unlock_val Allows auotpopulated data elements to be autopopulated. B NONE. B NONE. B NONE. =cut sub unlock_val { shift->_set(['_hold_val'], [0]); } ############################################################################### =item my $is_locked = $data->is_locked Returns true if the element has been locked. B NONE. B NONE. B NONE. =cut sub is_locked { my $self = shift; return unless $self->_get('_hold_val'); return $self; } ############################################################################### =item $data = $data->save Saves the changes to the data element to the database. B NONE. B NONE. B NONE. =cut sub save { my $self = shift; return unless $self->_get__dirty; if ($self->_get('id')) { $self->_do_update; } else { $self->_do_insert; } return $self->_set__dirty(0); } ################################################################################ #==============================================================================# # Private Methods # #======================================# =back =head1 PRIVATE =head2 Private Class Methods =over 4 =item Bric::Biz::Element::Field->_do_list($class, $param, $ids) Called by C or C, this method returns either a list of ids or a list of objects, depending on the third argument. B =over 4 =item * Object of type $obj_class not allowed to have elements. =item * Improper args for list. =back B NONE. B NONE. =cut sub _do_list { my ($class, $params, $ids_only) = @_; my ($obj_type, @params); my @wheres = ('f.field_type__id = ft.id'); while (my ($k, $v) = each %$params) { if ($k eq 'object') { $obj_type = $v->key_name; push @wheres, any_where $v->get_version_id, 'f.object_instance_id = ?', \@params; } elsif ($k eq 'object_type') { $obj_type = $v; } elsif ($k eq 'object_instance_id') { push @wheres, any_where $v, 'f.object_instance_id = ?', \@params; } elsif ($k eq 'id') { push @wheres, any_where $v, 'f.id = ?', \@params; } elsif ($k eq 'active') { push @wheres, 'f.active = ?'; push @params, $v ? 1 : 0; } elsif ($k eq 'parent_id') { push @wheres, any_where($v, 'f.parent_id = ?', \@params); } elsif ($k eq 'field_type_id' || $k eq 'element_data_id') { push @wheres, any_where $v, 'f.field_type__id = ?', \@params; } elsif ($k eq 'key_name' || $k eq 'name' || $k eq 'description') { push @wheres, any_where $v, "LOWER(ft.$k) LIKE LOWER(?)", \@params; } } throw_gen 'Missing required parameter "object" or "object_type"' unless $obj_type; my $tables = "$obj_type\_field f, field_type ft"; my ($qry_cols, $order) = $ids_only ? ('DISTINCT f.id', 'f.id') : (join(', ', @SEL_COLS), 'f.object_instance_id, f.place'); my $wheres = @wheres ? 'WHERE ' . join(' AND ', @wheres) : ''; my $sel = prepare_c(qq{ SELECT $qry_cols FROM $tables $wheres ORDER BY $order }, undef, DEBUG); # Just return the IDs, if they're what's wanted. if ($ids_only) { my $ids = col_aref($sel, @params); return wantarray ? @$ids : $ids; } my @objs; execute($sel, @params); my @d; bind_columns( $sel, \@d[ 0..$#SEL_COLS ] ); while ( fetch($sel) ) { my $self = $class->SUPER::new; $self->_set( [ 'object_type', @SEL_FIELDS ] => [$obj_type, @d] ); $self->_set__dirty(0); push @objs, $self->cache_me; } return wantarray ? @objs : \@objs; } ################################################################################ =back =head2 Private Instance Methods =over 4 =item $data->_do_insert() Called by C, this method inserts the data element into the database. B =over 4 =item * Object must be a media or story to add elements. =back B NONE. B NONE. =cut sub _do_insert { my $self = shift; my $table = $self->get_object_type . '_field'; my $value_cols = join ', ', ('?') x @COLS; my $ins_cols = join ', ', @COLS; my $ins = prepare_c(qq{ INSERT INTO $table ($ins_cols) VALUES ($value_cols) }, undef); execute($ins, $self->_get(@FIELDS) ); return $self->_set( ['id'] => [last_key($table)] ); } ################################################################################ =item $data->_do_update Called by C, this method updates the data element into the database. B =over 4 =item * Object must be a media or story to add elements. =back B NONE. B NONE. =cut sub _do_update { my $self = shift; my $table = $self->get_object_type . '_field'; my $set_cols = join ' = ?, ', @COLS; my $upd = prepare_c(qq{ UPDATE $table SET $set_cols = ? WHERE id = ? }, undef); execute($upd, $self->_get(@FIELDS, 'id')); return $self; } ################################################################################ 1; __END__ =back =head1 NOTES NONE =head1 AUTHORS Michael Soderstrom Refactored by David Wheeler =head1 SEE ALSO L, L, L, L, L =cut