<commit_before><?php
/**
 * @package framework.indexer
 * @author franck.stauffer
 */
class indexer_IndexService extends BaseService 
{
	const PUBLIC_DOCUMENT_ACCESSOR_ID = 0;
	const MAX_QUEUE_LENGTH = 100;
	const DEFAULT_SOLR_INDEXER_CLIENT = "defaultChangeIndexerClient";
	const BACKOFFICE_SUFFIX = '__backoffice';
	const INDEXER_MODE_BACKOFFICE = 0;
	const INDEXER_MODE_FRONTOFFICE = 1;
	
	const TO_INDEX = 'TO_INDEX';
	const TO_DELETE = 'TO_DELETE';
	const INDEXED = 'INDEXED';
	const DELETED = 'DELETED';
	
	/**
	 * @var indexer_IndexingService
	 */
	private static $instance = null;
	
	/**
	 * @var indexer_SolrManager
	 */
	private $manager = null;
	
	/**
	 * @var integer
	 */
	private $indexerMode = null;
	
	/**
	 * @var array
	 */
	private $modelsInfos;
	
	/**
	 * Get the service instance.
	 *
	 * @return indexer_IndexService
	 */
	public final static function getInstance()
	{
		if (null === self::$instance)
		{
			$newInstance = self::getServiceClassInstance(get_class());
			if (defined('SOLR_INDEXER_URL'))
			{
				$solrURL = SOLR_INDEXER_URL;
				if (!f_util_StringUtils::endsWith($solrURL, '/'))
				{
					$solrURL .= '/';
				}
				$newInstance->manager = new indexer_SolrManager($solrURL);
			}
			else
			{
				$newInstance->manager = new indexer_FakeSolrManager();
			}
			self::$instance = $newInstance;
		}
		return self::$instance;
	}
	
	/**
	 * @return integer
	 */
	public function getIndexerMode()
	{
		return $this->indexerMode;
	}
	
	public function loadModelsInfos()
	{
		$compiledFilePath = f_util_FileUtils::buildChangeBuildPath('indexableDocumentInfos.ser');
		if (!file_exists($compiledFilePath))
		{
			throw new Exception("File not found : $compiledFilePath. compile-documents needed");
		}
		$this->modelsInfos = unserialize(file_get_contents($compiledFilePath));
	}
	
	/**
	 * @return string[]
	 */
	public function getBackOfficeModelsName()
	{
		if ($this->modelsInfos === null)
		{
			$this->loadModelsInfos();
		}
		return $this->modelsInfos['bo'];
	}
	
	/**
	 * @return string[]
	 */
	public function getFrontOfficeModelsName()
	{
		if ($this->modelsInfos === null)
		{
			$this->loadModelsInfos();
		}
		return $this->modelsInfos['fo'];
	}	
	
	private $indexDocuments;
	private $indexBackofficeDocuments;
	
	public function beginIndexTransaction()
	{
		$this->indexDocuments = array();
		$this->indexBackofficeDocuments = array();
	}
	
	public function commitIndex()
	{
		if (is_array($this->indexDocuments))
		{
			if (Framework::isInfoEnabled())
			{
				Framework::info(__METHOD__ . ' IFO: ' . count($this->indexDocuments). ', IBO: '. count($this->indexBackofficeDocuments));
			}
			
			$pp = $this->getPersistentProvider();
			foreach ($this->indexDocuments as $id => $status) 
			{
				$pp->setIndexingDocumentStatus($id, self::INDEXER_MODE_FRONTOFFICE, $status);
			}
			
			foreach ($this->indexBackofficeDocuments as $id => $status) 
			{
				$pp->setIndexingDocumentStatus($id, self::INDEXER_MODE_BACKOFFICE, $status);
			}
			$this->indexDocuments = null;
			$this->indexBackofficeDocuments = null;
		}
	}
	
	public function rollBackIndex()
	{
		$this->indexDocuments = null;
		$this->indexBackofficeDocuments = null;
	}
	
	/**
	 * @param Boolean $bool
	 */
	public function setAutoCommit($bool)
	{
		if (!is_null($this->manager) && is_bool($bool))
		{
			$this->manager->setAutoCommit($bool);
		}
	}
	
	public function commit()
	{
		if (!is_null($this->manager))
		{
			$this->manager->sendCommit();
		}
	}
	
	/**
	 * @throws IllegalArgumentException
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function add($document)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$this->toIndexStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
		
	/**
	 * @throws IllegalArgumentException
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function addBackoffice($document)
	{
		try
		{
			$this->beginBackIndexerMode();
			$this->toIndexStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Update the indexer_IndexableDocument $document in the index. 
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function update($document)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$this->toIndexStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Update the indexer_IndexableDocument $document in the index. 
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function updateBackoffice($document)
	{
		try
		{
			$this->beginBackIndexerMode();
			$this->toIndexStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Delete the indexer_IndexableDocument $document from the index.
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function delete($document)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$this->toDeleteStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Delete the indexer_IndexableDocument $document from the index.
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function deleteBackoffice($document)
	{
		try
		{
			$this->beginBackIndexerMode();
			$this->toDeleteStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	public function clearBackofficeIndex()
	{
		if ($this->manager !== null)
		{
			try
			{
				
				$this->beginBackIndexerMode();
				$this->manager->clearIndexQuery();
				$this->manager->commit();
				

				
				$this->endIndexerMode();
			}
			catch (Exception $e)
			{
				$this->endIndexerMode($e);
			}
			
			try 
			{
				$this->getTransactionManager()->beginTransaction();
				$this->getPersistentProvider()->clearIndexingDocumentStatus(self::INDEXER_MODE_BACKOFFICE);
				$this->getTransactionManager()->commit();				
			} 
			catch (Exception $e) 
			{
				$this->getTransactionManager()->rollBack($e);
			}
		}
	}
	
	public function clearFrontofficeIndex()
	{
		if ($this->manager !== null)
		{
			try
			{
				$this->beginFrontIndexerMode();
				$this->manager->clearIndexQuery();
				$this->manager->commit();
				$this->endIndexerMode();
			}
			catch (Exception $e)
			{
				$this->endIndexerMode($e);
			}
			
			try 
			{
				$this->getTransactionManager()->beginTransaction();
				$this->getPersistentProvider()->clearIndexingDocumentStatus(self::INDEXER_MODE_FRONTOFFICE);
				$this->getTransactionManager()->commit();				
			} 
			catch (Exception $e) 
			{
				$this->getTransactionManager()->rollBack($e);
			}
		}
	}
	
	public function clearIndex()
	{
		$this->clearBackofficeIndex();
		$this->clearFrontofficeIndex();
	}
	
	public function optimizeIndex()
	{
		if ($this->manager !== null)
		{
			try
			{
				$this->beginBackIndexerMode();
				$this->manager->optimizeIndexQuery();
				$this->endIndexerMode();
				
				$this->beginFrontIndexerMode();
				$this->manager->optimizeIndexQuery();
				$this->endIndexerMode();
			}
			catch (Exception $e)
			{
				$this->endIndexerMode($e);
			}
		}
	}
	
	public function rebuildSpellCheckIndexForLang($lang)
	{
		if ($this->manager !== null)
		{
			try
			{
				$this->beginBackIndexerMode();
				$this->manager->rebuildSpellCheckIndexForLang($lang);
				$this->endIndexerMode();
				
				$this->beginFrontIndexerMode();
				$this->manager->rebuildSpellCheckIndexForLang($lang);
				$this->endIndexerMode();
			}
			catch (Exception $e)
			{
				$this->endIndexerMode($e);
			}
		}
	}
	

	/**
	 * Execute $query on the configured <strong>frontoffice</strong> indexer using the standard request handler
	 * (search on label and full text with a boost on the label). 
	 * 
	 * @param indexer_Query $query
	 * @param String[] $suggestionTerms
	 * @return indexer_SearchResults
	 */
	public function search(indexer_Query $query, $suggestionTerms = null)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$solrSearch = new indexer_StandardSolrSearch($query);
			if (f_util_ArrayUtils::isNotEmpty($suggestionTerms))
			{
				$solrSearch->doSuggestion($suggestionTerms);
			}
			$data = $this->manager->query($solrSearch);
			$searchResults = new indexer_SolrSearchResults($data, $solrSearch);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
		return $searchResults;
	}
	
	/**
	 * Execute $query on the configured <strong>backoffice</strong> indexer using the standard request handler
	 * (search on label and full text with a boost on the label). 
	 * 
	 * @param indexer_Query $query
	 * @return indexer_SearchResults
	 */
	public function searchBackoffice(indexer_Query $query)
	{
		try
		{
			$this->beginBackIndexerMode();
			$solrSearch = new indexer_StandardSolrSearch($query);
			$data = $this->manager->query($solrSearch);
			$searchResults = new indexer_SolrSearchResults($data, $solrSearch);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
		return $searchResults;
	}
	
	/**
	 * Execute $query on the configured <strong>frontoffice</strong> using solr's dismax request handler
	 * (see http://wiki.apache.org/solr/DisMaxRequestHandler). 
	 * 
	 * @param indexer_Query $query
	 * @return indexer_SearchResults
	 */
	public function dismaxSearch(indexer_Query $query)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$solrSearch = new indexer_DismaxSolrSearch($query);
			$data = $this->manager->query($solrSearch);
			$searchResults = new indexer_SolrSearchResults($data, $solrSearch);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
		return $searchResults;
	}
	
	/**
	 * Get a *single* suggestion for the word $word from the spellchecker for $lang. 
	 * If $lang is null, the RequestContext's lang is used.
	 *
	 * @param String $word
	 * @param String $lang
	 * @return String
	 */
	public function getSuggestionForWord($word, $lang = null)
	{
		$suggestions = $this->getSuggestionArrayForWord($word, $lang);
		if (count($suggestions) > 0)
		{
			return $suggestions[0];
		}
		return null;
	}
	
	/**
	 * Get a suggestion for a term list. This method is only here for compatibility
	 * with old SolR schema (2.0.4). You should use indexer_StandardSolrSearch::doSuggestion() method
	 * @param String[] $words
	 * @param String $lang
	 * @return String
	 */
	public function getSuggestionForWords($words, $lang = null)
	{
		if (indexer_SolrManager::getSchemaVersion() == "2.0.4")
		{
			throw new Exception("This method only works with SolR 1.4 + change schema 3.0.3");
		}
		try
		{
			$this->beginFrontIndexerMode();
			$query = new indexer_SuggestionSolrSearch($words, $lang);
			$query->setSuggestionCount(1);
			$data = $this->manager->query($query);
			$this->endIndexerMode();
			
			$dataDom = f_util_DOMUtils::fromString($data);
			$elem = $dataDom->findUnique("lst[@name='spellcheck']/lst[@name='suggestions']/str[@name='collation']");
			if ($elem !== null)
			{
				return $elem->textContent;
			}
			return null;
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Get an array of at most $count suggestions for the word $word from the spellchecker for $lang. 
	 *
	 * @param String $word
	 * @param String $lang
	 * @param String $count
	 * @return Array<String>
	 */
	public function getSuggestionArrayForWord($word, $lang = null, $count = null)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$query = new indexer_SuggestionSolrSearch($word, $lang);
			if (!is_null($count) && $count > 0)
			{
				$query->setSuggestionCount($count);
			}
			$data = $this->manager->query($query);
			$schemaVersion = indexer_SolrManager::getSchemaVersion();
			if ($schemaVersion == "2.0.4")
			{
				$searchResults = $this->manager->getArrayPropertyFromData('suggestions', $data);
			}
			else
			{
				$dataDom = f_util_DOMUtils::fromString($data);
				$elems = $dataDom->find("lst[@name='spellcheck']/lst[@name='suggestions']/lst[@name='".str_replace("'", "&#39;", $word)."']/arr[@name='suggestion']/str");
				$searchResults = array();
				foreach ($elems as $elem)
				{
					$searchResults[] = $elem->textContent;
				}
			}
			
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
		return $searchResults;
	}
			
	/**
	 * @return String
	 */
	public function getClientId()
	{
		if ($this->getIndexerMode() == self::INDEXER_MODE_BACKOFFICE)
		{
			return $this->getBaseClientId() . self::BACKOFFICE_SUFFIX;
		}
		return $this->getBaseClientId();
	}
	
	/**
	 * @param string $documentModelName
	 * @param integer $documentIndex
	 * @param integer $chunkSize
	 * @return integer
	 */
	public function reIndexModelName($documentModelName, $documentIndex, $chunkSize)
	{
		$modes = array();
		if (in_array($documentModelName, $this->getBackOfficeModelsName()))
		{
			$modes[] = self::INDEXER_MODE_BACKOFFICE;
		}
		if (in_array($documentModelName, $this->getFrontOfficeModelsName()))
		{
			$modes[] = self::INDEXER_MODE_FRONTOFFICE;
		}
		if (count($modes) == 0) {return 0;}
		$query = $this->getIndexableDocumentsByModelNameQuery($documentModelName)
				->setProjection(Projections::property('id', 'id'))
				->setFirstResult($documentIndex)
				->setMaxResults($chunkSize)
				->addOrder(Order::asc('document_id'));
		$ids = $query->findColumn('id');
		$this->getTransactionManager()->beginTransaction();
		
		foreach ($modes as $indexingMode) 
		{
			foreach ($ids as $id) 
			{
				$this->getPersistentProvider()->setIndexingDocumentStatus($id, $indexingMode, self::TO_INDEX);
			}
		}
		$this->getTransactionManager()->commit();
		return count($ids);	
	}

	/**
	 * @param string $mode
	 * @param string $modelName
	 * @param integer $documentIndex
	 * @param integer $chunkSize
	 */
	protected function indexDocumentChunkDelayed($mode, $modelName, $documentIndex, $chunkSize)
	{
		$this->beginIndexerMode(($mode == 'back') ? self::INDEXER_MODE_BACKOFFICE : self::INDEXER_MODE_FRONTOFFICE);		
		$persistentDocumentModel = f_persistentdocument_PersistentDocumentModel::getInstanceFromDocumentModelName($modelName);
		if ($persistentDocumentModel->getName() != $modelName)
		{
			//Injected document not indexed
			return 0;
		}
		
		if (!$this->isModelNameIndexable($modelName))
		{
			if ($this->getIndexerMode() === self::INDEXER_MODE_BACKOFFICE)
			{
				Framework::warn(__METHOD__ . " model " . $modelName . " is not backoffice indexable");
			}
			else
			{
				Framework::warn(__METHOD__ . " model " . $modelName . " is not frontoffice indexable");
			}
			return 'ERROR';
		}
		
		$query = $this->getIndexableDocumentsByModelNameQuery($modelName)
				->setProjection(Projections::property('id', 'id'))
				->setFirstResult($documentIndex)
				->setMaxResults($chunkSize)
				->addOrder(Order::asc('document_id'));
				
		$ids = $query->findColumn('id');
		
		try 
		{
			$this->getTransactionManager()->beginTransaction();
			foreach ($ids as $documentId)
			{
				try 
				{
					$document = DocumentHelper::getDocumentInstance($documentId);
					$this->toIndexStatus($document);
				}
				catch (Exception $ed)
				{
					Framework::exception($ed);
				}
			}
			$this->getTransactionManager()->commit();				
		} 
		catch (Exception $e) 
		{
			Framework::exception($e);
		}	
		$this->endIndexerMode();
		return count($ids);
	}
	
	/**
	 * @param string $mode
	 * @param string $modelName
	 * @param integer $documentIndex
	 * @param integer $chunkSize
	 * @param boolean $delayed
	 * @return integer || 'ERROR'
	 */
	public function indexDocumentChunk($mode, $modelName, $documentIndex, $chunkSize, $delayed = false)
	{
		if ($delayed)
		{
			return $this->indexDocumentChunkDelayed($mode, $modelName, $documentIndex, $chunkSize);
		}
		
		if ($this->manager === null)
		{
			Framework::error(__METHOD__ . ' Can not index documents please check your config.');
			return 'ERROR';
		}
		$this->beginIndexerMode(($mode == 'back') ? self::INDEXER_MODE_BACKOFFICE : self::INDEXER_MODE_FRONTOFFICE);		
		$persistentDocumentModel = f_persistentdocument_PersistentDocumentModel::getInstanceFromDocumentModelName($modelName);
		if ($persistentDocumentModel->getName() != $modelName)
		{
			//Injected document not indexed
			return 0;
		}
		
		if (!$this->isModelNameIndexable($modelName))
		{
			if ($this->getIndexerMode() === self::INDEXER_MODE_BACKOFFICE)
			{
				Framework::warn(__METHOD__ . " model " . $modelName . " is not backoffice indexable");
			}
			else
			{
				Framework::warn(__METHOD__ . " model " . $modelName . " is not frontoffice indexable");
			}
			return 'ERROR';
		}
		
		$query = $this->getIndexableDocumentsByModelNameQuery($modelName)
				->setProjection(Projections::property('id', 'id'))
				->setFirstResult($documentIndex)
				->setMaxResults($chunkSize)
				->addOrder(Order::asc('document_id'));
		$ids = $query->findColumn('id');	
		$this->getTransactionManager()->beginTransaction();
		foreach ($ids as $documentId)
		{
			if ($delayed)
			{
				try 
				{
					$document = DocumentHelper::getDocumentInstance($documentId);
					if ($this->getIndexerMode() === self::INDEXER_MODE_BACKOFFICE)
					{
						$this->addBackoffice($document);
					}
					else
					{
						$this->add($document);
					}	
				}
				catch (Exception $ed)
				{
					Framework::exception($ed);
				}
				continue;
			}	
								
			try
			{
				list($oldStatus, $lastUpdate) = $this->getPersistentProvider()->getIndexingDocumentStatus($documentId, $this->getIndexerMode());
				$result = $this->indexDocumentId($documentId);
				if ($result === self::INDEXED)
				{
					if ($oldStatus !== $result)
					{
						$this->getPersistentProvider()->setIndexingDocumentStatus($documentId, $this->getIndexerMode(), $result);
					}
				}
				elseif ($result === self::DELETED)
				{
					if ($oldStatus !== null)
					{
						$this->getPersistentProvider()->deleteIndexingDocumentStatus($documentId, $this->getIndexerMode());
					}
				}
				elseif ($oldStatus !== self::TO_INDEX)
				{
					$this->getPersistentProvider()->setIndexingDocumentStatus($documentId, $this->getIndexerMode(), self::TO_INDEX);
				}
				
			}
			catch (Exception $e)
			{
				Framework::exception($e);
			}
		}
		
		$this->getTransactionManager()->commit();
		$this->manager->commit();
		
		$this->endIndexerMode();
		return count($ids);
	}
	
	public function backgroundIndex($indexingMode, $maxDocumentId, $chunkSize = 100)
	{
		if ($this->manager === null)
		{
			Framework::error(__METHOD__ . ' Can not index documents please check your config.');
			return 'ERROR';
		}
		$documentIds = $this->getPersistentProvider()->getIndexingDocuments($indexingMode, $maxDocumentId, $chunkSize);
		if (count($documentIds) === 0)
		{
			return -1;
		}		
		try 
		{
			$this->getTransactionManager()->beginTransaction();
			$this->beginIndexerMode($indexingMode);
			foreach ($documentIds as $documentId) 
			{
				$result = $this->indexDocumentId($documentId);
				if ($result === self::INDEXED)
				{
					$this->getPersistentProvider()->setIndexingDocumentStatus($documentId, $indexingMode, $result);
				}
				elseif ($result === self::DELETED)
				{
					$this->getPersistentProvider()->deleteIndexingDocumentStatus($documentId, $indexingMode);
				}
			}
			$this->endIndexerMode();
			$this->getTransactionManager()->commit();
			$this->manager->commit();
		}
		catch (Exception $e)
		{
			$this->getTransactionManager()->rollBack($e);
		}
		
		if (count($documentIds) < $chunkSize)
		{
			return -1;
		}
		return min($documentIds) - 1;
	}
	
	protected function indexDocumentId($documentId)
	{
		//Framework::fatal(__METHOD__ . "($documentId)");
		$rc = RequestContext::getInstance();
		$projectLangs = $rc->getSupportedLanguages();
		$documentModelName = $this->getPersistentProvider()->getDocumentModelName($documentId);
		if ($documentModelName === false || !$this->isModelNameIndexable($documentModelName))
		{
			foreach ($projectLangs as $lang) 
			{
				//Framework::fatal(__METHOD__ . " delete($documentId/$lang)");
				$this->manager->delete($documentId .'/' .$lang);
			}
			return self::DELETED;
		}
		$result = false;
		$document = DocumentHelper::getDocumentInstance($documentId, $documentModelName);
		$documentModel = $document->getPersistentModel();
		try 
		{
			if ($documentModel->isLocalized())
			{
				foreach ($projectLangs as $lang)
				{
					$rc->setLang($lang);
					if ($document->isContextLangAvailable())
					{
						$res = $this->updateRealDocument($document);
					}
					else
					{
						//Framework::fatal(__METHOD__ . " delete($documentId/$lang)");
						$this->manager->delete($documentId .'/' .$lang);
						$res = self::DELETED;
					}
					if ($result === false || $result === self::DELETED)
					{
						$result = $res;
					}
				}
			}
			else
			{
				$rc->setLang($document->getLang());
				$result = $this->updateRealDocument($document);
			}
		} 
		catch (Exception $e)
		{
			Framework::exception($e);
		}
		return $result;
	}
		
	/**
	 * @param string $modelName
	 * @return boolean
	 */
	public function isModelNameIndexable($modelName)
	{
		if ($this->getIndexerMode() == self::BACKOFFICE_SUFFIX)
		{
			return in_array($modelName, $this->getBackOfficeModelsName());
		}
		return in_array($modelName, $this->getFrontOfficeModelsName());
	}	
	
	/**
	 * @param f_persistentdocument_PersistentDocument $document
	 * @param indexer_IndexedDocument $indexedDocument
	 * @return Array<Integer>
	 */
	private function getBackendAccessorIds($document, $indexedDocument)
	{
		$ps = f_permission_PermissionService::getInstance();
		$model = $document->getPersistentModel();
		$fields = $indexedDocument->getFields();
		$module = $fields['module' . indexer_Field::STRING]['value'];
		$packageName = 'modules_' . $module;
		
		$roleService = f_permission_PermissionService::getRoleServiceByModuleName($module);		
		if ($roleService === null || count($roleService->getRoles()) === 0)
		{
			// We have no role service or no roles declared
			return array(self::PUBLIC_DOCUMENT_ACCESSOR_ID);
		}
		
		$definitionPointId = $ps->getDefinitionPointForPackage($document->getId(), $packageName);
		$permissionName = $packageName . '.Update.' . $model->getOriginalDocumentName();
		return $ps->getAccessorIdsForPermissionAndDocumentId($permissionName, $definitionPointId);
	}

	/**
	 * @param f_persistentdocument_PersistentDocument $document
	 * @return Integer[]
	 */
	private function getFrontendAccessorIds($document)
	{
		$userIds = array();
		$page = $document->getDocumentService()->getDisplayPage($document);
		if ($page !== null)
		{
			$userIds = f_permission_PermissionService::getInstance()->getAccessorIdsForRoleByDocumentId('modules_website.AuthenticatedFrontUser', $page->getId());
		}
		if (count($userIds) == 0)
		{
			$userIds[] = indexer_IndexService::PUBLIC_DOCUMENT_ACCESSOR_ID;
		}
		return $userIds;
	}
	
	/**
	 * @param integer $indexerMode
	 */
	protected function beginIndexerMode($indexerMode)
	{
		if ($indexerMode != self::INDEXER_MODE_BACKOFFICE && $indexerMode != self::INDEXER_MODE_FRONTOFFICE)
		{
			throw new IllegalArgumentException('$indexerMode has to be either self::INDEXER_MODE_BACKOFFICE or self::INDEXER_MODE_FRONTOFFICE');
		}
		$this->indexerMode = $indexerMode;
	}
	
	/**
	 * 
	 * @param Exception $e
	 * @throws Exception
	 */
	protected function endIndexerMode($e = null)
	{
		$this->indexerMode = self::INDEXER_MODE_FRONTOFFICE;
		if (null !== $e)
		{
			throw $e;
		}
	}
	
	/**
	 * Shortcut to begin a front office indexing session
	 */
	protected final function beginFrontIndexerMode()
	{
		$this->beginIndexerMode(self::INDEXER_MODE_FRONTOFFICE);
	}
	
	/**
	 * Shortcut to begin a backoffice indexing session
	 */
	protected final function beginBackIndexerMode()
	{
		$this->beginIndexerMode(self::INDEXER_MODE_BACKOFFICE);
	}
	

	/**
	 * @param f_persistentdocument_PersistentDocument $document
	 * @return boolean
	 */
	private function isIndexingOperationPossible($document)
	{
		if ($this->getIndexerMode() == self::INDEXER_MODE_BACKOFFICE)
		{
			return $document->getPersistentModel()->isBackofficeIndexable();
		}
		return $document->getPersistentModel()->isIndexable();
	}
		
	private function toIndexStatus($document)
	{
		$documentId = $document->getId();
		if (!$this->isIndexingOperationPossible($document))
		{
			Framework::warn(__METHOD__ . ' Can not index document ' . $documentId . ', please check your config and document model.');
			return;
		}
		
		if (is_array($this->indexDocuments))
		{
			if ($this->getIndexerMode() == self::BACKOFFICE_SUFFIX)
			{
				if (!isset($this->indexBackofficeDocuments[$documentId]))
				{
					$this->indexBackofficeDocuments[$documentId] = self::TO_INDEX;
				}			
			}
			else
			{
				if (!isset($this->indexDocuments[$documentId]))
				{
					$this->indexDocuments[$documentId] = self::TO_INDEX;
				}
			}
		}
		else
		{
			$this->getTransactionManager()->beginTransaction();
			$mode = ($this->getIndexerMode() == self::BACKOFFICE_SUFFIX) ? self::INDEXER_MODE_BACKOFFICE :  self::INDEXER_MODE_FRONTOFFICE;
			$this->getPersistentProvider()->setIndexingDocumentStatus($documentId, $mode, self::TO_INDEX);
			$this->getTransactionManager()->commit();
		}
	}<commit_msg>[FIX] l'indexation bo utilise maintenant les permissions du module d'edition du document.<commit_after><?php
/**
 * @package framework.indexer
 * @author franck.stauffer
 */
class indexer_IndexService extends BaseService 
{
	const PUBLIC_DOCUMENT_ACCESSOR_ID = 0;
	const MAX_QUEUE_LENGTH = 100;
	const DEFAULT_SOLR_INDEXER_CLIENT = "defaultChangeIndexerClient";
	const BACKOFFICE_SUFFIX = '__backoffice';
	const INDEXER_MODE_BACKOFFICE = 0;
	const INDEXER_MODE_FRONTOFFICE = 1;
	
	const TO_INDEX = 'TO_INDEX';
	const TO_DELETE = 'TO_DELETE';
	const INDEXED = 'INDEXED';
	const DELETED = 'DELETED';
	
	/**
	 * @var indexer_IndexingService
	 */
	private static $instance = null;
	
	/**
	 * @var indexer_SolrManager
	 */
	private $manager = null;
	
	/**
	 * @var integer
	 */
	private $indexerMode = null;
	
	/**
	 * @var array
	 */
	private $modelsInfos;
	
	/**
	 * Get the service instance.
	 *
	 * @return indexer_IndexService
	 */
	public final static function getInstance()
	{
		if (null === self::$instance)
		{
			$newInstance = self::getServiceClassInstance(get_class());
			if (defined('SOLR_INDEXER_URL'))
			{
				$solrURL = SOLR_INDEXER_URL;
				if (!f_util_StringUtils::endsWith($solrURL, '/'))
				{
					$solrURL .= '/';
				}
				$newInstance->manager = new indexer_SolrManager($solrURL);
			}
			else
			{
				$newInstance->manager = new indexer_FakeSolrManager();
			}
			self::$instance = $newInstance;
		}
		return self::$instance;
	}
	
	/**
	 * @return integer
	 */
	public function getIndexerMode()
	{
		return $this->indexerMode;
	}
	
	public function loadModelsInfos()
	{
		$compiledFilePath = f_util_FileUtils::buildChangeBuildPath('indexableDocumentInfos.ser');
		if (!file_exists($compiledFilePath))
		{
			throw new Exception("File not found : $compiledFilePath. compile-documents needed");
		}
		$this->modelsInfos = unserialize(file_get_contents($compiledFilePath));
	}
	
	/**
	 * @return string[]
	 */
	public function getBackOfficeModelsName()
	{
		if ($this->modelsInfos === null)
		{
			$this->loadModelsInfos();
		}
		return $this->modelsInfos['bo'];
	}
	
	/**
	 * @return string[]
	 */
	public function getFrontOfficeModelsName()
	{
		if ($this->modelsInfos === null)
		{
			$this->loadModelsInfos();
		}
		return $this->modelsInfos['fo'];
	}	
	
	private $indexDocuments;
	private $indexBackofficeDocuments;
	
	public function beginIndexTransaction()
	{
		$this->indexDocuments = array();
		$this->indexBackofficeDocuments = array();
	}
	
	public function commitIndex()
	{
		if (is_array($this->indexDocuments))
		{
			if (count($this->indexDocuments) || count($this->indexBackofficeDocuments))
			{
				if (Framework::isInfoEnabled())
				{
					Framework::info(__METHOD__ . ' IFO: ' . count($this->indexDocuments). ', IBO: '. count($this->indexBackofficeDocuments));
				}
				
				$pp = $this->getPersistentProvider();
				foreach ($this->indexDocuments as $id => $status) 
				{
					$pp->setIndexingDocumentStatus($id, self::INDEXER_MODE_FRONTOFFICE, $status);
				}
				
				foreach ($this->indexBackofficeDocuments as $id => $status) 
				{
					$pp->setIndexingDocumentStatus($id, self::INDEXER_MODE_BACKOFFICE, $status);
				}
			}
			$this->indexDocuments = null;
			$this->indexBackofficeDocuments = null;
		}
	}
	
	public function rollBackIndex()
	{
		$this->indexDocuments = null;
		$this->indexBackofficeDocuments = null;
	}
	
	/**
	 * @param Boolean $bool
	 */
	public function setAutoCommit($bool)
	{
		if (!is_null($this->manager) && is_bool($bool))
		{
			$this->manager->setAutoCommit($bool);
		}
	}
	
	public function commit()
	{
		if (!is_null($this->manager))
		{
			$this->manager->sendCommit();
		}
	}
	
	/**
	 * @throws IllegalArgumentException
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function add($document)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$this->toIndexStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
		
	/**
	 * @throws IllegalArgumentException
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function addBackoffice($document)
	{
		try
		{
			$this->beginBackIndexerMode();
			$this->toIndexStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Update the indexer_IndexableDocument $document in the index. 
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function update($document)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$this->toIndexStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Update the indexer_IndexableDocument $document in the index. 
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function updateBackoffice($document)
	{
		try
		{
			$this->beginBackIndexerMode();
			$this->toIndexStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Delete the indexer_IndexableDocument $document from the index.
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function delete($document)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$this->toDeleteStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Delete the indexer_IndexableDocument $document from the index.
	 * @param f_persistentdocument_PersistentDocument $document
	 */
	public function deleteBackoffice($document)
	{
		try
		{
			$this->beginBackIndexerMode();
			$this->toDeleteStatus($document);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	public function clearBackofficeIndex()
	{
		if ($this->manager !== null)
		{
			try
			{
				
				$this->beginBackIndexerMode();
				$this->manager->clearIndexQuery();
				$this->manager->commit();
				

				
				$this->endIndexerMode();
			}
			catch (Exception $e)
			{
				$this->endIndexerMode($e);
			}
			
			try 
			{
				$this->getTransactionManager()->beginTransaction();
				$this->getPersistentProvider()->clearIndexingDocumentStatus(self::INDEXER_MODE_BACKOFFICE);
				$this->getTransactionManager()->commit();				
			} 
			catch (Exception $e) 
			{
				$this->getTransactionManager()->rollBack($e);
			}
		}
	}
	
	public function clearFrontofficeIndex()
	{
		if ($this->manager !== null)
		{
			try
			{
				$this->beginFrontIndexerMode();
				$this->manager->clearIndexQuery();
				$this->manager->commit();
				$this->endIndexerMode();
			}
			catch (Exception $e)
			{
				$this->endIndexerMode($e);
			}
			
			try 
			{
				$this->getTransactionManager()->beginTransaction();
				$this->getPersistentProvider()->clearIndexingDocumentStatus(self::INDEXER_MODE_FRONTOFFICE);
				$this->getTransactionManager()->commit();				
			} 
			catch (Exception $e) 
			{
				$this->getTransactionManager()->rollBack($e);
			}
		}
	}
	
	public function clearIndex()
	{
		$this->clearBackofficeIndex();
		$this->clearFrontofficeIndex();
	}
	
	public function optimizeIndex()
	{
		if ($this->manager !== null)
		{
			try
			{
				$this->beginBackIndexerMode();
				$this->manager->optimizeIndexQuery();
				$this->endIndexerMode();
				
				$this->beginFrontIndexerMode();
				$this->manager->optimizeIndexQuery();
				$this->endIndexerMode();
			}
			catch (Exception $e)
			{
				$this->endIndexerMode($e);
			}
		}
	}
	
	public function rebuildSpellCheckIndexForLang($lang)
	{
		if ($this->manager !== null)
		{
			try
			{
				$this->beginBackIndexerMode();
				$this->manager->rebuildSpellCheckIndexForLang($lang);
				$this->endIndexerMode();
				
				$this->beginFrontIndexerMode();
				$this->manager->rebuildSpellCheckIndexForLang($lang);
				$this->endIndexerMode();
			}
			catch (Exception $e)
			{
				$this->endIndexerMode($e);
			}
		}
	}
	

	/**
	 * Execute $query on the configured <strong>frontoffice</strong> indexer using the standard request handler
	 * (search on label and full text with a boost on the label). 
	 * 
	 * @param indexer_Query $query
	 * @param String[] $suggestionTerms
	 * @return indexer_SearchResults
	 */
	public function search(indexer_Query $query, $suggestionTerms = null)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$solrSearch = new indexer_StandardSolrSearch($query);
			if (f_util_ArrayUtils::isNotEmpty($suggestionTerms))
			{
				$solrSearch->doSuggestion($suggestionTerms);
			}
			$data = $this->manager->query($solrSearch);
			$searchResults = new indexer_SolrSearchResults($data, $solrSearch);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
		return $searchResults;
	}
	
	/**
	 * Execute $query on the configured <strong>backoffice</strong> indexer using the standard request handler
	 * (search on label and full text with a boost on the label). 
	 * 
	 * @param indexer_Query $query
	 * @return indexer_SearchResults
	 */
	public function searchBackoffice(indexer_Query $query)
	{
		try
		{
			$this->beginBackIndexerMode();
			$solrSearch = new indexer_StandardSolrSearch($query);
			$data = $this->manager->query($solrSearch);
			$searchResults = new indexer_SolrSearchResults($data, $solrSearch);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
		return $searchResults;
	}
	
	/**
	 * Execute $query on the configured <strong>frontoffice</strong> using solr's dismax request handler
	 * (see http://wiki.apache.org/solr/DisMaxRequestHandler). 
	 * 
	 * @param indexer_Query $query
	 * @return indexer_SearchResults
	 */
	public function dismaxSearch(indexer_Query $query)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$solrSearch = new indexer_DismaxSolrSearch($query);
			$data = $this->manager->query($solrSearch);
			$searchResults = new indexer_SolrSearchResults($data, $solrSearch);
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
		return $searchResults;
	}
	
	/**
	 * Get a *single* suggestion for the word $word from the spellchecker for $lang. 
	 * If $lang is null, the RequestContext's lang is used.
	 *
	 * @param String $word
	 * @param String $lang
	 * @return String
	 */
	public function getSuggestionForWord($word, $lang = null)
	{
		$suggestions = $this->getSuggestionArrayForWord($word, $lang);
		if (count($suggestions) > 0)
		{
			return $suggestions[0];
		}
		return null;
	}
	
	/**
	 * Get a suggestion for a term list. This method is only here for compatibility
	 * with old SolR schema (2.0.4). You should use indexer_StandardSolrSearch::doSuggestion() method
	 * @param String[] $words
	 * @param String $lang
	 * @return String
	 */
	public function getSuggestionForWords($words, $lang = null)
	{
		if (indexer_SolrManager::getSchemaVersion() == "2.0.4")
		{
			throw new Exception("This method only works with SolR 1.4 + change schema 3.0.3");
		}
		try
		{
			$this->beginFrontIndexerMode();
			$query = new indexer_SuggestionSolrSearch($words, $lang);
			$query->setSuggestionCount(1);
			$data = $this->manager->query($query);
			$this->endIndexerMode();
			
			$dataDom = f_util_DOMUtils::fromString($data);
			$elem = $dataDom->findUnique("lst[@name='spellcheck']/lst[@name='suggestions']/str[@name='collation']");
			if ($elem !== null)
			{
				return $elem->textContent;
			}
			return null;
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
	}
	
	/**
	 * Get an array of at most $count suggestions for the word $word from the spellchecker for $lang. 
	 *
	 * @param String $word
	 * @param String $lang
	 * @param String $count
	 * @return Array<String>
	 */
	public function getSuggestionArrayForWord($word, $lang = null, $count = null)
	{
		try
		{
			$this->beginFrontIndexerMode();
			$query = new indexer_SuggestionSolrSearch($word, $lang);
			if (!is_null($count) && $count > 0)
			{
				$query->setSuggestionCount($count);
			}
			$data = $this->manager->query($query);
			$schemaVersion = indexer_SolrManager::getSchemaVersion();
			if ($schemaVersion == "2.0.4")
			{
				$searchResults = $this->manager->getArrayPropertyFromData('suggestions', $data);
			}
			else
			{
				$dataDom = f_util_DOMUtils::fromString($data);
				$elems = $dataDom->find("lst[@name='spellcheck']/lst[@name='suggestions']/lst[@name='".str_replace("'", "&#39;", $word)."']/arr[@name='suggestion']/str");
				$searchResults = array();
				foreach ($elems as $elem)
				{
					$searchResults[] = $elem->textContent;
				}
			}
			
			$this->endIndexerMode();
		}
		catch (Exception $e)
		{
			$this->endIndexerMode($e);
		}
		return $searchResults;
	}
			
	/**
	 * @return String
	 */
	public function getClientId()
	{
		if ($this->getIndexerMode() == self::INDEXER_MODE_BACKOFFICE)
		{
			return $this->getBaseClientId() . self::BACKOFFICE_SUFFIX;
		}
		return $this->getBaseClientId();
	}
	
	/**
	 * @param string $documentModelName
	 * @param integer $documentIndex
	 * @param integer $chunkSize
	 * @return integer
	 */
	public function reIndexModelName($documentModelName, $documentIndex, $chunkSize)
	{
		$modes = array();
		if (in_array($documentModelName, $this->getBackOfficeModelsName()))
		{
			$modes[] = self::INDEXER_MODE_BACKOFFICE;
		}
		if (in_array($documentModelName, $this->getFrontOfficeModelsName()))
		{
			$modes[] = self::INDEXER_MODE_FRONTOFFICE;
		}
		if (count($modes) == 0) {return 0;}
		$query = $this->getIndexableDocumentsByModelNameQuery($documentModelName)
				->setProjection(Projections::property('id', 'id'))
				->setFirstResult($documentIndex)
				->setMaxResults($chunkSize)
				->addOrder(Order::asc('document_id'));
		$ids = $query->findColumn('id');
		$this->getTransactionManager()->beginTransaction();
		
		foreach ($modes as $indexingMode) 
		{
			foreach ($ids as $id) 
			{
				$this->getPersistentProvider()->setIndexingDocumentStatus($id, $indexingMode, self::TO_INDEX);
			}
		}
		$this->getTransactionManager()->commit();
		return count($ids);	
	}

	/**
	 * @param string $mode
	 * @param string $modelName
	 * @param integer $documentIndex
	 * @param integer $chunkSize
	 */
	protected function indexDocumentChunkDelayed($mode, $modelName, $documentIndex, $chunkSize)
	{
		$this->beginIndexerMode(($mode == 'back') ? self::INDEXER_MODE_BACKOFFICE : self::INDEXER_MODE_FRONTOFFICE);		
		$persistentDocumentModel = f_persistentdocument_PersistentDocumentModel::getInstanceFromDocumentModelName($modelName);
		if ($persistentDocumentModel->getName() != $modelName)
		{
			//Injected document not indexed
			return 0;
		}
		
		if (!$this->isModelNameIndexable($modelName))
		{
			if ($this->getIndexerMode() === self::INDEXER_MODE_BACKOFFICE)
			{
				Framework::warn(__METHOD__ . " model " . $modelName . " is not backoffice indexable");
			}
			else
			{
				Framework::warn(__METHOD__ . " model " . $modelName . " is not frontoffice indexable");
			}
			return 'ERROR';
		}
		
		$query = $this->getIndexableDocumentsByModelNameQuery($modelName)
				->setProjection(Projections::property('id', 'id'))
				->setFirstResult($documentIndex)
				->setMaxResults($chunkSize)
				->addOrder(Order::asc('document_id'));
				
		$ids = $query->findColumn('id');
		
		try 
		{
			$this->getTransactionManager()->beginTransaction();
			foreach ($ids as $documentId)
			{
				try 
				{
					$document = DocumentHelper::getDocumentInstance($documentId);
					$this->toIndexStatus($document);
				}
				catch (Exception $ed)
				{
					Framework::exception($ed);
				}
			}
			$this->getTransactionManager()->commit();				
		} 
		catch (Exception $e) 
		{
			Framework::exception($e);
		}	
		$this->endIndexerMode();
		return count($ids);
	}
	
	/**
	 * @param string $mode
	 * @param string $modelName
	 * @param integer $documentIndex
	 * @param integer $chunkSize
	 * @param boolean $delayed
	 * @return integer || 'ERROR'
	 */
	public function indexDocumentChunk($mode, $modelName, $documentIndex, $chunkSize, $delayed = false)
	{
		if ($delayed)
		{
			return $this->indexDocumentChunkDelayed($mode, $modelName, $documentIndex, $chunkSize);
		}
		
		if ($this->manager === null)
		{
			Framework::error(__METHOD__ . ' Can not index documents please check your config.');
			return 'ERROR';
		}
		$this->beginIndexerMode(($mode == 'back') ? self::INDEXER_MODE_BACKOFFICE : self::INDEXER_MODE_FRONTOFFICE);		
		$persistentDocumentModel = f_persistentdocument_PersistentDocumentModel::getInstanceFromDocumentModelName($modelName);
		if ($persistentDocumentModel->getName() != $modelName)
		{
			//Injected document not indexed
			return 0;
		}
		
		if (!$this->isModelNameIndexable($modelName))
		{
			if ($this->getIndexerMode() === self::INDEXER_MODE_BACKOFFICE)
			{
				Framework::warn(__METHOD__ . " model " . $modelName . " is not backoffice indexable");
			}
			else
			{
				Framework::warn(__METHOD__ . " model " . $modelName . " is not frontoffice indexable");
			}
			return 'ERROR';
		}
		
		$query = $this->getIndexableDocumentsByModelNameQuery($modelName)
				->setProjection(Projections::property('id', 'id'))
				->setFirstResult($documentIndex)
				->setMaxResults($chunkSize)
				->addOrder(Order::asc('document_id'));
		$ids = $query->findColumn('id');	
		$this->getTransactionManager()->beginTransaction();
		foreach ($ids as $documentId)
		{
			if ($delayed)
			{
				try 
				{
					$document = DocumentHelper::getDocumentInstance($documentId);
					if ($this->getIndexerMode() === self::INDEXER_MODE_BACKOFFICE)
					{
						$this->addBackoffice($document);
					}
					else
					{
						$this->add($document);
					}	
				}
				catch (Exception $ed)
				{
					Framework::exception($ed);
				}
				continue;
			}	
								
			try
			{
				list($oldStatus, $lastUpdate) = $this->getPersistentProvider()->getIndexingDocumentStatus($documentId, $this->getIndexerMode());
				$result = $this->indexDocumentId($documentId);
				if ($result === self::INDEXED)
				{
					if ($oldStatus !== $result)
					{
						$this->getPersistentProvider()->setIndexingDocumentStatus($documentId, $this->getIndexerMode(), $result);
					}
				}
				elseif ($result === self::DELETED)
				{
					if ($oldStatus !== null)
					{
						$this->getPersistentProvider()->deleteIndexingDocumentStatus($documentId, $this->getIndexerMode());
					}
				}
				elseif ($oldStatus !== self::TO_INDEX)
				{
					$this->getPersistentProvider()->setIndexingDocumentStatus($documentId, $this->getIndexerMode(), self::TO_INDEX);
				}
				
			}
			catch (Exception $e)
			{
				Framework::exception($e);
			}
		}
		
		$this->getTransactionManager()->commit();
		$this->manager->commit();
		
		$this->endIndexerMode();
		return count($ids);
	}
	
	public function backgroundIndex($indexingMode, $maxDocumentId, $chunkSize = 100)
	{
		if ($this->manager === null)
		{
			Framework::error(__METHOD__ . ' Can not index documents please check your config.');
			return 'ERROR';
		}
		$documentIds = $this->getPersistentProvider()->getIndexingDocuments($indexingMode, $maxDocumentId, $chunkSize);
		if (count($documentIds) === 0)
		{
			return -1;
		}		
		try 
		{
			$this->getTransactionManager()->beginTransaction();
			$this->beginIndexerMode($indexingMode);
			foreach ($documentIds as $documentId) 
			{
				$result = $this->indexDocumentId($documentId);
				if ($result === self::INDEXED)
				{
					$this->getPersistentProvider()->setIndexingDocumentStatus($documentId, $indexingMode, $result);
				}
				elseif ($result === self::DELETED)
				{
					$this->getPersistentProvider()->deleteIndexingDocumentStatus($documentId, $indexingMode);
				}
			}
			$this->endIndexerMode();
			$this->getTransactionManager()->commit();
			$this->manager->commit();
		}
		catch (Exception $e)
		{
			$this->getTransactionManager()->rollBack($e);
		}
		
		if (count($documentIds) < $chunkSize)
		{
			return -1;
		}
		return min($documentIds) - 1;
	}
	
	protected function indexDocumentId($documentId)
	{
		//Framework::fatal(__METHOD__ . "($documentId)");
		$rc = RequestContext::getInstance();
		$projectLangs = $rc->getSupportedLanguages();
		$documentModelName = $this->getPersistentProvider()->getDocumentModelName($documentId);
		if ($documentModelName === false || !$this->isModelNameIndexable($documentModelName))
		{
			foreach ($projectLangs as $lang) 
			{
				//Framework::fatal(__METHOD__ . " delete($documentId/$lang)");
				$this->manager->delete($documentId .'/' .$lang);
			}
			return self::DELETED;
		}
		$result = false;
		$document = DocumentHelper::getDocumentInstance($documentId, $documentModelName);
		$documentModel = $document->getPersistentModel();
		try 
		{
			if ($documentModel->isLocalized())
			{
				foreach ($projectLangs as $lang)
				{
					$rc->setLang($lang);
					if ($document->isContextLangAvailable())
					{
						$res = $this->updateRealDocument($document);
					}
					else
					{
						//Framework::fatal(__METHOD__ . " delete($documentId/$lang)");
						$this->manager->delete($documentId .'/' .$lang);
						$res = self::DELETED;
					}
					if ($result === false || $result === self::DELETED)
					{
						$result = $res;
					}
				}
			}
			else
			{
				$rc->setLang($document->getLang());
				$result = $this->updateRealDocument($document);
			}
		} 
		catch (Exception $e)
		{
			Framework::exception($e);
		}
		return $result;
	}
		
	/**
	 * @param string $modelName
	 * @return boolean
	 */
	public function isModelNameIndexable($modelName)
	{
		if ($this->getIndexerMode() == self::BACKOFFICE_SUFFIX)
		{
			return in_array($modelName, $this->getBackOfficeModelsName());
		}
		return in_array($modelName, $this->getFrontOfficeModelsName());
	}	
	
	/**
	 * @param f_persistentdocument_PersistentDocument $document
	 * @param indexer_IndexedDocument $indexedDocument
	 * @return Array<Integer>
	 */
	private function getBackendAccessorIds($document, $indexedDocument)
	{
		$ps = f_permission_PermissionService::getInstance();
		$model = $document->getPersistentModel();
		$fields = $indexedDocument->getFields();
		$module = $fields['editmodule' . indexer_Field::STRING]['value'];
		if (empty($module)) {$module = $fields['module' . indexer_Field::STRING]['value'];}
		$packageName = 'modules_' . $module;
		
		$roleService = f_permission_PermissionService::getRoleServiceByModuleName($module);		
		if ($roleService === null || count($roleService->getRoles()) === 0)
		{
			// We have no role service or no roles declared
			return array(self::PUBLIC_DOCUMENT_ACCESSOR_ID);
		}
		
		$definitionPointId = $ps->getDefinitionPointForPackage($document->getId(), $packageName);
		$permissionName = $packageName . '.Update.' . $model->getDocumentName();
		return $ps->getAccessorIdsForPermissionAndDocumentId($permissionName, $definitionPointId);
	}

	/**
	 * @param f_persistentdocument_PersistentDocument $document
	 * @return Integer[]
	 */
	private function getFrontendAccessorIds($document)
	{
		$userIds = array();
		$page = $document->getDocumentService()->getDisplayPage($document);
		if ($page !== null)
		{
			$userIds = f_permission_PermissionService::getInstance()->getAccessorIdsForRoleByDocumentId('modules_website.AuthenticatedFrontUser', $page->getId());
		}
		if (count($userIds) == 0)
		{
			$userIds[] = indexer_IndexService::PUBLIC_DOCUMENT_ACCESSOR_ID;
		}
		return $userIds;
	}
	
	/**
	 * @param integer $indexerMode
	 */
	protected function beginIndexerMode($indexerMode)
	{
		if ($indexerMode != self::INDEXER_MODE_BACKOFFICE && $indexerMode != self::INDEXER_MODE_FRONTOFFICE)
		{
			throw new IllegalArgumentException('$indexerMode has to be either self::INDEXER_MODE_BACKOFFICE or self::INDEXER_MODE_FRONTOFFICE');
		}
		$this->indexerMode = $indexerMode;
	}
	
	/**
	 * 
	 * @param Exception $e
	 * @throws Exception
	 */
	protected function endIndexerMode($e = null)
	{
		$this->indexerMode = self::INDEXER_MODE_FRONTOFFICE;
		if (null !== $e)
		{
			throw $e;
		}
	}
	
	/**
	 * Shortcut to begin a front office indexing session
	 */
	protected final function beginFrontIndexerMode()
	{
		$this->beginIndexerMode(self::INDEXER_MODE_FRONTOFFICE);
	}
	
	/**
	 * Shortcut to begin a backoffice indexing session
	 */
	protected final function beginBackIndexerMode()
	{
		$this->beginIndexerMode(self::INDEXER_MODE_BACKOFFICE);
	}
	

	/**
	 * @param f_persistentdocument_PersistentDocument $document
	 * @return boolean
	 */
	private function isIndexingOperationPossible($document)
	{
		if ($this->getIndexerMode() == self::INDEXER_MODE_BACKOFFICE)
		{
			return $document->getPersistentModel()->isBackofficeIndexable();
		}
		return $document->getPersistentModel()->isIndexable();
	}
		
	private function toIndexStatus($document)
	{
		$documentId = $document->getId();
		if (!$this->isIndexingOperationPossible($document))
		{
			Framework::warn(__METHOD__ . ' Can not index document ' . $documentId . ', please check your config and document model.');
			return;
		}
		
		if (is_array($this->indexDocuments))
		{
			if ($this->getIndexerMode() == self::BACKOFFICE_SUFFIX)
			{
				if (!isset($this->indexBackofficeDocuments[$documentId]))
				{
					$this->indexBackofficeDocuments[$documentId] = self::TO_INDEX;
				}			
			}
			else
			{
				if (!isset($this->indexDocuments[$documentId]))
				{
					$this->indexDocuments[$documentId] = self::TO_INDEX;
				}
			}
		}
		else
		{
			$this->getTransactionManager()->beginTransaction();
			$mode = ($this->getIndexerMode() == self::BACKOFFICE_SUFFIX) ? self::INDEXER_MODE_BACKOFFICE :  self::INDEXER_MODE_FRONTOFFICE;
			if (Framework::isInfoEnabled())
			{
				Framework::info(__METHOD__ . ' ' . ($mode == 0 ? 'IBO' : 'IFO') . ' -> ' . $documentId);
			}
			$this->getPersistentProvider()->setIndexingDocumentStatus($documentId, $mode, self::TO_INDEX);
			$this->getTransactionManager()->commit();
		}
	}
{'fraction_non_alphanumeric': 0.1539906838376326, 'fraction_numerical': 0.0015461698046737387, 'mean_word_length': 3.9181826932332275, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 596, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6062174', 'n_tokens_mistral': 19914, 'n_tokens_neox': 16476, 'n_words': 2863}