mirror of
https://github.com/salesagility/SuiteCRM.git
synced 2024-11-24 16:46:48 +00:00
a60ee78993
Update function return type to ?string so that null and string can be returned
306 lines
9.5 KiB
PHP
306 lines
9.5 KiB
PHP
<?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 - 2021 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".
|
|
*/
|
|
|
|
namespace SuiteCRM\Search;
|
|
|
|
use BeanFactory;
|
|
use LoggerManager;
|
|
use SugarBean;
|
|
use SuiteCRM\Exception\Exception;
|
|
use SuiteCRM\Exception\InvalidArgumentException;
|
|
|
|
if (!defined('sugarEntry') || !sugarEntry) {
|
|
die('Not A Valid Entry Point');
|
|
}
|
|
|
|
/**
|
|
* Class SearchResults holds results and metadata of a search.
|
|
*
|
|
* @author Vittorio Iocolano
|
|
*/
|
|
#[\AllowDynamicProperties]
|
|
class SearchResults
|
|
{
|
|
/** @var array Contains the results ids */
|
|
private $hits;
|
|
/** @var array Contains the scores of each hit. This should match in structure with $hits */
|
|
private $scores;
|
|
/** @var array Similar to scores, but customisable by the search engine */
|
|
private $options;
|
|
/** @var float The number of seconds it took to perform the search */
|
|
private $searchTime;
|
|
/** @var int The total number of hits (without pagination) */
|
|
private $total;
|
|
/** @var bool Flag specifying if the (nested) hits are grouped by the modules */
|
|
private $groupedByModule;
|
|
|
|
/**
|
|
* SearchResults constructor.
|
|
*
|
|
* @param array $hits Contains the results ids
|
|
* @param bool $groupedByModule Flag specifying if the (nested) hits are grouped by the modules
|
|
* @param float|null $searchTime The number of seconds it took to perform the search
|
|
* @param int|null $total The number of total hits (without pagination)
|
|
* @param array|null $scores Contains the scores of each hit. This should match in structure with $hits
|
|
* @param array|null $options Similar to scores, but customisable by the search engine
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public function __construct(
|
|
array $hits,
|
|
$groupedByModule = true,
|
|
float $searchTime = null,
|
|
int $total = null,
|
|
array $scores = null,
|
|
array $options = null
|
|
) {
|
|
$this->hits = $hits;
|
|
$this->scores = $scores;
|
|
$this->options = $options;
|
|
$this->groupedByModule = $groupedByModule;
|
|
$this->searchTime = $searchTime;
|
|
$this->total = $total;
|
|
|
|
if ($this->scores !== null && count($hits) !== count((array) $scores)) {
|
|
throw new InvalidArgumentException('The sizes of $hits and $scores must match.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the results.
|
|
*
|
|
* Note that the structure of the results depends whether of not they have been nested by module or not.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getHits(): array
|
|
{
|
|
return $this->hits;
|
|
}
|
|
|
|
/**
|
|
* Fetches the results (originally just module->id) as Beans.
|
|
*
|
|
* @return array
|
|
* @throws Exception
|
|
* @see getHits()
|
|
*/
|
|
public function getHitsAsBeans(): array
|
|
{
|
|
$searchHits = $this->hits;
|
|
$parsed = [];
|
|
|
|
foreach ($searchHits as $module => $beans) {
|
|
foreach ((array)$beans as $bean) {
|
|
$obj = BeanFactory::getBean($module, $bean);
|
|
|
|
// if a search found a bean but suitecrm does not, it could happens
|
|
// maybe the bean is deleted but elsasticsearch is not re-indexing yet.
|
|
// so at this point we trying to rebuild the index and try again to get bean:
|
|
if (!$obj) {
|
|
ElasticSearch\ElasticSearchIndexer::repairElasticsearchIndex();
|
|
$obj = BeanFactory::getBean($module, $bean);
|
|
}
|
|
|
|
if (!$obj) {
|
|
throw new Exception('Error retrieving bean: ' . $module . ' [' . $bean . ']');
|
|
}
|
|
|
|
$obj->load_relationships();
|
|
$fieldDefs = $obj->getFieldDefinitions();
|
|
$parsed[$module][] = $this->updateFieldDefLinks($obj, $fieldDefs);
|
|
}
|
|
}
|
|
|
|
return $parsed;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param SugarBean $obj
|
|
* @param array $fieldDefs
|
|
* @return SugarBean
|
|
*/
|
|
protected function updateFieldDefLinks(SugarBean $obj, array $fieldDefs): SugarBean
|
|
{
|
|
foreach ($fieldDefs as $fieldDef) {
|
|
if (isset($obj->{$fieldDef['name']})) {
|
|
$obj = $this->updateObjLinks($obj, $fieldDef);
|
|
}
|
|
}
|
|
|
|
return $obj;
|
|
}
|
|
|
|
/**
|
|
* Update related links in a bean to show it on results page
|
|
*
|
|
* @param SugarBean $obj
|
|
* @param array $fieldDef
|
|
* @return SugarBean
|
|
*/
|
|
protected function updateObjLinks(SugarBean $obj, array $fieldDef): SugarBean
|
|
{
|
|
if (isset($fieldDef['link']) && !empty($fieldDef['id_name']) && $fieldDef['type'] === 'relate') {
|
|
$relId = $this->getRelatedId($obj, $fieldDef['id_name'], $fieldDef['link']);
|
|
if (!empty($relId)) {
|
|
$obj->{$fieldDef['name']} = $this->getLink(
|
|
$obj->{$fieldDef['name']},
|
|
$fieldDef['module'],
|
|
$relId,
|
|
'DetailView'
|
|
);
|
|
}
|
|
} elseif ($fieldDef['name'] === 'name') {
|
|
$obj->{$fieldDef['name']} = $this->getLink(
|
|
$obj->{$fieldDef['name']},
|
|
$obj->module_name,
|
|
$obj->id,
|
|
'DetailView'
|
|
);
|
|
}
|
|
|
|
return $obj;
|
|
}
|
|
|
|
/**
|
|
* resolve related record ID
|
|
*
|
|
* @param SugarBean $obj
|
|
* @param string $idName
|
|
* @param string $link
|
|
* @return string
|
|
*/
|
|
protected function getRelatedId(SugarBean $obj, string $idName, string $link): ?string
|
|
{
|
|
$relField = $idName;
|
|
if (isset($obj->$link)) {
|
|
$relId = $obj->$link->getFocus()->$relField;
|
|
if (is_object($relId)) {
|
|
if (method_exists($relId, "getFocus")) {
|
|
$relId = $relId->getFocus()->id;
|
|
} else {
|
|
$relId = null;
|
|
}
|
|
}
|
|
} elseif (isset($obj->$relField)) {
|
|
$relId = $obj->$relField;
|
|
} else {
|
|
$relId = null;
|
|
LoggerManager::getLogger()->warn('Unresolved related ID for field: ' . $relField);
|
|
}
|
|
|
|
if (!$relId) {
|
|
$relId = '';
|
|
}
|
|
return $relId;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param string $label
|
|
* @param string $module
|
|
* @param string $record
|
|
* @param string $action
|
|
* @return string
|
|
* @global array $sugar_config
|
|
*/
|
|
protected function getLink(string $label, string $module, string $record, string $action): string
|
|
{
|
|
global $sugar_config;
|
|
|
|
return "<a href=\"{$sugar_config['site_url']}/index.php?action={$action}&module={$module}&record={$record}&offset=1\"><span>{$label}</span></a>";
|
|
}
|
|
|
|
/**
|
|
* Returns an arbitrary scores defining how much related a hit is to the query.
|
|
*
|
|
* @return mixed[]|null
|
|
*/
|
|
public function getScores(): ?array
|
|
{
|
|
return $this->scores;
|
|
}
|
|
|
|
/**
|
|
* Returns the total number of hits (without pagination).
|
|
*
|
|
* @return int|null
|
|
*/
|
|
public function getTotal(): ?int
|
|
{
|
|
return $this->total;
|
|
}
|
|
|
|
/**
|
|
* @return mixed[]|null
|
|
*/
|
|
public function getOptions(): ?array
|
|
{
|
|
return $this->options;
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getOption(string $key): array
|
|
{
|
|
return $this->options[$key];
|
|
}
|
|
|
|
/**
|
|
* Time in seconds it took to perform the search.
|
|
*
|
|
* @return float|null
|
|
*/
|
|
public function getSearchTime(): ?float
|
|
{
|
|
return $this->searchTime;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isGroupedByModule(): bool
|
|
{
|
|
return $this->groupedByModule;
|
|
}
|
|
}
|