package Bric::Biz::Site; =head1 NAME Bric::Biz::Site - Interface to Bricolage Site Objects =head1 VITALS =over 4 =item Version $LastChangedRevision$ =cut # Grab the Version Number. require Bric; our $VERSION = Bric->VERSION; =item Date $LastChangedDate: 2006-06-17 00:51:26 +0200 (Sat, 17 Jun 2006) $ =item Subversion ID $Id: Site.pm 7538 2006-06-16 22:51:26Z theory $ =back =head1 SYNOPSIS use Bric::Biz::Site; # Constructors. my $site = Bric::Biz::Site->new($init); $site = Bric::Biz::Site->lookup({ id => $id }); my @sites = Bric::Biz::Site->list($params); # Class Methods. my @site_ids = Bric::Biz::Site->list_ids($params); my $meths = Bric::Biz::Site->my_meths; # Instance methods. $id = $site->get_id; my $name = $site->get_name; $site = $site->set_name($name) my $desc = $site->get_description; $site = $site->set_description($desc); my $domain_name = $site->get_domain_name; $site = $site->set_domain_name($domain_name); $site = $site->activate; $site = $site->deactivate; $site = $site->is_active; # Save the changes to the database $site = $site->save; =head1 DESCRIPTION Sites are first-class Bricolage objects designed to manage different sites from within a single Bricolage instance. =cut ############################################################################## # Dependencies ############################################################################## # Standard Dependencies use strict; ############################################################################## # Programmatic Dependences use Bric::Util::Grp::Site; use Bric::Util::Grp::User; use Bric::Util::Grp::Asset; use Bric::Util::DBI qw(:standard col_aref); use Bric::Util::Fault qw(throw_da throw_not_unique throw_undef); use Bric::Util::Priv; use Bric::Config qw(:qa); use Bric::Biz::Category; ############################################################################## # Inheritance ############################################################################## use base qw(Bric); ############################################################################## # Function and Closure Prototypes ############################################################################## my ($get_em, $set_unique_attr, $rename_grps); ############################################################################## # Constants ############################################################################## use constant DEBUG => 0; use constant GROUP_PACKAGE => 'Bric::Util::Grp::Site'; use constant INSTANCE_GROUP_ID => 47; ############################################################################## # Fields ############################################################################## # Private Class Fields my $TABLE = 'site'; my @COLS = qw(id name description domain_name active); my @PROPS = qw(id name description domain_name _active); my $SEL_COLS = 'a.' . join(', a.', @COLS) . ', m.grp__id'; my @SEL_PROPS = (@PROPS, 'grp_ids'); ############################################################################## # Instance Fields BEGIN { Bric::register_fields ({ # Public Fields id => Bric::FIELD_READ, name => Bric::FIELD_RDWR, description => Bric::FIELD_RDWR, domain_name => Bric::FIELD_RDWR, grp_ids => Bric::FIELD_READ, # Private Fields _active => Bric::FIELD_NONE, _asset_grp => Bric::FIELD_NONE, _rename => Bric::FIELD_NONE, }); } ############################################################################## # Constructors. ############################################################################## =head1 INTERFACE =head2 Constructors =head3 new my $site = Bric::Biz::Site->new; $site = Bric::Biz::Site->new($init); Constructs a new site object and returns it. An anonymous hash of initial values may be passed. The supported keys for that hash references are: =over 4 =item name =item description =item domain_name =back The C and C attributes must be globally unique or an exception will be thrown. B =over =item Exception::DA =item Error::Undef =item Error::NotUnique =back =cut sub new { my ($invocant, $init) = @_; my $class = ref $invocant || $invocant; $init->{_active} = 1; push @{$init->{grp_ids}}, INSTANCE_GROUP_ID; my ($name, $domain_name) = delete @{$init}{qw(name domain_name)}; my $self = $class->SUPER::new($init); $self->set_name($name) if defined $name; $self->set_domain_name($domain_name) if defined $domain_name; return $self; } ################################################################################ =head3 lookup my $site = Bric::Biz::Site->lookup({ id => $id }); $site = Bric::Biz::Site->lookup({ name => $name }); $site = Bric::Biz::Site->lookup({ domain_name => $domain_name }); Looks up and constructs an existing site object in the database and returns it. A Site ID, name, or domain name can be used as the site object unique identifier to look up. If no site object is found in the database, then C will return C. B =over 4 =item Exception::DA =back =cut sub lookup { my $invocant = shift; # Look for the site in the cache, first. my $site = $invocant->cache_lookup(@_); return $site if $site; # Look up the site in the database. my $class = ref $invocant || $invocant; $site = $get_em->($class, @_) or return; # Throw an exception if we looked up more than one site. throw_da "Too many $class objects found" if @$site > 1; return $site->[0]; } ############################################################################## =head3 list my @sites = Bric::Biz::Site->list($params); my $sites_aref = Bric::Biz::Site->list($params); Returns a list or anonymous array of site objects based on the search parameters passed via an anonymous hash. The supported lookup keys that may use valid SQL wild card characters are: =over =item name =item description =item domain_name =back The C operator may be used to specify a list of values for any of these parameters. The supported lookup keys that must be an exact value are: =over 4 =item id Site ID. May use C for a list of possible values. =item active A boolean value indicating if the site is active. =item grp_id A Bric::Util::Grp::Site object ID. May use C for a list of possible values. =item element_type_id =item element_id A Bric::Biz::ElementType (element type) ID. May use C for a list of possible values. =item output_channel_id A Bric::Biz:OutputChannel ID. Pass in C to check for when it's C, and "not null" to check for when it's C. May use C for a list of possible values. =back B =over 4 =item Exception::DA =back =cut sub list { $get_em->(@_) } ############################################################################## =head3 href my $sites_href = Bric::Biz::Site->href($params); Returns an anonymous hash of site objects based on the search parameters passed via an anonymous hash. The hash keys will be the site IDs, and the values will be the corresponding sites. The supported lookup keys are the same as those for C. B =over 4 =item Exception::DA =back =cut sub href { $get_em->(@_, undef, 1) } ############################################################################## # Class Methods ############################################################################## =head2 Class Methods =head3 list_ids my @site_ids = Bric::Biz::Site->list_ids($params); my $site_ids_aref = Bric::Biz::Site->list_ids($params); Returns a list or anonymous array of site object IDs based on the search parameters passed via an anonymous hash. The supported lookup keys are the same as for the C method. B =over 4 =item Exception::DA =back =cut sub list_ids { $get_em->(@_, 1) } ############################################################################## =head3 my_meths my $meths = Bric::Biz::Site->my_meths my @meths = Bric::Biz::Site->my_meths(1); my $meths_aref = Bric::Biz::Site->my_meths(1); @meths = Bric::Biz::Site->my_meths(0, 1); $meths_aref = Bric::Biz::Site->my_meths(0, 1); Returns Bric::Biz::Site attribute accessor introspection data. See L for complete documtation of the format of that data. Returns accessor introspection data for the following attributes: =over =item name The site name. A unique identifier attribute. =item domain_name The site domain name. A unique identifier attribute. =item description A description of the site. =item active The site's active status boolean. =back =cut { my @ORD = qw(name domain_name description active); my $METHS = { name => { name => 'name', get_meth => sub { shift->get_name(@_) }, get_args => [], set_meth => sub { shift->set_name(@_) }, set_args => [], disp => 'Name', search => 1, req => 1, props => { type => 'text', length => 32 } }, domain_name => { name => 'domain_name', get_meth => sub { shift->get_domain_name(@_) }, get_args => [], set_meth => sub { shift->set_domain_name(@_) }, set_args => [], disp => 'Domain Name', req => 1, props => { type => 'text', length => 32 } }, description => { name => 'description', get_meth => sub { shift->get_description(@_) }, get_args => [], set_meth => sub { shift->set_description(@_) }, set_args => [], disp => 'Description', props => { type => 'textarea', cols => 40, rows => 4 } }, active => { name => 'active', get_meth => sub { shift->is_active(@_) ? 1 : 0 }, get_args => [], set_meth => sub { $_[1] ? shift->activate(@_) : shift->deactivate(@_) }, set_args => [], disp => 'Active', len => 1, req => 1, props => { type => 'checkbox' } }, }; sub my_meths { my ($invocant, $ord, $ident) = @_; if ($ord) { return wantarray ? @{$METHS}{@ORD} : [@{$METHS}{@ORD}]; } elsif ($ident) { return wantarray ? @{$METHS}{qw(name domain_name)} : [@{$METHS}{qw(name domain_name)}]; } else { return $METHS; } } } ############################################################################## # Instance Methods ############################################################################## =head2 Accessors =head3 id my $id = $site->get_id; Returns the site object's unique database ID. =head3 name my $name = $site->get_name; $site = $site->set_name($name); Get and set the site object's unique name. The value of this attribute must be case-insensitively globally unique. If a non-unique value is passed to C, an exception will be thrown. B =over =item Error::Undef =item Error::NotUnique =back =cut sub set_name { $set_unique_attr->('name', @_) } =head3 description my $description = $site->get_description; $site = $site->set_description($description); Get and set the site object's description. =head3 domain_name my $domain_name = $site->get_domain_name; $site = $site->set_domain_name($domain_name); Get and set the site object's unique domain name. The value of this attribute must be case-insensitively globally unique. If a non-unique value is passed to C, an exception will be thrown. B =over =item Error::Undef =item Error::NotUnique =back =cut sub set_domain_name { $set_unique_attr->('domain_name', @_) } =head3 active $site = $site->activate; $site = $site->deactivate; $site = $site->is_active; Get and set the site object's active status. C and C each return the site object. C returns the site object when the site is active, and C when it is not. =cut sub activate { $_[0]->_set(['_active'], [1]) } sub deactivate { $_[0]->_set(['_active'], [0]) } sub is_active { $_[0]->_get('_active') ? $_[0] : undef } ############################################################################## =head2 Instance Methods =head3 save $site = $site->save; Saves any changes to the site object to the database. Returns the site object on success and throws an exception on failure. B Creates five internal permanent groups. One is a Bric::Util::Grp::Asset group. Its ID is used for the site ID, so that it can easily be used by Bric::Biz::Asset to add the ID to its C attribute because every asset is related to a site. The other four are Bric::Util::Grp::User groups, and one is created for each permission, READ, EDIT, CREATE, and DENY. Furthermore, a Bric::Util::Priv object is created for each of these user groups in turn, to grant their users the appropriate permissions to any assets associated with the site. B =over 4 =item Error::Undef =item Exception::DA =back =cut sub save { my $self = shift; return $self unless $self->_get__dirty; my ($id, $name, $dn) = $self->_get(qw(id name domain_name)); # Make sure we have a name. unless (defined $name and $name ne '') { my $disp = $self->my_meths->{name}{disp}; throw_undef error => "Value of $disp cannot be empty", maketext => ["Value of [_1] cannot be empty", $disp]; } unless (defined $dn and $dn ne '') { my $disp = $self->my_meths->{domain_name}{disp}; throw_undef error => "Value of $disp cannot be empty", maketext => ["Value of [_1] cannot be empty", $disp]; } if ($id) { # Update the record in the database. my $set_cols = join ' = ?, ', @COLS; my $upd = prepare_c(qq{ UPDATE $TABLE SET $set_cols = ? WHERE id = ? }, undef, DEBUG); # Make it so. execute($upd, $self->_get(@PROPS), $id); # Rename the groups, if need be. $rename_grps->($self) if $self->_get('_rename'); } else { # Create a new permanent secret asset group for this site. my $grp = Bric::Util::Grp::Asset->new ({ name => 'Secret Site Asset Group', permanent => 1, secret => 1 }); $grp->save; # Swipe the group's ID for our own! $id = $grp->get_id; $self->_set([qw(id _asset_grp)], [$id, $grp]); # Insert a new record into the database. 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, DEBUG); # Don't try to set ID - it will fail! execute($ins, $self->_get(@PROPS)); # Register this site in the "All Sites" group and add the group ID. $self->register_instance(INSTANCE_GROUP_ID, GROUP_PACKAGE); $self->_set(['grp_ids'], [[INSTANCE_GROUP_ID]]); # Create user permission groups. my $name = $self->get_name; my $privs = Bric::Util::Priv->vals_href ; while (my ($priv, $field) = each %$privs) { my $g = Bric::Util::Grp::User->new ({ name => "$name $field Users", description => "__Site $id Users__", secret => 1, permanent => 1}); $g->save; Bric::Util::Priv->new({ obj_grp => $grp, usr_grp => $g, value => $priv })->save; } # Create a new root category with this site. Bric::Biz::Category->create_new_root_category($self); } # Finish up. $self->SUPER::save; } ############################################################################## =begin private =head2 Private Instance Methods These methods are included for the sake of completeness, but since there appears to be no use for them currently, I'm keeping them private for now. =head3 get_asset_grp my $site_grp = $site->get_asset_grp; Returns the group object for this site. Used for creating permissions objects. B Do I change the name, description, or permission associations of the asset group. Those functions are handled internally by Bric::Biz::Site. Just use this group to add and remove users you wish to associate with a site. B =over 4 =item Exception::DA =back =cut sub get_asset_grp { my $self = shift; my ($id, $grp) = $self->_get(qw(id _asset_grp)); return $grp if $grp; return unless $id; $grp = Bric::Util::Grp::Asset->lookup({ id => $id }); $self->_set(['_asset_grp'], [$grp]); return $grp; } ############################################################################## =head3 list_priv_grps my @grps = $site->list_priv_grps; my $grps_aref = $site->list_priv_grps; Returns a list or array reference of the Bric::Util::Grp::User objects that are used for granting users permission to access the assets in a site. B Do I change the name, description, or permission associations of these groups. Those functions are handled internally by Bric::Biz::Site. Just use these groups to add and remove users you wish to associate with a site. =cut sub list_priv_grps { my $self = shift; my ($id, $old, $new) = $self->_get(qw(id _rename name)); Bric::Util::Grp::User->list({ description => "__Site $id Users__", permanent => 1, all => 1 }); } ############################################################################## =head2 Private Functions =head3 $set_unique_attr sub set_name { $set_unique_attr->('name', @_) } Used by the accessors for attributes that require a globally-unique value. The first argument should be the name of the attribute to be set, and the succeeding values. B =over 4 =item Error::Undef =item Error::NotUnique =back =cut $set_unique_attr = sub { my ($field, $self, $value) = @_; my $disp = $self->my_meths->{$field}{disp}; # Make sure we have a value. throw_undef error => "Value of $disp cannot be empty", maketext => ["Value of [_1] cannot be empty", $disp] unless defined $value and $value ne ''; my $old_value = $self->_get($field); # Just succeed if the new value is the same as the old value. return $self if defined $old_value and lc $value eq lc $old_value; # Check the database for any existing sites with the new value. if ($self->list_ids({ $field => $value })) { throw_not_unique error => "A site with the $disp '$value' already exists", maketext => ["A site with the [_1] '[_2]' already exists", $disp, $value]; } # Success! return $self->_set([$field, '_rename'] => [$value, $old_value]) if $field eq 'name'; return $self->_set([$field] => [$value]); }; ############################################################################## =head3 $get_em $get_em->($invocant, $params, $ids_only); Function used by C, C, and C to retrieve site objects from the database. The arguments are as follows: =over =item C<$invocant> The class name or object that invoked the method call. =item C<$params> The hashref of parameters supported by the method and that can be used to create a SQL search query. =item C<$ids_only> A boolean indicating whether to return site objects or site IDs only. =item C<$href> A boolean indicating whether to return the site objects as a hash reference. Used by C. =back B =over 4 =item Exception::DA =back =cut $get_em = sub { my ($invocant, $params, $ids_only, $href) = @_; my $tables = "$TABLE a, member m, site_member c"; my $wheres = 'a.id = c.object_id AND c.member__id = m.id AND ' . "m.active = '1'"; my @params; while (my ($k, $v) = each %$params) { if ($k eq 'id') { # Simple lookup by ID. $wheres .= ' AND ' . any_where $v, 'a.id = ?', \@params; } elsif ($k eq 'active') { # Simple lookup by "active" boolean. $wheres .= ' AND a.active = ?'; push @params, $v ? '1' : '0'; } elsif ($k eq 'grp_id') { # Look up by group membership. $tables .= ", member m2, site_member c2"; $wheres .= " AND a.id = c2.object_id AND c2.member__id = m2.id" . " AND m2.active = '1' " . any_where $v, 'AND m2.grp__id = ?', \@params; } elsif ($k eq 'element_type_id' || $k eq 'element_id') { # Look up by element association. $tables .= ", element_type__site es"; $wheres .= " AND a.id = es.site__id AND es.active = '1'" . any_where $v, 'AND es.element_type__id = ?', \@params; } elsif ($k eq 'output_channel_id') { # Look up by output channel association. $tables .= ", output_channel oc"; $wheres .= " AND a.id = oc.site__id AND "; if (not defined $v) { $wheres .= "oc.id IS NULL"; } elsif (lc $v eq 'not null') { $wheres .= "oc.id IS NOT NULL"; } else { $wheres .= any_where $v, 'oc.id = ?', \@params; } } else { # Simple string comparison. $wheres .= ' AND ' . any_where $v, "LOWER(a.$k) LIKE LOWER(?)", \@params; } } my ($qry_cols, $order) = $ids_only ? (\'DISTINCT a.id', 'a.id') : (\$SEL_COLS, 'LOWER(a.name), a.id'); my $sel = prepare_c(qq{ SELECT $$qry_cols FROM $tables WHERE $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 unless @$ids; return wantarray ? @$ids : $ids; } execute($sel, @params); my (@d, @sites, %sites, $grp_ids); bind_columns($sel, \@d[0..$#SEL_PROPS]); my $last = -1; my $class = ref $invocant || $invocant; while (fetch($sel)) { if ($d[0] != $last) { $last = $d[0]; # Create a new site object. my $self = $class->SUPER::new; # Get a reference to the array of group IDs. $grp_ids = $d[$#d] = [$d[$#d]]; $self->_set(\@SEL_PROPS, \@d); $self->_set__dirty; # Disables dirty flag. $href ? $sites{$d[0]} = $self->cache_me : push @sites, $self->cache_me; } else { push @$grp_ids, $d[$#d]; } } return \%sites if $href; return unless @sites; return wantarray ? @sites : \@sites; }; ############################################################################## =head3 $rename_grps $get_em->($self); Looks up the associated user groups in the database and renames them. Used when the name of the site has changed. Since the permission user groups will appear in the UI, it makes sense that they always be appropriately named. B =over 4 =item Exception::DA =back =cut $rename_grps = sub { my $self = shift; my ($old, $new) = $self->_get(qw(_rename name)); foreach my $grp ($self->list_priv_grps) { (my $name = $grp->get_name) =~ s/^$old/$new/; $grp->set_name($name); $grp->save; } $self->_set(['_rename'], []); }; 1; __END__ ############################################################################## =pod =end private =head1 AUTHOR David Wheeler =head1 SEE ALSO =over 4 =item L Each category object is associated with a site. =item L Each output channel object is associated with a site. =item L Each top-level element object is associated with one or more site. =item L Each workflow object is associated with a site. =item L Each asseet object is associated with a site. =item L Each destination object is associated with a site. =back =head1 COPYRIGHT AND LICENSE Copyright (c) 2003 Kineticode, Inc. See L for complete license terms and conditions. =cut