backlinkage.pl 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. # Copyright (C) 2006 Charles Mauch <cmauch@gmail.com>
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. # Grab MLDBM at http://search.cpan.org/dist/MLDBM/lib/MLDBM.pm
  16. # ie: http://search.cpan.org/CPAN/authors/id/C/CH/CHAMAS/MLDBM-2.01.tar.gz
  17. use strict;
  18. use v5.10;
  19. use Fcntl;
  20. use MLDBM qw( DB_File Storable );
  21. AddModuleDescription('backlinkage.pl', 'Inline Backlinks');
  22. our ($q, %Action, %Page, @MyAdminCode, $DataDir, $LinkPattern);
  23. my $debug=1; # Set Text Output Verbosity when compiling
  24. my $backfile = $DataDir . '/backlinks.db'; # Where data lives
  25. # Stuff buildback action into admin menu.
  26. push(@MyAdminCode, \&BacklinksMenu);
  27. sub BacklinksMenu {
  28. my ($id, $menuref, $restref) = @_;
  29. push(@$menuref,
  30. ScriptLink('action=buildback', T('Rebuild BackLink database'))
  31. );
  32. }
  33. # Build Database, called my oddmuse uri action
  34. $Action{buildback} = \&BuildBacklinkDatabase;
  35. sub BuildBacklinkDatabase {
  36. print GetHttpHeader('text/plain');
  37. Unlink($backfile); # Remove old database
  38. tie my %backhash, 'MLDBM', encode_utf8($backfile) or die "Cannot open file $backfile $!\n";
  39. log1("Starting Database Store Process ... please wait\n\n");
  40. foreach my $name (AllPagesList()) {
  41. log3("Opening $name ... \n");
  42. OpenPage($name);
  43. my @backlinks = BacklinkProcess($name,$Page{text});
  44. my $hash = $backhash{$name}; # Declare Hash Ref
  45. my $backlinkcount = 0; # Used to create link key
  46. foreach my $link (@backlinks) {
  47. $backlinkcount++;
  48. $hash->{'link' . $backlinkcount} = $link;
  49. }
  50. log2("$backlinkcount Links found in $name\n") if $backlinkcount;
  51. $backhash{$name} = $hash; # Store Hash data in HoH
  52. }
  53. if ($debug >= 3) {
  54. log4("Printing dump of USABLE Data we stored, sorted and neat\n");
  55. for my $source (sort keys %backhash) {
  56. for my $role (sort keys %{ $backhash{$source} }) {
  57. log4("\n\$HoH\{\'$source\'\}\{\'$role\'\} = \"$backhash{$source}{$role}\"");
  58. }
  59. }
  60. }
  61. untie %backhash;
  62. log1("Done. \n");
  63. }
  64. # Used to filter though page text to find links, ensure there is only 1 link per destination
  65. # per page, and then return an array of backlinks.
  66. sub BacklinkProcess {
  67. my $name = $_[0];
  68. my $text = $_[1];
  69. my %seen = ();
  70. my @backlinks;
  71. my @wikilinks = ($text =~ m/$LinkPattern/g);
  72. foreach my $links (@wikilinks) {
  73. my ($class, $resolved, $title, $exists) = ResolveId($links);
  74. if ($exists) {
  75. push (@backlinks,$resolved) unless (($seen{$resolved}++) or ($resolved eq $name));
  76. }
  77. }
  78. return @backlinks;
  79. }
  80. # Function used by user to display backlinks in proper html.
  81. sub GetBackLink {
  82. my (@backlinks, @unpopped, @alldone);
  83. my $id = $_[0];
  84. our ($BacklinkBanned);
  85. $BacklinkBanned = "HomePage|ScratchPad" if !$BacklinkBanned;
  86. tie my %backhash, 'MLDBM', encode_utf8($backfile), O_CREAT|O_RDWR, oct(644) or die "Cannot open file $backfile $!\n";
  87. # Search database for matches
  88. while ( my ($source, $hashes) = each %backhash ) {
  89. while ( my ($key, $value) = each %$hashes ) {
  90. if ($id =~ /$value/) {
  91. push (@backlinks, $source);
  92. }
  93. }
  94. }
  95. untie %backhash;
  96. # Render backlinks into html links
  97. foreach my $backlink (@backlinks) {
  98. my ($class, $resolved, $title, $exists) = ResolveId($backlink);
  99. if (($resolved ne $id) && ($resolved !~ /^($BacklinkBanned)$/)) {
  100. push(@unpopped, ScriptLink(UrlEncode($resolved), $resolved, $class . ' backlink', undef, Ts('Internal Page: %s', $resolved)));
  101. }
  102. }
  103. my $arraycount = @unpopped;
  104. return if !$arraycount; # Dont bother with the rest if empty results
  105. # Pop and Push data to make it look good (no trailing commas)
  106. my $temp = pop(@unpopped);
  107. foreach my $backlink (@unpopped) {
  108. push(@alldone, $backlink . ", ");
  109. }
  110. push(@alldone, $temp); # And push last entry back in
  111. print $q->div({-class=>'docmeta'}, $q->h2(T('Pages that link to this page')), @alldone);
  112. }
  113. # Debug functions, all expect a string as input, and print it if the debug level is high enough.
  114. # This allows for increasing levels of verbosity for runtime commenting.
  115. sub log1 { # Very little info (only outputs if error - great for scripts)
  116. return if (($debug < 1) or ($debug == 4));
  117. my $msg = shift;
  118. print "$msg";
  119. }
  120. sub log2 { # Info Messages
  121. return if (($debug < 2) or ($debug == 4));
  122. my $msg = shift;
  123. print "$msg";
  124. }
  125. sub log3 { # More Info for the curious
  126. return if (($debug < 3) or ($debug == 4));
  127. my $msg = shift;
  128. print "$msg";
  129. }
  130. sub log4 { # Dump all sorts of garbage (usally data structures)
  131. return if ($debug < 4);
  132. my $msg = shift;
  133. print "$msg";
  134. }