BxDolSearch.php 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaCore UNA Core
  7. * @{
  8. */
  9. define('BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT', 10);
  10. define('BX_DOL_SEARCH_KEYWORD_PAGE', 'site-search-page');
  11. /**
  12. * Search class for different content.
  13. *
  14. * Allows present content in main search area for all modules.
  15. *
  16. * To add taking part content from your module to search area you need add a record to `sys_objects_search` table:
  17. * @code
  18. * ID - autoincremented id for internal usage
  19. * ObjectName - your unique module name, with vendor prefix, lowercase and spaces are underscored
  20. * Title - language key for module's choicer in global search form
  21. * ClassName - class name which is responsible for search process ad showing result for found results
  22. * ClassPath - file where your ClassName is stored (if class is stored in template folder then you can use key {tmpl})
  23. * @endcode
  24. *
  25. * For using this class you should have unit of BxDolSearchResult class (see description below) with method processing which is responsible for processing of search request.
  26. *
  27. * Example of usage can be seen in the BoonEx modules, for example: Posts
  28. *
  29. * Memberships/ACL:
  30. * Doesn't depend on user's membership.
  31. *
  32. * Alerts:
  33. * no alerts available
  34. *
  35. */
  36. class BxDolSearch extends BxDol
  37. {
  38. protected $_bIsApi;
  39. protected $aClasses = array(); ///< array of all search classes
  40. protected $aChoice = array(); ///< array of current search classes which were choosen in search area
  41. protected $_bRawProcessing = false; ///< display search results without design box and paginate
  42. protected $_bDataProcessing = false; ///< return result as array of data
  43. protected $_bLiveSearch = false;
  44. protected $_sMetaType = '';
  45. protected $_sCategoryObject = ''; ///< by default = object from sys_objects_category table, for multicategories set to 'multi' string
  46. protected $_aCustomSearchCondition = array();
  47. protected $_aCustomCurrentCondition = array();
  48. protected $_sUnitTemplate = '';
  49. /**
  50. * Constructor
  51. * @param array $aChoice - array of choosen classes (will take a part only existing in `sys_objects_search` table)
  52. */
  53. public function __construct ($aChoice = '')
  54. {
  55. parent::__construct();
  56. $this->_bIsApi = bx_is_api();
  57. $this->aClasses = self::getSections();
  58. if (is_array($aChoice) && !empty($aChoice)) {
  59. foreach ($aChoice as $sValue) {
  60. if (isset($this->aClasses[$sValue]))
  61. $this->aChoice[$sValue] = $this->aClasses[$sValue];
  62. }
  63. } else {
  64. $this->aChoice = $this->aClasses;
  65. }
  66. }
  67. public static function getSections($bGlobal = null)
  68. {
  69. $sWhere = '';
  70. if ($bGlobal != null)
  71. $sWhere = ' AND `GlobalSearch` = ' . ($bGlobal ? '1' : '0');
  72. return BxDolDb::getInstance()->fromCache('sys_objects_search', 'getAllWithKey',
  73. 'SELECT `ID` as `id`,
  74. `Title` as `title`,
  75. `ClassName` as `class`,
  76. `ClassPath` as `file`,
  77. `ObjectName`,
  78. `GlobalSearch`
  79. FROM `sys_objects_search`
  80. WHERE 1 ' . $sWhere . '
  81. ORDER BY `Order` ASC', 'ObjectName'
  82. );
  83. }
  84. public function setApiOutput($bIsApi)
  85. {
  86. $this->_bIsApi = $bIsApi;
  87. }
  88. /**
  89. * create units for all classes and calling their processing methods
  90. */
  91. public function response ()
  92. {
  93. $sCode = $this->_bDataProcessing ? [] : '';
  94. $bSingle = count($this->aChoice) == 1;
  95. foreach($this->aChoice as $sKey => $aValue) {
  96. if(!$this->_sMetaType && !$aValue['GlobalSearch'])
  97. continue;
  98. $sClassName = 'BxTemplSearchResult';
  99. if(!empty($aValue['class'])) {
  100. $sClassName = $aValue['class'];
  101. if(!empty($aValue['file']))
  102. require_once(BX_DIRECTORY_PATH_ROOT . $aValue['file']);
  103. }
  104. $oEx = new $sClassName();
  105. if($this->_sMetaType && !$oEx->isMetaEnabled($this->_sMetaType))
  106. continue;
  107. $oEx->setId($aValue['id']);
  108. $oEx->setLiveSearch($this->_bLiveSearch);
  109. $oEx->setMetaType($this->_sMetaType);
  110. $oEx->setCategoryObject($this->_sCategoryObject);
  111. $oEx->setCenterContentUnitSelector(false);
  112. $oEx->setSingleSearch($bSingle);
  113. $oEx->setCustomSearchCondition($this->_aCustomSearchCondition);
  114. $oEx->aCurrent = array_merge_recursive($oEx->aCurrent, $this->_aCustomCurrentCondition);
  115. if($this->_sUnitTemplate)
  116. $oEx->setUnitTemplate($this->_sUnitTemplate);
  117. if($this->_bDataProcessing) {
  118. if($this->_bIsApi) {
  119. if($bSingle) {
  120. $sMethod = 'decodeDataAPI';
  121. if($this->_bLiveSearch && ($sMethodLs = 'decodeLsDataAPI') && method_exists($oEx, $sMethodLs))
  122. $sMethod = $sMethodLs;
  123. $sCode = $oEx->$sMethod($oEx->getSearchData());
  124. }
  125. else
  126. $sCode[$sKey] = $oEx->getSearchQuery($sKey);
  127. }
  128. else
  129. $sCode[$sKey] = $oEx->getSearchData();
  130. }
  131. else
  132. $sCode .= $this->_bRawProcessing ? $oEx->processingRaw() : $oEx->processing();
  133. }
  134. if($this->_bIsApi && !$bSingle && is_array($sCode)) {
  135. $bExtendedUnits = getParam('sys_api_extended_units') == 'on';
  136. $aQueries = [];
  137. foreach($sCode as $aQuery)
  138. if(!empty($aQuery['query']))
  139. $aQueries[] = trim($aQuery['query'] . ' ' . $aQuery['order'] . ' ' . $aQuery['limit']);
  140. $aItems = BxDolDb::getInstance()->getAll('(' . implode(') UNION (', $aQueries) . ')');
  141. $sCode = [];
  142. foreach($aItems as $aItem)
  143. if(($oContentInfo = BxDolContentInfo::getObjectInstance($aItem['content_info'])))
  144. $sCode[] = $oContentInfo->getContentInfoAPI($aItem['id'], $bExtendedUnits);
  145. }
  146. return $sCode;
  147. }
  148. public function getEmptyResult ()
  149. {
  150. $sKey = _t('_Empty');
  151. return DesignBoxContent($sKey, MsgBox($sKey), BX_DB_PADDING_DEF);
  152. }
  153. public function getSearchResultObject($sChoice = '')
  154. {
  155. $aSearchResult = [];
  156. if($sChoice) {
  157. if(empty($this->aChoice[$sChoice]) || !is_array($this->aChoice[$sChoice]))
  158. return false;
  159. $aSearchResult = $this->aChoice[$sChoice];
  160. }
  161. else
  162. $aSearchResult = reset($this->aChoice);
  163. $sClassName = 'BxTemplSearchResult';
  164. if(!empty($aSearchResult['class'])) {
  165. $sClassName = $aSearchResult['class'];
  166. if(!empty($aSearchResult['file']))
  167. require_once(BX_DIRECTORY_PATH_ROOT . $aSearchResult['file']);
  168. }
  169. return new $sClassName();
  170. }
  171. protected function getKeyTitlesPairs ()
  172. {
  173. $a = array();
  174. foreach ($this->aChoice as $sKey => $r)
  175. if ($this->_sMetaType || $r['GlobalSearch'])
  176. $a[$sKey] = _t($r['title']);
  177. return $a;
  178. }
  179. public function setLiveSearch($bLiveSearch)
  180. {
  181. $this->_bLiveSearch = $bLiveSearch;
  182. }
  183. public function setMetaType($s)
  184. {
  185. $aMetaTypes = array('location_country', 'location_country_city', 'location_country_state', 'mention', 'keyword');
  186. if (in_array($s, $aMetaTypes))
  187. $this->_sMetaType = $s;
  188. }
  189. public function setCategoryObject($s)
  190. {
  191. $this->_sCategoryObject = $s;
  192. }
  193. /**
  194. * Set custom search condition to use istead of GET/POST variables
  195. * @param $a array of params, such as 'keyword', 'state', 'city'
  196. * @return nothing
  197. */
  198. public function setCustomSearchCondition($a)
  199. {
  200. $this->_aCustomSearchCondition = $a;
  201. }
  202. /**
  203. * Set custom data for aCurrent array
  204. * @param $a array of params
  205. * @return nothing
  206. */
  207. public function setCustomCurrentCondition($a)
  208. {
  209. $this->_aCustomCurrentCondition = $a;
  210. }
  211. /**
  212. * Set custom unit template
  213. * @param $s template name
  214. * @return nothing
  215. */
  216. public function setUnitTemplate($s)
  217. {
  218. $this->_sUnitTemplate = $s;
  219. }
  220. /**
  221. * Display search results without design box and paginate
  222. */
  223. public function setRawProcessing($b)
  224. {
  225. $this->_bRawProcessing = $b;
  226. }
  227. /**
  228. * Return search result as array of data
  229. */
  230. public function setDataProcessing($b)
  231. {
  232. $this->_bDataProcessing = $b;
  233. }
  234. }
  235. /*
  236. * Search class for processing search requests and displaying search results.
  237. *
  238. * Allows present content from modules on search params or internal (via fields of class) conditions.
  239. *
  240. * Example of usage (you can see example in any of default Dolhpin's modules):
  241. *
  242. * 1. Extends your own search class from this one (or from BxBaseSearchResult or BxBaseSearchResultSharedMedia classes)
  243. * 2. Set necessary fields of class (using as example BxFilesSearch):
  244. *
  245. * @code
  246. *
  247. * // main field of search class
  248. * $this->aCurrent = array(
  249. *
  250. * // name of module
  251. * 'name' => 'bx_files',
  252. *
  253. * // language key with name of module
  254. * 'title' => '_bx_files',
  255. *
  256. * // main content table
  257. * 'table' => 'bx_files_main',
  258. *
  259. * // array of all fields which can be choosen for select in result query
  260. * 'ownFields' => array('ID', 'Title', 'Uri', 'Desc', 'Date', 'Size', 'Ext', 'Views', 'Rate', 'RateCount', 'Type'),
  261. *
  262. * // array of fields which take a part in search by keyword (global search and related words only - in other cases leave it blank)
  263. * 'searchFields' => array('Title', 'Tags', 'Desc', 'Categories'),
  264. *
  265. * // array of join tables
  266. * // 'type' - type of join
  267. * // 'table' - join table
  268. * // 'mainField' - field from main table for 'on' condition
  269. * // 'onField' - field from joining table for 'on' condition
  270. * // 'joinFields' - array of fields from joining table
  271. *
  272. * 'join' => array(
  273. * 'profile' => array(
  274. * 'type' => 'left',
  275. * 'table' => 'Profiles',
  276. * 'mainField' => 'Owner',
  277. * 'onField' => 'ID',
  278. * 'joinFields' => array('NickName'),
  279. * ),
  280. * // ...
  281. * ),
  282. *
  283. * // array of search parameters
  284. * // 'value' - value of search parameter (can be number, string or array)
  285. * // 'field' - field which will take value of search, may be lefy as blank then in search query will be pasted to WHERE conditon via operator AGAINST
  286. * // 'operator' - operator between field and value (can be 'in', 'not in', '>', '<', 'against', 'like' and '=' by default),
  287. * // 'paramName' - GET param which will keep 'value' for pagianation
  288. *
  289. * 'restriction' => array(
  290. * 'activeStatus' => array('value'=>'approved', 'field'=>'Status', 'operator'=>'=', 'paramName' => 'status'),
  291. * 'albumType' => array('value'=>'', 'field'=>'Type', 'operator'=>'=', 'paramName'=>'albumType', 'table'=>'sys_albums'),
  292. * ),
  293. *
  294. * // array of pagination
  295. * // 'perPage' - units per page
  296. * // 'start' - show search results starting from this number
  297. * // 'num' - number of element to show on current page + 1
  298. *
  299. * 'paginate' => array('perPage' => 10, 'start' => 0, 'num' => 11),
  300. *
  301. * // sort mode - by default it was last (DESC by date)
  302. * 'sorting' => 'last',
  303. *
  304. * // mode of units presentation in search result, can be 'short' or 'full' view
  305. * 'view' => 'full',
  306. *
  307. * // field of id
  308. * 'ident' => 'ID',
  309. *
  310. * // rss feed array
  311. * 'rss' => array(
  312. * 'title' => '',
  313. * 'link' => '',
  314. * 'image' => '',
  315. * 'profile' => 0,
  316. * 'fields' => array (
  317. * 'Link' => '',
  318. * 'Title' => 'Title',
  319. * 'DateTimeUTS' => 'Date',
  320. * 'Desc' => 'Desc',
  321. * 'Photo' => '',
  322. * ),
  323. * ),
  324. * );
  325. *
  326. * // array of fields renamings
  327. * $aPseud - filling in costructor by function _getPseud
  328. *
  329. * // unique identificator from `sys_objects_search` table
  330. * $id;
  331. *
  332. * @endcode
  333. *
  334. * Memberships/ACL:
  335. * Doesn't depend on user's membership.
  336. *
  337. * Alerts:
  338. * no alerts available
  339. *
  340. */
  341. class BxDolSearchResult implements iBxDolReplaceable
  342. {
  343. public $aCurrent; ///< search results configuration
  344. protected $aPseud; ///< array of fields renamings
  345. protected $id; ///< unique identificator of block on mixed search results page
  346. protected $bDisplayEmptyMsg = false; ///< display empty message instead of nothing, when no results
  347. protected $sDisplayEmptyMsgKey = ''; ///< custom empty message language key, instead of default "empty" message
  348. protected $bProcessPrivateContent = true; ///< check each item for privacy, if view isn't allowed then display private view instead
  349. protected $aPrivateConditionsIndexes = array('restriction' => array(), 'join' => array()); ///< conditions indexes for bProcessPrivateContent
  350. protected $bForceAjaxPaginate = false; ///< force ajax paginate
  351. protected $iPaginatePerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT; ///< default 'per page' value for paginate.
  352. protected $_bIsApi;
  353. protected $_bSingleSearch = true;
  354. protected $_bLiveSearch = false;
  355. protected $_sMetaType = '';
  356. protected $_sMode = '';
  357. protected $_aParams = [];
  358. protected $_sCategoryObject = '';
  359. protected $_aCustomSearchCondition = array();
  360. protected $_aMarkers = array (); ///< markers to replace somewhere, usually title and browse url (defined in custom class)
  361. /**
  362. * constructor
  363. * filling identificator field
  364. */
  365. function __construct ()
  366. {
  367. $this->_bIsApi = bx_is_api();
  368. if (isset($this->aPseud['id']))
  369. $this->aCurrent['ident'] = $this->aPseud['id'];
  370. }
  371. public function getId()
  372. {
  373. return $this->id;
  374. }
  375. public function setId($sId)
  376. {
  377. $this->id = $sId;
  378. }
  379. public function getModuleName()
  380. {
  381. return isset($this->aCurrent['module_name']) ? $this->aCurrent['module_name'] : '';
  382. }
  383. public function getContentInfoName()
  384. {
  385. return isset($this->aCurrent['name']) ? $this->aCurrent['name'] : '';
  386. }
  387. public function setAjaxPaginate($b = true)
  388. {
  389. $this->bForceAjaxPaginate = $b;
  390. }
  391. public function setPaginatePerPage($iPerPage)
  392. {
  393. $this->iPaginatePerPage = $iPerPage;
  394. }
  395. public function setSingleSearch($bSingleSearch)
  396. {
  397. $this->_bSingleSearch = $bSingleSearch;
  398. }
  399. public function setLiveSearch($bLiveSearch)
  400. {
  401. $this->_bLiveSearch = $bLiveSearch;
  402. }
  403. public function setMetaType($s)
  404. {
  405. $this->_sMetaType = $s;
  406. }
  407. public function isMetaEnabled($s)
  408. {
  409. if (empty($this->aCurrent['object_metatags']))
  410. return false;
  411. if (!($o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags'])))
  412. return false;
  413. switch ($s) {
  414. case 'location_country_city':
  415. case 'location_country_state':
  416. case 'location_country':
  417. return $o->locationsIsEnabled();
  418. case 'mention':
  419. return $o->mentionsIsEnabled();
  420. case 'keyword':
  421. return $o->keywordsIsEnabled();
  422. }
  423. return false;
  424. }
  425. public function setCategoryObject($s)
  426. {
  427. $this->_sCategoryObject = $s;
  428. }
  429. public function setCustomSearchCondition($a)
  430. {
  431. $this->_aCustomSearchCondition = $a;
  432. }
  433. public function setCustomCurrentCondition($a)
  434. {
  435. $this->aCurrent = array_merge_recursive($this->aCurrent, $a);
  436. }
  437. public function setCategoriesCondition($sKeyword)
  438. {
  439. $this->aCurrent['join']['multicat'] = [
  440. 'type' => 'INNER',
  441. 'table' => 'sys_categories2objects',
  442. 'mainField' => 'id',
  443. 'onField' => 'object_id',
  444. 'joinFields' => [],
  445. ];
  446. if(isset($this->aCurrent['tableSearch']))
  447. $this->aCurrent['join']['multicat']['mainTable'] = $this->aCurrent['tableSearch'];
  448. $this->aCurrent['join']['multicat2'] = [
  449. 'type' => 'INNER',
  450. 'table' => 'sys_categories',
  451. 'mainField' => 'category_id',
  452. 'mainTable' => 'sys_categories2objects',
  453. 'onField' => 'id',
  454. 'joinFields' => []
  455. ];
  456. $this->aCurrent['restriction']['multicat'] = [
  457. 'value' => $sKeyword,
  458. 'field' => 'value',
  459. 'operator' => '=',
  460. 'table' => 'sys_categories'
  461. ];
  462. }
  463. /**
  464. * Display empty message if there is no content, custom empty message can be used.
  465. * @param $b - boolan value to enable or disable 'empty' message
  466. * @param $sLangKey [optional] - custom 'empty' message
  467. */
  468. public function setDisplayEmptyMsg($b, $sLangKey = '')
  469. {
  470. $this->bDisplayEmptyMsg = $b;
  471. if ($sLangKey)
  472. $this->sDisplayEmptyMsgKey = $sLangKey;
  473. }
  474. /**
  475. * Perform privacy checking for every unit
  476. * @param $b - boolan value to enable or disable privacy checking
  477. */
  478. public function setProcessPrivateContent ($b)
  479. {
  480. $this->bProcessPrivateContent = $b;
  481. if ($b) {
  482. // unset condition which was set when 'bProcessPrivateContent' was set to 'false'
  483. foreach ($this->aPrivateConditionsIndexes['restriction'] as $sKey)
  484. unset($this->aCurrent['restriction'][$sKey]);
  485. foreach ($this->aPrivateConditionsIndexes['join'] as $sKey)
  486. unset($this->aCurrent['join'][$sKey]);
  487. }
  488. }
  489. /**
  490. * Get search results without design box and paginate
  491. * @return html code
  492. */
  493. function processingRaw ()
  494. {
  495. $sCode = $this->displayResultBlock();
  496. return $this->aCurrent['paginate']['num'] > 0 ? $sCode : '';
  497. }
  498. /**
  499. * Get html box of search results (usually used in grlobal search)
  500. * @return html code
  501. */
  502. function processing ()
  503. {
  504. if($this->_bIsApi)
  505. return $this->processingAPI();
  506. $sCode = $this->displayResultBlock();
  507. if ($this->aCurrent['paginate']['num'] > 0) {
  508. $sPaginate = $this->showPagination();
  509. $sCode = $this->displaySearchBox($sCode, $sPaginate);
  510. }
  511. else {
  512. $sCode = $this->bDisplayEmptyMsg ? $this->displaySearchBox($this->displayResultBlockEmpty()) : '';
  513. }
  514. return $sCode;
  515. }
  516. function processingAPI ($bForceGetData = false)
  517. {
  518. $sModule = 'system';
  519. $sUnitType = 'content';
  520. $sHomeUrl = '';
  521. if(!empty($this->oModule)) {
  522. $sModule = $this->oModule->getName();
  523. if(method_exists($this->oModule, 'serviceActAsProfile') && $this->oModule->serviceActAsProfile())
  524. $sUnitType = 'profile';
  525. if(method_exists($this->oModule, 'serviceIsGroupProfile') && $this->oModule->serviceIsGroupProfile())
  526. $sUnitType = 'context';
  527. $sHomeUrl = bx_api_get_relative_url(BxDolPermalinks::getInstance()->permalink($this->oModule->_oConfig->CNF['URL_HOME']));
  528. }
  529. $sUnit = 'list';
  530. if(in_array($this->sUnitViewDefault, ['showcase', 'gallery']))
  531. $sUnit = 'card';
  532. $aData = defined('BX_API_PAGE') && !$bForceGetData ? [] : $this->decodeDataAPI($this->getSearchData());
  533. $aParams = [
  534. 'type' => $this->_sMode,
  535. 'start' => $this->aCurrent['paginate']['start'],
  536. 'per_page' => $this->aCurrent['paginate']['perPage'],
  537. 'view' => $this->_aParams['unit_view'] ?? '',
  538. ];
  539. if($sHomeUrl)
  540. $aParams['home_url'] = $sHomeUrl;
  541. if(($sK = 'class_search_result') && isset($this->_aParams[$sK]))
  542. $aParams[$sK] = $this->_aParams[$sK];
  543. $aParamsAdd = [];
  544. if(($sMethod = 'addProcessingParamsAPI') && method_exists($this, $sMethod))
  545. $aParamsAdd = $this->$sMethod();
  546. foreach($aParamsAdd as $sParamAdd)
  547. if(isset($this->_aParams[$sParamAdd]))
  548. $aParams[$sParamAdd] = $this->_aParams[$sParamAdd];
  549. return [
  550. 'module' => $sModule,
  551. 'unit' => 'general-' . $sUnitType . '-' . $sUnit,
  552. 'request_url' => !empty($this->aCurrent['api_request_url']) ? $this->aCurrent['api_request_url'] : '/api.php?r=' . $sModule . '/browse/&params[]=',
  553. 'data' => $aData,
  554. 'params' => $aParams
  555. ];
  556. }
  557. function addProcessingParamsAPI()
  558. {
  559. return ['author', 'category', 'context', 'joined_profile', 'followed_contexts', 'level'];
  560. }
  561. function decodeDataAPI ($a)
  562. {
  563. return $a;
  564. }
  565. /**
  566. * Get html output of search result
  567. * @return html code
  568. */
  569. function displayResultBlock ()
  570. {
  571. $aData = $this->getSearchData();
  572. $sCode = '';
  573. if (count($aData) > 0) {
  574. $sCode = $this->addCustomParts();
  575. foreach ($aData as $iKey => $aValue) {
  576. $sCode .= $this->displaySearchUnit($aValue);
  577. }
  578. }
  579. return $sCode;
  580. }
  581. /**
  582. * Get html output of empty search result
  583. * @return html code
  584. */
  585. function displayResultBlockEmpty ()
  586. {
  587. return MsgBox(_t($this->sDisplayEmptyMsgKey ? $this->sDisplayEmptyMsgKey : '_Empty'));
  588. }
  589. /**
  590. * Add different code before html output (usually redeclared)
  591. * no return result
  592. */
  593. function addCustomParts ()
  594. {
  595. }
  596. /**
  597. * Get XML string for rss output
  598. */
  599. function rss ()
  600. {
  601. if (!isset($this->aCurrent['rss']['fields']) || !isset($this->aCurrent['rss']['link']))
  602. return '';
  603. $aData = $this->getSearchData();
  604. $f = &$this->aCurrent['rss']['fields'];
  605. if ($aData) {
  606. foreach ($aData as $k => $a) {
  607. $aData[$k][$f['Link']] = $this->getRssUnitLink ($a);
  608. if(isset($f['Image']))
  609. $aData[$k][$f['Image']] = $this->getRssUnitImage ($a, $f['Image']);
  610. }
  611. }
  612. $oRss = new BxDolRssFactory ();
  613. return $oRss->GenRssByCustomData(
  614. $aData,
  615. isset($this->aCurrent['rss']['title']) && $this->aCurrent['rss']['title'] ? $this->aCurrent['rss']['title'] : $this->aCurrent['title'],
  616. $this->aCurrent['rss']['link'],
  617. $this->aCurrent['rss']['fields'],
  618. isset($this->aCurrent['rss']['image']) ? $this->aCurrent['rss']['image'] : '',
  619. isset($this->aCurrent['rss']['profile']) ? $this->aCurrent['rss']['profile'] : 0
  620. );
  621. }
  622. /**
  623. * Output RSS XML with XML header
  624. */
  625. function outputRSS ()
  626. {
  627. header('Content-Type: text/xml; charset=UTF-8');
  628. echo $this->rss();
  629. }
  630. /**
  631. * Return rss unit link (redeclared)
  632. */
  633. function getRssUnitLink (&$a)
  634. {
  635. // override this functions to return permalink to rss unit
  636. }
  637. /**
  638. * Return rss unit image (redeclared)
  639. */
  640. function getRssUnitImage (&$a, $sField)
  641. {
  642. // override this functions to return image for rss unit
  643. }
  644. /**
  645. * Naming fields in query's body
  646. * @param string $sFieldName name of field
  647. * @param string $sTableName name of field's table
  648. * $param string $sOperator of field's calculation (like MAX)
  649. * @param boolean $bRenameMode indicator for renaming and unsetting fields from field of class $this->aPseud
  650. * return $sqlUnit sql code and unsetting elements from aPseud field
  651. */
  652. function setFieldUnit ($sFieldName, $sTableName, $sOperator = '', $bRenameMode = true)
  653. {
  654. $bOperator = !empty($sOperator);
  655. $sqlUnit = "`$sTableName`.`$sFieldName`";
  656. if($bOperator)
  657. $sqlUnit = $sOperator . '(' . $sqlUnit . ')';
  658. if(!empty($this->aPseud) && $bRenameMode !== false && ($sKey = array_search($sFieldName . ($bOperator ? '_' . strtolower($sOperator) : ''), $this->aPseud)) !== false) {
  659. $sqlUnit .= " as `$sKey`";
  660. unset($this->aPseud[$sKey]);
  661. }
  662. return $sqlUnit . ', ';
  663. }
  664. /**
  665. * Get html code of of every search unit
  666. * @param array $aData array of every search unit
  667. * return html code
  668. */
  669. function displaySearchUnit ($aData)
  670. {
  671. }
  672. /**
  673. * Get html code of search box with search results
  674. * @param string $sCode html code of search results
  675. * $param $sPaginate html code of paginate
  676. * return html code
  677. */
  678. function displaySearchBox ($sCode, $sPaginate = '')
  679. {
  680. }
  681. /**
  682. * Get html code of pagination
  683. */
  684. function showPagination ($bAdmin = false, $bChangePage = true, $bPageReload = true)
  685. {
  686. }
  687. /**
  688. * Get array of data with search results
  689. * return array with data
  690. */
  691. function getSearchData ()
  692. {
  693. /**
  694. * @hooks
  695. * @hookdef hook-simple_search-before_get_data 'simple_search', 'before_get_data' - hook on before after get data
  696. * - $unit_name - equals `simple_search`
  697. * - $action - equals `before_get_data`
  698. * - $object_id - not used
  699. * - $sender_id - not used
  700. * - $extra_params - array of additional params with the following array keys:
  701. * - `object` - [array] by ref, array of parameters, can be overridden in hook processing
  702. * - `mode` - [string] search mode
  703. * @hook @ref hook-simple_search-before_get_data
  704. */
  705. bx_alert('simple_search', 'before_get_data', 0, false, [
  706. 'object' => &$this->aCurrent,
  707. 'object_ref' => &$this->aCurrent,
  708. 'mode' => $this->_sMode
  709. ]);
  710. if(($this->aPseud = $this->_getPseudFromParam()) === false)
  711. $this->aPseud = $this->_getPseud();
  712. $this->setConditionParams();
  713. $aData = $this->aCurrent['paginate']['num'] > 0 ? $this->getSearchDataByParams() : [];
  714. /**
  715. * @hooks
  716. * @hookdef hook-simple_search-get_data 'simple_search', 'get_data' - hook on after get data
  717. * - $unit_name - equals `simple_search`
  718. * - $action - equals `get_data`
  719. * - $object_id - not used
  720. * - $sender_id - not used
  721. * - $extra_params - array of additional params with the following array keys:
  722. * - `object` - [array] by ref, array of parameters, can be overridden in hook processing
  723. * - `mode` - [string] search mode
  724. * - `search_object` - [string] search object name
  725. * @hook @ref hook-simple_search-get_data
  726. */
  727. bx_alert('simple_search', 'get_data', 0, false, [
  728. 'object' => &$this->aCurrent,
  729. 'object_ref' => &$this->aCurrent,
  730. 'mode' => $this->_sMode,
  731. 'search_results' => &$aData,
  732. 'search_results_ref' => &$aData
  733. ]);
  734. return $aData;
  735. }
  736. /**
  737. * Get query [query, limit] for search results. Is used for combined search from different sections.
  738. * @param type $aParams array with params
  739. * @return type array with query and limit
  740. */
  741. function getSearchQuery($sObject, $aParams = [])
  742. {
  743. if(!is_array($aParams))
  744. $aParams = [];
  745. $aParams = array_merge($aParams, ['for_union' => true]);
  746. $this->aPseud = [
  747. 'id' => !empty($this->aCurrent['ident']) ? $this->aCurrent['ident'] : 'id',
  748. 'added' => !empty($this->aCurrent['added']) ? $this->aCurrent['added'] : 'added'
  749. ];
  750. $this->setConditionParams();
  751. $aQuery = $this->getSearchDataByParams($aParams);
  752. /**
  753. * @hooks
  754. * @hookdef hook-simple_search-get_query 'simple_search', 'get_query' - hook on get sql queries
  755. * - $unit_name - equals `simple_search`
  756. * - $action - equals `get_query`
  757. * - $object_id - not used
  758. * - $sender_id - not used
  759. * - $extra_params - array of additional params with the following array keys:
  760. * - `object` - [array] by ref, array of parameters, can be overridden in hook processing
  761. * - `mode` - [string] search mode
  762. * - `search_object` - [string] search object name
  763. * - `search_query` - [array] by ref, array of sql, can be overridden in hook processing
  764. * @hook @ref hook-simple_search-get_query
  765. */
  766. bx_alert('simple_search', 'get_query', 0, false, [
  767. 'object' => &$this->aCurrent,
  768. 'object_ref' => &$this->aCurrent,
  769. 'mode' => $this->_sMode,
  770. 'search_object' => $sObject,
  771. 'search_query' => &$aQuery,
  772. 'search_query_ref' => &$aQuery
  773. ]);
  774. return $aQuery;
  775. }
  776. function getFieldsOwn()
  777. {
  778. return false;
  779. }
  780. function getFieldsJoin($sJoin)
  781. {
  782. return false;
  783. }
  784. /**
  785. * Get array with code for sql elements
  786. * @param $bRenameMode indicator of renmaing fields
  787. * return array with joinFields, ownFields, groupBy and join elements
  788. */
  789. function getJoins ($bRenameMode = true)
  790. {
  791. if(!isset($this->aCurrent['join']) || !is_array($this->aCurrent['join']))
  792. return false;
  793. $aSql = [
  794. 'join' => '',
  795. 'ownFields' => '',
  796. 'joinFields' => '',
  797. 'groupBy' => '',
  798. 'groupHaving' => ''
  799. ];
  800. foreach($this->aCurrent['join'] as $sKey => $aValue) {
  801. $sAlias = isset($aValue['table_alias']) ? $aValue['table_alias'] : $aValue['table'];
  802. $sTableAlias = isset($aValue['table_alias']) ? " AS {$aValue['table_alias']} " : '';
  803. $sOperator = isset($aValue['operator']) ? $aValue['operator'] : null;
  804. // joinFields
  805. $aJoinFields = $this->getFieldsJoin($sKey);
  806. if($aJoinFields === false && !empty($aValue['joinFields']) && is_array($aValue['joinFields']))
  807. $aJoinFields = $aValue['joinFields'];
  808. if($aJoinFields)
  809. foreach($aJoinFields as $mixedField) {
  810. list($sFldName, $sFldOperator) = is_array($mixedField) ? $mixedField : [$mixedField, $sOperator];
  811. $aSql['joinFields'] .= $this->setFieldUnit($sFldName, $sAlias, $sFldOperator, $bRenameMode);
  812. }
  813. // group by
  814. if(isset($aValue['groupTable']))
  815. $aSql['groupBy'] .= "`{$aValue['groupTable']}`.`{$aValue['groupField']}`, ";
  816. // having
  817. if(isset($aValue['groupHaving']))
  818. $aSql['groupHaving'] .= $aValue['groupHaving'];
  819. $sOn = isset($aValue['mainTable']) ? $aValue['mainTable'] : $this->aCurrent['table'];
  820. $aSql['join'] .= " {$aValue['type']} JOIN `{$aValue['table']}` $sTableAlias ON " . (!empty($aValue['on_sql']) ? $aValue['on_sql'] : "`{$sAlias}`.`{$aValue['onField']}`=" . trim($this->setFieldUnit($aValue['mainField'], $sOn, isset($aValue['mainFieldFunc']) ? $aValue['mainFieldFunc'] : '', false), ", "));
  821. $aSql['ownFields'] .= $this->setFieldUnit($aValue['mainField'], $sOn, '', $bRenameMode);
  822. }
  823. $aSql['joinFields'] = trim($aSql['joinFields'], ', ');
  824. if(!empty($aSql['groupBy']))
  825. $aSql['groupBy'] = trim('GROUP BY ' . $aSql['groupBy'], ', ');
  826. if(!empty($aSql['groupHaving']))
  827. $aSql['groupHaving'] = 'HAVING ' . $aSql['groupHaving'];
  828. return $aSql;
  829. }
  830. /**
  831. * Concat sql parts of query, run it and return result array
  832. * @param $aParams addon param
  833. * return $aData multivariate array
  834. */
  835. function getSearchDataByParams ($aParams = '')
  836. {
  837. $bForUnion = isset($aParams['for_union']) && $aParams['for_union'] === true;
  838. $aSql = [
  839. 'ownFields' => '',
  840. 'order' => ''
  841. ];
  842. // searchFields
  843. if($bForUnion) {
  844. $sTable = isset($this->aCurrent['tableSearch']) ? $this->aCurrent['tableSearch'] : $this->aCurrent['table'];
  845. $aSql['ownFields'] .= $this->setFieldUnit(!empty($this->aCurrent['ident']) ? $this->aCurrent['ident'] : 'id', $sTable);
  846. $aSql['ownFields'] .= $this->setFieldUnit(!empty($this->aCurrent['added']) ? $this->aCurrent['added'] : 'added', $sTable);
  847. $aSql['ownFields'] .= "'" . $this->getContentInfoName() . "' AS `content_info`";
  848. }
  849. else {
  850. $aOwnFields = $this->getFieldsOwn();
  851. if($aOwnFields === false)
  852. $aOwnFields = $this->aCurrent['ownFields'];
  853. foreach($aOwnFields as $mixedField) {
  854. list($sFldName, $sFldOperator) = is_array($mixedField) ? $mixedField : [$mixedField, ''];
  855. $aSql['ownFields'] .= $this->setFieldUnit($sFldName, $this->aCurrent['table'], $sFldOperator);
  856. }
  857. }
  858. // joinFields & join
  859. if(($aJoins = $this->getJoins()) !== false) {
  860. if(!$bForUnion) {
  861. $aSql['ownFields'] .= $aJoins['ownFields'];
  862. $aSql['ownFields'] .= $aJoins['joinFields'];
  863. }
  864. $aSql['join'] = $aJoins['join'];
  865. $aSql['groupBy'] = $aJoins['groupBy'];
  866. $aSql['groupHaving'] = $aJoins['groupHaving'];
  867. }
  868. $aSql['ownFields'] = trim($aSql['ownFields'], ', ');
  869. // from
  870. $aSql['from'] = " FROM `{$this->aCurrent['table']}`";
  871. // where
  872. $aSql['where'] = $this->getRestriction();
  873. // limit
  874. $aSql['limit'] = $this->getLimit();
  875. // sorting
  876. $this->setSorting();
  877. $aSort = $this->getSorting($this->aCurrent['sorting']);
  878. foreach ($aSort as $sKey => $sValue)
  879. $aSql[$sKey] .= $sValue;
  880. // execution
  881. $sqlQuery = 'SELECT';
  882. if(isset($this->aCurrent['distinct']) && $this->aCurrent['distinct'] === true)
  883. $sqlQuery .= ' DISTINCT';
  884. $sqlQuery .= ' ' . $aSql['ownFields'];
  885. $sqlQuery .= ' ' . $aSql['from'];
  886. if (isset($aSql['join']))
  887. $sqlQuery .= ' ' . $aSql['join'];
  888. if (isset($aSql['where']))
  889. $sqlQuery .= ' ' . $aSql['where'];
  890. if (isset($aSql['groupBy']))
  891. $sqlQuery .= ' ' . $aSql['groupBy'];
  892. if (isset($aSql['groupHaving']))
  893. $sqlQuery .= ' ' . $aSql['groupHaving'];
  894. if($bForUnion)
  895. return [
  896. 'query' => $sqlQuery,
  897. 'order' => $aSql['order'] ?? '',
  898. 'limit' => $aSql['limit'] ?? ''
  899. ];
  900. if (isset($aSql['order']))
  901. $sqlQuery .= ' ' . $aSql['order'];
  902. if (isset($aSql['limit']))
  903. $sqlQuery .= ' ' . $aSql['limit'];
  904. //echoDbg($sqlQuery);
  905. $aRes = BxDolDb::getInstance()->getAll($sqlQuery);
  906. return $aRes;
  907. }
  908. /**
  909. * Set class fields condition params and paginate array
  910. */
  911. function setConditionParams()
  912. {
  913. $this->aCurrent['paginate']['num'] = 0;
  914. // keyword
  915. $sKeyword = bx_process_input(isset($this->_aCustomSearchCondition['keyword']) ? $this->_aCustomSearchCondition['keyword'] : bx_get('keyword'));
  916. $bKeyword = !empty($sKeyword);
  917. if ($this->_bLiveSearch && !$bKeyword)
  918. return;
  919. if ($bKeyword) {
  920. if (substr($sKeyword, 0, 1) == '@') {
  921. $sModule = $this->aCurrent['module_name'];
  922. $sMethod = 'act_as_profile';
  923. if(!bx_is_srv($sModule, $sMethod) || !bx_srv($sModule, $sMethod))
  924. return;
  925. $sKeyword = substr($sKeyword, 1);
  926. }
  927. $this->aCurrent['restriction']['keyword'] = array(
  928. 'value' => $sKeyword,
  929. 'field' => '',
  930. 'operator' => 'against'
  931. );
  932. // for 'keyword' search results (Site/Live search) we need to show all items, not only public content
  933. if(!$this->_sCategoryObject)
  934. $this->setProcessPrivateContent(true);
  935. }
  936. // owner
  937. if (isset($_GET['ownerName'])) {
  938. $sName = bx_process_input($_GET['ownerName']);
  939. $iUser = (int)BxDolProfileQuery::getInstance()->getIdByNickname($sName);
  940. BxDolMenu::getInstance()->setCurrentProfileID($iUser);
  941. } elseif (isset($_GET['userID']))
  942. $iUser = bx_process_input($_GET['userID'], BX_DATA_INT);
  943. if (!empty($iUser))
  944. $this->aCurrent['restriction']['owner']['value'] = $iUser;
  945. // meta info
  946. if ($this->_sMetaType && !empty($this->aCurrent['object_metatags'])) {
  947. $o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags']);
  948. if ($o) {
  949. unset($this->aCurrent['restriction']['keyword']);
  950. $aLocationString = [];
  951. switch ($this->_sMetaType) {
  952. case 'location_country':
  953. $aLocationString['country'] = $sKeyword;
  954. $o->locationsSetSearchCondition($this, $sKeyword);
  955. break;
  956. case 'location_country_state':
  957. $aLocationString['country'] = $sKeyword;
  958. $aLocationString['state'] = bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state'));
  959. $o->locationsSetSearchCondition($this, $sKeyword, $aLocationString['state']);
  960. break;
  961. case 'location_country_city':
  962. $aLocationString['country'] = $sKeyword;
  963. $aLocationString['state'] = bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state'));
  964. $aLocationString['city'] = bx_process_input(isset($this->_aCustomSearchCondition['city']) ? $this->_aCustomSearchCondition['city'] : bx_get('city'));
  965. $o->locationsSetSearchCondition($this, $sKeyword, $aLocationString['state'], $aLocationString['city']);
  966. break;
  967. case 'location_state':
  968. $aLocationString['state'] = $sKeyword;
  969. $o->locationsSetSearchCondition($this, false, $sKeyword);
  970. break;
  971. case 'location_city':
  972. $aLocationString['city'] = $sKeyword;
  973. $o->locationsSetSearchCondition($this, false, false, $sKeyword);
  974. break;
  975. case 'mention':
  976. $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
  977. $o->mentionsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
  978. if(($oProfile = BxDolProfile::getInstance((int)$sKeyword)) !== false)
  979. $this->_setPageDescription('_sys_page_description_mention', $oProfile->getDisplayName(), $this->aCurrent['title']);
  980. break;
  981. case 'keyword':
  982. $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
  983. $o->keywordsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
  984. $this->_setPageDescription('_sys_page_description_hashtag', $sKeyword, $this->aCurrent['title']);
  985. break;
  986. }
  987. if(in_array($this->_sMetaType, ['location_country', 'location_country_state', 'location_country_city', 'location_state', 'location_city']))
  988. $this->_setPageDescription('_sys_page_description_location', $o->locationsStringFromArray($aLocationString, false), $this->aCurrent['title']);
  989. }
  990. }
  991. // category
  992. if($this->_sCategoryObject) {
  993. if(($o = BxDolCategory::getObjectInstance($this->_sCategoryObject)) && $this->_bSingleSearch) {
  994. if ($this->aCurrent['name'] == $o->getSearchObject()) {
  995. unset($this->aCurrent['restriction']['keyword']);
  996. $o->setSearchCondition($this, $sKeyword);
  997. }
  998. $this->_setPageDescription('_sys_page_description_category', $o->getCategoryTitle($sKeyword), $this->aCurrent['title']);
  999. }
  1000. if($this->_sCategoryObject == 'multi'){
  1001. unset($this->aCurrent['restriction']['keyword']);
  1002. $this->setCategoriesCondition($sKeyword);
  1003. }
  1004. }
  1005. if($bKeyword && !$this->_sMetaType && !$this->_sCategoryObject)
  1006. $this->_setPageDescription('_sys_page_description_keyword', $sKeyword, $this->aCurrent['title']);
  1007. $this->setPaginate();
  1008. $iNum = $this->getNum();
  1009. if ($iNum > 0)
  1010. $this->aCurrent['paginate']['num'] = $iNum;
  1011. }
  1012. /**
  1013. * Check number of records on current page
  1014. * return number of records on current page + 1
  1015. */
  1016. function getNum ()
  1017. {
  1018. $aJoins = $this->getJoins(false);
  1019. $sqlQuery = "SELECT * FROM `{$this->aCurrent['table']}` " . (isset($aJoins['join']) ? ' ' . $aJoins['join'] : '' ) . $this->getRestriction() . (isset($aJoins['groupBy']) ? ' ' . $aJoins['groupBy'] : '') . ' ' . $this->getLimit(true);
  1020. return count(BxDolDb::getInstance()->getAll($sqlQuery));
  1021. }
  1022. /**
  1023. * Get total number of records
  1024. * return total number of records
  1025. */
  1026. function getTotal ()
  1027. {
  1028. $aJoins = $this->getJoins(false);
  1029. $sqlQuery = "SELECT COUNT(*) FROM `{$this->aCurrent['table']}` " . (isset($aJoins['join']) ? ' ' . $aJoins['join'] : '' ) . $this->getRestriction() . (isset($aJoins['groupBy']) ? ' ' . $aJoins['groupBy'] : '');
  1030. return BxDolDb::getInstance()->getOne($sqlQuery);
  1031. }
  1032. /**
  1033. * Check restriction params and make condition part of query
  1034. * return $sqlWhere sql code of query for WHERE part
  1035. */
  1036. function getRestriction ()
  1037. {
  1038. $oDb = BxDolDb::getInstance();
  1039. $sqlWhere = '';
  1040. if (isset($this->aCurrent['restriction'])) {
  1041. $aWhere[] = '1';
  1042. foreach ($this->aCurrent['restriction'] as $sKey => $aValue) {
  1043. $sqlCondition = '';
  1044. if (isset($aValue['operator']) && isset($aValue['value']) && $aValue['value'] !== '' && $aValue['value'] !== false && $aValue['value'] !== null) {
  1045. $sFieldTable = isset($aValue['table']) ? $aValue['table'] : $this->aCurrent['table'];
  1046. $sqlCondition = "`{$sFieldTable}`.`{$aValue['field']}` ";
  1047. switch ($aValue['operator']) {
  1048. case 'empty value':
  1049. $sqlCondition .= " = '' ";
  1050. break;
  1051. case 'not empty value':
  1052. $sqlCondition .= " != '' ";
  1053. break;
  1054. case 'nothing':
  1055. $sqlCondition = " 0 ";
  1056. break;
  1057. case 'against':
  1058. $aCond = isset($aValue['field']) && strlen($aValue['field']) > 0 ? $aValue['field'] : $this->aCurrent['searchFields'];
  1059. $sqlCondition = !empty($aCond) ? $this->getSearchFieldsCond($aCond, $aValue['value']) : "";
  1060. break;
  1061. case 'like':
  1062. $sqlCondition .= "LIKE " . $oDb->escape('%' . $aValue['value'] . '%');
  1063. break;
  1064. case 'in':
  1065. case 'not in':
  1066. $sValuesString = $this->getMultiValues($aValue['value']);
  1067. $sqlCondition .= strtoupper($aValue['operator']) . '(' . $sValuesString . ')';
  1068. break;
  1069. case 'in_set':
  1070. $sqlCondition = "1 << (" . $sqlCondition . " - 1) & " . (int)$aValue['value'];
  1071. break;
  1072. default:
  1073. if (!$oDb->isValidOperator($aValue['operator'])) {
  1074. throw new Exception('Invalid operator in getRestriction method');
  1075. }
  1076. $sqlCondition .= $aValue['operator'] . (isset($aValue['no_quote_value']) && $aValue['no_quote_value'] ? $aValue['value'] : $oDb->escape($aValue['value']));
  1077. break;
  1078. }
  1079. }
  1080. if (strlen($sqlCondition) > 0)
  1081. $aWhere[] = $sqlCondition;
  1082. }
  1083. $sqlWhere .= "WHERE ". implode(' AND ', $aWhere) . (isset($this->aCurrent['restriction_sql']) ? $this->aCurrent['restriction_sql'] : '');
  1084. }
  1085. return $sqlWhere;
  1086. }
  1087. /**
  1088. * Get limit part of query
  1089. * return $sqlFrom code for limit part pf query
  1090. */
  1091. function getLimit ($isAddPlusOne = false)
  1092. {
  1093. if (!isset($this->aCurrent['paginate']))
  1094. return;
  1095. $sqlFrom = (int)$this->aCurrent['paginate']['start'] > 0 ? (int)$this->aCurrent['paginate']['start'] : 0;
  1096. $sqlTo = $this->aCurrent['paginate']['perPage'] + ($isAddPlusOne ? 1 : 0);
  1097. if ($sqlTo > 0)
  1098. return 'LIMIT ' . $sqlFrom . ', ' . $sqlTo;
  1099. }
  1100. /**
  1101. * Set sorting field of class
  1102. */
  1103. function setSorting ()
  1104. {
  1105. $this->aCurrent['sorting'] = isset($_GET[$this->aCurrent['name'] . '_mode']) ? $_GET[$this->aCurrent['name'] . '_mode'] : $this->aCurrent['sorting'];
  1106. }
  1107. /**
  1108. * Get sorting part of query according current sorting mode
  1109. * @param string $sSortType sorting type
  1110. * return array with sql elements order and ownFields
  1111. */
  1112. function getSorting ($sSortType = 'last')
  1113. {
  1114. if (isset($this->aCurrent['sorting_sql']))
  1115. return array('order' => $this->aCurrent['sorting_sql']);
  1116. $aOverride = $this->getAlterOrder();
  1117. if (is_array($aOverride) && !empty($aOverride))
  1118. return $aOverride;
  1119. $aSql = array();
  1120. switch ($sSortType) {
  1121. case 'rand':
  1122. $aSql['order'] = "ORDER BY RAND()";
  1123. break;
  1124. case 'score':
  1125. if (is_array($this->aCurrent['restriction']['keyword'])) {
  1126. $sPseud = '';
  1127. if ('on' == getParam('useLikeOperator')) {
  1128. $aSql['order'] = "ORDER BY `score` DESC";
  1129. $sPseud = 'score';
  1130. }
  1131. $aSql['ownFields'] .= $this->getSearchFieldsCond($this->aCurrent['searchFields'], $this->aCurrent['restriction']['keyword']['value'], $sPseud) . ',';
  1132. }
  1133. break;
  1134. case 'none':
  1135. $aSql['order'] = "";
  1136. break;
  1137. default:
  1138. $aSql['order'] = "ORDER BY `date` DESC";
  1139. }
  1140. return $aSql;
  1141. }
  1142. /**
  1143. * Return own varaint for sorting (redeclare if necessary)
  1144. * return array of sql elements
  1145. */
  1146. function getAlterOrder ()
  1147. {
  1148. return array();
  1149. }
  1150. /**
  1151. * Set paginate fields of class according GET params 'start' and 'per_page'
  1152. * forcePage is need for setting most important number of current page
  1153. */
  1154. function setPaginate ()
  1155. {
  1156. $iStart = 0;
  1157. $iPerPage = 0;
  1158. //--- Check in aCurrent (low priority).
  1159. if(isset($this->aCurrent['paginate']['perPage']) && (int)$this->aCurrent['paginate']['perPage'] != 0)
  1160. $iPerPage = (int)$this->aCurrent['paginate']['perPage'];
  1161. //--- Check in aParams (middle priority).
  1162. if(isset($this->_aParams['per_page']) && (int)$this->_aParams['per_page'] != 0)
  1163. $iPerPage = (int)$this->_aParams['per_page'];
  1164. //--- Check in GET params (high priority).
  1165. if(isset($_GET['per_page']) && (int)$_GET['per_page'] != 0)
  1166. $iPerPage = (int)$_GET['per_page'];
  1167. //--- Trying to get from System settings.
  1168. if (empty($iPerPage))
  1169. $iPerPage = (int)getParam('sys_per_page_search_keyword_' . ($this->_bSingleSearch ? 'single' : 'plural'));
  1170. //--- Use default value in case of emergency.
  1171. if (empty($iPerPage))
  1172. $iPerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT;
  1173. if ($this->iPaginatePerPage != BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT)
  1174. $iPerPage = $this->iPaginatePerPage;
  1175. $this->aCurrent['paginate']['perPage'] = $iPerPage;
  1176. if(!isset($this->aCurrent['paginate']['forceStart'])) {
  1177. if(isset($this->_aParams['start']))
  1178. $iStart = (int)$this->_aParams['start'];
  1179. if(($iStartGet = bx_get('start')) !== false)
  1180. $iStart = (int)$iStartGet;
  1181. }
  1182. else
  1183. $iStart = (int)$this->aCurrent['paginate']['forceStart'];
  1184. if($iStart < 0)
  1185. $iStart = 0;
  1186. $this->aCurrent['paginate']['start'] = $iStart;
  1187. }
  1188. function unsetPaginate()
  1189. {
  1190. unset($this->aCurrent['paginate']);
  1191. }
  1192. /**
  1193. * Get sql where condition for search fields
  1194. * @param array of search fields
  1195. * @param string $sKeyword keyword value for search
  1196. * @param string sPseud for setting new name for generated set of fields in query
  1197. * return sql code of WHERE part in query
  1198. */
  1199. function getSearchFieldsCond ($aFields, $sKeyword, $sPseud = '')
  1200. {
  1201. if (!$sKeyword)
  1202. return '';
  1203. $sTable = empty($this->aCurrent['tableSearch']) ? $this->aCurrent['table'] : $this->aCurrent['tableSearch'];
  1204. $oDb = BxDolDb::getInstance();
  1205. $bLike = getParam('useLikeOperator');
  1206. if (!is_array($aFields))
  1207. $aFields = array($aFields);
  1208. if ($bLike == 'on') {
  1209. $sKeyword = $oDb->escape('%' . preg_replace('/\s+/', '%', $sKeyword) . '%');
  1210. $sSqlWhere = '';
  1211. foreach ($aFields as $sValue)
  1212. $sSqlWhere .= "`{$sTable}`.`$sValue` LIKE " . $sKeyword . " OR ";
  1213. $sSqlWhere = '(' . trim($sSqlWhere, 'OR ') . ')';
  1214. } else {
  1215. $sKeyword = $oDb->escape($sKeyword);
  1216. $sSqlWhere = '';
  1217. foreach ($aFields as $sValue)
  1218. $sSqlWhere .= "`{$sTable}`.`$sValue`, ";
  1219. $sSqlWhere = trim($sSqlWhere, ', ');
  1220. $sSqlWhere = " MATCH({$sSqlWhere}) AGAINST (" . $sKeyword . ") ";
  1221. if (!empty($sPseud))
  1222. $sSqlWhere .= " AS `$sPseud` ";
  1223. }
  1224. return $sSqlWhere;
  1225. }
  1226. /**
  1227. * Get set from several values for 'in' and 'not in' operators
  1228. * @param $aValues array of values
  1229. * return sql code for field with operator IN (NOT IN)
  1230. */
  1231. function getMultiValues ($aValues)
  1232. {
  1233. $oDb = BxDolDb::getInstance();
  1234. return $oDb->implode_escape($aValues);
  1235. }
  1236. /**
  1237. * System method for filling aPseud array. Fill field aPseud for current class from system option.
  1238. * It may be useful when Fields are taken from system option too.
  1239. */
  1240. function _getPseudFromParam ()
  1241. {
  1242. return false;
  1243. }
  1244. /**
  1245. * System method for filling aPseud array.
  1246. * Fill field aPseud for current class (if you will use own getSearchUnit methods then not necessary to redeclare).
  1247. */
  1248. function _getPseud ()
  1249. {
  1250. }
  1251. /**
  1252. * Add replace markers. Markers are replaced in titles and browse urls
  1253. * @param $a array of markers as key => value
  1254. * @return true on success or false on error
  1255. */
  1256. public function addMarkers ($a)
  1257. {
  1258. if (empty($a) || !is_array($a))
  1259. return false;
  1260. $this->_aMarkers = array_merge ($this->_aMarkers, $a);
  1261. return true;
  1262. }
  1263. /**
  1264. * Replace provided markers in a string
  1265. * @param $mixed string or array to replace markers in
  1266. * @return string where all occured markers are replaced
  1267. */
  1268. protected function _replaceMarkers ($mixed)
  1269. {
  1270. return bx_replace_markers($mixed, $this->_aMarkers);
  1271. }
  1272. protected function _setPageDescription()
  1273. {
  1274. $aArgs = func_get_args();
  1275. if($this->_bSingleSearch)
  1276. $aArgs[0] .= '_in_section';
  1277. BxDolTemplate::getInstance()->setPageDescription(call_user_func_array('_t', $aArgs));
  1278. }
  1279. }
  1280. /** @} */