0

I'm trying to create an issue in JIRA from my PHP YII2 framework. What I'm trying to do is - Whenever I create a new version in my system I want that an issue JIRA will be created automatically for this version. I've found examples in CURL but so far it's not working. I don't even get any error message. It creates a new version in my system, but nothing happens in JIRA, looks like it's not even trying to connect to JIRA.

This is my VersionController.php -

<?php

namespace app\controllers;

require_once("Curl.php");

use Yii;
use app\models\Version;
use app\models\VersionSearch;
use app\models\Binfile;
use app\models\VersionStatus;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
use yii\swiftmailer\Mailer;
use yii\web\UnauthorizedHttpException;
use linslin\yii2\curl;
use understeam\yii2\httpclient;
use understeam\yii2\jira;

/**
 * VersionController implements the CRUD actions for Version model.
 */



class VersionController extends Controller
{
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'delete' => ['post'],
                ],
            ],
            'access' => [
                        'class' => \yii\filters\AccessControl::className(),
                        'only' => ['index','create','update','view'],
                        'rules' => [
                            // allow authenticated users
                            [
                                'allow' => true,
                                'roles' => ['@'],
                            ],
                            // everything else is denied
                        ],
                    ],            
        ];
    }

    /**
     * Lists all Version models.
     * @return mixed
     */



    public function actionIndex()
    {

        if (\Yii::$app->user->can('deleteVersion')) {
            $template = '{view} {update} {delete} ';    
        }
        else if((\Yii::$app->user->can('changeStatus')) || (\Yii::$app->user->can('uploadVersion'))){
            $template = '{view} {update}';
        }
        else{$template = '{view}';
        }


        $searchModel = new VersionSearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->render('index', [
            'searchModel' => $searchModel,
            'dataProvider' => $dataProvider,
            'template' => $template,
        ]);
    }

    /**
     * Displays a single Version model.
     * @param integer $id
     * @return mixed
     */
    public function actionView($id)
    {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }

    /**
     * Creates a new Version model.
     * If creation is successful, the browser will be redirected to the 'view' page.
     * @return mixed
     */
    public function actionCreate()
    {
        if(!\Yii::$app->user->can('createVersion')){
            throw new UnauthorizedHttpException("Access denied: You don't have permission to create a version");
        }else{
                $model = new Version();

                if ($model->load(Yii::$app->request->post()) && $model->save()) {
                    //$this->actionSend();
                    $this->actionPostExample();
                //  $this->actionGetExample();

                    return $this->redirect(['view', 'id' => $model->id]);

                } else {
                    return $this->render('create', [
                        'model' => $model,
                    ]);
                }
        }
    }

    /**
     * Updates an existing Version model.
     * If update is successful, the browser will be redirected to the 'view' page.
     * @param integer $id
     * @return mixed
     */
    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('update', [
                'model' => $model,
            ]);
        }
    }

    /**
     * Deletes an existing Version model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function actionDelete($id)
    {
        if(!\Yii::$app->user->can('isAdmin')){
            throw new UnauthorizedHttpException("Access denied: Only Admin can perform this action!!!");
        }else{
                $this->findModel($id)->delete();

                return $this->redirect(['index']);
        }
    }

    /**
     * Finds the Version model based on its primary key value.
     * If the model is not found, a 404 HTTP exception will be thrown.
     * @param integer $id
     * @return Version the loaded model
     * @throws NotFoundHttpException if the model cannot be found
     */
    protected function findModel($id)
    {
        if (($model = Version::findOne($id)) !== null) {
            return $model;
        } else {
            throw new NotFoundHttpException('The requested page does not exist.');
        }
    }

    public function actionSend()
    {
         Yii::$app->mailer->compose()
        ->setFrom('[email protected]')
        ->setTo('[email protected]')
        ->setSubject('Message test')
        ->setTextBody('Plain text content')
        ->setHtmlBody('<b>test</b>')
        ->send();

    }

    public function actionPostExample()
    {


            define('JIRA_URL', 'http://jiratest.../');
            define('USERNAME', 'jenya');
            define('PASSWORD', 'password');



            function post_to($resource, $data)
            {
                $jdata = json_encode($data);
                $ch = curl_init();
                curl_setopt_array($ch, array(
                CURLOPT_POST => 1,
                CURLOPT_URL => JIRA_URL . '/rest/api/latest/' . $resource,
                CURLOPT_USERPWD => USERNAME . ':' . PASSWORD,
                CURLOPT_POSTFIELDS => $jdata,
                CURLOPT_HTTPHEADER => array('Content-type: application/json'),
                CURLOPT_RETURNTRANSFER => true
            ));
            $result = curl_exec($ch);
            curl_close($ch);
            return json_decode($result);
            }


            $new_issue = array(
                'fields' => array(
                'project' => array('key' => 'key'),
                'issuetype' => array('name' => 'Version Integration Task'),
                'summary' => 'Test via REST',
                'components' => 'General',
                'customfield_10110' => 'name of value',
                'fixVersions' => 'name of version',
                'Description' => 'Description of issue goes here.',

                //'labels' => array('a','b')
                )
            );

        function create_issue($issue) 
        {
            return post_to('issue', $issue);
        }

        $result = create_issue($new_issue);

         if (property_exists($this, 'errors')) 
        {
            echo "Error(s) creating issue:\n";
            var_dump($result);
        } 
        else 
        {
            echo "New issue created at " . JIRA_URL ."/browse/{$result}\n";
        } 

    }

} 

Curl.php-

<?php
/**
 * Yii2 cURL wrapper
 * With RESTful support.
 *
 * @category  Web-yii2
 * @package   yii2-curl
 * @author    Nils Gajsek <[email protected]>
 * @copyright 2013-2015 Nils Gajsek<[email protected]>
 * @license   http://opensource.org/licenses/MIT MIT Public
 * @version   1.0.7
 * @link      http://www.linslin.org
 *
 */

namespace linslin\yii2\curl;

use Yii;
use yii\base\Exception;
use yii\helpers\Json;
use yii\web\HttpException;

/**
 * cURL class
 */
class Curl
{

    // ################################################ class vars // ################################################


    /**
     * @var string
     * Holds response data right after sending a request.
     */
    public $response = null;

    /**
     * @var integer HTTP-Status Code
     * This value will hold HTTP-Status Code. False if request was not successful.
     */
    public $responseCode = null;

    /**
     * @var array HTTP-Status Code
     * Custom options holder
     */
    private $_options = array();


    /**
     * @var object
     * Holds cURL-Handler
     */
    private $_curl = null;


    /**
     * @var array default curl options
     * Default curl options
     */
    private $_defaultOptions = array(
        CURLOPT_USERAGENT      => 'Yii2-Curl-Agent',
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_CONNECTTIMEOUT => 30,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADER         => false,
    );



    // ############################################### class methods // ##############################################

    /**
     * Start performing GET-HTTP-Request
     *
     * @param string  $url
     * @param boolean $raw if response body contains JSON and should be decoded
     *
     * @return mixed response
     */
    public function get($url, $raw = true)
    {
        return $this->_httpRequest('GET', $url, $raw);
    }


    /**
     * Start performing HEAD-HTTP-Request
     *
     * @param string $url
     *
     * @return mixed response
     */
    public function head($url)
    {
        return $this->_httpRequest('HEAD', $url);
    }


    /**
     * Start performing POST-HTTP-Request
     *
     * @param string  $url
     * @param boolean $raw if response body contains JSON and should be decoded
     *
     * @return mixed response
     */
    public function post($url, $raw = true)
    {
        return $this->_httpRequest('POST', $url, $raw);
    }


    /**
     * Start performing PUT-HTTP-Request
     *
     * @param string  $url
     * @param boolean $raw if response body contains JSON and should be decoded
     *
     * @return mixed response
     */
    public function put($url, $raw = true)
    {
        return $this->_httpRequest('PUT', $url, $raw);
    }


    /**
     * Start performing DELETE-HTTP-Request
     *
     * @param string  $url
     * @param boolean $raw if response body contains JSON and should be decoded
     *
     * @return mixed response
     */
    public function delete($url, $raw = true)
    {
        return $this->_httpRequest('DELETE', $url, $raw);
    }


    /**
     * Set curl option
     *
     * @param string $key
     * @param mixed  $value
     *
     * @return $this
     */
    public function setOption($key, $value)
    {
        //set value
        if (in_array($key, $this->_defaultOptions) && $key !== CURLOPT_WRITEFUNCTION) {
            $this->_defaultOptions[$key] = $value;
        } else {
            $this->_options[$key] = $value;
        }

        //return self
        return $this;
    }


    /**
     * Unset a single curl option
     *
     * @param string $key
     *
     * @return $this
     */
    public function unsetOption($key)
    {
        //reset a single option if its set already
        if (isset($this->_options[$key])) {
            unset($this->_options[$key]);
        }

        return $this;
    }


    /**
     * Unset all curl option, excluding default options.
     *
     * @return $this
     */
    public function unsetOptions()
    {
        //reset all options
        if (isset($this->_options)) {
            $this->_options = array();
        }

        return $this;
    }


    /**
     * Total reset of options, responses, etc.
     *
     * @return $this
     */
    public function reset()
    {
        if ($this->_curl !== null) {
            curl_close($this->_curl); //stop curl
        }

        //reset all options
        if (isset($this->_options)) {
            $this->_options = array();
        }

        //reset response & status code
        $this->_curl = null;
        $this->response = null;
        $this->responseCode = null;

        return $this;
    }


    /**
     * Return a single option
     *
     * @param string|integer $key
     * @return mixed|boolean
     */
    public function getOption($key)
    {
        //get merged options depends on default and user options
        $mergesOptions = $this->getOptions();

        //return value or false if key is not set.
        return isset($mergesOptions[$key]) ? $mergesOptions[$key] : false;
    }


    /**
     * Return merged curl options and keep keys!
     *
     * @return array
     */
    public function getOptions()
    {
        return $this->_options + $this->_defaultOptions;
    }


    /**
     * Get curl info according to http://php.net/manual/de/function.curl-getinfo.php
     *
     * @return mixed
     */
    public function getInfo($opt = null)
    {
        if ($this->_curl !== null && $opt === null) {
            return curl_getinfo($this->_curl);
        } elseif ($this->_curl !== null && $opt !== null)  {
            return curl_getinfo($this->_curl, $opt);
        } else {
            return [];
        }
    }


    /**
     * Performs HTTP request
     *
     * @param string  $method
     * @param string  $url
     * @param boolean $raw if response body contains JSON and should be decoded -> helper.
     *
     * @throws Exception if request failed
     *
     * @return mixed
     */
    private function _httpRequest($method, $url, $raw = false)
    {
        //set request type and writer function
        $this->setOption(CURLOPT_CUSTOMREQUEST, strtoupper($method));

        //check if method is head and set no body
        if ($method === 'HEAD') {
            $this->setOption(CURLOPT_NOBODY, true);
            $this->unsetOption(CURLOPT_WRITEFUNCTION);
        }

        //setup error reporting and profiling
        Yii::trace('Start sending cURL-Request: '.$url.'\n', __METHOD__);
        Yii::beginProfile($method.' '.$url.'#'.md5(serialize($this->getOption(CURLOPT_POSTFIELDS))), __METHOD__);

        /**
         * proceed curl
         */
        $this->_curl = curl_init($url);
        curl_setopt_array($this->_curl, $this->getOptions());
        $body = curl_exec($this->_curl);

        //check if curl was successful
        if ($body === false) {
            switch (curl_errno($this->_curl)) {

                case 7:
                    $this->responseCode = 'timeout';
                    return false;
                    break;

                default:
                    throw new Exception('curl request failed: ' . curl_error($this->_curl) , curl_errno($this->_curl));
                    break;
            }
        }

        //retrieve response code
        $this->responseCode = curl_getinfo($this->_curl, CURLINFO_HTTP_CODE);
        $this->response = $body;

        //end yii debug profile
        Yii::endProfile($method.' '.$url .'#'.md5(serialize($this->getOption(CURLOPT_POSTFIELDS))), __METHOD__);

        //check responseCode and return data/status
        if ($this->getOption(CURLOPT_CUSTOMREQUEST) === 'HEAD') {
            return true;
        } else {
            $this->response = $raw ? $this->response : Json::decode($this->response);
            return $this->response;
        }
    }
}

I would really appreciate your help, I don't know what else to try. Thanks in advance.

2
  • What is the error you get back from JIRA (the HTTP response code and body of that response?). If the diagnostic response doesn't help, to further debug this I suggest this approach: Capture the body of the HTTP POST used to create the issue, remove all none-essential details and submit manually via curl, then add back parts of the original body until something breaks. If the minimal issue JSON body fails post it here and I'll take a look. Commented Jan 21, 2016 at 12:09
  • The problem is, and I forgot to mention it in my original post- I don't get any error. Nothing. On my part it creates a new version in my system, but nothing happens in JIRA, no new issue there.. Commented Jan 21, 2016 at 14:34

2 Answers 2

0

There is a bug in VersionController.php / actionPostExample() / post_to. As written I would expect the HTTP post to JIRA to result in an HTTP 404 response.

This line:

CURLOPT_URL => JIRA_URL . '/rest/api/latest/' . $resource,

Should be:

CURLOPT_URL => JIRA_URL . '/rest/api/2/' . $resource,

../latest/... is used in the JIRA api docs pages but it is not part of the rest API. .../rest/api/2/... is compatible with JIRA 6 and 7.

Sign up to request clarification or add additional context in comments.

5 Comments

I fixed it, but nothing changed, as I said in another comment above- I don't get any error, as if it's not even trying to communicate with JIRA.
I'm a little confused by this section:
$result = curl_exec($ch); It looks like you would avoid some complexity by calling the public function post($url, $raw = true) defined in Curl.php. In either case you might have a better idea what it's doing if you log a message just before you make the request then log the result. You should always get some kind of HTTP result good bad or a timeout. Also well spotted on removing the password (I didn't want to draw attention to it while it was still there)
I added an 'if' function right after the: $result = curl_exec($ch); , to see if it fails or not, and surprisingly I get "true". Meaning that this function is working, but still no new issue in JIRA. So if I'm not getting any response, it means it's not even sending any request isn't it? Maybe I missed something in defining the connection to JIRA?
It's not 100% clear to me when curl_exec returns true (or 1) when using CURLOPT_POST => 1, or CURLOPT_RETURNTRANSFER => true Take a look at stackoverflow.com/questions/5514139/… . To confirm if the request is working or at least the behavior of curl_exec try putting something clearly incorrect in the URL to see what a bad response should be. e.g. CURLOPT_URL => JIRA_URL . 'BROKEN/rest/api/latest/' . $resource,
0

I did it. This is the function that works for me:

function post_to($resource, $data)
        {
            $jdata = json_encode($data);
            $ch = curl_init();
            curl_setopt_array($ch, array(
            CURLOPT_POST => true,
            CURLOPT_URL => JIRA_URL . '/rest/api/latest/' . $resource,
            CURLOPT_USERPWD => USERNAME . ':' . PASSWORD,
            CURLOPT_POSTFIELDS => $jdata,
            CURLOPT_HTTPHEADER => array('Content-type: application/json'),
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            //CURLOPT_RETURNTRANSFER => true
        ));  

After that I had to fix several things in JSON which are specific for my project.

For easier debugging you can add this:

error_reporting(E_ALL);
        ini_set('display_errors', 1);

To get clear errors.

1 Comment

We like to set E_ALL on our dev machines, then E_ALL ~E_NOTICE ~E_DEPRICATED on the testing/QA boxes. Once it gets to production only criticals and exceptions are set on. Forces the devs to write better code but also prevents an overflow of useless errors in production.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.