Indefero

Indefero Commit Details


Date:2008-11-21 13:33:39 (16 years 1 month ago)
Author:Loic d'Anterroches
Branch:dev, develop, feature-issue_links, feature.better-home, feature.content-md5, feature.diff-whitespace, feature.download-md5, feature.issue-links, feature.issue-of-others, feature.issue-summary, feature.search-filter, feature.webrepos, feature.wiki-default-page, master, release-1.1, release-1.2, release-1.3
Commit:9814a75f82670f492b786d6f2cd9ec783d769c29
Parents: 0e725bea26b9a543f66fa201eecf1666e9e7a598
Message:Added the first work on an API.

Changes:

File differences

src/IDF/Middleware.php
3434
3535
3636
37
38
39
37
38
39
4040
4141
4242
......
4444
4545
4646
47
47
4848
4949
5050
* When processing the request, check if matching a project. If
* so, directly set $request->project to the project.
*
* The url to match a project is in the format
* /p/(\w+)/whatever. This means that it will not try to match on
* /login/ or /logout/.
* The url to match a project is in the format /p/(\w+)/whatever
* or /api/p/(\w+)/whatever. This means that it will not try to
* match on /login/ or /logout/.
*
* @param Pluf_HTTP_Request The request
* @return bool false or redirect.
function process_request(&$request)
{
$match = array();
if (preg_match('#^/p/([\-\w]+)/#', $request->query, $match)) {
if (preg_match('#^/(?:api/p|p)/([\-\w]+)/#', $request->query, $match)) {
try {
$request->project = IDF_Project::getOr404($match[1]);
} catch (Pluf_HTTP_Error404 $e) {
src/IDF/Precondition.php
142142
143143
144144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
145177
}
return self::accessTabGeneric($request, 'downloads_access_rights');
}
/**
* Based on the request, it is automatically setting the user.
*
* API calls are not translated.
*/
static public function apiSetUser($request)
{
// REQUEST is used to be used both for POST and GET requests.
if (!isset($request->REQUEST['_hash'])
or !isset($request->REQUEST['_login'])
or !isset($request->REQUEST['_salt'])) {
// equivalent to anonymous access.
return true;
}
$db =& Pluf::db();
$true = Pluf_DB_BooleanToDb(true, $db);
$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) {
// Should return a special authentication error like user
// not found.
return true;
}
$hash = sha1($request->REQUEST['_salt'].sha1($users[0]->password));
if ($hash != $request->REQUEST['_hash']) {
return true; // Again need authentication error
}
$request->user = $users[0];
return true;
}
}
src/IDF/Views.php
185185
186186
187187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
188204
189205
190206
}
/**
* API FAQ.
*/
public function faqApi($request, $match)
{
$title = __('InDefero API (Application Programming Interface)');
$projects = self::getProjects($request->user);
return Pluf_Shortcuts_RenderToResponse('idf/faq-api.html',
array(
'page_title' => $title,
'projects' => $projects,
),
$request);
}
/**
* Returns a list of projects accessible for the user.
*
* @param Pluf_User
src/IDF/Views/Api.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2008 CĂ©ondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# InDefero is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ***** END LICENSE BLOCK ***** */
/**
* API views.
*
* These are just small wrappers around the "normal" views. The normal
* views are called with a third parameters $api set to true to return
* JSON instead of HTML.
*
* A special precondition is used to set the $request->user from the
* _login, _hash and _salt parameters.
*/
class IDF_Views_Api
{
/**
* View list of issues for a given project.
*/
public $issuesIndex_precond = array('IDF_Precondition::apiSetUser',
'IDF_Precondition::accessIssues');
public function issuesIndex($request, $match)
{
$views = new IDF_Views_Issue();
$p = $views->index($request, $match, true);
$out = array(
'project' => $request->project->shortname,
'open' => $p['open'],
'closed' => $p['closed'],
'issues' => $p['issues']->render_array(),
);
return new Pluf_HTTP_Response_Json($out);
}
/**
* Create a new issue.
*/
public $issueCreate_precond = array('IDF_Precondition::apiSetUser',
'IDF_Precondition::accessIssues');
public function issueCreate($request, $match)
{
$views = new IDF_Views_Issue();
$p = $views->create($request, $match, true);
$out = array();
if ($request->method == 'GET') {
// We give the details of the form
$out['doc'] = 'A POST request against this url will allow you to create a new issue.';
if ($request->user->hasPerm('IDF.project-owner', $request->project)
or $request->user->hasPerm('IDF.project-member', $request->project)) {
$out['status'] = array();
foreach (self::getTags($request->project) as $tag) {
$out['status'][] = $tag->name;
}
}
} else {
// We need to give back the results of the creation
if (is_object($p) and 'IDF_Issue' == get_class($p)) {
$out['mess'] = 'success';
$out['issue'] = $p->id;
} else {
$out['mess'] = 'error';
$out['errors'] = $p['form']->errors;
}
}
return new Pluf_HTTP_Response_Json($out);
}
/**
* Get the list of tags to give them to the end users when doing a
* GET request against a form. That way it is possible for them to
* know which tags/labels are available.
*
* @param IDF_Project Current project
* @param string Which tags to get ('issue-open')
* @return ArrayObject Tags
*/
public static function getTags($project, $what='issue-open')
{
switch ($what) {
case 'issue-open':
$key = 'labels_issue_open';
$default = IDF_Form_IssueTrackingConf::init_open;
return $project->getTagsFromConfig($key, $default);
case 'issue-closed':
return $project->getTagIdsByStatus('closed');
}
return array();
}
}
src/IDF/Views/Issue.php
3535
3636
3737
38
38
3939
4040
4141
......
6565
6666
6767
68
69
70
71
72
73
74
6875
69
70
71
72
73
74
75
76
76
7777
7878
7979
......
133133
134134
135135
136
136
137137
138138
139139
......
169169
170170
171171
172
172173
173174
174175
175176
176177
177
178
179
180
181
182
183
184
185
178186
179
180
181
182
183
184
185
187
186188
187189
188190
* View list of issues for a given project.
*/
public $index_precond = array('IDF_Precondition::accessIssues');
public function index($request, $match)
public function index($request, $match, $api=false)
{
$prj = $request->project;
$title = sprintf(__('%s Open Issues'), (string) $prj);
$pag->items_per_page = 10;
$pag->no_results_text = __('No issues were found.');
$pag->setFromRequest($request);
$params = array('project' => $prj,
'page_title' => $title,
'open' => $open,
'closed' => $closed,
'issues' => $pag,
'cloud' => 'issues');
if ($api) return $params;
return Pluf_Shortcuts_RenderToResponse('idf/issues/index.html',
array('project' => $prj,
'page_title' => $title,
'open' => $open,
'closed' => $closed,
'issues' => $pag,
'cloud' => 'issues',
),
$request);
$params, $request);
}
/**
public $create_precond = array('IDF_Precondition::accessIssues',
'Pluf_Precondition::loginRequired');
public function create($request, $match)
public function create($request, $match, $api=false)
{
$prj = $request->project;
$title = __('Submit a new issue');
$email->addTextMessage($tmpl->render($context));
$email->sendMail();
}
if ($api) return $issue;
return new Pluf_HTTP_Response_Redirect($url);
}
} else {
$form = new IDF_Form_IssueCreate(null, $params);
}
$arrays = self::autoCompleteArrays($prj);
$params = array_merge(
array('project' => $prj,
'form' => $form,
'page_title' => $title,
),
self::autoCompleteArrays($prj)
);
if ($api == true) return $params;
return Pluf_Shortcuts_RenderToResponse('idf/issues/create.html',
array_merge(
array('project' => $prj,
'form' => $form,
'page_title' => $title,
),
$arrays),
$request);
$params, $request);
}
public $search_precond = array('IDF_Precondition::accessIssues');
src/IDF/Views/User.php
3838
3939
4040
41
42
43
4144
4245
4346
......
5255
5356
5457
58
5559
5660
5761
public $myAccount_precond = array('Pluf_Precondition::loginRequired');
public function myAccount($request, $match)
{
// As the password is salted, we can directly take the sha1 of
// the salted password.
$api_key = sha1($request->user->password);
$params = array('user' => $request->user);
if ($request->method == 'POST') {
$form = new IDF_Form_UserAccount($request->POST, $params);
}
return Pluf_Shortcuts_RenderToResponse('idf/user/myaccount.html',
array('page_title' => __('Your Account'),
'api_key' => $api_key,
'form' => $form),
$request);
}
src/IDF/conf/urls.php
272272
273273
274274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
275295
'model' => 'IDF_Views_Project',
'method' => 'adminTabs');
// ---------- API ----------------------------------------
$ctl[] = array('regex' => '#^/help/api/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views',
'method' => 'faqApi');
$ctl[] = array('regex' => '#^/api/p/([\-\w]+)/issues/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Api',
'method' => 'issuesIndex');
$ctl[] = array('regex' => '#^/api/p/([\-\w]+)/issues/create/$#',
'base' => $base,
'priority' => 4,
'model' => 'IDF_Views_Api',
'method' => 'issueCreate');
return $ctl;
src/IDF/templates/idf/base-simple.html
3535
3636
3737
38
38
3939
4040
4141
<div id="hd">
<p class="top"><a href="#title" accesskey="2"></a>
{if !$user.isAnonymous()}{aurl 'url', 'IDF_Views_User::myAccount'}{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
| <a href="{url 'IDF_Views::faq'}">{trans 'Help'}</a>
| <a href="{url 'IDF_Views::faq'}" title="{trans 'Help and accessibility features'}">{trans 'Help'}</a>
</p>
<h1 id="title" class="title">{block title}{$page_title}{/block}</h1>
src/IDF/templates/idf/base.html
3737
3838
3939
40
40
4141
4242
4343
<p class="top"><a href="#title" accesskey="2"></a>
{if !$user.isAnonymous()}{aurl 'url', 'IDF_Views_User::myAccount'}{blocktrans}Welcome, <strong><a class="userw" href="{$url}">{$user}</a></strong>.{/blocktrans} <a href="{url 'IDF_Views::logout'}">{trans 'Sign Out'}</a>{else}<a href="{url 'IDF_Views::login'}">{trans 'Sign in or create your account'}</a>{/if}
{if $project} | <a href="{url 'IDF_Views::index'}">{trans 'Project List'}</a>{/if}
| <a href="{url 'IDF_Views::faq'}">{trans 'Help'}</a>
| <a href="{url 'IDF_Views::faq'}" title="{trans 'Help and accessibility features'}">{trans 'Help'}</a>
</p>
<div id="header">
<div id="main-tabs">
src/IDF/templates/idf/faq-api.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
{extends "idf/base-simple.html"}
{block docclass}yui-t3{/block}
{block body}
<p>At the moment, this documentation is only available in English.</p>
<ul>
<li><a href="#q-overview">How to access the API?</a></li>
<li><a href="#q-authentication">How to authenticate the queries?</a></li>
</ul>
<h2 id="q-overview">How to access the API?</h2>
<p>
The API is a REST API and you can access it by using the same URL you
are using for the web interface but with the <code>/api/</code>
prefix.
</p>
<p>
For example, if you access a project with the
URL <code>http://www.example.com/p/myproject/</code>, you have the
following API URLs available:
</p>
<ul>
<li><code>http://www.example.com/api/p/myproject/issues/</code>: list the open issues.</li>
<li><code>http://www.example.com/api/p/myproject/issues/create/</code>: create a new issue.</li>
</ul>
<p>
The answer you get is JSON and UTF-8 encoded.
</p>
<h2 id="q-authentication">How to authenticate the queries?</h2>
<p>
Authentication is really simple and is optional. If you do not
authenticate your queries, you will have the same rights as an
anonymous user visiting the normal web interface.
</p>
<p>
To authenticate your query, you need to provide 3 parameters to your
requests, the parameters are the followings:
</p>
<ul>
<li><code>_login</code>: your login.</li>
<li><code>_salt</code>: a random salt string.</li>
<li><code>_hash</code>: the sha1 hash created from the concatenation of the random salt string and the API key.</li>
</ul>
<p>
Please note that the 3 parameters are all starting with the underscore
"_" character.
</p>
<p>
An example of PHP code to generate the <code>_hash</code> value is:
</p>
<pre>
&lt;?php
$api_key = '1234567890abcdefghijklmnopqrstuvwxyz';
$_salt = rand(10000, 999999);
$_hash = sha1($_salt.$api_key);
echo sprintf("_salt: %s\n", $_salt);
echo sprintf("_hash: %s\n", $_hash);
?&gt;
</pre>
<p>
If you replace the string '123...xyz' with your own API key and
execute this script, you will have as output something like that:
</p>
<pre>
_salt: 123456
_hash: 1357924680acegikmoqsuwybdfhjlnprtvxz
</pre>
<p>
Together with your login, you will be able to use those values to
authenticate a query.
</p>
{/block} {block context}
<p>{trans 'Here we are, just to help you.'}</p>
<h2>{trans 'Projects'}</h2>
<ul>{foreach $projects as $p}
<li><a href="{url 'IDF_Views_Project::home', array($p.shortname)}">{$p}</a></li>
{/foreach}</ul>
{/block}
src/IDF/templates/idf/faq.html
44
55
66
7
78
89
910
......
3738
3839
3940
41
42
43
44
45
4046
4147
4248
<ul>
<li><a href="#q-keyboard">{trans 'What are the keyboard shortcuts?'}</a></li>
<li><a href="#q-duplicate">{trans 'How to mark an issue as duplicate?'}</a></li>
<li><a href="#q-api">{trans 'What is the API and how to use it?'}</a></li>
</ul>
<h2 id="q-keyboard">{trans 'What are the keyboard shortcuts?'}</h2>
<li>Change the status of the current issue to <em>Duplicate</em>.</li>
<li>Submit the changes.</li>
</ol>{/blocktrans}
<h2 id="q-api">{trans 'What is the API and how to use it?'}</h2>
<p>{blocktrans}The API (Application Programming Interface) is used to interact with InDefero with another program. For example, this can be used to create a desktop program to submit new tickets easily.{/blocktrans}</p>{aurl 'url', 'IDF_Views::faqApi'}
<p>{blocktrans}<a href="{$url}">Learn more about the API</a>.{/blocktrans}</p>
{/block}
{block context}
src/IDF/templates/idf/user/myaccount.html
4141
4242
4343
44
45
46
47
48
49
4450
4551
4652
......
5157
5258
5359
60
5461
62
5563
5664
5765
</td>
</tr>
<tr>
<th>{trans 'API key'}:</th>
<td><span class="mono">{$api_key}</span><br />
<span class="helptext">{trans 'Your API key will automatically be regenerated if you change your password.'}</span>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td><input type="submit" value="{trans 'Update Your Account'}" name="submit" /> | <a href="{url 'IDF_Views::index'}">{trans 'Cancel'}</a>
</td>
{block context}
<div class="issue-submit-info">
<p>{trans 'If possible, use your real name. By using your real name, people will have more trust in your comments and remarks.'}</p>
<p>{trans 'The API key is used to interact with this website using a program.'}</p>
</div>{/block}
{block javascript}<script type="text/javascript">
document.getElementById('id_first_name').focus()
</script>

Archive Download the corresponding diff file

Page rendered in 0.11803s using 13 queries.