BxDolSearch.php 51 KB

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