3 * Copyright (c) Institut TELECOM, 2010. All Rights Reserved.
5 * Originally written by Sabri LABBENE, 201O
7 * Codendi is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * Codendi is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Codendi. If not, see <http://www.gnu.org/licenses/>.
21 require_once 'CmController.php';
22 require_once 'CodendiOSLCConnector.php';
24 class CodendiCmController extends CmController {
28 * This will be the OSLC-CM controller managing the business logic of the application
33 * Defines accepted mime-types for queries, and corresponding
36 * Order is important for the XML variants :
37 * the first one is the default returned when only basic XML is required
41 private static $supportedAcceptMimeTypes = array();
44 * Init Codendi REST controller.
46 public function init() {
47 self::$supportedAcceptMimeTypes = parent::getSupportedAcceptMimeTypes();
49 parent::loadModelClasses('ChangeRequests');
51 // Now do things that relate to the REST framework
53 $req = $this->getRequest();
55 if(($req->getActionName()=='post')||($req->getActionName()=='put')) {
56 $accept = $req->getHeader('Content-Type');
58 elseif($req->getActionName()=='get') {
59 $accept = $req->getHeader('Accept');
62 $action = $req->getActionName();
64 $mime = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $action);
69 // determine output format
70 if (isset(self::$supportedAcceptMimeTypes[$action])) {
71 if (isset(self::$supportedAcceptMimeTypes[$action][$accept])) {
72 $format = self::$supportedAcceptMimeTypes[$action][$accept];
73 $req->setParam('format', $format);
77 $contextSwitch = $this->_helper->getHelper('contextSwitch');
79 // we'll handle JSON ourselves
80 $contextSwitch->setAutoJsonSerialization(false);
82 foreach (self::$supportedAcceptMimeTypes as $action => $typesarr) {
83 //print_r(array_unique(array_values($typesarr)));
84 $types = array_unique(array_values($typesarr));
85 //print_r("Typesarr : ".$typesarr);
86 $contextSwitch->addActionContext($action, $types)->initContext();
89 // Create an OSLC Controller for Codendi.
90 $this->oslc = new CodendiOSLCConnector();
93 public function getAction(){
94 $params = $this->getRequest()->getParams();
96 // Check if Authorization header was set in the request.
97 $auth = $this->getRequest()->getHeader('Authorization');
98 if (strlen($auth) != 0) {
99 // check authentication
100 if(!$this->retrieveAuthentication($login)){
101 throw new Exception('Invalid authentication provided !');
105 // handle OSLC services catalog access (http://open-services.net/bin/view/Main/OslcServiceProviderCatalogV1)
106 if ( isset($params['id']) && ($params['id'] == "oslc-services")) {
107 $this->_forward('oslcServiceCatalog');
111 // handle OSLC-CM service document access
112 elseif (isset($params['oslc-cm-service'])) {
113 $this->_forward('oslcCmServiceDocument');
117 // Now, do the OSLC-CM resources access work
118 // if no bug was mentioned, then return a resource collection
119 if (!array_key_exists('bug', $params)) {
120 // forward to an independant action so that it has its own views
121 // (see readresourcecollectionAction())
122 $this->_forward('readResourceCollection');
124 elseif(array_key_exists('bug', $params)) {
125 // now we're indeed getting one single resource
126 $this->_forward('readResource');
128 throw new NotFoundException("Resource ".$this->getRequest()->getRequestUri()." was not found on the server!");
131 // This is not explicitely required in OSLC-CM V1
132 // In the case of RDF+XML requested, mention it explicitely : LOD friendly
133 $req = $this->getRequest();
134 $accept = $req->getHeader('Accept');
136 case (strstr($accept, 'application/rdf+xml')):
137 $resp = $this->getResponse()->setHeader('Content-Type', 'application/rdf+xml');
143 * Handles PUT action as routed by Zend_Rest_Route
145 * Update of an existing changerequest
146 * Will be invoked if PUT or if POST on a path relating to resources (due to Zend REST route behaviour)
147 * So in case of POST, will pass the handling to postAction()
149 * @return unknown_type
151 public function putAction(){
152 $req = $this->getRequest();
154 // in case invoked like POST on .../cm/project/whatever we arrive to putAction
155 // so we check such case and then redirect to postAction() if needed
156 if ($req->isPost()) {
159 // otherwise it is indeed a PUT and we are trying to modify a change request
161 // do authentication.
163 $authenticated = $this->retrieveAuthentication($login);
165 // Basic auth requested
166 if (!$authenticated) {
167 // not succesfully authd as $login
169 throw new Exception('Invalid authentication provided !');
173 $contenttype = $req->getHeader('Content-Type');
174 $contenttype = $contenttype ? $contenttype : 'none';
176 switch($contenttype) {
177 case 'application/x-oslc-cm-change-request+xml':
178 case 'application/x-oslc-cm-change-request+json':
179 case 'application/xml':
182 throw new Exception('Unknown Content-Type for method put : '. $contenttype .' !');
188 $params = $req->getParams();
190 if (array_key_exists('id', $params)) {
191 $identifier = $req->getParam('id');
193 $identifier = $req->getParam('bug');
194 if (! isset($identifier)) {
195 throw new Exception('No change request id provided !');
199 // checking if modification
201 $modifiedproperties = null;
203 $oslc_cm_properties = $req->getParam('oslc_cm_properties');
204 if (isset($oslc_cm_properties)) {
205 $modifiedproperties = explode(',', $oslc_cm_properties);
206 if (array_key_exists('identifier', $modifiedproperties)) {
207 throw new Exception('Identifier cannot be modified !');
211 $body = file_get_contents('php://input');
213 // TODO: This should be done by $this->oslc
214 switch($contenttype) {
215 case 'application/x-oslc-cm-change-request+xml':
216 case 'application/xml':
217 // extract values from XML
218 $newchangerequest = CodendiChangeRequest::CreateCodendiArrayFromXml($body);
220 case 'application/x-oslc-cm-change-request+json':
221 // extract values from JSON.
222 $newchangerequest = CodendiChangeRequest::CreateCodendiArrayFromJson($body);
228 if(!$this->oslc->checkChangeRequestExists($identifier)) {
229 throw new Exception("Change Request to be updated doesn't exist!");
231 // Proceed to change request update
232 $this->oslc->updateChangeRequest($identifier, $newchangerequest, $modifiedproperties);
240 public function postAction(){
241 $req = $this->getRequest();
243 // check that we're indeed invoked by a POST request
244 if(! $req->isPost()) {
245 throw new Exception('postAction invoked without POST !');
248 $params = $req->getParams();
250 // do authentication.
252 $authenticated = $this->retrieveAuthentication($login);
254 // Basic auth requested
255 if (!$authenticated) {
256 // not succesfully authd as $login
258 throw new Exception('Invalid authentication provided !');
262 $contenttype = $req->getHeader('Content-Type');
263 $contenttype = $contenttype ? $contenttype : 'none';
265 switch($contenttype) {
266 case 'application/x-oslc-cm-change-request+xml':
267 case 'application/x-oslc-cm-change-request+xml; charset=UTF-8':
268 case 'application/x-oslc-cm-change-request+json':
269 case 'application/json':
270 case 'application/xml':
273 throw new UnsupportedMediaTypeException('Unknown Content-Type for method post : '. $contenttype .' !');
277 // used for PhpUnit tests.
278 if(APPLICATION_ENV=='testing') {
279 $body = $_POST['xml'];
281 $body = file_get_contents('php://input');
285 if(array_key_exists('project',$params)) {
286 if (array_key_exists('tracker', $params)) {
287 // create a change request
288 switch($contenttype) {
289 case 'application/x-oslc-cm-change-request+xml':
290 case 'application/xml':
291 case 'application/x-oslc-cm-change-request+xml; charset=UTF-8':
292 $newchangerequest = CodendiChangeRequest::CreateCodendiArrayFromXml($body);
294 case 'application/x-oslc-cm-change-request+json':
295 $newchangerequest = CodendiChangeRequest::CreateCodendiArrayFromJson($body);
299 $creationparams = array('project' => $params['project'],
300 'tracker' => $params['tracker'],
301 'new' => $newchangerequest);
303 // pass the creation work to the OSLC connector
304 $identifier = $this->oslc->createChangeRequest($creationparams);
306 throw new ConflictException('Need a valid tracker to create a change request');
309 throw new ConflictException('Need a valid project and tracker to create change request !');
312 // prepare redirection
313 $httpScheme = $this->getRequest()->getScheme();
314 $httpHost = $this->getRequest()->getHttpHost();
315 $requestUri = $this->getRequest()->getRequestUri();
316 $baseURL = $this->getFrontController()->getBaseUrl();
317 $controllerName = $this->getFrontController()->getDefaultControllerName();
319 if(APPLICATION_ENV=='testing') {
320 $newlocation = '/'.$controllerName.'/project/'.$params['project'].'/tracker/'.$params['tracker'].'/bug/'.$identifier;
322 $newlocation = $httpScheme.'://'.$httpHost.$baseURL.'/'.$controllerName.'/project/'.$params['project'].'/tracker/'.$params['tracker'].'/bug/'.$identifier;
328 //redirect to new change request
329 $this->getResponse()->setRedirect($newlocation,201);
332 public function indexAction(){
336 public function deleteAction(){
341 * Retrieve an individual resource and populates the view of an OSLC CM ChangeRequest
343 * @param string $identifier
346 public function readresourceAction() {
348 $params = $this->getRequest()->getParams();
349 $content_type = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
350 if (! $content_type) {
351 $this->_forward('UnknownAcceptType','error');
355 $identifier = $params['bug'];
357 // prepare resource URI
358 $httpScheme = $this->getRequest()->getScheme();
359 $httpHost = $this->getRequest()->getHttpHost();
360 $requestUri = $this->getRequest()->getRequestUri();
361 $changerequestURI = $httpScheme.'://'.$httpHost.$requestUri;
363 // Check if some specific fields have been requested for the ChangeRequest.
364 if (isset($params['oslc_properties'])){
365 $preparedChangeRequest = $this->oslc->fetchChangeRequest($identifier, $changerequestURI, $params['oslc_properties']);
367 $preparedChangeRequest = $this->oslc->fetchChangeRequest($identifier, $changerequestURI);
370 if(isset($preparedChangeRequest)) {
372 // populate the view with the model
373 foreach($preparedChangeRequest as $field => $value) {
374 $this->view->{$field} = $value;
377 $this->getResponse()->setHeader('Content-Type', $content_type);
379 $this->view->missing_resource = $identifier;
380 $this->_forward('ResNotFound','error');
384 public function readresourcecollectionAction() {
386 $content_type = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
387 if (!$content_type) {
388 throw new NotAcceptableException("Accept header ".$this->getRequest()->getHeader('Accept')." not supported!");
392 $req = $this->getRequest();
393 $params = $req->getParams();
395 // load the model. Will fetch requested change requests from the db.
396 $params = $this->oslc->init($params);
398 // construct ATOM feed-like header
399 $httpScheme = $this->getRequest()->getScheme();
400 $httpHost = $this->getRequest()->getHttpHost();
401 $requestUri = $this->getRequest()->getRequestUri();
402 $requestUri = str_replace('bugs','bug', $requestUri);
403 $requestUri = preg_replace("/project.*/", 'bug/', $requestUri);
404 $requestUri = $requestUri.(($requestUri[strlen($requestUri)-1]=='/')?'':'/');
405 $prefix = $httpScheme.'://'.$httpHost.$requestUri;
408 $collection = $this->oslc->getResourceCollection($prefix);
410 // construct an intermediate array that will be used to populate the view
411 $preparedCollection = array ('id' => $httpScheme.'://'.$httpHost.$requestUri,
412 'collection' => $collection
414 // Add request params so they ca reach views.
415 foreach($params as $key => $value){
416 $preparedCollection[$key] = $value;
419 // populate the view with the loaded values
420 foreach($preparedCollection as $key => $value) {
421 $this->view->$key = $value;
424 //print_r($this->view);
425 $this->getResponse()->setHeader('Content-Type', $content_type);
429 * Handle OSLC services catalog access.
431 public function oslcservicecatalogAction() {
433 $content_type = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
434 if (!$content_type) {
435 throw new NotAcceptableException("Accept header ".$this->getRequest()->getHeader('Accept')." not supported!");
439 // each project will generate its own service description
440 $proj_arr = $this->oslc->getProjectsList();
442 $this->view->projects = $proj_arr;
444 $this->getResponse()->setHeader('Content-Type', $content_type);
449 * Handles OSLC service document (service document) access.
450 * TODO: Implement service document details.
452 public function oslccmservicedocumentAction() {
453 $content_type = parent::checkSupportedActionMimeType(self::$supportedAcceptMimeTypes, $this->getRequest()->getActionName());
455 throw new NotAcceptableException("Accept header ".$this->getRequest()->getHeader('Accept')." not supported!");
459 $req = $this->getRequest();
460 $params = $req->getParams();
461 $project = $params['oslc-cm-service'];
462 $this->view->project = $project;
464 $this->getResponse()->setHeader('Content-Type', $content_type);
468 * Performs authentication according to the configured AUTH_TYPE configured
470 * @param string $login
471 * @return True if auth is valid, in which case $login is modified.
472 * If there was actually no auth requested, then return False, but $login will be set to null.
474 private function retrieveAuthentication(&$login) {
477 return $this->retrieveRequestAuthHttpBasic();
480 return $this->checkOauthAuthorization($login);
483 throw new BadRequestException('Unsupported AUTH_TYPE : '. AUTH_TYPE .' !');
489 * Helper function that performs HTTP Basic authentication from request parameters/headers
491 * @return True if auth is valid, in which case $login is modified.
492 * If there was actually no auth requested, then return False, but $login will be set to null.
494 private function retrieveRequestAuthHttpBasic() {
495 // extract login and password from Basic auth
501 $request = $this->getRequest();
502 $auth = $request->getHeader('Authorization');
504 if (strlen($auth) != 0) {
505 $auth = explode(' ',$auth);
506 if($auth[0] == 'Basic') {
507 $basic = base64_decode($auth[1]);
508 $basic = explode(':',$basic);
511 $password = $basic[1];
513 throw new BadRequestException('Unsupported auth method : '. $auth[0] .' !');
516 // Do authentication in Codendi
517 if(isset($password)) {
518 $user = UserManager::instance()->login($login, $password);
519 if($user->isLoggedIn()) {