| Current Path : /home/chauffn/www/components/com_jmap/models/ |
| Current File : /home/chauffn/www/components/com_jmap/models/sitemap.php |
<?php
// namespace components\com_jmap\models;
/**
* @package JMAP::SITEMAP::components::com_jmap
* @subpackage models
* @author Joomla! Extensions Store
* @copyright (C) 2015 - Joomla! Extensions Store
* @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
*/
defined ( '_JEXEC' ) or die ( 'Restricted access' );
/**
* Main sitemap model public responsibilities interface
*
* @package JMAP::SITEMAP::components::com_jmap
* @subpackage models
*/
interface IJMapModelSitemap {
/**
* Get the Data
* @access public
* @return array
*/
public function getSitemapData();
/**
* Get the component params width view override/merge
* @access public
* @return Object
*/
public function getComponentParams();
}
/**
* CPanel export XML sitemap responsibility
*
* @package JMAP::CPANEL::administrator::components::com_jmap
* @subpackage models
* @since 1.0
*/
interface IJMapModelExportable {
/**
* Export XML file for sitemap
*
* @access public
* @param string $contents
* @param string $fileNameSuffix
* @param string $fileNameFormat
* @param string $fileNameLanguage
* @param string $fileNameDatasetFilter
* @param string $fileNameItemidFilter
* @param string $mimeType
* @param boolean $isFile
* @return boolean
*/
public function exportXMLSitemap($contents, $fileNameSuffix, $fileNameFormat, $fileNameLanguage, $fileNameDatasetFilter, $fileNameItemidFilter, $mimeType, $isFile = false);
}
/**
* Main sitemap model class <<testable_behavior>>
*
* @package JMAP::SITEMAP::components::com_jmap
* @subpackage models
* @since 1.0
*/
class JMapModelSitemap extends JMapModel implements IJMapModelSitemap, IJMapModelExportable {
/**
* Fallback default site language
* @access private
* @var string
*/
private $fallbackDefaultLanguage;
/**
* Fallback default site language RFC format
* @access private
* @var string
*/
private $fallbackDefaultLanguageRFC;
/**
* Default site language
* @access private
* @var string
*/
private $langTag;
/**
* Default site language
* @access private
* @var string
*/
private $siteLanguageRFC;
/**
* Document formats
* @access private
* @var array
*/
private $documentFormat;
/**
* Component params with view overrides/merge
* @access private
* @var array
*/
private $cparams;
/**
* Supported tables for options components supported to generate
* 3PD Google News sitemap
* @access private
* @var array
*/
private $supportedGNewsTablesOptions;
/**
* Access level
* @access private
* @var string
*/
private $accessLevel = array();
/**
* Main data structure
* @access private
* @var array
*/
private $data = array ();
/**
* Sources array
* @access private
* @var array
*/
private $sources = array ();
/**
* RSS extesions manifest supported
* @access private
* @var Object
*/
private $rssExtensionsManifest;
/**
* Calculated limit start for source data query during a precaching process
* @access public
* @var int
*/
public $limitStart;
/**
* Calculated limit rows for source data query during a precaching process
* @access public
* @var int
*/
public $limitRows;
/**
* Send as attachment download
*
* @access public
* @param String $contents
* @param String $filename Nome del file esportato
* @param String $mimeType Mime Type dell'attachment
* @param boolean $isFile Se trattare il contenuto come file name o content pronti
* @return void
*/
private function sendAsBinary($contents, $filename, $mimeType, $isFile = false) {
if($isFile) {
$fsize = @filesize ( $contents );
} else {
$fsize = strlen($contents);
}
// required for IE, otherwise Content-disposition is ignored
if (ini_get ( 'zlib.output_compression' )) {
ini_set ( 'zlib.output_compression', 'Off' );
}
header ( "Pragma: public" );
header ( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );
header ( "Expires: 0" );
header ( "Content-Transfer-Encoding: binary" );
header ( 'Content-Disposition: attachment;' . ' filename="' . $filename . '";' . ' size=' . $fsize . ';' ); //RFC2183
header ( "Content-Type: " . $mimeType); // MIME type
header ( "Content-Length: " . $fsize );
if (! ini_get ( 'safe_mode' )) { // set_time_limit doesn't work in safe mode
@set_time_limit ( 0 );
}
if(!$isFile) {
echo $contents;
} else {
$this->readfile_chunked ( $contents );
}
exit();
}
/**
* Read and send in the output stream the contents of the file in chunks,
* Resolving the problems of limitations related to the normal readfile
*
* @access private
* @param string $nomefile
* @return boolean
*/
private function readfile_chunked($filename) {
$chunksize = 1 * (1024 * 1024); // how many bytes per chunk
$buffer = '';
$cnt = 0;
$handle = fopen ( $filename, 'rb' );
if ($handle === false) {
return false;
}
while ( ! feof ( $handle ) ) {
$buffer = fread ( $handle, $chunksize );
echo $buffer;
@ob_flush ();
flush ();
}
$status = fclose ( $handle );
return $status;
}
/**
* Pagebreaks detection
*
* @access private
* @param Object& $article
* @return boolean
*/
private function addPagebreaks(&$article) {
$matches = array ();
if (preg_match_all ( '/<hr\s*[^>]*?(?:(?:\s*alt="(?P<alt>[^"]+)")|(?:\s*title="(?P<title>[^"]+)"))+[^>]*>/i', $article->completetext, $matches, PREG_SET_ORDER )) {
foreach ( $matches as $i=>$match ) {
if (strpos ( $match [0], 'class="system-pagebreak"' ) !== FALSE) {
if (@$match ['alt']) {
$title = stripslashes ( $match ['alt'] );
} elseif (@$match ['title']) {
$title = stripslashes ( $match ['title'] );
} else {
$title = JText::sprintf ( 'Page #', $i );
}
$article->expandible[] = $title;
}
}
return true;
}
return false;
}
/**
* Preprocessing at runtime for third party extensions generated query
* General purpouse function to fil gaps of 3PD extensions at runtime
* Maybe in future could migrate to configurable JSON manifest as for wizard
*
* @access private
* @param Object $source
* @param string $query
* @param Object $params
* @return string The processed query SQL string
*/
private function runtimePreProcessing($query, $resultSourceObject) {
// Switch data source option name
$sqlqueryManaged = $resultSourceObject->chunks;
$params = $resultSourceObject->params;
$option = $resultSourceObject->chunks->option;
switch ($sqlqueryManaged->option) {
case 'com_virtuemart':
// If site user language not match default for generated query and is VM 2 with extended lang tables
if(!stristr($sqlqueryManaged->table_maintable, $this->siteLanguageRFC) && stristr($sqlqueryManaged->table_maintable, 'virtuemart') && JMapLanguageMultilang::isEnabled()) {
// Find language with which generated query has been generated
$maintableChunked = explode('_', $sqlqueryManaged->table_maintable);
$langReverseChunks = array();
$langReverseChunks[] = array_pop($maintableChunked);
$langReverseChunks[] = array_pop($maintableChunked);
$originalLang = array_reverse($langReverseChunks);
$originalLang = implode('_', $originalLang);
// Site language has been changed from default for which query has been generated, so process
$processedQuery = preg_replace('/(#__virtuemart.*)(_'.$originalLang.')/iU', '$1_'.$this->siteLanguageRFC.'$3', $query);
// Now test if admin has created virtuemart 2 tables for the site user chosen language otherwise proceed with same of generated query
$this->_db->setQuery($processedQuery);
try {
if($this->_db->execute()) {
$query = $processedQuery;
}
} catch (Exception $e) {
// No exception throw, just go on
}
}
break;
case 'com_jshopping':
// If site user language not match default for generated query and is Joomshopping we can go on to replace language value
if(!stristr($sqlqueryManaged->titlefield, $this->langTag) && stristr($sqlqueryManaged->table_maintable, 'jshopping')) {
// Find deafult site language with which raw query has been generated
$maintableChunked = explode('_', $sqlqueryManaged->titlefield);
$originalLang = array_pop($maintableChunked);
// Site language has been changed from default for which query has been generated, so process
$processedQuery = preg_replace('/(name)(_'.$originalLang.')/iU', '$1_'.$this->langTag.'$3', $query);
$resultSourceObject->chunks->titlefield = str_replace($originalLang, $this->langTag, $resultSourceObject->chunks->titlefield);
$resultSourceObject->chunks->orderby_maintable = str_replace($originalLang, $this->langTag, $resultSourceObject->chunks->orderby_maintable);
$resultSourceObject->chunks->field_select_jointable2 = @str_replace($originalLang, $this->langTag, $resultSourceObject->chunks->field_select_jointable2);
// Now test if admin has created virtuemart 2 tables for the site user chosen language otherwise proceed with same of generated query
$checkSafeQuery = "SHOW COLUMNS FROM " . $this->_db->quoteName($sqlqueryManaged->table_maintable);
$this->_db->setQuery($checkSafeQuery);
$columnsArray = $this->_db->loadColumn();
if(in_array('name_' . $this->langTag, $columnsArray)) {
$query = $processedQuery;
}
}
break;
case 'com_j2store':
// Leave untouched
break;
// Preprocessing with generic tasks for all extensions
default:
// Only if multilanguage is not currently enabled, switch query to select all disregarding language filter
if(!JMapLanguageMultilang::isEnabled() && stristr($query, '{langtag}')) {
// Do replacement to avoid language filtering
$toReplaceString = "{langtag} OR " . $this->_db->quoteName($sqlqueryManaged->table_maintable) . "." .
$this->_db->quoteName('language') . " != ''";
$query = str_replace("{langtag}", $toReplaceString, $query);
}
// Avoid access filtering if ACL disabled
if($params->get('disable_acl') === 'disabled' && stristr($query, '{aid}')) {
$toReplaceString = ">= 0";
$query = str_replace("IN {aid}", $toReplaceString, $query);
}
}
// Manage preprocessing query for multi level categorization recursion types level/adiacency/multiadiacency. This let avoid to change backend wizard manifests
$hasJSitemapCategoryId = false;
if($params->get('multilevel_categories', 0) && $this->hasCategorization($resultSourceObject)) {
$manifestConfiguration = $this->loadManifest ($option);
// Error decoding configuration object, exit and fallback to standard indenting
if(is_object($manifestConfiguration) && isset($manifestConfiguration->recursion_type) && $manifestConfiguration->recursion_type == 'level') {
$toReplaceString = "SELECT " .
$this->_db->quoteName($manifestConfiguration->categories_table) . "." .
$this->_db->quoteName($manifestConfiguration->level_field) .
" AS " . $this->_db->quoteName('jsitemap_level') . ", ";
$query = preg_replace("/SELECT/", $toReplaceString, $query, 1);
$resultSourceObject->catRecursion = true;
$resultSourceObject->recursionType = $manifestConfiguration->recursion_type;
} elseif (is_object($manifestConfiguration) && isset($manifestConfiguration->recursion_type) && $manifestConfiguration->recursion_type == 'adiacency') {
$toReplaceString = "SELECT " .
$this->_db->quoteName($manifestConfiguration->categories_table) . "." .
$this->_db->quoteName($manifestConfiguration->category_table_id_field) .
" AS " . $this->_db->quoteName('jsitemap_category_id') . ", ";
$query = preg_replace("/SELECT/", $toReplaceString, $query, 1);
$resultSourceObject->catRecursion = true;
$resultSourceObject->recursionType = $manifestConfiguration->recursion_type;
$hasJSitemapCategoryId = true;
} elseif (is_object($manifestConfiguration) &&
isset($manifestConfiguration->recursion_type) &&
$manifestConfiguration->recursion_type == 'multiadiacency' &&
!preg_match('/categor|cats|catg/i', $resultSourceObject->chunks->table_maintable)) {
$toReplaceString = "SELECT " .
$this->_db->quoteName($manifestConfiguration->item2category_table) . "." .
$this->_db->quoteName($manifestConfiguration->item2category_catid_field) .
" AS " . $this->_db->quoteName('jsitemap_category_id') . ", ";
$query = preg_replace("/SELECT/", $toReplaceString, $query, 1);
$resultSourceObject->catRecursion = true;
$resultSourceObject->recursionType = $manifestConfiguration->recursion_type;
$hasJSitemapCategoryId = true;
} elseif (is_object($manifestConfiguration) &&
isset($manifestConfiguration->recursion_type) &&
$manifestConfiguration->recursion_type == 'multiadiacency' &&
preg_match('/categor|cats|catg/i', $resultSourceObject->chunks->table_maintable)) {
$toReplaceString = "SELECT " .
$this->_db->quoteName($manifestConfiguration->categories_table) . "." .
$this->_db->quoteName($manifestConfiguration->category_table_id_field) .
" AS " . $this->_db->quoteName('jsitemap_category_id') . ", ";
$query = preg_replace("/SELECT/", $toReplaceString, $query, 1);
$resultSourceObject->catRecursion = true;
$resultSourceObject->recursionType = $manifestConfiguration->recursion_type;
$hasJSitemapCategoryId = true;
}
}
// Not just injected jsitemap_category_id for the multilevel, param $guessItemid active and not category type data source itself
$guessItemid = $params->get('guess_sef_itemid', 0);
if( !$hasJSitemapCategoryId
&& $guessItemid
&& $this->hasCategorization($resultSourceObject, true)
&& !preg_match('/categor|cats|catg/i', $resultSourceObject->chunks->table_maintable)
&& $routeManifest = $this->loadRouteManifest($option)) {
$toReplaceString = "SELECT " .
$this->_db->quoteName($routeManifest->categories_table) . "." .
$this->_db->quoteName($routeManifest->categories_table_id_field) .
" AS " . $this->_db->quoteName('jsitemap_category_id') . ", ";
$query = preg_replace("/SELECT/", $toReplaceString, $query, 1);
}
return $query;
}
/**
* Preprocessing at runtime for native content and third party extensions
* for the exclusive RSS format feed, thanks to adapter manifest allow to
* inject the extra field to extract the feed description
*
* @access private
* @param string $keyIndexTable The extension key to get the field from the manifest
* @param string $query The query to process
* @return string The processed query SQL string
*/
private function runtimeRssPreProcessing($keyIndexTable, $query) {
// Get the description field based on table key
$descField = $this->rssExtensionsManifest->{$keyIndexTable};
$toReplaceString = "SELECT " .
$this->_db->quoteName($keyIndexTable) . "." .
$this->_db->quoteName($descField) .
" AS " . $this->_db->quoteName('jsitemap_rss_desc') . ", ";
$query = preg_replace("/SELECT/", $toReplaceString, $query, 1);
return $query;
}
/**
* sort a menu view
*
* @param
* array the menu
* @return array the sorted menu
*/
private function sortMenu($m, &$sourceParams) {
$rootlevel = array ();
$sublevels = array ();
$newmenuitems = array();
$r = 0;
$s = 0;
foreach ( $m as $item ) {
if ($item->parent == 1) {
// rootlevel
$item->ebene = 0;
$rootlevel [$r] = $item;
$r ++;
} else {
// sublevel
$item->ebene = 1;
$sublevels [$s] = $item;
$s ++;
}
}
$maxlevels = $sourceParams->get ( 'maxlevels', '5' );
$z = 0;
if ($s != 0 and $maxlevels != 0) {
foreach ( $rootlevel as $elm ) {
$newmenuitems [$z] = $elm;
$z ++;
$this->sortMenuRecursive ( $z, $elm->id, $sublevels, 1, $maxlevels, $newmenuitems );
}
} else {
$newmenuitems = $rootlevel;
}
return $newmenuitems;
}
/**
* sort a menu view Recursive through the tree
*
* @param
* int element number to work with
* @param
* int the parent id
* @param
* array the sublevels
* @param
* int the level
* @param
* int the maximun depth for the search
* @param
* array new menu
*/
private function sortMenuRecursive(&$z, $id, $sl, $ebene, $maxlevels, &$nm) {
if ($ebene > $maxlevels) {
return true;
}
foreach ( $sl as $selm ) {
if ($selm->parent == $id) {
$selm->ebene = $ebene;
$nm [$z] = $selm;
$z ++;
$nebene = $ebene + 1;
$this->sortMenuRecursive ( $z, $selm->id, $sl, $nebene, $maxlevels, $nm );
}
}
return true;
}
/**
* Get the Data for a view
*
* @access private
* @param object $source
* @param array $accessLevels
*
* @return Object
*/
private function getSourceData($source, $accessLevels) {
// Create di un nuovo result source object popolato delle properties necessarie alla view e degli items recuperati da DB
$resultSourceObject = new stdClass ();
$resultSourceObject->id = $source->id;
$resultSourceObject->name = $source->name;
$resultSourceObject->type = $source->type;
if($source->sqlquery_managed) {
$resultSourceObject->chunks = json_decode($source->sqlquery_managed);
}
// If sitemap format is gnews, allow only content data source and 3PD user data source that are supported as compatible, avoid calculate unuseful data and return immediately
if($this->documentFormat === 'gnews') {
if($source->type === 'menu') {
return false;
}
if($source->type === 'user') {
if(isset($resultSourceObject->chunks)) {
if(!in_array($resultSourceObject->chunks->table_maintable, $this->supportedGNewsTablesOptions)) {
return false;
}
}
}
}
// If sitemap format is rss, allow only content data source and 3PD user data source that are supported as compatible, avoid calculate unuseful data and return immediately
if($this->documentFormat === 'rss') {
if($source->type === 'menu' || $source->type === 'links') {
return false;
}
// Load always manifest, both content and third party data sources
$this->rssExtensionsManifest = $this->loadManifest('rss');
// If third party data sources check if it's supported, otherwise return false
if($source->type === 'user' && isset($resultSourceObject->chunks)) {
// Add dynamic Virtuemart
$vmProperty = '#__virtuemart_products_' . $this->siteLanguageRFC;
$this->rssExtensionsManifest->{$vmProperty} = 'product_desc';
// Skip extensions not supported for RSS feed generation
if(!property_exists($this->rssExtensionsManifest, $resultSourceObject->chunks->table_maintable)) {
return false;
}
}
}
// Already a JRegistry object! Please note object cloning to avoid reference overwriting!
// Component -> menu view specific level params override
$resultSourceObject->params = clone($this->cparams);
// Item specific level params override
$resultSourceObject->params->merge(new JRegistry($source->params ));
// Ensure the current datasource is enabled for the current sitemap format otherwise skip processing
if(!$resultSourceObject->params->get('htmlinclude', 1) && $this->documentFormat == 'html') {
return false;
}
if(!$resultSourceObject->params->get('xmlinclude', 1) && $this->documentFormat == 'xml') {
return false;
}
if(!$resultSourceObject->params->get('xmlimagesinclude', 1) && $this->documentFormat == 'images') {
return false;
}
if(!$resultSourceObject->params->get('xmlmobileinclude', 1) && $this->documentFormat == 'mobile') {
return false;
}
if(!$resultSourceObject->params->get('gnewsinclude', 1) && $this->documentFormat == 'gnews') {
return false;
}
if(!$resultSourceObject->params->get('rssinclude', 1) && $this->documentFormat == 'rss') {
return false;
}
// ACL filtering
$disableAcl = $resultSourceObject->params->get('disable_acl');
$sourceItems = array();
switch ($source->type) {
case 'user':
// Get the language param for the user data source and ensure that it's all langs or match the current language, if not skip getting data
$languageTag = JMapLanguageMultilang::getCurrentSefLanguage();
$dataSourceLanguage = $resultSourceObject->params->get('datasource_language', '*');
if($dataSourceLanguage != '*' && ($languageTag != $dataSourceLanguage)) {
// Detected a precaching call, set 0 affected rows to complete correctly the precaching process just now
if($this->limitRows) {
$this->setState('affected_rows', 0);
}
break;
}
$query = $source->sqlquery;
$debugMode = $resultSourceObject->params->get('debug_mode', 0);
// Do runtime preprocessing if any for selected data source extension
$query = $this->runtimePreProcessing($query, $resultSourceObject);
// Se la raw query � stata impostata
if($query) {
$query = str_replace('{aid}', '(' . implode(',', $accessLevels) . ')', $query);
$query = str_replace('{langtag}', $this->_db->quote($this->langTag), $query);
$query = str_replace('{languagetag}', $this->_db->quote($this->langTag), $query);
$query = str_replace('{languagetagrfc}', $this->siteLanguageRFC, $query);
// Manage for latest months placeholder if found one
if(preg_match("/'?{(\d+)months}'?/i", $query, $matches)) {
$minValidCreatedDate = gmdate ( "Y-m-d H:i:s", strtotime ( "-" . $matches[1] . " months", time()));
// All items need to be created after the minimum valid created date
$query = preg_replace("/'?{(\d+)months}'?/i", $this->_db->quote($minValidCreatedDate), $query);
}
// Runtime preprocessing for RSS description field
if($this->documentFormat === 'rss') {
$query = $this->runtimeRssPreProcessing($resultSourceObject->chunks->table_maintable, $query);
}
// Check if a limit for query rows has been set, this means we are in precaching process by JS App client
if(!$this->limitRows) {
$this->_db->setQuery ( $query );
} else {
$this->_db->setQuery ( $query, $this->limitStart, $this->limitRows);
}
try {
// Security safe check: only SELECT allowed
if(preg_match('/((?<!`)delete|update|insert|password)/i', $query)) {
throw new JMapException(sprintf(JText::_('COM_JMAP_QUERY_NOT_ALLOWED_FROM_USER_DATASOURCE' ), $source->name), 'warning');
}
$sourceItems = $this->_db->loadObjectList ();
if ($this->_db->getErrorNum () && !$sourceItems) {
$queryExplained = null;
if ($debugMode) {
$queryExplained = '<br /><br />' . $this->_db->getErrorMsg () . '<br /><br />' .
JText::_('COM_JMAP_SQLQUERY_EXPLAINED' ) . '<br /><br />' .
$this->_db->getQuery () . '<br /><br />' .
JText::_('COM_JMAP_SQLQUERY_EXPLAINED_END' );
}
throw new JMapException(sprintf(JText::_('COM_JMAP_ERROR_RETRIEVING_DATA_FROM_USER_DATASOURCE' ), $source->name) . $queryExplained, 'warning');
}
// Detected a precaching call, so store in the model state the number of affected rows for JS app
if($this->limitRows) {
$this->setState('affected_rows', count($sourceItems));
}
// Start subQueriesPostProcessor if needed for nested multilevel categories
if($resultSourceObject->params->get('multilevel_categories', 0) && $this->hasCategorization($resultSourceObject)) {
// Pre assignment
$resultSourceObject->data = $sourceItems;
// Start post processor
$this->subQueriesPostProcessor($resultSourceObject);
}
} catch (JMapException $e) {
if($e->getErrorLevel() == 'notice' && $debugMode) {
$this->app->enqueueMessage($e->getMessage(), $e->getErrorLevel());
} elseif($e->getErrorLevel() != 'notice') {
$this->app->enqueueMessage($e->getMessage(), $e->getErrorLevel());
}
$resultSourceObject->data = array();
return $resultSourceObject;
} catch (Exception $e) {
$jmapException = new JMapException($e->getMessage(), 'warning');
$this->app->enqueueMessage(sprintf(JText::_('COM_JMAP_ERROR_RETRIEVING_DATA_FROM_USER_DATASOURCE' ), $source->name), 'warning');
if($debugMode) {
$this->app->enqueueMessage($jmapException->getMessage(), $jmapException->getErrorLevel());
}
$resultSourceObject->data = array();
return $resultSourceObject;
}
}
break;
case 'menu':
$menuAccess = null;
$originalSourceItems = array();
$autoExcludeNoIndex = null;
// Unpublished items
$doUnpublishedItems = $resultSourceObject->params->get('dounpublished', 0);
// Exclusion menu
$subQueryExclusion = null;
$exclusionMenuItems = $resultSourceObject->params->get('exclusion', array());
if($exclusionMenuItems && !is_array($exclusionMenuItems)) {
$exclusionMenuItems = array($exclusionMenuItems);
}
if(count($exclusionMenuItems) && !in_array('0',$exclusionMenuItems)) {
$subQueryExclusion = "\n AND menuitems.id NOT IN (" . implode(',', $exclusionMenuItems) . ")";
}
$queryChunk = null;
if(!$doUnpublishedItems) {
$queryChunk = "\n AND menuitems.published = 1";
}
// Filter by access if ACL option enabled
if($disableAcl !== 'disabled') {
$menuAccess = "\n AND menuitems.access IN ( " . implode(',', $accessLevels) . " )";
}
// Filter by language only if multilanguage is correctly enabled by Joomla! plugin
$menuLanguageFilter = null;
if(JMapLanguageMultilang::isEnabled()) {
$menuLanguageFilter = "\n AND ( menuitems.language = " . $this->_db->quote('*') . " OR menuitems.language = " . $this->_db->quote($this->langTag) . " ) ";
}
// Auto exclude noindex articles from XML sitemaps
$format = $this->getState('format');
if($format != 'html' && $this->cparams->get('auto_exclude_noindex', 0)) {
$autoExcludeNoIndex = "\n AND (menuitems.params NOT REGEXP 'noindex')";
}
$menuQueryItems = "SELECT menuitems.*, menuitems.parent_id AS parent, menuitems.level AS sublevel, menuitems.title AS name, menupriorities.priority" .
"\n FROM #__menu as menuitems" .
"\n INNER JOIN #__menu_types AS menutypes" .
"\n ON menuitems.menutype = menutypes.menutype" .
"\n LEFT JOIN #__jmap_menu_priorities AS menupriorities" .
"\n ON menupriorities.id = menuitems.id" .
"\n WHERE menuitems.client_id = 0 AND menuitems.published >= 0" . $queryChunk .
$menuAccess .
$autoExcludeNoIndex .
"\n AND menutypes.title = " . $this->_db->quote($source->name) .
$menuLanguageFilter .
$subQueryExclusion .
"\n ORDER BY menuitems.menutype, menuitems.parent_id, menuitems.level, menuitems.lft";
// Check if a limit for query rows has been set, this means we are in precaching process by JS App client
if(!$this->limitRows) {
$this->_db->setQuery ( $menuQueryItems );
} else {
$this->_db->setQuery ( $menuQueryItems, $this->limitStart, $this->limitRows);
}
try {
$originalSourceItems = $this->_db->loadObjectList();
if ($this->_db->getErrorNum ()) {
throw new JMapException(sprintf(JText::_('COM_JMAP_ERROR_RETRIEVING_DATA' ), $source->name), 'notice');
}
// Detected a precaching call, so store in the model state the number of affected rows for JS app
if($this->limitRows) {
$this->setState('affected_rows', count($originalSourceItems));
}
// Recursive ordering for menu rows ONLY if HTML format, otherwise make no sense so save resources and time
if($this->documentFormat == 'html') {
$sourceItems = $this->sortMenu ( $originalSourceItems, $resultSourceObject->params);
} else {
$sourceItems = $originalSourceItems;
}
} catch (JMapException $e) {
$this->app->enqueueMessage($e->getMessage(), $e->getErrorLevel());
$resultSourceObject->data = array();
return $resultSourceObject;
} catch (Exception $e) {
$jmapException = new JMapException($e->getMessage(), 'error');
$this->app->enqueueMessage($jmapException->getMessage(), $jmapException->getErrorLevel());
$resultSourceObject->data = array();
return $resultSourceObject;
}
break;
case 'content':
$access = null;
$catAccess = null;
$limitRecent = null;
$limitFeatured = null;
$autoExcludeNoIndex = null;
$dynamicSelectFields = null;
$dynamicOrdering = null;
$categoriesJoin = 'RIGHT';
$defaultOrdering = 'c.ordering';
$usersTableJoin = null;
$exclusionWay = $resultSourceObject->params->get('choose_exclusion_way', 'exclude');
$exclusionWayOperator = $exclusionWay == 'exclude' ? 'NOT' : '';
$now = gmdate('Y-m-d H:i:s', time());
// Exclusion access for Google News Sitemap and if ACL is disabled
$format = $this->getState('format');
// Set select fields only if html sitemap format is detected, save memory if XML and not needed
if($format == 'html') {
$dynamicSelectFields = 'c.title AS title, cat.title AS category, cat.level,';
}
// Set select fields only if RSS sitemap format is detected, save memory if not needed
if($format == 'rss' || $format == 'gnews') {
$dynamicSelectFields = 'c.title AS title, cat.title AS category,';
}
// Check if article images are required to be added to the RSS feed text desc
if($format == 'rss' && (int)$this->cparams->get('rss_include_images', 0)) {
$dynamicSelectFields .= 'c.images,';
}
// Check if article author is required to be added to the RSS feed
if($format == 'rss' && (int)$this->cparams->get('rss_include_author', 0)) {
$usersTableJoin = "\n LEFT JOIN " . $this->_db->quoteName('#__users') . " AS u ON c.created_by = u.id";
$dynamicSelectFields .= 'u.name AS authorname, u.email AS authoremail,';
}
// Manage content articles order
if($format != 'html' && (int)$resultSourceObject->params->get('orderbydate', 0) == 1) {
$dynamicOrdering = "created DESC,";
}
// Manage content articles order
if($format != 'rss' && $format != 'html' && (int)$resultSourceObject->params->get('orderbydate', 0) == 2) {
$dynamicOrdering = "modified DESC,";
}
// Manage content articles order
if($format != 'html' && (int)$resultSourceObject->params->get('orderbydate', 0) == 3) {
$dynamicOrdering = "publish_up DESC,";
}
// Fallback for rss feed always created desc
if($format == 'rss' && !$dynamicOrdering) {
$dynamicOrdering = "created DESC,";
}
// Manage content articles order for the HTML sitemap format
if($format == 'html' && $resultSourceObject->params->get('orderbyalpha', 0)) {
$defaultOrdering = "c.title ASC";
}
if($format != 'gnews' && $disableAcl !== 'disabled') {
$access = "\n AND c.access IN ( " . implode(',', $accessLevels) . " )";
$catAccess = "\n AND cat.access IN ( " . implode(',', $accessLevels) . " )";
}
// Choose to limit valid articles for Google News Sitemap to last n most recent days
if($format == 'gnews' && $this->cparams->get('gnews_limit_recent', false)) {
$validDays = $this->cparams->get('gnews_limit_valid_days', 2);
$limitRecent = "\n AND UNIX_TIMESTAMP(c.publish_up) > " . (time() - (24 * 60 * 60 * $validDays));
}
// Choose to limit valid articles for the RSS feed to last n most recent days
if($format == 'rss' && (int)$this->cparams->get('rss_limit_valid_days', null)) {
$validDays = (int)$this->cparams->get('rss_limit_valid_days');
$limitRecent = "\n AND UNIX_TIMESTAMP(c.publish_up) > " . (time() - (24 * 60 * 60 * $validDays));
}
// Choose to filter only by featured articles
if((int)$resultSourceObject->params->get('limit_featured_articles', 0)) {
$limitFeatured = "\n AND c.featured = 1";
}
// Exclusion categories
$subQueryCatExclusion = null;
$subQueryCategoryExclusion = null;
$exclusionCategories = $resultSourceObject->params->get('catexclusion', array());
// Normalize select options
if($exclusionCategories && !is_array($exclusionCategories)) {
$exclusionCategories = array($exclusionCategories);
}
// Exclusion children categories da table orm nested set model
if(count($exclusionCategories)) {
JTable::addIncludePath(JPATH_LIBRARIES . '/joomla/database/table');
$categoriesTableNested = JTable::getInstance('Category');
$children = array();
foreach ($exclusionCategories as $topCatID) {
// Load Children categories se presenti
$categoriesTableNested->load($topCatID);
$tempChildren = $categoriesTableNested->getTree();
if(is_array($tempChildren) && count($tempChildren)) {
foreach ($tempChildren as $child) {
if(!in_array($child->id, $children) && !in_array($child->id, $exclusionCategories)) {
$exclusionCategories[] = $child->id;
}
}
}
}
$subQueryCatExclusion = "\n AND c.catid $exclusionWayOperator IN (" . implode(',', $exclusionCategories) . ")";
$subQueryCategoryExclusion = "\n AND cat.id $exclusionWayOperator IN (" . implode(',', $exclusionCategories) . ")";
}
// Exclusion articles
$subQueryArticleExclusion = null;
$exclusionArticles = $resultSourceObject->params->get('articleexclusion', array());
// Normalize select options
if($exclusionArticles && !is_array($exclusionArticles)) {
$exclusionArticles = array($exclusionArticles);
}
if(count($exclusionArticles)) {
$subQueryArticleExclusion = "\n AND c.id $exclusionWayOperator IN (" . implode(',', $exclusionArticles) . ")";
}
// Evaluate content levels to include
$includeArchived = $this->cparams->get('include_archived', 0);
$contentLevel = $includeArchived ? ' > 0' : ' = 1';
// Filter by language only if multilanguage is correctly enabled by Joomla! plugin
$contentLanguageFilter = null;
$categoryLanguageFilter = null;
if(JMapLanguageMultilang::isEnabled()) {
$contentLanguageFilter = "\n AND ( c.language = " . $this->_db->quote('*') . " OR c.language = " . $this->_db->quote($this->langTag) . " ) ";
$categoryLanguageFilter = "\n AND ( cat.language = " . $this->_db->quote('*') . " OR cat.language = " . $this->_db->quote($this->langTag) . " ) ";
}
// Check if pagebreaks analysis is required
$pageBreaksFullText = null;
if($pageBreaksLinks = $this->cparams->get('show_pagebreaks', 0)) {
$pageBreaksFullText = "\n ,CONCAT(c.introtext, c.fulltext) AS completetext";
}
// Check if limit by recent months is set for content data source
// Manage for latest months placeholder if found one
$limitLatestItems = null;
if($monthsLimit = $resultSourceObject->params->get('created_date', null)) {
$minValidCreatedDate = gmdate ( "Y-m-d H:i:s", strtotime ( "-" . $monthsLimit . " months", time()));
$limitLatestItems = "\n AND c.created > " . $this->_db->quote($minValidCreatedDate);
// If we are on a precaching process plus a limit by recent month avoid empty records because of the RIGHT JOIN #__categories
if($this->limitRows) {
$categoriesJoin = 'LEFT';
}
}
// Auto exclude noindex articles from XML sitemaps
if($format != 'html' && $this->cparams->get('auto_exclude_noindex', 0)) {
$autoExcludeNoIndex = "\n AND (c.metadata NOT REGEXP 'noindex')";
}
$contentQueryItems = "SELECT c.id, c.alias, c.language, c.publish_up, c.access, c.metakey, catspriorities.priority," .
$dynamicSelectFields .
"\n cat.id AS catid," .
"\n UNIX_TIMESTAMP(c.modified) AS modified," .
"\n c.catid AS catslug" .
$pageBreaksFullText .
"\n FROM " . $this->_db->quoteName('#__content') . " AS c" .
"\n LEFT JOIN #__jmap_cats_priorities AS catspriorities ON catspriorities.id = c.catid" .
$usersTableJoin .
"\n $categoriesJoin JOIN #__categories AS cat ON cat.id = c.catid" .
"\n AND c.state $contentLevel".
"\n AND ( c.publish_up = " . $this->_db->quote($this->_db->getNullDate()) . " OR c.publish_up <= '$now' )" .
"\n AND ( c.publish_down = " . $this->_db->quote($this->_db->getNullDate()) . " OR c.publish_down >= '$now' )" .
$limitRecent .
$limitFeatured .
$limitLatestItems .
$access .
$contentLanguageFilter .
$subQueryCatExclusion .
$subQueryArticleExclusion .
$autoExcludeNoIndex .
"\n WHERE cat.published = '1'" .
$catAccess .
"\n AND cat.extension = " . $this->_db->quote('com_content') .
$categoryLanguageFilter .
$subQueryCategoryExclusion .
"\n ORDER BY $dynamicOrdering cat.lft, $defaultOrdering";
// Runtime preprocessing for RSS description field
if($this->documentFormat === 'rss') {
$contentQueryItems = $this->runtimeRssPreProcessing('c', $contentQueryItems);
}
// Check if a limit for query rows has been set, this means we are in precaching process by JS App client
if(!$this->limitRows) {
$this->_db->setQuery ( $contentQueryItems );
} else {
$this->_db->setQuery ( $contentQueryItems, $this->limitStart, $this->limitRows);
}
try {
$sourceItems = $this->_db->loadObjectList ();
if ($this->_db->getErrorNum ()) {
throw new JMapException(sprintf(JText::_('COM_JMAP_ERROR_RETRIEVING_DATA' ), $source->name), 'notice');
}
// Detected a precaching call, so store in the model state the number of affected rows for JS app
if($this->limitRows) {
$this->setState('affected_rows', count($sourceItems));
}
// Sub article pagebreaks processing
if($pageBreaksLinks) {
foreach ($sourceItems as $article) {
$this->addPagebreaks($article);
}
}
} catch (JMapException $e) {
$this->app->enqueueMessage($e->getMessage(), $e->getErrorLevel());
$resultSourceObject->data = array();
return $resultSourceObject;
} catch (Exception $e) {
$jmapException = new JMapException($e->getMessage(), 'error');
$this->app->enqueueMessage($jmapException->getMessage(), $jmapException->getErrorLevel());
$resultSourceObject->data = array();
return $resultSourceObject;
}
break;
case 'plugin':
// Get the language param for the user data source and ensure that it's all langs or match the current language, if not skip getting data
$languageTag = JMapLanguageMultilang::getCurrentSefLanguage();
$dataSourceLanguage = $resultSourceObject->params->get('datasource_language', '*');
if($dataSourceLanguage != '*' && ($languageTag != $dataSourceLanguage)) {
// Detected a precaching call, set 0 affected rows to complete correctly the precaching process just now
if($this->limitRows) {
$this->setState('affected_rows', 0);
}
break;
}
// Call the plugin interface and retrieve data
$pluginName = strtolower($source->name);
$className = 'JMapFilePlugin' . ucfirst($source->name);
try {
// Check if the plugin interface implementation exists
if(!file_exists(JPATH_ADMINISTRATOR . '/components/com_jmap/plugins/' . $pluginName . '/' . $pluginName . '.php')) {
throw new JMapException(sprintf(JText::_('COM_JMAP_ERROR_PLUGIN_DATASOURCE_NOT_EXISTS' ), $pluginName . '.php'), 'warning');
}
// Include for multiple instances of this data source
include_once JPATH_ADMINISTRATOR . '/components/com_jmap/plugins/' . $pluginName . '/' . $pluginName . '.php';
// Check if the concrete class exists now
if(!class_exists($className)) {
throw new JMapException(sprintf(JText::_('COM_JMAP_ERROR_PLUGIN_CLASS_NOT_EXISTS' ), $className), 'warning');
}
// Load the language file for the plugin, manage partial language translations
$jLang = JFactory::getLanguage();
$jLang->load($pluginName, JPATH_ADMINISTRATOR . '/components/com_jmap/plugins/' . $pluginName, 'en-GB', true, true);
if($jLang->getTag() != 'en-GB') {
$jLang->load($pluginName, JPATH_ADMINISTRATOR . '/components/com_jmap/plugins/' . $pluginName, null, true, false);
}
// Instantiate the plugin class, inject $this class and manage limitRows for precaching in third party plugins
$pluginInstance = new $className();
$retrievedData = $pluginInstance->getSourceData($resultSourceObject->params, $this->_db, $this);
// 1) first structure required: plain list of items -> PLAIN LIST OF ELEMENTS
if(!array_key_exists('items', $retrievedData)) {
throw new JMapException(sprintf(JText::_('COM_JMAP_ERROR_PLUGIN_NODATA_RETURNED' ), $pluginName), 'warning');
}
$sourceItems = $retrievedData['items'];
// Check if additional structures for nested categories tree are returned
// 2) second structure optional: plain list of items grouped by cats -> LIST OF ELEMENTS GROUPED BY PLAIN CATS STRUCTURE
if(array_key_exists('items_tree', $retrievedData)) {
$resultSourceObject->itemsTree = $retrievedData['items_tree'];
}
// 3) third structure optional: nested tree of cats by parents -> LIST OF ELEMENTS GROUPED BY NESTED CATS STRUCTURE
if(array_key_exists('categories_tree', $retrievedData)) {
$resultSourceObject->categoriesTree = $retrievedData['categories_tree'];
}
} catch (JMapException $e) {
if($e->getErrorLevel() == 'notice' && $debugMode) {
$this->app->enqueueMessage($e->getMessage(), $e->getErrorLevel());
} elseif($e->getErrorLevel() != 'notice') {
$this->app->enqueueMessage($e->getMessage(), $e->getErrorLevel());
}
$resultSourceObject->data = array();
return $resultSourceObject;
} catch (Exception $e) {
$debugMode = $this->cparams->get('enable_debug', 0);
$jmapException = new JMapException($e->getMessage(), 'warning');
$this->app->enqueueMessage(sprintf(JText::_('COM_JMAP_ERROR_RETRIEVING_DATA_FROM_USER_DATASOURCE' ), $source->name), 'warning');
if($debugMode) {
$this->app->enqueueMessage($jmapException->getMessage(), $jmapException->getErrorLevel());
}
$resultSourceObject->data = array();
return $resultSourceObject;
}
break;
case 'links':
// Re-initialize the $sourceItems var as an object not an array
$sourceItems = new stdClass();
$sourceItems->title = array();
$sourceItems->link = array();
// Get the language param for the links data source and ensure that it's all langs or match the current language, if not skip getting data
$languageTag = JMapLanguageMultilang::getCurrentSefLanguage();
$dataSourceLanguage = $resultSourceObject->params->get('datasource_language', '*');
if($dataSourceLanguage != '*' && ($languageTag != $dataSourceLanguage)) {
// Detected a precaching call, set 0 affected rows to complete correctly the precaching process just now
if($this->limitRows) {
$this->setState('affected_rows', 0);
}
break;
}
// Check if a limit for query rows has been set, this means we are in precaching process by JS App client
if(!$this->limitRows) {
// Get directly the links from the source
$sourceItems = $resultSourceObject->chunks;
} else {
// Get directly the links from the source
$sourceItems->title = array_slice($resultSourceObject->chunks->title, $this->limitStart, $this->limitRows, false);
$sourceItems->link = array_slice($resultSourceObject->chunks->link, $this->limitStart, $this->limitRows, false);
// Detected a precaching call, so store in the model state the number of affected rows for JS app
$this->setState('affected_rows', count($sourceItems->link));
}
break;
}
// Final assignment
$resultSourceObject->data = $sourceItems;
return $resultSourceObject;
}
/**
* Get available sitemap source
* @access private
* @return array
*/
private function getSources() {
$join = null;
$where = array();
// Check exclude from menu view params data sources IDs
$filterDataSource = $this->getState('cparams')->get('datasource_filter', array());
// Only if first array item is not the 'No filter' false first option of multiselect
if(!empty($filterDataSource) && $filterDataSource[0]) {
$where[] = "\n v.id IN (" . implode(',', $filterDataSource) . ")";
}
// Check if JS client app has set some data source restrictions
if($dataSourceID = $this->getState('datasourceid', null)) {
$where[] = "\n v.id = " . $this->_db->quote($dataSourceID);
}
// Check if some restrictions based on dataset filter are found
$filterDataset = $this->app->input->getInt('dataset', null);
if(!empty($filterDataset)) {
$join = "\n INNER JOIN #__jmap_dss_relations AS dss" .
"\n ON v.id = dss.datasourceid";
$where[] = "\n dss.datasetid = " . (int)$filterDataset;
}
// Default for published data sources
$where[] = "\n v.published = 1";
$query = "SELECT v.*" .
"\n FROM #__jmap AS v" .
$join .
"\n WHERE " . implode(' AND ', $where) .
"\n ORDER BY v.ordering ASC";
$this->_db->setQuery ( $query );
$this->sources = $this->_db->loadObjectList ();
return $this->sources;
}
/**
* Load manifest file for this type of data source
* @access private
* @return mixed
*/
private function loadManifest($option) {
// Load configuration manifest file
$fileName = JPATH_SITE . '/components/com_jmap/manifests/' . $option . '.json';
// Check if file exists and is valid manifest
if(!file_exists($fileName)) {
return false;
}
// Load the manifest serialized file and assign to local variable
$manifest = file_get_contents($fileName);
$manifestConfiguration = json_decode($manifest);
return $manifestConfiguration;
}
/**
* Load manifest file for this type of data source
* @access private
* @return mixed
*/
private function loadRouteManifest($option) {
// Load configuration manifest file
$fileName = JPATH_ADMINISTRATOR . '/components/com_jmap/framework/route/manifests/' . $option . '.json';
// Check if file exists and is valid manifest
if(!file_exists($fileName)) {
return false;
}
// Load the manifest serialized file and assign to local variable
$manifest = file_get_contents($fileName);
$manifestConfiguration = json_decode($manifest);
return $manifestConfiguration;
}
/**
* Detect if a data source has ideally a categorization active, through title categorization param set and not empty
*
* @access private
* @param Object $dataSource
* @param boolean $ignoreFormat
* @return boolean
*/
private function hasCategorization($dataSource, $ignoreFormat = false) {
// Check first of all for right focument format, used only for presentation purpouse for HTML document format
if(!is_null($this->documentFormat) && $ignoreFormat === false) {
if($this->documentFormat != 'html') {
return false;
}
}
// Check if data source is elated to categories entities itself
if(preg_match('/categor|cats|catg/i', $dataSource->chunks->table_maintable)) {
return true;
}
// Check if a valid field has been chosen and activated for category titles
if(isset($dataSource->chunks->use_category_title_jointable1) && $dataSource->chunks->use_category_title_jointable1) {
return true;
}
if(isset($dataSource->chunks->use_category_title_jointable2) && $dataSource->chunks->use_category_title_jointable2) {
return true;
}
if(isset($dataSource->chunks->use_category_title_jointable3) && $dataSource->chunks->use_category_title_jointable3) {
return true;
}
return false;
}
/**
* Fetch default table fields used to filter out categories query
*
* @access protected
* @param string $tableName
* @return array
*/
protected function fetchAutoInjectDefaultWhereFields($tableName, $manifestConfig, $source) {
$whereConditions = array();
$excludeConditions = isset($manifestConfig->category_exclude_condition) ? $manifestConfig->category_exclude_condition : array();
// Get required maintable table fields, if not valid throw exception
$columnsQuery = "SHOW COLUMNS FROM " . $this->_db->quoteName($tableName);
$this->_db->setQuery($columnsQuery);
try {
if($tableFields = $this->_db->loadColumn()) {
// *AUTO WHERE PART* injected fields
if(is_array($tableFields) && count($tableFields)) {
// Published field supported
if(in_array('published', $tableFields) && !(in_array('published', $excludeConditions))) {
$whereConditions[] = $this->_db->quoteName($tableName) . "." . $this->_db->quoteName('published') . " = " . $this->_db->quote(1);
} elseif(in_array('state', $tableFields) && !(in_array('state', $excludeConditions))) { // State field supported fallback
$whereConditions[] = $this->_db->quoteName($tableName) . "." . $this->_db->quoteName('state') . " = " . $this->_db->quote(1);
}
// Access field supported
if(in_array('access', $tableFields) && !(in_array('access', $excludeConditions))) {
if(!$this->accessLevel) {
// Load access level if not loaded
$user = JFactory::getUser();
$this->accessLevel = $user->getAuthorisedViewLevels();
}
$whereConditions[] = $this->_db->quoteName($tableName) . "." . $this->_db->quoteName('access') . " IN (" . implode(',', $this->accessLevel) . ")";
}
// Language field supported
if(in_array('language', $tableFields) && !(in_array('language', $excludeConditions))) {
$whereConditions[] = " (" . $this->_db->quoteName($tableName) . "." . $this->_db->quoteName('language') . " = " . $this->_db->quote('*') .
" OR " . $this->_db->quoteName($tableName) . "." . $this->_db->quoteName('language') . " = " . $this->_db->quote('') .
" OR " . $this->_db->quoteName($tableName) . "." . $this->_db->quoteName('language') . " = " . $this->_db->quote($this->langTag) . ")";
}
// Explicit category conditions from manifest config file
if(isset($manifestConfig->category_condition) && is_array($manifestConfig->category_condition)) {
foreach ($manifestConfig->category_condition as $condition) {
if(!empty($source->chunks->where1_maintable) && !empty($source->chunks->where1_value_maintable) && strpos($condition, '$$$')) {
if(strpos($source->chunks->where1_value_maintable, ',')) {
$condition = str_replace('@', ' IN(', $condition);
$condition = str_replace('$$$', $source->chunks->where1_value_maintable . ')', $condition);
} else {
$condition = str_replace('@', '=', $condition);
$condition = str_replace('$$$', $source->chunks->where1_value_maintable, $condition);
}
} elseif(strpos($condition, '$$$')) {
$condition = null;
}
// Assignment if valid $condition
if($condition) {
$whereConditions[] = $condition;
}
}
}
}
}
} catch (Exception $e) {
return false;
}
return $whereConditions;
}
/**
* Postprocessing at runtime for third party extensions subqueries
* that generate data for nested cats tree
* New data will be appended to catsTree property of resultSourceObject
*
* @access protected
* @param Object $source
* @return array An array of objects, one for products by cat and one for catChildren by cat, also assigned to $source properties to be used inside view
*/
protected function subQueriesPostProcessor($source, $manifest = null) {
// Replace these variables reading from manifest json file
if($manifest) {
$manifestConfiguration = json_decode($manifest);
} else {
$manifestConfiguration = $this->loadManifest ($source->chunks->option);
if($manifestConfiguration === false) {
return false;
}
}
// Error decoding configuration object, exit and fallback to standard indenting
if(!is_object($manifestConfiguration)) {
throw new JMapException(JText::sprintf('COM_JMAP_ERROR_MULTILEVELCATS_MANIFEST_ERROR', $source->name), 'notice');
}
// Detect if cat recoursion is enabled
if($manifestConfiguration->catrecursion) {
// Enable cat recursion for this data source
$source->catRecursion = true;
} else {
return false;
}
// Recursion type not adiacency, already managed natively by raw sql query compiler and standard indenting by level value
if(!in_array($manifestConfiguration->recursion_type, array('adiacency', 'multiadiacency'))) {
return false;
}
// Init query chunks
$selectCatName = null;
$validCategoryCondition = null;
$categoriesJoinCondition = null;
$additionalCategoriesTableCondition = null;
$additionalCategoriesSorting = null;
$validCategory2CategoryCondition = null;
$asCategoryTableIdField = $manifestConfiguration->category_table_id_field;
$asCategoryTableNameField = str_replace('{langtag}', $this->langTag, $manifestConfiguration->category_table_name_field);
// Category table
$categoriesTable = $manifestConfiguration->categories_table;
// SELECT for catname
$selectCatName = $this->_db->quoteName($categoriesTable) . "." . $this->_db->quoteName($asCategoryTableNameField) . " AS " . $this->_db->quoteName('catname');
// Parent field
$asCategoryParentIdField = $manifestConfiguration->parent_field;
// Child field #Optional
$asCategoryChildIdField = $manifestConfiguration->child_field;
// Categories 2 categories table
$category2categoryTable = $manifestConfiguration->category2category_table;
// Valid category condition
if($categoryConditions = $this->fetchAutoInjectDefaultWhereFields($categoriesTable, $manifestConfiguration, $source)) {
$validCategoryCondition = "\n WHERE " . implode("\n AND ", $categoryConditions);
}
// Detect type of adiacency set model for database tables
switch($manifestConfiguration->recursion_type) {
case 'multiadiacency':
// Additional categories table required for some weird reason from 3PD developers
// Field cat id su record entities that MUST match, used by multi adiacency instead of jsitemap_category and must be needed from route string, is not unset
if(isset($manifestConfiguration->additional_categories_table) && isset($manifestConfiguration->additional_categories_table_on_catid_field)) {
if(strpos($manifestConfiguration->additional_categories_table, '$$$')) {
$additionalCategoriesTable = str_replace('$$$', '_' . $this->siteLanguageRFC, $manifestConfiguration->additional_categories_table);
// Check if $additionalCategoriesTable exists otherwise fallback and replace again with default site language hoping that table exists
$checkTableQuery = "SELECT 1 FROM " . $this->_db->quoteName($additionalCategoriesTable);
$tableExists = $this->_db->setQuery($checkTableQuery)->loadResult();
if(!$tableExists) {
$additionalCategoriesTable = str_replace('$$$', '_' . $this->fallbackDefaultLanguageRFC, $manifestConfiguration->additional_categories_table);
}
} else {
$additionalCategoriesTable = $manifestConfiguration->additional_categories_table;
}
$additionalCategoriesTableOnCatidField = $manifestConfiguration->additional_categories_table_on_catid_field;
$selectCatName = $this->_db->quoteName($additionalCategoriesTable) . "." . $this->_db->quoteName($asCategoryTableNameField) . " AS " . $this->_db->quoteName('catname');
if(isset($manifestConfiguration->additional_categories_table_on_fkcatid_field)) {
$additionalCategoriesTableOnFKCatidField = $manifestConfiguration->additional_categories_table_on_fkcatid_field;
$categoriesJoinCondition = "\n INNER JOIN " . $this->_db->quoteName($additionalCategoriesTable) .
"\n ON " . $this->_db->quoteName($categoriesTable) . "." . $this->_db->quoteName($additionalCategoriesTableOnCatidField) . " = " .
$this->_db->quoteName($additionalCategoriesTable) . "." . $this->_db->quoteName($additionalCategoriesTableOnFKCatidField);
} else {
$categoriesJoinCondition = "\n INNER JOIN " . $this->_db->quoteName($additionalCategoriesTable) .
"\n ON " . $this->_db->quoteName($categoriesTable) . "." . $this->_db->quoteName($additionalCategoriesTableOnCatidField) . " = " .
$this->_db->quoteName($additionalCategoriesTable) . "." . $this->_db->quoteName($additionalCategoriesTableOnCatidField);
}
}
if(isset($manifestConfiguration->additional_categories_table_condition)) {
$languageID = JMapLanguageMultilang::loadLanguageID($this->langTag);
$additionalCategoriesTableCondition = str_replace('$$$', $languageID, "\n WHERE " . $manifestConfiguration->additional_categories_table_condition);
}
if(isset($manifestConfiguration->additional_and_categories_table_condition)) {
$additionalCategoriesTableCondition = str_replace('$$$', $this->_db->quote($this->langTag), "\n AND " . $manifestConfiguration->additional_and_categories_table_condition);
}
if(isset($manifestConfiguration->additional_categories_sorting)) {
$additionalCategoriesSorting = "\n ORDER BY " . $manifestConfiguration->additional_categories_sorting;
}
// Valid category 2 category condition
if(isset($manifestConfiguration->category2category_condition)) {
if(!empty($source->chunks->where1_maintable) && !empty($source->chunks->where1_value_maintable) && strpos($manifestConfiguration->category2category_condition, '$$$')) {
if(strpos($source->chunks->where1_value_maintable, ',')) {
$validCategory2CategoryCondition = str_replace('@', ' IN(', $manifestConfiguration->category2category_condition);
$validCategory2CategoryCondition = str_replace('$$$', $source->chunks->where1_value_maintable . ')', $validCategory2CategoryCondition);
} else {
$validCategory2CategoryCondition = str_replace('@', '=', $manifestConfiguration->category2category_condition);
$validCategory2CategoryCondition = str_replace('$$$', $source->chunks->where1_value_maintable, $validCategory2CategoryCondition);
}
} elseif(strpos($manifestConfiguration->category2category_condition, '$$$')) {
$validCategory2CategoryCondition = null;
} else {
$validCategory2CategoryCondition = $manifestConfiguration->category2category_condition;
}
}
$where = $validCategory2CategoryCondition ? "\n WHERE " . $validCategory2CategoryCondition : null;
$query = "SELECT " .
$this->_db->quoteName($asCategoryParentIdField) . " AS " . $this->_db->quoteName('parent') . "," .
$this->_db->quoteName($asCategoryChildIdField) . " AS " . $this->_db->quoteName('child') .
"\n FROM " . $this->_db->quoteName($category2categoryTable) .
$where;
$totalItemsCatsTree = $this->_db->setQuery($query)->loadAssocList('child');
// Cancel post processor effect if db error detected and fallback on standard one level tree
if ($this->_db->getErrorNum ()) {
return false;
}
break;
case 'adiacency';
default;
$query = "SELECT " .
$this->_db->quoteName($asCategoryParentIdField) . " AS " . $this->_db->quoteName('parent') . "," .
$this->_db->quoteName($asCategoryChildIdField) . " AS " . $this->_db->quoteName('child') .
"\n FROM " . $this->_db->quoteName($category2categoryTable);
$totalItemsCatsTree = $this->_db->setQuery($query)->loadAssocList('child');
// Cancel post processor effect if db error detected and fallback on standard one level tree
if ($this->_db->getErrorNum ()) {
return false;
}
break;
}
// First pass organize items by cats
$itemsByCats = array();
if(count($source->data)) {
foreach ($source->data as $item) {
$itemsByCats[$item->jsitemap_category_id][] = $item;
}
}
// ASSIGNMENT TO SOURCE
$source->itemsByCat = $itemsByCats;
// Grab total items cats IDs/Names and inject auto fields
$query = "SELECT DISTINCT " .
$this->_db->quoteName($categoriesTable) . "." . $this->_db->quoteName($asCategoryTableIdField) . " AS " . $this->_db->quoteName('id') . "," .
$selectCatName .
"\n FROM " . $this->_db->quoteName($categoriesTable) .
$categoriesJoinCondition .
$validCategoryCondition .
$additionalCategoriesTableCondition .
$additionalCategoriesSorting;
$totalItemsCats = $this->_db->setQuery($query)->loadAssocList();
// Cancel post processor effect if db error detected and fallback on standard one level tree
if ($this->_db->getErrorNum ()) {
return false;
}
// Second pass organize categories by parent - children
$childrenCats = array();
if(count($totalItemsCats)) {
foreach ($totalItemsCats as $childCat) {
$parentCat = $totalItemsCatsTree[$childCat['id']]['parent'];
$childrenCats[$parentCat][] = $childCat;
}
}
// ASSIGNMENT TO SOURCE
$source->catChildrenByCat = $childrenCats;
return array($itemsByCats, $childrenCats);
}
/**
* Get the Data
* @access public
* @return array
*/
public function getSitemapData() {
// Get the view
$this->sources = $this->getSources ();
$data = array ();
$user = JFactory::getUser();
// Getting degli access levels associati all'utente in base ai gruppi di appartenenza
$this->accessLevel = $user->getAuthorisedViewLevels();
// Get data for a view
foreach ( $this->sources as $source ) {
$sourceData = $this->getSourceData ( $source, $this->accessLevel );
// Data retrieved for this data source, assign safely
if($sourceData && !empty($sourceData->data)) {
$data[] = $sourceData;
}
}
$this->data = $data;
return $data;
}
/**
* Get excluded links from the sitemap
* @access public
* @param string $liveSite to replace
* @return array
*/
public function getExcludedLinks($liveSite) {
// Exclude filtering if the client is the backend metainfo that manages all
if($this->state->get('metainfojsclient', false)) {
return array();
}
$query = "SELECT REPLACE(" . $this->_db->quoteName('linkurl') . ", '" . $liveSite . "', '') AS " . $this->_db->quoteName('linkurl') . " ," .
"\n " . $this->_db->quoteName('excluded') .
"\n FROM #__jmap_metainfo" .
"\n WHERE " . $this->_db->quoteName('excluded') . " = 1" ;
$this->_db->setQuery ( $query );
$excludedLinks = $this->_db->loadAssocList ('linkurl', 'excluded');
return $excludedLinks;
}
/**
* Get the component params with view override/merge
* @access public
* @return Object
*/
public function getComponentParams() {
if(is_object($this->cparams)) {
return $this->cparams;
}
$this->cparams = $this->app->getParams('com_jmap');
return $this->cparams;
}
/**
* Export XML file for sitemap
*
* @access public
* @param string $contents
* @param string $fileNameSuffix
* @param string $fileNameFormat
* @param string $fileNameLanguage
* @param string $fileNameDatasetFilter
* @param string $fileNameItemidFilter
* @param string $mimeType
* @param boolean $isFile
* @return boolean
*/
public function exportXMLSitemap($contents, $fileNameSuffix, $fileNameFormat, $fileNameLanguage, $fileNameDatasetFilter, $fileNameItemidFilter, $mimeType, $isFile = false) {
$this->sendAsBinary($contents, 'sitemap_' . $fileNameSuffix . $fileNameLanguage . $fileNameDatasetFilter . $fileNameItemidFilter . '.' . $fileNameFormat, $mimeType, $isFile);
return false;
}
/**
* Class Constructor
* @access public
* @return Object&
*/
function __construct($config = array()) {
parent::__construct ($config);
$this->cparams = $this->app->getParams('com_jmap');
// Check if a module request is detected, in this case merge module params instead of menu view params: Component -> module specific level params override
if(isset($config['jmap_module']) && $config['jmap_module']) {
$query = $this->_db->getQuery(true);
$query->select('params');
$query->from('#__modules');
$query->where($this->_db->quoteName('id') . ' = ' . (int)$config['jmap_module']);
$strParams = $this->_db->setQuery($query)->loadResult();
$moduleParams = new JRegistry;
$moduleParams->loadString($strParams);
// Merge module params in place of view params simulating
$this->cparams->merge($moduleParams);
// Ensure all links will open up the parent main window
$this->cparams->set('opentarget', '_parent');
$this->setState ( 'jmap_module', $config['jmap_module'] );
}
$this->setState('cparams', $this->cparams);
$this->documentFormat = $config['document_format'];
$this->setState('documentformat', $this->documentFormat);
// Languages installed on this system
$langManager = JFactory::getLanguage();
$this->fallbackDefaultLanguage = $langManager->getDefault();
$this->fallbackDefaultLanguageRFC = str_replace('-', '_', strtolower($this->fallbackDefaultLanguage));
$this->langTag = $langManager->getTag();
$this->siteLanguageRFC = str_replace('-', '_', strtolower($this->langTag));
// Init supported 3PD extensions tables for Google news sitemap
$this->supportedGNewsTablesOptions = array('#__k2_items',
'#__zoo_item',
'#__easyblog_post',
'#__mt_links',
'#__jevents_vevdetail'
);
// Calculate limitstart and limitrows if precaching process detected
if(isset($config['iteration_counter'])) {
$formatLimitParam = in_array($this->documentFormat, array('images', 'videos')) ? $this->cparams->get('precaching_limit_images', 50) : $this->cparams->get('precaching_limit_xml', 5000);
$this->limitStart = $config['iteration_counter'] * $formatLimitParam;
$this->limitRows = $formatLimitParam;
}
}
}