First pass at adding key files
This commit is contained in:
		@@ -0,0 +1,95 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Links - enhanced directory management system
 | 
			
		||||
#
 | 
			
		||||
#   Website  : http://gossamer-threads.com/
 | 
			
		||||
#   Support  : http://gossamer-threads.com/scripts/support/
 | 
			
		||||
#   CVS Info : 087,071,086,086,085      
 | 
			
		||||
#   Revision : $Id: CatLinks.pm,v 1.4 2006/03/25 01:13:35 brewt Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# Redistribution in part or in whole strictly prohibited. Please
 | 
			
		||||
# see LICENSE file for full details.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
 | 
			
		||||
package Links::Table::CatLinks;
 | 
			
		||||
# ==================================================================
 | 
			
		||||
use strict;
 | 
			
		||||
use Links qw/:payment :objects/;
 | 
			
		||||
use GT::SQL;
 | 
			
		||||
use GT::SQL::Table;
 | 
			
		||||
use vars qw /@ISA $ERROR_MESSAGE @DELETING/;
 | 
			
		||||
 | 
			
		||||
@ISA = qw/GT::SQL::Table/;
 | 
			
		||||
$ERROR_MESSAGE = 'GT::SQL';
 | 
			
		||||
@DELETING = (); # Used by Links::Table::Links
 | 
			
		||||
 | 
			
		||||
sub delete {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# We override the default CatLinks delete to delete any links that will no
 | 
			
		||||
# longer be referenced as a result of the deletion.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $cond) = @_;
 | 
			
		||||
 | 
			
		||||
    ref $cond or return $self->fatal(BADARGS => '$catlinks->delete(condition)');
 | 
			
		||||
 | 
			
		||||
# Get the CatLinks rows that are about to be deleted
 | 
			
		||||
    my (%delete, %links);
 | 
			
		||||
    my $sth = $self->select($cond);
 | 
			
		||||
    while (my $row = $sth->fetchrow_hashref) {
 | 
			
		||||
        $delete{$row->{LinkID}}++;
 | 
			
		||||
        if (exists $links{$row->{LinkID}}) {
 | 
			
		||||
            push @{$links{$row->{LinkID}}}, $row->{CategoryID};
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            $links{$row->{LinkID}} = [$row->{CategoryID}];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Delete the CatLinks rows
 | 
			
		||||
    my $ret = $self->SUPER::delete($cond) or return;
 | 
			
		||||
 | 
			
		||||
# Get the links that still exist in the CatLinks table after the delete (ie.
 | 
			
		||||
# links that were in multiple categories).  These are the links that shouldn't
 | 
			
		||||
# be deleted from the Links table.
 | 
			
		||||
    my @remaining = keys %delete ? $self->select('LinkID', { LinkID => [keys %delete] })->fetchall_list : ();
 | 
			
		||||
    for (@remaining, @DELETING) {
 | 
			
		||||
        delete $delete{$_};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Non-validated links don't increment Category counts.
 | 
			
		||||
    my @notval = keys %links ? $DB->table('Links')->select('ID', { ID => [keys %links], isValidated => 'No' })->fetchall_list : ();
 | 
			
		||||
    for (@notval, @DELETING) {
 | 
			
		||||
        delete $links{$_};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Any links in %delete have no references to it from CatLinks
 | 
			
		||||
    if (keys %delete) {
 | 
			
		||||
        $DB->table('Links')->delete({ ID => [keys %delete] });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Build a list of categories that need their counts updated
 | 
			
		||||
    my %cats;
 | 
			
		||||
    for (keys %links) {
 | 
			
		||||
        for (@{$links{$_}}) {
 | 
			
		||||
            $cats{$_}++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Update the Category link counts
 | 
			
		||||
    if (keys %cats) {
 | 
			
		||||
        my $category = $DB->table('Category');
 | 
			
		||||
        my %change;
 | 
			
		||||
        while (my ($catid, $count) = each %cats) {
 | 
			
		||||
            push @{$change{-$count}}, $catid;
 | 
			
		||||
        }
 | 
			
		||||
        $category->link_count(\%change);
 | 
			
		||||
 | 
			
		||||
        while (my ($change, $ids) = each %change) {
 | 
			
		||||
            $category->update({ Direct_Links => \("Direct_Links - " . abs $change) }, { ID => $ids });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
@@ -0,0 +1,638 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Links - enhanced directory management system
 | 
			
		||||
#
 | 
			
		||||
#   Website  : http://gossamer-threads.com/
 | 
			
		||||
#   Support  : http://gossamer-threads.com/scripts/support/
 | 
			
		||||
#   CVS Info : 087,071,086,086,085      
 | 
			
		||||
#   Revision : $Id: Category.pm,v 1.29 2009/05/11 05:57:45 brewt Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# Redistribution in part or in whole strictly prohibited. Please
 | 
			
		||||
# see LICENSE file for full details.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
 | 
			
		||||
package Links::Table::Category;
 | 
			
		||||
# ==================================================================
 | 
			
		||||
use strict;
 | 
			
		||||
use Links qw/:payment :objects/;
 | 
			
		||||
use GT::SQL;
 | 
			
		||||
use GT::SQL::Table;
 | 
			
		||||
use GT::Lock qw/lock unlock LOCK_TRY/;
 | 
			
		||||
use vars qw /@ISA $ERRORS $ERROR_MESSAGE/;
 | 
			
		||||
 | 
			
		||||
@ISA = qw/GT::SQL::Table/;
 | 
			
		||||
$ERROR_MESSAGE = 'GT::SQL';
 | 
			
		||||
$ERRORS = {
 | 
			
		||||
    BADCATNAME => "Invalid category name: %s",
 | 
			
		||||
    BADCATID   => "Invalid category id: %s",
 | 
			
		||||
    BADCATSUG  => "There is no category with that name. Perhaps you meant: %s",
 | 
			
		||||
    CATEXISTS  => "A category with the name '%s' already exists.",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
# We wrap new() to handle updating Number_of_Links - but only once: the first
 | 
			
		||||
# time a Category table object is created.
 | 
			
		||||
sub new {
 | 
			
		||||
    my $self = shift->SUPER::new(@_) or return;
 | 
			
		||||
 | 
			
		||||
    return $self if $STASH{expired_links}++;
 | 
			
		||||
 | 
			
		||||
    my $links = $DB->table('Links');
 | 
			
		||||
    my $cond;
 | 
			
		||||
    if ($CFG->{payment}->{enabled}) {
 | 
			
		||||
        $cond = GT::SQL::Condition->new(
 | 
			
		||||
            ExpiryCounted => '=' => 0,
 | 
			
		||||
            ExpiryDate    => '<' => time,
 | 
			
		||||
            isValidated   => '=' => 'Yes'
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $cond = GT::SQL::Condition->new(
 | 
			
		||||
            ExpiryCounted => '=' => 1,
 | 
			
		||||
            isValidated   => '=' => 'Yes'
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    # Don't select the ID's here because we haven't established a lock.  Since
 | 
			
		||||
    # most requests won't catch expired links, doing a count here to avoid
 | 
			
		||||
    # needing the lock is going to be slightly slower occassionally, but
 | 
			
		||||
    # usually faster.
 | 
			
		||||
    return $self unless $links->count($cond);
 | 
			
		||||
 | 
			
		||||
    # We've now determined that there _are_ links that have expired that
 | 
			
		||||
    # haven't been counted yet, so we establish a lock (to prevent a race
 | 
			
		||||
    # condition), and then update the links counts for categories that have
 | 
			
		||||
    # newly-expired links.  If getting the lock fails, simply return - this is
 | 
			
		||||
    # only likely to happen when another process has the lock and is performing
 | 
			
		||||
    # the updates already, or when a previous process with a lock died - the
 | 
			
		||||
    # 120 should make sure that such a condition doesn't last longer than 2
 | 
			
		||||
    # minutes.
 | 
			
		||||
    lock cat_link_count => 1, LOCK_TRY, 120
 | 
			
		||||
        or return $self;
 | 
			
		||||
 | 
			
		||||
    my @links = $links->select(ID => $cond)->fetchall_list;
 | 
			
		||||
    unless (@links) { # Despite the above count, there might not be links now if we had to wait for a lock
 | 
			
		||||
        unlock 'cat_link_count';
 | 
			
		||||
        return $self;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($CFG->{payment}->{expired_is_free}) {
 | 
			
		||||
        # This gets a bit hairy - expired links need to become free but NOT in
 | 
			
		||||
        # required categories.  On the other hand, links in non-required
 | 
			
		||||
        # categories don't affect the count.
 | 
			
		||||
        my %req_links = map { $_ => 1 } $DB->table('Category', 'CatLinks')->select(LinkID => { LinkID => \@links, Payment_Mode => $CFG->{payment}->{mode} == REQUIRED ? [GLOBAL, REQUIRED] : REQUIRED })->fetchall_list;
 | 
			
		||||
        my @to_free = grep !$req_links{$_}, @links;
 | 
			
		||||
        if (@to_free) {
 | 
			
		||||
            $DB->table('Links')->update({ LinkExpired => \'ExpiryDate' }, { ID => \@to_free });
 | 
			
		||||
            $DB->table('Links')->update({ ExpiryDate => FREE }, { ID => \@to_free });
 | 
			
		||||
        }
 | 
			
		||||
        @links = keys %req_links;
 | 
			
		||||
        unless (@links) {
 | 
			
		||||
            unlock 'cat_link_count';
 | 
			
		||||
            return $self;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    my $catlinks = $DB->table('CatLinks');
 | 
			
		||||
    $catlinks->select_options('GROUP BY CategoryID');
 | 
			
		||||
    my %cats = $catlinks->select(CategoryID => 'COUNT(*)' => { LinkID => \@links })->fetchall_list; # FIXME this query can be huge and will fail (the select() will fail and return undef)
 | 
			
		||||
    my %adjust;
 | 
			
		||||
    my %direct_adj;
 | 
			
		||||
 | 
			
		||||
    my $parents = $self->parents([keys %cats]);
 | 
			
		||||
    for my $cat_id (keys %cats) {
 | 
			
		||||
        $adjust{$cat_id} ||= 0;
 | 
			
		||||
        $adjust{$cat_id} += $cats{$cat_id};
 | 
			
		||||
        $direct_adj{$cat_id} ||= 0;
 | 
			
		||||
        $direct_adj{$cat_id} += $cats{$cat_id};
 | 
			
		||||
        for (@{$parents->{$cat_id}}) {
 | 
			
		||||
            $adjust{$_} ||= 0;
 | 
			
		||||
            $adjust{$_} += $adjust{$cat_id};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my %change;
 | 
			
		||||
    while (my ($id, $change) = each %adjust) {
 | 
			
		||||
        push @{$change{$CFG->{payment}->{enabled} ? -$change : $change}}, $id;
 | 
			
		||||
    }
 | 
			
		||||
    my %change_direct;
 | 
			
		||||
    while (my ($id, $change) = each %direct_adj) {
 | 
			
		||||
        push @{$change_direct{$CFG->{payment}->{enabled} ? -$change : $change}}, $id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while (my ($adjust, $ids) = each %change) {
 | 
			
		||||
        $self->update({ Number_of_Links => \("Number_of_Links " . ($adjust >= 0 ? '+' : '-') . ' ' . abs $adjust) }, { ID => $ids });
 | 
			
		||||
    }
 | 
			
		||||
    while (my ($adjust, $ids) = each %change_direct) {
 | 
			
		||||
        $self->update({ Direct_Links => \("Direct_Links " . ($adjust >= 0 ? '+' : '-') . ' ' . abs $adjust) }, { ID => $ids });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $links->update({ ExpiryCounted => $CFG->{payment}->{enabled} ? 1 : 0 }, { ID => \@links });
 | 
			
		||||
 | 
			
		||||
    unlock 'cat_link_count';
 | 
			
		||||
 | 
			
		||||
    return $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub add {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Adds a category, but passes it through the plugin system.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $p    = (ref $_[0] eq 'HASH') ? shift : {@_};
 | 
			
		||||
 | 
			
		||||
    $PLG->dispatch('add_category', sub { return $self->_plg_add(@_) }, $p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_add {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Add a category.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $p) = @_;
 | 
			
		||||
 | 
			
		||||
    $self->can_add($p) or return;
 | 
			
		||||
 | 
			
		||||
# If successful, we need to update timestamps of parents to denote a change.
 | 
			
		||||
    if (my $id = $self->SUPER::add($p)) {
 | 
			
		||||
        if ($p->{FatherID}) {
 | 
			
		||||
            $self->update(
 | 
			
		||||
                { Timestmp => \"NOW()" },
 | 
			
		||||
                { ID => $self->parents($id) },
 | 
			
		||||
                { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 }
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return $id;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub can_add {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Confirms that a category can be added.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $p = $self->common_param(@_) or return $self->warn(BADARGS => 'Usage: $table->add(HASH or HASH_REF or CGI)');
 | 
			
		||||
 | 
			
		||||
# Checks that the FatherID exists and set the full name.
 | 
			
		||||
    $p->{FatherID} ||= 0;
 | 
			
		||||
    if ($p->{FatherID} =~ /\D/) {
 | 
			
		||||
        my $sth = $self->select(ID => Full_Name => { Full_Name => $p->{FatherID} });
 | 
			
		||||
        if (my @row = $sth->fetchrow) {
 | 
			
		||||
            $p->{FatherID} = $row[0];
 | 
			
		||||
            $p->{Full_Name} = "$row[1]/$p->{Name}";
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            my $names = $self->suggestions($p->{FatherID});
 | 
			
		||||
            return $self->warn(
 | 
			
		||||
                @$names
 | 
			
		||||
                    ? (BADCATSUG  => '<ul>' . join('', map "<li>$_</li>", @$names) . '</ul>')
 | 
			
		||||
                    : (BADCATNAME => $p->{FatherId})
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif ($p->{FatherID} != 0) {
 | 
			
		||||
        my $full_name = $self->get_name_from_id($p->{FatherID}) or return $self->warn(BADCATID => $p->{FatherID});
 | 
			
		||||
        $p->{Full_Name} = "$full_name/$p->{Name}";
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $p->{Full_Name} = $p->{Name};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Checks that there is no other category with the same (Name, FatherID)
 | 
			
		||||
    return $self->warn(CATEXISTS => $p->{Name})
 | 
			
		||||
        if $self->count({ Name => $p->{Name}, FatherID => $p->{FatherID} });
 | 
			
		||||
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub delete {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Deletes a category, but passes through the plugin system.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $where) = @_;
 | 
			
		||||
    if (not ref $where or ref $where eq 'ARRAY') {
 | 
			
		||||
        $where = { ID => $where };
 | 
			
		||||
    }
 | 
			
		||||
    return $self->fatal(BADARGS => 'Usage: $category->delete(condition)')
 | 
			
		||||
        unless ref $where eq 'HASH' or UNIVERSAL::isa($where, 'GT::SQL::Condition');
 | 
			
		||||
 | 
			
		||||
    my $ret;
 | 
			
		||||
    my %cats = $self->select(qw/ID Direct_Links/ => $where)->fetchall_list;
 | 
			
		||||
    if ($PLG->active_plugins('delete_category')) {
 | 
			
		||||
        for my $id (keys %cats) {
 | 
			
		||||
            my $r = $PLG->dispatch('delete_category', sub { return $self->SUPER::delete(@_) }, { ID => $id });
 | 
			
		||||
            $ret += $r if defined $r;
 | 
			
		||||
        }
 | 
			
		||||
        $ret = '0 but true' if (defined $ret and $ret == 0) or not keys %cats;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $ret = $self->SUPER::delete($where);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $ret unless $ret;
 | 
			
		||||
 | 
			
		||||
# Clear out the cache as the hierarchy has changed.
 | 
			
		||||
    $self->_clear_cache;
 | 
			
		||||
 | 
			
		||||
    $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub modify {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Modifies a category, but passes through the plugin system.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $cat) = @_;
 | 
			
		||||
    $PLG->dispatch('modify_category', sub { return $self->_plg_modify(@_) }, $cat);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_modify {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Modify a single category.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $set  = shift      or return $self->error('BADARGS', 'FATAL', "Usage: \$cat->modify( { col => value ... } ).");
 | 
			
		||||
    my $id   = $set->{ID} or return $self->error('BADARGS', 'FATAL', "No primary key passed to modify!");
 | 
			
		||||
 | 
			
		||||
# Get the original info.
 | 
			
		||||
    my $orig = $self->select(qw/ID FatherID Full_Name Name Number_of_Links/ => { ID => $id })->fetchrow_hashref
 | 
			
		||||
        or return $self->warn(BADCATID => $id);
 | 
			
		||||
 | 
			
		||||
# Fix up the father ID.
 | 
			
		||||
    $set->{FatherID} ||= 0;
 | 
			
		||||
    if ($set->{FatherID} !~ /^\d+$/) {
 | 
			
		||||
        my $new_id = $self->get_id_from_name($set->{FatherID});
 | 
			
		||||
        if (! $new_id) {
 | 
			
		||||
            my $names = $self->suggestions($set->{FatherID});
 | 
			
		||||
            return $self->error(@$names
 | 
			
		||||
                ? ('BADCATSUG', 'WARN', "<ul>" . join('', map "<li>$_</li>", @$names) . "</ul>")
 | 
			
		||||
                : ('BADCATNAME', 'WARN', $set->{FatherID})
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        $set->{FatherID} = $new_id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $self->can_modify($set, $orig) or return;
 | 
			
		||||
 | 
			
		||||
    if ($orig->{Name} eq $set->{Name} and $orig->{FatherID} == $set->{FatherID}) {
 | 
			
		||||
        # Name and parent haven't changed, no special modify handling needed
 | 
			
		||||
        return $self->SUPER::modify($set);
 | 
			
		||||
    }
 | 
			
		||||
    elsif ($orig->{FatherID} == $set->{FatherID}) {
 | 
			
		||||
        # Name has changed, but parent is the same: update ancestors'
 | 
			
		||||
        # timestamps, change the full name, and update subcategory names.
 | 
			
		||||
        ($set->{Full_Name} = $orig->{Full_Name}) =~ s/\Q$orig->{Name}\E$/$set->{Name}/i;
 | 
			
		||||
        my $ret = $self->SUPER::modify($set);
 | 
			
		||||
        if ($ret) {
 | 
			
		||||
            # Update was successful, update the timestamp of old and new parents
 | 
			
		||||
 | 
			
		||||
            # Clear the as the tree just changed
 | 
			
		||||
            $self->_clear_cache;
 | 
			
		||||
 | 
			
		||||
            if ($set->{FatherID}) {
 | 
			
		||||
                my $parents = $self->parents($id);
 | 
			
		||||
                $self->update({ Timestmp => \"NOW()" }, { ID => $parents }, { GT_SQL_SKIP_CHECK => 1 })
 | 
			
		||||
                    if @$parents;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $self->update_full_name($id, $orig->{Full_Name}, $set->{Full_Name});
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $ret;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        # The category has moved; get the new parent's full name and update
 | 
			
		||||
        my $fn = $self->select(Full_Name => { ID => $set->{FatherID} })->fetchrow;
 | 
			
		||||
        $set->{Full_Name} = ($fn ? "$fn/" : '') . $set->{Name};
 | 
			
		||||
 | 
			
		||||
        my $ret = $self->SUPER::modify($set);
 | 
			
		||||
        if ($ret) {
 | 
			
		||||
# Clear the cache as the tree has changed.
 | 
			
		||||
            $self->_clear_cache;
 | 
			
		||||
 | 
			
		||||
            $self->update_full_name($id, $orig->{Full_Name}, $set->{Full_Name});
 | 
			
		||||
 | 
			
		||||
# Now update counters on the above parents.
 | 
			
		||||
# Clear out the cache as otherwise we get our old parents.
 | 
			
		||||
            if ($orig->{Number_of_Links} != 0) {
 | 
			
		||||
                $self->link_count($orig->{FatherID}, -$orig->{Number_of_Links});
 | 
			
		||||
                $self->link_count($set->{FatherID}, $orig->{Number_of_Links});
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
# Clear out the cache.
 | 
			
		||||
        $self->_clear_cache;
 | 
			
		||||
        return $ret;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub update_full_name {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Call this after changing a category's Full_Name to change all the category's
 | 
			
		||||
# children's full names.  Call with the category ID, old full name, and new
 | 
			
		||||
# full name.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $id, $old, $new) = @_;
 | 
			
		||||
 | 
			
		||||
    my @children = @{$self->children($id)};
 | 
			
		||||
 | 
			
		||||
    my $new_escaped = $self->quote($new . '/');
 | 
			
		||||
    my $old_offset = length($old) + 2;
 | 
			
		||||
    my $set;
 | 
			
		||||
    if (lc $self->{connect}->{driver} eq 'mysql') {
 | 
			
		||||
        $set = "CONCAT($new_escaped, SUBSTRING(Full_Name, $old_offset))";
 | 
			
		||||
    }
 | 
			
		||||
    elsif (lc $self->{connect}->{driver} eq 'pg') {
 | 
			
		||||
        $set = "$new_escaped || SUBSTRING(Full_Name, $old_offset)";
 | 
			
		||||
    }
 | 
			
		||||
    elsif (lc $self->{connect}->{driver} eq 'odbc' or lc $self->{connect}->{driver} eq 'mssql') {
 | 
			
		||||
        $set = "$new_escaped + SUBSTRING(Full_Name, $old_offset, 255)";
 | 
			
		||||
    }
 | 
			
		||||
    elsif (lc $self->{connect}->{driver} eq 'oracle') {
 | 
			
		||||
        $set = "$new_escaped || SUBSTR(Full_Name, $old_offset)";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($set) {
 | 
			
		||||
        $self->update(
 | 
			
		||||
            { Full_Name => \$set },
 | 
			
		||||
            { ID => \@children },
 | 
			
		||||
            { GT_SQL_SKIP_CHECK => 1 }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        my $sth = $self->select(qw/ID Full_Name/ => { ID => \@children });
 | 
			
		||||
        while (my ($id, $full_name) = $sth->fetchrow) {
 | 
			
		||||
            $full_name =~ s/^\Q$old/$new/ or next;
 | 
			
		||||
            $self->update({ Full_Name => $full_name }, { ID => $id }, { GT_SQL_SKIP_CHECK => 1 });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub can_modify {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Returns 1 if a record can be modified, undef otherwise.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $new, $orig) = @_;
 | 
			
		||||
 | 
			
		||||
# If the FatherID has changed, make sure the new father exists. If it's 0, then
 | 
			
		||||
# it's the root category and we don't worry about it.
 | 
			
		||||
    if ($orig->{FatherID} != $new->{FatherID} or $orig->{Name} ne $new->{Name}) {
 | 
			
		||||
        if ($orig->{FatherID} != $new->{FatherID} and $new->{FatherID}) {
 | 
			
		||||
            $self->count({ ID => $new->{FatherID} }) or return $self->error('BADCATID', 'WARN', $new->{FatherID});
 | 
			
		||||
        }
 | 
			
		||||
# Now make sure the new FatherID,Name doesn't exist as it must be unique.
 | 
			
		||||
        $self->count({ FatherID => $new->{FatherID}, Name => $new->{Name} }, GT::SQL::Condition->new(ID => '!=' => $orig->{ID})) and return $self->error('CATEXISTS', 'WARN', $new->{Name});
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub template_set {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Return the value of template set to use for a given category.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $id   = shift or return $self->error('BADARGS', 'FATAL', "Must pass category id to template_set");
 | 
			
		||||
    return '' unless (exists $self->{schema}->{cols}->{Category_Template});
 | 
			
		||||
 | 
			
		||||
    return $self->{_template_cache}->{$id} if (exists $self->{_template_cache}->{$id});
 | 
			
		||||
 | 
			
		||||
# If this category has a template set, use it.
 | 
			
		||||
    my $cat_info = $self->select(Category_Template => { ID => $id })->fetchrow;
 | 
			
		||||
 | 
			
		||||
# Otherwise look at its parents.
 | 
			
		||||
    unless ($cat_info) {
 | 
			
		||||
        my $parents = $self->parents($id);
 | 
			
		||||
        for my $parent (@$parents) {
 | 
			
		||||
            $cat_info = $self->select(Category_Template => { ID => $parent })->fetchrow
 | 
			
		||||
                and last;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    $self->{_template_cache}->{$id} = $cat_info || '';
 | 
			
		||||
    return $self->{_template_cache}->{$id};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub parents {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Returns parent ID's given one or more category ID's.  If called with a single
 | 
			
		||||
# category ID, the return value is an array reference of the ID's of the
 | 
			
		||||
# category's parents, from father => root.  If called with an array reference
 | 
			
		||||
# of category ID's, the return value is a hash reference of
 | 
			
		||||
# (ID => [rootid ... parentid]) pairs, with one pair for each category.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $id = shift or return $self->error('BADARGS', 'FATAL', "No category id passed to parents");
 | 
			
		||||
 | 
			
		||||
    my (%ret, @lookup);
 | 
			
		||||
    for (ref $id ? @$id : $id) {
 | 
			
		||||
        unless ($ret{$_} = $self->{_parent_cache}->{$_}) {
 | 
			
		||||
            push @lookup, $_;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (@lookup) {
 | 
			
		||||
        my $parents = $self->tree->parent_ids(id => \@lookup, include_dist => 1);
 | 
			
		||||
        for (@lookup) {
 | 
			
		||||
            $ret{$_} = $self->{_parent_cache}->{$_} = [sort { $parents->{$_}->{$b} <=> $parents->{$_}->{$a} } keys %{$parents->{$_}}];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return ref $id
 | 
			
		||||
        ? \%ret
 | 
			
		||||
        : [reverse @{$ret{$id}}];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub children {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Exactly like parents(), except you get descendants rather than ancestors, and
 | 
			
		||||
# you get them in shallowest => deepest.
 | 
			
		||||
#
 | 
			
		||||
    my $self  = shift;
 | 
			
		||||
    my $id    = shift or return $self->error('BADARGS', 'FATAL', "No category id passed to children");
 | 
			
		||||
 | 
			
		||||
    my (%ret, @lookup);
 | 
			
		||||
    for (ref $id ? @$id : $id) {
 | 
			
		||||
        unless ($ret{$_} = $self->{_child_cache}->{$_}) {
 | 
			
		||||
            push @lookup, $_;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (@lookup) {
 | 
			
		||||
        my $children = $self->tree->child_ids(id => \@lookup, include_dist => 1);
 | 
			
		||||
        for (@lookup) {
 | 
			
		||||
            $ret{$_} = $self->{_child_cache}->{$_} = [sort { $children->{$_}->{$a} <=> $children->{$_}->{$b} } keys %{$children->{$_}}];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return ref $id
 | 
			
		||||
        ? \%ret
 | 
			
		||||
        : $ret{$id};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub suggestions {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Returns a list of suggested category names. Takes a name and optional limit.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $name = shift;
 | 
			
		||||
    $name =~ y/\r\n//d;
 | 
			
		||||
    $name =~ /\S/ or return [];
 | 
			
		||||
 | 
			
		||||
    $self->select_options('LIMIT 10');
 | 
			
		||||
    return [$self->select(Full_Name => GT::SQL::Condition->new(Full_Name => LIKE => "%$name%"))->fetchall_list];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub link_count {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Change the Number_of_Links count by n for specified id, and all parents.  You
 | 
			
		||||
# can pass multiple ID's by passing an array reference for ID.  You can pass
 | 
			
		||||
# both multiple change values by passing a hash reference of (CHANGE => [ID,
 | 
			
		||||
# ...]) pairs as the ID (the change value passed to the function will be
 | 
			
		||||
# ignored).  Note that Direct_Links counts are NOT changed.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $id, $change) = @_;
 | 
			
		||||
 | 
			
		||||
    my %id;
 | 
			
		||||
    if (!$id or ref $id eq 'ARRAY' and !@$id) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    elsif (ref $id eq 'HASH') {
 | 
			
		||||
        %id = %$id;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        %id = ($change => ref $id ? $id : [$id]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my %final;
 | 
			
		||||
    while (my ($change, $id) = each %id) {
 | 
			
		||||
        for (@$id) {
 | 
			
		||||
            $final{$_} = ($final{$_} || 0) + $change;
 | 
			
		||||
        }
 | 
			
		||||
        my $parents = $self->tree->parent_ids(id => $id);
 | 
			
		||||
        for my $parent (keys %$parents) {
 | 
			
		||||
            for (@{$parents->{$parent}}) {
 | 
			
		||||
                $final{$_} += $change;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my %change;
 | 
			
		||||
    for (keys %final) {
 | 
			
		||||
        push @{$change{$final{$_}}}, $_;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (keys %change) {
 | 
			
		||||
        $self->update(
 | 
			
		||||
            { Number_of_Links => \('Number_of_Links' . ($_ > 0 ? ' + ' : ' - ') . abs) },
 | 
			
		||||
            { ID => $change{$_} },
 | 
			
		||||
            { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub changed {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Returns a statement handle that can be looped through to get a list
 | 
			
		||||
# of changed categories.
 | 
			
		||||
#
 | 
			
		||||
    Links::init_date();
 | 
			
		||||
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $date = GT::Date::date_get(defined $_[0] ? $_[0] : time);
 | 
			
		||||
    my $sth  = $self->select(GT::SQL::Condition->new(Timestmp => '>=' => $date ));
 | 
			
		||||
    return $sth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub get_id_from_name {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Returns the category id based on the name.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $name) = @_;
 | 
			
		||||
    $name =~ y/\r\n//d;
 | 
			
		||||
    $name =~ /\S/ or return;
 | 
			
		||||
 | 
			
		||||
    return $self->{_id_cache}->{$name} if exists $self->{_id_cache}->{$name};
 | 
			
		||||
    $self->{_id_cache}->{$name} = $self->select(ID => { Full_Name => $name })->fetchrow_array;
 | 
			
		||||
    return $self->{_id_cache}->{$name};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub get_name_from_id {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Returns the category full name based on the id.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $id) = @_;
 | 
			
		||||
    return $self->{_name_cache}->{$id} if exists $self->{_name_cache}->{$id};
 | 
			
		||||
    return $self->{_name_cache}->{$id} = $self->select(Full_Name => { ID => $id })->fetchrow;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub as_url {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $name, $format) = @_;
 | 
			
		||||
    return $PLG->dispatch('category_as_url', sub { return $self->_as_url(@_) }, $name, $format);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _as_url {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Return the passed-in category name as a formatted category path, usable for
 | 
			
		||||
# static templates.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $name, $format) = @_;
 | 
			
		||||
 | 
			
		||||
    my $cat = $self->select({ Full_Name => $name })->fetchrow_hashref
 | 
			
		||||
        or return $name;
 | 
			
		||||
    require Links::Tools;
 | 
			
		||||
    $format ||= $IN->param('d') ? $CFG->{build_category_dynamic} ? "%$CFG->{build_category_dynamic}%" : '' : $CFG->{build_category_format};
 | 
			
		||||
    $format ||= '%Full_Name%';
 | 
			
		||||
    if ($format eq '%Full_Name%' and ($IN->param('d') or $CFG->{build_format_compat})) {
 | 
			
		||||
        # Old Links SQL's (prior to configurable category naming) didn't
 | 
			
		||||
        # coalesce multiple _'s into a single _, and dynamic mode still depends
 | 
			
		||||
        # on that behaviour - so if the format is just Full_Name, mimic the old
 | 
			
		||||
        # behaviour.
 | 
			
		||||
        (my $ret = $cat->{Full_Name}) =~ y|a-zA-Z0-9_/-|_|c;
 | 
			
		||||
        return $ret;
 | 
			
		||||
    }
 | 
			
		||||
    if ($format =~ /%Full_ID%/) {
 | 
			
		||||
        $cat->{Full_ID} = join '/', (@{$self->tree->parent_ids(id => $cat->{ID})}, $cat->{ID});
 | 
			
		||||
    }
 | 
			
		||||
    return Links::Tools::parse_format(
 | 
			
		||||
        $format,
 | 
			
		||||
        %$cat,
 | 
			
		||||
        clean => 1
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_new {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Sets the new flag for a given category id (or list).
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my @ids  = ref $_[0] eq 'ARRAY' ? @{shift()} : shift;
 | 
			
		||||
    my $rel  = $DB->table('Links', 'CatLinks', 'Category');
 | 
			
		||||
    for my $id (@ids) {
 | 
			
		||||
        my $parents = $self->parents($id);
 | 
			
		||||
        my @pids    = reverse @$parents;
 | 
			
		||||
        push @pids, $id;
 | 
			
		||||
 | 
			
		||||
        for my $pid (@pids) {
 | 
			
		||||
            my $children = $self->children($pid);
 | 
			
		||||
            $rel->select_options('GROUP BY Add_Date');
 | 
			
		||||
            my $sth = $rel->select(qw/MAX(Add_Date) isNew/ => GT::SQL::Condition->new(
 | 
			
		||||
                CategoryID => '=' => [$pid, @$children],
 | 
			
		||||
                VIEWABLE
 | 
			
		||||
            ));
 | 
			
		||||
            my ($newest, $new) = $sth->fetchrow;
 | 
			
		||||
            $self->update(
 | 
			
		||||
                { Has_New_Links => $new || 'No', Newest_Link => $newest },
 | 
			
		||||
                { ID => $pid },
 | 
			
		||||
                { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 }
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _clear_cache {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Clear out cache results whenever a category is added/deleted/changed.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    delete @$self{qw{_parent_cache _child_cache _name_cache _id_cache _template_cache}};
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Links - enhanced directory management system
 | 
			
		||||
#
 | 
			
		||||
#   Website  : http://gossamer-threads.com/
 | 
			
		||||
#   Support  : http://gossamer-threads.com/scripts/support/
 | 
			
		||||
#   CVS Info : 087,071,086,086,085      
 | 
			
		||||
#   Revision : $Id: ClickTrack.pm,v 1.3 2009/05/08 19:56:50 brewt Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# Redistribution in part or in whole strictly prohibited. Please
 | 
			
		||||
# see LICENSE file for full details.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
#
 | 
			
		||||
# ClickTrack is subclassed so that new() is wrapped to handle ClickTrack table
 | 
			
		||||
# cleanups - but only the first time a ClickTrack table object is created, and
 | 
			
		||||
# only once / day.
 | 
			
		||||
 | 
			
		||||
package Links::Table::ClickTrack;
 | 
			
		||||
 | 
			
		||||
use strict;
 | 
			
		||||
use Links qw/$CFG %STASH/;
 | 
			
		||||
use GT::SQL::Table ();
 | 
			
		||||
use vars qw/@ISA/;
 | 
			
		||||
@ISA = 'GT::SQL::Table';
 | 
			
		||||
 | 
			
		||||
sub new {
 | 
			
		||||
    my $self = shift->SUPER::new(@_) or return;
 | 
			
		||||
 | 
			
		||||
    return $self if $STASH{clicktrack_cleanup}++;
 | 
			
		||||
    Links::init_date();
 | 
			
		||||
    my $cleanup_date = GT::Date::date_get(time - 2*24*60*60, '%yyyy%-%mm%-%dd%');
 | 
			
		||||
    return $self if $CFG->{last_clicktrack_cleanup} and $cleanup_date eq $CFG->{last_clicktrack_cleanup};
 | 
			
		||||
 | 
			
		||||
    $self->delete(GT::SQL::Condition->new(Created => '<' => $cleanup_date));
 | 
			
		||||
    $CFG->{last_clicktrack_cleanup} = $cleanup_date;
 | 
			
		||||
    $CFG->save;
 | 
			
		||||
 | 
			
		||||
    $self;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
							
								
								
									
										630
									
								
								site/slowtwitch.com/cgi-bin/articles/admin/Links/Table/Links.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										630
									
								
								site/slowtwitch.com/cgi-bin/articles/admin/Links/Table/Links.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,630 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Links - enhanced directory management system
 | 
			
		||||
#
 | 
			
		||||
#   Website  : http://gossamer-threads.com/
 | 
			
		||||
#   Support  : http://gossamer-threads.com/scripts/support/
 | 
			
		||||
#   CVS Info : 087,071,086,086,085      
 | 
			
		||||
#   Revision : $Id: Links.pm,v 1.33 2009/05/11 05:57:45 brewt Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2001 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# Redistribution in part or in whole strictly prohibited. Please
 | 
			
		||||
# see LICENSE file for full details.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
 | 
			
		||||
package Links::Table::Links;
 | 
			
		||||
# ==================================================================
 | 
			
		||||
use strict;
 | 
			
		||||
use Links qw/:payment :objects/;
 | 
			
		||||
use GT::SQL;
 | 
			
		||||
use GT::SQL::Table;
 | 
			
		||||
use vars qw /@ISA $DEBUG $ERRORS $ERROR_MESSAGE $CATLINK/;
 | 
			
		||||
 | 
			
		||||
@ISA = qw/GT::SQL::Table/;
 | 
			
		||||
$DEBUG = 0;
 | 
			
		||||
$ERROR_MESSAGE = 'GT::SQL';
 | 
			
		||||
 | 
			
		||||
$ERRORS = {
 | 
			
		||||
    NOCATEGORY  => "You did not specify a category for this link.",
 | 
			
		||||
    BADCATSUG   => "There is no category with that name. Perhaps you meant: %s",
 | 
			
		||||
    BADCATEGORY => "Invalid Category '%s', it does not exist.",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
sub _query {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Overrides the default query to allow searching on category values.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $opts = $self->common_param(@_) or return $self->fatal(BADARGS => '$obj->insert(HASH or HASH_REF or CGI) only.');
 | 
			
		||||
 | 
			
		||||
# Parse date/time
 | 
			
		||||
    if ($opts->{ExpiryDate} and $opts->{ExpiryDate} !~ /^\s*-?\d+\s*$/) {
 | 
			
		||||
        my $converted = Links::date_to_time($opts->{ExpiryDate});
 | 
			
		||||
        $opts->{ExpiryDate} = $converted if defined $converted;
 | 
			
		||||
    }
 | 
			
		||||
    my $cat_id = $opts->{'CatLinks.CategoryID'} or return $self->SUPER::_query($opts);
 | 
			
		||||
    $cat_id    = $self->clean_category_ids($cat_id) or return;
 | 
			
		||||
 | 
			
		||||
# Strip out values that are empty or blank (as query is generally
 | 
			
		||||
# derived from cgi input).
 | 
			
		||||
    my %input = map { $_ => $opts->{$_} } grep { defined $opts->{$_} and $opts->{$_} !~ /^\s*$/ } keys %$opts;
 | 
			
		||||
    $opts = \%input;
 | 
			
		||||
 | 
			
		||||
# Create a CatLinks,Links table to do the search.
 | 
			
		||||
    my $db = $DB->table('CatLinks','Links');
 | 
			
		||||
 | 
			
		||||
# Now start handling the search
 | 
			
		||||
    my $cond = $self->build_query_cond($opts, $self->{schema}->{cols});
 | 
			
		||||
    if ( (ref $cond) =~ /::sth/i ) {
 | 
			
		||||
        return $cond;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Set the limit clause, defaults to 25, set to -1 for none.
 | 
			
		||||
    my $in       = $self->_get_search_opts($opts);
 | 
			
		||||
    my $offset   = ($in->{nh} - 1) * $in->{mh};
 | 
			
		||||
    $db->select_options("ORDER BY $in->{sb} $in->{so}") if $in->{sb};
 | 
			
		||||
    $db->select_options("LIMIT $in->{mh} OFFSET $offset") unless $in->{mh} == -1;
 | 
			
		||||
 | 
			
		||||
# Add to the condition the category clause.
 | 
			
		||||
    my $final = new GT::SQL::Condition;
 | 
			
		||||
    $final->add($cond) if $cond;
 | 
			
		||||
    $final->add('CatLinks.CategoryID', 'IN', $cat_id);
 | 
			
		||||
 | 
			
		||||
# Now do the select.
 | 
			
		||||
    my @sel;
 | 
			
		||||
    push @sel, $final if $final;
 | 
			
		||||
    push @sel, $opts->{rs} if $opts->{rs} and $final;
 | 
			
		||||
    my $sth = $db->select(@sel) or return;
 | 
			
		||||
    $self->{last_hits} = $db->hits;
 | 
			
		||||
    return $sth;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub add {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Adds a link, but passes through Plugins::Dispatch.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $p    = (ref $_[0] eq 'HASH') ? shift : {@_};
 | 
			
		||||
 | 
			
		||||
    $PLG->dispatch('add_link', sub { $self->_plg_add(@_) }, $p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_add {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Add a link.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $p) = @_;
 | 
			
		||||
 | 
			
		||||
# Check to see if we can add a link, all errors get cascaded back.
 | 
			
		||||
    $p->{'CatLinks.CategoryID'} or return $self->warn('NOCATEGORY');
 | 
			
		||||
    $p->{'CatLinks.CategoryID'} = $self->clean_category_ids($p->{'CatLinks.CategoryID'}) or return;
 | 
			
		||||
 | 
			
		||||
    $self->set_date_flags($p);
 | 
			
		||||
 | 
			
		||||
    my $counted = ($p->{isValidated} eq 'Yes' and $p->{ExpiryDate} >= time);
 | 
			
		||||
    if ($p->{ExpiryDate} >= time) {
 | 
			
		||||
        $p->{ExpiryCounted} = 0;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $p->{ExpiryCounted} = 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Add the link, and return if there was an error, the error is propogated back.
 | 
			
		||||
    my $id = $self->SUPER::add($p) or return;
 | 
			
		||||
 | 
			
		||||
# Now add all the categories that the link belongs too.
 | 
			
		||||
    my $cat     = $DB->table('Category');
 | 
			
		||||
    my $cat_lnk = $DB->table('CatLinks');
 | 
			
		||||
 | 
			
		||||
    my @cat_ids = ref $p->{'CatLinks.CategoryID'} ? @{$p->{'CatLinks.CategoryID'}} : $p->{'CatLinks.CategoryID'};
 | 
			
		||||
    my %parents;
 | 
			
		||||
 | 
			
		||||
# Get a list of all the parents that this will affect.
 | 
			
		||||
    foreach my $cat_id (@cat_ids) {
 | 
			
		||||
        $cat_lnk->insert({ LinkID => $id, CategoryID => $cat_id }) or return;
 | 
			
		||||
        if ($counted) {
 | 
			
		||||
            for (@{$cat->parents($cat_id)}) { $parents{$_}++ }
 | 
			
		||||
            $parents{$cat_id}++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Now update those categories.
 | 
			
		||||
    if ($counted) {
 | 
			
		||||
        $cat->update(
 | 
			
		||||
            { Newest_Link => $p->{Add_Date}, Has_New_Links => 'Yes', Number_of_Links => \"Number_of_Links + 1" },
 | 
			
		||||
            { ID => [keys %parents] },
 | 
			
		||||
            { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 }
 | 
			
		||||
        );
 | 
			
		||||
        $cat->update({ Direct_Links => \"Direct_Links + 1" }, { ID => \@cat_ids });
 | 
			
		||||
    }
 | 
			
		||||
    return $id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub delete {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Deletes one or more links; there is a 'delete_link' hook below that can be
 | 
			
		||||
# used by plugins.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $where) = @_;
 | 
			
		||||
    if (not ref $where or ref $where eq 'ARRAY') {
 | 
			
		||||
        $where = { ID => $where };
 | 
			
		||||
    }
 | 
			
		||||
    return $self->fatal(BADARGS => 'Usage: $links->delete(condition)')
 | 
			
		||||
        unless (ref $where eq 'HASH' and keys %$where) or (UNIVERSAL::isa($where, 'GT::SQL::Condition') and $where->sql);
 | 
			
		||||
 | 
			
		||||
    my $CatLinks = $DB->table('CatLinks');
 | 
			
		||||
 | 
			
		||||
    # Sometimes { ID => x, CatLinks.CategoryID => y } gets passed in; it is
 | 
			
		||||
    # wrong - CatLinks->delete should be used instead, which will recall this
 | 
			
		||||
    # subroutine if any links need to be deleted.
 | 
			
		||||
    if (ref $where eq 'HASH' and $where->{ID} and not ref $where->{ID}
 | 
			
		||||
        and $where->{'CatLinks.CategoryID'} and not ref $where->{'CatLinks.CategoryID'}) {
 | 
			
		||||
        return $CatLinks->delete({ LinkID => $where->{ID}, CategoryID => $where->{'CatLinks.CategoryID'} });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Delete called with a normal condition
 | 
			
		||||
    my $links = $self->select(qw/ID isValidated Add_Date ExpiryDate ExpiryCounted/ => $where)->fetchall_hashref;
 | 
			
		||||
    @$links or return '0 but true';
 | 
			
		||||
 | 
			
		||||
    my $new_cutoff = GT::Date::timelocal(0, 0, 0, (localtime time - $CFG->{build_new_cutoff})[3 .. 5]);
 | 
			
		||||
    my (@counts, @new);
 | 
			
		||||
    for (@$links) {
 | 
			
		||||
        my $add_time = GT::Date::timelocal(GT::Date::parse_format($_->{Add_Date}, GT::Date::FORMAT_DATE));
 | 
			
		||||
        if ($_->{isValidated} eq 'Yes' and ($_->{ExpiryDate} >= time or not $_->{ExpiryCounted})) {
 | 
			
		||||
            push @counts, $_->{ID};
 | 
			
		||||
            push @new, $_->{ID} if $add_time >= $new_cutoff;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    # Figure out how much each category needs to be decremented
 | 
			
		||||
    $CatLinks->select_options("GROUP BY CategoryID");
 | 
			
		||||
    my %cats = $CatLinks->select(CategoryID => 'COUNT(*)' => { LinkID => \@counts })->fetchall_list;
 | 
			
		||||
 | 
			
		||||
    my %change;
 | 
			
		||||
    while (my ($catid, $count) = each %cats) {
 | 
			
		||||
        push @{$change{-$count}}, $catid;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my $ret;
 | 
			
		||||
    {
 | 
			
		||||
        # CatLinks, which has an fk to Links.ID, needs to know what we're
 | 
			
		||||
        # deleting so that it doesn't try to recall Links->delete
 | 
			
		||||
        local @Links::Table::CatLinks::DELETING;
 | 
			
		||||
        if ($PLG->active_plugins('delete_link')) {
 | 
			
		||||
            for (@$links) {
 | 
			
		||||
                @Links::Table::CatLinks::DELETING = $_->{ID};
 | 
			
		||||
                my $r = $PLG->dispatch('delete_link', sub { return $self->_plg_delete_link(@_) }, { ID => $_->{ID} });
 | 
			
		||||
                $ret += $r if defined $r;
 | 
			
		||||
            }
 | 
			
		||||
            $ret = '0 but true' if defined $ret and $ret == 0;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            # delete_link plugin hook isn't being used, a single delete will do it
 | 
			
		||||
            my @lids = map $_->{ID}, @$links;
 | 
			
		||||
            @Links::Table::CatLinks::DELETING = @lids;
 | 
			
		||||
            $ret = $self->SUPER::delete({ ID => \@lids });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my $Category = $DB->table('Category');
 | 
			
		||||
    $Category->link_count(\%change);
 | 
			
		||||
 | 
			
		||||
    while (my ($change, $ids) = each %change) {
 | 
			
		||||
        $Category->update({ Direct_Links => \("Direct_Links - " . abs $change) }, { ID => $ids });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $CatLinks->select_options("GROUP BY CategoryID");
 | 
			
		||||
    my @new_cats = $CatLinks->select(CategoryID => { LinkID => \@new })->fetchall_list;
 | 
			
		||||
# Now reset new flags on categories.
 | 
			
		||||
    if ($ret and @new_cats) {
 | 
			
		||||
        $Category->set_new(\@new_cats);
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_delete_link {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Deletes a single link ID (plugin hook 'delete_link'.  The second argument,
 | 
			
		||||
# $link, will, for historic reasons, always be a hash reference containing an
 | 
			
		||||
# 'ID' key, the value of which is the ID of the link to be deleted.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $link) = @_;
 | 
			
		||||
    my $link_id = $link->{ID};
 | 
			
		||||
 | 
			
		||||
    return $self->SUPER::delete({ ID => $link_id });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub modify {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Modifies a link, but passes through the plugin system.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $link) = @_;
 | 
			
		||||
    $PLG->dispatch('modify_link', sub { return $self->_plg_modify(@_) }, $link);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_modify {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Modify a single link.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $set  = shift      or return $self->fatal(BADARGS => "Usage: \$cat->modify( { col => value ... } ).");
 | 
			
		||||
    my $id   = $set->{ID} or return $self->fatal(BADARGS => "No primary key passed to modify!");
 | 
			
		||||
 | 
			
		||||
# Let's set the changed date to right now.
 | 
			
		||||
    Links::init_date();
 | 
			
		||||
    $set->{Mod_Date} = GT::Date::date_get();
 | 
			
		||||
 | 
			
		||||
# Force it to uncounted so that category counts will be properly updated
 | 
			
		||||
    $set->{ExpiryCounted} = 0;
 | 
			
		||||
 | 
			
		||||
# Check to see if we can modify (makes sure valid category id's were set).
 | 
			
		||||
    $set->{'CatLinks.CategoryID'} or return $self->warn('NOCATEGORY');
 | 
			
		||||
    $set->{'CatLinks.CategoryID'} = $self->clean_category_ids($set->{'CatLinks.CategoryID'}) or return;
 | 
			
		||||
 | 
			
		||||
    $self->set_date_flags($set);
 | 
			
		||||
 | 
			
		||||
# Check to see if we are changing from not validated => validated.
 | 
			
		||||
    my ($old_validated, $old_expiry) = $self->select(qw/isValidated ExpiryDate/ => { ID => $set->{ID} })->fetchrow;
 | 
			
		||||
 | 
			
		||||
# Check that the ExpiryDate is valid for the categories the link is in.
 | 
			
		||||
    require Links::Payment;
 | 
			
		||||
    my $expiry = (exists $set->{ExpiryDate} and $set->{ExpiryDate}) ? $set->{ExpiryDate} : $old_expiry;
 | 
			
		||||
    $expiry = Links::Payment::check_expiry_date({ ExpiryDate => $expiry }, $set->{'CatLinks.CategoryID'});
 | 
			
		||||
    $set->{ExpiryDate} = $expiry if $expiry;
 | 
			
		||||
 | 
			
		||||
    my $new_validated = exists $set->{isValidated} ? $set->{isValidated} : $old_validated;
 | 
			
		||||
    my $new_expiry    = exists $set->{ExpiryDate}  ? $set->{ExpiryDate}  : $old_expiry;
 | 
			
		||||
 | 
			
		||||
    my $was_counted = $old_validated eq 'Yes' && $old_expiry >= time;
 | 
			
		||||
    my $now_counted = $new_validated eq 'Yes' && $new_expiry >= time;
 | 
			
		||||
 | 
			
		||||
    if (exists $set->{ExpiryDate}) {
 | 
			
		||||
        $set->{ExpiryCounted} = $set->{ExpiryDate} >= time ? 0 : 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
=for comment
 | 
			
		||||
Here are the various cases that the category count update code needs to handle and what to do in those cases:
 | 
			
		||||
 | 
			
		||||
add the link to a new category
 | 
			
		||||
    was counted, now_counted    increment new cat
 | 
			
		||||
    !was counted, now counted   increment new cat
 | 
			
		||||
    was counted, !now counted   nothing
 | 
			
		||||
    !was counted, !now counted  nothing
 | 
			
		||||
 | 
			
		||||
remove the link from a category
 | 
			
		||||
    was counted, now_counted    decrement old cat (CatLinks handles correctly)
 | 
			
		||||
    !was counted, now counted   nothing           (CatLinks handles incorrectly and decrements in some cases, we fix and increment)
 | 
			
		||||
    was counted, !now counted   decrement old cat (CatLinks handles correctly)
 | 
			
		||||
    !was counted, !now counted  nothing           (CatLinks handles correctly)
 | 
			
		||||
 | 
			
		||||
no category changes
 | 
			
		||||
    was counted, now_counted    nothing
 | 
			
		||||
    !was counted, now counted   increment cats
 | 
			
		||||
    was counted, !now counted   decrement cats
 | 
			
		||||
    !was counted, !now counted  nothing
 | 
			
		||||
 | 
			
		||||
the above combined (what the code needs to do)
 | 
			
		||||
    was counted, now_counted    increment new cats
 | 
			
		||||
    !was counted, now counted   increment curr cats, leave removed cats
 | 
			
		||||
    was counted, !now counted   decrement cats except removed and new cats (ie. decrement curr cats, except new cats)
 | 
			
		||||
    !was counted, !now counted  nothing
 | 
			
		||||
=cut
 | 
			
		||||
 | 
			
		||||
# Do the update.
 | 
			
		||||
    my $ret = $self->SUPER::modify($set);
 | 
			
		||||
# Check to see if the link has been moved into another category.
 | 
			
		||||
    if ($ret) {
 | 
			
		||||
        my $cat_lnk = $DB->table('CatLinks');
 | 
			
		||||
        my %orig_ids = map { $_ => 1 } $cat_lnk->select(CategoryID => { LinkID => $id })->fetchall_list;
 | 
			
		||||
        my %cat_ids = map { $_ => 1 } ref $set->{'CatLinks.CategoryID'} ? @{$set->{'CatLinks.CategoryID'}} : $set->{'CatLinks.CategoryID'};
 | 
			
		||||
 | 
			
		||||
# Categories that the link has just been added to
 | 
			
		||||
        my @new_cats = grep !$orig_ids{$_}, keys %cat_ids;
 | 
			
		||||
# Categories that the link has just been removed from
 | 
			
		||||
        my @old_cats = grep !$cat_ids{$_}, keys %orig_ids;
 | 
			
		||||
 | 
			
		||||
        my %link_adjustment;
 | 
			
		||||
        my $Category = $DB->table('Category');
 | 
			
		||||
 | 
			
		||||
# CatLinks doesn't update category counts on insert, so it's done further down in the code
 | 
			
		||||
        if (@new_cats) {
 | 
			
		||||
            $cat_lnk->insert_multiple([qw/LinkID CategoryID/], map [$id, $_], @new_cats);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
# However, deleting from CatLinks does result in updated category counts
 | 
			
		||||
        if (@old_cats) {
 | 
			
		||||
            $cat_lnk->delete({ LinkID => $id, CategoryID => \@old_cats });
 | 
			
		||||
 | 
			
		||||
# If the link has been modified from isValidated = No to Yes then the delete()
 | 
			
		||||
# from CatLinks will end up incorrectly decrementing the category count.  If
 | 
			
		||||
# this is the case, then the count needs to increment to comphensate for this
 | 
			
		||||
# bug.  This isn't !$was_counted && $now_counted because CatLinks delete
 | 
			
		||||
# currently does not take ExpiryDate into consideration.
 | 
			
		||||
            push @{$link_adjustment{1}}, @old_cats if $old_validated eq 'No' and $new_validated eq 'Yes';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
# The status hasn't changed: increment the new categories
 | 
			
		||||
        if ($was_counted and $now_counted) {
 | 
			
		||||
            push @{$link_adjustment{1}}, @new_cats if @new_cats;
 | 
			
		||||
        }
 | 
			
		||||
# It wasn't viewable, but is now: increment all the current categories
 | 
			
		||||
        elsif (not $was_counted and $now_counted) {
 | 
			
		||||
            push @{$link_adjustment{1}}, keys %cat_ids;
 | 
			
		||||
        }
 | 
			
		||||
# Was viewable, but now isn't: decrement all the current categories (except new ones)
 | 
			
		||||
        elsif ($was_counted and not $now_counted) {
 | 
			
		||||
# Don't decrement counts on new categories, since the addition of the link
 | 
			
		||||
# never incremented the count in the first place
 | 
			
		||||
            my %not_new = %cat_ids;
 | 
			
		||||
            for (@new_cats) {
 | 
			
		||||
                delete $not_new{$_};
 | 
			
		||||
            }
 | 
			
		||||
            push @{$link_adjustment{-1}}, keys %not_new;
 | 
			
		||||
        }
 | 
			
		||||
# Otherwise, it wasn't visible and still isn't, or it was visible but now
 | 
			
		||||
# isn't.  In both cases, the new categories don't need to be incremented.
 | 
			
		||||
 | 
			
		||||
# Actually adjust the link counts:
 | 
			
		||||
        $Category->link_count(\%link_adjustment);
 | 
			
		||||
 | 
			
		||||
        while (my ($change, $ids) = each %link_adjustment) {
 | 
			
		||||
            $Category->update({ Direct_Links => \("Direct_Links" . ($change > 0 ? ' + ' : ' - ') . abs $change) }, { ID => $ids });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
# If this link is now validated this link, let's update category counters and new flags.
 | 
			
		||||
# It also needs to be updated if a link has been added to new categories.
 | 
			
		||||
        if ((not $was_counted and $now_counted) or @new_cats) {
 | 
			
		||||
            foreach my $cat (keys %cat_ids) {
 | 
			
		||||
                my @cats = ($cat, @{$Category->parents($cat)});
 | 
			
		||||
                my $cond = GT::SQL::Condition->new(ID => '=', \@cats);
 | 
			
		||||
                if ($set->{isNew} eq 'Yes') {
 | 
			
		||||
                    $Category->update({ Has_New_Links => 'Yes' }, $cond, { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 });
 | 
			
		||||
                }
 | 
			
		||||
                $cond->add('Newest_Link', '<', $set->{Add_Date});
 | 
			
		||||
                $Category->update({ Newest_Link => $set->{Add_Date} }, $cond, { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
# Update the category timestamps to let people know that the page has changed.
 | 
			
		||||
        $Category->update({ Timestmp => \"NOW()" }, { ID => [keys %cat_ids, @old_cats] }, { GT_SQL_SKIP_CHECK => 1, GT_SQL_SKIP_INDEX => 1 });
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub update {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Update a link.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $set, $where) = @_;
 | 
			
		||||
 | 
			
		||||
    my $ret = $self->SUPER::update($set, $where);
 | 
			
		||||
 | 
			
		||||
# Update the Category Timestmp of links which have certain columns updated
 | 
			
		||||
    for (split(/\s*,\s*/, $CFG->{links_cols_update_category})) {
 | 
			
		||||
        if (exists $set->{$_}) {
 | 
			
		||||
            my @cats = $DB->table('Links', 'CatLinks')->select('CategoryID', $where)->fetchall_list;
 | 
			
		||||
            $DB->table('Category')->update({ Timestmp => \'NOW()' }, { ID => \@cats });
 | 
			
		||||
            last;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub detailed_url {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Takes one or more link ID's, returns one or more parsed detailed URL/paths in
 | 
			
		||||
# the same order and position the links were passed in, NOT prefixed with
 | 
			
		||||
# build_detail_url/build_detail_path.  If the ID passed in is actually a
 | 
			
		||||
# hashref, it is assumed that this hash ref includes a full set of Links and
 | 
			
		||||
# Category values for the link.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, @ids) = @_;
 | 
			
		||||
 | 
			
		||||
    my (@links, @sel_links, $need_select);
 | 
			
		||||
    for (@ids) {
 | 
			
		||||
        if (ref) {
 | 
			
		||||
            push @links, $_;
 | 
			
		||||
            push @sel_links, undef;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            push @links, undef;
 | 
			
		||||
            push @sel_links, $_;
 | 
			
		||||
            $need_select++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($need_select) {
 | 
			
		||||
        my %links_cols = %{$self->cols};
 | 
			
		||||
        # Only select Category columns that don't conflict with Links columns.
 | 
			
		||||
        my @cat_cols = grep !$links_cols{$_}, keys %{$DB->table('Category')->cols};
 | 
			
		||||
 | 
			
		||||
        my $rel = $DB->table(qw/Links CatLinks Category/);
 | 
			
		||||
        my %links = map { $_->{ID} => $_ } @{$rel->select(
 | 
			
		||||
            'Links.*', @cat_cols, 'CategoryID', { LinkID => [grep $_, @sel_links] }
 | 
			
		||||
        )->fetchall_hashref};
 | 
			
		||||
 | 
			
		||||
        for my $i (0 .. $#sel_links) {
 | 
			
		||||
            $links[$i] = $links{$sel_links[$i]} if $sel_links[$i];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    require Links::Tools;
 | 
			
		||||
    my $format;
 | 
			
		||||
    $format = $CFG->{build_detail_format} unless $IN->param('d');
 | 
			
		||||
    $format ||= '%ID%';
 | 
			
		||||
    $format .= '_%ID%' unless $format =~ /%ID%/;
 | 
			
		||||
    my @ret = $PLG->dispatch('detailed_url', sub {
 | 
			
		||||
        my ($format, @links) = @_;
 | 
			
		||||
        my @ret;
 | 
			
		||||
        for (@links) {
 | 
			
		||||
            my $parsed;
 | 
			
		||||
            if ($_) {
 | 
			
		||||
# Make Full_Name act the same for both category and detailed urls.  Set
 | 
			
		||||
# build_format_compat = 2 if you want the < 3.3 behaviour of coalesced _'s for
 | 
			
		||||
# Full_Name.
 | 
			
		||||
                if ($CFG->{build_format_compat} == 1) {
 | 
			
		||||
                    (my $fn = $_->{Full_Name}) =~ y|a-zA-Z0-9_/-|_|c;
 | 
			
		||||
                    $format =~ s/%Full_Name%/$fn/g;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $parsed = Links::Tools::parse_format(
 | 
			
		||||
                    $format,
 | 
			
		||||
                    %$_,
 | 
			
		||||
                    clean => 1
 | 
			
		||||
                );
 | 
			
		||||
                $parsed =~ s{(^|[/\\])index$}{${1}_index};
 | 
			
		||||
                $parsed .= $CFG->{build_extension};
 | 
			
		||||
            }
 | 
			
		||||
            push @ret, $parsed;
 | 
			
		||||
        }
 | 
			
		||||
        return @ret;
 | 
			
		||||
    }, $format, @links);
 | 
			
		||||
    return wantarray ? @ret : $ret[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub category_detailed_url {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# A wrapper to detailed_url which will return url's which given a category id,
 | 
			
		||||
# will only return url's which take the category into consideration.  The only
 | 
			
		||||
# use for this is when a link is in multiple categories.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $cat_id, @ids) = @_;
 | 
			
		||||
 | 
			
		||||
    my %links_cols = %{$self->cols};
 | 
			
		||||
# Only select Category columns that don't conflict with Links columns.
 | 
			
		||||
    my @cat_cols = grep !$links_cols{$_}, keys %{$DB->table('Category')->cols};
 | 
			
		||||
 | 
			
		||||
    my @links;
 | 
			
		||||
    my $rel = $DB->table(qw/Links CatLinks Category/);
 | 
			
		||||
    for (@ids) {
 | 
			
		||||
        push @links, $rel->select('Links.*', @cat_cols, 'CategoryID', { LinkID => $_, CategoryID => $cat_id })->fetchrow_hashref;
 | 
			
		||||
    }
 | 
			
		||||
    my @ret = $self->detailed_url(@links);
 | 
			
		||||
    return wantarray ? @ret : $ret[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub clean_category_ids {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Takes an argument which could be a list of category names or ids
 | 
			
		||||
# and returns an array ref of ids.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $arg) = @_;
 | 
			
		||||
    my $cat = $DB->table('Category');
 | 
			
		||||
 | 
			
		||||
# Fix up Category Names => Id numbers and offer suggestions
 | 
			
		||||
# if name was not found.
 | 
			
		||||
    if (! ref $arg and $arg !~ /^\d*$/) {
 | 
			
		||||
        my @cat_names = split /\n\r?/, $arg;
 | 
			
		||||
        my @cat_ids   = ();
 | 
			
		||||
        foreach my $name (@cat_names) {
 | 
			
		||||
            $name =~ s/[\r\n]//g; # Textareas have a nasty habit of putting \r's on the results.
 | 
			
		||||
            my $id = ($name =~ /^\d+$/) ? $name : $cat->get_id_from_name($name);
 | 
			
		||||
            if ($id) {
 | 
			
		||||
                push(@cat_ids, $id);
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                my $names = $cat->suggestions($name);
 | 
			
		||||
                return $self->error(@$names
 | 
			
		||||
                    ? ('BADCATSUG', 'WARN', "<ul>" . join('', map "<li>$_</li>", @$names) . "</ul>")
 | 
			
		||||
                    : ('BADCATEGORY', 'WARN', $name)
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return \@cat_ids;
 | 
			
		||||
    }
 | 
			
		||||
# We assume that if ID numbers are passed in, that they will
 | 
			
		||||
# be correct. This will get checked anyways by GT::SQL::Table,
 | 
			
		||||
# so no point doing it twice.
 | 
			
		||||
    else {
 | 
			
		||||
        my @ids = ref $arg ? @$arg : ($arg);
 | 
			
		||||
        return \@ids;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub get_categories {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Takes a link id and returns a hash of category id => category name.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $id   = shift;
 | 
			
		||||
    my $db   = $DB->table('Category', 'CatLinks');
 | 
			
		||||
    my $sth  = $db->select( { 'CatLinks.LinkID' => $id }, [ 'Category.ID', 'Category.Full_Name' ] );
 | 
			
		||||
    my %res  = ();
 | 
			
		||||
    while (my ($id, $name) = $sth->fetchrow_array) {
 | 
			
		||||
        $res{$id} = $name;
 | 
			
		||||
    }
 | 
			
		||||
    return \%res;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub set_date_flags {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Takes a link hash ref and sets the date flags properly.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $p) = @_;
 | 
			
		||||
 | 
			
		||||
    Links::init_date();
 | 
			
		||||
    my $today = GT::Date::date_get();
 | 
			
		||||
    if (GT::Date::date_diff($today, $p->{Add_Date}) <= $CFG->{build_new_cutoff}) {
 | 
			
		||||
        $p->{isNew} = 'Yes';
 | 
			
		||||
        $p->{isChanged} = 'No';
 | 
			
		||||
    }
 | 
			
		||||
    elsif (GT::Date::date_diff($today, $p->{Mod_Date}) <= $CFG->{build_new_cutoff}) {
 | 
			
		||||
        $p->{isChanged} = 'Yes';
 | 
			
		||||
        $p->{isNew} = 'No';
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        $p->{isNew} = 'No';
 | 
			
		||||
        $p->{isChanged} = 'No';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Possible ExpiryDate values that have to be handled here:
 | 
			
		||||
#       -1 (unpaid link) - leave it as is, does not need to be converted
 | 
			
		||||
#       \d (unixtime) - leave it as is, does not need to be converted
 | 
			
		||||
#       >=\d (doesn't actually occur here, but in _query) - leave it as is, does not need to be converted
 | 
			
		||||
#       YYYY-MM-DD
 | 
			
		||||
#       YYYY/MM/DD
 | 
			
		||||
#       YYYY/MM/DD HH::MM::SS
 | 
			
		||||
# The purpose of this bit of code is to convert any human readable dates into
 | 
			
		||||
# unixtime and leave everything else as is.
 | 
			
		||||
    if ($p->{ExpiryDate} and $p->{ExpiryDate} !~ /^\s*-?\d+\s*$/) {
 | 
			
		||||
        my $converted = Links::date_to_time($p->{ExpiryDate});
 | 
			
		||||
        $p->{ExpiryDate} = $converted if defined $converted;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub add_reviews {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Adds review information, but passes through the plugin system.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $link) = @_;
 | 
			
		||||
    $PLG->dispatch('add_reviews', sub { return $self->_plg_add_reviews(@_) }, $link);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_add_reviews {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Adds review information to an array ref of hash refs of links passed in
 | 
			
		||||
# in one query.
 | 
			
		||||
#
 | 
			
		||||
    my $self      = shift;
 | 
			
		||||
    my $links     = shift;
 | 
			
		||||
    if (ref $links eq 'HASH') {
 | 
			
		||||
        $links = [ $links ];
 | 
			
		||||
    }
 | 
			
		||||
    my $review_db = $DB->table('Reviews');
 | 
			
		||||
    my @ids       = map { $_->{ID} } @$links;
 | 
			
		||||
    return unless (@ids);
 | 
			
		||||
 | 
			
		||||
    my $sth = $review_db->select({ Review_Validated => 'Yes' }, { Review_LinkID => \@ids });
 | 
			
		||||
    my %reviews;
 | 
			
		||||
    my %review_count;
 | 
			
		||||
    while (my $review = $sth->fetchrow_hashref) {
 | 
			
		||||
        push @{$reviews{$review->{Review_LinkID}}}, $review;
 | 
			
		||||
        $review_count{$review->{Review_LinkID}}++;
 | 
			
		||||
    }
 | 
			
		||||
    for my $link (@$links) {
 | 
			
		||||
        $link->{Review_Count} = $review_count{$link->{ID}};
 | 
			
		||||
        $link->{Review_Loop}  = $reviews{$link->{ID}};
 | 
			
		||||
    }
 | 
			
		||||
    return $links;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
@@ -0,0 +1,93 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Links - enhanced directory management system
 | 
			
		||||
#
 | 
			
		||||
#   Website  : http://gossamer-threads.com/
 | 
			
		||||
#   Support  : http://gossamer-threads.com/scripts/support/
 | 
			
		||||
#   CVS Info : 087,071,086,086,085      
 | 
			
		||||
#   Revision : $Id: Reviews.pm,v 1.1 2007/11/16 07:15:00 brewt Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2007 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# Redistribution in part or in whole strictly prohibited. Please
 | 
			
		||||
# see LICENSE file for full details.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
 | 
			
		||||
package Links::Table::Reviews;
 | 
			
		||||
# ==================================================================
 | 
			
		||||
use strict;
 | 
			
		||||
use Links qw/:objects/;
 | 
			
		||||
use GT::SQL;
 | 
			
		||||
use GT::SQL::Table;
 | 
			
		||||
use vars qw/@ISA $ERROR_MESSAGE/;
 | 
			
		||||
 | 
			
		||||
@ISA = qw/GT::SQL::Table/;
 | 
			
		||||
$ERROR_MESSAGE = 'GT::SQL';
 | 
			
		||||
 | 
			
		||||
sub add {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Add a review.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $rec = (ref $_[0] eq 'HASH') ? shift : { @_ };
 | 
			
		||||
 | 
			
		||||
    my $id = $self->SUPER::add($rec) or return;
 | 
			
		||||
 | 
			
		||||
# Update the link/category timestamp if the review is validated.
 | 
			
		||||
    _update_timestamp($rec->{Review_LinkID}) if $rec->{Review_Validated} eq 'Yes';
 | 
			
		||||
 | 
			
		||||
    $id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub modify {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Modify a review.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $set  = shift            or return $self->fatal(BADARGS => 'Usage: $reviews->modify({ col => value ... }).');
 | 
			
		||||
    my $id   = $set->{ReviewID} or return $self->fatal(BADARGS => 'No primary key passed to modify!');
 | 
			
		||||
 | 
			
		||||
    my ($old, $link_id) = $self->select('Review_Validated', 'Review_LinkID', { ReviewID => $id })->fetchrow;
 | 
			
		||||
 | 
			
		||||
    my $ret = $self->SUPER::modify($set) or return;
 | 
			
		||||
 | 
			
		||||
# Only update the timestamp if it was unvalidated and still is - this is the
 | 
			
		||||
# only case where the pages shouldn't be rebuilt.
 | 
			
		||||
    my $new = $set->{Review_Validated} || $old;
 | 
			
		||||
    _update_timestamp($link_id) unless $old eq 'No' and $new eq 'No';
 | 
			
		||||
 | 
			
		||||
    $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub delete {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Delete one or more reviews.
 | 
			
		||||
#
 | 
			
		||||
    my ($self, $cond) = @_;
 | 
			
		||||
    ref $cond or return $self->fatal(BADARGS => '$reviews->delete(condition)');
 | 
			
		||||
 | 
			
		||||
# Get the link ids of the reviews that are about to be deleted and are
 | 
			
		||||
# validated (as only those pages need to be rebuilt).
 | 
			
		||||
    my @link_ids = $self->select('Review_LinkID', $cond, { Review_Validated => 'Yes' })->fetchall_list;
 | 
			
		||||
 | 
			
		||||
    my $ret = $self->SUPER::delete($cond) or return;
 | 
			
		||||
 | 
			
		||||
    _update_timestamp(\@link_ids) if @link_ids;
 | 
			
		||||
 | 
			
		||||
    $ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _update_timestamp {
 | 
			
		||||
# -----------------------------------------------------------------------------
 | 
			
		||||
# Given a link ID (or an array ref if you want to update more than one link),
 | 
			
		||||
# update the Timestmp columns of the link as well as all the categories that
 | 
			
		||||
# the link is in.  This ensures that these pages will be rebuilt on "Build
 | 
			
		||||
# Changed".
 | 
			
		||||
#
 | 
			
		||||
    my $link_id = shift;
 | 
			
		||||
    return unless $link_id;
 | 
			
		||||
 | 
			
		||||
    my @cats = $DB->table('Links', 'CatLinks')->select('CategoryID', { LinkID => $link_id })->fetchall_list;
 | 
			
		||||
    $DB->table('Category')->update({ Timestmp => \'NOW()' }, { ID => \@cats }) if @cats;
 | 
			
		||||
    $DB->table('Links')->update({ Timestmp => \'NOW()' }, { ID => $link_id });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
							
								
								
									
										162
									
								
								site/slowtwitch.com/cgi-bin/articles/admin/Links/Table/Users.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								site/slowtwitch.com/cgi-bin/articles/admin/Links/Table/Users.pm
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
			
		||||
# ==================================================================
 | 
			
		||||
# Gossamer Links - enhanced directory management system
 | 
			
		||||
#
 | 
			
		||||
#   Website  : http://gossamer-threads.com/
 | 
			
		||||
#   Support  : http://gossamer-threads.com/scripts/support/
 | 
			
		||||
#   CVS Info : 087,071,086,086,085      
 | 
			
		||||
#   Revision : $Id: Users.pm,v 1.5 2005/05/12 20:51:24 brewt Exp $
 | 
			
		||||
#
 | 
			
		||||
# Copyright (c) 2001 Gossamer Threads Inc.  All Rights Reserved.
 | 
			
		||||
# Redistribution in part or in whole strictly prohibited. Please
 | 
			
		||||
# see LICENSE file for full details.
 | 
			
		||||
# ==================================================================
 | 
			
		||||
 | 
			
		||||
package Links::Table::Users;
 | 
			
		||||
# ==================================================================
 | 
			
		||||
use strict;
 | 
			
		||||
use GT::SQL;
 | 
			
		||||
use GT::SQL::Table;
 | 
			
		||||
use Links qw/$CFG $PLG/;
 | 
			
		||||
use vars qw/@ISA $ERRORS $ERROR_MESSAGE $AUTH/;
 | 
			
		||||
 | 
			
		||||
@ISA = qw/GT::SQL::Table/;
 | 
			
		||||
$ERROR_MESSAGE = 'GT::SQL';
 | 
			
		||||
 | 
			
		||||
$ERRORS = {
 | 
			
		||||
    AUTHERROR     => "Authentication Error: %s",
 | 
			
		||||
    INVALIDFORMAT => "Invalid format for username: %s"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
sub init {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Load the authentication module.
 | 
			
		||||
#
 | 
			
		||||
    require Links::Authenticate;
 | 
			
		||||
    Links::Authenticate::auth('init', {});
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
sub add {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
    my ($self, @args) = @_;
 | 
			
		||||
    return $PLG->dispatch('add_user', sub { return $self->_plg_add(@_); }, @args );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_add {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
    init();
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $p = ref $_[0] eq 'HASH' ? shift : {@_};
 | 
			
		||||
 | 
			
		||||
    if (! Links::Authenticate::auth('valid_format', { Username => $p->{Username} })) {
 | 
			
		||||
        $ERRORS->{INVALIDFORMAT} = Links::language('USER_INVALIDUSERNAME');
 | 
			
		||||
        return $self->error('INVALIDFORMAT', 'WARN', $p->{Username});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    my $h = Links::Authenticate::auth('add_user', { Username => $p->{Username}, Password => $p->{Password} });
 | 
			
		||||
    unless ($h) {
 | 
			
		||||
        $ERRORS->{AUTHERROR} = Links::language('USER_AUTHERROR');
 | 
			
		||||
        return $self->error('AUTHERROR', 'WARN', $Links::Authenticate::error);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $p->{Username} = $h->{Username};
 | 
			
		||||
    $p->{Password} = $h->{Password};
 | 
			
		||||
 | 
			
		||||
    return $self->SUPER::add($p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub delete {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
    my ($self, @args) = @_;
 | 
			
		||||
    return $PLG->dispatch('delete_user', sub { return $self->_plg_delete(@_); }, @args );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_delete {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
    init();
 | 
			
		||||
    my ($self, $cond) = @_;
 | 
			
		||||
    if (! ref $cond) {
 | 
			
		||||
        $cond = { Username => $cond };
 | 
			
		||||
    }
 | 
			
		||||
    my $count   = 0;
 | 
			
		||||
    my $link_db = $Links::DB->table('Links');
 | 
			
		||||
    my $sth     = $self->select('Username', $cond);
 | 
			
		||||
    while (my ($user) = $sth->fetchrow_array) {
 | 
			
		||||
        my @links = $link_db->select('ID', { LinkOwner => $user })->fetchall_list;
 | 
			
		||||
        for my $link_id (@links) {
 | 
			
		||||
            $link_db->delete($link_id);
 | 
			
		||||
        }
 | 
			
		||||
        if (Links::Authenticate::auth('del_user', { Username => $user })) {
 | 
			
		||||
            my $ret = $self->SUPER::delete($user);
 | 
			
		||||
            $count++ if $ret;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return $count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub modify {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
    my ($self, @args) = @_;
 | 
			
		||||
    return $PLG->dispatch('modify_user', sub { return $self->_plg_modify(@_); }, @args );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub _plg_modify {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
    init();
 | 
			
		||||
    my $self  = shift;
 | 
			
		||||
    my $input = $self->common_param(@_) or return $self->error('BADARGS', 'FATAL', '$obj->insert(HASH or HASH_REF or CGI) only.');
 | 
			
		||||
    my $id    = $input->{Username} or return $self->error("BADARGS", "FATAL", "No primary key passed to modify!");
 | 
			
		||||
    my $sth   = $self->select('Username', 'Password', { Username => $id });
 | 
			
		||||
    my $rec   = $sth->fetchrow_hashref;
 | 
			
		||||
    if ($rec) {
 | 
			
		||||
        if ($input->{Password} ne $rec->{Password}) {
 | 
			
		||||
            Links::Authenticate::auth('change_pass', { Username => $rec->{Username}, Password => $rec->{Password}, New_Password => $input->{Password} })
 | 
			
		||||
                or return $self->error('AUTHERROR', 'WARN', $Links::Authenticate::error);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Connect to the database if we are not already connected
 | 
			
		||||
    $self->connect;
 | 
			
		||||
 | 
			
		||||
# Copy the data and remove anything that doesn't make sense here.
 | 
			
		||||
    my $c   = $self->{schema}->{cols};
 | 
			
		||||
    my $set = {};
 | 
			
		||||
    for (keys %$c) {
 | 
			
		||||
        $set->{$_} = $input->{$_} if exists $input->{$_};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Remove primary keys from update clause.
 | 
			
		||||
    my $where;
 | 
			
		||||
    if ($input->{orig_username}) {
 | 
			
		||||
        $where->{Username} = $input->{orig_username};
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        foreach my $key (@{$self->{schema}->{pk}}) {
 | 
			
		||||
            $where->{$key} = delete $set->{$key} if exists $set->{$key};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return $self->error("NOPKTOMOD", "WARN") unless keys %$where == @{$self->{schema}->{pk}};
 | 
			
		||||
 | 
			
		||||
# Remove timestamps - no sense updating.
 | 
			
		||||
    $self->_check_timestamp($where, $set) or return;
 | 
			
		||||
    foreach my $col (keys %$c) {
 | 
			
		||||
        delete $set->{$col} if $c->{$col}->{type} eq 'TIMESTAMP';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
# Execute the update
 | 
			
		||||
    $self->update($set, $where) or return;
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub random_pass {
 | 
			
		||||
# -------------------------------------------------------------------
 | 
			
		||||
# Returns a random password.
 | 
			
		||||
#
 | 
			
		||||
    my $self = shift;
 | 
			
		||||
    my $pass = '';
 | 
			
		||||
    for (1 .. 8) { $pass .= chr(65 + int rand 57); }
 | 
			
		||||
    return $pass;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
1;
 | 
			
		||||
		Reference in New Issue
	
	Block a user