=====Advanced Referrers Handler===== >>==See also:== ~-Documentation: ""AdvancedReferrersHandlerInfo"". ~-If you're looking for how to adapt the styling of the user interface to match your own skin, see ""8. css/refmenu_col.css"" at the end of the page. >>This is the development page for an advanced referrers handler.::c:: Referrer lists generated by WikkaWiki on high-traffic servers are likely to become unmanageable, due to their ever growing size. Of course you can limit the volume of the referrers by changing the ##referrers_purge_time## in the ConfigurationOptions, so referrers older than n days are purged from the database. To allow a better management of the referrer list without purging the DB, I've modified the referrer handlers to allow //searching// and //filtering//. --DarTar After DarTar's first version published on this page, we discussed some ideas, and then started completely revising all handlers dealing with referrers and the blacklist in close cooperation (working together on code on IRC is fun!). The original ##referrers_sites## handler is now completely integrated with the ##referrers## handler, and also the ##review_blacklist## and ##delete_referrer## handlers have been modified to integrate seamlessly. The details are below, and the new version will soon be installed as a beta feature on this site, too. --JavaWoman ==== Preview ==== Here's an example of what the new interface looks like: ""

External pages linking to HomePage (last 7 days)

Note to spammers: This page is not indexed by search engines, so don't waste your time.


Total: 1 referrers linking to HomePage

Filter view:

Result: 1 referrers linking to HomePage

HitsReferrers
1http://javawoman.com/

"" ==== Features ==== Current version: **0.8** ~-search referrers/domains by string ~-filter referrers/domains by number of hits ~-filter referrers/domains by time interval ~-seamless integration of the handlers ~-valid XHTML (some of the old code wasn't) ~-accessible form and results table ~-internationalization-ready Todo: ~-see docblocks in the code ==== The code ==== ===1. ##wikka.php##=== The method ##""LoadReferrers()""## (from line **754**) is obsolete now. You can comment it out, or remove it, or even leave it in place, but it isn't used any more. All queries are completely dynamically generated depending on the "view" requested and the selection criteria given. ===2. ##handlers/page/referrers_sites.php##=== This handler file is now obsolete as well; its functionality is completely integrated with the new **referrers ** handler (below). It's best to remove or rename this file since it will not work together with the new handlers. ===3. ##handlers/page/referrers.php##=== This has undergone a complete overhaul by both DarTar and JavaWoman. See the docblock and various comments in the code for details. Since it's still beta code, there is some debug code present as well - that will disappear by the time we get to version 1.0 (see the @todo list in the docblock). %%(php;1) config[] ! * @uses GetPageTag() * @uses IsAdmin() * @uses GetUser() * @uses LoadAll() * @uses LoadSingle() * @uses Href() * @uses makeList() * @uses FormOpen() * @uses FormClose() * @uses GetMethod() * @uses makeId() * @uses htmlspecialchars_ent() */ // Utilities /** * Build an array of numbers consisting of 'ranges' with increasing step size in each 'range'. * * A list of numbers like this is useful for instance for a dropdown to choose * a period expressed in number of days: a difference between 2 and 5 days may * be significant while that between 92 and 95 may not be. * * @author {@link http://wikka.jsnx.com/JavaWoman JavaWoman} * @copyright Copyright © 2005, Marjolein Katsma * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @version 1.0 * * @param mixed $limits required: single integer or array of integers; * defines the upper limits of the ranges as well as the next step size * @param int $max required: upper limit for the whole list * (will be included if smaller than the largest limit) * @param int $firstinc optional: increment for the first range; default 1 * @return array resulting list of numbers */ function optionRanges($limits, $max, $firstinc = 1) { // initializations if (is_int($limits)) $limits = array($limits); if ($firstinc < 1) $firstinc = 1; $opts = array(); $inc = $firstinc; // first element is the first increment $opts[] = $inc; // each $limit is the upper limit of a 'range' foreach ($limits as $limit) { for ($i = $inc + $inc; $i <= $limit && $i < $max; $i += $inc) { $opts[] = $i; } // we quit at $max, even if there are more $limit elements if ($limit >= $max) { // add $max to the list; then break out of the loop $opts[] = $max; break; } // when $limit is reached, it becomes the new start and increment for the next 'range' $inc = $limit; } return $opts; } // constants define('DEBUG',FALSE); # @@@ set TRUE to generate debugging output define('SEARCH_LIKE','LIKE'); # search string operator define('SEARCH_UNLIKE','NOT LIKE'); # search string operator define('HITS_DEFAULT', '1'); # (was 0 for referrers, 1 for sites) define('HITS_MIN_OPTION', '>='); define('HITS_MAX_OPTION', '<='); define('HOURS_LIMIT',2); # days expressed as hours @@@ could be made configurable define('DAYS_MAX', $this->GetConfigValue('referrers_purge_time')); define('DAYS_DEFAULT', '7'); # default period to retrieve @@@ make configurable $days_limits = array(7,30,90,365); # ranges for days dropdown @@@ make configurable // ------------------------------------- // initialize parameters $q = NULL; # search string $qo = 1; # search string option $h = HITS_DEFAULT; # hits number $ho = 1; # hits option $days = DAYS_DEFAULT; # period selection $global = FALSE; # global (site) or this page only $sites = FALSE; # referrers or referring sites $refdel = NULL; # referrer records deleted $bladd = NULL; # blacklist records added // ------------------------------------- // initialize internal variables $string_option = SEARCH_LIKE; # LIKE or NOT LIKE $hits_option = HITS_MIN_OPTION; # MIN (>=) or MAX (<=) $tag = $this->GetPageTag(); $isAdmin = $this->IsAdmin(); $loggedin = ($isAdmin) ? TRUE : (bool)$this->GetUser(); $pre = $this->config['table_prefix']; $par = ''; $query = ''; $rows = 0; // ------------------------------------- // User-interface strings define('NAME_GLOBAL',$this->GetConfigValue('wakka_name')); define('TITLE_REFERRERS','External pages linking to %s'); define('TITLE_SITES','Domains linking to %s'); define('REPORT_BLACKLIST','Referrer records removed: %d; blacklist records added: %d'); define('TOTAL_REFERRERS','Total: %1$d referrers linking to %2$s'); define('TOTAL_SITES','Total: %1$d referrers linking to %2$s'); // current target # you can use NAME_GLOBAL instead of 'this site' if the site name is short enough # @@@ JW: choice between 'this site' and NAME_GLOBAL could be set via configuration (later) define('TARGET_GLOBAL','this site'); define('TARGET_PAGE',$tag); // menus don't use current target but *possible* targets define('MENU_REFERRERS','Referrers to %s'); define('MENU_SITES','Domains linking to %s'); define('MENU_REFERRERS_PAGE',sprintf(MENU_REFERRERS,TARGET_PAGE)); define('MENU_SITES_PAGE',sprintf(MENU_SITES,TARGET_PAGE)); define('MENU_REFERRERS_GLOBAL',sprintf(MENU_REFERRERS,TARGET_GLOBAL)); define('MENU_SITES_GLOBAL',sprintf(MENU_SITES,TARGET_GLOBAL)); define('MENU_BLACKLIST','Blacklisted sites'); define('FORM_LEGEND','Filter view:'); define('FORM_URL_OPT_REFERRERS','URL:'); define('FORM_URL_OPT_SITES','Domain:'); define('FORM_URL_OPT_TITLE','Select search option'); define('FORM_URL_OPT_1','containing'); define('FORM_URL_OPT_0','not containing'); define('FORM_URL_STRING_LABEL','string'); define('FORM_URL_STRING_TITLE','Enter a search string'); define('FORM_HITS_OPT_LABEL','Hits:'); define('FORM_HITS_OPT_TITLE','Select filter option'); define('FORM_HITS_OPT_1','at least'); define('FORM_HITS_OPT_0','no more than'); define('FORM_HITS_NUM_LABEL','hits'); define('FORM_HITS_NUM_TITLE','Enter number of hits'); define('FORM_DAYS_OPT_LABEL','Period:'); define('FORM_DAYS_OPT_TITLE','Select period in days'); define('FORM_DAYS_NUM_LABEL','days'); define('FORM_SUBMIT_URLS','Show referrers'); define('FORM_SUBMIT_SITES','Show referring domains'); define('LIST_PERIOD_HOURS',' (last %d hours)'); define('LIST_PERIOD_DAYS',' (last %d days)'); define('LIST_SUMMARY_REFERRERS','Filtered list of referrers, with hits%s, sorted by number of hits'); define('LIST_SUMMARY_SITES','Filtered list of referring sites, with hits%s, sorted by number of hits'); define('LIST_HEAD_HITS','Hits'); define('LIST_HEAD_ACTION','Action'); define('LIST_HEAD_LIST_REFERRERS','Referrers'); define('LIST_HEAD_LIST_SITES','Referring hosts'); define('LIST_REF_UNKNOWN','unknown'); # make sure the *exact* same string is used in the whitelist definition (delete_referrer.php) define('LIST_ACTION_DESC',' and links to blacklist spammers'); define('LIST_ACTION_BLACKLIST','Blacklist'); define('LIST_ACTION_BLACKLIST_TITLE','Blacklist this domain'); define('SPAM_NOTE','Note to spammers: This page is not indexed by search engines, so don\'t waste your time.'); define('LOGIN_NOTE','You need to login to see referring sites.'); // show result counts for target define('LIST_RESULT_COUNTER_REFERRERS','Filtered result: %1$d referrers linking to %2$s'); # @@@ does not take account of singular define('LIST_RESULT_COUNTER_SITES','Filtered result: %1$d domains linking to %2$s'); # @@@ does not take account of singular define('LIST_RESULT_NONE','Filtered result:'); // show 'no result' summary for target define('NONE_NOTE_REFERRERS','No referrers found linking to %s'); define('NONE_NOTE_SITES','No domains found linking to %s'); // ------------------------------------- // fetch and validate parameters // get query string and comparison method if (isset($_POST['q'])) { $tq = trim(strip_tags($_POST['q'])); if ('' != $tq) { $q = mysql_real_escape_string($tq); if (isset($_POST['qo'])) { $qo = ($_POST['qo'] == '1') ? 1 : 0; $string_option = ($qo == 1) ? SEARCH_LIKE : SEARCH_UNLIKE; } } } // get hits and min or max criteria if (isset($_POST['h'])) { $h = (is_numeric($_POST['h'])) ? abs((int)$_POST['h']) : HITS_DEFAULT; # cast to positive integer if numeric } if (isset($_POST['ho'])) { $ho = ($_POST['ho'] == '1') ? 1 : 0; $hits_option = ($ho == 1) ? HITS_MIN_OPTION : HITS_MAX_OPTION; } // get period, not longer than purge time if (isset($_POST['days'])) { $days = (is_numeric($_POST['days'])) ? min(abs((int)$_POST['days']),DAYS_MAX) : DAYS_DEFAULT; } // get search target: page or site (global) if (isset($_POST['global'])) { $global = (bool)$_POST['global']; } elseif (isset($_GET['global'])) { $global = (bool)$_GET['global']; } $iglobal = (int)$global; // get precision: URLS (referrers) or referring sites (domains) if (isset($_POST['sites'])) { $sites = (bool)$_POST['sites']; } elseif (isset($_GET['sites'])) { $sites = (bool)$_GET['sites']; } $isites = (int)$sites; //get reported values (no validation needed, just cast to integer) if (isset($_GET['refdel'])) { $refdel = (int)$_GET['refdel']; $bladd = (isset($_GET['bladd'])) ? $bladd = (int)$_GET['bladd'] : 0; } // derive parameters for 'current' links if (1 == $global) { if ('' != $par) $par .= '&'; $par .= 'global=1'; } if (1 == $sites) { if ('' != $par) $par .= '&'; $par .= 'sites=1'; } // ------------------------------------- // build query from chunks depending on criteria chosen if ($loggedin) { $query = 'SELECT referrer'; if ($sites) { // add 'host' = domain extracted from referrring URL using this algorithm: // find first char after http:// : LOCATE('//',referrer)+2 // find first / after this: LOCATE('/',referrer,(LOCATE('//',referrer)+2)-1 // calculate length: (LOCATE('/',referrer,(LOCATE('//',referrer)+2)-1) - (LOCATE('//',referrer)+2) // get host (standard): SUBSTRING(referrer FROM (LOCATE('//',referrer)+2) FOR ((LOCATE('/',referrer,(LOCATE('//',referrer)+2)-1) - (LOCATE('//',referrer)+2))) // *or* // get host (MySQL-specific): SUBSTRING(SUBSTRING_INDEX(referrer,'/',3) FROM (LOCATE('//',referrer)+1)) $protocol_host = 'SUBSTRING_INDEX(referrer,"/",3)'; # protocol and host: everything before first single / $start_host = 'LOCATE("//",referrer)+2'; # start position of host: after // $query .= ', SUBSTRING('.$protocol_host.' FROM ('.$start_host.')) AS host'; // NOTE: COUNT() cannot use a derived column name but it *can* take an expression $query .= ', COUNT(SUBSTRING('.$protocol_host.' FROM ('.$start_host.'))) AS num'; $query .= ' FROM '.$pre.'referrers'; if (!$global) { $query .= " WHERE page_tag = '".mysql_real_escape_string($tag)."'"; } #if ($days != $max_days) if ($days != DAYS_MAX) { $query .= (!strpos($query,'WHERE')) ? ' WHERE' : ' AND'; $query .= ' TO_DAYS(NOW()) - TO_DAYS(time) <= '.$days; # filter by period } $query .= ' GROUP BY host '; if (isset($q)) { $query .= ' HAVING host '.$string_option." '%".$q."%'"; # filter by string (derived column so we use HAVING) } if ($hits_option != HITS_MIN_OPTION || $h != 1) { $query .= (!strpos($query,'HAVING')) ? ' HAVING' : ' AND'; $query .= ' num '.$hits_option.' '.$h; # filter by hits number (derived column so we use HAVING) } } else { $query = 'SELECT referrer'; $query .= ', COUNT(referrer) AS num'; $query .= ' FROM '.$pre.'referrers'; if (!$global) { $query .= " WHERE page_tag = '".mysql_real_escape_string($tag)."'"; } if (isset($q)) { $query .= (!strpos($query,'WHERE')) ? ' WHERE' : ' AND'; $query .= ' referrer '.$string_option." '%".$q."%'"; # filter by string } #if ($days != $max_days) if ($days != DAYS_MAX) { $query .= (!strpos($query,'WHERE')) ? ' WHERE' : ' AND'; $query .= ' TO_DAYS(NOW()) - TO_DAYS(time) <= '.$days; # filter by period } $query .= ' GROUP BY referrer '; if ($hits_option != HITS_MIN_OPTION || $h != 1) { $query .= ' HAVING num '.$hits_option.' '.$h; # filter by hits number (derived column so we use HAVING) } } $query .= ' ORDER BY num DESC, referrer ASC'; # set order // get total number of referrers (NOT records!) $query_refcount = 'SELECT COUNT(DISTINCT(referrer)) AS total'; # @@@ referrer column should be indexed to make this really efficient $query_refcount .= ' FROM '.$pre.'referrers'; if (!$global) { $query_refcount .= " WHERE page_tag = '".mysql_real_escape_string($tag)."'"; } } // ------------------------------------- // execute query (if logged in) // @@@ NOTE: we don't use LoadReferrers any more since the query is now completely dynamically built if ($loggedin) { // execute query $referrers = $this->LoadAll($query); $totalrefs = $this->LoadSingle($query_refcount); } // ------------------------------------- // build UI elements // define current target $target = ($global) ? TARGET_GLOBAL : TARGET_PAGE; // title $title = ($sites) ? sprintf(TITLE_SITES,$target) : sprintf(TITLE_REFERRERS,$target); $title .= ($days <= HOURS_LIMIT) ? sprintf(LIST_PERIOD_HOURS,24*$days) : sprintf(LIST_PERIOD_DAYS,$days); if ($isAdmin) { if (isset($refdel)) $rptblacklisted = sprintf(REPORT_BLACKLIST,$refdel,$bladd); } if ($loggedin) { // results $tot = $totalrefs['total']; $total = ($sites) ? sprintf(TOTAL_SITES,$tot,$target) : sprintf(TOTAL_REFERRERS,$tot,$target); $creferrers = count($referrers); if ($creferrers > 0) { $result = ($sites) ? sprintf(LIST_RESULT_COUNTER_SITES,$creferrers,$target) : sprintf(LIST_RESULT_COUNTER_REFERRERS,$creferrers,$target); } else { $result = LIST_RESULT_NONE; } // menu elements: prevent wrapping within element (these *don't* use current target! $menu_referrers_page = str_replace(' ',' ',MENU_REFERRERS_PAGE); $menu_sites_page = str_replace(' ',' ',MENU_SITES_PAGE); $menu_referrers_global = str_replace(' ',' ',MENU_REFERRERS_GLOBAL); $menu_sites_global = str_replace(' ',' ',MENU_SITES_GLOBAL); $menu_blacklist = str_replace(' ',' ',MENU_BLACKLIST); // menu if ($global) { $m_referrers_page = ''.$menu_referrers_page.''; $m_sites_page =''.$menu_sites_page.''; $m_referrers_global = ($sites) ? ''.$menu_referrers_global.'' : $menu_referrers_global; $m_sites_global = ($sites) ? $menu_sites_global : ''.$menu_sites_global.''; } else { $m_referrers_page = ($sites) ? ''.$menu_referrers_page.'' : $menu_referrers_page; $m_sites_page = ($sites) ? $menu_sites_page : ''.$menu_sites_page.''; $m_referrers_global = ''.$menu_referrers_global.''; $m_sites_global = ''.$menu_sites_global.''; } $m_blacklist = ''.$menu_blacklist.''; // don't generate id since the menu is used multiple times $menu = $this->makeList(array($m_referrers_page,$m_sites_page,$m_referrers_global,$m_sites_global,$m_blacklist),FALSE,'menu'); // days dropdown content $daysopts = optionRanges($days_limits,DAYS_MAX); // form $form = $this->FormOpen('referrers','','POST'); # @@@ add parameter for id $form .= ''."\n"; $form .= '
'."\n"; $form .= ''.FORM_LEGEND.''."\n"; $form .= ' '."\n"; $form .= ' '."\n"; $form .= ' '."\n"; $form .= ''; $form .= '
'."\n"; $form .= ' '."\n"; $form .= ' '."\n"; $form .= ''."\n"; $form .= ' '; $form .= '
'."\n"; $form .= ' '."\n"; $form .= ' '."\n"; $form .= ' '."\n"; $form .= '
'."\n"; $form .= ''."\n"; $form .= $this->FormClose(); // referrers list with admin link for blacklisting if ($sites) { $summary = ($isAdmin) ? sprintf(LIST_SUMMARY_SITES,LIST_ACTION_DESC) : sprintf(LIST_SUMMARY_SITES,''); $refshead = LIST_HEAD_LIST_SITES; } else { $summary = ($isAdmin) ? sprintf(LIST_SUMMARY_REFERRERS,LIST_ACTION_DESC) : sprintf(LIST_SUMMARY_REFERRERS,''); $refshead = LIST_HEAD_LIST_REFERRERS; } if ($isAdmin) { $redir = ($global||$sites) ? $this->GetMethod().'&'.$par : $this->GetMethod(); # ensure we return to the same view $par = ($sites) ? 'spam_site' : 'spam_link'; $blacklisturl = $this->Href('delete_referrer','',$par.'=').'%s&redirect=%s'; $blacklink = ''.LIST_ACTION_BLACKLIST.''; } // ids - use constant for variable-content heading $idTitle = $this->makeId('hn','title'); $idTotal = $this->makeId('hn','total'); $idResult = $this->makeId('hn','result'); } // ------------------------------------- // show user interface (pre-template) echo '
'."\n"; echo '

'.$title.'

'."\n"; echo '

'.SPAM_NOTE.'

'."\n"; echo '

Doesn\'t look right with your skin? See AdvancedReferrersHandler.'."\n"; # debug if (DEBUG) { echo 'Query (ref): '.$query.'
'; echo 'Query (sites): '.$query_sites.'
'; echo ($global) ? 'Global: TRUE
' : 'Global: FALSE
'; echo ($sites) ? 'Sites: TRUE
' : 'Sites: FALSE
'; } # debug if ($loggedin) { if ($isAdmin && isset($refdel)) echo '

'.$rptblacklisted.'

'; echo '
'.$menu.'

'."\n"; echo '

'.$total.'

'."\n"; echo '
'.$form.'
'."\n"; # @@@ kluge until FormOpen() is adapted: id should actually be on form itself and div not necessary! if ($creferrers != 0) { echo '

'.$result.'

'."\n"; echo ''."\n"; echo ''; echo ''; if ($isAdmin) echo ''; echo ''."\n"; echo ''."\n"; echo ''."\n"; foreach ($referrers as $referrer) { $hits = $referrer['num']; if ($sites) { $ref = $this->htmlspecialchars_ent($referrer['host']); } else { $ref = $this->htmlspecialchars_ent($referrer['referrer']); } echo ''; echo ''; if ($isAdmin) echo ''; if ($sites) { echo ''; } else { echo ''; } echo ''."\n"; } echo ''."\n"; echo '
'.LIST_HEAD_HITS.''.LIST_HEAD_ACTION.''.$refshead.'
'.$hits.''.sprintf($blacklink,$ref,$redir).''.$ref.''.$ref.'
'."\n"; } else { echo '

'.$result.'

'."\n"; echo '

'.(($sites) ? sprintf(NONE_NOTE_SITES,$target) : sprintf(NONE_NOTE_REFERRERS,$target)).'

'."\n"; } echo '
'.$menu.'

'."\n"; } else { echo '

'.LOGIN_NOTE.'

'."\n"; } echo '
'."\n"; ?> %% ===4. ##handlers/page/review_blacklist.php##=== This is rewritten mainly to make it integrate seamlessly with the referrers handler. There was also a problem with the output which was not valid XHTML; it now follows the same pattern as the referrers handler and got the same treatment for preparation for internationalization as well. %%(php;1) GetPageTag(); $isAdmin = $this->IsAdmin(); $loggedin = ($isAdmin) ? TRUE : (bool)$this->GetUser(); $pre = $this->config['table_prefix']; $queryd = ''; $querys = ''; $rows = 0; // ------------------------------------- // User-interface strings define('TITLE','Blacklisted domains'); define('REPORT_REMOVED','Removed: %d records'); # @@@ does not take account of singular define('TOTAL_BL','Total: %d blacklisted domain'); // current target # you can use NAME_GLOBAL instead of 'this site' if the site name is short enough # @@@ JW: choice between 'this site' and NAME_GLOBAL could be set via configuration (later) define('TARGET_GLOBAL','this site'); define('TARGET_PAGE',$tag); // menus don't use current target but *possible* targets define('MENU_REFERRERS','Referrers to %s'); define('MENU_SITES','Domains linking to %s'); define('MENU_REFERRERS_PAGE',sprintf(MENU_REFERRERS,TARGET_PAGE)); define('MENU_SITES_PAGE',sprintf(MENU_SITES,TARGET_PAGE)); define('MENU_REFERRERS_GLOBAL',sprintf(MENU_REFERRERS,TARGET_GLOBAL)); define('MENU_SITES_GLOBAL',sprintf(MENU_SITES,TARGET_GLOBAL)); define('MENU_BLACKLIST','Blacklisted sites'); define('FORM_LEGEND','Filter view:'); define('FORM_URL_OPT_LABEL','Domain:'); define('FORM_URL_OPT_TITLE','Select search option'); define('FORM_URL_OPT_1','containing'); define('FORM_URL_OPT_0','not containing'); define('FORM_URL_STRING_LABEL','string'); define('FORM_URL_STRING_TITLE','Enter a search string'); define('FORM_SUBMIT_BLACKLIST','Show blacklisted domains'); define('LIST_SUMMARY_BL','Filtered list of blacklisted domains%s, sorted alphabetically'); define('LIST_HEAD_ACTION','Action'); define('LIST_HEAD_BL','Blacklisted domains'); define('LIST_ACTION_DESC',' and links to remove domains from the blacklist'); define('LIST_ACTION_BL','Remove'); define('LIST_ACTION_BL_TITLE','Remove this domain from the blacklist'); define('LOGIN_NOTE','You need to login to see blacklisted domains.'); define('LIST_RESULT_COUNTER_SITES','Filtered result: %d domains'); # @@@ does not take account of singular define('LIST_RESULT_NONE','Filtered result:'); define('NONE_NOTE','No blacklisted domains found'); // ------------------------------------- // fetch and validate parameters // get query string and comparison method if (isset($_POST['q'])) { $tq = trim(strip_tags($_POST['q'])); if ('' != $tq) { $q = mysql_real_escape_string($tq); if (isset($_POST['qo'])) { $qo = ($_POST['qo'] == '1') ? 1 : 0; $string_option = ($qo == 1) ? SEARCH_LIKE : SEARCH_UNLIKE; } } } // get host(s) to be removed if (isset($_GET['remove'])) { $remove = mysql_real_escape_string(strip_tags($_GET['remove'])); } // ------------------------------------- // build remove query if ($isAdmin) { $queryd = 'DELETE FROM '.$pre.'referrer_blacklist' . ' WHERE spammer = "'.$remove.'"'; } // build filter query if ($loggedin) { $querys = 'SELECT * FROM '.$pre.'referrer_blacklist'; if (isset($q)) { $querys .= ' WHERE spammer '.$string_option." '%".$q."%'"; # filter by string } $querys .= ' ORDER BY spammer ASC'; # set order // get total number of domains in blacklist $query_refcount = 'SELECT COUNT(spammer) AS total'; $query_refcount .= ' FROM '.$pre.'referrer_blacklist'; } // ------------------------------------- // execute query (if logged in) // do a 'remove' query first, then follow with the select query: // the list should then reflect the situation after removal of a domain if ($loggedin) { if ($isAdmin && isset($remove)) { $rc = $this->Query($queryd); # TRUE on success $numbldeleted = mysql_affected_rows(); # @@@ report back as GET parameter (in $removeurl/$removelink!) } $blacklist = $this->LoadAll($querys); $totalrefs = $this->LoadSingle($query_refcount); } // ------------------------------------- // build UI elements // title $title = TITLE; if ($isAdmin) { if (isset($numbldeleted)) $rptremoved = sprintf(REPORT_REMOVED,$numbldeleted); $removeurl = $this->Href('review_blacklist','','remove=').'%s'; $removelink = ''.LIST_ACTION_BL.''; } if ($loggedin) { // results $tot = $totalrefs['total']; $total = sprintf(TOTAL_BL,$tot); $cdomains = count($blacklist); if ($cdomains > 0) { $result = sprintf(LIST_RESULT_COUNTER_SITES,$cdomains); } else { $result = LIST_RESULT_NONE; } // menu elements: prevent wrapping within element (these *don't* use current target! $menu_referrers_page = str_replace(' ',' ',MENU_REFERRERS_PAGE); $menu_sites_page = str_replace(' ',' ',MENU_SITES_PAGE); $menu_referrers_global = str_replace(' ',' ',MENU_REFERRERS_GLOBAL); $menu_sites_global = str_replace(' ',' ',MENU_SITES_GLOBAL); $menu_blacklist = str_replace(' ',' ',MENU_BLACKLIST); // menu $m_referrers_page = ''.$menu_referrers_page.''; $m_sites_page =''.$menu_sites_page.''; $m_referrers_global = ''.$menu_referrers_global.''; $m_sites_global = ''.$menu_sites_global.''; $m_blacklist = $menu_blacklist; // don't generate id since the menu is used multiple times $menu = $this->makeList(array($m_referrers_page,$m_sites_page,$m_referrers_global,$m_sites_global,$m_blacklist),FALSE,'menu'); // form $form = $this->FormOpen('review_blacklist','','POST'); # @@@ add parameter for id $form .= '
'."\n"; $form .= ''.FORM_LEGEND.''."\n"; $form .= ' '."\n"; $form .= ' '."\n"; $form .= ' '."\n"; $form .= ''; $form .= '
'."\n"; $form .= ''."\n"; $form .= $this->FormClose(); // blacklist with admin link for removing $summary = ($isAdmin) ? sprintf(LIST_SUMMARY_BL,LIST_ACTION_DESC) : sprintf(LIST_SUMMARY_BL,''); $refshead = LIST_HEAD_BL; // ids - use constant for variable-content heading $idTitle = $this->makeId('hn','title'); $idTotal = $this->makeId('hn','total'); $idResult = $this->makeId('hn','result'); } // ------------------------------------- // show user interface (pre-template) echo '
'."\n"; echo '

'.$title.'

'."\n"; # debug if (DEBUG) { echo 'Query remove: '.$queryd.'
'; echo 'Query blacklist: '.$querys.'
'; echo 'remove: '.$remove.'
'; echo 'removed: '.$numbldeleted.'
'; } # debug if ($loggedin) { if ($isAdmin && isset($numbldeleted)) echo '

'.$rptremoved.'

'; echo '
'.$menu.'

'."\n"; echo '

'.$total.'

'."\n"; echo '
'.$form.'
'."\n"; # @@@ kluge until FormOpen() is adapted: id should actually be on form itself and div not necessary! if ($cdomains != 0) { echo '

'.$result.'

'."\n"; echo ''."\n"; echo ''; if ($isAdmin) echo ''; echo ''."\n"; echo ''."\n"; echo ''."\n"; foreach ($blacklist as $spammer) { $ref = $this->htmlspecialchars_ent($spammer['spammer']); echo ''; if ($isAdmin) echo ''; echo ''; echo ''."\n"; } echo ''."\n"; echo '
'.LIST_HEAD_ACTION.''.$refshead.'
'.sprintf($removelink,$ref).''.$ref.'
'."\n"; } else { echo '

'.$result.'

'."\n"; echo '

'.NONE_NOTE.'

'."\n"; } echo '
'.$menu.'

'."\n"; } else { echo '

'.LOGIN_NOTE.'

'."\n"; } echo '
'."\n"; ?> %% ===5. ##handlers/page/delete_referrer.php##=== Two problems here were solved: the code was not actually secure (anyone knowing how to build a URL could blacklist a domain), and when the action was completed you would get back to a single view only - often not the view you were coming form, causing an extra click to get back. Also there is now a list of domains that are "whitelisted" so they will never be blacklisted. Basically this is for local machines, but you could add your own domains here as well. We'll make this list configurable. For further details see the code (there's stil quite a lot of debug code which will disappear): %%(php;1) IsAdmin(); $home = $this->Href('',$this->config['root_page']); $pre = $this->config['table_prefix']; $par = ''; // ------------------------------------- // User-interface strings define('MSG_NOT_ALLOWED','Blacklisting not allowed'); define('MSG_PAR_ERROR','Cannot blacklist: missing or incorrect parameter'); // ------------------------------------- // check permission and immediately redirect to home page if check fails if (!$isAdmin) $this->Redirect($home,MSG_NOT_ALLOWED); // ------------------------------------- // fetch and validate parameters // ensure we have a spam_link OR spam_site parameter if(isset($_GET['spam_link'])) { $spam_link = strip_tags($_GET['spam_link']); # blacklisting from referrers list } elseif (isset($_GET['spam_site'])) { $spam_site = strip_tags($_GET['spam_site']); # blacklisting from sites list } // ensure we have a redirect parameter 'referrers' (we won't allow any other value) if (isset($_GET['redirect'])) { $redirect = preg_match('/^referrers$/',$_GET['redirect']) ? strip_tags($_GET['redirect']) : NULL; } if (isset($_GET['global'])) { $global = abs((int)$_GET['global']); # make sure we have a positive integer } if (isset($_GET['sites'])) { $sites = abs((int)$_GET['sites']); # make sure we have a positive integer } # debug if (DEBUG) { echo 'spamlink: '.$spam_link.'
'; echo 'spamsite: '.$spam_site.'
'; } # end debug // check required parameters and immediately redirect to home page if check fails if (!(isset($spam_link) || (isset($spam_site))) || !isset($redirect)) $this->Redirect($home,MSG_PAR_ERROR); // ------------------------------------- // derive internal variables // With $spam_link we get a full URL and need to parse out the host name; // with $spam_site we get a domain: no need to parse anything; // for both: check against whitelist before acting on it if (isset($spam_site)) { // referring domain already is host name (no need to parse) $domain = $spam_site; } else { $parsed_url = parse_url($spam_link); if (FALSE !== $parsed_url) { // derive host name from referring URL if (isset($parsed_url['host'])) { $domain = $parsed_url['host']; } } } // exclude 'unknown', 'localhost' and others in the "whitelist" if (!in_array($domain,$whitelist)) { $spammer = $domain; } # debug if (DEBUG) { echo 'domain: '.$domain.'
'; echo 'spammer: '.$spammer.'
'; #exit; } # end debug // prepare extra parameters for redirect if (1 == $global) { if ('' != $par) $par .= '&'; $par .= 'global=1'; } if (1 == $sites) { if ('' != $par) $par .= '&'; $par .= 'sites=1'; } // ------------------------------------- // do the blacklisting if (isset($spammer)) { // if $spammer = 'wakka' $queryd should remove http://wakka... // but NOT http://example.com/wakka from the referrers table #$hspammer = mysql_real_escape_string('//'.$spammer.'/'); # string to recognize host in referrers table $hspammer = mysql_real_escape_string('//'.$spammer); # string to recognize host in referrers table (trailing / removed: some referrers don't have one $spammer = mysql_real_escape_string($spammer); # string to use for spammer in referrer_blacklist table $queryd = 'DELETE FROM '.$pre.'referrers' . ' WHERE referrer LIKE "%'.$hspammer.'%"'; // check if domain is already blacklisted (must start with $spammer) # @@@ JW: should not be necessary if we'd have a _unique_ index on spammer! (let the database do the work) $querys = 'SELECT spammer FROM '.$pre.'referrer_blacklist' . ' WHERE spammer like "'.$spammer.'%"'; // add domain to blacklist $queryi = 'INSERT INTO '.$pre.'referrer_blacklist' . ' SET spammer = "'.$spammer.'"'; # debug if (DEBUG) { echo 'delete referrers: '.$queryd.'
'; echo 'check blacklist : '.$querys.'
'; echo 'blacklist domain: '.$queryi.'
'; $querye = str_replace('DELETE','EXPLAIN SELECT *',$queryd); $explain = $this->LoadAll($querye); echo 'Explain:
';
	print_r($explain);
	echo '
'; $queryes = str_replace('DELETE','SELECT *',$queryd); $todelete = $this->LoadAll($queryes); echo 'To delete:
';
	print_r($todelete);
	echo '
'; #exit; } # end debug $rcd = $this->Query($queryd); # TRUE on success $numrefdeleted = mysql_affected_rows(); # @@@ report back as GET parameter (in $par) if ($rcd) $rcs = $this->LoadSingle($querys); # row (array) if spammer already blacklisted if (!is_array($rcs)) $rci = $this->Query($queryi); # TRUE on success $numblacklisted = mysql_affected_rows(); # @@@ report back as GET parameter (in $par) // if referrers were deleted, report both deleted referrers and added blacklist records if (isset($numrefdeleted)) { if ('' != $par) $par .= '&'; $par .= 'refdel='.$numrefdeleted; $par .= '&bladd='; $par .= (isset($numblacklisted)) ? $numblacklisted : 0; } # debug if (DEBUG) { echo 'referrers deleted: '.$numrefdeleted.'
'; echo 'blacklisted: '.$numblacklisted.'
'; echo 'par: '.$par.'
'; } # end debug } // redirect to current page & handler, adding any extra parameters to get back to the original view # debug if (DEBUG) { // display link instead of doing redirect so debug output can be seen echo 'Back'; exit; } # end debug $this->Redirect($this->Href($redirect,'',$par)); ?> %% ===6. ##actions/header.php##=== We have created an extension of the stylesheet to style the user-interface elements for these handlers; to avoid (most) problems with all the custom "skins" people are using on this site (and maybe yours as well?), this is kept in a separate file (for now) so most of the new styles will become available. Therefore the extra stylesheet file should be linked into the ##header## template **before** the general display stylesheet: Existing ##actions/header.php##: %%(php;13) " /> " /> GetCookie("wikiskin"): $this->GetConfigValue("stylesheet") ?>" media="screen" /> %% Insert an extra link after line 15: %%(php;13) " /> " /> GetCookie("wikiskin"): $this->GetConfigValue("stylesheet") ?>" media="screen" /> %% This will put the necessary styling for the referrers handler user interface in place even if a custom skin is used. ===7. ##css/refmenu.css##=== >>**see also:** ""Styling"" on DbInfoAction >>This is the actual stylesheet file - it will later be integrated in the main wikka stylesheet, of course.::c:: %%(css;1)/* This stylesheet is for the referrers and blacklist handlers. It will need to be integrated with the main stylesheet. JW 2005-07-08 - extended for the dbinfo action and forms. */ h4 { margin-top: 0.3em !important; /* remove !important when integrating into main stylesheet or including it after that */ } .refmenu { margin: 0; padding: 0; margin-top: 1em; } .refmenu .menu { margin: 0; padding: 0; } .refmenu .menu li { list-style: none; float: left; margin-right: 3px; /* margin-right goes together with float left (or vice versa) */ padding: 1px 2px; font-size: 85%; line-height: 1.2em; color: #000000; background-color: #DDDDDD; } br.clear { clear: both; } form fieldset.hidden { /* for all forms! not just referrers/dbinfo */ display: none; } #refform, #dbinfo { color: inherit; background-color: inherit; margin-top: 1em; margin-bottom: 1em; } #refform { width: 32em; } #form_dbsel, #form_tablesel { width: 40em; } #refform fieldset, #form_dbsel fieldset, #form_tablesel fieldset { padding: 1em; margin-bottom: 0.3em; border: 1px solid #666666; } #refform legend, #form_dbsel legend, #form_tablesel legend { padding: 0 2px; color: #000000; background-color: #DDDDDD; border: 1px solid #666666; margin-bottom: 0.3em; } #refform .mainlabel { float: left; width: 4.6em; /* width will work on _floated_ element, even if not a block! */ padding-right: 0.5em; } #form_dbsel .mainlabel, #form_tablesel .mainlabel { float: left; width: 9.8em; /* width will work on _floated_ element, even if not a block! */ padding-right: 0.5em; } #q, #qo, #ho { width: 10em; } #h { width: 3em; text-align: right; } #reflist { margin-top: 1em; margin-bottom: 1em; border: none; } #reflist .hits { width: 3em; padding-right: 5px; text-align: right; vertical-align: middle; } #reflist .action { width: 5em; padding-left: 5px; padding-right: 5px; text-align: center; vertical-align: middle; } #reflist .refs { padding-left: 5px; text-align: left; vertical-align: middle; } %% ''This the version now extended for the [[DbInfoAction DbInfo action]].'' ===8. ##css/refmenu_col.css##=== >>**see also:** ""Styling"" on DbInfoAction >>The styling was designed to match with the default Wikka style. If you're using a custom skin here, everything should be positioned and spaced correctly, but the colors may not fit in with yours. To save you hunting down what would need to be changed, grab this little file and copy it into your own skin on TestSkin: it contains all the color settings using in the extra stylesheet. Then simply adapt the colors to match your own: these will then override those in ##css/refmenu_col.css##. ::c:: %%(css;1)/* For custom stylesheets: copy this into your stylesheet; the adapt the colors here (made to match the default Wikka skin) to match your own. JW 2005-07-08 - extended for the dbinfo action and forms. */ .refmenu .menu li { color: #000000; background-color: #DDDDDD; } form fieldset.hidden { /* for all forms! not just referrers/dbinfo */ display: none; } #refform fieldset, #form_dbsel fieldset, #form_tablesel fieldset { border: 1px solid #666666; } #refform legend, #form_dbsel legend, #form_tablesel legend { color: #000000; background-color: #DDDDDD; border: 1px solid #666666; } %% ''This the version now extended for the [[DbInfoAction DbInfo action]].'' ---- CategoryDevelopmentHandlers