232 lines
8.6 KiB
Perl
232 lines
8.6 KiB
Perl
|
|
#!/usr/bin/perl
|
||
|
|
#
|
||
|
|
# DW::User::Edges
|
||
|
|
#
|
||
|
|
# This module defines relationships between accounts. It allows for finding
|
||
|
|
# edges, defining edges, removing edges, and other tasks related to the edges
|
||
|
|
# that can exist between accounts. Methods are added to the LJ::User/DW::User
|
||
|
|
# classes as appropriate.
|
||
|
|
#
|
||
|
|
# Authors:
|
||
|
|
# Mark Smith <mark@dreamwidth.org>
|
||
|
|
#
|
||
|
|
# Copyright (c) 2008 by Dreamwidth Studios, LLC.
|
||
|
|
#
|
||
|
|
# This program is free software; you may redistribute it and/or modify it under
|
||
|
|
# the same terms as Perl itself. For a copy of the license, please reference
|
||
|
|
# 'perldoc perlartistic' or 'perldoc perlgpl'.
|
||
|
|
#
|
||
|
|
|
||
|
|
###############################################################################
|
||
|
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||
|
|
###############################################################################
|
||
|
|
|
||
|
|
package DW::User::Edges;
|
||
|
|
use strict;
|
||
|
|
|
||
|
|
# FYI - including edges is done at the end of this file. scroll down to the
|
||
|
|
# comment denoted 'XXX'.
|
||
|
|
|
||
|
|
# overall list of edges that are valid, if it's not in this list (and not one
|
||
|
|
# of the special edges like 'all') then we don't know how to deal with it
|
||
|
|
our %VALID_EDGES;
|
||
|
|
|
||
|
|
# defines a new edge in the valid list above. this function is assumed to be
|
||
|
|
# called at startup, so we are safe using 'die' for any error conditions, as
|
||
|
|
# we WANT to prevent site startup.
|
||
|
|
sub define_edge {
|
||
|
|
my ( $name, $opts ) = @_;
|
||
|
|
|
||
|
|
die "Attempt to define edge with bad name: $name.\n"
|
||
|
|
unless $name =~ /^[\w\d-]+$/;
|
||
|
|
die "Attempt to re-define edge $name.\n"
|
||
|
|
if exists $VALID_EDGES{$name} && !$LJ::IS_DEV_SERVER;
|
||
|
|
die "Defined edge $name contains no type.\n"
|
||
|
|
unless $opts->{type};
|
||
|
|
die "Defined edge $name contains invalid type: $opts->{type}.\n"
|
||
|
|
unless $opts->{type} =~ /^(?:int|bool|hashref)$/;
|
||
|
|
die "Defined edge $name contains invalid db_edge: $opts->{db_edge}.\n"
|
||
|
|
if exists $opts->{db_edge} && $opts->{db_edge} !~ /^\w$/;
|
||
|
|
|
||
|
|
if ( my $hr = $opts->{options} ) {
|
||
|
|
die "Defined edge $name options not a hashref.\n"
|
||
|
|
unless ref $hr && ref $hr eq 'HASH';
|
||
|
|
die "Edge $name must have type 'hashref'.\n"
|
||
|
|
unless $opts->{type} && $opts->{type} eq 'hashref';
|
||
|
|
|
||
|
|
foreach my $opt ( keys %$hr ) {
|
||
|
|
die "Defined edge $name has invalid option name: $opt.\n"
|
||
|
|
unless $opt =~ /^[\w\d-]+$/;
|
||
|
|
die "Defined edge $name option $opt is not a hashref.\n"
|
||
|
|
unless $hr->{$opt} && ref $hr->{$opt} eq 'HASH';
|
||
|
|
die "Defined edge $name option $opt has invalid type: $hr->{$opt}->{type}.\n"
|
||
|
|
unless $hr->{$opt}->{type} && $hr->{$opt}->{type} =~ /^(?:int|bool)$/;
|
||
|
|
|
||
|
|
# by default, not required, fill that in if they didn't specify it
|
||
|
|
$hr->{$opt}->{required} ||= 0;
|
||
|
|
die "Defined edge $name option $opt value 'required' must be 0 or 1.\n"
|
||
|
|
unless $hr->{$opt}->{required} =~ /^(?:0|1)$/;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach (qw/ add_sub del_sub /) {
|
||
|
|
die "Defined edge $name does not define $_.\n"
|
||
|
|
unless $opts->{$_};
|
||
|
|
die "Defined edge $name not given a code reference for $_.\n"
|
||
|
|
if ref $opts->{$_} ne 'CODE';
|
||
|
|
}
|
||
|
|
|
||
|
|
$VALID_EDGES{$name} = $opts;
|
||
|
|
}
|
||
|
|
|
||
|
|
# takes as input a hashref of items to be validated and makes sure that the
|
||
|
|
# inputs are valid according to what we know about the defined edges
|
||
|
|
sub validate_edges {
|
||
|
|
my $edges = $_[0];
|
||
|
|
|
||
|
|
# error stuff
|
||
|
|
my $err = sub {
|
||
|
|
warn "validate_edges: " . shift() . "\n";
|
||
|
|
return 0;
|
||
|
|
};
|
||
|
|
return $err->('Invalid parameter')
|
||
|
|
unless ref $edges eq 'ARRAY' || ref $edges eq 'HASH';
|
||
|
|
|
||
|
|
# iterate over each edge in the hash and validate
|
||
|
|
my @iter = ref $edges eq 'HASH' ? keys %$edges : @$edges;
|
||
|
|
foreach my $edge (@iter) {
|
||
|
|
|
||
|
|
# if it's not in valid edges, it's bunk
|
||
|
|
my $er = $VALID_EDGES{$edge};
|
||
|
|
return $err->("Edge '$edge' unknown.") unless $er;
|
||
|
|
|
||
|
|
# at this point, if they gave us an array of items to check (as opposed to a hash) then we
|
||
|
|
# assume it's good. the array behavior is used in cases where they are deleting edges and
|
||
|
|
# only know the name of the edge.
|
||
|
|
next if ref $edges eq 'ARRAY';
|
||
|
|
|
||
|
|
# type assurance
|
||
|
|
return $err->("Edge $edge of type bool with invalid value [$edges->{$edge}].")
|
||
|
|
if $er->{type} eq 'bool' && $edges->{$edge} !~ /^(?:0|1)$/;
|
||
|
|
return $err->("Edge $edge of type int with invalid value [$edges->{$edge}].")
|
||
|
|
if $er->{type} eq 'int' && $edges->{$edge} !~ /^\d+$/;
|
||
|
|
|
||
|
|
# if it's a hashref/subopt/complex type, check the options
|
||
|
|
if ( $er->{type} eq 'hashref' ) {
|
||
|
|
return $err->("Edge $edge of type hashref with invalid value.")
|
||
|
|
unless ref $edges->{$edge} eq 'HASH';
|
||
|
|
|
||
|
|
## FIXME: we don't assert all of the 'required' options are passed yet
|
||
|
|
|
||
|
|
my $opts = $er->{options};
|
||
|
|
foreach my $opt ( keys %$opts ) {
|
||
|
|
|
||
|
|
# set default if we've been given one
|
||
|
|
$edges->{$edge}->{$opt} = $opts->{$opt}->{default}
|
||
|
|
if !exists $edges->{$edge}->{$opt}
|
||
|
|
&& exists $opts->{$opt}->{default};
|
||
|
|
|
||
|
|
# skip the edge if they didn't provide and it's not required
|
||
|
|
next
|
||
|
|
unless exists $edges->{$edge}->{$opt}
|
||
|
|
|| $opts->{$opt}->{required};
|
||
|
|
|
||
|
|
# now error check
|
||
|
|
return $err->(
|
||
|
|
"Edge $edge option $opt of type bool with invalid value [$edges->{$edge}->{$opt}]."
|
||
|
|
) if $opts->{$opt}->{type} eq 'bool' && $edges->{$edge}->{$opt} !~ /^(?:0|1)$/;
|
||
|
|
return $err->(
|
||
|
|
"Edge $edge option $opt of type int with invalid value [$edges->{$edge}->{$opt}]."
|
||
|
|
) if $opts->{$opt}->{type} eq 'int' && $edges->{$edge}->{$opt} !~ /^\d+$/;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# should be valid at this point
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# XXX: add new edge modules that are global here
|
||
|
|
use DW::User::Edges::WatchTrust;
|
||
|
|
use DW::User::Edges::CommMembership;
|
||
|
|
|
||
|
|
###############################################################################
|
||
|
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||
|
|
###############################################################################
|
||
|
|
|
||
|
|
# for now, we push our methods into the DW::User namespace
|
||
|
|
package DW::User;
|
||
|
|
use strict;
|
||
|
|
|
||
|
|
# adds edges between one user and another
|
||
|
|
sub add_edge {
|
||
|
|
my ( $from_u, $to_u, %edges ) = @_;
|
||
|
|
|
||
|
|
# need u objects
|
||
|
|
$from_u = LJ::want_user($from_u);
|
||
|
|
$to_u = LJ::want_user($to_u);
|
||
|
|
|
||
|
|
# error check inputs
|
||
|
|
return 0 unless $from_u && $to_u;
|
||
|
|
return 0 unless DW::User::Edges::validate_edges( \%edges );
|
||
|
|
|
||
|
|
# now we try to add these edges. note that we do this in this way so that
|
||
|
|
# multiple edges can be consumed by one add sub.
|
||
|
|
my @to_add = keys %edges;
|
||
|
|
my $ok = 1;
|
||
|
|
while ( my $key = shift @to_add ) {
|
||
|
|
|
||
|
|
# some modules will define multiple edges, and so one call to add_sub might
|
||
|
|
# get rid of more than one edge, so we have to do this check to ensure that
|
||
|
|
# the edge still exists
|
||
|
|
next unless $edges{$key};
|
||
|
|
|
||
|
|
# simply calls an add_sub to handle the edge. we expect them to remove the
|
||
|
|
# edge from the hashref if they process it.
|
||
|
|
my $success = $DW::User::Edges::VALID_EDGES{$key}->{add_sub}->( $from_u, $to_u, \%edges );
|
||
|
|
$ok &&= $success; # will zero out if any edges fail
|
||
|
|
}
|
||
|
|
|
||
|
|
# all good
|
||
|
|
return $ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
# removes an edge between two users
|
||
|
|
sub remove_edge {
|
||
|
|
my ( $from_u, $to_u, %edges ) = @_;
|
||
|
|
|
||
|
|
# need u objects
|
||
|
|
$from_u = LJ::want_user($from_u);
|
||
|
|
$to_u = LJ::want_user($to_u);
|
||
|
|
|
||
|
|
# error check inputs
|
||
|
|
return 0 unless $from_u && $to_u;
|
||
|
|
return 0 unless DW::User::Edges::validate_edges( \%edges );
|
||
|
|
|
||
|
|
# now we try to remove these edges. note that we do this in this way so that
|
||
|
|
# multiple edges can be consumed by one remove sub.
|
||
|
|
my @to_del = keys %edges;
|
||
|
|
my $ok = 1;
|
||
|
|
while ( my $key = shift @to_del ) {
|
||
|
|
|
||
|
|
# some modules will define multiple edges, and so one call to add_sub might
|
||
|
|
# get rid of more than one edge, so we have to do this check to ensure that
|
||
|
|
# the edge still exists
|
||
|
|
next unless $edges{$key};
|
||
|
|
|
||
|
|
# simply calls an add_sub to handle the edge. we expect them to remove the
|
||
|
|
# edge from the hashref if they process it.
|
||
|
|
my $success = $DW::User::Edges::VALID_EDGES{$key}->{del_sub}->( $from_u, $to_u, \%edges );
|
||
|
|
$ok &&= $success; # will zero out if any edges fail
|
||
|
|
}
|
||
|
|
|
||
|
|
# all good
|
||
|
|
return $ok;
|
||
|
|
}
|
||
|
|
|
||
|
|
# and now we link these into the LJ::User namespace for backwards compatibility
|
||
|
|
*LJ::User::add_edge = \&add_edge;
|
||
|
|
*LJ::User::remove_edge = \&remove_edge;
|
||
|
|
|
||
|
|
1;
|