<?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_COMPONENT_ADMINISTRATOR . '/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_COMPONENT_ADMINISTRATOR . '/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_COMPONENT_ADMINISTRATOR . '/plugins/' . $pluginName, 'en-GB', true, true); if($jLang->getTag() != 'en-GB') { $jLang->load($pluginName, JPATH_COMPONENT_ADMINISTRATOR . '/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_COMPONENT . '/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_COMPONENT_ADMINISTRATOR . '/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 width 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('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' ); // 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; } } }