From 9c44bc5fe5c549959706b2b8a9d892398743bbf1 Mon Sep 17 00:00:00 2001 From: Loic d'Anterroches Date: Wed, 3 Dec 2008 15:00:47 +0100 Subject: [PATCH] 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. --- src/IDF/Commit.php | 32 +++++++++ src/IDF/Issue.php | 30 ++++++++ src/IDF/IssueComment.php | 54 +++++++++++++++ src/IDF/Middleware.php | 23 +++--- src/IDF/Precondition.php | 68 +++++++++++++++++- src/IDF/Review.php | 5 ++ src/IDF/Review/FileComment.php | 5 ++ src/IDF/Review/Patch.php | 5 ++ src/IDF/Upload.php | 26 +++++++ src/IDF/Views/Project.php | 77 +++++++++++++++++++++ src/IDF/WikiPage.php | 27 ++++++++ src/IDF/WikiRevision.php | 28 ++++++++ src/IDF/conf/urls.php | 14 ++++ src/IDF/templates/idf/index.atom | 13 ++++ src/IDF/templates/idf/project/timeline.html | 1 + 15 files changed, 397 insertions(+), 11 deletions(-) create mode 100644 src/IDF/templates/idf/index.atom diff --git a/src/IDF/Commit.php b/src/IDF/Commit.php index d4671eb..8ee82c8 100644 --- a/src/IDF/Commit.php +++ b/src/IDF/Commit.php @@ -183,4 +183,36 @@ class IDF_Commit extends Pluf_Model
'.__('Commit').' '.$this->scm_id.', '.__('by').' '.strip_tags($this->origauthor).'
'; 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 = ''."\n" + .'
' + .$tag->start($this->summary, $request, false, false, false); + if ($this->fullmessage) { + $summary .= '

' + .$tag->start($this->fullmessage, $request, false, false, false); + } + $date = Pluf_Date::gmDateToGmString($this->creation_dtime); + $summary .= '
'; + $out = ' + '.Pluf_esc($request->project->name).': '.__('Commit').' '.$this->scm_id.' + + '.$url.' + '.$date.''.$summary.' + +'; + return $out; + } } diff --git a/src/IDF/Issue.php b/src/IDF/Issue.php index 1cda1a2..d901852 100644 --- a/src/IDF/Issue.php +++ b/src/IDF/Issue.php @@ -195,4 +195,34 @@ class IDF_Issue extends Pluf_Model
'.sprintf(__('Creation of issue %d'), $url, $ic, $this->id).', '.__('by').' '.Pluf_esc($submitter).'
'; return Pluf_Template::markSafe($out); } + + public function feedFragment($request) + { + $base = ' + %%title%% + + %%url%% + %%date%% +
+
%%content%%
+
+
'; + $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)); + } } \ No newline at end of file diff --git a/src/IDF/IssueComment.php b/src/IDF/IssueComment.php index 741a3d5..fe1ab2d 100644 --- a/src/IDF/IssueComment.php +++ b/src/IDF/IssueComment.php @@ -172,4 +172,58 @@ class IDF_IssueComment extends Pluf_Model return Pluf_Template::markSafe($out); } + + public function feedFragment($request) + { + $base = ' + %%title%% + + %%url%% + %%date%% +
+ %%content%% +
+
'; + $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 = '

'.$tag->start($this->content, $request, false).'

'; + if ($this->changedIssue()) { + $content .= '

'; + foreach ($this->changes as $w => $v) { + $content .= ''; + switch ($w) { + case 'su': + $content .= __('Summary:'); break; + case 'st': + $content .= __('Status:'); break; + case 'ow': + $content .= __('Owner:'); break; + case 'lb': + $content .= __('Labels:'); break; + } + $content .= ' '; + if ($w == 'lb') { + $content .= Pluf_esc(implode(', ', $v)); + } else { + $content .= Pluf_esc($v); + } + $content .= ' '; + } + $content .= '

'; + } + $date = Pluf_Date::gmDateToGmString($this->creation_dtime); + return Pluf_Translation::sprintf($base, + array('url' => $url, + 'title' => $title, + 'content' => $content, + 'date' => $date)); + } } diff --git a/src/IDF/Middleware.php b/src/IDF/Middleware.php index 94d37ae..0d4f6ad 100644 --- a/src/IDF/Middleware.php +++ b/src/IDF/Middleware.php @@ -52,18 +52,23 @@ class IDF_Middleware } $request->conf = new IDF_Conf(); $request->conf->setProject($request->project); - $ak = array('downloads_access_rights' => 'hasDownloadsAccess', - 'wiki_access_rights' => 'hasWikiAccess', - 'review_access_rights' => 'hasReviewAccess', - 'source_access_rights' => 'hasSourceAccess', - 'issues_access_rights' => 'hasIssuesAccess'); - $request->rights = array(); - foreach ($ak as $key=>$val) { - $request->rights[$val] = (true === IDF_Precondition::accessTabGeneric($request, $key)); - } + self::setRights($request); } return false; } + + public static function setRights(&$request) + { + $ak = array('downloads_access_rights' => 'hasDownloadsAccess', + 'wiki_access_rights' => 'hasWikiAccess', + 'review_access_rights' => 'hasReviewAccess', + 'source_access_rights' => 'hasSourceAccess', + 'issues_access_rights' => 'hasIssuesAccess'); + $request->rights = array(); + foreach ($ak as $key=>$val) { + $request->rights[$val] = (true === IDF_Precondition::accessTabGeneric($request, $key)); + } + } } diff --git a/src/IDF/Precondition.php b/src/IDF/Precondition.php index 018a1c2..a52b670 100644 --- a/src/IDF/Precondition.php +++ b/src/IDF/Precondition.php @@ -161,7 +161,7 @@ class IDF_Precondition return self::accessTabGeneric($request, 'review_access_rights'); } - /** + /** * Based on the request, it is automatically setting the user. * * API calls are not translated. @@ -180,7 +180,7 @@ class IDF_Precondition $sql = new Pluf_SQL('login=%s AND active='.$true, $request->REQUEST['_login']); $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 // not found. return true; @@ -190,6 +190,70 @@ class IDF_Precondition return true; // Again need authentication error } $request->user = $users[0]; + IDF_Middleware::setRights($request); 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; + } } \ No newline at end of file diff --git a/src/IDF/Review.php b/src/IDF/Review.php index 5e04be8..224ce85 100644 --- a/src/IDF/Review.php +++ b/src/IDF/Review.php @@ -165,4 +165,9 @@ class IDF_Review extends Pluf_Model { return ''; } + + public function feedFragment($request) + { + return ''; + } } \ No newline at end of file diff --git a/src/IDF/Review/FileComment.php b/src/IDF/Review/FileComment.php index 9f0f223..3975ffc 100644 --- a/src/IDF/Review/FileComment.php +++ b/src/IDF/Review/FileComment.php @@ -110,4 +110,9 @@ class IDF_Review_FileComment extends Pluf_Model { return ''; } + + public function feedFragment($request) + { + return ''; + } } diff --git a/src/IDF/Review/Patch.php b/src/IDF/Review/Patch.php index f25765c..f00845b 100644 --- a/src/IDF/Review/Patch.php +++ b/src/IDF/Review/Patch.php @@ -115,4 +115,9 @@ class IDF_Review_Patch extends Pluf_Model public function timelineFragment($request) { } + + public function feedFragment($request) + { + return ''; + } } diff --git a/src/IDF/Upload.php b/src/IDF/Upload.php index 4d02a3a..da18d03 100644 --- a/src/IDF/Upload.php +++ b/src/IDF/Upload.php @@ -184,4 +184,30 @@ class IDF_Upload extends Pluf_Model
'.sprintf(__('Addition of download %d'), $url, $this->id).', '.__('by').' '.Pluf_esc($submitter).'
'; return Pluf_Template::markSafe($out); } + + public function feedFragment($request) + { + $base = ' + %%title%% + + %%url%% + %%date%% +
+ %%content%% +
+
'; + $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)); + } } \ No newline at end of file diff --git a/src/IDF/Views/Project.php b/src/IDF/Views/Project.php index 3d113cf..0a40400 100644 --- a/src/IDF/Views/Project.php +++ b/src/IDF/Views/Project.php @@ -115,14 +115,91 @@ class IDF_Views_Project // the first tag is the featured, the last is the deprecated. $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', array( 'page_title' => $title, + 'feedurl' => $feedurl, 'timeline' => $pag, 'team' => $team, 'downloads' => $downloads, ), $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('' + ."\n".$tmpl->render($context), + 'application/atom+xml; charset=utf-8'); } diff --git a/src/IDF/WikiPage.php b/src/IDF/WikiPage.php index 6d17a11..92ee800 100644 --- a/src/IDF/WikiPage.php +++ b/src/IDF/WikiPage.php @@ -198,4 +198,31 @@ class IDF_WikiPage extends Pluf_Model
'.sprintf(__('Creation of page %s'), $url, Pluf_esc($this->title)).', '.__('by').' '.Pluf_esc($submitter).'
'; return Pluf_Template::markSafe($out); } + + public function feedFragment($request) + { + $base = ' + %%title%% + + %%url%% + %%date%% +
+ %%content%% +
+
'; + $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)); + } + } \ No newline at end of file diff --git a/src/IDF/WikiRevision.php b/src/IDF/WikiRevision.php index e920d2e..783f2d8 100644 --- a/src/IDF/WikiRevision.php +++ b/src/IDF/WikiRevision.php @@ -189,4 +189,32 @@ class IDF_WikiRevision extends Pluf_Model
'.sprintf(__('Change of %s'), $url, Pluf_esc($page->title)).', '.__('by').' '.Pluf_esc($submitter).'
'; return Pluf_Template::markSafe($out); } + + public function feedFragment($request) + { + $base = ' + %%title%% + + %%url%% + %%date%% +
+ %%content%% +
+
'; + $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)); + } + } diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index bce6226..43693ca 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -91,6 +91,20 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/timeline/$#', 'model' => 'IDF_Views_Project', '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/$#', 'base' => $base, 'priority' => 4, diff --git a/src/IDF/templates/idf/index.atom b/src/IDF/templates/idf/index.atom new file mode 100644 index 0000000..f4ff7e0 --- /dev/null +++ b/src/IDF/templates/idf/index.atom @@ -0,0 +1,13 @@ + + {$title}, {$project} - {$project.shortdesc} +{if !$user.isAnonymous()} {blocktrans}Personal project feed for {$user}.{/blocktrans}{/if} + + + {$updated} + + Not given + Not given + + {$feedurl} +{$body} + diff --git a/src/IDF/templates/idf/project/timeline.html b/src/IDF/templates/idf/project/timeline.html index 395acbe..f0fd70d 100644 --- a/src/IDF/templates/idf/project/timeline.html +++ b/src/IDF/templates/idf/project/timeline.html @@ -1,4 +1,5 @@ {extends "idf/base.html"} +{block extraheader}{/block} {block docclass}yui-t2{/block} {block tabhome} class="active"{/block} {block subtabs}