| Current Path : /home/digilove/www/administrator/components/com_jmap/js/ |
| Current File : /home/digilove/www/administrator/components/com_jmap/js/seospider.js |
/**
* Spider client for SEO reports and issues reporter
*
* @package JMAP::SEOSPIDER::administrator::components::com_jmap
* @subpackage js
* @author Joomla! Extensions Store
* @copyright (C) 2021 Joomla! Extensions Store
* @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
*/
//'use strict';
(function($) {
var SeoSpider = function() {
/**
* Target sitemap link
*
* @access private
* @var String
*/
var targetSitemapLink = null;
/**
* Promises array
*
* @access private
* @var Array
*/
var promisesCollection = new Array();
/**
* Timings of each promise start and resolve
*
* @access private
* @var Array
*/
var promisesCollectionTimings = new Array();
/**
* Titles collection
*
* @access private
* @var Object
*/
var titlesCollection = {};
/**
* Descriptions collection
*
* @access private
* @var Object
*/
var descriptionsCollection = {};
/**
* Timeout reference for arrows
*
* @access private
* @var Object
*/
var arrowsTimeout = null;
/**
* Mapping structure for performance/rating review of page load time
*
* @access private
* @var Object
*/
var mappingRatings = {
level1 : {
labelcolor : 'success',
tooltip : COM_JMAP_SEOSPIDER_PAGELOAD_FAST,
lowerlimit : 0,
upperlimit : 3
},
level4 : {
labelcolor : 'warning',
tooltip : COM_JMAP_SEOSPIDER_PAGELOAD_AVERAGE,
lowerlimit : 3,
upperlimit : 6
},
level6 : {
labelcolor : 'danger',
tooltip : COM_JMAP_SEOSPIDER_PAGELOAD_SLOW,
lowerlimit : 6,
upperlimit : 99999
}
}
/**
* Parse url to grab query string params to post to server side for sitemap generation
*
* @access private
* @return Object
*/
var parseURL = function(url) {
var a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function(){
var ret = {},
seg = a.search.replace(/^\?/,'').split('&'),
len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/')
};
}
/**
* Generate an unique hash for a title or description string in input
*
* @access private
* @return Object
*/
var generateHash = function(string) {
var hash = 0, i, chr, len;
if (string.length == 0) return hash;
for (i = 0, len = string.length; i < len; i++) {
chr = string.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
/**
* Register user events for interface controls
*
* @access private
* @param Boolean initialize
* @return Void
*/
var addListeners = function(initialize) {
// Start the precaching process, first operation is enter the progress modal mode
$('a.jmap_seospider').on('click.seospider', function(jqEvent){
// Prevent click link default
jqEvent.preventDefault();
// Show striped progress started generation
showProgress(true, 50, 'striped', COM_JMAP_SEOSPIDER_STARTED_SITEMAP_GENERATION);
// Grab targeted sitemap link
targetSitemapLink = $(this).attr('href');
});
// Register form submit event
$('#adminForm ul.pagination-list li').filter(function(){
if($(this).hasClass('active') || $(this).hasClass('disabled')) {
return false;
}
return true;
}).on('click.seospider', function(jqEvent){
// Show striped progress started generation
showProgress(true, 100, 'striped', COM_JMAP_SEOSPIDER_CRAWLING_LINKS);
});
$('#adminForm select[class!=noanalyzer]').on('change.seospider', function(jqEvent){
showProgress(true, 100, 'striped', COM_JMAP_SEOSPIDER_CRAWLING_LINKS);
});
$('#adminForm table.adminlist th a.hasTooltip').on('click.seospider', function(jqEvent){
// Show striped progress started generation
showProgress(true, 100, 'striped', COM_JMAP_SEOSPIDER_CRAWLING_LINKS);
});
// Live event binding only once on initialize, avoid repeated handlers and executed callbacks
if(initialize) {
// Live event binding for close button AKA stop process
$(document).on('click.seospider', 'label.closeprecaching', function(jqEvent){
$('#seospider_process').modal('hide');
});
}
// Append a dialog with links list detail
$('div[data-bind="{title-duplicates}"], div[data-bind="{desc-duplicates}"]').on('click.seospider', function(jqEvent){
// Ensure to not execute noduplicates badge
if($(this).hasClass('noduplicates')) {
return false;
}
// Remove any previous instance
$('#details_dialog').remove();
var dialogTitle = '';
var dialogContents = new Array();
var thisLinkToSkip = $(this).data('link');
var thisTitleHash = $(this).data('titlehash');
var thisDescriptionHash = $(this).data('descriptionhash');
var didascaly = '';
// Determine the type of the dialog and title
var thisBind = $(this).data('bind');
switch(thisBind) {
case '{title-duplicates}':
dialogTitle = COM_JMAP_SEOSPIDER_DIALOG_DUPLICATES_TITLE;
dialogContents = titlesCollection[thisTitleHash];
didascaly = COM_JMAP_SEOSPIDER_TITLE_DETAILS + $(this).parents('tr').find('div[data-bind="{title}"] div.seospider_textlabel').text();
break;
case '{desc-duplicates}':
dialogTitle = COM_JMAP_SEOSPIDER_DIALOG_DUPLICATES_DESCRIPTION;
dialogContents = descriptionsCollection[thisDescriptionHash];
didascaly = COM_JMAP_SEOSPIDER_DESCRIPTION_DETAILS + $(this).parents('tr').find('div[data-bind="{desc}"] div.seospider_textlabel').text();
break;
}
showDuplicatesDetails(dialogTitle, dialogContents, didascaly, thisLinkToSkip);
});
// Append a dialog with links list detail
$('div.trigger_content_analysis').on('click.seospider', function(jqEvent){
// Remove any previous instance
$('#analysis_dialog').remove();
var linkToAnalyze = $(this).data('link');
showContentAnalysis(linkToAnalyze);
});
// Append a dialog with headings editing
if(typeof(jmap_overrideheadings) !== 'undefined' && parseInt(jmap_overrideheadings) == 1) {
$(document).on('click.seospider', 'div[data-bind="{h1}"],div[data-bind="{h2}"],div[data-bind="{h3}"]', function(jqEvent){
// Only open headings dialog when really needed
if(!$(jqEvent.target).hasClass('seospider-headings')) {
return false;
}
// Remove any previous instance
$('#headings_dialog').remove();
var linkToAnalyze = $(this).parents('tr').data('link');
var headingTag = $(this).data('bind').replace(/[\{\}]/gi, '');
showHeadingsDialog(linkToAnalyze, headingTag);
});
}
// Append a dialog with canonical editing
if(typeof(jmap_overridecanonical) !== 'undefined' && parseInt(jmap_overridecanonical) == 1) {
$(document).on('click.seospider', 'div[data-bind="{canonical}"]', function(jqEvent){
// Remove any previous instance
$('#canonical_dialog').remove();
var linkToAnalyze = $(this).parents('tr').data('link');
showCanonicalDialog(linkToAnalyze);
});
}
// Bind the save/delete buttons for headings
$(document).on('click.seospider', '#save_heading, #delete_heading', function(jqEvent){
var buttonAction = $(this).data('action');
var buttonHeading = $(this).data('heading');
saveDataStatus(buttonAction, buttonHeading);
});
// Bind the save/delete buttons for canonical
$(document).on('click.seospider', '#save_canonical, #delete_canonical', function(jqEvent){
var buttonAction = $(this).data('action');
saveCanonicalDataStatus(buttonAction);
});
// Bind the start analysis button
$(document).on('click.seospider', '#start_analysis', function(jqEvent){
$('#focus_keyword').removeClass('error');
// Validate the required field
var focusKeyword = $('#focus_keyword').val();
if(!focusKeyword) {
$('#focus_keyword').addClass('error').focus();
return false;
}
// Set the running interface
$('span.seospider-cogicon', this).addClass('running');
$('span.seospider-labelicon-start', this).text(COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_STARTED);
var linkToAnalyze = $('#analysis_dialog').data('linktoanalyze');
// Start here the real analysis process
startLinkAnalysis(linkToAnalyze, focusKeyword);
});
$(document).on('keyup.seospider', '#focus_keyword, #override_heading_content, #override_canonical_content', function(jqEvent){
$(this).removeClass('error');
});
$(document).on('keyup.seospider', '#override_heading_content, #override_canonical_content', function(jqEvent){
$(this).next('ul.seospider_validation_errorlist').remove();
});
// Closer dialog button
$(document).on('click.seospider', 'label.closedialog', function(jqEvent){
$(this).parents('#details_dialog, #analysis_dialog, #headings_dialog, #canonical_dialog').remove();
});
// Link duplicate with scroller
$(document).on('click.seospider', 'li.seospider_duplicate a, a.seospider_duplicate', function(jqEvent){
// Reset timeout if any
if(typeof(arrowsTimeout) !== 'undefined') {
clearTimeout(arrowsTimeout);
}
var anchorTarget = $(this).attr('href');
var elementTarget = $('a[data-role="link"][href="' + anchorTarget + '"]');
if(elementTarget.length) {
$('html, body').animate({
scrollTop: elementTarget.offset().top - 95
}, 500);
}
// Append an indicator arrow
$(elementTarget).next('span.seospider_indicator').remove();
$(elementTarget).after('<span class="seospider_indicator glyphicon glyphicon-circle-arrow-left"></span>');
arrowsTimeout = setTimeout(function(){
$('span.seospider_indicator').remove();
}, 3500);
return false;
});
};
/**
* Show progress dialog bar with informations about the ongoing started process
*
* @access private
* @return Void
*/
var showProgress = function(isNew, percentage, type, status, classColor) {
// No progress process injected
if(isNew) {
// Show progress
var progressBar = '<div class="progress progress-' + type + ' active">' +
'<div id="progress_bar" class="progress-bar" role="progressbar" aria-valuenow="' + percentage + '" aria-valuemin="0" aria-valuemax="100">' +
'<span class="sr-only"></span>' +
'</div>' +
'</div>';
// Build modal dialog
var modalDialog = '<div class="modal fade" id="seospider_process" tabindex="-1" role="dialog" aria-labelledby="progressModal" aria-hidden="true">' +
'<div class="modal-dialog">' +
'<div class="modal-content">' +
'<div class="modal-header">' +
'<h4 class="modal-title">' + COM_JMAP_SEOSPIDER_TITLE + '</h4>' +
'<label class="closeprecaching glyphicon glyphicon-remove-circle"></label>' +
'<p class="modal-subtitle">' + COM_JMAP_SEOSPIDER_PROCESS_RUNNING + '</p>' +
'</div>' +
'<div class="modal-body">' +
'<p>' + progressBar + '</p>' +
'<p id="progress_info">' + status + '</p>' +
'</div>' +
'<div class="modal-footer">' +
'</div>' +
'</div><!-- /.modal-content -->' +
'</div><!-- /.modal-dialog -->' +
'</div>';
// Inject elements into content body
$('body').append(modalDialog);
// Setup modal
var modalOptions = {
backdrop:'static'
};
$('#seospider_process').modal(modalOptions);
// Async event progress showed and styling
$('#seospider_process').on('shown.bs.modal', function(event) {
$('#seospider_process div.modal-body').css({'width':'90%', 'margin':'auto'});
$('#progress_bar').css({'width':percentage + '%'});
// Start AJAX GET request for sitemap generation in the cache folder
startSitemapCaching(targetSitemapLink);
});
// Remove backdrop after removing DOM modal
$('#seospider_process').on('hidden.bs.modal',function(jqEvent){
$('.modal-backdrop').remove();
$(this).remove();
// Redirect to MVC core cpanel, discard seospider
window.location.href = 'index.php?option=com_jmap&task=cpanel.display'
});
} else {
// Refresh only status, progress and text
$('#progress_bar').addClass(classColor)
.css({'width':percentage + '%'});
$('#progress_bar').parent().removeClass('progress-normal progress-striped')
.addClass('progress-' + type);
$('#progress_info').html(status);
// An error has been detected, so auto close process and progress bar
if(classColor == 'progress-bar-danger') {
setTimeout(function(){
$('#seospider_process').modal('hide');
}, 3500);
}
}
}
/**
* The first operation is to generate and precache the requested sitemap and links
*
* @access private
* @param String targetSitemapLink
* @return Void
*/
var startSitemapCaching = function(targetSitemapLink) {
// No ajax request if no control panel generation in 2 steps
if(!targetSitemapLink) {
return;
}
// Request JSON2JSON
var dataSourcePromise = $.Deferred(function(defer) {
$.ajax({
type : "GET",
url : targetSitemapLink,
dataType : 'json',
context : this,
data: {'seospiderjsclient' : true}
}).done(function(data, textStatus, jqXHR) {
if(!data.result) {
// Error found
defer.reject(COM_JMAP_SEOSPIDER_ERROR_STORING_FILE, textStatus);
return false;
}
// Check response all went well
if(data.result) {
defer.resolve();
}
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
defer.reject('-' + genericStatus + '- ' + errorThrown);
});
}).promise();
dataSourcePromise.then(function() {
// Update process status, we started
showProgress(false, 100, 'striped', COM_JMAP_SEOSPIDER_GENERATION_COMPLETE, 'progress-normal');
// Parse sitemap parameters
var sitemapParams = parseURL(targetSitemapLink).params;
var sitemapLang = sitemapParams.lang ? '&sitemaplang=' + sitemapParams.lang : '';
var sitemapDataset = sitemapParams.dataset ? '&sitemapdataset=' + sitemapParams.dataset : '';
var sitemapMenuID = sitemapParams.Itemid ? '&sitemapitemid=' + sitemapParams.Itemid : '';
// Redirect to MVC core
window.location.href = 'index.php?option=com_jmap&task=seospider.display&jsclient=1' + sitemapLang + sitemapDataset + sitemapMenuID;
}, function(errorText, error) {
// Do stuff and exit
showProgress(false, 100, 'normal', errorText, 'progress-bar-danger');
});
};
/**
* Show the duplicated links dialog details with scroll interaction
*
* @access private
* @return Void
*/
var showDuplicatesDetails = function(modalTitle, modalContents, didascalyFooter, linkToSkip) {
var contentsString = '';
if(modalContents.length) {
$.each(modalContents, function(index, value){
value = decodeURIComponent(value);
if(value == linkToSkip) {
return true;
}
contentsString += '<li class="seospider_duplicate"><a href="' + value + '">' + value + '</a> <label class="glyphicon glyphicon-resize-vertical"></label></li>';
});
}
// Build modal dialog
var detailsDialog = '<div id="details_dialog" class="panel panel-primary">' +
'<div class="panel-heading">' +
'<h3 class="panel-title">' + modalTitle + '</h3>' +
'<label class="closedialog glyphicon glyphicon-remove-circle"></label>' +
'</div>' +
'<div class="panel-body">' +
'<ul class="seospider_duplicate">' + contentsString + '</ul>' +
'</div>' +
'<div class="panel-footer">' +
didascalyFooter +
'<div>' + COM_JMAP_SEOSPIDER_SELECTED_LINK_DETAILS +
'<a class="seospider_duplicate" href="' + linkToSkip + '">' + linkToSkip + '</a>' +
'</div>' +
'</div>' +
'</div>';
// Inject elements into content body
$('body').append(detailsDialog);
// Bind the draggable feature
$('#details_dialog').draggable({
handle: 'div.panel-heading'
});
}
/**
* Show the SEO Content Analysis dialog
*
* @access private
* @return Void
*/
var showContentAnalysis = function(linkToAnalyze) {
var contentsString = '';
// Build modal dialog
var analysisDialog = '<div id="analysis_dialog" class="panel panel-primary">' +
'<div class="panel-heading">' +
'<h3 class="panel-title">' + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_DIALOG_TITLE + '</h3>' +
'<a class="seospider_analyzed_link badge" target="_blank" href="' + linkToAnalyze + '">' + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_LINK + linkToAnalyze + '</a>' +
'<label class="closedialog glyphicon glyphicon-remove-circle"></label>' +
'</div>' +
'<div class="panel-body">' +
'<label class="label label-primary analysis-labels">' + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_FOCUS_KEYWORD + '</label>' +
'<input id="focus_keyword" class="analysis-input" type="text" value="" placeholder="' + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_CHOOSE_KEYWORD + '"/>' +
'<button id="start_analysis" class="btn btn-success active">' +
'<span class="seospider-cogicon"></span><span class="seospider-labelicon-start">' + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_START + '</span>' +
'</button>' +
'</div>' +
'<div class="panel-footer">' +
'<div id="pagescore">' + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_DIALOG_FOOTER + '</div>' +
'</div>' +
'</div>';
// Inject elements into content body
$('body').append(analysisDialog);
$('#analysis_dialog').data('linktoanalyze', linkToAnalyze);
// Bind the draggable feature
$('#analysis_dialog').draggable({
handle: 'div.panel-heading'
});
}
/**
* Show the headings dialog
*
* @access private
* @param String linkToAnalyze
* @param String hTag
* @return Void
*/
var showHeadingsDialog = function(linkToAnalyze, hTag) {
// Async request current real page
var pagePromise = $.Deferred(function(defer) {
$.ajax({
type : "GET",
url : linkToAnalyze,
}).done(function(data, textStatus, jqXHR) {
// Check response HTTP status code
defer.resolve(data, jqXHR.status);
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
defer.resolve(null, jqXHR.status);
});
}).promise();
pagePromise.then(function(responseData, status) {
// Fetch the Hx tag and compile the current textarea
// Set the parsed wrapped set
var responseDataWrappedSet = $(responseData.trim());
var headingsArray = responseDataWrappedSet.find(hTag);
// Get the text of first Hx heading found
var headingText = $(headingsArray[0]).text().trim();
$('#original_heading_content').text(headingText);
}).always(function(){
$('div.headings-original span.seospider-cogicon').removeClass('running');
});
// Fetch the Hx override for this URL and compile the override textarea
// Object to send to server
var ajaxparams = {
idtask : 'fetchHeadingOverride',
param: {linkurl:linkToAnalyze, headingtag: hTag}
};
// Unique param 'data'
var uniqueParam = JSON.stringify(ajaxparams);
var serverModelPromise = $.Deferred(function(defer) {
$.ajax({
type : "POST",
url: "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json",
dataType : 'json',
context : this,
data : {
data : uniqueParam
}
}).done(function(data, textStatus, jqXHR) {
if(!data.result) {
// Error found
defer.reject(data.exception_message, textStatus);
return false;
}
// Check response all went well
if(data.result) {
defer.resolve(data.headingtext, data);
}
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
defer.reject('-' + genericStatus + '- ' + errorThrown);
});
}).promise();
serverModelPromise.then(function(message, dataResponse) {
// Fetch the Hx override for this URL and compile the override textarea
$('#override_heading_content').val(message);
// Enable the delete btn only if there is an override
if(message) {
$('#delete_heading').removeAttr('disabled');
}
}).always(function(message){
$('#override_heading_content').removeAttr('disabled');
$('div.headings-override span.seospider-cogicon').removeClass('running');
$('#save_heading').removeAttr('disabled').attr('data-heading', hTag);
$('#delete_heading').attr('data-heading', hTag);
});
var contentsString = '';
// Build modal dialog
var headingsDialog = '<div id="headings_dialog" class="panel panel-primary">' +
'<div class="panel-heading">' +
'<h3 class="panel-title">' + COM_JMAP_SEOSPIDER_HEADINGS_DIALOG_TITLE + hTag.toUpperCase() + '</h3>' +
'<a class="seospider_analyzed_link badge" target="_blank" href="' + linkToAnalyze + '">' + COM_JMAP_SEOSPIDER_HEADINGS_LINK + linkToAnalyze + '</a>' +
'<label class="closedialog glyphicon glyphicon-remove-circle"></label>' +
'</div>' +
'<div class="panel-body">' +
'<div class="headings-original">' +
'<label class="label label-primary headings-labels"><span class="seospider-cogicon running"></span><span class="seospider-label-desc">' + COM_JMAP_SEOSPIDER_HEADINGS_ORIGINAL_HEADING + '</span></label>' +
'<textarea id="original_heading_content" class="headings-content" readonly></textarea>' +
'</div>' +
'<div class="headings-override">' +
'<label class="label label-primary headings-labels"><span class="seospider-cogicon running"></span><span class="seospider-label-desc">' + COM_JMAP_SEOSPIDER_HEADINGS_OVERRIDE_HEADING + '</span></label>' +
'<textarea id="override_heading_content" class="headings-content" disabled></textarea>' +
'</div>' +
'<button id="save_heading" class="btn btn-primary active" data-action="saveHeading" disabled>' +
'<span class="glyphicon glyphicon-floppy-disk"></span> <span class="seospider-labelicon-start">' + COM_JMAP_SEOSPIDER_HEADINGS_SAVE + '</span>' +
'</button>' +
'<button id="delete_heading" class="btn btn-danger active" data-action="deleteHeading" disabled>' +
'<span class="glyphicon glyphicon-remove-circle"></span> <span class="seospider-labelicon-start">' + COM_JMAP_SEOSPIDER_HEADINGS_DELETE + '</span>' +
'</button>' +
'</div>' +
'<div class="panel-footer">' +
'<div id="headings_opmessage"></div>' +
'</div>' +
'</div>';
// Inject elements into content body
$('body').append(headingsDialog);
$('#headings_dialog').data('linktoanalyze', linkToAnalyze);
// Bind the draggable feature
$('#headings_dialog').draggable({
handle: 'div.panel-heading'
});
}
/**
* Show the canonical dialog
*
* @access private
* @param String linkToAnalyze
* @return Void
*/
var showCanonicalDialog = function(linkToAnalyze) {
// Async request current real page
var pagePromise = $.Deferred(function(defer) {
$.ajax({
type : "GET",
url : linkToAnalyze,
}).done(function(data, textStatus, jqXHR) {
// Check response HTTP status code
defer.resolve(data, jqXHR.status);
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
defer.resolve(null, jqXHR.status);
});
}).promise();
pagePromise.then(function(responseData, status) {
// Fetch the canonical tag and compile the current input field
// Set the parsed wrapped set
var responseDataWrappedSet = $(responseData.trim());
var canonical = responseDataWrappedSet.filter('link[rel=canonical]');
// Found a canonical tag?
if(canonical.length) {
$('#original_canonical_content').val(decodeURIComponent(canonical.attr('href')));
}
}).always(function(){
$('div.canonical-original span.seospider-cogicon').removeClass('running');
});
// Fetch the canonical override for this URL and compile the override input field
// Object to send to server
var ajaxparams = {
idtask : 'fetchCanonicalOverride',
param: {linkurl:linkToAnalyze}
};
// Unique param 'data'
var uniqueParam = JSON.stringify(ajaxparams);
var serverModelPromise = $.Deferred(function(defer) {
$.ajax({
type : "POST",
url: "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json",
dataType : 'json',
context : this,
data : {
data : uniqueParam
}
}).done(function(data, textStatus, jqXHR) {
if(!data.result) {
// Error found
defer.reject(data.exception_message, textStatus);
return false;
}
// Check response all went well
if(data.result) {
defer.resolve(data.canonicaltext, data);
}
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
defer.reject('-' + genericStatus + '- ' + errorThrown);
});
}).promise();
serverModelPromise.then(function(message, dataResponse) {
// Fetch the canonical override for this URL and compile the override input field
$('#override_canonical_content').val(message);
// Enable the delete btn only if there is an override
if(message) {
$('#delete_canonical').removeAttr('disabled');
}
}).always(function(message){
$('#override_canonical_content').removeAttr('disabled');
$('div.canonical-override span.seospider-cogicon').removeClass('running');
$('#save_canonical').removeAttr('disabled');
});
var contentsString = '';
// Build modal dialog
var canonicalDialog = '<div id="canonical_dialog" class="panel panel-primary">' +
'<div class="panel-heading">' +
'<h3 class="panel-title">' + COM_JMAP_SEOSPIDER_CANONICAL_DIALOG_TITLE + '</h3>' +
'<a class="seospider_analyzed_link badge" target="_blank" href="' + linkToAnalyze + '">' + COM_JMAP_SEOSPIDER_CANONICAL_LINK + linkToAnalyze + '</a>' +
'<label class="closedialog glyphicon glyphicon-remove-circle"></label>' +
'</div>' +
'<div class="panel-body">' +
'<div class="canonical-original">' +
'<label class="label label-primary canonical-labels"><span class="seospider-cogicon running"></span><span class="seospider-label-desc">' + COM_JMAP_SEOSPIDER_CANONICAL_ORIGINAL_HEADING + '</span></label>' +
'<input type="text" id="original_canonical_content" class="canonical-content" readonly />' +
'</div>' +
'<div class="canonical-override">' +
'<label class="label label-primary canonical-labels"><span class="seospider-cogicon running"></span><span class="seospider-label-desc">' + COM_JMAP_SEOSPIDER_CANONICAL_OVERRIDE_HEADING + '</span></label>' +
'<input type="text" id="override_canonical_content" class="canonical-content" disabled />' +
'</div>' +
'<button id="save_canonical" class="btn btn-primary active" data-action="saveCanonical" disabled>' +
'<span class="glyphicon glyphicon-floppy-disk"></span> <span class="seospider-labelicon-start">' + COM_JMAP_SEOSPIDER_CANONICAL_SAVE + '</span>' +
'</button>' +
'<button id="delete_canonical" class="btn btn-danger active" data-action="deleteCanonical" disabled>' +
'<span class="glyphicon glyphicon-remove-circle"></span> <span class="seospider-labelicon-start">' + COM_JMAP_SEOSPIDER_CANONICAL_DELETE + '</span>' +
'</button>' +
'</div>' +
'<div class="panel-footer">' +
'<div id="canonical_opmessage"></div>' +
'</div>' +
'</div>';
// Inject elements into content body
$('body').append(canonicalDialog);
$('#canonical_dialog').data('linktoanalyze', linkToAnalyze);
// Bind the draggable feature
$('#canonical_dialog').draggable({
handle: 'div.panel-heading'
});
}
/**
* Process the asyncronous analysis of links showed in the SeoSpider list
* It performs parallel async requests for each link evaluating the HTTP status code in response and acting accordingly
*
* @access private
* @return Void
*/
var startLinksCrawling = function() {
// Retrieve all the links to analyze on page
var linksToAnalyze = $('a[data-role=link]');
var successIcon = ' src="' + jmap_baseURI + 'administrator/components/com_jmap/images/icon-16-tick.png"/>';
var failureIcon = ' src="' + jmap_baseURI + 'administrator/components/com_jmap/images/publish_x.png"/>';
// Due to the page loading time feature, force the crawler delay to at least 500ms
if(jmap_crawlerDelay < 500) {
jmap_crawlerDelay = 500;
}
// No ajax request if no links to analyze
if(!linksToAnalyze.length) {
return;
}
$.each(linksToAnalyze, function(index, link){
var targetCrawledLink = $('a[data-role="link"]').get(index);
var targetStatus = $('div[data-bind="{status}"]').get(index);
var targetTitle = $('div[data-bind="{title}"]').get(index);
var targetDesc = $('div[data-bind="{desc}"]').get(index);
var targetH1 = $('div[data-bind="{h1}"]').get(index);
var targetH2 = $('div[data-bind="{h2}"]').get(index);
var targetH3 = $('div[data-bind="{h3}"]').get(index);
var targetCanonical = $('div[data-bind="{canonical}"]').get(index);
var targetAnalysis = $('div.trigger_content_analysis').get(index);
var targetPageLoad = $('div[data-bind="{pageload}"]').get(index);
promisesCollection[index] = $.Deferred(function(defer) {
setTimeout(function(){
// Save the start time of this async promise
var beforeDateObject = new Date()
promisesCollectionTimings[index] = beforeDateObject.getTime();
$.ajax({
type : "GET",
url : $(link).attr('href'),
}).done(function(data, textStatus, jqXHR) {
// Check response HTTP status code
defer.resolve(data, jqXHR.status);
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
defer.resolve(null, jqXHR.status);
});
}, index * jmap_crawlerDelay);
}).promise();
promisesCollection[index].then(function(responseData, status) {
// STEP 1 - Status validation and reporting
if(status == 200) {
$(targetStatus).html('<span class="badge badge-success seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_LINKVALID + '">' + status + '</span>');
} else {
$(targetStatus).html('<span class="badge badge-danger seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_LINK_NOVALID + '">' + status + '</span>');
$(targetTitle).html('-');
$(targetDesc).html('-');
$(targetH1).html('-');
$(targetH2).html('-');
$(targetH3).html('-');
$(targetCanonical).html('-');
$(targetAnalysis).remove();
$(targetPageLoad).html('-');
return;
}
// Set the parsed wrapped set
var responseDataWrappedSet = $(responseData.trim());
// STEP 2 - Title retrieval and reporting
var title = responseDataWrappedSet.filter('title').text().trim() || '-';
var titleBadge = '';
// Manage title validity
if(title && title != '-') {
switch(true) {
case (title.length < 50):
titleBadge = '<div class="badge badge-warning seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_TITLE_TOOSHORT_DESC + '">' + COM_JMAP_SEOSPIDER_TITLE_TOOSHORT + '</div>';
break;
case (title.length > 90):
titleBadge = '<div class="badge badge-warning seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_TITLE_TOOLONG_DESC + '">' + COM_JMAP_SEOSPIDER_TITLE_TOOLONG + '</div>';
break;
}
} else {
titleBadge = '<div class="badge badge-danger seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_TITLE_MISSING_DESC + '">' + COM_JMAP_SEOSPIDER_TITLE_MISSING + '</div>';
}
$(targetTitle).html(titleBadge + '<div class="seospider_textlabel">' + title + '</div>');
linksToAnalyze[index]['seospider_title'] = title;
// STEP 3 - Description retrieval and reporting
var description = responseDataWrappedSet.filter('meta[name=description]').attr('content') || '';
var descriptionBadge = '';
description = description.trim();
description = description || '-';
// Manage description validity
if(description && description != '-') {
switch(true) {
case (description.length < 130):
descriptionBadge = '<div class="badge badge-warning seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_DESCRIPTION_TOOSHORT_DESC + '">' + COM_JMAP_SEOSPIDER_DESCRIPTION_TOOSHORT + '</div>';
break;
case (description.length > 180):
descriptionBadge = '<div class="badge badge-warning seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_DESCRIPTION_TOOLONG_DESC + '">' + COM_JMAP_SEOSPIDER_DESCRIPTION_TOOLONG + '</div>';
break;
}
} else {
descriptionBadge = '<div class="badge badge-danger seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_DESCRIPTION_MISSING_DESC + '">' + COM_JMAP_SEOSPIDER_DESCRIPTION_MISSING + '</div>';
}
$(targetDesc).html(descriptionBadge + '<div class="seospider_textlabel">' + description + '</div>');
linksToAnalyze[index]['seospider_description'] = description;
// STEP 4 - Headers retrieval and reporting
var H1Array = new Array();
var isHeadingH1Override = '';
var isHeadingH1OverrideText = '';
$.each(responseDataWrappedSet.find('h1'), function (index, headerTag) {
// Mark as an override heading
if(!isHeadingH1Override) {
isHeadingH1Override = $(headerTag).data('jmap-heading-override') ? ' seospider-headings-override' : '';
isHeadingH1OverrideText = isHeadingH1Override ? COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE_ACTIVE : '';
}
// If the first heading is overriden mark it as bold
if(index == 0 && isHeadingH1Override) {
H1Array[index] = '<b class="badge badge-warning seospider-headings">' + $(headerTag).text() + '</b>';
} else {
H1Array[index] = $(headerTag).text();
}
});
var H1 = H1Array.join(' | ') || '-';
if(jmap_overrideheadings && H1 != '-') {
H1 = '<span class="seospider seospider-headings hasTooltip' + isHeadingH1Override + '" title="' + COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE + isHeadingH1OverrideText +'">' + H1 + '</span>';
}
var H2Array = new Array();
var isHeadingH2Override = '';
var isHeadingH2OverrideText = '';
$.each(responseDataWrappedSet.find('h2'), function (index, headerTag) {
// Mark as an override heading
if(!isHeadingH2Override) {
isHeadingH2Override = $(headerTag).data('jmap-heading-override') ? ' seospider-headings-override' : '';
isHeadingH2OverrideText = isHeadingH2Override ? COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE_ACTIVE : '';
}
// If the first heading is overriden mark it as bold
if(index == 0 && isHeadingH2Override) {
H2Array[index] = '<b class="badge badge-warning seospider-headings">' + $(headerTag).text() + '</b>';
} else {
H2Array[index] = $(headerTag).text();
}
});
var H2 = H2Array.join(' | ') || '-';
if(jmap_overrideheadings && H2 != '-') {
H2 = '<span class="seospider seospider-headings hasTooltip' + isHeadingH2Override + '" title="' + COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE + isHeadingH2OverrideText + '">' + H2 + '</span>';
}
var H3Array = new Array();
var isHeadingH3Override = '';
var isHeadingH3OverrideText = '';
$.each(responseDataWrappedSet.find('h3'), function (index, headerTag) {
// Mark as an override heading
if(!isHeadingH3Override) {
isHeadingH3Override = $(headerTag).data('jmap-heading-override') ? ' seospider-headings-override' : '';
isHeadingH3OverrideText = isHeadingH3Override ? COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE_ACTIVE : '';
}
// If the first heading is overriden mark it as bold
if(index == 0 && isHeadingH3Override) {
H3Array[index] = '<b class="badge badge-warning seospider-headings">' + $(headerTag).text() + '</b>';
} else {
H3Array[index] = $(headerTag).text();
}
});
var H3 = H3Array.join(' | ') || '-';
if(jmap_overrideheadings && H3 != '-') {
H3 = '<span class="seospider seospider-headings hasTooltip' + isHeadingH3Override + '" title="' + COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE + isHeadingH3OverrideText + '">' + H3 + '</span>';
}
$(targetH1).html(H1);
$(targetH2).html(H2);
$(targetH3).html(H3);
// Report missing H1 and H2 tags
if(!H1Array.length && !H2Array.length) {
var noticeHeadersMissing = '<div class="badge badge-danger seospider hasTooltip" title="' + COM_JMAP_SEOSPIDER_HEADERS_MISSING_DESC + '">' + COM_JMAP_SEOSPIDER_HEADERS_MISSING + '</div>';
$(targetH1).html(noticeHeadersMissing);
$(targetH2).html(noticeHeadersMissing);
}
// STEP 5 - Canonical retrieval and reporting
var canonical = responseDataWrappedSet.filter('link[rel=canonical]');
var canonicalValue = canonical.attr('href') || '';
canonicalValue = decodeURIComponent(canonicalValue);
// Mark as an override canonical
if(jmap_overridecanonical) {
var isCanonicalOverrideEmpty = canonicalValue ? '' : ' seospider-canonical-override-empty';
var isCanonicalOverride = $(canonical).data('jmap-canonical-override') ? ' seospider-canonical-override' : '';
var isCanonicalOverrideText = isCanonicalOverride ? COM_JMAP_SEOSPIDER_CANONICAL_EDIT_OVERRIDE_ACTIVE : '';
$(targetCanonical).html('<span class="seospider seospider-canonical hasTooltip' + isCanonicalOverride + isCanonicalOverrideEmpty + '" title="' + COM_JMAP_SEOSPIDER_CANONICAL_EDIT_OVERRIDE + isCanonicalOverrideText + '">' + canonicalValue + '</span>');
} else {
$(targetCanonical).text(canonicalValue);
}
// STEP 6 - Count duplicated titles
if(title && title != '-') {
// Initialize as Array if not defined
if(typeof(titlesCollection[generateHash(title)]) === 'undefined'){
titlesCollection[generateHash(title)] = new Array();
}
titlesCollection[generateHash(title)].push(link);
}
// STEP 7 - Count duplicated descriptions
if(description && description != '-') {
// Initialize as Array if not defined
if(typeof(descriptionsCollection[generateHash(description)]) === 'undefined'){
descriptionsCollection[generateHash(description)] = new Array();
}
descriptionsCollection[generateHash(description)].push(link);
}
// STEP 8 - Check if the noindex directive is in place
var indexingDirective = responseDataWrappedSet.filter('meta[name=robots]').attr('content') || '';
if(indexingDirective) {
var isNoIndex = indexingDirective.indexOf('noindex') >= 0;
if(isNoIndex) {
$(targetCrawledLink).before('<div class="badge badge-warning seospider seospider-robots hasTooltip" title="' + COM_JMAP_SEOSPIDER_NOINDEX_DESC + '">' + COM_JMAP_SEOSPIDER_NOINDEX + '</div>');
}
var isNoFollow = indexingDirective.indexOf('nofollow') >= 0;
if(isNoFollow) {
$(targetCrawledLink).before('<div class="badge badge-warning seospider seospider-robots hasTooltip" title="' + COM_JMAP_SEOSPIDER_NOFOLLOW_DESC + '">' + COM_JMAP_SEOSPIDER_NOFOLLOW + '</div>');
}
}
// STEP 9 - Calculate the page loading time
// When defered object complete get new timing
var afterDateObject = new Date();
var afterTiming = afterDateObject.getTime();
// Find elapsed time to load site and format for users
var elapsedTiming = (afterTiming - promisesCollectionTimings[index]) / 1000;
// Detect speed level based on this test
$.each(mappingRatings, function(index, mappingObject) {
// Found the level based on this timing test
if(elapsedTiming > mappingObject.lowerlimit && elapsedTiming < mappingObject.upperlimit) {
$(targetPageLoad).html('<div title="' + mappingObject.tooltip + '" class="badge badge-' + mappingObject.labelcolor + ' seospider nodash hasLeftTooltip">' + elapsedTiming + 's</div>');
}
});
}).always(function(){
// Refresh tooltips
$('*.seospider.hasTooltip').tooltip({trigger:'hover', placement:'top', html : 1});
$('*.seospider.hasLeftTooltip').tooltip({trigger:'hover', placement:'left', container:'body'});
});
});
// When all promises are resolved start the async duplicated title/desc count
$.when.apply($, promisesCollection).then(function() {
// Start analysis for each link
$.each(linksToAnalyze, function(index, link){
// Find the target elements
var targetTitleDuplicates = $('div[data-bind="{title-duplicates}"]').get(index);
var targetDescDuplicates = $('div[data-bind="{desc-duplicates}"]').get(index);
// Calculate duplicates, 0 or -1 AKA no duplicates, > 0 AKA at least 1 duplicate
if(link['seospider_title']) {
var thisTitleHash = generateHash(link['seospider_title']);
var titlesDuplicates = 0;
if(typeof(titlesCollection[thisTitleHash]) !== 'undefined') {
titlesDuplicates = parseInt(titlesCollection[thisTitleHash].length) - 1;
}
titlesDuplicates = titlesDuplicates > 0 ? titlesDuplicates : 0;
// Find the correct badge class
var badgeTitleClass = titlesDuplicates > 0 ? 'badge-danger' : 'badge-success';
var badgeDetails = titlesDuplicates > 0 ? COM_JMAP_SEOSPIDER_OPEN_DETAILS : '';
// Assign badge
$(targetTitleDuplicates).html('<span class="badge ' + badgeTitleClass + ' seospider-duplicates hasTooltip" title="' + badgeDetails + '">' + titlesDuplicates + '</span>');
// Disable and exclude no duplicates badge
if(!titlesDuplicates) {
$(targetTitleDuplicates).addClass('noduplicates');
}
} else {
// Fallback
$(targetTitleDuplicates).html('-').addClass('noduplicates');
}
$(targetTitleDuplicates).attr('data-link', link.href);
$(targetTitleDuplicates).attr('data-titlehash', thisTitleHash);
if(link['seospider_description']) {
var thisDescriptionHash = generateHash(link['seospider_description']);
var descriptionsDuplicates = 0;
if(typeof(descriptionsCollection[thisDescriptionHash]) !== 'undefined') {
descriptionsDuplicates = parseInt(descriptionsCollection[thisDescriptionHash].length) - 1;
}
descriptionsDuplicates = descriptionsDuplicates > 0 ? descriptionsDuplicates : 0;
// Find the correct badge class
var badgeDescriptionClass = descriptionsDuplicates > 0 ? 'badge-danger' : 'badge-success';
var badgeDetails = descriptionsDuplicates > 0 ? COM_JMAP_SEOSPIDER_OPEN_DETAILS : '';
// Assign badge
$(targetDescDuplicates).html('<span class="badge ' + badgeDescriptionClass + ' seospider-duplicates hasTooltip" title="' + badgeDetails + '">' + descriptionsDuplicates + '</span>');
// Disable and exclude no duplicates badge
if(!descriptionsDuplicates) {
$(targetDescDuplicates).addClass('noduplicates');
}
} else {
// Fallback
$(targetDescDuplicates).html('-').addClass('noduplicates');
}
// Assign data hash
$(targetDescDuplicates).attr('data-link', link.href);
$(targetDescDuplicates).attr('data-descriptionhash', thisDescriptionHash);
});
// Refresh tooltips
$('*.seospider-duplicates.hasTooltip').tooltip({trigger:'hover', placement:'top', html : 1});
var seospiderTable = $('table.seospiderlist').clone();
$(seospiderTable).find('*.badge-success').wrap('<font COLOR="#FFFFFF"></font>').parents('td').attr({'BGCOLOR':'#3c763d'});
$(seospiderTable).find('*.badge-danger').wrap('<font COLOR="#FFFFFF"></font>').parents('td').attr({'BGCOLOR':'#d9534f'});
$(seospiderTable).find('*.badge-warning').wrap('<font COLOR="#FFFFFF"></font>').parents('td').attr({'BGCOLOR':'#f89406'});
$(seospiderTable).find('*.badge-warning:not(.nodash)').append(' - ');
$(seospiderTable).find('div[data-bind]').filter(function(index){
return $(this).text() === '-';
}).text(' ');
$(seospiderTable).find('br').remove();
var seospiderTableHtml = seospiderTable.html();
seospiderTableHtml = seospiderTableHtml.replace(/<a/g, '<div');
seospiderTableHtml = seospiderTableHtml.replace(/<\/a>/g, '</div>');
// Create a unique file name for download
var saveDate = new Date();
var saveDateYear = saveDate.getFullYear();
var saveDateMonth = parseInt(saveDate.getMonth()) + 1;
saveDateMonth = saveDateMonth < 10 ? '0' + saveDateMonth : saveDateMonth;
var saveDateDay = saveDate.getDate();
saveDateDay = saveDateDay < 10 ? '0' + saveDateDay : saveDateDay;
var saveDateHour = saveDate.getHours();
saveDateHour = saveDateHour < 10 ? '0' + saveDateHour : saveDateHour;
var saveDateMinute = saveDate.getMinutes();
saveDateMinute = saveDateMinute < 10 ? '0' + saveDateMinute : saveDateMinute;
var saveDateSecond = saveDate.getSeconds();
saveDateSecond = saveDateSecond < 10 ? '0' + saveDateSecond : saveDateSecond;
var filename = 'seospider_report_' +
saveDateYear + '-' +
saveDateMonth + '-' +
saveDateDay + '_' +
saveDateHour + ':' +
saveDateMinute + ':' +
saveDateSecond + '.xls';
$('#toolbar-arrow-down-2 button').remove();
$('#toolbar-arrow-down-2').append('<a class="btn btn-small"><span class="icon-arrow-down-2"></span>' + COM_JMAP_EXPORT_XLS + '</a>');
$('#toolbar-arrow-down-2 > a').attr('href', 'data:text/html;charset=utf-8,' + encodeURIComponent('<table>' + seospiderTableHtml + '</table>'))
.attr('download', filename);
$('#toolbar-upload button').removeAttr('disabled');
$('#toolbar-download button').removeAttr('disabled');
});
};
/**
* Process the asyncronous analysis of links showed in the SeoSpider list
* It performs parallel async requests for each link evaluating the HTTP status code in response and acting accordingly
*
* @access private
* @param String linkToAnalyze
* @return Void
*/
var startLinkAnalysis = function(linkToAnalyze, focusKeyword) {
// No ajax request if no valid link and keyword specified
if(!linkToAnalyze || !focusKeyword) {
// Reset the analyzing button
$('span.seospider-cogicon').removeClass('running');
$('span.seospider-labelicon-start').text(COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_START);
return;
}
// Clear previous results
$('label.label-results, ul.analysis-results, #pagescore_slider, #pagescore_numeric').remove();
// Focus keyword regular expression object
var reObject = new RegExp(focusKeyword, 'gi');
// Initialize semaphores
var semaphoreRed = '<div class="trigger_content_analysis_red"></div>';
var semaphoreYellow = '<div class="trigger_content_analysis_yellow"></div>';
var semaphoreGreen = '<div class="trigger_content_analysis_green"></div>';
// Init vars
var keywordRepetitionsInTags = 0;
var finalPageScore = 0;
var maxScore = 6;
// Async request
var analysisPromise = $.Deferred(function(defer) {
$.ajax({
type : "GET",
url : linkToAnalyze,
}).done(function(data, textStatus, jqXHR) {
// Check response HTTP status code
defer.resolve(data, jqXHR.status);
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
defer.resolve(null, jqXHR.status);
});
}).promise();
analysisPromise.then(function(responseData, status) {
// STEP 1 - Status validation and reporting
if(status == 200 && responseData) {
// All went ok, now analyze and report data
$('#analysis_dialog div.panel-body').append('<label class="label label-primary analysis-labels label-results">' + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_RESULTS + '</label>');
var ulResults = $('<ul/>');
ulResults.addClass('analysis-results');
// STEP 1 - Set the parsed wrapped set
var responseDataWrappedSet = $(responseData.trim());
// STEP 2 - Title retrieval and reporting
var title = responseDataWrappedSet.filter('title').text().trim() || '';
var keywordInTitle = title.match(reObject);
if(keywordInTitle) {
var titleResult = semaphoreGreen + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_TITLE_KEYWORD;
keywordRepetitionsInTags += keywordInTitle.length;
finalPageScore += 1;
} else {
var titleResult = semaphoreRed + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_TITLE_NOKEYWORD;
}
ulResults.append('<li>' + titleResult + '</li>');
// STEP 3 - Description retrieval and reporting
var description = responseDataWrappedSet.filter('meta[name=description]').attr('content') || '';
var keywordInDescription = description.match(reObject);
if(keywordInDescription) {
var descriptionResult = semaphoreGreen + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_DESCRIPTION_KEYWORD;
keywordRepetitionsInTags += keywordInDescription.length;
finalPageScore += 1;
} else {
var descriptionResult = semaphoreRed + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_DESCRIPTION_NOKEYWORD;
}
ulResults.append('<li>' + descriptionResult + '</li>');
// STEP 4 - Headers retrieval and reporting
var H1Array = new Array();
$.each(responseDataWrappedSet.find('h1'), function (index, headerTag) {
H1Array[index] = $(headerTag).text();
});
var H1 = H1Array.join(' ') || '';
var keywordInH1 = H1.match(reObject);
var H2Array = new Array();
$.each(responseDataWrappedSet.find('h2'), function (index, headerTag) {
H2Array[index] = $(headerTag).text();
});
var H2 = H2Array.join(' ') || '';
var keywordInH2 = H2.match(reObject);
var H3Array = new Array();
$.each(responseDataWrappedSet.find('h3'), function (index, headerTag) {
H3Array[index] = $(headerTag).text();
});
var H3 = H3Array.join(' ') || '';
var keywordInH3 = H3.match(reObject);
if(keywordInH1) {
var headersResult = semaphoreGreen + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_H1_KEYWORD;
finalPageScore += 1;
} else if(keywordInH2 || keywordInH3) {
var headersResult = semaphoreYellow + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_H2_H3_KEYWORD;
finalPageScore += 0.5;
} else {
var headersResult = semaphoreRed + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_HEADERS_NO_KEYWORD;
}
ulResults.append('<li>' + headersResult + '</li>');
if(keywordInH1) {
keywordRepetitionsInTags += keywordInH1.length;
}
if(keywordInH2) {
keywordRepetitionsInTags += keywordInH2.length;
}
if(keywordInH3) {
keywordRepetitionsInTags += keywordInH3.length;
}
// STEP 5 - Get the URL of the page
var URL = linkToAnalyze;
URL = URL.replace(/-/g, ' ');
var keywordInURL = URL.match(reObject);
if(keywordInURL) {
var keywordinurlResult = semaphoreGreen + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_INURL_KEYWORD;
finalPageScore += 1;
} else {
var keywordinurlResult = semaphoreRed + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_INURL_NOKEYWORD;
}
ulResults.append('<li>' + keywordinurlResult + '</li>');
// STEP 6 - Count occurrences in the page - title, description and headers
var keywordTotal = responseDataWrappedSet.text().match(reObject);
if(keywordTotal) {
var keywordTotalReps = keywordTotal.length;
if((keywordTotalReps - keywordRepetitionsInTags) > 0 ) {
var keywordRepetitions = semaphoreGreen + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_REPS_KEYWORD;
finalPageScore += 1;
} else {
var keywordRepetitions = semaphoreRed + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_REPS_NOKEYWORD;
}
} else {
var keywordRepetitions = semaphoreRed + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_REPS_NOKEYWORD;
}
ulResults.append('<li>' + keywordRepetitions + '</li>');
// STEP 7 - Check in the ALT attribute of images
var altImagesKeyword = responseDataWrappedSet.find('img[alt*="' + focusKeyword.replace(/"/g, '\\"') + '"]');
if(altImagesKeyword.length) {
var altImagesResult = semaphoreGreen + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_ALTIMAGES_KEYWORD;
finalPageScore += 1;
} else {
var altImagesResult = semaphoreRed + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_ALTIMAGES_NOKEYWORD;
}
ulResults.append('<li>' + altImagesResult + '</li>');
// STEP 8 - Calculation of the page score
var sliderClass = '';
switch(true) {
case (finalPageScore <= 2):
sliderClass = 'score_red';
resultClass = '';
break;
case (finalPageScore > 2 && finalPageScore <= 4):
sliderClass = 'score_yellow';
break;
case (finalPageScore > 4 && finalPageScore <= 6):
sliderClass = 'score_green';
break;
}
// Append page score
$('#pagescore').append('<div id="pagescore_slider"><div id="inner_slider"></div></div><span id="pagescore_numeric" class="label ' + sliderClass + '">' + finalPageScore + ' / ' + maxScore + '</span>');
$('#pagescore_slider #inner_slider').css('width', ( finalPageScore * 16.66) + '%' ).addClass(sliderClass);
// Append analysis results
$('#analysis_dialog div.panel-body').append(ulResults);
} else {
// An error in the AJAX request occurred, kindly inform user and return
$('#analysis_dialog div.panel-body').append('<label class="label label-danger analysis-labels label-results">' + COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_ERROR + status + '</label>');
return;
}
}).always(function(){
// Reset the analyzing button
$('span.seospider-cogicon').removeClass('running');
$('span.seospider-labelicon-start').text(COM_JMAP_SEOSPIDER_CONTENT_ANALYSIS_START);
});
};
/**
* Manage the data saving for the headings tags
* in the model database table
*
* @access private
* @param String action
* @param String heading
* @return Void
*/
var saveDataStatus = function(action, heading) {
// Validate the overriden textarea
if(action == 'saveHeading' && !$('#override_heading_content').val()) {
$('#override_heading_content').addClass('error')
.next('ul.seospider_validation_errorlist').remove().end()
.after('<ul class="seospider_validation_errorlist"><li class="validation label label-danger">' + COM_JMAP_ROBOTS_REQUIRED +'</li></ul>');
return;
}
if(action == 'deleteHeading' && !$('#override_heading_content').val()) {
$('#override_heading_content').removeClass('error').next('ul.seospider_validation_errorlist').remove();
}
// Start the cog icon engine
$('div.headings-override span.seospider-cogicon').addClass('running');
// Retrieve informations
var targetLink = $('#headings_dialog a.seospider_analyzed_link').attr('href');
var overrideContent = $('#override_heading_content').val();
// If the HTML support is not enabled ensure to clean in realtime even the override content textarea
if(typeof(jmap_overrideheadingsHtml) !== 'undefined' && parseInt(jmap_overrideheadingsHtml) != 1) {
var cleanedOverrideContent = overrideContent.replace(/(<([^>]+)>)/ig,"");
$('#override_heading_content').val(cleanedOverrideContent);
}
// Object to send to server
var ajaxparams = {
idtask : action,
param : {
linkurl : targetLink,
headingTag : heading,
fieldValue : overrideContent
}
};
// Unique param 'data'
var uniqueParam = JSON.stringify(ajaxparams);
// Request JSON2JSON
var headingsPromise = $.Deferred(function(defer) {
var footerMessage = action == 'saveHeading' ? COM_JMAP_SEOSPIDER_HEADINGS_SAVING_OVERRIDE : COM_JMAP_SEOSPIDER_HEADINGS_DELETING_OVERRIDE;
$('#headings_opmessage').html('<label class="label label-warning"><span class="seospider-cogicon running"></span><span class="seospider-label-desc">' + footerMessage + '</span></label>');
$.ajax({
type : "POST",
url: "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json",
dataType : 'json',
context : this,
data : {
data : uniqueParam
}
}).done(function(data, textStatus, jqXHR) {
if(!data.result) {
// Error found
defer.reject(data.exception_message, textStatus);
return false;
}
// Check response all went well
if(data.result) {
var userMessage = data.exception_message || COM_JMAP_SEOSPIDER_HEADINGS_SAVED_MESSAGE;
defer.resolve(userMessage);
}
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
defer.reject('-' + genericStatus + '- ' + errorThrown);
});
}).promise();
headingsPromise.then(function(message) {
// Append the operation result to the footer
$('#headings_opmessage').html('<label class="label label-success"><span class="glyphicon glyphicon-ok-sign"></span> ' + message + '</label>');
// If this is a save action ensure that the 'delete' button is always enabled
if(action == 'saveHeading') {
$('#delete_heading').removeAttr('disabled');
}
// If this is a delete action go on to clear the textarea for this heading
if(action == 'deleteHeading') {
$('#override_heading_content').val('');
$('#delete_heading').attr('disabled', true);
}
// Refetch the updated current tag content
$.get(targetLink, function(responseData) {
// Fetch the Hx tag and compile the current textarea
// Set the parsed wrapped set
var responseDataWrappedSet = $(responseData.trim());
var headingsArray = responseDataWrappedSet.find(heading);
// Get the text of first Hx heading found
var headingText = $(headingsArray[0]).text().trim();
$('#original_heading_content').text(headingText);
// On save/delete action refresh even the SEO Spider row
// Check if there are other headings
var additionalHeadings = '';
if(headingsArray.length > 1) {
var headingsArrayJoin = new Array();
headingsArray.each(function(index, heading){
if(index < 1) {
return true;
}
headingsArrayJoin[index] = $(heading).text();
});
additionalHeadings = headingsArrayJoin.join(' | ');
}
var rowScope = $('table.seospiderlist tr[data-link="' + targetLink + '"]');
if(action == 'saveHeading') {
$('div[data-bind="{' + heading + '}"] span.seospider-headings', rowScope).html('<b class="badge badge-warning seospider-headings">' + headingText + '</b>' + additionalHeadings)
.attr('title', COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE + COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE_ACTIVE)
.attr('data-original-title', COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE + COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE_ACTIVE);
} else if(action == 'deleteHeading'){
$('div[data-bind="{' + heading + '}"] span.seospider-headings', rowScope).html(headingText + additionalHeadings)
.attr('title', COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE)
.attr('data-original-title', COM_JMAP_SEOSPIDER_HEADINGS_EDIT_OVERRIDE);
}
});
}, function(errorText, error) {
// Do stuff and exit
$('#headings_opmessage').html('<label class="label label-danger"><span class="glyphicon glyphicon-warning-sign"></span> ' + errorText+ '</label>');
}).always(function(){
// Start the cog icon engine
$('div.headings-override span.seospider-cogicon').removeClass('running');
setTimeout(function(){
$('#headings_opmessage').empty();
}, 2000);
});
};
/**
* Manage the data saving for the canonical tags
* in the model database table
*
* @access private
* @param String action
* @return Void
*/
var saveCanonicalDataStatus = function(action) {
// Retrieve informations and validate the URL for the canonical field
var targetLink = $('#canonical_dialog a.seospider_analyzed_link').attr('href');
var overrideContent = $('#override_canonical_content').val();
var urlValidator = new RegExp("^https?://(.+.)+.{2,4}(/.*)?$", "");
var isValidUrl = urlValidator.test(overrideContent);
// Validate the overriden input field
if(action == 'saveCanonical' && (!overrideContent || !isValidUrl)) {
$('#override_canonical_content').addClass('error')
.next('ul.seospider_validation_errorlist').remove().end()
.after('<ul class="seospider_validation_errorlist"><li class="validation label label-danger">' + COM_JMAP_CANONICAL_URL_REQUIRED +'</li></ul>');
return;
}
if(action == 'deleteCanonical' && (!overrideContent || !isValidUrl)) {
$('#override_canonical_content').removeClass('error').next('ul.seospider_validation_errorlist').remove();
}
// Start the cog icon engine
$('div.canonical-override span.seospider-cogicon').addClass('running');
// Object to send to server
var ajaxparams = {
idtask : action,
param : {
linkurl : targetLink,
fieldValue : overrideContent
}
};
// Unique param 'data'
var uniqueParam = JSON.stringify(ajaxparams);
// Request JSON2JSON
var canonicalPromise = $.Deferred(function(defer) {
var footerMessage = action == 'saveCanonical' ? COM_JMAP_SEOSPIDER_CANONICAL_SAVING_OVERRIDE : COM_JMAP_SEOSPIDER_CANONICAL_DELETING_OVERRIDE;
$('#canonical_opmessage').html('<label class="label label-warning"><span class="seospider-cogicon running"></span><span class="seospider-label-desc">' + footerMessage + '</span></label>');
$.ajax({
type : "POST",
url: "../administrator/index.php?option=com_jmap&task=ajaxserver.display&format=json",
dataType : 'json',
context : this,
data : {
data : uniqueParam
}
}).done(function(data, textStatus, jqXHR) {
if(!data.result) {
// Error found
defer.reject(data.exception_message, textStatus);
return false;
}
// Check response all went well
if(data.result) {
var userMessage = data.exception_message || COM_JMAP_SEOSPIDER_CANONICAL_SAVED_MESSAGE;
defer.resolve(userMessage);
}
}).fail(function(jqXHR, textStatus, errorThrown) {
// Error found
var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
defer.reject('-' + genericStatus + '- ' + errorThrown);
});
}).promise();
canonicalPromise.then(function(message) {
// Append the operation result to the footer
$('#canonical_opmessage').html('<label class="label label-success"><span class="glyphicon glyphicon-ok-sign"></span> ' + message + '</label>');
// If this is a save action ensure that the 'delete' button is always enabled
if(action == 'saveCanonical') {
$('#delete_canonical').removeAttr('disabled');
}
// If this is a delete action go on to clear the input value for this canonical tag, then restore the delete button as disabled
if(action == 'deleteCanonical') {
$('#override_canonical_content').val('');
$('#delete_canonical').attr('disabled', true);
}
// Refetch the updated current tag content
$.get(targetLink, function(responseData) {
// Fetch the canonical tag and compile the current input field again
// Set the parsed wrapped set
var responseDataWrappedSet = $(responseData.trim());
var canonical = responseDataWrappedSet.filter('link[rel=canonical]').attr('href') || '';
canonical = decodeURIComponent(canonical);
$('#original_canonical_content').val(canonical);
var rowScope = $('table.seospiderlist tr[data-link="' + targetLink + '"]');
if(action == 'saveCanonical') {
$('div[data-bind="{canonical}"] span.seospider-canonical', rowScope).addClass('seospider-canonical-override')
.removeClass('seospider-canonical-override-empty')
.text(canonical)
.attr('title', COM_JMAP_SEOSPIDER_CANONICAL_EDIT_OVERRIDE + COM_JMAP_SEOSPIDER_CANONICAL_EDIT_OVERRIDE_ACTIVE)
.attr('data-original-title', COM_JMAP_SEOSPIDER_CANONICAL_EDIT_OVERRIDE + COM_JMAP_SEOSPIDER_CANONICAL_EDIT_OVERRIDE_ACTIVE);
} else if(action == 'deleteCanonical'){
var bindedCanonical = $('div[data-bind="{canonical}"] span.seospider-canonical', rowScope);
bindedCanonical.removeClass('seospider-canonical-override').text(canonical)
.attr('title', COM_JMAP_SEOSPIDER_CANONICAL_EDIT_OVERRIDE)
.attr('data-original-title', COM_JMAP_SEOSPIDER_CANONICAL_EDIT_OVERRIDE);
// Restore the empty canonical class if the canonical tag is empty so missing
if(!canonical) {
bindedCanonical.addClass('seospider-canonical-override-empty');
}
}
});
}, function(errorText, error) {
// Do stuff and exit
$('#canonical_opmessage').html('<label class="label label-danger"><span class="glyphicon glyphicon-warning-sign"></span> ' + errorText+ '</label>');
}).always(function(){
// Start the cog icon engine
$('div.canonical-override span.seospider-cogicon').removeClass('running');
setTimeout(function(){
$('#canonical_opmessage').empty();
}, 2000);
});
};
/**
* Function dummy constructor
*
* @access private
* @param String
* contextSelector
* @method <<IIFE>>
* @return Void
*/
(function __construct() {
// Add UI events
addListeners.call(this, true);
$('div.hasTooltip').tooltip({trigger:'hover', placement:'left', html : 1});
/// Execute analysis only if the view Seospider is executed
if($('table.seospiderlist').length) {
$('#toolbar-arrow-down-2 button').attr('disabled', true);
$('#toolbar-upload button').attr('disabled', true);
$('#toolbar-download button').attr('disabled', true);
// Start to analyze the validation status if enabled the async mode
startLinksCrawling();
}
}).call(this);
}
// On DOM Ready
$(function() {
window.JMapSeoSpider = new SeoSpider();
});
})(jQuery);