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}