<?php
// namespace administrator\components\com_jmap\models;
/**
 * @package JMAP::ANALYZER::administrator::components::com_jmap
 * @subpackage models
 * @author Joomla! Extensions Store
 * @copyright (C) 2021 - Joomla! Extensions Store
 * @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
 */
defined ( '_JEXEC' ) or die ( 'Restricted access' );
jimport ( 'joomla.filesystem.file' );

/**
 * Analyzer concrete model
 * Operates not on DB but directly on a cached copy of the XML sitemap file
 *
 * @package JMAP::ANALYZER::administrator::components::com_jmap
 * @subpackage models
 * @since 2.3.3
 */
class JMapModelAnalyzer extends JMapModel {
	/**
	 * Number of XML records
	 * 
	 * @access private
	 * @var Int
	 */
	private $recordsNumber;
	
	/**
	 * Counter result set
	 *
	 * @access public
	 * @return int
	 */
	public function getTotal() {
		// Return simply the XML records number
		return $this->recordsNumber;
	}
	
	/**
	 * Main get data method
	 *
	 * @access public
	 * @return Object[]
	 */
	public function getData() {
		// Load data from XML file, parse it to load records
		$cachedSitemapFilePath = JPATH_COMPONENT_ADMINISTRATOR . '/cache/analyzer/';
		$originalBackendLanguageTag = $this->app->getLanguage()->getTag();
		
		// Has sitemap some vars such as lang or Itemid?
		$sitemapLang = $this->getState('sitemaplang', '');
		$sitemapLinksLang = $sitemapLang ? $sitemapLang . '/' : '';
		$sitemapLang = $sitemapLang ? '_' . $sitemapLang : '';
		
		$sitemapDataset = $this->getState('sitemapdataset', '');
		$sitemapDataset = $sitemapDataset ? '_dataset' . (int)$sitemapDataset : '';
		
		$sitemapItemid = $this->getState('sitemapitemid', '');
		$sitemapItemid = $sitemapItemid ? '_menuid' . (int)$sitemapItemid : '';
		
		// Final name
		$cachedSitemapFilename = 'sitemap_xml' . $sitemapLang . $sitemapDataset . $sitemapItemid . '.xml'; 
		
		// Detect PHP 7
		$php7 = false;
		if (version_compare(PHP_VERSION, '7.0', '>=')) {
			$php7 = true;
		}
		
		// Start processing
		try {
			// Now check if the file correctly exists
			if(JFile::exists($cachedSitemapFilePath . $cachedSitemapFilename)) {
				$loadedSitemapXML = simplexml_load_file($cachedSitemapFilePath . $cachedSitemapFilename);
				if(!$loadedSitemapXML) {
					throw new JMapException ( 'Invalid XML', 'error' );
				}
			} else {
				throw new JMapException ( JText::sprintf ( 'COM_JMAP_ANALYZER_NOCACHED_FILE_EXISTS', $this->_db->getErrorMsg () ), 'error' );
			}
			
			// Execute HTTP request and associate HTTP response code with each record links
			// Instantiante a new HTTP client
			$cParams = $this->getComponentParams();
			$httpTransport = $cParams->get('analytics_service_http_transport', 'curl') == 'socket' ? new JMapHttpTransportSocket () : new JMapHttpTransportCurl ();
			$httpClient = new JMapHttp ( $httpTransport, $cParams );
			if($loadedSitemapXML->url->count()) {
				// Manage splice pagination here for the XML records
				$convertedIteratorToArray = iterator_to_array($loadedSitemapXML->url, false);
				
				// Store number of records for pagination
				$this->recordsNumber = count($convertedIteratorToArray);
				
				// Execute pagination splicing if any limit is set
				$limit = $this->getState ( 'limit' );
				if($limit) {
					$loadedSitemapXML = array_splice($convertedIteratorToArray, $this->getState ( 'limitstart' ), $this->getState ( 'limit' ));
				} else {
					$loadedSitemapXML = $convertedIteratorToArray;
				}
				
				// Now start the Analyzer
				$linksAnalyzerWorkingMode = $this->getComponentParams()->get('linksanalyzer_workingmode', 1);
				if($linksAnalyzerWorkingMode) {
					$siteRouter = JRouterSite::getInstance('site', array('mode'=>JROUTER_MODE_SEF));
				}
				// Check the Analyzer Analysis working mode for the validation of links
				$validationAnalysisStandard = (bool)($this->getComponentParams()->get('linksanalyzer_validation_analysis', 2) == 2);
				
				if( version_compare(JVERSION, '3.4', '>=')) {
					JApplicationCms::getInstance('site')->loadLanguage();
				}
				$forceScheme = false;
				if ( version_compare(JVERSION, '3.7', '>=') && $this->app->get('force_ssl') >= 1) {
					$forceScheme = true;
				}
				
				// Add compatibility support for J3.9 new router between sites management
				$removeLanguageSef = true;
				if(version_compare(JVERSION, '3.9', '>=')) {
					$removeLanguageSef = false;
				}
				
				// Get a search filter
				$searchFilter = $this->state->get('searchpageword', null);
				$headers = array('Accept'=>'text/html', 'User-Agent'=>'JSitemapbot/1.0');
				foreach ($loadedSitemapXML as $index=>&$url) {
					// Evaluate filtering by search word
					if($searchFilter) {
						// Evaluate position or exact match
						if($this->getState('exactsearchpage', null)) {
							$isMatching = $url->loc == $searchFilter;
						} else {
							$isMatching = (stripos($url->loc, $searchFilter) !== false);
						}
						if(!$isMatching) {
							array_splice($loadedSitemapXML, $index, 1);
							
							// Re-assign array
							if($php7) {
								$tmp = array_values($loadedSitemapXML);
								$loadedSitemapXML = $tmp;
							}
							continue;
						}
					}
					
					if($validationAnalysisStandard) {
						$httpResponse = $httpClient->get($url->loc, $headers);
						$url->httpstatus = $httpResponse->code;
					}
					
					// Find informations about the link, component and menu itemid if any, need a workaround to parse correctly from backend
					if($linksAnalyzerWorkingMode) {
						$baseAdmin = JUri::base();
						$baseSite = str_replace('/administrator', '', $baseAdmin);
						$fakedUrl = str_replace($baseSite, $baseAdmin, $url->loc);
						if($removeLanguageSef) {
							$fakedUrl = str_replace($sitemapLinksLang, '', $fakedUrl);
						}
						$fakedUrl = str_replace('/index.php', '', $fakedUrl);
						// Now instantiate and parse the faked url from backend, replacing the uri base it will be = site
						$uriObject = JUri::getInstance((string)$fakedUrl);
						try {
							// Avoid parse uri redirects when saving an article
							if ($forceScheme) {
								$uriObject->setScheme('https');
							}
							$parseUri = $siteRouter->parse($uriObject);
						} catch(Exception $e) {
							// Always ignore the new router exceptions in order to evaluate error codes
						}
					}
					
					// Now augment the object to show informations
					$url->component = isset($parseUri['option']) ? $parseUri['option'] : '-';
					$url->menuId = isset($parseUri['Itemid']) ? $parseUri['Itemid'] : '-';
					$url->menuTitle = '-';
					// Translate human menu
					if(isset($parseUri['Itemid'])) {
						$query = "SELECT" . $this->_db->quoteName('title') .
								 "\n FROM #__menu" .
								 "\n WHERE " . 
								 $this->_db->quoteName('id') . " = " . (int)$parseUri['Itemid'];
						$menuTitle = $this->_db->setQuery($query)->loadResult();
						$url->menuTitle = $menuTitle;
					}
				}
				
				// Override always the $siteRouter->parse language from the frontend side link applying the original backend language
				if(version_compare(JVERSION, '3.9', '>=') && $linksAnalyzerWorkingMode && $sitemapLinksLang) {
					$jLang = JFactory::getLanguage();
					$jLang->setLanguage($originalBackendLanguageTag);
					$jLang->load('com_jmap', JPATH_COMPONENT_ADMINISTRATOR, 'en-GB', true, true);
					if($originalBackendLanguageTag != 'en-GB') {
						$jLang->load('com_jmap', JPATH_ADMINISTRATOR, $originalBackendLanguageTag, true, false);
						$jLang->load('com_jmap', JPATH_COMPONENT_ADMINISTRATOR, $originalBackendLanguageTag, true, false);
					}
				}
				
				// Perform array sorting if any
				$order = $this->getState('order', null);
				$jmapAnalyzerOrderDir = $this->getState('order_dir', 'asc');
				
				if($validationAnalysisStandard && $order == 'httpstatus') {
					function cmpAsc($a, $b){
						return ((int)$a->httpstatus < (int)$b->httpstatus) ? -1 : 1;
					}
					function cmpDesc($a, $b){
						return ((int)$a->httpstatus > (int)$b->httpstatus) ? -1 : 1;
					}
					$callbackName = ($jmapAnalyzerOrderDir == 'asc') ? 'cmpAsc' : 'cmpDesc';
					usort($loadedSitemapXML, $callbackName);
				}
				
				if($order == 'link') {
					function cmpAsc($a, $b){
						return strcmp($a->loc, $b->loc);
					}
					function cmpDesc($a, $b){
						return strcmp($b->loc, $a->loc);
					}
					$callbackName = ($jmapAnalyzerOrderDir == 'asc') ? 'cmpAsc' : 'cmpDesc';
					usort($loadedSitemapXML, $callbackName);
				}
			} else {
				throw new JMapException ( JText::sprintf ( 'COM_JMAP_ANALYZER_EMPTY_SITEMAP', $this->_db->getErrorMsg () ), 'notice' );
			}
		} catch ( JMapException $e ) {
			$this->app->enqueueMessage ( $e->getMessage (), $e->getErrorLevel () );
			$loadedSitemapXML = array ();
		} catch ( Exception $e ) {
			$jmapException = new JMapException ( $e->getMessage (), 'error' );
			$this->app->enqueueMessage ( $jmapException->getMessage (), $jmapException->getErrorLevel () );
			$loadedSitemapXML = array ();
		}
		
		return $loadedSitemapXML;
	}
	
	/**
	 * Return select lists used as filter for listEntities
	 *
	 * @access public
	 * @return array
	 */
	public function getFilters() {
		$validationType = (int)($this->getComponentParams()->get('linksanalyzer_validation_analysis', 2));
		
		$filteringFunction = ($validationType == 2) ? 'onchange="Joomla.submitform();"' : 'class="noanalyzer" onchange="JMapAnalyzer.filterOnAsyncPage(this);"';
		$selectName = ($validationType == 2) ? 'filter_type' : 'async_type';
		
		$datasourceTypes = array ();
		$datasourceTypes [] = JHtml::_ ( 'select.option', null, JText::_ ( 'COM_JMAP_ANALYZER_ALL' ) );
		$datasourceTypes [] = JHtml::_ ( 'select.option', '200', JText::_ ( 'COM_JMAP_ANALYZER_200' ) );
		$datasourceTypes [] = JHtml::_ ( 'select.option', '301', JText::_ ( 'COM_JMAP_ANALYZER_301' ) );
		$datasourceTypes [] = JHtml::_ ( 'select.option', '303', JText::_ ( 'COM_JMAP_ANALYZER_303' ) );
		$datasourceTypes [] = JHtml::_ ( 'select.option', '404', JText::_ ( 'COM_JMAP_ANALYZER_404' ) );
		$datasourceTypes [] = JHtml::_ ( 'select.option', '500', JText::_ ( 'COM_JMAP_ANALYZER_500' ) );
		$filters ['type'] = JHtml::_ ( 'select.genericlist', $datasourceTypes, $selectName, $filteringFunction, 'value', 'text', $this->getState ( 'link_type' ) );
		
		return $filters;
	}
}