BxDolSearch.php 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  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 $aClasses = array(); ///< array of all search classes
  39. protected $aChoice = array(); ///< array of current search classes which were choosen in search area
  40. protected $_bRawProcessing = false; ///< display search results without design box and paginate
  41. protected $_bLiveSearch = false;
  42. protected $_sMetaType = '';
  43. protected $_sCategoryObject = '';
  44. protected $_aCustomSearchCondition = array();
  45. protected $_aCustomCurrentCondition = array();
  46. protected $_sUnitTemplate = '';
  47. /**
  48. * Constructor
  49. * @param array $aChoice - array of choosen classes (will take a part only existing in `sys_objects_search` table)
  50. */
  51. public function __construct ($aChoice = '')
  52. {
  53. parent::__construct();
  54. $this->aClasses = BxDolDb::getInstance()->fromCache('sys_objects_search', 'getAllWithKey',
  55. 'SELECT `ID` as `id`,
  56. `Title` as `title`,
  57. `ClassName` as `class`,
  58. `ClassPath` as `file`,
  59. `ObjectName`,
  60. `GlobalSearch`
  61. FROM `sys_objects_search`
  62. ORDER BY `Order` ASC', 'ObjectName'
  63. );
  64. if (is_array($aChoice) && !empty($aChoice)) {
  65. foreach ($aChoice as $sValue) {
  66. if (isset($this->aClasses[$sValue]))
  67. $this->aChoice[$sValue] = $this->aClasses[$sValue];
  68. }
  69. } else {
  70. $this->aChoice = $this->aClasses;
  71. }
  72. }
  73. /**
  74. * create units for all classes and calling their processing methods
  75. */
  76. public function response ()
  77. {
  78. $sCode = '';
  79. $bSingle = count($this->aChoice) == 1;
  80. foreach ($this->aChoice as $sKey => $aValue) {
  81. if (!$this->_sMetaType && !$aValue['GlobalSearch'])
  82. continue;
  83. $sClassName = 'BxTemplSearchResult';
  84. if(!empty($aValue['class'])) {
  85. $sClassName = $aValue['class'];
  86. if(!empty($aValue['file']))
  87. require_once(BX_DIRECTORY_PATH_ROOT . $aValue['file']);
  88. }
  89. $oEx = new $sClassName();
  90. if ($this->_sMetaType && !$oEx->isMetaEnabled($this->_sMetaType))
  91. continue;
  92. $oEx->setId($aValue['id']);
  93. $oEx->setLiveSearch($this->_bLiveSearch);
  94. $oEx->setMetaType($this->_sMetaType);
  95. $oEx->setCategoryObject($this->_sCategoryObject);
  96. $oEx->setCenterContentUnitSelector(false);
  97. $oEx->setSingleSearch($bSingle);
  98. $oEx->setCustomSearchCondition($this->_aCustomSearchCondition);
  99. $oEx->aCurrent = array_merge($oEx->aCurrent, $this->_aCustomCurrentCondition);
  100. if ($this->_sUnitTemplate)
  101. $oEx->setUnitTemplate($this->_sUnitTemplate);
  102. $sCode .= $this->_bRawProcessing ? $oEx->processingRaw() : $oEx->processing();
  103. }
  104. return $sCode;
  105. }
  106. public function getEmptyResult ()
  107. {
  108. $sKey = _t('_Empty');
  109. return DesignBoxContent($sKey, MsgBox($sKey), 1);
  110. }
  111. protected function getKeyTitlesPairs ()
  112. {
  113. $a = array();
  114. foreach ($this->aChoice as $sKey => $r)
  115. if ($this->_sMetaType || $r['GlobalSearch'])
  116. $a[$sKey] = _t($r['title']);
  117. return $a;
  118. }
  119. public function setLiveSearch($bLiveSearch)
  120. {
  121. $this->_bLiveSearch = $bLiveSearch;
  122. }
  123. public function setMetaType($s)
  124. {
  125. $aMetaTypes = array('location_country', 'location_country_city', 'location_country_state', 'mention', 'keyword');
  126. if (in_array($s, $aMetaTypes))
  127. $this->_sMetaType = $s;
  128. }
  129. public function setCategoryObject($s)
  130. {
  131. $this->_sCategoryObject = $s;
  132. }
  133. /**
  134. * Set custom search condition to use istead of GET/POST variables
  135. * @param $a array of params, such as 'keyword', 'state', 'city'
  136. * @return nothing
  137. */
  138. public function setCustomSearchCondition($a)
  139. {
  140. $this->_aCustomSearchCondition = $a;
  141. }
  142. /**
  143. * Set custom data for aCurrent array
  144. * @param $a array of params
  145. * @return nothing
  146. */
  147. public function setCustomCurrentCondition($a)
  148. {
  149. $this->_aCustomCurrentCondition = $a;
  150. }
  151. /**
  152. * Set custom unit template
  153. * @param $s template name
  154. * @return nothing
  155. */
  156. public function setUnitTemplate($s)
  157. {
  158. $this->_sUnitTemplate = $s;
  159. }
  160. /**
  161. * Display search results without design box and paginate
  162. */
  163. public function setRawProcessing($b)
  164. {
  165. $this->_bRawProcessing = $b;
  166. }
  167. }
  168. /*
  169. * Search class for processing search requests and displaying search results.
  170. *
  171. * Allows present content from modules on search params or internal (via fields of class) conditions.
  172. *
  173. * Example of usage (you can see example in any of default Dolhpin's modules):
  174. *
  175. * 1. Extends your own search class from this one (or from BxBaseSearchResult or BxBaseSearchResultSharedMedia classes)
  176. * 2. Set necessary fields of class (using as example BxFilesSearch):
  177. *
  178. * @code
  179. *
  180. * // main field of search class
  181. * $this->aCurrent = array(
  182. *
  183. * // name of module
  184. * 'name' => 'bx_files',
  185. *
  186. * // language key with name of module
  187. * 'title' => '_bx_files',
  188. *
  189. * // main content table
  190. * 'table' => 'bx_files_main',
  191. *
  192. * // array of all fields which can be choosen for select in result query
  193. * 'ownFields' => array('ID', 'Title', 'Uri', 'Desc', 'Date', 'Size', 'Ext', 'Views', 'Rate', 'RateCount', 'Type'),
  194. *
  195. * // array of fields which take a part in search by keyword (global search and related words only - in other cases leave it blank)
  196. * 'searchFields' => array('Title', 'Tags', 'Desc', 'Categories'),
  197. *
  198. * // array of join tables
  199. * // 'type' - type of join
  200. * // 'table' - join table
  201. * // 'mainField' - field from main table for 'on' condition
  202. * // 'onField' - field from joining table for 'on' condition
  203. * // 'joinFields' - array of fields from joining table
  204. *
  205. * 'join' => array(
  206. * 'profile' => array(
  207. * 'type' => 'left',
  208. * 'table' => 'Profiles',
  209. * 'mainField' => 'Owner',
  210. * 'onField' => 'ID',
  211. * 'joinFields' => array('NickName'),
  212. * ),
  213. * // ...
  214. * ),
  215. *
  216. * // array of search parameters
  217. * // 'value' - value of search parameter (can be number, string or array)
  218. * // '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
  219. * // 'operator' - operator between field and value (can be 'in', 'not in', '>', '<', 'against', 'like' and '=' by default),
  220. * // 'paramName' - GET param which will keep 'value' for pagianation
  221. *
  222. * 'restriction' => array(
  223. * 'activeStatus' => array('value'=>'approved', 'field'=>'Status', 'operator'=>'=', 'paramName' => 'status'),
  224. * 'albumType' => array('value'=>'', 'field'=>'Type', 'operator'=>'=', 'paramName'=>'albumType', 'table'=>'sys_albums'),
  225. * ),
  226. *
  227. * // array of pagination
  228. * // 'perPage' - units per page
  229. * // 'start' - show search results starting from this number
  230. * // 'num' - number of element to show on current page + 1
  231. *
  232. * 'paginate' => array('perPage' => 10, 'start' => 0, 'num' => 11),
  233. *
  234. * // sort mode - by default it was last (DESC by date)
  235. * 'sorting' => 'last',
  236. *
  237. * // mode of units presentation in search result, can be 'short' or 'full' view
  238. * 'view' => 'full',
  239. *
  240. * // field of id
  241. * 'ident' => 'ID',
  242. *
  243. * // rss feed array
  244. * 'rss' => array(
  245. * 'title' => '',
  246. * 'link' => '',
  247. * 'image' => '',
  248. * 'profile' => 0,
  249. * 'fields' => array (
  250. * 'Link' => '',
  251. * 'Title' => 'Title',
  252. * 'DateTimeUTS' => 'Date',
  253. * 'Desc' => 'Desc',
  254. * 'Photo' => '',
  255. * ),
  256. * ),
  257. * );
  258. *
  259. * // array of fields renamings
  260. * $aPseud - filling in costructor by function _getPseud
  261. *
  262. * // unique identificator from `sys_objects_search` table
  263. * $id;
  264. *
  265. * @endcode
  266. *
  267. * Memberships/ACL:
  268. * Doesn't depend on user's membership.
  269. *
  270. * Alerts:
  271. * no alerts available
  272. *
  273. */
  274. class BxDolSearchResult implements iBxDolReplaceable
  275. {
  276. public $aCurrent; ///< search results configuration
  277. protected $aPseud; ///< array of fields renamings
  278. protected $id; ///< unique identificator of block on mixed search results page
  279. protected $bDisplayEmptyMsg = false; ///< display empty message instead of nothing, when no results
  280. protected $sDisplayEmptyMsgKey = ''; ///< custom empty message language key, instead of default "empty" message
  281. protected $bProcessPrivateContent = true; ///< check each item for privacy, if view isn't allowed then display private view instead
  282. protected $aPrivateConditionsIndexes = array('restriction' => array(), 'join' => array()); ///< conditions indexes for bProcessPrivateContent
  283. protected $bForceAjaxPaginate = false; ///< force ajax paginate
  284. protected $iPaginatePerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT; ///< default 'per page' value for paginate.
  285. protected $_bSingleSearch = true;
  286. protected $_bLiveSearch = false;
  287. protected $_sMetaType = '';
  288. protected $_sCategoryObject = '';
  289. protected $_aCustomSearchCondition = array();
  290. protected $_aMarkers = array (); ///< markers to replace somewhere, usually title and browse url (defined in custom class)
  291. /**
  292. * constructor
  293. * filling identificator field
  294. */
  295. function __construct ()
  296. {
  297. if (isset($this->aPseud['id']))
  298. $this->aCurrent['ident'] = $this->aPseud['id'];
  299. }
  300. public function getId()
  301. {
  302. return $this->id;
  303. }
  304. public function setId($sId)
  305. {
  306. $this->id = $sId;
  307. }
  308. public function setAjaxPaginate($b = true)
  309. {
  310. $this->bForceAjaxPaginate = $b;
  311. }
  312. public function setPaginatePerPage($iPerPage)
  313. {
  314. $this->iPaginatePerPage = $iPerPage;
  315. }
  316. public function setSingleSearch($bSingleSearch)
  317. {
  318. $this->_bSingleSearch = $bSingleSearch;
  319. }
  320. public function setLiveSearch($bLiveSearch)
  321. {
  322. $this->_bLiveSearch = $bLiveSearch;
  323. }
  324. public function setMetaType($s)
  325. {
  326. $this->_sMetaType = $s;
  327. }
  328. public function isMetaEnabled($s)
  329. {
  330. if (empty($this->aCurrent['object_metatags']))
  331. return false;
  332. if (!($o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags'])))
  333. return false;
  334. switch ($s) {
  335. case 'location_country_city':
  336. case 'location_country_state':
  337. case 'location_country':
  338. return $o->locationsIsEnabled();
  339. case 'mention':
  340. return $o->mentionsIsEnabled();
  341. case 'keyword':
  342. return $o->keywordsIsEnabled();
  343. }
  344. return false;
  345. }
  346. public function setCategoryObject($s)
  347. {
  348. $this->_sCategoryObject = $s;
  349. }
  350. public function setCustomSearchCondition($a)
  351. {
  352. $this->_aCustomSearchCondition = $a;
  353. }
  354. /**
  355. * Display empty message if there is no content, custom empty message can be used.
  356. * @param $b - boolan value to enable or disable 'empty' message
  357. * @param $sLangKey [optional] - custom 'empty' message
  358. */
  359. public function setDisplayEmptyMsg($b, $sLangKey = '')
  360. {
  361. $this->bDisplayEmptyMsg = $b;
  362. if ($sLangKey)
  363. $this->sDisplayEmptyMsgKey = $sLangKey;
  364. }
  365. /**
  366. * Perform privacy checking for every unit
  367. * @param $b - boolan value to enable or disable privacy checking
  368. */
  369. public function setProcessPrivateContent ($b)
  370. {
  371. $this->bProcessPrivateContent = $b;
  372. if ($b) {
  373. // unset condition which was set when 'bProcessPrivateContent' was set to 'false'
  374. foreach ($this->aPrivateConditionsIndexes['restriction'] as $sKey)
  375. unset($this->aCurrent['restriction'][$sKey]);
  376. foreach ($this->aPrivateConditionsIndexes['join'] as $sKey)
  377. unset($this->aCurrent['join'][$sKey]);
  378. }
  379. }
  380. /**
  381. * Get search results without design box and paginate
  382. * @return html code
  383. */
  384. function processingRaw ()
  385. {
  386. $sCode = $this->displayResultBlock();
  387. return $this->aCurrent['paginate']['num'] > 0 ? $sCode : '';
  388. }
  389. /**
  390. * Get html box of search results (usually used in grlobal search)
  391. * @return html code
  392. */
  393. function processing ()
  394. {
  395. $sCode = $this->displayResultBlock();
  396. if ($this->aCurrent['paginate']['num'] > 0) {
  397. $sPaginate = $this->showPagination();
  398. $sCode = $this->displaySearchBox($sCode, $sPaginate);
  399. } else {
  400. $sCode = $this->bDisplayEmptyMsg ? $this->displaySearchBox(MsgBox(_t($this->sDisplayEmptyMsgKey ? $this->sDisplayEmptyMsgKey : '_Empty'))) : '';
  401. }
  402. return $sCode;
  403. }
  404. /**
  405. * Get html output of search result
  406. * @return html code
  407. */
  408. function displayResultBlock ()
  409. {
  410. $aData = $this->getSearchData();
  411. $sCode = '';
  412. if (count($aData) > 0) {
  413. $sCode = $this->addCustomParts();
  414. foreach ($aData as $iKey => $aValue) {
  415. $sCode .= $this->displaySearchUnit($aValue);
  416. }
  417. }
  418. return $sCode;
  419. }
  420. /**
  421. * Add different code before html output (usually redeclared)
  422. * no return result
  423. */
  424. function addCustomParts ()
  425. {
  426. }
  427. /**
  428. * Get XML string for rss output
  429. */
  430. function rss ()
  431. {
  432. if (!isset($this->aCurrent['rss']['fields']) || !isset($this->aCurrent['rss']['link']))
  433. return '';
  434. $aData = $this->getSearchData();
  435. $f = &$this->aCurrent['rss']['fields'];
  436. if ($aData) {
  437. foreach ($aData as $k => $a) {
  438. $aData[$k][$f['Link']] = $this->getRssUnitLink ($a);
  439. if(isset($f['Image']))
  440. $aData[$k][$f['Image']] = $this->getRssUnitImage ($a, $f['Image']);
  441. }
  442. }
  443. $oRss = new BxDolRssFactory ();
  444. return $oRss->GenRssByCustomData(
  445. $aData,
  446. isset($this->aCurrent['rss']['title']) && $this->aCurrent['rss']['title'] ? $this->aCurrent['rss']['title'] : $this->aCurrent['title'],
  447. $this->aCurrent['rss']['link'],
  448. $this->aCurrent['rss']['fields'],
  449. isset($this->aCurrent['rss']['image']) ? $this->aCurrent['rss']['image'] : '',
  450. isset($this->aCurrent['rss']['profile']) ? $this->aCurrent['rss']['profile'] : 0
  451. );
  452. }
  453. /**
  454. * Output RSS XML with XML header
  455. */
  456. function outputRSS ()
  457. {
  458. header('Content-Type: text/xml; charset=UTF-8');
  459. echo $this->rss();
  460. }
  461. /**
  462. * Return rss unit link (redeclared)
  463. */
  464. function getRssUnitLink (&$a)
  465. {
  466. // override this functions to return permalink to rss unit
  467. }
  468. /**
  469. * Return rss unit image (redeclared)
  470. */
  471. function getRssUnitImage (&$a, $sField)
  472. {
  473. // override this functions to return image for rss unit
  474. }
  475. /**
  476. * Naming fields in query's body
  477. * @param string $sFieldName name of field
  478. * @param string $sTableName name of field's table
  479. * $param string $sOperator of field's calculation (like MAX)
  480. * @param boolean $bRenameMode indicator for renaming and unsetting fields from field of class $this->aPseud
  481. * return $sqlUnit sql code and unsetting elements from aPseud field
  482. */
  483. function setFieldUnit ($sFieldName, $sTableName, $sOperator = '', $bRenameMode = true)
  484. {
  485. if (!empty($sOperator))
  486. $sqlUnit = "$sOperator(`$sTableName`.`$sFieldName`)";
  487. else
  488. $sqlUnit = "`$sTableName`.`$sFieldName`";
  489. if (isset($this->aPseud) && $bRenameMode !== false) {
  490. $sKey = array_search($sFieldName, $this->aPseud);
  491. if ($sKey !== false) {
  492. $sqlUnit .= " as `$sKey`";
  493. unset($this->aPseud[$sKey]);
  494. }
  495. }
  496. return $sqlUnit . ', ';
  497. }
  498. /**
  499. * Get html code of of every search unit
  500. * @param array $aData array of every search unit
  501. * return html code
  502. */
  503. function displaySearchUnit ($aData)
  504. {
  505. }
  506. /**
  507. * Get html code of search box with search results
  508. * @param string $sCode html code of search results
  509. * $param $sPaginate html code of paginate
  510. * return html code
  511. */
  512. function displaySearchBox ($sCode, $sPaginate = '')
  513. {
  514. }
  515. /**
  516. * Get html code of pagination
  517. */
  518. function showPagination ($bAdmin = false, $bChangePage = true, $bPageReload = true)
  519. {
  520. }
  521. /**
  522. * Get array of data with search results
  523. * return array with data
  524. */
  525. function getSearchData ()
  526. {
  527. $this->aPseud = $this->_getPseud();
  528. $this->setConditionParams();
  529. if ($this->aCurrent['paginate']['num'] > 0) {
  530. $aData = $this->getSearchDataByParams();
  531. return $aData;
  532. }
  533. return array();
  534. }
  535. /**
  536. * Get array with code for sql elements
  537. * @param $bRenameMode indicator of renmaing fields
  538. * return array with joinFields, ownFields, groupBy and join elements
  539. */
  540. function getJoins ($bRenameMode = true)
  541. {
  542. $aSql = array();
  543. // joinFields & join
  544. if (isset($this->aCurrent['join']) && is_array($this->aCurrent['join'])) {
  545. $aSql = array('join'=>'', 'ownFields'=>'', 'joinFields'=>'', 'groupBy'=>'');
  546. foreach ($this->aCurrent['join'] as $sKey => $aValue) {
  547. $sAlias = isset($aValue['table_alias']) ? $aValue['table_alias'] : $aValue['table'];
  548. $sTableAlias = isset($aValue['table_alias']) ? " AS {$aValue['table_alias']} " : '';
  549. if (is_array($aValue['joinFields'])) {
  550. foreach ($aValue['joinFields'] as $sValue) {
  551. $aSql['joinFields'] .= $this->setFieldUnit($sValue, $sAlias, isset($aValue['operator']) ? $aValue['operator'] : null, $bRenameMode);
  552. }
  553. }
  554. // group by
  555. if (isset($aValue['groupTable']))
  556. $aSql['groupBy'] = "GROUP BY `{$aValue['groupTable']}`.`{$aValue['groupField']}`, ";
  557. $sOn = isset($aValue['mainTable']) ? $aValue['mainTable'] : $this->aCurrent['table'];
  558. $aSql['join'] .= " {$aValue['type']} JOIN `{$aValue['table']}` $sTableAlias ON `{$sAlias}`.`{$aValue['onField']}`=`$sOn`.`{$aValue['mainField']}`";
  559. $aSql['ownFields'] .= $this->setFieldUnit($aValue['mainField'], $sOn, '', $bRenameMode);
  560. }
  561. $aSql['joinFields'] = trim($aSql['joinFields'], ', ');
  562. $aSql['groupBy'] = trim($aSql['groupBy'], ', ');
  563. }
  564. return $aSql;
  565. }
  566. /**
  567. * Concat sql parts of query, run it and return result array
  568. * @param $aParams addon param
  569. * return $aData multivariate array
  570. */
  571. function getSearchDataByParams ($aParams = '')
  572. {
  573. $aSql = array('ownFields'=>'', 'joinFields'=>'', 'order'=>'');
  574. // searchFields
  575. foreach ($this->aCurrent['ownFields'] as $sValue)
  576. $aSql['ownFields'] .= $this->setFieldUnit($sValue, $this->aCurrent['table']);
  577. // joinFields & join
  578. $aJoins = $this->getJoins();
  579. if (!empty($aJoins)) {
  580. $aSql['ownFields'] .= $aJoins['ownFields'];
  581. $aSql['ownFields'] .= $aJoins['joinFields'];
  582. $aSql['join'] = $aJoins['join'];
  583. $aSql['groupBy'] = $aJoins['groupBy'];
  584. }
  585. $aSql['ownFields'] = trim($aSql['ownFields'], ', ');
  586. // from
  587. $aSql['from'] = " FROM `{$this->aCurrent['table']}`";
  588. // where
  589. $aSql['where'] = $this->getRestriction();
  590. // limit
  591. $aSql['limit'] = $this->getLimit();
  592. // sorting
  593. $this->setSorting();
  594. $aSort = $this->getSorting($this->aCurrent['sorting']);
  595. foreach ($aSort as $sKey => $sValue)
  596. $aSql[$sKey] .= $sValue;
  597. // execution
  598. $sqlQuery = "SELECT " . $aSql['ownFields'];
  599. $sqlQuery .= ' ' . $aSql['from'];
  600. if (isset($aSql['join']))
  601. $sqlQuery .= ' ' . $aSql['join'];
  602. if (isset($aSql['where']))
  603. $sqlQuery .= ' ' . $aSql['where'];
  604. if (isset($aSql['groupBy']))
  605. $sqlQuery .= ' ' . $aSql['groupBy'];
  606. if (isset($aSql['order']))
  607. $sqlQuery .= ' ' . $aSql['order'];
  608. if (isset($aSql['limit']))
  609. $sqlQuery .= ' ' . $aSql['limit'];
  610. // echoDbg($sqlQuery);
  611. $aRes = BxDolDb::getInstance()->getAll($sqlQuery);
  612. return $aRes;
  613. }
  614. /**
  615. * Set class fields condition params and paginate array
  616. */
  617. function setConditionParams()
  618. {
  619. // keyword
  620. $sKeyword = bx_process_input(isset($this->_aCustomSearchCondition['keyword']) ? $this->_aCustomSearchCondition['keyword'] : bx_get('keyword'));
  621. if ($sKeyword !== false) {
  622. $this->aCurrent['restriction']['keyword'] = array(
  623. 'value' => $sKeyword,
  624. 'field' => '',
  625. 'operator' => 'against'
  626. );
  627. // for search results we need to show all items, not only public content
  628. $this->setProcessPrivateContent(true);
  629. }
  630. // owner
  631. if (isset($_GET['ownerName'])) {
  632. $sName = bx_process_input($_GET['ownerName']);
  633. $iUser = (int)BxDolProfileQuery::getInstance()->getIdByNickname($sName);
  634. BxDolMenu::getInstance()->setCurrentProfileID($iUser);
  635. } elseif (isset($_GET['userID']))
  636. $iUser = bx_process_input($_GET['userID'], BX_DATA_INT);
  637. if (!empty($iUser))
  638. $this->aCurrent['restriction']['owner']['value'] = $iUser;
  639. // meta info
  640. if ($this->_sMetaType && !empty($this->aCurrent['object_metatags'])) {
  641. $o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags']);
  642. if ($o) {
  643. unset($this->aCurrent['restriction']['keyword']);
  644. switch ($this->_sMetaType) {
  645. case 'location_country':
  646. $o->locationsSetSearchCondition($this, $sKeyword);
  647. break;
  648. case 'location_country_state':
  649. $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state')));
  650. break;
  651. case 'location_country_city':
  652. $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(isset($this->_aCustomSearchCondition['state']) ? $this->_aCustomSearchCondition['state'] : bx_get('state')), bx_process_input(isset($this->_aCustomSearchCondition['city']) ? $this->_aCustomSearchCondition['city'] : bx_get('city')));
  653. break;
  654. case 'mention':
  655. $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
  656. $o->mentionsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
  657. break;
  658. case 'keyword':
  659. $oCmts = !empty($this->sModuleObjectComments) ? BxDolCmts::getObjectInstance($this->sModuleObjectComments, 0, false) : false;
  660. $o->keywordsSetSearchCondition($this, $sKeyword, $oCmts ? $oCmts->getSystemId() : 0);
  661. break;
  662. }
  663. }
  664. }
  665. // category
  666. if ($this->_sCategoryObject && ($o = BxDolCategory::getObjectInstance($this->_sCategoryObject)) && $this->_bSingleSearch) {
  667. unset($this->aCurrent['restriction']['keyword']);
  668. $o->setSearchCondition($this, $sKeyword);
  669. }
  670. $this->setPaginate();
  671. $iNum = $this->getNum();
  672. if ($iNum > 0) {
  673. $this->aCurrent['paginate']['num'] = $iNum;
  674. } else {
  675. $this->aCurrent['paginate']['num'] = 0;
  676. }
  677. }
  678. /**
  679. * Check number of records on current page
  680. * return number of records on current page + 1
  681. */
  682. function getNum ()
  683. {
  684. $aJoins = $this->getJoins(false);
  685. $sqlQuery = "SELECT * FROM `{$this->aCurrent['table']}` " . (isset($aJoins['join']) ? $aJoins['join'] : '' ). $this->getRestriction() . (isset($aJoins['groupBy']) ? $aJoins['groupBy'] : '') . ' ' . $this->getLimit(true);
  686. return count(BxDolDb::getInstance()->getAll($sqlQuery));
  687. }
  688. /**
  689. * Check restriction params and make condition part of query
  690. * return $sqlWhere sql code of query for WHERE part
  691. */
  692. function getRestriction ()
  693. {
  694. $oDb = BxDolDb::getInstance();
  695. $sqlWhere = '';
  696. if (isset($this->aCurrent['restriction'])) {
  697. $aWhere[] = '1';
  698. foreach ($this->aCurrent['restriction'] as $sKey => $aValue) {
  699. $sqlCondition = '';
  700. if (isset($aValue['operator']) && isset($aValue['value']) && $aValue['value'] !== '' && $aValue['value'] !== false && $aValue['value'] !== null) {
  701. $sFieldTable = isset($aValue['table']) ? $aValue['table'] : $this->aCurrent['table'];
  702. $sqlCondition = "`{$sFieldTable}`.`{$aValue['field']}` ";
  703. switch ($aValue['operator']) {
  704. case 'nothing':
  705. $sqlCondition = " 0 ";
  706. break;
  707. case 'against':
  708. $aCond = isset($aValue['field']) && strlen($aValue['field']) > 0 ? $aValue['field'] : $this->aCurrent['searchFields'];
  709. $sqlCondition = !empty($aCond) ? $this->getSearchFieldsCond($aCond, $aValue['value']) : "";
  710. break;
  711. case 'like':
  712. $sqlCondition .= "LIKE " . $oDb->escape('%' . $aValue['value'] . '%');
  713. break;
  714. case 'in':
  715. case 'not in':
  716. $sValuesString = $this->getMultiValues($aValue['value']);
  717. $sqlCondition .= strtoupper($aValue['operator']) . '(' . $sValuesString . ')';
  718. break;
  719. default:
  720. $sqlCondition .= $aValue['operator'] . (isset($aValue['no_quote_value']) && $aValue['no_quote_value'] ? $aValue['value'] : $oDb->escape($aValue['value']));
  721. break;
  722. }
  723. }
  724. if (strlen($sqlCondition) > 0)
  725. $aWhere[] = $sqlCondition;
  726. }
  727. $sqlWhere .= "WHERE ". implode(' AND ', $aWhere) . (isset($this->aCurrent['restriction_sql']) ? $this->aCurrent['restriction_sql'] : '');
  728. }
  729. return $sqlWhere;
  730. }
  731. /**
  732. * Get limit part of query
  733. * return $sqlFrom code for limit part pf query
  734. */
  735. function getLimit ($isAddPlusOne = false)
  736. {
  737. if (!isset($this->aCurrent['paginate']))
  738. return;
  739. $sqlFrom = (int)$this->aCurrent['paginate']['start'] > 0 ? (int)$this->aCurrent['paginate']['start'] : 0;
  740. $sqlTo = $this->aCurrent['paginate']['perPage'] + ($isAddPlusOne ? 1 : 0);
  741. if ($sqlTo > 0)
  742. return 'LIMIT ' . $sqlFrom . ', ' . $sqlTo;
  743. }
  744. /**
  745. * Set sorting field of class
  746. */
  747. function setSorting ()
  748. {
  749. $this->aCurrent['sorting'] = isset($_GET[$this->aCurrent['name'] . '_mode']) ? $_GET[$this->aCurrent['name'] . '_mode'] : $this->aCurrent['sorting'];
  750. }
  751. /**
  752. * Get sorting part of query according current sorting mode
  753. * @param string $sSortType sorting type
  754. * return array with sql elements order and ownFields
  755. */
  756. function getSorting ($sSortType = 'last')
  757. {
  758. $aOverride = $this->getAlterOrder();
  759. if (is_array($aOverride) && !empty($aOverride))
  760. return $aOverride;
  761. $aSql = array();
  762. switch ($sSortType) {
  763. case 'rand':
  764. $aSql['order'] = "ORDER BY RAND()";
  765. break;
  766. case 'score':
  767. if (is_array($this->aCurrent['restriction']['keyword'])) {
  768. $sPseud = '';
  769. if ('on' == getParam('useLikeOperator')) {
  770. $aSql['order'] = "ORDER BY `score` DESC";
  771. $sPseud = 'score';
  772. }
  773. $aSql['ownFields'] .= $this->getSearchFieldsCond($this->aCurrent['searchFields'], $this->aCurrent['restriction']['keyword']['value'], $sPseud) . ',';
  774. }
  775. break;
  776. case 'none':
  777. $aSql['order'] = "";
  778. break;
  779. default:
  780. $aSql['order'] = "ORDER BY `date` DESC";
  781. }
  782. return $aSql;
  783. }
  784. /**
  785. * Return own varaint for sorting (redeclare if necessary)
  786. * return array of sql elements
  787. */
  788. function getAlterOrder ()
  789. {
  790. return array();
  791. }
  792. /**
  793. * Set paginate fields of class according GET params 'start' and 'per_page'
  794. * forcePage is need for setting most important number of current page
  795. */
  796. function setPaginate ()
  797. {
  798. $iPerPage = 0;
  799. //--- Check in aCurrent (low priority).
  800. if(isset($this->aCurrent['paginate']['perPage']) && (int)$this->aCurrent['paginate']['perPage'] != 0)
  801. $iPerPage = (int)$this->aCurrent['paginate']['perPage'];
  802. //--- Check in GET params (high priority).
  803. if(isset($_GET['per_page']) && (int)$_GET['per_page'] != 0)
  804. $iPerPage = (int)$_GET['per_page'];
  805. //--- Trying to get from System settings.
  806. if (empty($iPerPage))
  807. $iPerPage = (int)getParam('sys_per_page_search_keyword_' . ($this->_bSingleSearch ? 'single' : 'plural'));
  808. //--- Use default value in case of emergency.
  809. if (empty($iPerPage))
  810. $iPerPage = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT;
  811. $this->aCurrent['paginate']['perPage'] = $iPerPage;
  812. $this->aCurrent['paginate']['start'] = isset($this->aCurrent['paginate']['forceStart'])
  813. ? (int)$this->aCurrent['paginate']['forceStart']
  814. : (empty($_GET['start']) ? 0 : (int)$_GET['start']);
  815. if ($this->aCurrent['paginate']['start'] < 0)
  816. $this->aCurrent['paginate']['start'] = 0;
  817. }
  818. function unsetPaginate()
  819. {
  820. unset($this->aCurrent['paginate']);
  821. }
  822. /**
  823. * Get sql where condition for search fields
  824. * @param array of search fields
  825. * @param string $sKeyword keyword value for search
  826. * @param string sPseud for setting new name for generated set of fields in query
  827. * return sql code of WHERE part in query
  828. */
  829. function getSearchFieldsCond ($aFields, $sKeyword, $sPseud = '')
  830. {
  831. if (!$sKeyword)
  832. return '';
  833. $sTable = empty($this->aCurrent['tableSearch']) ? $this->aCurrent['table'] : $this->aCurrent['tableSearch'];
  834. $oDb = BxDolDb::getInstance();
  835. $bLike = getParam('useLikeOperator');
  836. if (!is_array($aFields))
  837. $aFields = array($aFields);
  838. if ($bLike == 'on') {
  839. $sKeyword = $oDb->escape('%' . preg_replace('/\s+/', '%', $sKeyword) . '%');
  840. $sSqlWhere = '';
  841. foreach ($aFields as $sValue)
  842. $sSqlWhere .= "`{$sTable}`.`$sValue` LIKE " . $sKeyword . " OR ";
  843. $sSqlWhere = '(' . trim($sSqlWhere, 'OR ') . ')';
  844. } else {
  845. $sKeyword = $oDb->escape($sKeyword);
  846. $sSqlWhere = '';
  847. foreach ($aFields as $sValue)
  848. $sSqlWhere .= "`{$sTable}`.`$sValue`, ";
  849. $sSqlWhere = trim($sSqlWhere, ', ');
  850. $sSqlWhere = " MATCH({$sSqlWhere}) AGAINST (" . $sKeyword . ") ";
  851. if (!empty($sPseud))
  852. $sSqlWhere .= " AS `$sPseud` ";
  853. }
  854. return $sSqlWhere;
  855. }
  856. /**
  857. * Get set from several values for 'in' and 'not in' operators
  858. * @param $aValues array of values
  859. * return sql code for field with operator IN (NOT IN)
  860. */
  861. function getMultiValues ($aValues)
  862. {
  863. $oDb = BxDolDb::getInstance();
  864. return $oDb->implode_escape($aValues);
  865. }
  866. /**
  867. * System method for filling aPseud array.
  868. * Fill field aPseud for current class (if you will use own getSearchUnit methods then not necessary to redeclare).
  869. */
  870. function _getPseud ()
  871. {
  872. }
  873. /**
  874. * Add replace markers. Markers are replaced in titles and browse urls
  875. * @param $a array of markers as key => value
  876. * @return true on success or false on error
  877. */
  878. public function addMarkers ($a)
  879. {
  880. if (empty($a) || !is_array($a))
  881. return false;
  882. $this->_aMarkers = array_merge ($this->_aMarkers, $a);
  883. return true;
  884. }
  885. /**
  886. * Replace provided markers in a string
  887. * @param $mixed string or array to replace markers in
  888. * @return string where all occured markers are replaced
  889. */
  890. protected function _replaceMarkers ($mixed)
  891. {
  892. return bx_replace_markers($mixed, $this->_aMarkers);
  893. }
  894. }
  895. /** @} */