PageRenderTime 86ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

/campsite/src/classes/Article.php

https://github.com/joechrysler/Campsite
PHP | 2949 lines | 1770 code | 339 blank | 840 comment | 277 complexity | eae368d1937328571a9f832ba82e3673 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, LGPL-2.1, Apache-2.0
  1. <?php
  2. /**
  3. * @package Campsite
  4. */
  5. /**
  6. * Includes
  7. */
  8. require_once($GLOBALS['g_campsiteDir'].'/db_connect.php');
  9. require_once($GLOBALS['g_campsiteDir'].'/classes/DatabaseObject.php');
  10. require_once($GLOBALS['g_campsiteDir'].'/classes/DbObjectArray.php');
  11. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleData.php');
  12. require_once($GLOBALS['g_campsiteDir'].'/classes/Log.php');
  13. require_once($GLOBALS['g_campsiteDir'].'/classes/Language.php');
  14. require_once($GLOBALS['g_campsiteDir'].'/classes/CampCacheList.php');
  15. /**
  16. * @package Campsite
  17. */
  18. class Article extends DatabaseObject {
  19. /**
  20. * The column names used for the primary key.
  21. * @var array
  22. */
  23. var $m_keyColumnNames = array('Number',
  24. 'IdLanguage');
  25. var $m_dbTableName = 'Articles';
  26. var $m_columnNames = array(
  27. // int - Publication ID
  28. 'IdPublication',
  29. // int -Issue ID
  30. 'NrIssue',
  31. // int - Section ID
  32. 'NrSection',
  33. // int - Article ID
  34. 'Number',
  35. // int - Language ID,
  36. 'IdLanguage',
  37. // string - Article Type
  38. 'Type',
  39. // int - User ID of user who manages the article in Campsite
  40. 'IdUser',
  41. // int - id of the author who wrote the article
  42. 'fk_default_author_id',
  43. // string - The title of the article.
  44. 'Name',
  45. // string
  46. // Whether the article is on the front page or not.
  47. // This is represented as 'N' or 'Y'.
  48. 'OnFrontPage',
  49. /**
  50. * Whether or not the article is on the section or not.
  51. * This is represented as 'N' or 'Y'.
  52. * @var string
  53. */
  54. 'OnSection',
  55. 'Published',
  56. 'PublishDate',
  57. 'UploadDate',
  58. 'Keywords',
  59. 'Public',
  60. 'IsIndexed',
  61. 'LockUser',
  62. 'LockTime',
  63. 'ShortName',
  64. 'ArticleOrder',
  65. 'comments_enabled',
  66. 'comments_locked',
  67. 'time_updated',
  68. 'object_id');
  69. var $m_languageName = null;
  70. private static $s_defaultOrder = array(array('field'=>'byPublication', 'dir'=>'ASC'),
  71. array('field'=>'byIssue', 'dir'=>'DESC'),
  72. array('field'=>'bySection', 'dir'=>'ASC'),
  73. array('field'=>'bySectionOrder', 'dir'=>'ASC'));
  74. private static $s_regularParameters = array('idpublication'=>'Articles.IdPublication',
  75. 'nrissue'=>'Articles.NrIssue',
  76. 'issue'=>'Articles.NrIssue',
  77. 'nrsection'=>'Articles.NrSection',
  78. 'section'=>'Articles.NrSection',
  79. 'idlanguage'=>'Articles.IdLanguage',
  80. 'name'=>'Articles.Name',
  81. 'number'=>'Articles.Number',
  82. 'upload_date'=>'DATE(Articles.UploadDate)',
  83. 'publish_date'=>'DATE(Articles.PublishDate)',
  84. 'type'=>'Articles.Type',
  85. 'keyword'=>'Articles.Keywords',
  86. 'onfrontpage'=>'Articles.OnFrontPage',
  87. 'onsection'=>'Articles.OnSection',
  88. 'public'=>'Articles.Public',
  89. 'published'=>'Articles.Published',
  90. 'workflow_status'=>'Articles.Published',
  91. 'issue_published'=>'Issues.Published',
  92. 'reads'=>'RequestObjects.request_count');
  93. /**
  94. * Construct by passing in the primary key to access the article in
  95. * the database.
  96. *
  97. * @param int $p_languageId
  98. * @param int $p_articleNumber
  99. * Not required when creating an article.
  100. */
  101. public function Article($p_languageId = null, $p_articleNumber = null)
  102. {
  103. parent::DatabaseObject($this->m_columnNames);
  104. $this->m_data['IdLanguage'] = $p_languageId;
  105. $this->m_data['Number'] = $p_articleNumber;
  106. if ($this->keyValuesExist()) {
  107. $this->fetch();
  108. }
  109. } // constructor
  110. /**
  111. * Fetch a single record from the database for the given key.
  112. *
  113. * @param array $p_recordSet
  114. * If the record has already been fetched and we just need to
  115. * assign the data to the object's internal member variable.
  116. *
  117. * @return boolean
  118. * TRUE on success, FALSE on failure
  119. */
  120. public function fetch($p_recordSet = null)
  121. {
  122. $res = parent::fetch($p_recordSet);
  123. if ($this->exists()) {
  124. settype($this->m_data['IdPublication'], 'integer');
  125. settype($this->m_data['NrIssue'], 'integer');
  126. settype($this->m_data['NrSection'], 'integer');
  127. settype($this->m_data['IdLanguage'], 'integer');
  128. settype($this->m_data['Number'], 'integer');
  129. settype($this->m_data['IdUser'], 'integer');
  130. settype($this->m_data['fk_default_author_id'], 'integer');
  131. settype($this->m_data['LockUser'], 'integer');
  132. settype($this->m_data['ArticleOrder'], 'integer');
  133. }
  134. return $res;
  135. }
  136. /**
  137. * A way for internal functions to call the superclass create function.
  138. * @param array $p_values
  139. */
  140. public function __create($p_values = null) { return parent::create($p_values); }
  141. /**
  142. * Create an article in the database. Use the SET functions to
  143. * change individual values.
  144. *
  145. * If you would like to "place" the article using the publication ID,
  146. * issue number, and section number, you can only do so if all three
  147. * of these parameters are present. Otherwise, the article will remain
  148. * unplaced.
  149. *
  150. * @param string $p_articleType
  151. * @param string $p_name
  152. * @param int $p_publicationId
  153. * @param int $p_issueNumber
  154. * @param int $p_sectionNumber
  155. * @return void
  156. */
  157. public function create($p_articleType, $p_name = null, $p_publicationId = null, $p_issueNumber = null, $p_sectionNumber = null)
  158. {
  159. global $g_ado_db;
  160. $this->m_data['Number'] = $this->__generateArticleNumber();
  161. $this->m_data['ArticleOrder'] = $this->m_data['Number'];
  162. // Create the record
  163. $values = array();
  164. if (!is_null($p_name)) {
  165. $values['Name'] = $p_name;
  166. }
  167. // Only categorize the article if all three arguments:
  168. // $p_publicationId, $p_issueNumber, and $p_sectionNumber
  169. // are present.
  170. if (is_numeric($p_publicationId)
  171. && is_numeric($p_issueNumber)
  172. && is_numeric($p_sectionNumber)
  173. && ($p_publicationId > 0)
  174. && ($p_issueNumber > 0)
  175. && ($p_sectionNumber > 0) ) {
  176. $values['IdPublication'] = (int)$p_publicationId;
  177. $values['NrIssue'] = (int)$p_issueNumber;
  178. $values['NrSection'] = (int)$p_sectionNumber;
  179. }
  180. $values['ShortName'] = $this->m_data['Number'];
  181. $values['Type'] = $p_articleType;
  182. $values['Public'] = 'Y';
  183. if (!is_null($p_publicationId) && $p_publicationId > 0) {
  184. $where = " WHERE IdPublication = $p_publicationId AND NrIssue = $p_issueNumber"
  185. . " and NrSection = $p_sectionNumber";
  186. } else {
  187. $where = '';
  188. }
  189. // compute article order number
  190. $queryStr = "SELECT MIN(ArticleOrder) AS min FROM Articles$where";
  191. $articleOrder = $g_ado_db->GetOne($queryStr);
  192. if (is_null($articleOrder) || !isset($values['NrSection'])) {
  193. $articleOrder = $this->m_data['Number'];
  194. } else {
  195. $increment = $articleOrder > 0 ? 1 : 2;
  196. $queryStr = "UPDATE Articles SET ArticleOrder = ArticleOrder + $increment $where";
  197. $g_ado_db->Execute($queryStr);
  198. $articleOrder = 1;
  199. }
  200. $values['ArticleOrder'] = $articleOrder;
  201. $success = parent::create($values);
  202. if (!$success) {
  203. return;
  204. }
  205. $this->fetch();
  206. $this->setProperty('UploadDate', 'NOW()', true, true);
  207. // Insert an entry into the article type table.
  208. $articleData = new ArticleData($this->m_data['Type'],
  209. $this->m_data['Number'],
  210. $this->m_data['IdLanguage']);
  211. $articleData->create();
  212. if (function_exists("camp_load_translation_strings")) {
  213. camp_load_translation_strings("api");
  214. }
  215. $logtext = getGS('Article #$1 "$2" ($3) created.',
  216. $this->m_data['Number'], $this->m_data['Name'], $this->getLanguageName());
  217. Log::Message($logtext, null, 31);
  218. } // fn create
  219. /**
  220. * Create a unique identifier for an article.
  221. * @access private
  222. */
  223. public function __generateArticleNumber()
  224. {
  225. global $g_ado_db;
  226. $queryStr = 'UPDATE AutoId SET ArticleId=LAST_INSERT_ID(ArticleId + 1)';
  227. $g_ado_db->Execute($queryStr);
  228. if ($g_ado_db->Affected_Rows() <= 0) {
  229. // If we were not able to get an ID.
  230. return 0;
  231. }
  232. return (int)$g_ado_db->Insert_ID();
  233. } // fn __generateArticleNumber
  234. /**
  235. * Create a copy of this article.
  236. *
  237. * @param int $p_destPublicationId -
  238. * The destination publication ID.
  239. * @param int $p_destIssueNumber -
  240. * The destination issue number.
  241. * @param int $p_destSectionNumber -
  242. * The destination section number.
  243. * @param int $p_userId -
  244. * The user creating the copy. If null, keep the same user ID as the original.
  245. * @param mixed $p_copyTranslations -
  246. * If false (default), only this article will be copied.
  247. * If true, all translations will be copied.
  248. * If an array is passed, the translations given will be copied.
  249. * Any translations that do not exist will be ignored.
  250. *
  251. * @return Article
  252. * If $p_copyTranslations is TRUE or an array, return an array of newly created articles.
  253. * If $p_copyTranslations is FALSE, return the new Article.
  254. */
  255. public function copy($p_destPublicationId = 0, $p_destIssueNumber = 0,
  256. $p_destSectionNumber = 0, $p_userId = null,
  257. $p_copyTranslations = false)
  258. {
  259. global $g_ado_db;
  260. // It is an optimization to put these here because in most cases
  261. // you dont need these files.
  262. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleImage.php');
  263. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleTopic.php');
  264. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleAttachment.php');
  265. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleAudioclip.php');
  266. $copyArticles = array();
  267. if ($p_copyTranslations) {
  268. // Get all translations for this article
  269. $copyArticles = $this->getTranslations();
  270. // Remove any translations that are not requested to be translated.
  271. if (is_array($p_copyTranslations)) {
  272. $tmpArray = array();
  273. foreach ($copyArticles as $tmpArticle) {
  274. if (in_array($tmpArticle->m_data['IdLanguage'], $p_copyTranslations)) {
  275. $tmpArray[] = $tmpArticle;
  276. }
  277. }
  278. $copyArticles = $tmpArray;
  279. }
  280. } else {
  281. $copyArticles[] = $this;
  282. }
  283. $newArticleNumber = $this->__generateArticleNumber();
  284. // Load translation file for log message.
  285. if (function_exists("camp_load_translation_strings")) {
  286. camp_load_translation_strings("api");
  287. }
  288. $articleOrder = null;
  289. $logtext = '';
  290. $newArticles = array();
  291. foreach ($copyArticles as $copyMe) {
  292. // Construct the duplicate article object.
  293. $articleCopy = new Article();
  294. $articleCopy->m_data['IdPublication'] = (int)$p_destPublicationId;
  295. $articleCopy->m_data['NrIssue'] = (int)$p_destIssueNumber;
  296. $articleCopy->m_data['NrSection'] = (int)$p_destSectionNumber;
  297. $articleCopy->m_data['IdLanguage'] = (int)$copyMe->m_data['IdLanguage'];
  298. $articleCopy->m_data['Number'] = (int)$newArticleNumber;
  299. $values = array();
  300. // Copy some attributes
  301. $values['ShortName'] = $newArticleNumber;
  302. $values['Type'] = $copyMe->m_data['Type'];
  303. $values['OnFrontPage'] = $copyMe->m_data['OnFrontPage'];
  304. $values['OnSection'] = $copyMe->m_data['OnSection'];
  305. $values['Public'] = $copyMe->m_data['Public'];
  306. $values['ArticleOrder'] = $articleOrder;
  307. $values['Keywords'] = $copyMe->m_data['Keywords'];
  308. // Change some attributes
  309. $values['Published'] = 'N';
  310. $values['IsIndexed'] = 'N';
  311. $values['LockUser'] = 0;
  312. $values['LockTime'] = 0;
  313. if (!is_null($p_userId)) {
  314. $values['IdUser'] = $p_userId;
  315. } else {
  316. $values['IdUser'] = $copyMe->m_data['IdUser'];
  317. }
  318. $values['Name'] = $articleCopy->getUniqueName($copyMe->m_data['Name']);
  319. $articleCopy->__create($values);
  320. $articleCopy->setProperty('UploadDate', 'NOW()', true, true);
  321. if (is_null($articleOrder)) {
  322. $g_ado_db->Execute('LOCK TABLES Articles WRITE');
  323. $articleOrder = $g_ado_db->GetOne('SELECT MAX(ArticleOrder) + 1 FROM Articles');
  324. $articleCopy->setProperty('ArticleOrder', $articleOrder);
  325. $g_ado_db->Execute('UNLOCK TABLES');
  326. }
  327. // Insert an entry into the article type table.
  328. $newArticleData = new ArticleData($articleCopy->m_data['Type'],
  329. $articleCopy->m_data['Number'],
  330. $articleCopy->m_data['IdLanguage']);
  331. $newArticleData->create();
  332. $origArticleData = $copyMe->getArticleData();
  333. $origArticleData->copyToExistingRecord($articleCopy->m_data['Number']);
  334. // Copy image pointers
  335. ArticleImage::OnArticleCopy($copyMe->m_data['Number'], $articleCopy->m_data['Number']);
  336. // Copy topic pointers
  337. ArticleTopic::OnArticleCopy($copyMe->m_data['Number'], $articleCopy->m_data['Number']);
  338. // Copy file pointers
  339. ArticleAttachment::OnArticleCopy($copyMe->m_data['Number'], $articleCopy->m_data['Number']);
  340. // Copy audioclip pointers
  341. ArticleAudioclip::OnArticleCopy($copyMe->m_data['Number'], $articleCopy->m_data['Number']);
  342. // Position the new article at the beginning of the section
  343. $articleCopy->positionAbsolute(1);
  344. $newArticles[] = $articleCopy;
  345. $languageObj = new Language($copyMe->getLanguageId());
  346. $logtext .= getGS('Article #$1 "$2" ($3) copied to Article #$4 (publication $5, issue $6, section $7).',
  347. $copyMe->getArticleNumber(), $copyMe->getName(), $languageObj->getCode(),
  348. $articleCopy->getArticleNumber(), $articleCopy->getPublicationId(),
  349. $articleCopy->getIssueNumber(), $articleCopy->getSectionNumber());
  350. }
  351. Log::Message($logtext, null, 155);
  352. if ($p_copyTranslations) {
  353. return $newArticles;
  354. } else {
  355. return array_pop($newArticles);
  356. }
  357. } // fn copy
  358. /**
  359. * This is a convenience function to move an article from
  360. * one section to another.
  361. *
  362. * @param int $p_destPublicationId -
  363. * The destination publication ID.
  364. * @param int $p_destIssueNumber -
  365. * The destination issue number.
  366. * @param int $p_destSectionNumber -
  367. * The destination section number.
  368. *
  369. * @return boolean
  370. */
  371. public function move($p_destPublicationId = 0, $p_destIssueNumber = 0,
  372. $p_destSectionNumber = 0)
  373. {
  374. global $g_ado_db;
  375. $columns = array();
  376. if ($this->m_data["IdPublication"] != $p_destPublicationId) {
  377. $columns["IdPublication"] = (int)$p_destPublicationId;
  378. }
  379. if ($this->m_data["NrIssue"] != $p_destIssueNumber) {
  380. $columns["NrIssue"] = (int)$p_destIssueNumber;
  381. }
  382. if ($this->m_data["NrSection"] != $p_destSectionNumber) {
  383. $columns["NrSection"] = (int)$p_destSectionNumber;
  384. }
  385. $success = false;
  386. if (count($columns) > 0) {
  387. $success = $this->update($columns);
  388. if ($success) {
  389. $g_ado_db->Execute('LOCK TABLES Articles WRITE');
  390. $articleOrder = $g_ado_db->GetOne('SELECT MAX(ArticleOrder) + 1 FROM Articles');
  391. $this->setProperty('ArticleOrder', $articleOrder);
  392. $g_ado_db->Execute('UNLOCK TABLES');
  393. $this->positionAbsolute(1);
  394. }
  395. }
  396. return $success;
  397. } // fn move
  398. /**
  399. * Return a unique name based on this article's name.
  400. * The name returned will have the form "original_article_name (duplicate #)"
  401. * @return string
  402. */
  403. public function getUniqueName($p_currentName)
  404. {
  405. global $g_ado_db;
  406. $origNewName = $p_currentName . " (".getGS("Duplicate");
  407. $newName = $origNewName .")";
  408. $count = 1;
  409. while (true) {
  410. $queryStr = 'SELECT * FROM Articles '
  411. .' WHERE IdPublication = '.$this->m_data['IdPublication']
  412. .' AND NrIssue = ' . $this->m_data['NrIssue']
  413. .' AND NrSection = ' . $this->m_data['NrSection']
  414. .' AND IdLanguage = ' . $this->m_data['IdLanguage']
  415. ." AND Name = '" . mysql_escape_string($newName) . "'";
  416. $row = $g_ado_db->GetRow($queryStr);
  417. if (count($row) > 0) {
  418. $newName = $origNewName.' '.++$count.')';
  419. } else {
  420. break;
  421. }
  422. }
  423. return $newName;
  424. } // fn getUniqueName
  425. /**
  426. * Create a copy of the article, but make it a translation
  427. * of the current one.
  428. *
  429. * @param int $p_languageId
  430. * @param int $p_userId
  431. * @param string $p_name
  432. * @return Article
  433. */
  434. public function createTranslation($p_languageId, $p_userId, $p_name)
  435. {
  436. // Construct the duplicate article object.
  437. $articleCopy = new Article();
  438. $articleCopy->m_data['IdPublication'] = $this->m_data['IdPublication'];
  439. $articleCopy->m_data['NrIssue'] = $this->m_data['NrIssue'];
  440. $articleCopy->m_data['NrSection'] = $this->m_data['NrSection'];
  441. $articleCopy->m_data['IdLanguage'] = $p_languageId;
  442. $articleCopy->m_data['Number'] = $this->m_data['Number'];
  443. $values = array();
  444. // Copy some attributes
  445. $values['ShortName'] = $this->m_data['ShortName'];
  446. $values['Type'] = $this->m_data['Type'];
  447. $values['OnFrontPage'] = $this->m_data['OnFrontPage'];
  448. $values['OnSection'] = $this->m_data['OnFrontPage'];
  449. $values['Public'] = $this->m_data['Public'];
  450. $values['ArticleOrder'] = $this->m_data['ArticleOrder'];
  451. $values['comments_enabled'] = $this->m_data['comments_enabled'];
  452. $values['comments_locked'] = $this->m_data['comments_locked'];
  453. // Change some attributes
  454. $values['Name'] = $p_name;
  455. $values['Published'] = 'N';
  456. $values['IsIndexed'] = 'N';
  457. $values['LockUser'] = 0;
  458. $values['LockTime'] = 0;
  459. $values['IdUser'] = $p_userId;
  460. // Create the record
  461. $success = $articleCopy->__create($values);
  462. if (!$success) {
  463. return false;
  464. }
  465. $articleCopy->setProperty('UploadDate', 'NOW()', true, true);
  466. // Insert an entry into the article type table.
  467. $articleCopyData = new ArticleData($articleCopy->m_data['Type'],
  468. $articleCopy->m_data['Number'], $articleCopy->m_data['IdLanguage']);
  469. $articleCopyData->create();
  470. $origArticleData = $this->getArticleData();
  471. $origArticleData->copyToExistingRecord($articleCopy->getArticleNumber(), $p_languageId);
  472. if (function_exists("camp_load_translation_strings")) {
  473. camp_load_translation_strings("api");
  474. }
  475. $logtext = getGS('Article #$1 "$2" ($3) translated to "$4" ($5)',
  476. $this->getArticleNumber(), $this->getTitle(), $this->getLanguageName(),
  477. $articleCopy->getTitle(), $articleCopy->getLanguageName());
  478. Log::Message($logtext, null, 31);
  479. return $articleCopy;
  480. } // fn createTranslation
  481. /**
  482. * Delete article from database. This will
  483. * only delete one specific translation of the article.
  484. *
  485. * @return boolean
  486. */
  487. public function delete()
  488. {
  489. // It is an optimization to put these here because in most cases
  490. // you dont need these files.
  491. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleImage.php');
  492. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleTopic.php');
  493. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleIndex.php');
  494. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleAttachment.php');
  495. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleAudioclip.php');
  496. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleComment.php');
  497. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticlePublish.php');
  498. // Delete scheduled publishing
  499. ArticlePublish::OnArticleDelete($this->m_data['Number'], $this->m_data['IdLanguage']);
  500. // Delete Article Comments
  501. ArticleComment::OnArticleDelete($this->m_data['Number'], $this->m_data['IdLanguage']);
  502. // is this the last translation?
  503. if (count($this->getLanguages()) <= 1) {
  504. // Delete image pointers
  505. ArticleImage::OnArticleDelete($this->m_data['Number']);
  506. // Delete topics pointers
  507. ArticleTopic::OnArticleDelete($this->m_data['Number']);
  508. // Delete file pointers
  509. ArticleAttachment::OnArticleDelete($this->m_data['Number']);
  510. // Delete audioclip pointers
  511. ArticleAudioclip::OnArticleDelete($this->m_data['Number']);
  512. // Delete indexes
  513. ArticleIndex::OnArticleDelete($this->getPublicationId(), $this->getIssueNumber(),
  514. $this->getSectionNumber(), $this->getLanguageId(), $this->getArticleNumber());
  515. }
  516. // Delete row from article type table.
  517. $articleData = new ArticleData($this->m_data['Type'],
  518. $this->m_data['Number'],
  519. $this->m_data['IdLanguage']);
  520. $articleData->delete();
  521. $tmpData = $this->m_data;
  522. $tmpData['languageName'] = $this->getLanguageName();
  523. // Delete row from Articles table.
  524. $deleted = parent::delete();
  525. if ($deleted) {
  526. if (function_exists("camp_load_translation_strings")) {
  527. camp_load_translation_strings("api");
  528. }
  529. $logtext = getGS('Article #$1 "$2" ($3) deleted.',
  530. $tmpData['Number'], $tmpData['Name'], $tmpData['languageName'])
  531. ." (".getGS("Publication")." ".$tmpData['IdPublication'].", "
  532. ." ".getGS("Issue")." ".$tmpData['NrIssue'].", "
  533. ." ".getGS("Section")." ".$tmpData['NrSection'].")";
  534. Log::Message($logtext, null, 32);
  535. }
  536. return $deleted;
  537. } // fn delete
  538. /**
  539. * Get the time the article was locked.
  540. *
  541. * @return string
  542. * In the form of YYYY-MM-DD HH:MM:SS
  543. */
  544. public function getLockTime()
  545. {
  546. return $this->m_data['LockTime'];
  547. } // fn getLockTime
  548. /**
  549. * Return TRUE if the article is locked, FALSE if it isnt.
  550. * @return boolean
  551. */
  552. public function isLocked()
  553. {
  554. if ( ($this->m_data['LockUser'] == 0) && ($this->m_data['LockTime'] == 0) ) {
  555. return false;
  556. } else {
  557. return true;
  558. }
  559. } // fn isLocked
  560. /**
  561. * Lock or unlock the article.
  562. *
  563. * Locking the article requires the user ID parameter.
  564. *
  565. * @param boolean $p_lock
  566. * @param int $p_userId
  567. * @return void
  568. */
  569. public function setIsLocked($p_lock, $p_userId = null)
  570. {
  571. // Check parameters
  572. if ($p_lock && !is_numeric($p_userId)) {
  573. return;
  574. }
  575. // Don't change the article timestamp when the
  576. // article is locked.
  577. $lastModified = $this->m_data['time_updated'];
  578. if ($p_lock) {
  579. $this->setProperty('LockUser', $p_userId);
  580. $this->setProperty('LockTime', 'NOW()', true, true);
  581. } else {
  582. $this->setProperty('LockUser', '0', false);
  583. $this->setProperty('LockTime', '0', false);
  584. $this->commit();
  585. }
  586. $this->setProperty('time_updated', $lastModified);
  587. } // fn setIsLocked
  588. /**
  589. * Return an array of Language objects, one for each
  590. * language the article is written in.
  591. *
  592. * @param boolean $p_excludeCurrent
  593. * If true, exclude the current language from the list.
  594. * @param array $p_order
  595. * The array of order directives in the format:
  596. * array('field'=>field_name, 'dir'=>order_direction)
  597. * field_name can take one of the following values:
  598. * bynumber, byname, byenglish_name, bycode
  599. * order_direction can take one of the following values:
  600. * asc, desc
  601. * @return array
  602. */
  603. public function getLanguages($p_excludeCurrent = false, array $p_order = array(),
  604. $p_published = false)
  605. {
  606. if (!$this->exists()) {
  607. return array();
  608. }
  609. $tmpLanguage = new Language();
  610. $columnNames = $tmpLanguage->getColumnNames(true);
  611. $queryStr = 'SELECT '.implode(',', $columnNames).' FROM Articles, Languages '
  612. .' WHERE Articles.IdLanguage = Languages.Id'
  613. .' AND IdPublication = ' . $this->m_data['IdPublication']
  614. .' AND NrIssue = ' . $this->m_data['NrIssue']
  615. .' AND NrSection = ' . $this->m_data['NrSection']
  616. .' AND Number = ' . $this->m_data['Number'];
  617. if ($p_excludeCurrent) {
  618. $queryStr .= ' AND Languages.Id != ' . $this->m_data['IdLanguage'];
  619. }
  620. if ($p_published) {
  621. $queryStr .= " AND Articles.Published = 'Y'";
  622. }
  623. $order = Article::ProcessLanguageListOrder($p_order);
  624. foreach ($order as $orderDesc) {
  625. $sqlOrder[] = $orderDesc['field'] . ' ' . $orderDesc['dir'];
  626. }
  627. if (count($sqlOrder) > 0) {
  628. $queryStr .= ' ORDER BY ' . implode(', ', $sqlOrder);
  629. }
  630. $languages = DbObjectArray::Create('Language', $queryStr);
  631. return $languages;
  632. } // fn getLanguages
  633. /**
  634. * Return an array of Article objects, one for each
  635. * type of language the article is written in.
  636. *
  637. * @param int $p_articleNumber
  638. * Optional. Use this if you call this function statically.
  639. *
  640. * @return array
  641. */
  642. public function getTranslations($p_articleNumber = null)
  643. {
  644. if (!is_null($p_articleNumber)) {
  645. $articleNumber = $p_articleNumber;
  646. } elseif (isset($this)) {
  647. $articleNumber = $this->m_data['Number'];
  648. } else {
  649. return array();
  650. }
  651. $queryStr = 'SELECT * FROM Articles '
  652. ." WHERE Number=$articleNumber";
  653. $articles = DbObjectArray::Create('Article', $queryStr);
  654. return $articles;
  655. } // fn getTranslations
  656. /**
  657. * A simple way to get the name of the language the article is
  658. * written in. The value is cached in case there are multiple
  659. * calls to this function.
  660. *
  661. * @return string
  662. */
  663. public function getLanguageName()
  664. {
  665. if (is_null($this->m_languageName)) {
  666. $language = new Language($this->m_data['IdLanguage']);
  667. $this->m_languageName = $language->getNativeName();
  668. }
  669. return $this->m_languageName;
  670. } // fn getLanguageName
  671. /**
  672. * Get the section that this article is in.
  673. * @return object
  674. */
  675. public function getSection()
  676. {
  677. $section = new Section($this->getPublicationId(), $this->getIssueNumber(),
  678. $this->getLanguageId(), $this->getSectionNumber());
  679. if (!$section->exists()) {
  680. $sections = Section::GetSections($this->getPublicationId(), $this->getIssueNumber());
  681. if (count($sections) > 0) {
  682. return $sections[0];
  683. }
  684. }
  685. return $section;
  686. } // fn getSection
  687. /**
  688. * Change the article's position in the order sequence
  689. * relative to its current position.
  690. *
  691. * @param string $p_direction -
  692. * Can be "up" or "down". "Up" means towards the beginning of the list,
  693. * and "down" means towards the end of the list.
  694. *
  695. * @param int $p_spacesToMove -
  696. * The number of spaces to move the article.
  697. *
  698. * @return boolean
  699. */
  700. public function positionRelative($p_direction, $p_spacesToMove = 1)
  701. {
  702. global $g_ado_db;
  703. CampCache::singleton()->clear('user');
  704. $this->fetch();
  705. $g_ado_db->Execute('LOCK TABLES Articles WRITE');
  706. // Get the article that is in the final position where this
  707. // article will be moved to.
  708. $compareOperator = ($p_direction == 'up') ? '<' : '>';
  709. $order = ($p_direction == 'up') ? 'desc' : 'asc';
  710. $queryStr = 'SELECT DISTINCT(Number), ArticleOrder FROM Articles '
  711. .' WHERE IdPublication='.$this->m_data['IdPublication']
  712. .' AND NrIssue='.$this->m_data['NrIssue']
  713. .' AND NrSection='.$this->m_data['NrSection']
  714. .' AND ArticleOrder '.$compareOperator.' '.$this->m_data['ArticleOrder']
  715. .' ORDER BY ArticleOrder ' . $order
  716. .' LIMIT '.($p_spacesToMove-1).', 1';
  717. $destRow = $g_ado_db->GetRow($queryStr);
  718. if (!$destRow) {
  719. // Special case: there was a bug when you duplicated articles that
  720. // caused them to have the same order number. So we check here if
  721. // there are any articles that match the order number of the current
  722. // article. The end result will be that this article will have
  723. // a different order number than all the articles it used to share it
  724. // with. However, the other articles will still have the same
  725. // order number, which means that the article may appear to 'jump'
  726. // across multiple articles.
  727. $queryStr = 'SELECT DISTINCT(Number), ArticleOrder FROM Articles '
  728. .' WHERE IdPublication='.$this->m_data['IdPublication']
  729. .' AND NrIssue='.$this->m_data['NrIssue']
  730. .' AND NrSection='.$this->m_data['NrSection']
  731. .' AND ArticleOrder='.$this->m_data['ArticleOrder']
  732. .' LIMIT '.($p_spacesToMove-1).', 1';
  733. $destRow = $g_ado_db->GetRow($queryStr);
  734. if (!$destRow) {
  735. $g_ado_db->Execute('UNLOCK TABLES');
  736. return false;
  737. }
  738. }
  739. // Shift all articles one space between the source and destination article.
  740. $operator = ($p_direction == 'up') ? '+' : '-';
  741. $minArticleOrder = min($destRow['ArticleOrder'], $this->m_data['ArticleOrder']);
  742. $maxArticleOrder = max($destRow['ArticleOrder'], $this->m_data['ArticleOrder']);
  743. $queryStr2 = 'UPDATE Articles SET ArticleOrder = ArticleOrder '.$operator.' 1 '
  744. .' WHERE IdPublication = '. $this->m_data['IdPublication']
  745. .' AND NrIssue = ' . $this->m_data['NrIssue']
  746. .' AND NrSection = ' . $this->m_data['NrSection']
  747. .' AND ArticleOrder >= '.$minArticleOrder
  748. .' AND ArticleOrder <= '.$maxArticleOrder;
  749. $g_ado_db->Execute($queryStr2);
  750. // Change position of this article to the destination position.
  751. $queryStr3 = 'UPDATE Articles SET ArticleOrder = ' . $destRow['ArticleOrder']
  752. .' WHERE IdPublication = '. $this->m_data['IdPublication']
  753. .' AND NrIssue = ' . $this->m_data['NrIssue']
  754. .' AND NrSection = ' . $this->m_data['NrSection']
  755. .' AND Number = ' . $this->m_data['Number'];
  756. $g_ado_db->Execute($queryStr3);
  757. $g_ado_db->Execute('UNLOCK TABLES');
  758. CampCache::singleton()->clear('user');
  759. // Re-fetch this article to get the updated article order.
  760. $this->fetch();
  761. return true;
  762. } // fn positionRelative
  763. /**
  764. * Move the article to the given position (i.e. reorder the article).
  765. * @param int $p_moveToPosition
  766. * @return boolean
  767. */
  768. public function positionAbsolute($p_moveToPosition = 1)
  769. {
  770. global $g_ado_db;
  771. CampCache::singleton()->clear('user');
  772. $this->fetch();
  773. $g_ado_db->Execute('LOCK TABLES Articles WRITE');
  774. // Get the article that is in the location we are moving
  775. // this one to.
  776. $queryStr = 'SELECT Number, IdLanguage, ArticleOrder FROM Articles '
  777. .' WHERE IdPublication='.$this->m_data['IdPublication']
  778. .' AND NrIssue='.$this->m_data['NrIssue']
  779. .' AND NrSection='.$this->m_data['NrSection']
  780. .' ORDER BY ArticleOrder ASC LIMIT '.($p_moveToPosition - 1).', 1';
  781. $destRow = $g_ado_db->GetRow($queryStr);
  782. if (!$destRow) {
  783. $g_ado_db->Execute('UNLOCK TABLES');
  784. return false;
  785. }
  786. if ($destRow['ArticleOrder'] == $this->m_data['ArticleOrder']) {
  787. $g_ado_db->Execute('UNLOCK TABLES');
  788. // Move the destination down one.
  789. $destArticle = new Article($destRow['IdLanguage'], $destRow['Number']);
  790. $destArticle->positionRelative("down", 1);
  791. return true;
  792. }
  793. if ($destRow['ArticleOrder'] > $this->m_data['ArticleOrder']) {
  794. $operator = '-';
  795. } else {
  796. $operator = '+';
  797. }
  798. // Reorder all the other articles in this section
  799. $minArticleOrder = min($destRow['ArticleOrder'], $this->m_data['ArticleOrder']);
  800. $maxArticleOrder = max($destRow['ArticleOrder'], $this->m_data['ArticleOrder']);
  801. $queryStr = 'UPDATE Articles '
  802. .' SET ArticleOrder = ArticleOrder '.$operator.' 1 '
  803. .' WHERE IdPublication='.$this->m_data['IdPublication']
  804. .' AND NrIssue='.$this->m_data['NrIssue']
  805. .' AND NrSection='.$this->m_data['NrSection']
  806. .' AND ArticleOrder >= '.$minArticleOrder
  807. .' AND ArticleOrder <= '.$maxArticleOrder;
  808. $g_ado_db->Execute($queryStr);
  809. // Reposition this article.
  810. $queryStr = 'UPDATE Articles '
  811. .' SET ArticleOrder='.$destRow['ArticleOrder']
  812. .' WHERE IdPublication='.$this->m_data['IdPublication']
  813. .' AND NrIssue='.$this->m_data['NrIssue']
  814. .' AND NrSection='.$this->m_data['NrSection']
  815. .' AND Number='.$this->m_data['Number'];
  816. $g_ado_db->Execute($queryStr);
  817. $g_ado_db->Execute('UNLOCK TABLES');
  818. CampCache::singleton()->clear('user');
  819. $this->fetch();
  820. return true;
  821. } // fn positionAbsolute
  822. /**
  823. * Return true if the given user has permission to modify the content of this article.
  824. *
  825. * 1) Publishers can always edit.
  826. * 2) Users who have the ChangeArticle right can edit as long as the
  827. * article is not published. i.e. they can edit ALL articles that are
  828. * new or submitted.
  829. * 3) The user created the article and the article is in the "New" state.
  830. *
  831. * @return boolean
  832. */
  833. public function userCanModify($p_user)
  834. {
  835. $userCreatedArticle = ($this->m_data['IdUser'] == $p_user->getUserId());
  836. $articleIsNew = ($this->m_data['Published'] == 'N');
  837. $articleIsNotPublished = (($this->m_data['Published'] == 'N') || ($this->m_data['Published'] == 'S'));
  838. if ($p_user->hasPermission('Publish')
  839. || ($p_user->hasPermission('ChangeArticle')
  840. && $articleIsNotPublished)
  841. || ($userCreatedArticle && $articleIsNew)) {
  842. return true;
  843. } else {
  844. return false;
  845. }
  846. } // fn userCanModify
  847. /**
  848. * Get the name of the dynamic article type table.
  849. *
  850. * @return string
  851. */
  852. public function getArticleTypeTableName()
  853. {
  854. return 'X'.$this->m_data['Type'];
  855. } // fn getArticleTypeTableName
  856. /**
  857. * Get the publication ID of the publication that contains this article.
  858. * @return int
  859. */
  860. public function getPublicationId()
  861. {
  862. return (int)$this->m_data['IdPublication'];
  863. } // fn getPublicationId
  864. /**
  865. * Set the publication ID.
  866. *
  867. * @param int $p_value
  868. * @return boolean
  869. */
  870. public function setPublicationId($p_value)
  871. {
  872. if (is_numeric($p_value)) {
  873. return $this->setProperty('IdPublication', (int)$p_value);
  874. } else {
  875. return false;
  876. }
  877. } // fn setPublicationId
  878. /**
  879. * Get the issue that the article resides within.
  880. *
  881. * @return int
  882. */
  883. public function getIssueNumber()
  884. {
  885. return (int)$this->m_data['NrIssue'];
  886. } // fn getIssueNumber
  887. /**
  888. * Set the issue number.
  889. *
  890. * @param int $p_value
  891. * @return boolean
  892. */
  893. public function setIssueNumber($p_value)
  894. {
  895. if (is_numeric($p_value)) {
  896. return $this->setProperty('NrIssue', (int)$p_value);
  897. } else {
  898. return false;
  899. }
  900. } // fn setIssueNumber
  901. /**
  902. * Get the section number that contains this article.
  903. *
  904. * @return int
  905. */
  906. public function getSectionNumber()
  907. {
  908. return (int)$this->m_data['NrSection'];
  909. } // fn getSectionNumber
  910. /**
  911. * Set the section number.
  912. *
  913. * @param int $p_value
  914. * @return boolean
  915. */
  916. public function setSectionNumber($p_value)
  917. {
  918. if (is_numeric($p_value)) {
  919. return $this->setProperty('NrSection', (int)$p_value);
  920. } else {
  921. return false;
  922. }
  923. } // fn setSectionNumber
  924. /**
  925. * Return the language the article was written in.
  926. *
  927. * @return int
  928. */
  929. public function getLanguageId()
  930. {
  931. return (int)$this->m_data['IdLanguage'];
  932. } // fn getLanguageId
  933. /**
  934. * Return the article number. The article number is
  935. * not necessarily unique. Articles that have been translated into
  936. * multiple languages all have the same article number.
  937. * Therefore to uniquely identify an article you need both
  938. * the article number and the language ID.
  939. *
  940. * @return int
  941. */
  942. public function getArticleNumber()
  943. {
  944. return (int)$this->m_data['Number'];
  945. } // fn getArticleNumber
  946. /**
  947. * Get the title of the article.
  948. *
  949. * @return string
  950. */
  951. public function getTitle()
  952. {
  953. return $this->m_data['Name'];
  954. } // fn getTitle
  955. /**
  956. * Alias for getTitle().
  957. *
  958. * @return string
  959. */
  960. public function getName()
  961. {
  962. return $this->m_data['Name'];
  963. } // fn getName
  964. /**
  965. * Set the title of the article.
  966. *
  967. * @param string $p_title
  968. *
  969. * @return void
  970. */
  971. public function setTitle($p_title)
  972. {
  973. return parent::setProperty('Name', $p_title);
  974. } // fn setTitle
  975. /**
  976. * Get the article type.
  977. * @return string
  978. */
  979. public function getType()
  980. {
  981. return $this->m_data['Type'];
  982. } // fn getType
  983. /**
  984. * Get the logged in language's translation of the article type.
  985. * @return string
  986. */
  987. public function getTranslateType($p_languageId = null)
  988. {
  989. $type = $this->getType();
  990. $typeObj = new ArticleType($type);
  991. return $typeObj->getDisplayName($p_languageId);
  992. }
  993. /**
  994. * Return the user ID of the user who created this article.
  995. * @return int
  996. */
  997. public function getCreatorId()
  998. {
  999. return (int)$this->m_data['IdUser'];
  1000. } // fn getCreatorId
  1001. /**
  1002. * Set the user ID of the user who created this article.
  1003. *
  1004. * @param int $p_value
  1005. * @return boolean
  1006. */
  1007. public function setCreatorId($p_value)
  1008. {
  1009. return parent::setProperty('IdUser', (int)$p_value);
  1010. } // fn setCreatorId
  1011. /**
  1012. * Return the ID of the author who wrote this article.
  1013. * @return int
  1014. */
  1015. public function getAuthorId()
  1016. {
  1017. return (int)$this->m_data['fk_default_author_id'];
  1018. } // fn getAuthorId
  1019. /**
  1020. * Set the ID of the author who wrote this article.
  1021. *
  1022. * @param int $p_value
  1023. * @return boolean
  1024. */
  1025. public function setAuthorId($p_value)
  1026. {
  1027. return parent::setProperty('fk_default_author_id', (int)$p_value);
  1028. } // fn setAuthorId
  1029. /**
  1030. * Return an integer representing the order of the article
  1031. * within the section. Note that these numbers are not sequential
  1032. * and can only be compared with the other articles in the section.
  1033. *
  1034. * @return int
  1035. */
  1036. public function getOrder()
  1037. {
  1038. return $this->m_data['ArticleOrder'];
  1039. } // fn getOrder
  1040. /**
  1041. * Return true if the article will appear on the front page.
  1042. *
  1043. * @return boolean
  1044. */
  1045. public function onFrontPage()
  1046. {
  1047. return ($this->m_data['OnFrontPage'] == 'Y');
  1048. } // fn onFrontPage
  1049. /**
  1050. * Set whether the article should appear on the front page.
  1051. *
  1052. * @param boolean $p_value
  1053. * @return boolean
  1054. */
  1055. public function setOnFrontPage($p_value)
  1056. {
  1057. return parent::setProperty('OnFrontPage', $p_value?'Y':'N');
  1058. } // fn setOnFrontPage
  1059. /**
  1060. * Return TRUE if this article will appear on the section page.
  1061. *
  1062. * @return boolean
  1063. */
  1064. public function onSectionPage()
  1065. {
  1066. return ($this->m_data['OnSection'] == 'Y');
  1067. } // fn onSectionPage
  1068. /**
  1069. * Set whether the article will appear on the section page.
  1070. * @param boolean $p_value
  1071. * @return boolean
  1072. */
  1073. public function setOnSectionPage($p_value)
  1074. {
  1075. return parent::setProperty('OnSection', $p_value?'Y':'N');
  1076. } // fn setOnSectionPage
  1077. /**
  1078. * Return the current workflow state of the article:
  1079. * 'Y' = "Published"
  1080. * 'S' = "Submitted"
  1081. * 'N' = "New"
  1082. *
  1083. * @return string
  1084. * Can be 'Y', 'S', or 'N'.
  1085. */
  1086. public function getWorkflowStatus()
  1087. {
  1088. return $this->m_data['Published'];
  1089. } // fn getWorkflowStatus
  1090. /**
  1091. * Return a human-readable string for the status of the workflow.
  1092. * This can be called statically or as a member function.
  1093. * If called statically, you must pass in a parameter.
  1094. *
  1095. * @param string $p_value
  1096. * @return string
  1097. */
  1098. public function getWorkflowDisplayString($p_value = null)
  1099. {
  1100. if (is_null($p_value)) {
  1101. $p_value = $this->m_data['Published'];
  1102. }
  1103. if ( ($p_value != 'Y') && ($p_value != 'S') && ($p_value != 'N') && $p_value != 'M') {
  1104. return '';
  1105. }
  1106. switch ($p_value) {
  1107. case 'Y':
  1108. return getGS("Published");
  1109. case 'M':
  1110. return getGS('Publish with issue');
  1111. case 'S':
  1112. return getGS("Submitted");
  1113. case 'N':
  1114. return getGS("New");
  1115. }
  1116. } // fn getWorkflowDisplayString
  1117. /**
  1118. * Set the workflow state of the article.
  1119. * 'Y' = 'Published'
  1120. * 'S' = 'Submitted'
  1121. * 'N' = 'New'
  1122. *
  1123. * @param string $p_value
  1124. * @return boolean
  1125. */
  1126. public function setWorkflowStatus($p_value)
  1127. {
  1128. require_once($GLOBALS['g_campsiteDir'].'/classes/ArticleIndex.php');
  1129. $p_value = strtoupper($p_value);
  1130. if ( ($p_value != 'Y') && ($p_value != 'S') && ($p_value != 'N') && ($p_value != 'M')) {
  1131. return false;
  1132. }
  1133. // If the article is being published
  1134. if ( ($this->getWorkflowStatus() != 'Y') && ($p_value == 'Y') ) {
  1135. $this->setProperty('PublishDate', 'NOW()', true, true);
  1136. }
  1137. // Unlock the article if it changes status.
  1138. if ( $this->getWorkflowStatus() != $p_value ) {
  1139. $this->setIsLocked(false);
  1140. }
  1141. if ($p_value == 'Y' || $p_value == 'M') {
  1142. $issueObj = new Issue($this->getPublicationId(), $this->getLanguageId(),
  1143. $this->getIssueNumber());
  1144. if (!$issueObj->exists()) {
  1145. return false;
  1146. }
  1147. $p_value = $issueObj->isPublished() ? 'Y' : 'M';
  1148. }
  1149. $oldStatus = $this->getWorkflowStatus();
  1150. if (!parent::setProperty('Published', $p_value)) {
  1151. return false;
  1152. }
  1153. CampCache::singleton()->clear('user');
  1154. if (function_exists("camp_load_translation_strings")) {
  1155. camp_load_translation_strings("api");
  1156. }
  1157. $logtext = getGS('Article #$1 "$2" status changed from $3 to $4.',
  1158. $this->m_data['Number'], $this->m_data['Name'],
  1159. $this->getWorkflowDisplayString($oldStatus), $this->getWorkflowDisplayString($p_value))
  1160. ." (".getGS("Publication")." ".$this->m_data['IdPublication'].", "
  1161. ." ".getGS("Issue")." ".$this->m_data['NrIssue'].", "
  1162. ." ".getGS("Section")." ".$this->m_data['NrSection'].")";
  1163. Log::Message($logtext, null, 35);
  1164. return true;
  1165. } // fn setWorkflowStatus
  1166. /**
  1167. * Get the date the article was published.
  1168. * @return string
  1169. */
  1170. public function getPublishDate()
  1171. {
  1172. return $this->m_data['PublishDate'];
  1173. } // fn getPublishDate
  1174. /**
  1175. * Set the date the article was published, parameter must be in the
  1176. * form YYYY-MM-DD.
  1177. * @param string $p_value
  1178. * @return boolean
  1179. */
  1180. public function setPublishDate($p_value)
  1181. {
  1182. return $this->setProperty('PublishDate', $p_value);
  1183. } // fn setPublishDate
  1184. /**
  1185. * Return the date the article was created in the
  1186. * form YYYY-MM-DD HH:MM:SS.
  1187. *
  1188. * @return string
  1189. */
  1190. public function getCreationDate()
  1191. {
  1192. return $this->m_data['UploadDate'];
  1193. } // fn getCreationDate
  1194. /**
  1195. * Set the date the article was created, parameter must be in the
  1196. * form YYYY-MM-DD.
  1197. * @param string $p_value
  1198. * @return boolean
  1199. */
  1200. public function setCreationDate($p_value)
  1201. {
  1202. return $this->setProperty('UploadDate', $p_value);
  1203. } // fn setCreationDate
  1204. /**
  1205. * Return the date the article was last modified in the
  1206. * form YYYY-MM-DD HH:MM:SS.
  1207. *
  1208. * @return string
  1209. */
  1210. public function getLastModified()
  1211. {
  1212. // Deal with the differences between MySQL 4
  1213. // and MySQL 5.
  1214. if (strpos($this->m_data['time_updated'], "-") === false) {
  1215. $t = $this->m_data['time_updated'];
  1216. $str = substr($t, 0, 4).'-'.substr($t, 4, 2)
  1217. .'-'.substr($t, 6, 2).' '.substr($t, 8, 2)
  1218. .':'.substr($t, 10, 2).':'.substr($t, 12);
  1219. return $str;
  1220. } else {
  1221. return $this->m_data['time_updated'];
  1222. }
  1223. } // fn getLastModified
  1224. /**
  1225. * @return string
  1226. */
  1227. public function getKeywords()
  1228. {
  1229. require_once($GLOBALS['g_campsiteDir'].'/classes/SystemPref.php');
  1230. $keywords = $this->m_data['Keywords'];
  1231. $keywordSeparator = SystemPref::Get("KeywordSeparator");
  1232. return str_replace(",", $keywordSeparator, $keywords);
  1233. } // fn getKeywords
  1234. public function getReads() {
  1235. if (!$this->exists()) {
  1236. return null;
  1237. }
  1238. if (empty($this->m_data['object_id'])) {
  1239. return 0;
  1240. }
  1241. $requestObject = new RequestObject($this->m_data['object_id']);
  1242. return $requestObject->getRequestCount();
  1243. }
  1244. /**
  1245. * @param string $p_value
  1246. * @return boolean
  1247. */
  1248. public function setKeywords($p_value)
  1249. {
  1250. require_once($GLOBALS['g_campsiteDir'].'/classes/SystemPref.php');
  1251. $keywordsSeparator = SystemPref::Get('KeywordSeparator');
  1252. $p_value = str_replace($keywordsSeparator, ",", $p_value);
  1253. return parent::setProperty('Keywords', $p_value);
  1254. } // fn setKeywords
  1255. /**
  1256. * Return TRUE if this article was published.
  1257. *
  1258. * @return boolean
  1259. */
  1260. public function isPublished()
  1261. {
  1262. return ($this->m_data['Published'] == 'Y');
  1263. } // fn isPublic
  1264. /**
  1265. * Return TRUE if this article is viewable by non-subscribers.
  1266. *
  1267. * @return boolean
  1268. */
  1269. public function isPublic()
  1270. {
  1271. return ($this->m_data['Public'] == 'Y');
  1272. } // fn isPublic
  1273. /**
  1274. * Set whether this article is viewable by non-subscribers.
  1275. *
  1276. * @param boolean $p_value
  1277. * @return boolean
  1278. */
  1279. public function setIsPublic($p_value)
  1280. {
  1281. return parent::setProperty('Public', $p_value?'Y':'N');
  1282. } // fn setIsPublic
  1283. /**
  1284. * @return boolean
  1285. */
  1286. public function isIndexed()
  1287. {
  1288. return ($this->m_data['IsIndexed'] == 'Y');
  1289. } // fn isIndexed
  1290. /**
  1291. * @param boolean value
  1292. */
  1293. public function setIsIndexed($p_value)
  1294. {
  1295. return parent::setProperty('IsIndexed', $p_value?'Y':'N');
  1296. } // fn setIsIndexed
  1297. /**
  1298. * Return the user ID of the user who has locked the article.
  1299. * @return int
  1300. */
  1301. public function getLockedByUser()
  1302. {
  1303. return $this->m_data['LockUser'];
  1304. } // fn getLockedByUser
  1305. /**
  1306. * Get the URL name for this article.
  1307. *
  1308. * @return string
  1309. */
  1310. public function getUrlName()
  1311. {
  1312. return $this->m_data['ShortName'];
  1313. } // fn getUrlName
  1314. /**
  1315. * @param string value
  1316. */
  1317. public function setUrlName($p_value)
  1318. {
  1319. return parent::setProperty('ShortName', $p_value);
  1320. } // fn setUrlName
  1321. /**
  1322. * Return the ArticleData object for this article.
  1323. *
  1324. * @return ArticleData
  1325. */
  1326. public function getArticleData()
  1327. {
  1328. return new ArticleData($this->m_data['Type'],
  1329. $this->m_data['Number'],
  1330. $this->m_data['IdLanguage']);
  1331. } // fn getArticleData
  1332. /**
  1333. * Return TRUE if comments have been activated.
  1334. *
  1335. * @return boolean
  1336. */
  1337. public function commentsEnabled()
  1338. {
  1339. return $this->m_data['comments_enabled'];
  1340. } // fn commentsEnabled
  1341. /**
  1342. * Set whether comments are enabled for this article.
  1343. *
  1344. * @param boolean $p_value
  1345. * @return boolean
  1346. */
  1347. public function setCommentsEnabled($p_value)
  1348. {
  1349. $p_value = $p_value ? '1' : '0';
  1350. return $this->setProperty('comments_enabled', $p_value);
  1351. } // fn setCommentsEnabled
  1352. /**
  1353. * Return TRUE if comments are locked for this article.
  1354. * This means that comments cannot be added.
  1355. *
  1356. * @return boolean
  1357. */
  1358. public function commentsLocked()
  1359. {
  1360. return $this->m_data['comments_locked'];
  1361. } // fn commentsLocked
  1362. /**
  1363. * Set whether comments are locked for this article.
  1364. * If TRUE, this means that comments cannot be added to
  1365. * the article.
  1366. *
  1367. * @param boolean $p_value
  1368. * @return boolean
  1369. */
  1370. public function setCommentsLocked($p_value)
  1371. {
  1372. $p_value = $p_value ? '1' : '0';
  1373. return $this->setProperty('comments_locked', $p_value);
  1374. } // fn setCommentsLocked
  1375. /*****************************************************************/
  1376. /** Static Functions */
  1377. /*****************************************************************/
  1378. /**
  1379. * Set the article workflow on issue status change. Articles to be
  1380. * published with the issue will be published on article publish.
  1381. * Published articles are set to "publish with issue" on issue
  1382. * unpublish.
  1383. *
  1384. * @param int $p_publicationId
  1385. * @param int $p_languageId
  1386. * @param int $p_issueNo
  1387. * @param int $p_publish
  1388. */
  1389. public static function OnIssuePublish($p_publicationId, $p_languageId,
  1390. $p_issueNo, $p_publish = true)
  1391. {
  1392. global $g_ado_db;
  1393. settype($p_publicationId, 'integer');
  1394. settype($p_languageId, 'integer');
  1395. settype($p_issueNo, 'integer');
  1396. $issueObj = new Issue($p_publicationId, $p_languageId, $p_issueNo);
  1397. if (!$issueObj->exists()) {
  1398. return false;
  1399. }
  1400. if (($issueObj->isPublished() && $p_publish)
  1401. || (!$issueObj->isPublished() && !$p_publish)) {
  1402. return false;
  1403. }
  1404. $fromState = $p_publish ? 'M' : 'Y';
  1405. $toState = $p_publish ? 'Y' : 'M';
  1406. $sql = "UPDATE Articles SET Published = '$toState' WHERE "
  1407. . "IdPublication = $p_publicationId AND IdLanguage = $p_languageId"
  1408. . " AND NrIssue = $p_issueNo AND Published = '$fromState'";
  1409. $res = $g_ado_db->Execute($sql);
  1410. CampCache::singleton()->clear('user');
  1411. return $res;
  1412. }
  1413. /**
  1414. * Return an Article object having the given number
  1415. * in the given publication, issue, section, language.
  1416. *
  1417. * @param int $p_articleNr
  1418. * The article number
  1419. * @param int $p_publicationId
  1420. * The publication identifier
  1421. * @param int $p_issueNr
  1422. * The issue number
  1423. * @param int $p_sectionNr
  1424. * The section number
  1425. * @param int $p_languageId
  1426. * The language identifier
  1427. *
  1428. * @return object|null
  1429. * An article object on success, null on failure
  1430. */
  1431. public static function GetByNumber($p_articleNr, $p_publicationId, $p_issueNr,
  1432. $p_sectionNr, $p_languageId)
  1433. {
  1434. global $g_ado_db;
  1435. $queryStr = 'SELECT * FROM Articles '
  1436. .'WHERE IdPublication='.$p_publicationId
  1437. .' AND NrIssue='.$p_issueNr
  1438. .' AND NrSection='.$p_sectionNr
  1439. .' AND IdLanguage='.$p_languageId
  1440. .' AND Number='.$p_articleNr;
  1441. $result = DbObjectArray::Create('Article', $queryStr);
  1442. return (is_array($result) && sizeof($result)) ? $result[0] : null;
  1443. } // fn GetByNumber
  1444. /**
  1445. * Return an array of article having the given name
  1446. * in the given publication / issue / section / language.
  1447. *
  1448. * @param string $p_name
  1449. * @param int $p_publicationId
  1450. * @param int $p_issueId
  1451. * @param int $p_sectionId
  1452. * @param int $p_languageId
  1453. *
  1454. * @return array
  1455. */
  1456. public static function GetByName($p_name, $p_publicationId = null, $p_issueId = null,
  1457. $p_sectionId = null, $p_languageId = null, $p_skipCache = false)
  1458. {
  1459. global $g_ado_db;
  1460. $queryStr = 'SELECT * FROM Articles';
  1461. $whereClause = array();
  1462. if (!is_null($p_publicationId)) {
  1463. $whereClause[] = "IdPublication=$p_publicationId";
  1464. }
  1465. if (!is_null($p_issueId)) {
  1466. $whereClause[] = "NrIssue=$p_issueId";
  1467. }
  1468. if (!is_null($p_sectionId)) {
  1469. $whereClause[] = "NrSection=$p_sectionId";
  1470. }
  1471. if (!is_null($p_languageId)) {
  1472. $whereClause[] = "IdLanguage=$p_languageId";
  1473. }
  1474. $whereClause[] = "Name='" . $g_ado_db->escape($p_name) . "'";
  1475. if (count($whereClause) > 0) {
  1476. $queryStr .= ' WHERE ' . implode(' AND ', $whereClause);
  1477. }
  1478. if (!$p_skipCache && CampCache::IsEnabled()) {
  1479. $paramsArray['get_by_name_where_clause'] = serialize($whereClause);
  1480. $cacheListObj = new CampCacheList($paramsArray, __METHOD__);
  1481. $articlesList = $cacheListObj->fetchFromCache();
  1482. if ($articlesList !== false && is_array($articlesList)) {
  1483. return $articlesList;
  1484. }
  1485. }
  1486. $articlesList = DbObjectArray::Create("Article", $queryStr);
  1487. // stores articles list in cache
  1488. if (!$p_skipCache && CampCache::IsEnabled()) {
  1489. $cacheListObj->storeInCache($articlesList);
  1490. }
  1491. return $articlesList;
  1492. } // fn GetByName
  1493. /**
  1494. * Return the number of unique (language-independant) articles
  1495. * according to the given parameters.
  1496. * @param int $p_publicationId
  1497. * @param int $p_issueId
  1498. * @param int $p_sectionId
  1499. * @return int
  1500. */
  1501. public static function GetNumUniqueArticles($p_publicationId = null, $p_issueId = null,
  1502. $p_sectionId = null)
  1503. {
  1504. global $g_ado_db;
  1505. $queryStr = 'SELECT COUNT(DISTINCT(Number)) FROM Articles';
  1506. $whereClause = array();
  1507. if (!is_null($p_publicationId)) {
  1508. $whereClause[] = "IdPublication=$p_publicationId";
  1509. }
  1510. if (!is_null($p_issueId)) {
  1511. $whereClause[] = "NrIssue=$p_issueId";
  1512. }
  1513. if (!is_null($p_sectionId)) {
  1514. $whereClause[] = "NrSection=$p_sectionId";
  1515. }
  1516. if (count($whereClause) > 0) {
  1517. $queryStr .= ' WHERE ' . implode(' AND ', $whereClause);
  1518. }
  1519. $result = $g_ado_db->GetOne($queryStr);
  1520. return $result;
  1521. } // fn GetNumUniqueArticles
  1522. /**
  1523. * Return an array of (array(Articles), int) where
  1524. * the array of articles are those written by the given user,
  1525. * within the given range, and the int is the total number of
  1526. * articles written by the user.
  1527. *
  1528. * @param int $p_userId
  1529. * @param int $p_start
  1530. * @param int $p_upperLimit
  1531. *
  1532. * @return array
  1533. */
  1534. public static function GetArticlesByUser($p_userId, $p_start = 0, $p_upperLimit = 20)
  1535. {
  1536. global $g_ado_db;
  1537. $queryStr = 'SELECT * FROM Articles '
  1538. ." WHERE IdUser=$p_userId"
  1539. .' ORDER BY Number DESC, IdLanguage '
  1540. ." LIMIT $p_start, $p_upperLimit";
  1541. $query = $g_ado_db->Execute($queryStr);
  1542. $articles = array();
  1543. while ($row = $query->FetchRow()) {
  1544. $tmpArticle = new Article();
  1545. $tmpArticle->fetch($row);
  1546. $articles[] = $tmpArticle;
  1547. }
  1548. $queryStr = 'SELECT COUNT(*) FROM Articles '
  1549. ." WHERE IdUser=$p_userId"
  1550. .' ORDER BY Number DESC, IdLanguage ';
  1551. $totalArticles = $g_ado_db->GetOne($queryStr);
  1552. return array($articles, $totalArticles);
  1553. } // fn GetArticlesByUser
  1554. /**
  1555. * Get a list of submitted articles.
  1556. * Return an array of two elements: (array(Articles), int).
  1557. * The first element is an array of submitted articles.
  1558. * The second element is the total number of submitted articles.
  1559. *
  1560. * @param int $p_start
  1561. * @param int $p_upperLimit
  1562. * @return array
  1563. */
  1564. public static function GetSubmittedArticles($p_start = 0, $p_upperLimit = 20)
  1565. {
  1566. global $g_ado_db;
  1567. $tmpArticle = new Article();
  1568. $columnNames = $tmpArticle->getColumnNames(true);
  1569. $queryStr = 'SELECT '.implode(", ", $columnNames)
  1570. .' FROM Articles '
  1571. ." WHERE Published = 'S' "
  1572. .' ORDER BY Number DESC, IdLanguage '
  1573. ." LIMIT $p_start, $p_upperLimit";
  1574. $query = $g_ado_db->Execute($queryStr);
  1575. $articles = array();
  1576. if ($query != false) {
  1577. while ($row = $query->FetchRow()) {
  1578. $tmpArticle = new Article();
  1579. $tmpArticle->fetch($row);
  1580. $articles[] = $tmpArticle;
  1581. }
  1582. }
  1583. $queryStr = 'SELECT COUNT(*) FROM Articles'
  1584. ." WHERE Published = 'S' "
  1585. .' ORDER BY Number DESC, IdLanguage ';
  1586. $totalArticles = $g_ado_db->GetOne($queryStr);
  1587. return array($articles, $totalArticles);
  1588. } // fn GetSubmittedArticles
  1589. /**
  1590. * Get the articles that have no publication/issue/section.
  1591. *
  1592. * @param int $p_start
  1593. * @param int $p_maxRows
  1594. * @return array
  1595. * An array of two elements:
  1596. * An array of articles and the total number of articles.
  1597. */
  1598. public static function GetUnplacedArticles($p_start = 0, $p_maxRows = 20)
  1599. {
  1600. global $g_ado_db;
  1601. $tmpArticle = new Article();
  1602. $columnNames = $tmpArticle->getColumnNames(true);
  1603. $queryStr = 'SELECT '.implode(", ", $columnNames)
  1604. .' FROM Articles '
  1605. ." WHERE IdPublication=0 AND NrIssue=0 AND NrSection=0 "
  1606. .' ORDER BY Number DESC, IdLanguage '
  1607. ." LIMIT $p_start, $p_maxRows";
  1608. $query = $g_ado_db->Execute($queryStr);
  1609. $articles = array();
  1610. if ($query != false) {
  1611. while ($row = $query->FetchRow()) {
  1612. $tmpArticle = new Article();
  1613. $tmpArticle->fetch($row);
  1614. $articles[] = $tmpArticle;
  1615. }
  1616. }
  1617. $queryStr = 'SELECT COUNT(*) FROM Articles'
  1618. ." WHERE IdPublication=0 AND NrIssue=0 AND NrSection=0 ";
  1619. $totalArticles = $g_ado_db->GetOne($queryStr);
  1620. return array($articles, $totalArticles);
  1621. } // fn GetUnplacedArticles
  1622. /**
  1623. * Get the list of all languages that articles have been written in.
  1624. * @return array
  1625. */
  1626. public static function GetAllLanguages()
  1627. {
  1628. $tmpLanguage = new Language();
  1629. $languageColumns = $tmpLanguage->getColumnNames(true);
  1630. $languageColumns = implode(",", $languageColumns);
  1631. $queryStr = 'SELECT DISTINCT(IdLanguage), '.$languageColumns
  1632. .' FROM Articles, Languages '
  1633. .' WHERE Articles.IdLanguage = Languages.Id';
  1634. $languages = DbObjectArray::Create('Language', $queryStr);
  1635. return $languages;
  1636. } // fn GetAllLanguages
  1637. /**
  1638. * Get a list of articles. You can be as specific or as general as you
  1639. * like with the parameters: e.g. specifying only p_publication will get
  1640. * you all the articles in a particular publication. Specifying all
  1641. * parameters will get you all the articles in a particular section with
  1642. * the given language.
  1643. *
  1644. * @param int $p_publicationId -
  1645. * The publication ID.
  1646. *
  1647. * @param int $p_issueNumber -
  1648. * The issue number.
  1649. *
  1650. * @param int $p_sectionNumber -
  1651. * The section number.
  1652. *
  1653. * @param int $p_languageId -
  1654. * The language ID.
  1655. *
  1656. * @param array $p_sqlOptions
  1657. *
  1658. * @param boolean $p_countOnly
  1659. *
  1660. * @return array
  1661. * Return an array of Article objects with indexes in sequential order
  1662. * starting from zero.
  1663. */
  1664. public static function GetArticles($p_publicationId = null, $p_issueNumber = null,
  1665. $p_sectionNumber = null, $p_languageId = null,
  1666. $p_sqlOptions = null, $p_countOnly = false,
  1667. $p_whereOptions = null)
  1668. {
  1669. global $g_ado_db;
  1670. $whereClause = array();
  1671. if (!is_null($p_publicationId)) {
  1672. $whereClause[] = "IdPublication=$p_publicationId";
  1673. }
  1674. if (!is_null($p_issueNumber)) {
  1675. $whereClause[] = "NrIssue=$p_issueNumber";
  1676. }
  1677. if (!is_null($p_sectionNumber)) {
  1678. $whereClause[] = "NrSection=$p_sectionNumber";
  1679. }
  1680. if (!is_null($p_languageId)) {
  1681. $whereClause[] = "IdLanguage=$p_languageId";
  1682. }
  1683. if (!is_null($p_whereOptions) && is_array($p_whereOptions)) {
  1684. foreach ($p_whereOptions as $key => $value) {
  1685. $whereClause[] = $value;
  1686. }
  1687. }
  1688. $selectStr = "*";
  1689. if ($p_countOnly) {
  1690. $selectStr = "COUNT(*)";
  1691. }
  1692. $queryStr = "SELECT $selectStr FROM Articles";
  1693. // Add the WHERE clause.
  1694. if ((count($whereClause) > 0)) {
  1695. $queryStr .= ' WHERE (' . implode(' AND ', $whereClause) .')';
  1696. }
  1697. if ($p_countOnly) {
  1698. $count = $g_ado_db->GetOne($queryStr);
  1699. return $count;
  1700. } else {
  1701. if (is_null($p_sqlOptions)) {
  1702. $p_sqlOptions = array();
  1703. }
  1704. if (!isset($p_sqlOptions['ORDER BY'])) {
  1705. $p_sqlOptions['ORDER BY'] = array("ArticleOrder" => "ASC",
  1706. "Number" => "DESC");
  1707. }
  1708. $queryStr = DatabaseObject::ProcessOptions($queryStr, $p_sqlOptions);
  1709. $articles = DbObjectArray::Create('Article', $queryStr);
  1710. return $articles;
  1711. }
  1712. } // fn GetArticles
  1713. /**
  1714. * Get a list of articles. You can be as specific or as general as you
  1715. * like with the parameters: e.g. specifying only p_publication will get
  1716. * you all the articles in a particular publication. Specifying all
  1717. * parameters will get you all the articles in a particular section with
  1718. * the given language.
  1719. *
  1720. * This function differs from GetArticles in that any LIMIT set
  1721. * in $p_sqlOptions will be interpreted as the number of articles to
  1722. * return regardless of how many times an article has been translated.
  1723. * E.g. an article translated three times would be counted as one
  1724. * article, but counted as three articles in GetArticles().
  1725. *
  1726. * @param int $p_publicationId -
  1727. * The publication ID.
  1728. *
  1729. * @param int $p_issueNumber -
  1730. * The issue number.
  1731. *
  1732. * @param int $p_sectionNumber -
  1733. * The section number.
  1734. *
  1735. * @param int $p_languageId -
  1736. * The language ID.
  1737. *
  1738. * @param int $p_preferredLanguage -
  1739. * If specified, list the articles in this language before others.
  1740. *
  1741. * @param array $p_sqlOptions
  1742. *
  1743. * @param boolean $p_countOnly
  1744. * Whether to run just the number of articles that match the
  1745. * search criteria.
  1746. *
  1747. * @return array
  1748. * Return an array of Article objects.
  1749. */
  1750. public static function GetArticlesGrouped($p_publicationId = null,
  1751. $p_issueNumber = null,
  1752. $p_sectionNumber = null,
  1753. $p_languageId = null,
  1754. $p_preferredLanguage = null,
  1755. $p_sqlOptions = null,
  1756. $p_countOnly = false)
  1757. {
  1758. global $g_ado_db;
  1759. // Constraints
  1760. $whereClause = array();
  1761. if (!is_null($p_publicationId)) {
  1762. $whereClause[] = "IdPublication=$p_publicationId";
  1763. }
  1764. if (!is_null($p_issueNumber)) {
  1765. $whereClause[] = "NrIssue=$p_issueNumber";
  1766. }
  1767. if (!is_null($p_sectionNumber)) {
  1768. $whereClause[] = "NrSection=$p_sectionNumber";
  1769. }
  1770. if (!is_null($p_languageId)) {
  1771. $whereClause[] = "IdLanguage=$p_languageId";
  1772. }
  1773. $selectStr = "DISTINCT(Number)";
  1774. if ($p_countOnly) {
  1775. $selectStr = "COUNT(DISTINCT(Number))";
  1776. }
  1777. // Get the list of unique article numbers
  1778. $queryStr1 = "SELECT $selectStr FROM Articles ";
  1779. if (count($whereClause) > 0) {
  1780. $queryStr1 .= ' WHERE '. implode(' AND ', $whereClause);
  1781. }
  1782. if ($p_countOnly) {
  1783. $count = $g_ado_db->GetOne($queryStr1);
  1784. return $count;
  1785. }
  1786. if (is_null($p_sqlOptions)) {
  1787. $p_sqlOptions = array();
  1788. }
  1789. if (!isset($p_sqlOptions['ORDER BY'])) {
  1790. $p_sqlOptions['ORDER BY'] = array("ArticleOrder" => "ASC",
  1791. "Number"=> "DESC");
  1792. }
  1793. $queryStr1 = DatabaseObject::ProcessOptions($queryStr1, $p_sqlOptions);
  1794. $uniqueArticleNumbers = $g_ado_db->GetCol($queryStr1);
  1795. // Get the articles
  1796. $queryStr2 = 'SELECT *';
  1797. // This causes the preferred language to be listed first.
  1798. if (!is_null($p_preferredLanguage)) {
  1799. $queryStr2 .= ", abs($p_preferredLanguage - IdLanguage) as LanguageOrder ";
  1800. }
  1801. $queryStr2 .= ' FROM Articles';
  1802. $uniqueRowsClause = '';
  1803. if (count($uniqueArticleNumbers) > 0) {
  1804. $uniqueRowsClause = '(Number=' .implode(' OR Number=', $uniqueArticleNumbers).')';
  1805. }
  1806. // Add the WHERE clause.
  1807. if ((count($whereClause) > 0) || ($uniqueRowsClause != '')) {
  1808. $queryStr2 .= ' WHERE ';
  1809. if (count($whereClause) > 0) {
  1810. $queryStr2 .= '(' . implode(' AND ', $whereClause) .')';
  1811. }
  1812. if ($uniqueRowsClause != '') {
  1813. if (count($whereClause) > 0) {
  1814. $queryStr2 .= ' AND ';
  1815. }
  1816. $queryStr2 .= $uniqueRowsClause;
  1817. }
  1818. }
  1819. // ORDER BY clause
  1820. if (!is_null($p_preferredLanguage)) {
  1821. $p_sqlOptions['ORDER BY']['LanguageOrder'] = "ASC";
  1822. $p_sqlOptions['ORDER BY']['IdLanguage'] = "ASC";
  1823. }
  1824. unset($p_sqlOptions['LIMIT']);
  1825. $queryStr2 = DatabaseObject::ProcessOptions($queryStr2, $p_sqlOptions);
  1826. $articles = DbObjectArray::Create('Article', $queryStr2);
  1827. return $articles;
  1828. } // fn GetUniqueArticles
  1829. /**
  1830. * Return the number of articles of the given type.
  1831. * @param string $p_type
  1832. * Article Type
  1833. * @return int
  1834. */
  1835. public static function GetNumArticlesOfType($p_type)
  1836. {
  1837. $articleType = new ArticleType($p_type);
  1838. if (!$articleType->exists()) {
  1839. return false;
  1840. }
  1841. return $articleType->getNumArticles();
  1842. } // fn GetNumArticlesOfType
  1843. /**
  1844. * Return an array of article objects of a certain type.
  1845. *
  1846. * @param string p_type
  1847. *
  1848. * @return array
  1849. */
  1850. public static function GetArticlesOfType($p_type)
  1851. {
  1852. global $g_ado_db;
  1853. $sql = "SELECT * FROM Articles WHERE Type = '"
  1854. . $g_ado_db->escape($p_type) . "'";
  1855. $articles = DbObjectArray::Create('Article', $sql);
  1856. return $articles;
  1857. } // fn GetArticlesOfType
  1858. /**
  1859. * Get the $p_max number of the most recently published articles.
  1860. * @param int $p_max
  1861. * @return array
  1862. */
  1863. public static function GetRecentArticles($p_max)
  1864. {
  1865. $queryStr = "SELECT * FROM Articles "
  1866. ." WHERE Published='Y'"
  1867. ." ORDER BY PublishDate DESC"
  1868. ." LIMIT $p_max";
  1869. $result = DbObjectArray::Create('Article', $queryStr);
  1870. return $result;
  1871. } // fn GetRecentArticles
  1872. /**
  1873. * Get the $p_max number of the most recently modified articles.
  1874. * @param int $p_max
  1875. * @return array
  1876. */
  1877. public static function GetRecentlyModifiedArticles($p_max)
  1878. {
  1879. $queryStr = "SELECT * FROM Articles "
  1880. ." ORDER BY time_updated DESC"
  1881. ." LIMIT $p_max";
  1882. $result = DbObjectArray::Create('Article', $queryStr);
  1883. return $result;
  1884. } // fn GetRecentlyModifiedArticles
  1885. /**
  1886. * @param int $p_publicationId
  1887. *
  1888. * @param int $p_languageId
  1889. *
  1890. *
  1891. * @return mixed
  1892. * array of issue publication dates
  1893. * null if query does not match any issue
  1894. */
  1895. public static function GetPublicationDates($p_publicationId,
  1896. $p_languageId,
  1897. $p_skipCache = false)
  1898. {
  1899. global $g_ado_db;
  1900. $queryStr = 'SELECT Number FROM Articles '
  1901. . 'WHERE IdPublication = ' . $p_publicationId . ' AND '
  1902. . 'IdLanguage = ' . $p_languageId . " AND Published = 'Y' "
  1903. . 'GROUP BY PublishDate ORDER BY PublishDate';
  1904. $rows = $g_ado_db->GetAll($queryStr);
  1905. $dates = array();
  1906. if (is_array($rows)) {
  1907. foreach ($rows as $row) {
  1908. $tmpObj = new Article($p_languageId, $row['Number']);
  1909. if ($tmpObj->exists()) {
  1910. $dates[] = $tmpObj->getPublishDate();
  1911. }
  1912. }
  1913. }
  1914. if (empty($dates)) {
  1915. return null;
  1916. }
  1917. return array_unique($dates);
  1918. } // fn GetPublicationDates
  1919. /**
  1920. * Unlock all articles by the given user.
  1921. * @param int $p_userId
  1922. * @return void
  1923. */
  1924. public static function UnlockByUser($p_userId)
  1925. {
  1926. global $g_ado_db;
  1927. $queryStr = 'UPDATE Articles SET LockUser=0, LockTime=0, time_updated=time_updated'
  1928. ." WHERE LockUser=$p_userId";
  1929. $g_ado_db->Execute($queryStr);
  1930. } // fn UnlockByUser
  1931. /**
  1932. * Returns an articles list based on the given parameters.
  1933. *
  1934. * @param array $p_parameters
  1935. * An array of ComparisonOperation objects
  1936. * @param string $p_order
  1937. * An array of columns and directions to order by
  1938. * @param integer $p_start
  1939. * The record number to start the list
  1940. * @param integer $p_limit
  1941. * The offset. How many records from $p_start will be retrieved.
  1942. * @param integer $p_count
  1943. * The total count of the elements; this count is computed without
  1944. * applying the start ($p_start) and limit parameters ($p_limit)
  1945. *
  1946. * @return array $articlesList
  1947. * An array of Article objects
  1948. */
  1949. public static function GetList(array $p_parameters, $p_order = null,
  1950. $p_start = 0, $p_limit = 0, &$p_count, $p_skipCache = false)
  1951. {
  1952. global $g_ado_db;
  1953. if (!$p_skipCache && CampCache::IsEnabled()) {
  1954. $paramsArray['parameters'] = serialize($p_parameters);
  1955. $paramsArray['order'] = (is_null($p_order)) ? 'null' : $p_order;
  1956. $paramsArray['start'] = $p_start;
  1957. $paramsArray['limit'] = $p_limit;
  1958. $cacheListObj = new CampCacheList($paramsArray, __METHOD__);
  1959. $articlesList = $cacheListObj->fetchFromCache();
  1960. if ($articlesList !== false && is_array($articlesList)) {
  1961. return $articlesList;
  1962. }
  1963. }
  1964. $matchAllTopics = false;
  1965. $hasTopics = array();
  1966. $hasNotTopics = array();
  1967. $selectClauseObj = new SQLSelectClause();
  1968. $otherTables = array();
  1969. // sets the name of the table for the this database object
  1970. $tmpArticle = new Article();
  1971. $articleTable = $tmpArticle->getDbTableName();
  1972. $selectClauseObj->setTable($articleTable);
  1973. unset($tmpArticle);
  1974. $languageId = null;
  1975. // parses the given parameters in order to build the WHERE part of
  1976. // the SQL SELECT sentence
  1977. foreach ($p_parameters as $param) {
  1978. $comparisonOperation = self::ProcessListParameters($param, $otherTables);
  1979. $leftOperand = strtolower($comparisonOperation['left']);
  1980. if ($leftOperand == 'idlanguage' && $comparisonOperation['symbol'] == '=') {
  1981. $languageId = $comparisonOperation['right'];
  1982. }
  1983. if (array_key_exists($leftOperand, Article::$s_regularParameters)) {
  1984. // regular article field, having a direct correspondent in the
  1985. // Article table fields
  1986. $whereCondition = Article::$s_regularParameters[$leftOperand]
  1987. . ' ' . $comparisonOperation['symbol']
  1988. . " '" . $g_ado_db->escape($comparisonOperation['right']) . "' ";
  1989. if ($leftOperand == 'reads'
  1990. && strstr($comparisonOperation['symbol'], '=') !== false
  1991. && $comparisonOperation['right'] == 0) {
  1992. $selectClauseObj->addConditionalWhere($whereCondition);
  1993. $isNullCond = Article::$s_regularParameters[$leftOperand]
  1994. . ' IS NULL';
  1995. $selectClauseObj->addConditionalWhere($isNullCond);
  1996. } else {
  1997. $selectClauseObj->addWhere($whereCondition);
  1998. }
  1999. } elseif ($leftOperand == 'matchalltopics') {
  2000. // set the matchAllTopics flag
  2001. $matchAllTopics = true;
  2002. } elseif ($leftOperand == 'topic') {
  2003. // add the topic to the list of match/do not match topics depending
  2004. // on the operator
  2005. $topic = new Topic($comparisonOperation['right']);
  2006. if ($topic->exists()) {
  2007. $topicIds = $topic->getSubtopics(true);
  2008. $topicIds[] = $comparisonOperation['right'];
  2009. if ($comparisonOperation['symbol'] == '=') {
  2010. $hasTopics[] = $topicIds;
  2011. } else {
  2012. $hasNotTopics[] = $topicIds;
  2013. }
  2014. }
  2015. } elseif ($leftOperand == 'author') {
  2016. $otherTables['Authors'] = array('fk_default_author_id'=>'id');
  2017. $author = Author::ReadName($comparisonOperation['right']);
  2018. $symbol = $comparisonOperation['symbol'];
  2019. $valModifier = strtolower($symbol) == 'like' ? '%' : '';
  2020. $firstName = $author['first_name'];
  2021. $lastName = $author['last_name'];
  2022. $whereCondition = "CONCAT(Authors.first_name, ' ', Authors.last_name) $symbol "
  2023. . "'$valModifier$firstName $lastName$valModifier'";
  2024. $selectClauseObj->addWhere($whereCondition);
  2025. } elseif ($leftOperand == 'search_phrase') {
  2026. $searchQuery = ArticleIndex::SearchQuery($comparisonOperation['right']);
  2027. $mainClauseConstraint = "(Articles.Number, Articles.IdLanguage) IN ( $searchQuery )";
  2028. $selectClauseObj->addWhere($mainClauseConstraint);
  2029. } else {
  2030. // custom article field; has a correspondence in the X[type]
  2031. // table fields
  2032. $sqlQuery = self::ProcessCustomField($comparisonOperation, $languageId);
  2033. if (!is_null($sqlQuery)) {
  2034. $whereCondition = "Articles.Number IN (\n$sqlQuery )";
  2035. $selectClauseObj->addWhere($whereCondition);
  2036. }
  2037. }
  2038. }
  2039. if (count($hasTopics) > 0 || count($hasNotTopics) > 0) {
  2040. $typeAttributes = ArticleTypeField::FetchFields(null, null, 'topic', false,
  2041. false, false, true, $p_skipCache);
  2042. }
  2043. if (count($hasTopics) > 0) {
  2044. if ($matchAllTopics) {
  2045. foreach ($hasTopics as $topicId) {
  2046. $sqlQuery = Article::BuildTopicSelectClause($topicId, $typeAttributes);
  2047. $whereCondition = "Articles.Number IN (\n$sqlQuery )";
  2048. $selectClauseObj->addWhere($whereCondition);
  2049. }
  2050. } else {
  2051. $sqlQuery = Article::BuildTopicSelectClause($hasTopics, $typeAttributes);
  2052. $whereCondition = "Articles.Number IN (\n$sqlQuery )";
  2053. $selectClauseObj->addWhere($whereCondition);
  2054. }
  2055. }
  2056. if (count($hasNotTopics) > 0) {
  2057. $sqlQuery = Article::BuildTopicSelectClause($hasNotTopics, $typeAttributes);
  2058. $whereCondition = "Articles.Number NOT IN (\n$sqlQuery )";
  2059. $selectClauseObj->addWhere($whereCondition);
  2060. }
  2061. // create the count clause object
  2062. $countClauseObj = clone $selectClauseObj;
  2063. if (!is_array($p_order)) {
  2064. $p_order = array();
  2065. }
  2066. // sets the ORDER BY condition
  2067. $p_order = array_merge($p_order, Article::$s_defaultOrder);
  2068. $order = Article::ProcessListOrder($p_order, $otherTables, $otherWhereConditions);
  2069. foreach ($order as $orderDesc) {
  2070. $orderColumn = $orderDesc['field'];
  2071. $orderDirection = $orderDesc['dir'];
  2072. $selectClauseObj->addOrderBy($orderColumn . ' ' . $orderDirection);
  2073. }
  2074. if (count($otherTables) > 0) {
  2075. foreach ($otherTables as $table=>$fields) {
  2076. if (isset($fields['__TABLE_ALIAS'])) {
  2077. $tableAlias = $fields['__TABLE_ALIAS'];
  2078. $tableJoin = "\n LEFT JOIN $table AS $tableAlias ON \n";
  2079. } else {
  2080. $tableAlias = $table;
  2081. $tableJoin = "\n LEFT JOIN $tableAlias ON \n";
  2082. }
  2083. $firstCondition = true;
  2084. foreach ($fields as $parent=>$child) {
  2085. if ($parent == '__TABLE_ALIAS') {
  2086. continue;
  2087. }
  2088. $condOperator = $firstCondition ? '' : 'AND ';
  2089. if ($parent == '__CONST') {
  2090. $constTable = $child['table'];
  2091. $constField = $child['field'];
  2092. $value = $child['value'];
  2093. $negate = isset($child['negate']) ? $child['negate'] : false;
  2094. if (is_null($value)) {
  2095. $operator = $negate ? 'IS NOT' : 'IS';
  2096. $value = 'NULL';
  2097. } else {
  2098. $operator = $negate ? '!=' : '=';
  2099. $value = "'" . $g_ado_db->escape($value) . "'";
  2100. }
  2101. $tableJoin .= " $condOperator`$constTable`.`$constField` $operator $value\n";
  2102. } else {
  2103. $tableJoin .= " $condOperator`$articleTable`.`$parent` = `$tableAlias`.`$child`\n";
  2104. }
  2105. $firstCondition = false;
  2106. }
  2107. $selectClauseObj->addJoin($tableJoin);
  2108. $countClauseObj->addJoin($tableJoin);
  2109. }
  2110. }
  2111. // other where conditions needed for certain order options
  2112. foreach ($otherWhereConditions as $whereCondition) {
  2113. $selectClauseObj->addWhere($whereCondition);
  2114. $countClauseObj->addWhere($whereCondition);
  2115. }
  2116. // gets the column list to be retrieved for the database table
  2117. $selectClauseObj->addColumn('Articles.Number');
  2118. $selectClauseObj->addColumn('Articles.IdLanguage');
  2119. $countClauseObj->addColumn('COUNT(*)');
  2120. // sets the LIMIT start and offset values
  2121. $selectClauseObj->setLimit($p_start, $p_limit);
  2122. // builds the SQL query
  2123. $selectQuery = $selectClauseObj->buildQuery();
  2124. $countQuery = $countClauseObj->buildQuery();
  2125. // runs the SQL query
  2126. $articles = $g_ado_db->GetAll($selectQuery);
  2127. if (is_array($articles)) {
  2128. $p_count = $g_ado_db->GetOne($countQuery);
  2129. // builds the array of Article objects
  2130. $articlesList = array();
  2131. foreach ($articles as $article) {
  2132. $articlesList[] = new Article($article['IdLanguage'], $article['Number']);
  2133. }
  2134. } else {
  2135. $articlesList = array();
  2136. $p_count = 0;
  2137. }
  2138. // stores articles list in cache
  2139. if (!$p_skipCache && CampCache::IsEnabled()) {
  2140. $cacheListObj->storeInCache($articlesList);
  2141. }
  2142. return $articlesList;
  2143. } // fn GetList
  2144. private static function ProcessCustomField(array $p_comparisonOperation, $p_languageId = null)
  2145. {
  2146. global $g_ado_db;
  2147. $fieldName = $p_comparisonOperation['left'];
  2148. $fieldParts = preg_split('/\./', $fieldName);
  2149. if (count($fieldParts) > 1) {
  2150. $fieldName = $fieldParts[1];
  2151. $articleType = $fieldParts[0];
  2152. $field = new ArticleTypeField($articleType, $fieldName);
  2153. if (!$field->exists()) {
  2154. return null;
  2155. }
  2156. $fields = array($field);
  2157. } else {
  2158. $articleType = null;
  2159. $fields = ArticleTypeField::FetchFields($fieldName, $articleType,
  2160. null, false, false, false, true, true);
  2161. if (count($fields) == 0) {
  2162. return null;
  2163. }
  2164. }
  2165. $queries = array();
  2166. foreach ($fields as $fieldObj) {
  2167. $query .= ' SELECT NrArticle FROM `X' . $fieldObj->getArticleType()
  2168. . '` WHERE ' . $fieldObj->getName() . ' '
  2169. . $p_comparisonOperation['symbol']
  2170. . " '" . $g_ado_db->escape($p_comparisonOperation['right']) . "'";
  2171. if (!is_null($p_languageId)) {
  2172. $query .= " AND IdLanguage = '" . $g_ado_db->escape($p_languageId) . "'";
  2173. }
  2174. $query .= "\n";
  2175. $queries[] = $query;
  2176. }
  2177. if (count($queries) == 0) {
  2178. return null;
  2179. }
  2180. return implode(" union\n", $queries);
  2181. }
  2182. /**
  2183. *
  2184. */
  2185. private static function ProcessListParameters($p_param, array &$p_otherTables = array())
  2186. {
  2187. $conditionOperation = array();
  2188. $leftOperand = strtolower($p_param->getLeftOperand());
  2189. $conditionOperation['left'] = $leftOperand;
  2190. switch ($leftOperand) {
  2191. case 'keyword':
  2192. $conditionOperation['symbol'] = 'LIKE';
  2193. $conditionOperation['right'] = '%'.$p_param->getRightOperand().'%';
  2194. break;
  2195. case 'onfrontpage':
  2196. $conditionOperation['right'] = ($p_param->getRightOperand() == 1) ? 'Y' : 'N';
  2197. break;
  2198. case 'onsection':
  2199. $conditionOperation['right'] = ($p_param->getRightOperand() == 1) ? 'Y' : 'N';
  2200. break;
  2201. case 'public':
  2202. $conditionOperation['right'] = ($p_param->getRightOperand() == 1) ? 'Y' : 'N';
  2203. break;
  2204. case 'matchalltopics':
  2205. $conditionOperation['symbol'] = '=';
  2206. $conditionOperation['right'] = 'true';
  2207. break;
  2208. case 'topic':
  2209. $conditionOperation['right'] = (string)$p_param->getRightOperand();
  2210. break;
  2211. case 'published':
  2212. if (strtolower($p_param->getRightOperand()) == 'true') {
  2213. $conditionOperation['symbol'] = '=';
  2214. $conditionOperation['right'] = 'Y';
  2215. }
  2216. break;
  2217. case 'workflow_status':
  2218. $conditionOperation['symbol'] = '=';
  2219. switch(strtolower($p_param->getRightOperand())) {
  2220. case 'new':
  2221. $conditionOperation['right'] = 'N';
  2222. break;
  2223. case 'published':
  2224. $conditionOperation['right'] = 'Y';
  2225. break;
  2226. case 'submitted':
  2227. $conditionOperation['right'] = 'S';
  2228. break;
  2229. case 'withissue':
  2230. $conditionOperation['right'] = 'M';
  2231. break;
  2232. }
  2233. break;
  2234. case 'issue_published':
  2235. $p_otherTables['Issues'] = array('IdPublication'=>'IdPublication',
  2236. 'NrIssue'=>'Number', 'IdLanguage'=>'IdLanguage');
  2237. $conditionOperation['symbol'] = '=';
  2238. $conditionOperation['right'] = 'Y';
  2239. break;
  2240. case 'reads':
  2241. $p_otherTables['RequestObjects'] = array('object_id'=>'object_id');
  2242. default:
  2243. $conditionOperation['right'] = (string)$p_param->getRightOperand();
  2244. break;
  2245. }
  2246. if (!isset($conditionOperation['symbol'])) {
  2247. $operatorObj = $p_param->getOperator();
  2248. $conditionOperation['symbol'] = $operatorObj->getSymbol('sql');
  2249. }
  2250. return $conditionOperation;
  2251. } // fn ProcessListParameters
  2252. /**
  2253. * Returns a select query for obtaining the articles that have the given topics
  2254. *
  2255. * @param array $p_TopicIds
  2256. * @param array $p_typeAttributes
  2257. * @param bool $p_negate
  2258. * @return string
  2259. */
  2260. private static function BuildTopicSelectClause(array $p_TopicIds,
  2261. array $p_typeAttributes,
  2262. $p_negate = false)
  2263. {
  2264. $topicIds = array();
  2265. foreach ($p_TopicIds as $topicId) {
  2266. if (is_array($topicId)) {
  2267. $topicIds = array_merge($topicIds, $topicId);
  2268. } else {
  2269. $topicIds[] = $topicId;
  2270. }
  2271. }
  2272. $notCondition = $p_negate ? ' NOT' : '';
  2273. if (!$p_negate) {
  2274. $selectClause = ' SELECT NrArticle FROM ArticleTopics WHERE TopicId'
  2275. . ' IN (' . implode(', ', $topicIds) . ")\n";
  2276. } else {
  2277. $selectClause = " SELECT a.Number\n"
  2278. . " FROM Articles AS a LEFT JOIN ArticleTopics AS at\n"
  2279. . " ON a.Number = at.NrArticle\n"
  2280. . " WHERE TopicId IS NULL OR TopicId NOT IN ("
  2281. . implode(', ', $topicIds) . ")\n";
  2282. }
  2283. foreach ($p_typeAttributes as $typeAttribute) {
  2284. $selectClause .= " UNION\n"
  2285. . " SELECT NrArticle FROM `X" . $typeAttribute->getArticleType() . '`'
  2286. . " WHERE " . $typeAttribute->getName()
  2287. . "$notCondition IN (" . implode(', ', $topicIds) . ")\n";
  2288. }
  2289. return $selectClause;
  2290. }
  2291. /**
  2292. * Performs a search against the article content using the given
  2293. * keywords. Returns the list of articles matching the given criteria.
  2294. *
  2295. * @param string $p_searchPhrase
  2296. * @param string $p_fieldName - may be 'title' or 'author'
  2297. * @param bool $p_matchAll - true if all keyword have to match
  2298. * @param array $p_constraints
  2299. * @param array $p_order
  2300. * @param int $p_start - return results starting from the given order number
  2301. * @param int $p_limit - return at most $p_limit rows
  2302. * @param int $p_count - sets $p_count to the total number of rows in the search
  2303. * @param bool $p_countOnly - if true returns only the total number of rows
  2304. * @return array
  2305. */
  2306. public static function SearchByKeyword($p_searchPhrase,
  2307. $p_matchAll = false,
  2308. array $p_constraints = array(),
  2309. array $p_order = array(),
  2310. $p_start = 0,
  2311. $p_limit = 0,
  2312. &$p_count,
  2313. $p_countOnly = false)
  2314. {
  2315. global $g_ado_db;
  2316. $selectClauseObj = new SQLSelectClause();
  2317. // set tables and joins between tables
  2318. $selectClauseObj->setTable('Articles');
  2319. if ($p_matchAll) {
  2320. $p_searchPhrase = '__match_all ' . $p_searchPhrase;
  2321. }
  2322. $mainClauseConstraint = "(Articles.Number, Articles.IdLanguage) IN ("
  2323. . ArticleIndex::SearchQuery($p_searchPhrase) . ")";
  2324. $selectClauseObj->addWhere($mainClauseConstraint);
  2325. $joinTables = array();
  2326. // set other constraints
  2327. foreach ($p_constraints as $constraint) {
  2328. $leftOperand = $constraint->getLeftOperand();
  2329. $operandAttributes = explode('.', $leftOperand);
  2330. if (count($operandAttributes) == 2) {
  2331. $table = trim($operandAttributes[0]);
  2332. if (strtolower($table) != 'articles') {
  2333. $joinTables[] = $table;
  2334. }
  2335. }
  2336. $symbol = $constraint->getOperator()->getSymbol('sql');
  2337. $rightOperand = "'" . $g_ado_db->escape($constraint->getRightOperand()) . "'";
  2338. $selectClauseObj->addWhere("$leftOperand $symbol $rightOperand");
  2339. }
  2340. foreach ($joinTables as $table) {
  2341. $selectClauseObj->addJoin("LEFT JOIN $table ON Articles.Number = $table.NrArticle");
  2342. }
  2343. // create the count clause object
  2344. $countClauseObj = clone $selectClauseObj;
  2345. // set the columns for the select clause
  2346. $selectClauseObj->addColumn('Articles.Number');
  2347. $selectClauseObj->addColumn('Articles.IdLanguage');
  2348. // set the order for the select clause
  2349. $p_order = count($p_order) > 0 ? $p_order : Article::$s_defaultOrder;
  2350. $order = Article::ProcessListOrder($p_order);
  2351. foreach ($order as $orderDesc) {
  2352. $orderField = $orderDesc['field'];
  2353. $orderDirection = $orderDesc['dir'];
  2354. $selectClauseObj->addOrderBy($orderField . ' ' . $orderDirection);
  2355. }
  2356. // sets the LIMIT start and offset values
  2357. $selectClauseObj->setLimit($p_start, $p_limit);
  2358. // set the column for the count clause
  2359. $countClauseObj->addColumn('COUNT(*)');
  2360. $articlesList = array();
  2361. if (!$p_countOnly) {
  2362. $selectQuery = $selectClauseObj->buildQuery();
  2363. $articles = $g_ado_db->GetAll($selectQuery);
  2364. foreach ($articles as $article) {
  2365. $articlesList[] = new Article($article['IdLanguage'], $article['Number']);
  2366. }
  2367. }
  2368. $countQuery = $countClauseObj->buildQuery();
  2369. $p_count = $g_ado_db->GetOne($countQuery);
  2370. return $articlesList;
  2371. }
  2372. /**
  2373. * Processes an order directive coming from template tags.
  2374. *
  2375. * @param array $p_order
  2376. * The array of order directives in the format:
  2377. * array('field'=>field_name, 'dir'=>order_direction)
  2378. * field_name can take one of the following values:
  2379. * bynumber, byname, bydate, bycreationdate, bypublishdate,
  2380. * bypublication, byissue, bysection, bylanguage, bysectionorder,
  2381. * bypopularity, bycomments
  2382. * order_direction can take one of the following values:
  2383. * asc, desc
  2384. *
  2385. * @return array
  2386. * The array containing processed values of the condition
  2387. */
  2388. private static function ProcessListOrder(array $p_order, array &$p_otherTables = array(),
  2389. &$p_whereConditions = array())
  2390. {
  2391. if (!is_array($p_whereConditions)) {
  2392. $p_whereConditions = array();
  2393. }
  2394. $order = array();
  2395. foreach ($p_order as $orderDesc) {
  2396. $field = $orderDesc['field'];
  2397. $direction = $orderDesc['dir'];
  2398. $dbField = null;
  2399. switch (strtolower($field)) {
  2400. case 'bynumber':
  2401. $dbField = 'Articles.Number';
  2402. break;
  2403. case 'byname':
  2404. $dbField = 'Articles.Name';
  2405. break;
  2406. case 'bydate':
  2407. case 'bycreationdate':
  2408. $dbField = 'Articles.UploadDate';
  2409. break;
  2410. case 'bypublishdate':
  2411. $dbField = 'Articles.PublishDate';
  2412. break;
  2413. case 'bypublication':
  2414. $dbField = 'Articles.IdPublication';
  2415. break;
  2416. case 'byissue':
  2417. $dbField = 'Articles.NrIssue';
  2418. break;
  2419. case 'bysection':
  2420. $dbField = 'Articles.NrSection';
  2421. break;
  2422. case 'bylanguage':
  2423. $dbField = 'Articles.IdLanguage';
  2424. break;
  2425. case 'bysectionorder':
  2426. $dbField = 'Articles.ArticleOrder';
  2427. break;
  2428. case 'bypopularity':
  2429. $dbField = 'RequestObjects.request_count';
  2430. $p_otherTables['RequestObjects'] = array('object_id'=>'object_id');
  2431. break;
  2432. case 'bycomments':
  2433. $dbField = 'comments_counter.comments_count';
  2434. $joinTable = "(SELECT COUNT(*) AS comments_count, fk_article_number, fk_language_id \n"
  2435. . " FROM ArticleComments \n"
  2436. . ' GROUP BY fk_article_number, fk_language_id)';
  2437. $p_otherTables[$joinTable] = array('__TABLE_ALIAS'=>'comments_counter',
  2438. 'Number'=>'fk_article_number',
  2439. 'IdLanguage'=>'fk_language_id');
  2440. break;
  2441. case 'bylastcomment':
  2442. $dbField = 'comment_ids.last_comment_id';
  2443. $joinTable = "(SELECT MAX(fk_comment_id) AS last_comment_id, fk_article_number, fk_language_id \n"
  2444. . " FROM ArticleComments AS ac LEFT JOIN phorum_messages AS pm \n"
  2445. . " ON ac.fk_comment_id = pm.message_id \n"
  2446. . " WHERE pm.status = 2 AND ac.is_first = false"
  2447. . " GROUP BY fk_article_number, fk_language_id)";
  2448. $p_otherTables[$joinTable] = array('__TABLE_ALIAS'=>'comment_ids',
  2449. 'Number'=>'fk_article_number',
  2450. 'IdLanguage'=>'fk_language_id');
  2451. $p_whereConditions[] = "`comment_ids`.`last_comment_id` IS NOT NULL";
  2452. break;
  2453. }
  2454. if (!is_null($dbField)) {
  2455. $direction = !empty($direction) ? $direction : 'asc';
  2456. }
  2457. $order[] = array('field'=>$dbField, 'dir'=>$direction);
  2458. }
  2459. return $order;
  2460. }
  2461. /**
  2462. * Performs a search against the given article field using the given
  2463. * keywords. Returns the list of articles matching the given criteria.
  2464. *
  2465. * @param array $p_keywords
  2466. * @param string $p_fieldName - may be 'title' or 'author'
  2467. * @param bool $p_matchAll - true if all keyword have to match
  2468. * @param array $p_constraints
  2469. * @param array $p_order
  2470. * @param int $p_start - return results starting from the given order number
  2471. * @param int $p_limit - return at most $p_limit rows
  2472. * @param int $p_count - sets $p_count to the total number of rows in the search
  2473. * @param bool $p_countOnly - if true returns only the total number of rows
  2474. * @return array
  2475. */
  2476. public static function SearchByField(array $p_keywords,
  2477. $p_fieldName,
  2478. $p_matchAll = false,
  2479. array $p_constraints = array(),
  2480. array $p_order = array(),
  2481. $p_start = 0,
  2482. $p_limit = 0,
  2483. &$p_count,
  2484. $p_countOnly = false)
  2485. {
  2486. global $g_ado_db;
  2487. static $searchFields = array(
  2488. 'title'=>array('table_fields'=>array('Name'),
  2489. 'table'=>'Articles'),
  2490. 'author'=>array('table_fields'=>array('first_name', 'last_name'),
  2491. 'table'=>'Authors',
  2492. 'join_fields'=>array('fk_default_author_id'=>'id')));
  2493. $fieldName = strtolower($p_fieldName);
  2494. if (!array_key_exists($fieldName, $searchFields)) {
  2495. return false;
  2496. }
  2497. $selectClauseObj = new SQLSelectClause();
  2498. // set tables and joins between tables
  2499. $selectClauseObj->setTable('Articles');
  2500. $joinTable = $searchFields[$fieldName]['table'];
  2501. if ($joinTable != 'Articles') {
  2502. $selectClauseObj->addTableFrom($joinTable);
  2503. foreach ($searchFields[$fieldName]['join_fields'] as
  2504. $leftJoinField=>$rightJoinField) {
  2505. $selectClauseObj->addWhere("`Articles`.`$leftJoinField` = "
  2506. . "`$joinTable`.`$rightJoinField`");
  2507. }
  2508. }
  2509. foreach ($searchFields[$fieldName]['table_fields'] as $matchField) {
  2510. $matchFields[] = "`$joinTable`.`$matchField`";
  2511. }
  2512. $matchCond = 'MATCH (' . implode(', ', $matchFields) . ") AGAINST ('";
  2513. foreach ($p_keywords as $keyword) {
  2514. $matchCond .= ($p_matchAll ? '+' : '') . $g_ado_db->escape($keyword) . ' ';
  2515. }
  2516. $matchCond .= "' IN BOOLEAN MODE)";
  2517. $selectClauseObj->addWhere($matchCond);
  2518. $joinTables = array();
  2519. // set other constraints
  2520. foreach ($p_constraints as $constraint) {
  2521. $leftOperand = $constraint->getLeftOperand();
  2522. $operandAttributes = explode('.', $leftOperand);
  2523. if (count($operandAttributes) == 2) {
  2524. $table = trim($operandAttributes[0]);
  2525. if (strtolower($table) != 'articles') {
  2526. $joinTables[] = $table;
  2527. }
  2528. }
  2529. $symbol = $constraint->getOperator()->getSymbol('sql');
  2530. $rightOperand = "'" . $g_ado_db->escape($constraint->getRightOperand()) . "'";
  2531. $selectClauseObj->addWhere("$leftOperand $symbol $rightOperand");
  2532. }
  2533. foreach ($joinTables as $table) {
  2534. $selectClauseObj->addJoin("LEFT JOIN $table ON Articles.Number = $table.NrArticle");
  2535. }
  2536. // create the count clause object
  2537. $countClauseObj = clone $selectClauseObj;
  2538. // set the columns for the select clause
  2539. $selectClauseObj->addColumn('Articles.Number');
  2540. $selectClauseObj->addColumn('Articles.IdLanguage');
  2541. $selectClauseObj->addColumn($matchCond . ' AS score');
  2542. // set the order for the select clause
  2543. $p_order = count($p_order) > 0 ? $p_order : Article::$s_defaultOrder;
  2544. $order = Article::ProcessListOrder($p_order);
  2545. $selectClauseObj->addOrderBy('score DESC');
  2546. foreach ($order as $orderDesc) {
  2547. $orderField = $orderDesc['field'];
  2548. $orderDirection = $orderDesc['dir'];
  2549. $selectClauseObj->addOrderBy($orderField . ' ' . $orderDirection);
  2550. }
  2551. // sets the LIMIT start and offset values
  2552. $selectClauseObj->setLimit($p_start, $p_limit);
  2553. // set the column for the count clause
  2554. $countClauseObj->addColumn('COUNT(*)');
  2555. $articlesList = array();
  2556. if (!$p_countOnly) {
  2557. $selectQuery = $selectClauseObj->buildQuery();
  2558. $articles = $g_ado_db->GetAll($selectQuery);
  2559. foreach ($articles as $article) {
  2560. $articlesList[] = new Article($article['IdLanguage'], $article['Number']);
  2561. }
  2562. }
  2563. $countQuery = $countClauseObj->buildQuery();
  2564. $p_count = $g_ado_db->GetOne($countQuery);
  2565. return $articlesList;
  2566. }
  2567. /**
  2568. * Processes an order directive for the article translations list.
  2569. *
  2570. * @param array $p_order
  2571. * The array of order directives in the format:
  2572. * array('field'=>field_name, 'dir'=>order_direction)
  2573. * field_name can take one of the following values:
  2574. * bynumber, byname, byenglish_name, bycode
  2575. * order_direction can take one of the following values:
  2576. * asc, desc
  2577. *
  2578. * @return array
  2579. * The array containing processed values of the condition
  2580. */
  2581. private static function ProcessLanguageListOrder(array $p_order)
  2582. {
  2583. $order = array();
  2584. foreach ($p_order as $orderDesc) {
  2585. $field = $orderDesc['field'];
  2586. $direction = $orderDesc['dir'];
  2587. $dbField = null;
  2588. switch (strtolower($field)) {
  2589. case 'bynumber':
  2590. $dbField = 'Languages.Id';
  2591. break;
  2592. case 'byname':
  2593. $dbField = 'Languages.OrigName';
  2594. break;
  2595. case 'byenglish_name':
  2596. $dbField = 'Languages.Name';
  2597. break;
  2598. case 'bycode':
  2599. $dbField = 'Languages.Code';
  2600. break;
  2601. }
  2602. if (!is_null($dbField)) {
  2603. $direction = !empty($direction) ? $direction : 'asc';
  2604. }
  2605. $order[] = array('field'=>$dbField, 'dir'=>$direction);
  2606. }
  2607. return $order;
  2608. }
  2609. } // class Article
  2610. ?>