Fixed issue 71, add atom feeds.

The basic building block is here with the feed of the timeline, it is
rather easy to add the feed other elements based on this work. This will
require iterations and polishing.
This commit is contained in:
Loic d'Anterroches 2008-12-03 15:00:47 +01:00
parent a79d13ee3f
commit 9c44bc5fe5
15 changed files with 397 additions and 11 deletions

View File

@ -183,4 +183,36 @@ class IDF_Commit extends Pluf_Model
<div class="helptext right">'.__('Commit').'&nbsp;<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>, '.__('by').' '.strip_tags($this->origauthor).'</div></td></tr>'; <div class="helptext right">'.__('Commit').'&nbsp;<a href="'.$url.'" class="mono">'.$this->scm_id.'</a>, '.__('by').' '.strip_tags($this->origauthor).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
/**
* Returns the feed fragment for the commit.
*
* @param Pluf_HTTP_Request
* @return Pluf_Template_SafeString
*/
public function feedFragment($request)
{
$url = Pluf::f('url_base')
.Pluf_HTTP_URL_urlForView('IDF_Views_Source::commit',
array($request->project->shortname,
$this->scm_id));
$tag = new IDF_Template_IssueComment();
$summary = '<content type="xhtml">'."\n"
.'<div xmlns="http://www.w3.org/1999/xhtml">'
.$tag->start($this->summary, $request, false, false, false);
if ($this->fullmessage) {
$summary .= '<br /><br />'
.$tag->start($this->fullmessage, $request, false, false, false);
}
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
$summary .= '</div></content>';
$out = '<entry>
<title>'.Pluf_esc($request->project->name).': '.__('Commit').' '.$this->scm_id.'</title>
<link href="'.$url.'"/>
<id>'.$url.'</id>
<updated>'.$date.'</updated>'.$summary.'
</entry>
';
return $out;
}
} }

View File

@ -195,4 +195,34 @@ class IDF_Issue extends Pluf_Model
<div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue&nbsp;%d</a>'), $url, $ic, $this->id).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Creation of <a href="%s" class="%s">issue&nbsp;%d</a>'), $url, $ic, $this->id).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
public function feedFragment($request)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
<pre>%%content%%</pre>
</div></content>
</entry>';
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname,
$this->id));
$title = sprintf(__('%s: Issue %d created - %s'),
Pluf_esc($request->project->name),
$this->id, Pluf_esc($this->summary));
// Get the first comment of this issue.
$cts = $this->get_comments_list(array('order' => 'id ASC',
'nb' => 1));
$tag = new IDF_Template_IssueComment();
$content = $tag->start($cts[0]->content, $request, false);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
array('url' => $url,
'title' => $title,
'content' => $content,
'date' => $date));
}
} }

View File

@ -172,4 +172,58 @@ class IDF_IssueComment extends Pluf_Model
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
public function feedFragment($request)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
%%content%%
</div></content>
</entry>';
$issue = $this->get_issue();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
array($request->project->shortname,
$issue->id));
$url .= '#ic'.$this->id;
$title = sprintf(__('%s: Comment on issue %d - %s'),
Pluf_esc($request->project->name),
$issue->id, Pluf_esc($issue->summary));
$submitter = $this->get_submitter();
$tag = new IDF_Template_IssueComment();
$content = '<p><pre>'.$tag->start($this->content, $request, false).'</pre></p>';
if ($this->changedIssue()) {
$content .= '<p>';
foreach ($this->changes as $w => $v) {
$content .= '<strong>';
switch ($w) {
case 'su':
$content .= __('Summary:'); break;
case 'st':
$content .= __('Status:'); break;
case 'ow':
$content .= __('Owner:'); break;
case 'lb':
$content .= __('Labels:'); break;
}
$content .= '</strong> ';
if ($w == 'lb') {
$content .= Pluf_esc(implode(', ', $v));
} else {
$content .= Pluf_esc($v);
}
$content .= ' ';
}
$content .= '</p>';
}
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
array('url' => $url,
'title' => $title,
'content' => $content,
'date' => $date));
}
} }

View File

@ -52,6 +52,13 @@ class IDF_Middleware
} }
$request->conf = new IDF_Conf(); $request->conf = new IDF_Conf();
$request->conf->setProject($request->project); $request->conf->setProject($request->project);
self::setRights($request);
}
return false;
}
public static function setRights(&$request)
{
$ak = array('downloads_access_rights' => 'hasDownloadsAccess', $ak = array('downloads_access_rights' => 'hasDownloadsAccess',
'wiki_access_rights' => 'hasWikiAccess', 'wiki_access_rights' => 'hasWikiAccess',
'review_access_rights' => 'hasReviewAccess', 'review_access_rights' => 'hasReviewAccess',
@ -62,8 +69,6 @@ class IDF_Middleware
$request->rights[$val] = (true === IDF_Precondition::accessTabGeneric($request, $key)); $request->rights[$val] = (true === IDF_Precondition::accessTabGeneric($request, $key));
} }
} }
return false;
}
} }

View File

@ -180,7 +180,7 @@ class IDF_Precondition
$sql = new Pluf_SQL('login=%s AND active='.$true, $sql = new Pluf_SQL('login=%s AND active='.$true,
$request->REQUEST['_login']); $request->REQUEST['_login']);
$users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
if ($users->count() != 1) { if ($users->count() != 1 or !$users[0]->active) {
// Should return a special authentication error like user // Should return a special authentication error like user
// not found. // not found.
return true; return true;
@ -190,6 +190,70 @@ class IDF_Precondition
return true; // Again need authentication error return true; // Again need authentication error
} }
$request->user = $users[0]; $request->user = $users[0];
IDF_Middleware::setRights($request);
return true; return true;
} }
/**
* Based on the request, it is automatically setting the user.
*
* Authenticated feeds have a token set at the end of the url in
* the for of 'authenticated/url/token/234092384023woeiur/'. If
* you remove 'token/234092384023woeiur/' the url is not
* authenticated.
*
* If the user is already logged in and not anonymous and no token
* is given, then the user is unset and a non authenticated user
* is loaded. This is to avoid people to not understand why a
* normally not authenticated feed is providing authenticated
* data.
*/
static public function feedSetUser($request)
{
if (!isset($request->project)) {
return true; // we do not act on non project pages at the
// moment.
}
if (!$request->user->isAnonymous()) {
// by default anonymous
$request->user = new Pluf_User();
IDF_Middleware::setRights($request);
}
$match = array();
if (!preg_match('#/token/([^/]+)/$#', $request->query, $match)) {
return true; // anonymous
}
$token = $match[1];
$hash = substr($token, 0, 2);
$encrypted = substr($token, 2);
if ($hash != substr(md5(Pluf::f('secret_key').$encrypted), 0, 2)) {
return true; // no match in the hash, anonymous
}
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
list($userid, $projectid) = split(':', $cr->decrypt($encrypted), 2);
if ($projectid != $request->project->id) {
return true; // anonymous
}
$user = new Pluf_User($userid);
if (!$user->active) {
return true; // anonymous
}
$request->user = $user;
IDF_Middleware::setRights($request);
return true;
}
/**
* Generate the token for the feed.
*
* @param IDF_Project
* @param Pluf_User
* @return string Token
*/
static public function genFeedToken($project, $user)
{
$cr = new Pluf_Crypt(md5(Pluf::f('secret_key')));
$encrypted = trim($cr->encrypt($user->id.':'.$project->id), '~');
return substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted;
}
} }

View File

@ -165,4 +165,9 @@ class IDF_Review extends Pluf_Model
{ {
return ''; return '';
} }
public function feedFragment($request)
{
return '';
}
} }

View File

@ -110,4 +110,9 @@ class IDF_Review_FileComment extends Pluf_Model
{ {
return ''; return '';
} }
public function feedFragment($request)
{
return '';
}
} }

View File

@ -115,4 +115,9 @@ class IDF_Review_Patch extends Pluf_Model
public function timelineFragment($request) public function timelineFragment($request)
{ {
} }
public function feedFragment($request)
{
return '';
}
} }

View File

@ -184,4 +184,30 @@ class IDF_Upload extends Pluf_Model
<div class="helptext right">'.sprintf(__('Addition of <a href="%s">download&nbsp;%d</a>'), $url, $this->id).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Addition of <a href="%s">download&nbsp;%d</a>'), $url, $this->id).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
public function feedFragment($request)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
%%content%%
</div></content>
</entry>';
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Download::view',
array($request->project->shortname,
$this->id));
$title = sprintf(__('%s: Download %d added - %s'),
Pluf_esc($request->project->name),
$this->id, Pluf_esc($this->summary));
$content = Pluf_esc($this->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
array('url' => $url,
'title' => $title,
'content' => $content,
'date' => $date));
}
} }

View File

@ -115,14 +115,91 @@ class IDF_Views_Project
// the first tag is the featured, the last is the deprecated. // the first tag is the featured, the last is the deprecated.
$downloads = $tags[0]->get_idf_upload_list(); $downloads = $tags[0]->get_idf_upload_list();
} }
$pages = array();
if ($request->rights['hasWikiAccess']) {
$tags = IDF_Views_Wiki::getWikiTags($prj);
$pages = $tags[0]->get_idf_wikipage_list();
}
if (!$request->user->isAnonymous()) {
$feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed_auth',
array($prj->shortname,
IDF_Precondition::genFeedToken($prj, $request->user)));
} else {
$feedurl = Pluf_HTTP_URL_urlForView('idf_project_timeline_feed',
array($prj->shortname));
}
return Pluf_Shortcuts_RenderToResponse('idf/project/timeline.html', return Pluf_Shortcuts_RenderToResponse('idf/project/timeline.html',
array( array(
'page_title' => $title, 'page_title' => $title,
'feedurl' => $feedurl,
'timeline' => $pag, 'timeline' => $pag,
'team' => $team, 'team' => $team,
'downloads' => $downloads, 'downloads' => $downloads,
), ),
$request); $request);
}
/**
* Timeline feed.
*
* A custom view to have a bit more control on the way to handle
* it and optimize the output.
*
*/
public $timelineFeed_precond = array('IDF_Precondition::feedSetUser',
'IDF_Precondition::baseAccess');
public function timelineFeed($request, $match)
{
$prj = $request->project;
// Need to check the rights
$rights = array();
if (true === IDF_Precondition::accessSource($request)) {
$rights[] = '\'IDF_Commit\'';
IDF_Scm::syncTimeline($request);
}
if (true === IDF_Precondition::accessIssues($request)) {
$rights[] = '\'IDF_Issue\'';
$rights[] = '\'IDF_IssueComment\'';
}
if (true === IDF_Precondition::accessDownloads($request)) {
$rights[] = '\'IDF_Upload\'';
}
if (true === IDF_Precondition::accessWiki($request)) {
$rights[] = '\'IDF_WikiPage\'';
$rights[] = '\'IDF_WikiRevision\'';
}
if (count($rights) == 0) {
$rights[] = '\'IDF_Dummy\'';
}
$sqls = sprintf('model_class IN (%s)', implode(', ', $rights));
$sql = new Pluf_SQL('project=%s AND '.$sqls, array($prj->id));
$params = array(
'filter' => $sql->gen(),
'order' => 'creation_dtime DESC',
'nb' => 50,
);
$items = Pluf::factory('IDF_Timeline')->getList($params);
$set = new Pluf_Model_Set($items,
array('public_dtime' => 'public_dtime'));
$out = array();
foreach ($set as $item) {
$out[] = $item->feedFragment($request);
}
$out = Pluf_Template::markSafe(implode("\n", $out));
$tmpl = new Pluf_Template('idf/index.atom');
$title = __('Updates');
$feedurl = Pluf::f('url_base').Pluf::f('idf_base').$request->query;
$viewurl = Pluf_HTTP_URL_urlForView('IDF_Views_Project::timeline',
array($prj->shortname));
$context = new Pluf_Template_Context_Request($request,
array('body' => $out,
'title' => $title,
'feedurl' => $feedurl,
'viewurl' => $viewurl));
return new Pluf_HTTP_Response('<?xml version="1.0" encoding="utf-8"?>'
."\n".$tmpl->render($context),
'application/atom+xml; charset=utf-8');
} }

View File

@ -198,4 +198,31 @@ class IDF_WikiPage extends Pluf_Model
<div class="helptext right">'.sprintf(__('Creation of <a href="%s">page&nbsp;%s</a>'), $url, Pluf_esc($this->title)).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Creation of <a href="%s">page&nbsp;%s</a>'), $url, Pluf_esc($this->title)).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
public function feedFragment($request)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
%%content%%
</div></content>
</entry>';
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$this->title));
$title = sprintf(__('%s: Documentation page %s added - %s'),
Pluf_esc($request->project->name),
Pluf_esc($this->title), Pluf_esc($this->summary));
$content = Pluf_esc($this->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
array('url' => $url,
'title' => $title,
'content' => $content,
'date' => $date));
}
} }

View File

@ -189,4 +189,32 @@ class IDF_WikiRevision extends Pluf_Model
<div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>'), $url, Pluf_esc($page->title)).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>'; <div class="helptext right">'.sprintf(__('Change of <a href="%s">%s</a>'), $url, Pluf_esc($page->title)).', '.__('by').' '.Pluf_esc($submitter).'</div></td></tr>';
return Pluf_Template::markSafe($out); return Pluf_Template::markSafe($out);
} }
public function feedFragment($request)
{
$base = '<entry>
<title>%%title%%</title>
<link href="%%url%%"/>
<id>%%url%%</id>
<updated>%%date%%</updated>
<content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml">
%%content%%
</div></content>
</entry>';
$page = $this->get_wikipage();
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Wiki::view',
array($request->project->shortname,
$page->title));
$title = sprintf(__('%s: Documentation page %s updated - %s'),
Pluf_esc($request->project->name),
Pluf_esc($page->title), Pluf_esc($page->summary));
$content = Pluf_esc($this->summary);
$date = Pluf_Date::gmDateToGmString($this->creation_dtime);
return Pluf_Translation::sprintf($base,
array('url' => $url,
'title' => $title,
'content' => $content,
'date' => $date));
}
} }

View File

@ -91,6 +91,20 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/$#',
'model' => 'IDF_Views_Project', 'model' => 'IDF_Views_Project',
'method' => 'timeline'); 'method' => 'timeline');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'timelineFeed',
'name' => 'idf_project_timeline_feed');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/feed/timeline/token/(.*)/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Project',
'method' => 'timelineFeed',
'name' => 'idf_project_timeline_feed_auth');
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/$#', $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/$#',
'base' => $base, 'base' => $base,
'priority' => 4, 'priority' => 4,

View File

@ -0,0 +1,13 @@
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{$title}, {$project} - {$project.shortdesc}</title>
{if !$user.isAnonymous()} <subtitle>{blocktrans}Personal project feed for {$user}.{/blocktrans}</subtitle>{/if}
<link href="{$feedurl}" rel="self"/>
<link href="{$viewurl}"/>
<updated>{$updated}</updated>
<author>
<name>Not given</name>
<email>Not given</email>
</author>
<id>{$feedurl}</id>
{$body}
</feed>

View File

@ -1,4 +1,5 @@
{extends "idf/base.html"} {extends "idf/base.html"}
{block extraheader}<link rel="alternate" type="application/atom+xml" title="{trans 'Latest updates'}" href="{$feedurl}"/>{/block}
{block docclass}yui-t2{/block} {block docclass}yui-t2{/block}
{block tabhome} class="active"{/block} {block tabhome} class="active"{/block}
{block subtabs} {block subtabs}