BxDolSearch.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) BoonEx Pty Limited - http://www.boonex.com/
  4. * CC-BY License - http://creativecommons.org/licenses/by/3.0/
  5. *
  6. * @defgroup TridentCore Trident 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. * @page objects
  13. * @section search Search
  14. * @ref BxDolSearch
  15. */
  16. /**
  17. * Search class for different content.
  18. *
  19. * Allows present content in main search area for all modules.
  20. *
  21. * To add taking part content from your module to search area you need add a record to `sys_objects_search` table:
  22. * @code
  23. * ID - autoincremented id for internal usage
  24. * ObjectName - your unique module name, with vendor prefix, lowercase and spaces are underscored
  25. * Title - language key for module's choicer in global search form
  26. * ClassName - class name which is responsible for search process ad showing result for found results
  27. * ClassPath - file where your ClassName is stored (if class is stored in template folder then you can use key {tmpl})
  28. * @endcode
  29. *
  30. * 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.
  31. *
  32. * Example of usage can be seen in the BoonEx modules, for example: Posts
  33. *
  34. * Memberships/ACL:
  35. * Doesn't depend on user's membership.
  36. *
  37. * Alerts:
  38. * no alerts available
  39. *
  40. */
  41. class BxDolSearch extends BxDol
  42. {
  43. protected $aClasses = array(); ///< array of all search classes
  44. protected $aChoice = array(); ///< array of current search classes which were choosen in search area
  45. protected $_bLiveSearch = false;
  46. protected $_sMetaType = '';
  47. protected $_sCategoryObject = '';
  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 = '';
  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. $oEx->setId($aValue['id']);
  91. $oEx->setLiveSearch($this->_bLiveSearch);
  92. $oEx->setMetaType($this->_sMetaType);
  93. $oEx->setCategoryObject($this->_sCategoryObject);
  94. $oEx->setCenterContentUnitSelector(false);
  95. $sCode .= $oEx->processing();
  96. }
  97. return $sCode;
  98. }
  99. public function getEmptyResult ()
  100. {
  101. $sKey = _t('_Empty');
  102. return DesignBoxContent($sKey, MsgBox($sKey), 1);
  103. }
  104. protected function getKeyTitlesPairs ()
  105. {
  106. $a = array();
  107. foreach ($this->aChoice as $sKey => $r)
  108. if ($this->_sMetaType || $r['GlobalSearch'])
  109. $a[$sKey] = _t($r['title']);
  110. return $a;
  111. }
  112. public function setLiveSearch($bLiveSearch)
  113. {
  114. $this->_bLiveSearch = $bLiveSearch;
  115. }
  116. public function setMetaType($s)
  117. {
  118. $aMetaTypes = array('location_country', 'location_country_city', 'mention', 'keyword');
  119. if (in_array($s, $aMetaTypes))
  120. $this->_sMetaType = $s;
  121. }
  122. public function setCategoryObject($s)
  123. {
  124. $this->_sCategoryObject = $s;
  125. }
  126. }
  127. /*
  128. * Search class for processing search requests and displaying search results.
  129. *
  130. * Allows present content from modules on search params or internal (via fields of class) conditions.
  131. *
  132. * Example of usage (you can see example in any of default Dolhpin's modules):
  133. *
  134. * 1. Extends your own search class from this one (or from BxBaseSearchResult or BxBaseSearchResultSharedMedia classes)
  135. * 2. Set necessary fields of class (using as example BxFilesSearch):
  136. *
  137. * @code
  138. *
  139. * // main field of search class
  140. * $this->aCurrent = array(
  141. *
  142. * // name of module
  143. * 'name' => 'bx_files',
  144. *
  145. * // language key with name of module
  146. * 'title' => '_bx_files',
  147. *
  148. * // main content table
  149. * 'table' => 'bx_files_main',
  150. *
  151. * // array of all fields which can be choosen for select in result query
  152. * 'ownFields' => array('ID', 'Title', 'Uri', 'Desc', 'Date', 'Size', 'Ext', 'Views', 'Rate', 'RateCount', 'Type'),
  153. *
  154. * // array of fields which take a part in search by keyword (global search and related words only - in other cases leave it blank)
  155. * 'searchFields' => array('Title', 'Tags', 'Desc', 'Categories'),
  156. *
  157. * // array of join tables
  158. * // 'type' - type of join
  159. * // 'table' - join table
  160. * // 'mainField' - field from main table for 'on' condition
  161. * // 'onField' - field from joining table for 'on' condition
  162. * // 'joinFields' - array of fields from joining table
  163. *
  164. * 'join' => array(
  165. * 'profile' => array(
  166. * 'type' => 'left',
  167. * 'table' => 'Profiles',
  168. * 'mainField' => 'Owner',
  169. * 'onField' => 'ID',
  170. * 'joinFields' => array('NickName'),
  171. * ),
  172. * // ...
  173. * ),
  174. *
  175. * // array of search parameters
  176. * // 'value' - value of search parameter (can be number, string or array)
  177. * // '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
  178. * // 'operator' - operator between field and value (can be 'in', 'not in', '>', '<', 'against', 'like' and '=' by default),
  179. * // 'paramName' - GET param which will keep 'value' for pagianation
  180. *
  181. * 'restriction' => array(
  182. * 'activeStatus' => array('value'=>'approved', 'field'=>'Status', 'operator'=>'=', 'paramName' => 'status'),
  183. * 'albumType' => array('value'=>'', 'field'=>'Type', 'operator'=>'=', 'paramName'=>'albumType', 'table'=>'sys_albums'),
  184. * ),
  185. *
  186. * // array of pagination
  187. * // 'perPage' - units per page
  188. * // 'start' - show search results starting from this number
  189. * // 'num' - number of element to show on current page + 1
  190. *
  191. * 'paginate' => array('perPage' => 10, 'start' => 0, 'num' => 11),
  192. *
  193. * // sort mode - by default it was last (DESC by date)
  194. * 'sorting' => 'last',
  195. *
  196. * // mode of units presentation in search result, can be 'short' or 'full' view
  197. * 'view' => 'full',
  198. *
  199. * // field of id
  200. * 'ident' => 'ID',
  201. *
  202. * // rss feed array
  203. * 'rss' => array(
  204. * 'title' => '',
  205. * 'link' => '',
  206. * 'image' => '',
  207. * 'profile' => 0,
  208. * 'fields' => array (
  209. * 'Link' => '',
  210. * 'Title' => 'Title',
  211. * 'DateTimeUTS' => 'Date',
  212. * 'Desc' => 'Desc',
  213. * 'Photo' => '',
  214. * ),
  215. * ),
  216. * );
  217. *
  218. * // array of fields renamings
  219. * $aPseud - filling in costructor by function _getPseud
  220. *
  221. * // unique identificator from `sys_objects_search` table
  222. * $id;
  223. *
  224. * @endcode
  225. *
  226. * Memberships/ACL:
  227. * Doesn't depend on user's membership.
  228. *
  229. * Alerts:
  230. * no alerts available
  231. *
  232. */
  233. class BxDolSearchResult implements iBxDolReplaceable
  234. {
  235. public $aCurrent; ///< search results configuration
  236. protected $aPseud; ///< array of fields renamings
  237. protected $id; ///< unique identificator of block on mixed search results page
  238. protected $bDisplayEmptyMsg = false; ///< display empty message instead of nothing, when no results
  239. protected $sDisplayEmptyMsgKey = ''; ///< custom empty message language key, instead of default "empty" message
  240. protected $bProcessPrivateContent = true; ///< check each item for privacy, if view isn't allowed then display private view instead
  241. protected $bForceAjaxPaginate = false; ///< force ajax paginate
  242. protected $_bLiveSearch = false;
  243. protected $_sMetaType = '';
  244. protected $_sCategoryObject = '';
  245. protected $_aMarkers = array (); ///< markers to replace somewhere, usually title and browse url (defined in custom class)
  246. /**
  247. * constructor
  248. * filling identificator field
  249. */
  250. function __construct ()
  251. {
  252. if (isset($this->aPseud['id']))
  253. $this->aCurrent['ident'] = $this->aPseud['id'];
  254. }
  255. public function getId()
  256. {
  257. return $this->id;
  258. }
  259. public function setId($sId)
  260. {
  261. $this->id = $sId;
  262. }
  263. public function setAjaxPaginate($b = true)
  264. {
  265. $this->bForceAjaxPaginate = $b;
  266. }
  267. public function setLiveSearch($bLiveSearch)
  268. {
  269. $this->_bLiveSearch = $bLiveSearch;
  270. }
  271. public function setMetaType($s)
  272. {
  273. $this->_sMetaType = $s;
  274. }
  275. public function setCategoryObject($s)
  276. {
  277. $this->_sCategoryObject = $s;
  278. }
  279. /**
  280. * Display empty message if there is no content, custom empty message can be used.
  281. * @param $b - boolan value to enable or disable 'empty' message
  282. * @param $sLangKey [optional] - custom 'empty' message
  283. */
  284. public function setDisplayEmptyMsg($b, $sLangKey = '')
  285. {
  286. $this->bDisplayEmptyMsg = $b;
  287. if ($sLangKey)
  288. $this->sDisplayEmptyMsgKey = $sLangKey;
  289. }
  290. /**
  291. * Perform privacy checking for every unit
  292. * @param $b - boolan value to enable or disable privacy checking
  293. */
  294. public function setProcessPrivateContent ($b)
  295. {
  296. $this->bProcessPrivateContent = $b;
  297. }
  298. /**
  299. * Get html box of search results (usually used in grlobal search)
  300. * @return html code
  301. */
  302. function processing ()
  303. {
  304. $sCode = $this->displayResultBlock();
  305. if ($this->aCurrent['paginate']['num'] > 0) {
  306. $sPaginate = $this->showPagination();
  307. $sCode = $this->displaySearchBox($sCode, $sPaginate);
  308. } else {
  309. $sCode = $this->bDisplayEmptyMsg ? $this->displaySearchBox(MsgBox(_t($this->sDisplayEmptyMsgKey ? $this->sDisplayEmptyMsgKey : '_Empty'))) : '';
  310. }
  311. return $sCode;
  312. }
  313. /**
  314. * Get html output of search result
  315. * @return html code
  316. */
  317. function displayResultBlock ()
  318. {
  319. $aData = $this->getSearchData();
  320. if (count($aData) > 0) {
  321. $sCode .= $this->addCustomParts();
  322. foreach ($aData as $iKey => $aValue) {
  323. $sCode .= $this->displaySearchUnit($aValue);
  324. }
  325. }
  326. return $sCode;
  327. }
  328. /**
  329. * Add different code before html output (usually redeclared)
  330. * no return result
  331. */
  332. function addCustomParts ()
  333. {
  334. }
  335. /**
  336. * Get XML string for rss output
  337. */
  338. function rss ()
  339. {
  340. if (!isset($this->aCurrent['rss']['fields']) || !isset($this->aCurrent['rss']['link']))
  341. return '';
  342. $aData = $this->getSearchData();
  343. $f = &$this->aCurrent['rss']['fields'];
  344. if ($aData) {
  345. foreach ($aData as $k => $a)
  346. $aData[$k][$f['Link']] = $this->getRssUnitLink ($a);
  347. }
  348. $oRss = new BxDolRssFactory ();
  349. return $oRss->GenRssByCustomData(
  350. $aData,
  351. isset($this->aCurrent['rss']['title']) && $this->aCurrent['rss']['title'] ? $this->aCurrent['rss']['title'] : $this->aCurrent['title'],
  352. $this->aCurrent['rss']['link'],
  353. $this->aCurrent['rss']['fields'],
  354. isset($this->aCurrent['rss']['image']) ? $this->aCurrent['rss']['image'] : '',
  355. isset($this->aCurrent['rss']['profile']) ? $this->aCurrent['rss']['profile'] : 0
  356. );
  357. }
  358. /**
  359. * Output RSS XML with XML header
  360. */
  361. function outputRSS ()
  362. {
  363. header('Content-Type: text/xml; charset=UTF-8');
  364. echo $this->rss();
  365. }
  366. /**
  367. * Return rss unit link (redeclared)
  368. */
  369. function getRssUnitLink (&$a)
  370. {
  371. // override this functions to return permalink to rss unit
  372. }
  373. /**
  374. * Naming fields in query's body
  375. * @param string $sFieldName name of field
  376. * @param string $sTableName name of field's table
  377. * $param string $sOperator of field's calculation (like MAX)
  378. * @param boolean $bRenameMode indicator for renaming and unsetting fields from field of class $this->aPseud
  379. * return $sqlUnit sql code and unsetting elements from aPseud field
  380. */
  381. function setFieldUnit ($sFieldName, $sTableName, $sOperator = '', $bRenameMode = true)
  382. {
  383. if (!empty($sOperator))
  384. $sqlUnit = "$sOperator(`$sTableName`.`$sFieldName`)";
  385. else
  386. $sqlUnit = "`$sTableName`.`$sFieldName`";
  387. if (isset($this->aPseud) && $bRenameMode !== false) {
  388. $sKey = array_search($sFieldName, $this->aPseud);
  389. if ($sKey !== false) {
  390. $sqlUnit .= " as `$sKey`";
  391. unset($this->aPseud[$sKey]);
  392. }
  393. }
  394. return $sqlUnit . ', ';
  395. }
  396. /**
  397. * Get html code of of every search unit
  398. * @param array $aData array of every search unit
  399. * return html code
  400. */
  401. function displaySearchUnit ($aData)
  402. {
  403. }
  404. /**
  405. * Get html code of search box with search results
  406. * @param string $sCode html code of search results
  407. * $param $sPaginate html code of paginate
  408. * return html code
  409. */
  410. function displaySearchBox ($sCode, $sPaginate = '')
  411. {
  412. }
  413. /**
  414. * Get html code of pagination
  415. */
  416. function showPagination ($bAdmin = false, $bChangePage = true, $bPageReload = true)
  417. {
  418. }
  419. /**
  420. * Get array of data with search results
  421. * return array with data
  422. */
  423. function getSearchData ()
  424. {
  425. $this->aPseud = $this->_getPseud();
  426. $this->setConditionParams();
  427. if ($this->aCurrent['paginate']['num'] > 0) {
  428. $aData = $this->getSearchDataByParams();
  429. return $aData;
  430. }
  431. return array();
  432. }
  433. /**
  434. * Get array with code for sql elements
  435. * @param boolean #bREnameMode indicator of renmaing fields
  436. * return array with joinFields, ownFields, groupBy and join elements
  437. */
  438. function getJoins ($bRenameMode = true)
  439. {
  440. $aSql = array();
  441. // joinFields & join
  442. if (isset($this->aCurrent['join']) && is_array($this->aCurrent['join'])) {
  443. $aSql = array('join'=>'', 'ownFields'=>'', 'joinFields'=>'', 'groupBy'=>'');
  444. foreach ($this->aCurrent['join'] as $sKey => $aValue) {
  445. $sAlias = isset($aValue['table_alias']) ? $aValue['table_alias'] : $aValue['table'];
  446. $sTableAlias = isset($aValue['table_alias']) ? " AS {$aValue['table_alias']} " : '';
  447. if (is_array($aValue['joinFields'])) {
  448. foreach ($aValue['joinFields'] as $sValue) {
  449. $aSql['joinFields'] .= $this->setFieldUnit($sValue, $sAlias, isset($aValue['operator']) ? $aValue['operator'] : null, $bRenameMode);
  450. }
  451. }
  452. // group by
  453. if (isset($aValue['groupTable']))
  454. $aSql['groupBy'] = "GROUP BY `{$aValue['groupTable']}`.`{$aValue['groupField']}`, ";
  455. $sOn = isset($aValue['mainTable']) ? $aValue['mainTable'] : $this->aCurrent['table'];
  456. $aSql['join'] .= " {$aValue['type']} JOIN `{$aValue['table']}` $sTableAlias ON `{$sAlias}`.`{$aValue['onField']}`=`$sOn`.`{$aValue['mainField']}`";
  457. $aSql['ownFields'] .= $this->setFieldUnit($aValue['mainField'], $sOn, '', $bRenameMode);
  458. }
  459. $aSql['joinFields'] = trim($aSql['joinFields'], ', ');
  460. $aSql['groupBy'] = trim($aSql['groupBy'], ', ');
  461. }
  462. return $aSql;
  463. }
  464. /**
  465. * Concat sql parts of query, run it and return result array
  466. * @param $aParams addon param
  467. * return $aData multivariate array
  468. */
  469. function getSearchDataByParams ($aParams = '')
  470. {
  471. $aSql = array('ownFields'=>'', 'joinFields'=>'', 'order'=>'');
  472. // searchFields
  473. foreach ($this->aCurrent['ownFields'] as $sValue)
  474. $aSql['ownFields'] .= $this->setFieldUnit($sValue, $this->aCurrent['table']);
  475. // joinFields & join
  476. $aJoins = $this->getJoins();
  477. if (!empty($aJoins)) {
  478. $aSql['ownFields'] .= $aJoins['ownFields'];
  479. $aSql['ownFields'] .= $aJoins['joinFields'];
  480. $aSql['join'] = $aJoins['join'];
  481. $aSql['groupBy'] = $aJoins['groupBy'];
  482. }
  483. $aSql['ownFields'] = trim($aSql['ownFields'], ', ');
  484. // from
  485. $aSql['from'] = " FROM `{$this->aCurrent['table']}`";
  486. // where
  487. $aSql['where'] = $this->getRestriction();
  488. // limit
  489. $aSql['limit'] = $this->getLimit();
  490. // sorting
  491. $this->setSorting();
  492. $aSort = $this->getSorting($this->aCurrent['sorting']);
  493. foreach ($aSort as $sKey => $sValue)
  494. $aSql[$sKey] .= $sValue;
  495. // execution
  496. $sqlQuery = "SELECT " . $aSql['ownFields'];
  497. $sqlQuery .= ' ' . $aSql['from'];
  498. if (isset($aSql['join']))
  499. $sqlQuery .= ' ' . $aSql['join'];
  500. if (isset($aSql['where']))
  501. $sqlQuery .= ' ' . $aSql['where'];
  502. if (isset($aSql['groupBy']))
  503. $sqlQuery .= ' ' . $aSql['groupBy'];
  504. if (isset($aSql['order']))
  505. $sqlQuery .= ' ' . $aSql['order'];
  506. if (isset($aSql['limit']))
  507. $sqlQuery .= ' ' . $aSql['limit'];
  508. // echoDbg($sqlQuery);
  509. $aRes = BxDolDb::getInstance()->getAll($sqlQuery);
  510. return $aRes;
  511. }
  512. /**
  513. * Set class fields condition params and paginate array
  514. */
  515. function setConditionParams()
  516. {
  517. // keyword
  518. $sKeyword = bx_process_input(bx_get('keyword'));
  519. if ($sKeyword !== false)
  520. $this->aCurrent['restriction']['keyword'] = array(
  521. 'value' => $sKeyword,
  522. 'field' => '',
  523. 'operator' => 'against'
  524. );
  525. // owner
  526. if (isset($_GET['ownerName'])) {
  527. $sName = bx_process_input($_GET['ownerName']);
  528. $iUser = (int)BxDolProfileQuery::getInstance()->getIdByNickname($sName);
  529. BxDolMenu::getInstance()->setCurrentProfileID($iUser);
  530. } elseif (isset($_GET['userID']))
  531. $iUser = bx_process_input($_GET['userID'], BX_DATA_INT);
  532. if (!empty($iUser))
  533. $this->aCurrent['restriction']['owner']['value'] = $iUser;
  534. // meta info
  535. if ($this->_sMetaType && !empty($this->aCurrent['object_metatags'])) {
  536. $o = BxDolMetatags::getObjectInstance($this->aCurrent['object_metatags']);
  537. if ($o) {
  538. unset($this->aCurrent['restriction']['keyword']);
  539. switch ($this->_sMetaType) {
  540. case 'location_country':
  541. $o->locationsSetSearchCondition($this, $sKeyword);
  542. break;
  543. case 'location_country_city':
  544. $o->locationsSetSearchCondition($this, $sKeyword, bx_process_input(bx_get('state')), bx_process_input(bx_get('city')));
  545. break;
  546. case 'mention':
  547. // TODO:
  548. break;
  549. case 'keyword':
  550. $o->keywordsSetSearchCondition($this, $sKeyword);
  551. break;
  552. }
  553. }
  554. }
  555. // category
  556. if ($this->_sCategoryObject && $o = BxDolCategory::getObjectInstance($this->_sCategoryObject)) {
  557. unset($this->aCurrent['restriction']['keyword']);
  558. $o->setSearchCondition($this, $sKeyword);
  559. }
  560. $this->setPaginate();
  561. $iNum = $this->getNum();
  562. if ($iNum > 0) {
  563. $this->aCurrent['paginate']['num'] = $iNum;
  564. } else {
  565. $this->aCurrent['paginate']['num'] = 0;
  566. }
  567. }
  568. /**
  569. * Check number of records on current page
  570. * return number of records on current page + 1
  571. */
  572. function getNum ()
  573. {
  574. $aJoins = $this->getJoins(false);
  575. $sqlQuery = "SELECT * FROM `{$this->aCurrent['table']}` " . (isset($aJoins['join']) ? $aJoins['join'] : '' ). $this->getRestriction() . (isset($aJoins['groupBy']) ? $aJoins['groupBy'] : '') . ' ' . $this->getLimit(true);
  576. return count(BxDolDb::getInstance()->getAll($sqlQuery));
  577. }
  578. /**
  579. * Check restriction params and make condition part of query
  580. * return $sqlWhere sql code of query for WHERE part
  581. */
  582. function getRestriction ()
  583. {
  584. $oDb = BxDolDb::getInstance();
  585. $sqlWhere = '';
  586. if (isset($this->aCurrent['restriction'])) {
  587. $aWhere[] = '1';
  588. foreach ($this->aCurrent['restriction'] as $sKey => $aValue) {
  589. $sqlCondition = '';
  590. if (isset($aValue['operator']) && isset($aValue['value']) && $aValue['value'] !== '' && $aValue['value'] !== false && $aValue['value'] !== null) {
  591. $sFieldTable = isset($aValue['table']) ? $aValue['table'] : $this->aCurrent['table'];
  592. $sqlCondition = "`{$sFieldTable}`.`{$aValue['field']}` ";
  593. switch ($aValue['operator']) {
  594. case 'nothing':
  595. $sqlCondition = " 0 ";
  596. break;
  597. case 'against':
  598. $aCond = isset($aValue['field']) && strlen($aValue['field']) > 0 ? $aValue['field'] : $this->aCurrent['searchFields'];
  599. $sqlCondition = !empty($aCond) ? $this->getSearchFieldsCond($aCond, $aValue['value']) : "";
  600. break;
  601. case 'like':
  602. $sqlCondition .= "LIKE '%" . $oDb->escape($aValue['value']) . "%'";
  603. break;
  604. case 'in':
  605. case 'not in':
  606. $sValuesString = $this->getMultiValues($aValue['value']);
  607. $sqlCondition .= strtoupper($aValue['operator']) . '('.$sValuesString.')';
  608. break;
  609. default:
  610. $sqlCondition .= $aValue['operator'] . (isset($aValue['no_quote_value']) && $aValue['no_quote_value'] ? $aValue['value'] : "'" . $oDb->escape($aValue['value']) . "'");
  611. break;
  612. }
  613. }
  614. if (strlen($sqlCondition) > 0)
  615. $aWhere[] = $sqlCondition;
  616. }
  617. $sqlWhere .= "WHERE ". implode(' AND ', $aWhere) . (isset($this->aCurrent['restriction_sql']) ? $this->aCurrent['restriction_sql'] : '');
  618. }
  619. return $sqlWhere;
  620. }
  621. /**
  622. * Get limit part of query
  623. * return $sqlFrom code for limit part pf query
  624. */
  625. function getLimit ($isAddPlusOne = false)
  626. {
  627. if (!isset($this->aCurrent['paginate']))
  628. return;
  629. $sqlFrom = (int)$this->aCurrent['paginate']['start'] > 0 ? (int)$this->aCurrent['paginate']['start'] : 0;
  630. $sqlTo = $this->aCurrent['paginate']['perPage'] + ($isAddPlusOne ? 1 : 0);
  631. if ($sqlTo > 0)
  632. return 'LIMIT ' . $sqlFrom . ', ' . $sqlTo;
  633. }
  634. /**
  635. * Set sorting field of class
  636. */
  637. function setSorting ()
  638. {
  639. $this->aCurrent['sorting'] = isset($_GET[$this->aCurrent['name'] . '_mode']) ? $_GET[$this->aCurrent['name'] . '_mode'] : $this->aCurrent['sorting'];
  640. }
  641. /**
  642. * Get sorting part of query according current sorting mode
  643. * @param string $sSortType sorting type
  644. * return array with sql elements order and ownFields
  645. */
  646. function getSorting ($sSortType = 'last')
  647. {
  648. $aOverride = $this->getAlterOrder();
  649. if (is_array($aOverride) && !empty($aOverride))
  650. return $aOverride;
  651. $aSql = array();
  652. switch ($sSortType) {
  653. case 'rand':
  654. $aSql['order'] = "ORDER BY RAND()";
  655. break;
  656. case 'score':
  657. if (is_array($this->aCurrent['restriction']['keyword'])) {
  658. $sPseud = '';
  659. if ('on' == getParam('useLikeOperator')) {
  660. $aSql['order'] = "ORDER BY `score` DESC";
  661. $sPseud = 'score';
  662. }
  663. $aSql['ownFields'] .= $this->getSearchFieldsCond($this->aCurrent['searchFields'], $this->aCurrent['restriction']['keyword']['value'], $sPseud) . ',';
  664. }
  665. break;
  666. case 'none':
  667. $aSql['order'] = "";
  668. break;
  669. default:
  670. $aSql['order'] = "ORDER BY `date` DESC";
  671. }
  672. return $aSql;
  673. }
  674. /**
  675. * Return own varaint for sorting (redeclare if necessary)
  676. * return array of sql elements
  677. */
  678. function getAlterOrder ()
  679. {
  680. return array();
  681. }
  682. /**
  683. * Set paginate fields of class according GET params 'start' and 'per_page'
  684. * forcePage is need for setting most important number of current page
  685. */
  686. function setPaginate ()
  687. {
  688. $this->aCurrent['paginate']['perPage'] = (isset($_GET['per_page']) && (int)$_GET['per_page'] != 0) ? (int)$_GET['per_page'] : $this->aCurrent['paginate']['perPage'];
  689. if (empty($this->aCurrent['paginate']['perPage']))
  690. $this->aCurrent['paginate']['perPage'] = BX_DOL_SEARCH_RESULTS_PER_PAGE_DEFAULT;
  691. $this->aCurrent['paginate']['start'] = isset($this->aCurrent['paginate']['forceStart'])
  692. ? (int)$this->aCurrent['paginate']['forceStart']
  693. : (empty($_GET['start']) ? 0 : (int)$_GET['start']);
  694. if ($this->aCurrent['paginate']['start'] < 0)
  695. $this->aCurrent['paginate']['start'] = 0;
  696. }
  697. function unsetPaginate()
  698. {
  699. unset($this->aCurrent['paginate']);
  700. }
  701. /**
  702. * Get sql where condition for search fields
  703. * @param array of search fields
  704. * @param string $sKeyword keyword value for search
  705. * @param string sPseud for setting new name for generated set of fields in query
  706. * return sql code of WHERE part in query
  707. */
  708. function getSearchFieldsCond ($aFields, $sKeyword, $sPseud = '')
  709. {
  710. if (!$sKeyword)
  711. return '';
  712. $sTable = empty($this->aCurrent['tableSearch']) ? $this->aCurrent['table'] : $this->aCurrent['tableSearch'];
  713. $oDb = BxDolDb::getInstance();
  714. $bLike = getParam('useLikeOperator');
  715. $sKeyword = $oDb->escape($sKeyword);
  716. if (!is_array($aFields))
  717. $aFields = array($aFields);
  718. if ($bLike == 'on') {
  719. $sKeyword = '%' . preg_replace('/\s+/', '%', $sKeyword) . '%';
  720. $sSqlWhere = '';
  721. foreach ($aFields as $sValue)
  722. $sSqlWhere .= "`{$sTable}`.`$sValue` LIKE '$sKeyword' OR ";
  723. $sSqlWhere = '(' . trim($sSqlWhere, 'OR ') . ')';
  724. } else {
  725. $sSqlWhere = '';
  726. foreach ($aFields as $sValue)
  727. $sSqlWhere .= "`{$sTable}`.`$sValue`, ";
  728. $sSqlWhere = trim($sSqlWhere, ', ');
  729. $sSqlWhere = " MATCH({$sSqlWhere}) AGAINST ('{$sKeyword}') ";
  730. if (!empty($sPseud))
  731. $sSqlWhere .= " AS `$sPseud` ";
  732. }
  733. return $sSqlWhere;
  734. }
  735. /**
  736. * Get set from several values for 'in' and 'not in' operators
  737. * @param $aValues array of values
  738. * return sql code for field with operator IN (NOT IN)
  739. */
  740. function getMultiValues ($aValues)
  741. {
  742. $oDb = BxDolDb::getInstance();
  743. return $oDb->implode_escape($aValues);
  744. }
  745. /**
  746. * System method for filling aPseud array.
  747. * Fill field aPseud for current class (if you will use own getSearchUnit methods then not necessary to redeclare).
  748. */
  749. function _getPseud ()
  750. {
  751. }
  752. /**
  753. * Add replace markers. Markers are replaced in titles and browse urls
  754. * @param $a array of markers as key => value
  755. * @return true on success or false on error
  756. */
  757. public function addMarkers ($a)
  758. {
  759. if (empty($a) || !is_array($a))
  760. return false;
  761. $this->_aMarkers = array_merge ($this->_aMarkers, $a);
  762. return true;
  763. }
  764. /**
  765. * Replace provided markers in a string
  766. * @param $mixed string or array to replace markers in
  767. * @return string where all occured markers are replaced
  768. */
  769. protected function _replaceMarkers ($mixed)
  770. {
  771. return bx_replace_markers($mixed, $this->_aMarkers);
  772. }
  773. }
  774. /** @} */