BxDolSearch.php 37 KB

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