Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue256 - rss/atom feeds, WIP #771

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/configs/defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ allowDuplicate=0
allowOpenPhotoLogin=1
cdnPrefix=""

[feed]
pageSize=20
photoSize=100x100xCR

[secrets]
passwordSalt="salt_for_passwords"

Expand Down
1 change: 1 addition & 0 deletions src/html/assets/themes/beisel/templates/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<meta name="keywords" content="<?php $this->theme->meta('keywords', $page); ?>">
<meta name="author" content="openphoto.me">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="alternate" type="application/atom+xml" title="Feed (Atom 1.0)" href="/activities/list.atom" />
<link rel="shortcut icon" href="<?php $this->theme->asset('image', 'favicon.png'); ?>">
<link rel="apple-touch-icon" href="<?php $this->theme->asset('image', 'apple-touch-icon.png'); ?>">
<?php if($this->config->site->mode === 'dev') { ?>
Expand Down
1 change: 1 addition & 0 deletions src/html/assets/themes/beisel2.0/templates/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<!--[if lt IE 9]>
<script src="//html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link rel="alternate" type="application/atom+xml" title="Feed (Atom 1.0)" href="/activities/list.atom" />
<link rel="shortcut icon" href="<?php $this->utility->safe($this->config->site->cdnPrefix);?><?php $this->theme->asset('image', 'favicon.ico'); ?>">
<link rel="apple-touch-icon" href="<?php $this->utility->safe($this->config->site->cdnPrefix);?><?php $this->theme->asset('image', 'apple-touch-icon.png'); ?>">
<link rel="apple-touch-icon" sizes="72x72" href="<?php $this->utility->safe($this->config->site->cdnPrefix);?><?php $this->theme->asset('image', 'apple-touch-icon-72x72.png'); ?>">
Expand Down
1 change: 1 addition & 0 deletions src/html/assets/themes/default/templates/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<meta name="author" content="">

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="alternate" type="application/atom+xml" title="Feed (Atom 1.0)" href="/activities/list.atom" />
<link rel="shortcut icon" href="<?php $this->theme->asset('image', 'favicon.png'); ?>">
<link rel="apple-touch-icon" href="<?php $this->theme->asset('image', 'apple-touch-icon.png'); ?>">
<link rel="stylesheet" href="<?php $this->theme->asset('stylesheet', 'html5_boilerplate_style.css'); ?>">
Expand Down
194 changes: 194 additions & 0 deletions src/libraries/controllers/FeedActivityController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
<?php
/**
* Activity controller for feed endpoints
*
* @author Michel Valdrighi <[email protected]>
*/
class FeedActivityController extends ApiActivityController
{
/**
* Call the parent constructor
*
* @return void
*/
public function __construct()
{
parent::__construct();
$this->activity = new Activity;

$this->theme = getTheme();
$this->template->utility = $this->utility;
$this->template->url = $this->url;
}

/**
* Retrieve a list of the user's photo uploads from the remote datasource.
* The $filterOpts are values from the path but can also be in _GET.
* /photos/page-2/tags-favorites.json is identical to /photos.json?page=2&tags=favorites
*
* @param string $filterOpts Options on how to filter the list of photos.
* @return string Standard JSON envelope
*/
public function list_($filterOpts = null)
{
$args = func_get_args();
$feed_format = $args[1];
if ($feed_format === null)
$feed_format = 'atom';

// parse parameters in request
extract($this->parseFilters($filterOpts));
$activities = $this->activity->list_($filters, $pageSize);
if(isset($_GET['groupBy']))
$activities = $this->groupActivities($activities, $_GET['groupBy']);
else
$activities = $this->groupActivities($activities, 'hour');

if($activities !== false) {
$last_activity = current($activities);
$last_activity_item = current($last_activity);
$last_updated_timestamp = $last_activity_item['data']['dateUploaded'];
} else {
$last_updated_timestamp = time();
}

if($this->config->user)
{
$author_email = $this->config->user->email;
if ($this->config->user->name)
$author_name = '';
else
$author_name = $this->utility->getEmailHandle($author_email, false);
}
else
{
$author_email = '';
$author_name = '';
}

if($this->config->site->baseUrl)
$site_base_url = $this->config->site->baseUrl;
else
$site_base_url = '';

if($feed_format == 'atom')
{
$data = array(
'title' => getConfig()->get('titles')->default,
'link' => $site_base_url . '/activities/list.atom',
'updated' => gmdate('Y-m-d\TH:i:s\Z', $last_updated_timestamp),
'author' => array(
'name' => $author_name,
'email' => $author_email
),
'base_url' => $site_base_url,
'id' => $site_base_url . '/'
);

header('Content-type: application/atom+xml');

$this->theme->display($this->utility->getTemplate('feed-atom.php'), array('items' => $this->prepareFeedItems($activities, $site_base_url), 'data' => $data));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must use the proper way to use inheritable template files. Here this searches for the 'feed-atom.php' file in the current theme directory, so 1) it will break once you change themes… 2) it doesn't have anything to do with themes anyway unless one wants to customize the feed's markup.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should store the feed-atom.php template as src/templates/feed/atom.php and share it across themes.

}

exit(0);
}

protected function prepareFeedItems($activities, $site_base_url)
{
$feed_items = array();
$photoSize = $this->config->feed->photoSize;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a config option that must remain '100x100xCR' for the time being, as the Activity API doesn't return bigger sizes except "base" and "original".

if ($photoSize === null)
$photoSize = '100x100xCR';

if (count($activities) > 0) {
foreach ($activities as $activity_title => $activity) {
$feed_item = array();

$titles = array();
$photos = array();
$tags = array();

foreach ($activity as $item) {
// TODO: support other activity types in the future? -- mv
if ($item['type'] == 'photo-upload') {
$data = $item['data'];

if (isset($data['title']))
$titles[] = $data['title'];
else
$titles[] = $data['filenameOriginal'];

$photo = array(
'url' => $site_base_url . $this->url->photoView($data['id'], null, false),
'src' => $data[sprintf('path%s', $photoSize)],
'title' => $data['title'],
'description' => $data['description'],
);

$photos[] = $photo;

// add tags
$tags = array_merge($tags, $data['tags']);

// use the first photo's url and upload date for the item
if (!isset($feed_item['link'])) {
$feed_item['link'] = $photo['url'];
$feed_item['updated'] = $data['dateUploaded'];
$feed_item['license'] = $data['license'];
}
}
}

$feed_item['title'] = implode(', ', $titles);
$feed_item['photos'] = $photos;
$feed_item['tags'] = array_unique($tags);

$feed_items[] = $feed_item;
}
}

return $feed_items;
}

protected function parseFilters($filterOpts)
{
$pageSize = $this->config->feed->pageSize;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put a possibility to use a config option here, but it doesn't seem to have any effect, so I may have misunderstood something with the Activity API.

if ($pageSize === null)
$pageSize = 20;

$filters = array('sortBy' => 'dateCreated,desc', 'groupBy' => 'hour', 'type' => 'photo-upload');
if($filterOpts !== null)
{
$filterOpts = (array)explode('/', $filterOpts);
foreach($filterOpts as $value)
{
$dashPosition = strpos($value, '-');
if(!$dashPosition)
continue;

$parameterKey = substr($value, 0, $dashPosition);
$parameterValue = substr($value, ($dashPosition+1));
switch($parameterKey)
{
case 'pageSize':
$pageSize = intval($parameterValue);
break;
case 'type':
$filters['type'] = $value;
default:
$filters[$parameterKey] = $parameterValue;
break;
}
}
}
// merge path parameters with GET parameters. GET parameters override
if(isset($_GET['pageSize']) && intval($_GET['pageSize']) == $_GET['pageSize'])
$pageSize = intval($_GET['pageSize']);
$filters = array_merge($filters, $_GET);

// force only public items
$filters['permission'] = 0;

return array('filters' => $filters, 'pageSize' => $pageSize);
}
}
1 change: 1 addition & 0 deletions src/libraries/dependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
require $pathsObj->controllers . '/OAuthController.php';
require $pathsObj->controllers . '/ApiWebhookController.php';
require $pathsObj->controllers . '/WebhookController.php';
require $pathsObj->controllers . '/FeedActivityController.php';

// libraries
require $pathsObj->external . '/aws/sdk.class.php';
Expand Down
13 changes: 13 additions & 0 deletions src/libraries/models/Url.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ public function photosUpload($write = true)
return $utilityObj->returnValue('/photos/upload', $write);
}

public function photosFeed($options = null, $feed_format = 'atom', $write = true)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disregard this part, it's dead code. Sorry for including it in the pull request.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't had a look yet but feel free to prune code and update the pull request if needed.

{
$utilityObj = new Utility;
if(empty($options))
{
return $utilityObj->returnValue(sprintf('/photos/list.%s', $feed_format), $write);
}
else
{
return $utilityObj->returnValue(sprintf('/photos/%s/list.%s', $options, $feed_format), $write);
}
}

public function tagsView($write = true)
{
$utilityObj = new Utility;
Expand Down
8 changes: 8 additions & 0 deletions src/libraries/routes-feed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
/*
* Activity endpoints
* All activity endpoints follow the same convention.
* Everything in []'s are optional
* /activit(y|ies)/{action}.json
*/
$routeObj->get('/activities/?(.+)?/list.(atom|rss)', array('FeedActivityController', 'list_'), EpiApi::external); // retrieve activities (/activities/list.atom)
1 change: 1 addition & 0 deletions src/libraries/routes.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php
require $configObj->get('paths')->libraries . '/routes-api.php';
require $configObj->get('paths')->libraries . '/routes-feed.php';

/*
* Home page, optionally redirects if the theme doesn't have a front.php
Expand Down
46 changes: 46 additions & 0 deletions src/templates/feed-atom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php echo '<'; ?>?xml version="1.0" encoding="utf-8"?>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case it looks weird: that's to avoid PHP short-tags problems. (There may be a prettier solution.)

<feed xmlns="http://www.w3.org/2005/Atom">
<title type="text"><?php echo $data['title']; ?></title>
<link rel="self" href="<?php echo $data['link']; ?>"/>
<updated><?php echo $data['updated']; ?></updated>
<author>
<name><?php $this->utility->safe($data['author']['name']); ?></name>
<email><?php $this->utility->safe($data['author']['email']); ?></email>
</author>
<id><?php echo $data['id']; ?></id>
<generator uri="http://theopenphotoproject.org">
The OpenPhoto Project
</generator>
<?php if($this->config->keywords->default) { ?>
<?php $keywords = explode(',', $this->config->keywords->default); ?>
<?php foreach($keywords as $keyword) { ?>
<category term="<?php $this->utility->safe(trim($keyword)); ?>"/>
<?php } ?>
<?php } ?>

<?php foreach($items as $item) { ?>
<entry>
<title><?php $this->utility->safe($item['title']); ?></title>
<link href="<?php echo $item['link']; ?>"/>
<id><?php echo $item['link']; ?></id>
<updated><?php echo gmdate('Y-m-d\TH:i:s\Z', $item['updated']); ?></updated>
<summary type="html"><![CDATA[
<?php foreach ($item['photos'] as $photo) { ?>
<div>
<a href="<?php echo $photo['url']; ?>"><img src="<?php echo $photo['src'] ?>" alt="<?php $this->utility->safe($photo['title']); ?>" /></a>
<?php if ($photo['description'] != '') { ?>
<p><?php $this->utility->safe($photo['description']); ?></p>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add some markup for the title if there is one?

<?php } ?>
</div>
<?php } ?>
]]></summary>
<rights type="html"><?php $this->utility->licenseName($item['license']); ?></rights>
<?php if(count($item['tags']) > 0) { ?>
<?php foreach($item['tags'] as $tag) { ?>
<category term="<?php $this->utility->safe($tag); ?>" scheme="<?php echo $data['base_url']; $this->url->photosView("tags-{$tag}"); ?>"/>
<?php } ?>
<?php } ?>
</entry>
<?php } ?>

</feed>