# Copyright (C) 2006 Charles Mauch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Grab MLDBM at http://search.cpan.org/dist/MLDBM/lib/MLDBM.pm # ie: http://search.cpan.org/CPAN/authors/id/C/CH/CHAMAS/MLDBM-2.01.tar.gz use strict; use v5.10; use Fcntl; use MLDBM qw( DB_File Storable ); AddModuleDescription('backlinkage.pl', 'Inline Backlinks'); our ($q, %Action, %Page, @MyAdminCode, $DataDir, $LinkPattern); my $debug=1; # Set Text Output Verbosity when compiling my $backfile = $DataDir . '/backlinks.db'; # Where data lives # Stuff buildback action into admin menu. push(@MyAdminCode, \&BacklinksMenu); sub BacklinksMenu { my ($id, $menuref, $restref) = @_; push(@$menuref, ScriptLink('action=buildback', T('Rebuild BackLink database')) ); } # Build Database, called my oddmuse uri action $Action{buildback} = \&BuildBacklinkDatabase; sub BuildBacklinkDatabase { print GetHttpHeader('text/plain'); Unlink($backfile); # Remove old database tie my %backhash, 'MLDBM', encode_utf8($backfile) or die "Cannot open file $backfile $!\n"; log1("Starting Database Store Process ... please wait\n\n"); foreach my $name (AllPagesList()) { log3("Opening $name ... \n"); OpenPage($name); my @backlinks = BacklinkProcess($name,$Page{text}); my $hash = $backhash{$name}; # Declare Hash Ref my $backlinkcount = 0; # Used to create link key foreach my $link (@backlinks) { $backlinkcount++; $hash->{'link' . $backlinkcount} = $link; } log2("$backlinkcount Links found in $name\n") if $backlinkcount; $backhash{$name} = $hash; # Store Hash data in HoH } if ($debug >= 3) { log4("Printing dump of USABLE Data we stored, sorted and neat\n"); for my $source (sort keys %backhash) { for my $role (sort keys %{ $backhash{$source} }) { log4("\n\$HoH\{\'$source\'\}\{\'$role\'\} = \"$backhash{$source}{$role}\""); } } } untie %backhash; log1("Done. \n"); } # Used to filter though page text to find links, ensure there is only 1 link per destination # per page, and then return an array of backlinks. sub BacklinkProcess { my $name = $_[0]; my $text = $_[1]; my %seen = (); my @backlinks; my @wikilinks = ($text =~ m/$LinkPattern/g); foreach my $links (@wikilinks) { my ($class, $resolved, $title, $exists) = ResolveId($links); if ($exists) { push (@backlinks,$resolved) unless (($seen{$resolved}++) or ($resolved eq $name)); } } return @backlinks; } # Function used by user to display backlinks in proper html. sub GetBackLink { my (@backlinks, @unpopped, @alldone); my $id = $_[0]; our ($BacklinkBanned); $BacklinkBanned = "HomePage|ScratchPad" if !$BacklinkBanned; tie my %backhash, 'MLDBM', encode_utf8($backfile), O_CREAT|O_RDWR, oct(644) or die "Cannot open file $backfile $!\n"; # Search database for matches while ( my ($source, $hashes) = each %backhash ) { while ( my ($key, $value) = each %$hashes ) { if ($id =~ /$value/) { push (@backlinks, $source); } } } untie %backhash; # Render backlinks into html links foreach my $backlink (@backlinks) { my ($class, $resolved, $title, $exists) = ResolveId($backlink); if (($resolved ne $id) && ($resolved !~ /^($BacklinkBanned)$/)) { push(@unpopped, ScriptLink(UrlEncode($resolved), $resolved, $class . ' backlink', undef, Ts('Internal Page: %s', $resolved))); } } my $arraycount = @unpopped; return if !$arraycount; # Dont bother with the rest if empty results # Pop and Push data to make it look good (no trailing commas) my $temp = pop(@unpopped); foreach my $backlink (@unpopped) { push(@alldone, $backlink . ", "); } push(@alldone, $temp); # And push last entry back in print $q->div({-class=>'docmeta'}, $q->h2(T('Pages that link to this page')), @alldone); } # Debug functions, all expect a string as input, and print it if the debug level is high enough. # This allows for increasing levels of verbosity for runtime commenting. sub log1 { # Very little info (only outputs if error - great for scripts) return if (($debug < 1) or ($debug == 4)); my $msg = shift; print "$msg"; } sub log2 { # Info Messages return if (($debug < 2) or ($debug == 4)); my $msg = shift; print "$msg"; } sub log3 { # More Info for the curious return if (($debug < 3) or ($debug == 4)); my $msg = shift; print "$msg"; } sub log4 { # Dump all sorts of garbage (usally data structures) return if ($debug < 4); my $msg = shift; print "$msg"; }