PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/library/News/Article.php

https://bitbucket.org/richardingham/jcsu-files
PHP | 441 lines | 264 code | 85 blank | 92 comment | 47 complexity | ca276c33caee98c671f6325d38ff30f2 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. class News_Article extends Data_Store {
  3. // id: news/2008/01/01/my-news-article
  4. protected $_data = array(
  5. 'type' => 'News_Article',
  6. 'title' => '', // Max 100 chars
  7. 'name' => '', // Derived so max 100 chars
  8. 'date' => false, // DateTime object
  9. 'approved' => false,
  10. 'trash' => false,
  11. 'author_id' => '',
  12. 'author_name' => '',
  13. 'author_email' => '',
  14. 'author_ip' => '',
  15. 'author_host' => '',
  16. 'text_id' => '',
  17. 'has_more' => false
  18. );
  19. /**
  20. * (Unparsed) content of the article.
  21. *
  22. * @see {News_Article::setText()}
  23. * @see {News_Article::getText()}
  24. *
  25. * @var string
  26. */
  27. protected $_text = null;
  28. /**
  29. * Sets up this object with data from the specified article, or if the
  30. * argument is NULL, sets up a new News_Article object.
  31. *
  32. * @param string|NULL $id Article ID (format: news/2000/01/01/article-name)
  33. */
  34. public function __construct ($id = null) {
  35. $this->_indexes = array('News_Article_Index');
  36. parent::__construct($id);
  37. // Workaround until php 5.3, when you'll be able to serialize DateTime objects
  38. if (!($this->_data['date'] instanceof DateTime)) {
  39. $this->_data['date'] = date_create((string) $this->_data['date']);
  40. //$this->_data['date']->setTimezone(new DateTimeZone('UTC'));
  41. }
  42. }
  43. /**
  44. * Checks whether a particular article has existed previously,
  45. * but has been deleted. (Checks for a 'gone' flag);
  46. *
  47. * @param string $id Article id
  48. * @return boolean
  49. */
  50. public static function isGone ($id) {
  51. return self::hasShadow($id, 'gone');
  52. }
  53. /**
  54. * Checks whether a particular artricle has been moved.
  55. * If so, returns the new id
  56. *
  57. * @param string $id Article id
  58. * @return FALSE|string
  59. */
  60. public static function isRedirect ($id) {
  61. return self::getShadow($id, 'redirect');
  62. }
  63. /**
  64. * Return an article by ID
  65. *
  66. * @param string $id Article id
  67. * @return News_Article
  68. */
  69. public static function find ($id) {
  70. return new self($id);
  71. }
  72. /**
  73. * Returns an internal ID corresponding to the current data.
  74. *
  75. * @return string
  76. */
  77. protected function _makeId () {
  78. $date = clone $this->_data['date'];
  79. $date->setTimezone(new DateTimeZone('UTC'));
  80. return 'news/' . $date->format('Y/m/d/') . $this->_data['name'];
  81. }
  82. /**
  83. * Returns a unique name corresponding to the current data,
  84. * to create a unique ID.
  85. *
  86. * @return string
  87. */
  88. protected function _makeUniqueName () {
  89. $old_name = Inflector::urlize($this->_getOriginalData('title'));
  90. $new_name = Inflector::urlize($this->_data['title']);
  91. // Workaround until php 5.3 (change to clone)
  92. $old_date = date_create($this->_getOriginalData('date'));
  93. $old_date->setTimezone(new DateTimeZone('UTC'));
  94. $old_date = $old_date->format('Y/m/d/');
  95. $new_date = clone $this->_data['date'];
  96. $new_date->setTimezone(new DateTimeZone('UTC'));
  97. $new_date = $new_date->format('Y/m/d/');
  98. if ($this->_new || !($old_name == $new_name && $old_date == $new_date)) {
  99. $dir = self::$_dataDir . $this->_makeId();
  100. if ($this->_data['name'] != '') {
  101. $dir = dirname($dir) . '/';
  102. }
  103. $files = glob($dir . $new_name . '*');
  104. $count = 0;
  105. if (is_array($files)) {
  106. foreach ($files as $file) {
  107. if (preg_match('#^' . $new_name . '(?:-(\d+))?$#i', basename($file), $matches)) {
  108. if (count($matches) > 1) {
  109. list(,$number) = $matches;
  110. } else {
  111. $number = 1;
  112. }
  113. $count = max($count, max(1, (int) $number));
  114. }
  115. }
  116. }
  117. if ($count) {
  118. $new_name .= '-' . ($count + 1);
  119. }
  120. }
  121. return $new_name;
  122. }
  123. public function setApproved ($val) {
  124. $this->_data['approved'] = (bool) $val;
  125. return $this;
  126. }
  127. public function getText () {
  128. if ($this->_text === null) {
  129. $name = $this->_data['text_id'];
  130. if (!empty($name) && $this->hasAttachment($name)) {
  131. $this->_text = $this->getAttachment($name);
  132. } else {
  133. $this->_text = '';
  134. $this->_modified['text'] = true;
  135. }
  136. }
  137. return $this->_text;
  138. }
  139. public function setText ($value) {
  140. if ($this->getText() != $value) {
  141. $this->_text = $value;
  142. $this->set('has_more', (stripos($value, '<hr') !== false));
  143. $this->_modified['text'] = true;
  144. } else {
  145. unset($this->_modified['text']);
  146. }
  147. return $this;
  148. }
  149. public function getArticle ($email = false) {
  150. return $this->_getContent('article', $email);
  151. }
  152. public function getSummary ($email = false) {
  153. return $this->_getContent('summary', $email);
  154. }
  155. protected function _getContent ($method, $email) {
  156. $cache_id = $this->_id . '/' . $method . '-' . ($email ? 'email' : 'noemail');
  157. if (isset(self::$_cache[$cache_id])) {
  158. return self::$_cache[$cache_id];
  159. } else {
  160. $parsed = News_Parser::process($this->getText(), $method, $email);
  161. if (!$this->_new) {
  162. self::$_cache[$cache_id] = $parsed;
  163. }
  164. return $parsed;
  165. }
  166. }
  167. public function setTitle ($value) {
  168. $this->_data['title'] = substr($value, 0, 100);
  169. if (!$this->_new) {
  170. $this->_id = $this->_makeId();
  171. $this->_modified['id'] = true;
  172. }
  173. return $this;
  174. }
  175. public function setTrash ($val) {
  176. $this->_data['trash'] = (bool) $val;
  177. return $this;
  178. }
  179. public function setDate (DateTime $value) {
  180. $this->_data['date'] = $value;
  181. if (!$this->_new) {
  182. $this->_id = $this->_makeId();
  183. $this->_modified['id'] = true;
  184. }
  185. return $this;
  186. }
  187. public function save () {
  188. if ($this->_data['date'] == null) {
  189. $this->set('date', date_create());
  190. }
  191. if (trim($this->_data['title']) == '') {
  192. $this->set('title', 'Untitled Article');
  193. }
  194. if ($this->_new || isset($this->_modified['id'])) {
  195. $this->set('name', $this->_makeUniqueName());
  196. $this->_id = $this->_makeId();
  197. }
  198. if ($this->_new) {
  199. $this->_id = $this->_makeId();
  200. }
  201. if (isset($this->_modified['text'])) {
  202. $this->_data['text_id'] = 'text.' . date('YmdHis');
  203. $this->addAttachment($this->_data['text_id'], $this->_text); // TODO: gz
  204. self::$_cache->delete($this->_getOriginalData('id') . '/article-email');
  205. self::$_cache->delete($this->_getOriginalData('id') . '/article-noemail');
  206. self::$_cache->delete($this->_getOriginalData('id') . '/summary-email');
  207. self::$_cache->delete($this->_getOriginalData('id') . '/summary-noemail');
  208. }
  209. if (count($this->_modifiedAttachments) + count($this->_deletedAttachments) > 0) {
  210. self::$_cache->delete($this->_getOriginalData('id') . '/commentcount');
  211. }
  212. $original_id = (isset($this->_modified['id'])) ? $this->_getOriginalData('id') : false;
  213. // Workaround until php 5.3, when you'll be able to serialize DateTime objects
  214. //$this->_data['date']->setTimezone(new DateTimeZone('UTC'));
  215. $this->_data['date'] = $this->_data['date']->format(DateTime::ISO8601);
  216. parent::save();
  217. // Workaround until php 5.3, when you'll be able to serialize DateTime objects
  218. $this->_data['date'] = date_create((string) $this->_data['date']);
  219. // Create redirect if moved.
  220. if (false !== $original_id) {
  221. self::addShadow($original_id, 'redirect', $this->_id);
  222. }
  223. return $this;
  224. }
  225. /**
  226. * Deletes this article; sets 'gone' flag
  227. *
  228. * @return void
  229. */
  230. public function delete () {
  231. parent::delete();
  232. // Delete emails
  233. /* $filenames = glob(self::$_dataDir . $this->_getOriginalData('id') . '/resource-image.*');
  234. if ($filenames !== false) {
  235. foreach ($filenames as $filename) {
  236. try {
  237. $email = Resource_Email::find(file_get_contents($filename));
  238. $email->delete();
  239. } catch (Exception $e) {}
  240. }
  241. } */
  242. // Create deletion notice.
  243. self::addShadow($this->_getOriginalData('id'), 'gone');
  244. }
  245. public function addComment ($data) {
  246. $time = date_create();
  247. // Accept ids passed in to enable undo delete comment.
  248. if (!isset($data['id'])) {
  249. $time->setTimezone(new DateTimeZone('UTC'));
  250. $id = $time->format('YmdHis');
  251. } else {
  252. $id = $data['id'];
  253. }
  254. $text = isset($data['text']) ? $data['text'] : '';
  255. $comment_data = array(
  256. 'id' => $id,
  257. 'author_id' => isset($data['author_id']) ? $data['author_id'] : '',
  258. 'author_name' => isset($data['author_name']) ? $data['author_name'] : '',
  259. 'author_email' => isset($data['author_email']) ? $data['author_email'] : '',
  260. 'author_ip' => isset($data['author_ip']) ? $data['author_ip'] : '',
  261. 'author_host' => isset($data['author_host']) ? $data['author_host'] : '',
  262. 'date' => isset($data['date']) ? $data['date'] : $time->format(DateTime::ISO8601),
  263. 'text' => $text,
  264. 'text_email' => News_Parser::processComment($text, true),
  265. 'text_noemail' => News_Parser::processComment($text, false)
  266. );
  267. // This assumes that the comment ids are integers.
  268. // (Which they should be!)
  269. while (true) {
  270. try {
  271. $this->addAttachment('comment.' . $id, serialize($comment_data));
  272. break;
  273. } catch (Data_Store_AttachmentExistsException $e) {
  274. $comment_data['id'] = ++$id;
  275. }
  276. }
  277. return $comment_data;
  278. }
  279. /**
  280. * Deletes a particular comment. The comment id is the time it was posted,
  281. * in "YmdHis" format.
  282. *
  283. * @param string $comment_id
  284. * @return News_Article Fluent Interface
  285. */
  286. public function deleteComment ($comment_id) {
  287. $name = 'comment.' . $comment_id;
  288. if ($this->hasAttachment($name)) {
  289. $this->deleteAttachment($name);
  290. } else {
  291. throw new Exception ('Comment ' . $this->_id . '/' . $comment_id . ' does not exist');
  292. }
  293. return $this;
  294. }
  295. /**
  296. * Gets the data for a particular comment.
  297. * The comment id is the time it was posted, in "YmdHis" format.
  298. *
  299. * @param string $comment_id
  300. * @return array
  301. */
  302. public function getComment ($comment_id) {
  303. $name = 'comment.' . $comment_id;
  304. if ($this->hasAttachment($name)) {
  305. $comment = unserialize($this->getAttachment($name));
  306. $comment['date'] = date_create($comment['date']);
  307. } else {
  308. throw new Exception ('Comment ' . $this->_id . '/' . $comment_id . ' does not exist');
  309. }
  310. return $comment;
  311. }
  312. /**
  313. * Returns all of the comments for this article
  314. *
  315. * @return Array
  316. */
  317. public function getComments () {
  318. $comments = array();
  319. $filenames = glob(self::$_dataDir . $this->_getOriginalData('id') . '/comment.*');
  320. if ($filenames !== false) {
  321. foreach ($filenames as $filename) {
  322. $comment = unserialize(file_get_contents($filename));
  323. $comment['date'] = date_create($comment['date']);
  324. $comments[] = $comment;
  325. }
  326. }
  327. return $comments;
  328. }
  329. public function getCommentCount () {
  330. if ($this->_new) {
  331. return 0;
  332. }
  333. $cache_id = $this->_getOriginalData('id') . '/commentcount';
  334. if (isset(self::$_cache[$cache_id])) {
  335. return self::$_cache[$cache_id];
  336. } else {
  337. $comments = glob(self::$_dataDir . $this->_getOriginalData('id') . '/comment.*');
  338. $count = (int) ($comments === false ? 0 : count($comments));
  339. self::$_cache[$cache_id] = $count;
  340. }
  341. return $count;
  342. }
  343. public static function getAll () {
  344. $len = strlen(self::$_dataDir);
  345. $files = glob(self::$_dataDir . 'news/*/*/*/*/data');
  346. $return = array();
  347. if ($files !== false) {
  348. foreach ((array) $files as $file) {
  349. $id = substr(substr($file, $len), 0, -5);
  350. $return[$id] = new self($id);
  351. }
  352. }
  353. return $return;
  354. }
  355. }