/** * Spider client for SEO reports and issues reporter * * @package JMAP::SEOSPIDER::administrator::components::com_jmap * @subpackage js * @author Joomla! Extensions Store * @copyright (C) 2015 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){ 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(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') || ''; // 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 hasTooltip" title="' + COM_JMAP_SEOSPIDER_NOINDEX_DESC + '">' + COM_JMAP_SEOSPIDER_NOINDEX + '</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); $(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); $(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-download button').remove(); $('#toolbar-download').append('<a class="btn btn-small"><span class="icon-download"></span>' + COM_JMAP_EXPORT_XLS + '</a>'); $('#toolbar-download > a').attr('href', 'data:text/html;charset=utf-8,' + encodeURIComponent('<table>' + seospiderTableHtml + '</table>')) .attr('download', filename); $('#toolbar-upload button').removeAttr('disabled'); $('#toolbar-arrow-down-2 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') || ''; $('#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-download button').attr('disabled', true); $('#toolbar-upload button').attr('disabled', true); $('#toolbar-arrow-down-2 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);