lib/News/Feed.php
<?php
/**
* An individual users news feed, with articles filtered by their preferences
* @since Version 3.8.7
* @package Railpage
* @author Michael Greenhill
*/
namespace Railpage\News;
use Exception;
use DateTime;
use DateTimeZone;
use DateInterval;
use Railpage\Url;
use Railpage\Module;
use Railpage\Users\User;
use Foolz\SphinxQL\SphinxQL;
use Foolz\SphinxQL\Connection;
/**
* News feed
*/
class Feed extends News {
/**
* Filter: Unread articles only
* @since Version 3.10.0
* @const string FILTER_UNREAD
*/
const FILTER_UNREAD = "unread";
/**
* Filter: Read articles only
* @since Version 3.10.0
* @const string FILTER_READ
*/
const FILTER_READ = "read";
/**
* Filter: Articles published in the last 30 days
* @since Version 3.10.0
* @const string FILTER_LAST_30_DAYS
*/
const FILTER_LAST_30_DAYS = "age:30days";
/**
* Additional filters to apply to this feed
* @since Version 3.10.0
* @var array $filters
*/
private $filters;
/**
* Keywords to filter on
* @since Version 3.8.7
* @var array $filter_words
*/
public $filter_words;
/**
* Topic IDs to filter on
* @since Version 3.8.7
* @var array $filter_topics
*/
public $filter_topics;
/**
* Find the news articles matching this users filters
* @since Version 3.8.7
* @yield \Railpage\News\Article
* @param int $offset
* @param int $limit
* @param string $orderby
*/
public function findArticles($offset = 0, $limit = 25, $orderby = "story_time_unix") {
if (empty($this->filter_topics) || empty($this->filter_words)) {
$this->getFilters();
}
if (!$limit) {
$limit = $this->User->items_per_page;
}
$Sphinx = $this->getSphinx();
$query = $Sphinx->select("*")
->from("idx_news_article")
->orderBy($orderby, "DESC")
->limit($offset, $limit)
->where("story_active", "=", 1);
if (!empty($this->filter_topics) && count($this->filter_topics)) {
$query->where("topic_id", "IN", $this->filter_topics);
}
/**
* Unread items only
*/
if ($this->hasFilter(self::FILTER_UNREAD) !== false) {
$article_ids = [];
foreach (Utility\ArticleUtility::getReadArticlesForUser($this->User) as $row) {
$article_ids[] = intval($row['story_id']);
}
if (count($article_ids)) {
$query->where("story_id", "NOT IN", $article_ids);
}
}
/**
* Read items only
*/
if ($this->hasFilter(self::FILTER_READ) !== false) {
$article_ids = [];
foreach (Utility\ArticleUtility::getReadArticlesForUser($this->User) as $row) {
$article_ids[] = $row['story_id'];
}
if (count($article_ids)) {
$query->where("story_id", "IN", $article_ids);
}
}
/**
* Matching keywords
*/
if (!empty($this->filter_words) && count($this->filter_words) && !(count($this->filter_words) === 1 && $this->filter_words[0] == "")) {
$words = implode(" | ", $this->filter_words);
$query->match("story_text", $words)->option("ranker", "matchany");
}
/**
* Age: newer than 30 days
*/
if ($this->hasFilter(self::FILTER_LAST_30_DAYS) !== false) {
$query->where("story_time_unix", "BETWEEN", array(strtotime("30 days ago"), time()));
}
$matches = $query->execute();
foreach ($matches as $id => $row) {
$row['url'] = sprintf("/news/s/%s", $row['story_slug']);
$matches[$id] = $row;
}
return $matches;
}
/**
* Add a filter to the search
* @since Version 3.10.0
* @param string $filter
* @return \Railpage\News\Feed
*/
public function addFilter($filter) {
if (!$this->hasFilter($filter)) {
$this->filters[] = $filter;
}
if ($filter == self::FILTER_UNREAD) {
$key = $this->hasFilter(self::FILTER_READ);
if ($key !== false) {
unset($this->filters[$key]);
}
}
if ($filter == self::FILTER_READ) {
$key = $this->hasFilter(self::FILTER_UNREAD);
if ($key !== false) {
unset($this->filters[$key]);
}
}
return $this;
}
/**
* Check if we have a particular filter set
* @since Version 3.10.0
* @param string $filter
* @return boolean
*/
public function hasFilter($filter) {
if (!in_array($filter, $this->filters)) {
return false;
}
return array_search($filter, $this->filters);
}
/**
* Get filters from the database
* @since Version 3.8.7
* @return \Railpage\News\Feed
*/
public function getFilters() {
if (!$this->User instanceof User) {
throw new Exception("Cannot get filters for news feed because no valid user was specified");
}
if (!$row = $this->Memcached->fetch(sprintf("rp:news.feed=%d", $this->User->id))) {
if ($row = $this->db->fetchRow("SELECT keywords, topics FROM news_feed WHERE user_id = ?", $this->User->id)) {
$this->Memcached->save(sprintf("rp:news.feed=%d", $this->User->id), $row);
}
}
if (isset($row) && is_array($row)) {
$this->filter_words = json_decode($row['keywords'], true);
$this->filter_topics = json_decode($row['topics'], true);
}
return $this;
}
/**
* Set filters for this user
* @since Version 3.8.7
* @return \Railpage\News\Feed
*/
public function setFilters() {
if (!$this->User instanceof User) {
throw new Exception("Cannot get filters for news feed because no valid user was specified");
}
if (is_string($this->filter_words)) {
$this->filter_words = explode(",", $this->filter_words);
}
foreach ($this->filter_words as $id => $word) {
$this->filter_words[$id] = trim($word);
}
$data = array(
"keywords" => json_encode($this->filter_words),
"topics" => json_encode($this->filter_topics),
"user_id" => $this->User->id
);
if ($id = $this->db->fetchOne("SELECT id FROM news_feed WHERE user_id = ?", $this->User->id)) {
$this->db->update("news_feed", $data, array("id = ?" => $id));
} else {
$this->db->insert("news_feed", $data);
}
unset($data['user_id']);
$key = sprintf("rp:news.feed=%d", $this->User->id);
$this->Memcached->delete($key);
$this->Memcached->save($key, $data);
return $this;
}
}