grthtrhthjhtyjytjytkergtrhtrjytjerhrfh4:24 29/09/2026<?php
/**
 *
 * SugarCRM Community Edition is a customer relationship management program developed by
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
 *
 * SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
 * Copyright (C) 2011 - 2018 SalesAgility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along with
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 *
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "Powered by
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
 * reasonably feasible for technical reasons, the Appropriate Legal Notices must
 * display the words "Powered by SugarCRM" and "Supercharged by SuiteCRM".
 */

/**
 * THIS CLASS IS FOR DEVELOPERS TO MAKE CUSTOMIZATIONS IN
 */
require_once('modules/AOD_Index/AOD_Index_sugar.php');
require_once('modules/AOD_Index/LuceneUtils.php');
requireLucene();

class AOD_Index extends AOD_Index_sugar
{
    public function __construct()
    {
        parent::__construct();
        Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('utf-8');
        Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive());
    }

    /**
     * @deprecated deprecated since version 7.6, PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code, use __construct instead
     */
    public function AOD_Index()
    {
        $deprecatedMessage = 'PHP4 Style Constructors are deprecated and will be remove in 7.8, please update your code';
        if (isset($GLOBALS['log'])) {
            $GLOBALS['log']->deprecated($deprecatedMessage);
        } else {
            trigger_error($deprecatedMessage, E_USER_DEPRECATED);
        }
        self::__construct();
    }


    public function isEnabled()
    {
        global $sugar_config;
        return !empty($sugar_config['aod']['enable_aod']);
    }

    public function find($queryString)
    {
        $queryString = strtolower($queryString);
        $hits = $this->getLuceneIndex()->find($queryString);
        return $hits;
    }

    public function optimise()
    {
        if (!$this->isEnabled()) {
            return;
        }
        global $timedate;
        $this->getLuceneIndex()->optimize();
        $this->last_optimised = $timedate->getNow()->asDb();
        $this->save();
    }

    public function getIndex()
    {
        $index = BeanFactory::getBean('AOD_Index', 1);
        if (!empty($index) && !empty($index->id)) {
            return $index;
        } else {
            $index = new AOD_Index();
            $index->id = 1;
            $index->new_with_id = true;
            $index->name = "Index";
            $index->location = "modules/AOD_Index/Index/Index";
            $index->save();
            return $index;
        }
    }

    /**
     * @param $revision
     * @return bool|Zend_Search_Lucene_Document
     */
    private function getDocumentForRevision($revision)
    {
        $path = getDocumentRevisionPath($revision->id);
        if (!file_exists($path)) {
            return array("error"=>"File not found");
        }
        //Convert the file to a lucene document
        $mime = $revision->file_mime_type;
        switch ($mime) {
            case 'application/pdf':
                $document = createPDFDocument($path);
                break;
            case 'application/msword':
                $document = createDocDocument($path);
                break;
            case 'application/vnd.oasis.opendocument.text':
                $document = createOdtDocument($path);
                break;
            case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
                $document = createDocXDocument($path);
                break;
            case 'text/html':
                $document = createHTMLDocument($path);
                break;
            case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
                $document = createXLSXDocument($path);
                break;
            case 'application/rtf':
                $document = createRTFDocument($path);
                // no break
            case 'text/csv':
            case 'text/plain':
                $document = createTextDocument($path);
                break;
            case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
                $document = createPPTXDocument($path);
                break;
            case 'application/vnd.oasis.opendocument.spreadsheet':
            case 'application/vnd.ms-powerpoint':
            case 'application/vnd.ms-excel':
            default:
                return array("error"=>"Mime type $mime not supported");
        }
        if (!$document) {
            return array("error"=>"Failed to parse document");
        }
        $document->addField(Zend_Search_Lucene_Field::text("filename", $revision->filename));
        return array("error"=>false,"document"=>$document);
    }

    public function getDocumentForBean(SugarBean $bean)
    {
        if ($bean->module_name == 'DocumentRevisions') {
            $document = $this->getDocumentForRevision($bean);
        } else {
            $document = array("error"=>false,"document"=>new Zend_Search_Lucene_Document());
        }
        if ($document["error"]) {
            return $document;
        }
        $document["document"]->addField(Zend_Search_Lucene_Field::Keyword("aod_id", $bean->module_name." ".$bean->id));
        $document["document"]->addField(Zend_Search_Lucene_Field::UnIndexed("record_id", $bean->id));
        $document["document"]->addField(Zend_Search_Lucene_Field::UnIndexed("record_module", $bean->module_name));
        foreach ($GLOBALS['dictionary'][$bean->getObjectName()]['fields'] as $key => $field) {
            switch ($field['type']) {
                case "enum":
                    if (property_exists($bean, $key)) {
                        $document["document"]->addField(Zend_Search_Lucene_Field::Keyword($key, strtolower($bean->$key), 'UTF-8'));
                    }
                    break;

                case "multienum":
                    if (property_exists($bean, $key)) {
                        $vals = unencodeMultienum($bean->$key);
                        $document["document"]->addField(Zend_Search_Lucene_Field::unStored($key, strtolower(implode(" ", $vals)), 'UTF-8'));
                    }
                    break;
                case "name":
                case "phone":
                case "html":
                case "text":
                case "url":
                case "varchar":
                    if (property_exists($bean, $key)) {
                        $val = strtolower($bean->$key);
                    } else {
                        $val = '';
                    }
                    $field = Zend_Search_Lucene_Field::unStored($key, $val, 'UTF-8');
                    $field->boost = $this->getBoost($bean->module_name, $key);
                    $document["document"]->addField($field);
                    break;
                case "address":
                case "bool":
                case "currency":
                case "date":
                case "datetimecombo":
                case "decimal":
                case "float":
                case "iframe":
                case "int":
                case "radioenum":
                case "relate":
                default:
                    break;
            }
        }

        return $document;
    }

    private function getBoost($module, $field)
    {
        $fieldBoosts = array('name' =>0.5, 'first_name' => 0.5, 'last_name' => 0.5);
        $moduleBoosts = array('Accounts' => 0.5, 'Contacts' => 0.5, 'Leads' => 0.5, 'Opportunities' => 0.5);
        $boost = 1;
        if (!empty($fieldBoosts[$field])) {
            $boost += $fieldBoosts[$field];
        }
        if (!empty($moduleBoosts[$module])) {
            $boost += $moduleBoosts[$module];
        }
        return $boost;
    }

    private function getIndexEvent($module, $beanId)
    {
        global $timedate;
        $indexEventBean = BeanFactory::getBean("AOD_IndexEvent");
        $indexEvents = $indexEventBean->get_full_list('', "aod_indexevent.record_id = '".$beanId."' AND aod_indexevent.record_module = '".$module."'");
        if ($indexEvents) {
            $indexEvent = $indexEvents[0];
            if (count($indexEvents) > 1) {
                for ($x = 1; $x < count($indexEvents); $x++) {
                    $duplicateIE = $indexEvents[$x];
                    $duplicateIE->mark_deleted($duplicateIE->id);
                }
            }
        } else {
            $indexEvent = BeanFactory::newBean("AOD_IndexEvent");
            $indexEvent->record_id = $beanId;
            $indexEvent->record_module = $module;
        }
        /*
         * "Now" is cached in the SugarBean which means for long running processes (such as the indexing scheduler) that
         * the date_modified could be in the past. This caused issues when comparing the date modified of the event with that
         * of a bean. Here we explicitly set the date modified to be the current date.
         */
        $indexEvent->update_date_modified = false;
        $indexEvent->date_modified = $timedate->asDb(new DateTime());
        return $indexEvent;
    }

    public function commit()
    {
        if (!$this->isEnabled()) {
            return;
        }
        $this->getLuceneIndex()->commit();
    }

    public static function isModuleSearchable($module, $beanName)
    {
        $whiteList = array("DocumentRevisions","Cases");
        if (in_array($module, $whiteList)) {
            return true;
        }
        $blackList = array("AOD_IndexEvent","AOD_Index","AOW_Actions","AOW_Conditions","AOW_Processed","SchedulersJobs");
        if (in_array($module, $blackList)) {
            return false;
        }
        $manager = new VardefManager();
        $manager->loadVardef($module, $beanName);
        if (empty($GLOBALS['dictionary'][$beanName]['unified_search'])) {
            return false;
        }
        return true;
    }

    public function index($module, $beanId)
    {
        try {
            if (!$this->isEnabled()) {
                return;
            }
            if (empty($GLOBALS['beanList'][$module])) {
                return false;
            }
            $bean_name = $GLOBALS['beanList'][$module];
            $bean = new $bean_name();
            if (!$bean || ! $bean instanceof SugarBean) {
                return false;
            }

            if (!self::isModuleSearchable($module, BeanFactory::getObjectName($module))) {
                return false;
            }

            $bean = $bean->retrieve($beanId);
            if (!$bean) {
                return false;
            }

            $indexEvent = $this->getIndexEvent($module, $beanId);
            $indexEvent->name = $bean->get_summary_text();

            $document = $this->getDocumentForBean($bean);
            //Index name, id, date, filename
            if (!$document['error']) {
                $this->remove($module, $beanId);
                $this->getLuceneIndex()->addDocument($document['document']);
                $indexEvent->success = true;
            } else {
                $indexEvent->success = false;
                $indexEvent->error = $document['error'];
            }
            $indexEvent->save();
        } catch (Exception $ex) {
            $GLOBALS['log']->error($ex->getMessage());
            return false;
        }
        return true;
    }
    private function getIdForDoc($module, $beanId)
    {
        return $module . " " . $beanId;
    }

    public function remove($module, $beanId)
    {
        $term  = new Zend_Search_Lucene_Index_Term($module.' '.$beanId, 'aod_id');
        $query = new Zend_Search_Lucene_Search_Query_Term($term);
        $hits = $this->getLuceneIndex()->find($query);
        foreach ($hits as $hit) {
            $this->getLuceneIndex()->delete($hit->id);
        }
    }

    /**
     * Returns a handle on the actual lucene index.
     * @return Zend_Search_Lucene_Interface
     */
    private function getLuceneIndex()
    {
        if (file_exists($this->location)) {
            $this->index = new Zend_Search_Lucene($this->location);
        } else {
            $this->index = Zend_Search_Lucene::create($this->location);
        }
        $this->index->setMaxBufferedDocs(64);
        //$this->index->setMaxMergeDocs(50);
        Zend_Search_Lucene_Search_Query_Fuzzy::setDefaultPrefixLength(1);
        $this->index->setMergeFactor(5);
        return $this->index;
    }


    public function getIndexableModules()
    {
        $modules = array();
        $beanList = $GLOBALS['beanList'];
        ksort($beanList);
        foreach ($beanList as $beanModule => $beanName) {
            if (self::isModuleSearchable($beanModule, $beanName)) {
                $modules[$beanModule] = $beanName;
    