Contratti pubblici – Offerta economica – Tabelle ministeriali – Discostamento – Conseguenze
Se è vero che le tabelle ministeriali contengono, per alcune voci, dati non inderogabili, è altrettanto pacifico che alle medesime è assegnata la funzione di parametro legale (ai sensi dell’art. 86 del d. lgs. n. 163 del 2006) e ciò comporta che lo scostamento dalle voci di costo ivi indicate può esser accettato solo quando risulti puntualmente giustificato: una tale dimostrazione deve essere particolarmente rigorosa con riferimento alle stime delle ore annue mediamente lavorate dal personale, trattandosi di un dato che presuppone eventi imprevedibili (malattie, infortuni, maternità , etc.) che non rientrano nella disponibilità dell’impresa e non dipendono dalla capacità gestionale ed organizzativa e che quindi, per definizione, postulano previsioni particolarmente prudenziali. Pertanto, l’offerta basata su un numero di assenze inferiore rispetto a quello assunto a livello statistico, su campione rappresentativo, dalle tabelle ministeriali, per essere accettata come plausibile deve essere accompagnata da significativi ed univoci dati probatori.
N. 01374/2011 REG.PROV.COLL.
N. 01309/2010 REG.RIC.
REPUBBLICA ITALIANA
IN NOME DEL POPOLO ITALIANO
Il Tribunale Amministrativo Regionale per la Puglia
(Sezione Prima)
ha pronunciato la presente
SENTENZA
sul ricorso numero di registro generale 1309 del 2010, proposto da La Lucente s.p.a. e La Lucentezza s.r.l., rappresentate e difese dall’avv. Giacomo Valla, con domicilio eletto presso il suo studio in Bari, via Q. Sella, 36;
contro
Istituto di Ricovero e Cura Giovanni Paolo II – Istituto Oncologico di Bari, rappresentato e difeso dall’avv. Gennaro Notarnicola, con domicilio eletto presso il suo studio in Bari, via Piccinni, 150;
nei confronti di
Accadueo s.r.l., rappresentata e difesa dagli avv.ti Luca Alberto Clarizio e Anna Del Giudice, con domicilio eletto presso il primo in Bari, via De Nicolò 7;
per l’annullamento
– della delibera del Direttore Generale n. 306 dell’8 luglio 2010 di esclusione dell’offerta dell’a.t.i. ricorrente per presunta anomalia dell’offerta e di aggiudicazione provvisoria alla controinteressata Accadueo s.r.l. dell’appalto del servizio triennale di pulizia della nuova sede dell’Istituto;
– della delibera del Direttore Generale n. 336 del 28 luglio 2010 di aggiudicazione definitiva;
– di ogni altro atto presupposto, connesso e consequenziale, compresa la nota del responsabile dell’Area Gestione del patrimonio prot. n. 8622 del 29 luglio 2010, di comunicazione dell’aggiudicazione definitiva, nonchè ove occorra del provvedimento n. 238 del 20 maggio 2010 di nomina del consulente esterno per la valutazione dell’anomalia delle offerte;
Visti il ricorso e i relativi allegati;
Visti gli atti di costituzione in giudizio dell’Istituto di Ricovero e Cura Giovanni Paolo II – Istituto Oncologico di Bari e di Accadueo s.r.l.;
Viste le memorie difensive ed il ricorso incidentale;
Visti tutti gli atti della causa;
Relatore nell’udienza pubblica del giorno 6 luglio 2011 il dott. Savio Picone e uditi per le parti i difensori avv.ti Giacomo Valla, Gennaro Notarnicola e Luca Alberto Clarizio;
Ritenuto e considerato in fatto e diritto quanto segue.
FATTO
Con delibera del 16 ottobre 2009, l’Istituto di Ricovero e Cura Giovanni Paolo II ha indetto una procedura aperta per l’affidamento triennale del servizio di pulizia, da aggiudicarsi con il criterio del massimo ribasso, di importo complessivo presunto pari a euro 4.447.994,26.
Le società ricorrenti, riunite in associazione temporanea, sono state dapprima escluse nella seduta del 25 settembre 2009, in quanto la relazione tecnico-progettuale allegata all’offerta è stata ritenuta difforme da quanto prescritto nel disciplinare di gara.
Anche la controinteressata Accadueo s.r.l. è stata inizialmente esclusa, per motivi analoghi.
Entrambe le concorrenti hanno impugnato dinanzi a questo Tribunale i verbali con cui era stata decisa la loro esclusione e sono state riammesse, rispettivamente, con le ordinanze cautelari di questa Sezione n. 580 del 5 novembre 2009 (per l’a.t.i. La Lucente s.p.a. – La Lucentezza s.r.l.) e n. 704 del 18 novembre 2009 (per Accadueo s.r.l.).
L’Istituto, con delibera del 6 dicembre 2009, ha quindi disposto la riammissione dell’a.t.i. ricorrente e della società controinteressata ed ha proceduto all’apertura delle buste contenenti le offerte economiche.
Il miglior ribasso è risultato quello proposto dall’a.t.i. La Lucente s.p.a. – La Lucentezza s.r.l., odierna ricorrente, che ha offerto un corrispettivo annuo di euro 1.117.501,884.
Ne è scaturita la rituale richiesta di giustificazioni e verifica dell’anomalia dell’offerta, in contraddittorio con i rappresentanti del raggruppamento.
Con gli atti qui gravati, l’Istituto ha giudicato inaffidabile l’offerta, sulla base della relazione tecnica dal consulente rag. Donato Magistà (che ha evidenziato molteplici incongruenze sul costo della manodopera), ed ha aggiudicato definitivamente l’appalto alla seconda classificata Accadueo s.r.l., che aveva offerto un corrispettivo annuo pari ad euro 1.137.830,09 (viceversa ritenuto congruo, all’esito dell’esame delle giustificazioni).
Avverso il provvedimento di esclusione le ricorrenti deducono, con unico ed articolato motivo, violazione degli artt. 86 e 87 del d. lgs. n. 163 del 2006 ed eccesso di potere per sviamento, irrazionalità manifesta, difetto dei presupposti e carenza di motivazione. Affermano, in sintesi, che:
1) il consulente nominato dall’Istituto avrebbe ingiustamente trascurato i chiarimenti offerti nel corso del contraddittorio, in relazione al contingente di personale preposto alle sostituzioni (per ferie, permessi, malattia, infortuni, etc.) ed al numero medio di ore di assenza che, per il raggruppamento ricorrente, sarebbe assai inferiore alla media calcolata nella tabelle FISE;
2) in ogni caso, i costi indicati nelle tabelle FISE non sarebbero affatto inderogabili e l’impresa offerente potrebbe sempre rendere chiarimenti in ordine ad eventuali scostamenti dai minimi tabellari;
3) sul piano procedurale, la stazione appaltante avrebbe illegittimamente delegato al consulente esterno il giudizio sulla congruità dei ribassi offerti in gara, limitandosi a recepire sic et simpliciter le sue conclusioni;
4) l’offerta economica della Accadueo s.r.l., viceversa giudicata congrua, sarebbe superiore di soli 20.000 euro annui e le relative giustificazioni sarebbero fondate su presupposti inverosimili.
Si sono costituiti, depositando documenti e chiedendo il rigetto dell’impugnativa, l’Istituto di Ricovero e Cura Giovanni Paolo II e la Accadueo s.r.l.; quest’ultima ha altresì notificato ricorso incidentale, volto a contestare l’attendibilità delle giustificazioni fornite dall’a.t.i. ricorrente sotto diversi ed ulteriori profili, non presi in considerazione dal consulente dell’Istituto, che avrebbe dovuto in ogni caso comportarne l’esclusione.
Questa Sezione, con ordinanza n. 622 del 9 settembre 2010, ha respinto l’istanza cautelare.
Le parti hanno svolto difese in vista della pubblica udienza del 6 luglio 2011, nella quale la causa è passata in decisione.
DIRITTO
Il ricorso è infondato.
Il provvedimento di esclusione dell’a.t.i. La Lucente s.p.a. – La Lucentezza s.r.l. è motivato per relationem con il richiamo delle conclusioni cui è giunto il consulente esterno, rag. Donato Magistà , incaricato dalla stazione appaltante di verificare la correttezza delle giustificazioni in ordine al costo della manodopera, che normalmente costituisce il principale onere economico cui fanno fronte le imprese appaltatrici del servizio di pulizia.
Il consulente, nella relazione del 7 giugno 2010, ha evidenziato che:
– il capitolato d’appalto ha fissato il fabbisogno minimo, per lo svolgimento del servizio, pari a 86.706 ore annue effettive di lavoro;
– il costo orario medio è quello derivante dall’applicazione delle tabelle approvate dal Ministero del Lavoro con decreto del 25 febbraio 2009, in base alle quali ciascun dipendente garantisce una media di ore annue lavorate pari a 1.581;
– le giustificazioni prodotte dal raggruppamento ricorrente non appaiono soddisfacenti, in relazione ai costi imputabili alle assenze del personale per malattia, ferie, infortuni, permessi, riduzioni di orario e festività soppresse (sulla base delle previsioni del C.C.N.L. di settore);
– per effetto dei maggiori costi del lavoro, non ponderati in sede di giustificazioni, il raggruppamento subirebbe una perdita complessiva, nell’esercizio dell’appalto, pari ad euro 641.717,26 nel triennio.
Siffatti rilievi, ad avviso del Collegio, non sono stati adeguatamente confutati dalle ricorrenti, che si sono limitate a giustificare la sottostima del costo per le sostituzioni del personale assente affermando di disporre di un contingente a ciò preposto, per tutti gli appalti assunti nel territorio provinciale, rientrante nei costi generali d’impresa.
Le stesse ricorrenti hanno tuttavia ammesso di aver preventivato un numero di ore di assenza dal lavoro inferiore a quello medio calcolato nella tabelle ministeriali FISE.
In proposito, la giurisprudenza ha ripetutamente affermato che, se è vero che le tabelle ministeriali contengono, per alcune voci, dati non inderogabili, è altrettanto pacifico che alle medesime è assegnata la funzione di parametro legale (ai sensi dell’art. 86 del d. lgs. n. 163 del 2006) e ciò comporta che lo scostamento dalle voci di costo ivi indicate può esser accettato solo quando risulti puntualmente giustificato: una tale dimostrazione deve essere particolarmente rigorosa con riferimento alle stime delle ore annue mediamente lavorate dal personale, trattandosi di un dato che presuppone eventi imprevedibili (malattie, infortuni, maternità , etc.) che non rientrano nella disponibilità dell’impresa e non dipendono dalla capacità gestionale ed organizzativa e che quindi, per definizione, postulano previsioni particolarmente prudenziali, con la conseguenza che l’offerta basata su un numero di assenze inferiore rispetto a quello assunto a livello statistico, su campione rappresentativo, dalle tabelle ministeriali, per essere accettata come plausibile deve essere accompagnata da significativi ed univoci dati probatori (così Cons. Stato, sez. V, 12 marzo 2009 n. 1451; nello stesso senso, su fattispecie analoga a quella in esame, TAR Puglia, Bari, sez. I, 7 giugno 2010 n. 2257).
In concreto, l’Amministrazione ha legittimamente ritenuto che il raggruppamento primo classificato non avesse offerto elementi convincenti, in ordine ai costi da sostenere nel triennio per far fronte alle assenze ed in ordine all’asserita disponibilità di un nucleo di dipendenti addetti alle sostituzioni nella provincia di Bari. I conteggi eseguiti dal consulente hanno peraltro fatto emergere un difetto di stima niente affatto irrisorio, poichè il costo della manodopera esposto nell’offerta (pari a complessivi euro 2.947.113,63) è risultato assai inferiore a quello discendente dall’applicazione dei valori contenuti nella tabelle ministeriali FISE (pari a complessivi euro 3.662.717,90).
Anche sul piano procedimentale sono state rispettate le regole (in primis quella del contraddittorio) che caratterizzano la fase della verifica delle giustificazioni sull’anomalia del prezzo.
Non può accogliersi, al riguardo, la censura avanzata da parte ricorrente, secondo la quale l’Istituto avrebbe interamente delegato al consulente esterno la valutazione discrezionale sulla congruità dei ribassi offerti in gara, limitandosi a recepirne le conclusioni.
Alla consegna della relazione tecnica del consulente, in data 28 giugno 2010, è infatti seguita l’audizione in contraddittorio dei rappresentati delle ricorrenti, in data 2 luglio 2010, alla presenza dello stesso consulente esterno. Il giudizio definitivo di esclusione è stato perciò assunto dall’Istituto, in data 8 luglio 2010, dopo l’esame in contraddittorio della relazione del consulente, al quale non è stata dunque rimessa alcuna funzione decisoria definitiva, nel rispetto dell’ordinario regime di competenza.
Infine, sono inammissibili le doglianze mosse da parte ricorrente nei confronti della valutazione di congruità espressa dall’Istituto nei confronti dell’offerta economica della seconda classificata Accadueo s.r.l., odierna controinteressata.
Secondo un principio ormai consolidato, infatti, l’impresa legittimamente esclusa da una gara non ha titolo a contestarne gli esiti e la regolarità delle successive fasi procedimentali.
L’infondatezza nel merito dell’impugnativa principale consente inoltre di dichiarare improcedibile il ricorso incidentale proposto da Accadueo s.r.l., per difetto d’interesse, attesa la sua stretta accessorietà .
Le spese seguono la soccombenza nei confronti dell’Amministrazione resistente, mentre possono essere compensate nei confronti della società controinteressata (tenuto conto dell’accordo manifestato nei preliminari d’udienza dai difensori).
P.Q.M.
Il Tribunale Amministrativo Regionale per la Puglia (Sezione Prima) definitivamente pronunciando sul ricorso, come in epigrafe proposto, lo respinge.
Dichiara improcedibile il ricorso incidentale.
Condanna le società ricorrenti, in solido tra loro, al pagamento delle spese di giudizio in favore dell’Istituto di Ricovero e Cura Giovanni Paolo II, nella misura di euro 8.000 (oltre i.v.a., c.a.p. ed accessori di legge); compensa le spese nei confronti di Accadueo s.r.l.
Ordina che la presente sentenza sia eseguita dall’autorità amministrativa.
Così deciso in Bari nella camera di consiglio del giorno 6 luglio 2011 con l’intervento dei magistrati:
Corrado Allegretta, Presidente
Giuseppina Adamo, Consigliere
Savio Picone, Referendario, Estensore
L’ESTENSORE | IL PRESIDENTE | |
DEPOSITATA IN SEGRETERIA
Il 22/09/2011
IL SEGRETARIO
(Art. 89, co. 3, cod. proc. amm.)
/////////// SEARCH ///////////
var g_aEng;
var g_loc;
// Initialize namespace, use existing context
var searchshield = searchshield || {};
searchshield.clockUrl;
// constants
searchshield.SCORE_SS_SAFE = 1;
searchshield.SCORE_SS_CAUTION = 2;
searchshield.SCORE_SS_WARNING = 3;
searchshield.SCORE_SS_BLOCK = 4;
searchshield.SCORE_SS_VERISIGN = 7;
searchshield.BLOCK_NONE = 0;
searchshield.BLOCK_NORMAL = 1;
searchshield.BLOCK_PHISH = 2;
searchshield.BLOCK_YAHOO = 3;
searchshield.XPLCHECK_RESULT_SEV_NONE = 0;
searchshield.XPLCHECK_RESULT_SEV_LOW = 1;
searchshield.XPLCHECK_RESULT_SEV_MED = 2;
searchshield.XPLCHECK_RESULT_SEV_BLOCK = 3;
searchshield.VERISIGN_SPLIT_NOTEST = 0;
searchshield.VERISIGN_SPLIT_TESTA = 1;
searchshield.VERISIGN_SPLIT_TESTB = 2;
searchshield.needLivePhishCheck = false;
searchshield.allowedSites = [];
searchshield.enabled = function (doc)
{
var result = searchshield.avgCallFunc(doc, ‘GetSearchEnabled’);
return (result == ‘1’ ? 1 : 0);
};
searchshield.init = function (doc)
{
if ((doc == null) || (doc.location == null) || (doc.location.href.search(/about:/) != -1))
return;
if (!searchshield.enabled(doc))
return;
if (!g_aEng)
g_aEng = searchshield.Search.prototype.detectEngine(doc.location.href);
if (!g_aEng)
return;
// init search object (not declared or is null)
if (typeof xplSearch === ‘undefined’)
{
// global
xplSearch = new searchshield.Search();
// reset the links added flag
xplSearch.new_links = false;
xplSearch.doc = doc;
xplSearch.href = xplSearch.doc.location.href;
xplSearch.uri = searchshield.parseLink(xplSearch.href);
xplSearch.engine = new searchshield[g_aEng+’SearchEngine’](xplSearch)
xplSearch.addEngine(xplSearch.engine);
searchshield.launch(doc);
}
if (doc.location.href != g_loc)
{
g_loc = doc.location.href;
if ((typeof xplSearch !== ‘undefined’) && (xplSearch != null))
searchshield.launch(doc);
}
};
searchshield.launch = function (doc)
{
// IE specific check
searchshield.quirksMode = (self.top.document.compatMode == ‘BackCompat’);
searchshield.docMode = parseInt(navigator.userAgent.split(‘MSIE’)[1]);
if ((self === top) && (self.document === doc))
{
if (!xplSearch.engine)
return;
// set verdict display config
xplSearch.engine.setRatingsConfig(doc);
// init the alert popup
searchshield.initPopupAlert(doc);
if (xplSearch.engine.type != ‘inline’)
{
// save function reference for memory clean up later
var fn = function(event){avglsflyover.hide(null)};
//hide flyover if these events occur
window.detachEvent(‘onscroll’, fn);
window.attachEvent(‘onscroll’, fn);
doc.detachEvent(‘onkeydown’, fn);
doc.attachEvent(‘onkeydown’, fn);
}
// only start monitor on top doc
searchshield.avgPageMonitor.start(doc);
}
return;
};
// search monitors and processors – doc is always top most document
searchshield.avgPageMonitor = {
previousUrl: null,
start: function(doc){
searchshield.avgPageMonitor.stop();
searchshield.avgPageMonitor.process(doc);
searchshield.avgPageMonitor.timeoutID = window.setTimeout(function(){searchshield.avgPageMonitor.start(doc)}, 1000);
},
process: function(doc){
var currentUrl = doc.location.href;
var refresh = 0;
if (this.previousUrl != currentUrl) {
this.previousUrl = currentUrl;
avgreport.scanResult(doc, currentUrl);
refresh = (xplSearch.engine.name == ‘google’) ? 1 : 0;
}
searchshield.avgProcessSearch(doc, refresh);
},
stop: function(){
if (searchshield.avgPageMonitor.timeoutID)
{
window.clearTimeout(searchshield.avgPageMonitor.timeoutID);
delete searchshield.avgPageMonitor.timeoutID;
}
}
};
searchshield.avgProcessSearch = function (doc, refresh)
{
// doc may be about:Tabs or about:Blank
if (!doc)
return;
if (!searchshield.enabled(doc))
return;
if (!searchshield.clockUrl)
searchshield.clockUrl = searchshield.avgCallFunc(doc, ‘GetIconUrl’, ‘0’);
xplSearch.clockUrl = searchshield.clockUrl
if (!xplSearch.engine)
return;
// get result links
xplSearch.links = [];
var links = searchshield.avgGetSearchLinks(doc, xplSearch.engine, refresh);
searchshield.needLivePhishCheck = false;
for (var i=0; i < links.length; i++)
{
var isPhishing = searchshield.avglsCheckandUpdate(links[i]);
if (isPhishing)
searchshield.needLivePhishCheck = true;
}
if (searchshield.needLivePhishCheck)
{
var prev = '1';
if ( xplSearch.engine.type == 'inline' )
prev = '0';
searchshield.avgCallFunc(doc, 'GetPhishingResults', prev);
searchshield.needLivePhishCheck = false;
}
else if (links.length > 0 && xplSearch.engine.type != ‘inline’)
{
searchshield.avgCallFunc(doc, ‘FinalScanComplete’);
}
// attach click handlers for popup alerts
doc.body.detachEvent(“onclick”, searchshield.blockClick);
doc.body.attachEvent(“onclick”, searchshield.blockClick);
doc.body.detachEvent(“ondblclick”, searchshield.blockClick);
doc.body.attachEvent(“ondblclick”, searchshield.blockClick);
};
searchshield.avgGetSearchLinks = function (doc, engine, refresh)
{
if (!doc.body)
return;
var alltags = doc.body.getElementsByTagName(‘a’);
for (var i = 0; i < alltags.length; i++)
{
if ( !refresh )
{ // no checked test if refreshing - google
if (alltags[i].getAttribute('avglschecked'))
continue;
}
// mark search result anchor so it isn't processed repeatedly
alltags[i].setAttribute('avglschecked', '1');
// ignore linked resources
if (alltags[i].tagName == 'LINK')
continue;
// ignore in-page bookmarks and javascript
if ((!alltags[i].href) ||
(alltags[i].href.charAt(0) == '#') || // in-page bookmark
(alltags[i].href.indexOf("javascript") == 0))
continue;
// ignore verdicts
if (/XPLSS_/.test(alltags[i].id))
continue;
// ignore flyover anchors
if (/avgthreatlabs/.test(alltags[i].host))
continue;
var href = engine.includeLink(alltags[i]);
if (!href)
continue;
var newNode = engine.search.addLink(alltags[i], href);
engine.addImage(newNode, engine.search.clockUrl, false);
}
// recursivesly process all frames
var docFrames = doc.frames;
if (docFrames && engine.processFrames)
{
for (var j = 0; j < docFrames.length; j++)
{
var attr;
var frameDoc;
try {
attr = docFrames[j].frameElement.className;
frameDoc = docFrames[j].document;
}
catch(err){}
//TODO: make frame processing an engine function or at least make exclusions an engine property
// 'editable' frame it's probably a gmail reply
if (attr && (attr.indexOf("editable") != -1))
continue;
if (frameDoc)
searchshield.avgGetSearchLinks(frameDoc, engine, 0);
}
}
return engine.search.links;
};
searchshield.avglsCheckandUpdate = function (linkNode)
{
if (!xplSearch)
return;
// element is the search result anchor
var element = linkNode.element;
var href = linkNode.href;
var result = searchshield.avgCallFunc(xplSearch.doc, 'CheckSite', href, element.href);
if (result == null)
return;
var resultParse = result.split('::');
var phishing = resultParse[0]; // if phishing then rest of array does not exist.
if (phishing == 1)
return true;
if (resultParse.length < 8)
return;
var hash = resultParse[1];
var score = resultParse[2];
var new_image = resultParse[3];
var alt_image = resultParse[4];
var flyover = resultParse[5];
var click_thru= resultParse[6];
var altClick_thru = resultParse[7];
// iterate to get verdict anchor
nextElem = element.nextSibling;
while (nextElem)
{
if (nextElem.nodeType == 1 && nextElem.id && (nextElem.id.indexOf("XPLSS_") != -1))
break;
nextElem = nextElem.nextSibling;
}
return xplSearch.engine.updateImage(hash, xplSearch.searchHash, score, new_image, alt_image, flyover, click_thru, altClick_thru);
};
// click event handler - shows popup for links of caution and warning severity
searchshield.blockClick = function(event)
{
if (!event)
event = window.event;
// no action needed if click is not the left mouse button
if (event.button != 0)
return;
var anchor = searchshield.getAnchorNode(event.srcElement,
function(node) {return ((node.tagName.charAt(0) == "H") ||
(node.tagName.charAt(0) == "D") ||
(node.tagName.charAt(0) == "T"))} );
if ((anchor == null) || (anchor.href == null))
return true;
// ignore if anchor is on an xpl verdict
if (!!anchor.id)
{
if (anchor.id.indexOf('LXPLSS_') == 0)
return true;
if (anchor.id.indexOf('XPLSS_INTR') == 0)
{
searchshield.allowedSites.push(searchshield.GetDomain(anchor.href));
return true;
}
}
// VeriSign A/B Split reporting - only for VerSign domains
var avglschecked = anchor.getAttribute("avglschecked");
if (avglschecked && avglschecked != 1)
{
var sPos = avglschecked.indexOf("S");
var hash = (sPos > -1) ? avglschecked.substring(0, sPos) : null;
var split = (sPos > -1) ? avglschecked.substring(sPos+1) : null;
if (hash && split && split != searchshield.VERISIGN_SPLIT_NOTEST)
{
// check updated verdict anchor for verisign domain
var d = event.srcElement.ownerDocument;
if (d.getElementById(“LXPLSS_” + hash + “U” + searchshield.SCORE_SS_VERISIGN))
{
searchshield.avgCallFunc(d, “RecordVSClick”, hash, d.location.href);
}
}
}
var link = anchor.href;
var verdict = searchshield.getAvgImage(anchor);
var score = -1;
var img_id = ”;
if (verdict != null)
{
score = verdict.score;
img_id = verdict.rawId;
}
// show popup alert (upper left)
if ((score >= searchshield.SCORE_SS_CAUTION) && (score = elementRect.bottom) &&
(nextImgRect.left -1)
return true;
return false;
};
searchshield.FilterUrl = function (url, filter)
{
if (!url || (url.length < 1))
return false;
if (!filter || !(filter instanceof Array))
return false;
var parts = url.split('/');
if ((parts == null) || (parts.length < 3))
return false;
var domain = parts[2];
for (var i = 0; i < filter.length; i++)
{
if (domain.indexOf(filter[i]) != -1)
return true;
}
return false;
};
searchshield.GetDomain = function (url)
{
if (url != null)
{
// get url domain
var parts = url.split('/');
if ((parts != null) && (parts.length >= 3))
{
return parts[2].toLowerCase();
}
}
return url;
};
searchshield.getUrlContents = function (url)
{
if (url == null)
return null;
// don’t query if local url
if (url.indexOf(“linkscanner://”) != -1)
return null;
try
{
req = new XMLHttpRequest();
req.open(“GET”, url, false);
req.send(null);
if (req.status == 200)
return req.responseText;
else
return null;
}
catch (err)
{
// nothing to do
return null;
}
};
searchshield.parseLink = function (href, simpleMode)
{
var uri = {};
var parameter = {
complex: {
pattern: /^(?:([a-z]+):(?:([a-z]*):)?//)?(?:([^:@]*)(?::([^:@]*))?@)?((?:[a-z0-9_-]+.)+[a-z]{2,})(?::(d+))?(?:([^:?#]+))?(?:?([^#]+))?(?:#([^s]+))?$/i,
element: [‘source’,’scheme’,’subscheme’,’user’,’pass’,’host’,’port’,’path’,’query’,’fragment’]
},
simple: {
pattern: /^(?:([a-z]+)://)?((?:[a-z0-9_-]+.)+[a-z]{2,})(?:/)([^:?]+)?(?:([?|#])([^?]+))?$/i,
element: [‘source’,’scheme’,’host’,’path’,’delimiter’,’query’]
}
};
var mode = simpleMode !== false ? ‘simple’ : ‘complex’;
var pattern = parameter[mode].pattern;
var element = parameter[mode].element;
if (!href)
return uri;
var matches = href.match(pattern);
if (matches)
{
// ——————–
// iterate over the matches array and populate uri properties
// using the respective element parameter as the name.
// NOTE: set raw property type as String to make inArray()
// work properly with instanceof.
// ——————–
for (var i=0; i < matches.length; i++)
uri[element[i]] = new String(matches[i] || "");
// --------------------
// create an array, hostArray, from host, for example,
// host="www.google.com" and hostArray=["www","google","com"]
// --------------------
uri.hostArray = uri.host.split(".");
// --------------------
// create an array, qsArray, from query, for example,
// query='hl=en&q=javascript&btnG=Search&aq=f&aqi=g10&aql=&oq=&gs_rfai='
// qsArray=[{hl:'en'},{q:javascript}, ... ,(qs_rfai:''}]
//
// $0=entire match, $1=capture 1, $2=capture 2
// must include $0 even though it is unused so
// the replace works properly
// --------------------
uri.qsArray = searchshield.parseQuery(uri.query);
}
//non-standard urls require a fail-safe that relies on simply splitting the href
function splitLink(href)
{
// split the href on '/'
var linkParts = href.split("/");
// need domain and path
if ((linkParts == null) || (linkParts.length < 2))
return false;
var uri = {
delimiter: (linkParts[3]).substring(0,1),
host: linkParts[2],
hostArray: (linkParts[2]).split('.'),
path: (linkParts[3]).substring(1),
qsArray: [],
query: '',
scheme: (linkParts[0]).substring(0, linkParts[0].length-1),
source: href
};
return uri;
}
if (!uri.host)
uri = splitLink(href);
return uri;
};
searchshield.parseQuery = function (qs)
{
var qsArray = [];
qs.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,
function ($0, $1, $2) {
if ($1) qsArray[$1] = $2;
}
);
return qsArray;
};
// general functions
searchshield.arrayKeys = function (array)
{
var keys = new Array();
for(k in array)
keys.push(k);
return keys;
};
searchshield.inArray = function (key, array, caseSensitive, exactMatch)
{
if (! array instanceof Array)
return false;
if (caseSensitive !== true)
caseSensitive = false;
if (exactMatch !== false)
exactMatch = true;
if (key instanceof String)
{
for (var i=0; i < array.length; i++)
{
var k = caseSensitive ? key.valueOf() : key.valueOf().toLowerCase();
var a = caseSensitive ? array[i] : array[i].toLowerCase();
if(exactMatch && k === a)
return true;
else if (!exactMatch && (-1 !== k.indexOf(a)))
return true;
}
}
else if (key instanceof Array)
{
for (var i=0; i < array.length; i++)
for (var j=0; j < key.length; j++)
{
var k = caseSensitive ? key[j] : key[j].toLowerCase();
var a = caseSensitive ? array[i] : array[i].toLowerCase();
if (exactMatch && k === a)
return true;
else if (!exactMatch && (-1 !== k.indexOf(a)))
return true;
}
}
return false;
};
searchshield.getClickHandlerParams = function(clickHandler)
{
var re = /((?:'[^']*')|[w]*)(?:,|))/ig;
var chParams = [];
clickHandler.replace(re,
function($0, $1, $2){
if ($1)
chParams.push($1);
}
);
return chParams;
};
// general use functions - end
// Search constructor
searchshield.Search = function()
{
this.doc = null;
this.engine = null;
this.engines = null;
this.links = null;
this.uri = null;
this.searchHash = null;
this.checkUrl = null;
this.useLocalImgs = null;
this.clockUrl = null;
// create engine list (actually key/value object will be used)
this.engineList = {};
};
searchshield.Search.prototype.getSearchNames = function()
{ // order is important
var names = [
'Google',
'AVGGoogle',
'AltaVista',
'AVGYahoo',
'Yahoo',
'Bing',
'MSN', // MSN redirects to BING
'Baidu',
'Earthlink',
'AOL',
'Ask',
'Yandex',
'Seznam',
'Webhledani',
'eBay',
///temp 'Digg',
'Slashdot',
'Twitter',
'GMail',
'Facebook',
'MySpace'
];
return names;
};
searchshield.Search.prototype.detectEngine = function(href)
{
if (!href)
return;
var aEng = searchshield.Search.prototype.getSearchNames();
var aEngLen = aEng.length;
for (var i=0; i < aEngLen; i++)
{
if (searchshield[aEng[i] + 'SearchEngine'].prototype.validSearch(href))
return aEng[i];
}
return;
};
searchshield.Search.prototype.addEngine = function(engine)
{
if (!this.engines)
this.engines = new Array();
this.engines.push(engine);
};
searchshield.Search.prototype.addLink = function(inElement, inHref)
{
if (!this.links)
this.links = new Array();
var hrefHash;
try
{
hrefHash = searchshield.avgCallFunc(this.doc, 'GetHash', inHref);
}
catch (e){}
var newNode = {
element: inElement,
href: inHref,
hash: hrefHash,
search: this.searchHash
};
this.links.push(newNode);
return newNode;
}
// process the search result page after all search engines have been added
searchshield.Search.prototype.process = function(doc)
{
// only process when searchshield is enabled
if (!searchshield.enabled(doc))
return;
this.doc = doc;
this.href = this.doc.location.href;
this.uri = searchshield.parseLink(this.href);
try
{
this.searchHash = searchshield.avgCallFunc(this.doc, 'GetHash', this.href);
// get any previously active engine
this.engine = this.engineList[this.searchHash.toString()];
}
catch (e) {}
/*
Process Steps:
1. Add all supported search engines
2. Identify the active search engine
3. Get all document links and add AVG images
*/
// STEP 1 - Add all supported search engines
if (!this.engines)
{
var aEng = xplSearch.getSearchNames();
var aEngLen = aEng.length;
for (var i=0; i < aEngLen; i++)
{
xplSearch.addEngine(new searchshield[aEng[i]+'SearchEngine'](this));
}
}
// search the engines if we didn't find one
if (!this.engine)
{
// STEP 2 - Identify the active search engine
var engLen = this.engines.length;
for (var i = 0; i < engLen; i++)
{
if (this.engines[i].validSearch())
{
this.engine = this.engines[i];
break;
}
}
// create a new engine instance to store
this.engineList[this.searchHash.toString()] = this.engine;
// init this search, if < 1 either an error or disabled
//var sdkInit = 0;
//try {
// sdkInit = xpl_sdk.SXPL_InitSearch(this.href);
//}
//catch(e){}
//if (sdkInit < 1)
// return false;
}
// return immediately if there is not an active search engine
if (!this.engine)
return false;
try {
// base url to check for icons
this.checkUrl = searchshield.avgCallFunc(this.doc, 'GetIconUrl', '1');
// check if using linked or local icons
this.useLocalImgs = !searchshield.getUrlContents(this.checkUrl);
// get the clock url
this.clockUrl = searchshield.avgCallFunc(this.doc, 'GetIconUrl', '0');
}
catch(e){}
// STEP 3 - Get all document links and add AVG images
var alltags = this.doc.getElementsByTagName("*"); // this method works for IE, FF and Chrome
for (var i=0; i < alltags.length; i++)
{
// ignore verdicts
if (alltags[i].id && (alltags[i].id.indexOf("LXPLSS_") != -1))
continue;
//should the link be included? Make sure includeLink always returns an href else FALSE,
var href = this.engine.includeLink(alltags[i]);
if (!href)
continue;
var newNode = this.addLink(alltags[i], href);
this.engine.addImage(newNode, this.clockUrl, false);
}
return (this.links ? this.links.length : false);
};
//////////////// SEARCH ////////////////
//////////////// SEARCH ENGINE ////////////////
// Interface for a SearchEngine object
searchshield.SearchEngine = function(search)
{
this.search = search;
this.type = 'standard';
this.processFrames = false;
this.new_links = true;
this.onlyPrimaries = true;
this.inline = {
clockImage: "linkscanner://clock12.png",
image: [ "linkscanner://safe12.png",
"linkscanner://caution12.png",
"linkscanner://warning12.png",
"linkscanner://blocked12.png"
],
color: {
classname: ["green","yellow","orange","red"],
border: ["#00A120", "#EAA500", "#F57301", "#D20003"],
background: ["#C3E5CA", "#FEEFAE", "#FFD3B0", "#F5D4C1"]
}
};
this.filter_urls = [
"ad.doubleclick.net", "ads1.revenue.net", "aslads.ask.com",
"bluestreak.com", "clickbacktrack.net", "clickbank.net",
"clickboothlnk.com", "clickmanager.com", "clickserve.cc-dt.com",
"dartsearch.net", "clicktraxmedia.com", "clk.atdmt.com",
"dpi-digialphoto.com", "feedpoint.net", "hypertracker.com",
"jdoqocy.com", "kqzyfj.com", "m1428.ic-live.com",
"mediaplex.com", "mr.mdmngr.com", "n339.asp-cc.com",
"offeredby.net", "offerweb.com", "pinktrax.com",
"pinktrax.com", "pixel1523.everesttech.net", "qckjmp.com",
"r.rd06.com", "revenuewire.net", "s0b.bluestreak.com",
"s2.srtk.net", "servedby.advertising.com", "store.yahoo.com",
"tf8.cpcmanager.com", "thetoptracker.com", "track.searchignite.com",
"tracking.searchmarketing.com", "www.dpbolvw.net", "www.rkdms.com",
"www.yellowbookleads.com"
];
this.shortened_urls = [
"3.ly", "bit.ly", "is.gd", "tr.im", "short.to", "tiny.cc", "tinyurl.com", "lnk.ms", "msplinks.com", "t.co", "qr.net"
];
this.showCleanVerdicts = true;
this.showLowRiskVerdicts = true;
this.showMedRiskVerdicts = true;
this.VeriSignSplit = searchshield.VERISIGN_SPLIT_NOTEST;
};
searchshield.SearchEngine.prototype.flyoverExists = function (doc)
{
return !!doc.getElementById("XPLSS_Flyover");
};
searchshield.SearchEngine.prototype.inlineExists = function (doc)
{
return !!doc.getElementById("XPLSS_InlineFlyover");
};
searchshield.SearchEngine.prototype.validSearch = function(href) { return false; };
searchshield.SearchEngine.prototype.includeLink = function(link) { return false; };
searchshield.SearchEngine.prototype.insertNodes = function(node, doc)
{
var element = node.element;
var parentNode = node.element.parentNode;
if (parentNode == null)
{
// try and find element again based on the hash
element = doc.getElementById("xplid_" + node.hash);
parentNode = !!element ? element.parentNode : null;
}
var insertNode = !!element ? element.nextSibling : null;
while ((insertNode != null) &&
(insertNode.tagName != null) &&
(insertNode.tagName == "SPAN"))
{
insertNode = insertNode.nextSibling;
}
return [insertNode, parentNode];
};
searchshield.SearchEngine.prototype.addImage = function(node, image, hidden)
{
var element = node.element;
var hash = node.hash;
var score = node.score;
// set verdict display configuration
var doc = element.ownerDocument;
if (this.type != 'inline' && !doc.getElementById('XPLSS_Flyover'))
searchshield.initFlyover(doc, this);
// get the proper insertion point for the image
var insertNodes = this.insertNodes(node, doc);
var insertNode = insertNodes[0];
var parentNode = insertNodes[1];
if (!parentNode)
return;
// see if we already have an image
if ((insertNode != null) &&
(insertNode.id != null) &&
(insertNode.id.indexOf("XPLSS_") > -1))
{
return;
}
// mark search result anchor so it isn’t processed repeatedly
if (score == undefined)
element.setAttribute(“avglschecked”, hash + “S” + this.VeriSignSplit);
// create a new image
var img = doc.createElement(‘img’);
img.src = image;
img.id = “XPLSS_” + hash;
img.style.borderStyle = “none”;
img.style.margin = “0 3px”;
// for IE, specify these style attributes to prevent inadvertent inheritance from parent
if (img.width && img.height)
{
img.style.width = img.width + ‘px’;
img.style.height = img.height + ‘px’;
}
// apply custom element styles
this.updateElementStyle(img, this.addImageStyle);
// create the link element
var anchor = doc.createElement(“A”);
anchor.setAttribute(“id”, “LXPLSS_” + hash);
if ((hidden != null) && (hidden == true))
{ // hiding the parent will also hide its child nodes
anchor.style.display = “none”;
}
// Default anchor styles
//Over-ride possible border style with inline declaration
anchor.style.borderStyle = “none”;
// apply custom element styles
this.updateElementStyle(anchor, this.addAnchorStyle);
if (score == searchshield.SCORE_SS_VERISIGN)
{
anchor.style.textDecoration = “none”;
anchor.style.background = “none repeat scroll 0 0 transparent”;
}
// append the image to the link
anchor.appendChild(img);
// insert the node as either a sibling or a child
if (insertNode != null)
parentNode.insertBefore(anchor, insertNode);
else
parentNode.appendChild(anchor);
return anchor;
};
searchshield.SearchEngine.prototype.updateImage = function (hash, search, score, image, alt_image, flyover, click_thru, altClick_thru)
{
var updated = false;
var frameDoc = this.search.doc;
var docFrames = frameDoc.frames;
var frameElem;
if (docFrames && this.processFrames)
{
for (var i=0; i < docFrames.length; i++)
{
try {
if (docFrames[i].document.getElementById(hash))
{
frameElem = docFrames[i].frameElement;
frameDoc = docFrames[i].document;
break;
}
}
catch(err){}
}
}
while ((element = frameDoc.getElementById(hash)) != null)
{
// check configuration to determine if verdict display property
var showVerdict = true;
var nSeverity = Number(score - 1);
switch (nSeverity)
{
case searchshield.XPLCHECK_RESULT_SEV_LOW:
showVerdict = this.showLowRiskVerdicts;
break;
case searchshield.XPLCHECK_RESULT_SEV_MED:
showVerdict = this.showMedRiskVerdicts;
break;
case searchshield.XPLCHECK_RESULT_SEV_NONE:
showVerdict = this.showCleanVerdicts;
break;
default:
if (score == searchshield.SCORE_SS_VERISIGN)
showVerdict = this.showCleanVerdicts;
break;
}
// remove image if no url specified
if ((!showVerdict) || (image == null) || (image.length < 1))
{
// hide the parent anchor node
element.parentNode.style.display = "none";
// mark the id as being hidden (element is the image)
element.id = element.id + "H";
updated = true;
// if not a verisign score
if (score != searchshield.SCORE_SS_VERISIGN)
continue;
}
// cleanup flyover, replace any new lines or single quotes
flyover = searchshield.CleanupHTML(flyover);
// mark the id as having been updated
element.id = element.id + "U" + score;
element.src = image;
element.attachEvent("onmouseover", function(e){avglsflyover.popup(e, hash, search, flyover)});
element.attachEvent("onmouseout", function(e){avglsflyover.hide(e)});
// check for attribute updates (elementAttribute is an associative array (i.e., object)
if (this.elementAttribute)
{
for (a in this.elementAttribute)
{
if(this.elementAttribute[a])
element.setAttribute(a, this.elementAttribute[a]);
}
}
// To dynamically reduce verdict image size if it causes its container to scroll
// when not showing alt images determine if the element containing
// the verdict image is scrolling and decrease the image size by
// the scroll amount (min size is 80% or original)
var reduceBy = 0.8;
var scrl = 0;
if (!alt_image || this.omitAltImage || this.VeriSignSplit == searchshield.VERISIGN_SPLIT_TESTB)
{
try{
var maxLoop = 5;
var cN = element.parentNode.parentNode; //image->anchor->containerNodes…
while (cN && maxLoop–)
{
if (cN.tagName == “DIV” || cN.tagName == “SPAN”)
{
// get object height depending on ie document mode
var clientHeight = (cN.clientHeight == 0 ||
(this.search.doc.documentMode && this.search.doc.documentMode < 8)) ?
cN.offsetHeight :
cN.clientHeight;
scrl = cN.scrollHeight - clientHeight;
break;
}
cN = cN.parentNode;
}
if (0 < scrl)
{
var eH = (element.height - scrl)/element.height;
if (reduceBy > eH)
eH = reduceBy;
var newDim = Math.ceil(eH*element.height);
element.height = newDim;
element.width = newDim;
element.style.height = newDim + “px”;
element.style.width = newDim + “px”;
}
}
catch(e){}
}
// set default style attributes
element.style.display = “”;
// if verisign icon showing move our icon up for better centering of the 2
// except for IE7 browser – it does not like this style
try {
var ieVersion = parseFloat(navigator.appVersion.split(“MSIE”)[1]);
if (alt_image && (alt_image.length > 0) && ieVersion != 7)
element.style.verticalAlign = “10%”;
}
catch(err){};
// apply custom element styles
this.updateElementStyle(element, this.updateImageStyle)
// update the click thru
var link = this.search.doc.getElementById(“L” + hash);
if (link)
{
link.href = click_thru;
link.id = link.id + “U” + score;
}
updated = true;
// add the alternate image if supplied BUT not on avg yahoo
if ((alt_image) &&
(alt_image.length > 0) &&
(!this.omitAltImage) &&
(this.VeriSignSplit != searchshield.VERISIGN_SPLIT_TESTB))
{
var vhash = hash.substring(hash.indexOf(“_”)+1);
// create a temporary link node
var tmp_node = {
element: element.parentNode,
href: altClick_thru,
hash: vhash + “VU” + score,
search: this.searchHash,
score: score
};
var altAnchor = this.addImage(tmp_node, alt_image, false);
if (altAnchor && altAnchor.firstChild)
{
altAnchor.firstChild.setAttribute(“onmouseover”, “”);
altAnchor.href = altClick_thru;
}
}
}
if (updated != false)
{
this.resizeFrame(frameElem);
return true;
}
return false;
};
searchshield.SearchEngine.prototype.updateElementStyle = function (element, elementStyle)
{
if (elementStyle)
{ // a NULL attribte value will unset it
for(attr in elementStyle)
{
try {
if (element.style.setAttribute)
element.style.setAttribute(attr, elementStyle[attr]);
else
element.style[attr] = elementStyle[attr];
} catch(err){}
}
}
};
searchshield.SearchEngine.prototype.resizeFrame = function (frameElem)
{ // resize frame to prevent unwanted scrolling after inserting verdicts
// ignore inline and non-frame engines
if ((this.type == ‘inline’) || (!this.processFrames))
return;
// ensure all required elements are available
if ((frameElem == null) || (frameElem.style == null) || (frameElem.contentWindow == null))
return;
// if frame is scrolling vertically then resize
var frameHeight = parseInt(frameElem.style.height, 10);
if (!isNaN(frameHeight) && (frameHeight < frameElem.contentWindow.document.body.scrollHeight))
frameElem.style.height = frameElem.contentWindow.document.body.scrollHeight + 'px';
return;
};
searchshield.SearchEngine.prototype.getImgElement = function (element)
{ // return an xpl img element associated with a given element
if (element == null)
return null;
// go up the parent tree looking for a header or div
while ( (element.parentNode != null) &&
(element.tagName.charAt(0) != "H") &&
(element.tagName.charAt(0) != "D") &&
(element.tagName.charAt(0) != "T") )
{
element = element.parentNode;
}
// if all the way to the top, nothing
if ((element.tagName == "HTML") || (element == null))
return null;
// get image tags, if none we are done
var imgTags = element.getElementsByTagName("IMG");