<?php
/**
 * TestLink Open Source Project http://testlink.sourceforge.net/
 * This script is distributed under the GNU General Public License 2 or later.
 *
 * @filesource xmlrpc.class.php
 *
 * @author Asiel Brumfield <asielb@users.sourceforge.net>
 * @package TestlinkAPI
 *
 *          Testlink API makes it possible to interact with Testlink
 *          using external applications and services. This makes it possible to report test results
 *          directly from automation frameworks as well as other features.
 *
 *          See examples for additional detail
 * @example sample_clients/java/org/testlink/api/client/sample/TestlinkAPIXMLRPCClient.java java client sample
 * @example sample_clients/php/clientSample.php php client sample
 * @example sample_clients/ruby/clientSample.rb ruby client sample
 * @example sample_clients/python/clientSample.py python client sample
 *
 */

/**
 * IXR is the class used for the XML-RPC server
 */
define( "TL_APICALL", 'XML-RPC' );

require_once("../../../../config.inc.php");
require_once("common.php");
require_once("xml-rpc/class-IXR.php");
require_once("api.const.inc.php");
require_once("APIErrors.php");

/**
 * The entry class for serving XML-RPC Requests
 *
 * See examples for additional detail
 *
 * @example sample_clients/java/org/testlink/api/client/sample/TestlinkAPIXMLRPCClient.java java client sample
 * @example sample_clients/php/clientSample.php php client sample
 * @example sample_clients/ruby/clientSample.rb ruby client sample
 * @example sample_clients/python/clientSample.py python client sample
 *
 * @author Asiel Brumfield <asielb@users.sourceforge.net>
 * @package TestlinkAPI
 * @since Class available since Release 1.8.0
 */
class TestlinkXMLRPCServer extends IXR_Server {
    public static $version = "1.1";
    const OFF = false;
    const ON = true;
    const BUILD_GUESS_DEFAULT_MODE = OFF;
    const SET_ERROR = true;
    const CHECK_PUBLIC_PRIVATE_ATTR = true;
    const THROW_ON_ERROR = true;

    /**
     * The DB object used throughout the class
     *
     * @access protected
     */
    protected $dbObj = null;
    protected $tables = null;
    protected $tcaseMgr = null;
    protected $tprojectMgr = null;
    protected $tplanMgr = null;
    protected $tplanMetricsMgr = null;
    protected $reqSpecMgr = null;
    protected $reqMgr = null;
    protected $platformMgr = null;
    protected $itsMgr = null;

    /**
     * Whether the server will run in a testing mode
     */
    protected $testMode = false;

    /**
     * userID associated with the devKey provided
     */
    protected $userID = null;

    /**
     * UserObject associated with the userID
     */
    protected $user = null;

    /**
     * array where all the args are stored for requests
     */
    protected $args = null;

    /**
     * array where error codes and messages are stored
     */
    protected $errors = array();

    /**
     * The api key being used to make a request
     */
    protected $devKey = null;

    /**
     * boolean to allow a method to invoke another method and avoid double auth
     */
    protected $authenticated = false;

    /**
     * The version of a test case that is being used
     */
    /**
     * This value is setted in following method:
     */
    /**
     * _checkTCIDAndTPIDValid()
     */
    protected $tcVersionID = null;
    protected $versionNumber = null;

    /**
     * Mapping bewteen external & internal test case ID
     */
    protected $tcaseE2I = null;

    /**
     * needed in order to manage logs
     */
    protected $tlLogger = null;

    /**
     * #@+
     * string for parameter names are all defined statically
     * PLEASE define in DICTIONARY ORDER
     *
     * @static
     */
    public static $actionOnDuplicatedNameParamName = "actiononduplicatedname";
    public static $actionParamName = "action";
    public static $activeParamName = "active";
    public static $assignedToParamName = "assignedto";
    public static $automatedParamName = "automated";
    public static $authorLoginParamName = "authorlogin";
    public static $bugIDParamName = "bugid";
    public static $buildIDParamName = "buildid";
    public static $buildNameParamName = "buildname";
    public static $buildNotesParamName = "buildnotes";
    public static $checkDuplicatedNameParamName = "checkduplicatedname";
    public static $contentParamName = "content";
    public static $customFieldNameParamName = "customfieldname";
    public static $customFieldsParamName = "customfields";
    public static $deepParamName = "deep";
    public static $descriptionParamName = "description";
    public static $detailsParamName = "details";
    public static $devKeyParamName = "devKey";
    public static $executionIDParamName = "executionid";
    public static $executionOrderParamName = "executionorder";
    public static $executedParamName = "executed";
    public static $executeStatusParamName = "executestatus";
    public static $executionTypeParamName = "executiontype";
    public static $expectedResultsParamName = "expectedresults";
    public static $fileNameParamName = "filename";
    public static $fileTypeParamName = "filetype";
    public static $foreignKeyIdParamName = "fkid";
    public static $foreignKeyTableNameParamName = "fktable";
    public static $guessParamName = "guess";
    public static $getStepsInfoParamName = "getstepsinfo";
    public static $getKeywordsParamName = "getkeywords";
    public static $importanceParamName = "importance";
    public static $internalIDParamName = "internalid";
    public static $keywordIDParamName = "keywordid";
    public static $keywordNameParamName = "keywords";
    public static $linkIDParamName = "linkid";
    public static $nodeIDParamName = "nodeid";
    public static $nodeTypeParamName = "nodetype";
    public static $noteParamName = "notes";
    public static $openParamName = "open";
    public static $optionsParamName = "options";
    public static $orderParamName = "order";
    public static $overwriteParamName = "overwrite";
    public static $parentIDParamName = "parentid";
    public static $platformNameParamName = "platformname";
    public static $platformIDParamName = "platformid";
    public static $preconditionsParamName = "preconditions";
    public static $publicParamName = "public";
    public static $releaseDateParamName = "releasedate";
    public static $requirementsParamName = "requirements";
    public static $requirementIDParamName = "requirementid";
    public static $requirementDocIDParamName = "requirementdocid";
    public static $reqSpecIDParamName = "reqspecid";
    public static $scopeParamName = "scope";
    public static $summaryParamName = "summary";
    public static $statusParamName = "status";
    public static $stepsParamName = "steps";
    public static $testCaseIDParamName = "testcaseid";
    public static $testCaseExternalIDParamName = "testcaseexternalid";
    public static $testCaseNameParamName = "testcasename";
    public static $testCasePathNameParamName = "testcasepathname";
    public static $testCasePrefixParamName = "testcaseprefix";
    public static $testCaseVersionIDParamName = "testcaseid";
    public static $testModeParamName = "testmode";
    public static $testPlanIDParamName = "testplanid";
    public static $testPlanNameParamName = "testplanname";
    public static $testProjectIDParamName = "testprojectid";
    public static $testProjectNameParamName = "testprojectname";
    public static $testSuiteIDParamName = "testsuiteid";
    public static $testSuiteNameParamName = "testsuitename";
    public static $timeStampParamName = "timestamp";
    public static $titleParamName = "title";
    public static $urgencyParamName = "urgency";
    public static $userParamName = "user";
    public static $userIDParamName = "userid";
    public static $versionNumberParamName = "version";
    public static $estimatedExecDurationParamName = "estimatedexecduration";
    public static $executionDurationParamName = "execduration";
    public static $prefixParamName = "prefix";
    public static $testCaseVersionParamName = "tcversion";
    public static $itsNameParamName = "itsname";
    public static $itsEnabledParamName = "itsenabled";
    public static $copyTestersFromBuildParamName = "copytestersfrombuild";

    /**
     * #@-
     */

    /**
     * An array containing strings for valid statuses
     * Will be initialized using user configuration via config_get()
     */
    public $statusCode;
    public $codeStatus;

    /**
     * Constructor sets up the IXR_Server and db connection
     */
    public function __construct($callbacks = array()) {
        $this->dbObj = new database( DB_TYPE );
        $this->dbObj->db->SetFetchMode( ADODB_FETCH_ASSOC );
        $this->_connectToDB();

        global $g_tlLogger;
        $this->tlLogger = &$g_tlLogger;
        $this->tlLogger->setDB( $this->dbObj );

        // This close the default transaction that is started
        // when logger.class.php is included.
        $this->tlLogger->endTransaction();

        $this->tcaseMgr = new testcase( $this->dbObj );
        $this->tprojectMgr = new testproject( $this->dbObj );
        $this->tplanMgr = new testplan( $this->dbObj );
        $this->tplanMetricsMgr = new tlTestPlanMetrics( $this->dbObj );
        $this->reqSpecMgr = new requirement_spec_mgr( $this->dbObj );
        $this->reqMgr = new requirement_mgr( $this->dbObj );

        $this->tprojectMgr->setAuditEventSource( 'API-XMLRPC' );

        $this->tables = $this->tcaseMgr->getDBTables();

        $resultsCfg = config_get( 'results' );
        foreach( $resultsCfg['status_label_for_exec_ui'] as $key => $label ) {
            $this->statusCode[$key] = $resultsCfg['status_code'][$key];
        }

        if(isset( $this->statusCode['not_run'] )) {
            unset( $this->statusCode['not_run'] );
        }
        $this->codeStatus = array_flip( $this->statusCode );

        $this->initMethodYellowPages();
        $this->methods += $callbacks;

        // after changing IXR_Server() constructor to __contructor()
        parent::__construct( $this->methods );
    }

    /**
     */
    protected function _setArgs($args, $opt = null) {
        // TODO: should escape args
        $this->args = $args;

        if(isset( $this->args[self::$testProjectNameParamName] ) && ! isset( $this->args[self::$testProjectIDParamName] )) {
            $tprojMgr = new testproject( $this->dbObj );
            $name = trim( $this->args[self::$testProjectNameParamName] );
            $info = current( $this->tprojectMgr->get_by_name( $name ) );
            $this->args[self::$testProjectIDParamName] = $info['id'];
        }
    }

    /**
     * Set the BuildID from one place
     *
     * @param int $buildID
     * @access protected
     */
    protected function _setBuildID($buildID) {
        if(GENERAL_ERROR_CODE != $buildID) {
            $this->args[self::$buildIDParamName] = $buildID;
            return true;
        } else {
            $this->errors[] = new IXR_Error( INVALID_BUILDID, INVALID_BUILDID_STR );
            return false;
        }
    }

    /**
     * Set test case internal ID
     *
     * @param int $tcaseID
     * @access protected
     */
    protected function _setTestCaseID($tcaseID) {
        $this->args[self::$testCaseIDParamName] = $tcaseID;
    }

    /**
     * Set Build Id to latest build id(if test plan has builds)
     *
     * @return boolean
     * @access protected
     */
    protected function _setBuildID2Latest() {
        $tplan_id = $this->args[self::$testPlanIDParamName];
        $maxbuildid = $this->tplanMgr->get_max_build_id( $tplan_id );
        $status_ok =($maxbuildid > 0);
        if($status_ok) {
            $this->_setBuildID( $maxbuildid );
        }
        return $status_ok;
    }

    /**
     * connect to the db and set up the db object
     *
     * @access protected
     *
     * @internal revisions:
     *           20100731 - asimon - BUGID 3644(additional fix for BUGID 2607)
     *           20100711 - franciscom - BUGID 2607 - UTF8 settings for MySQL
     */
    protected function _connectToDB() {
        if(true == $this->testMode) {
            $this->dbObj->connect( TEST_DSN, TEST_DB_HOST, TEST_DB_USER, TEST_DB_PASS, TEST_DB_NAME );
        } else {
            $this->dbObj->connect( DSN, DB_HOST, DB_USER, DB_PASS, DB_NAME );
        }
        // asimon - BUGID 3644 & 2607 - $charSet was undefined here
        $charSet = config_get( 'charset' );
        if((DB_TYPE == 'mysql') &&($charSet == 'UTF-8')) {
            $this->dbObj->exec_query( "SET CHARACTER SET utf8" );
            $this->dbObj->exec_query( "SET collation_connection = 'utf8_general_ci'" );
        }
    }

    /**
     * authenticates a user based on the devKey provided
     *
     * This is the only method that should really be used directly to authenticate
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function authenticate($messagePrefix = '') {
        // check that the key was given as part of the args
        if(! $this->_isDevKeyPresent()) {
            $this->errors[] = new IXR_ERROR( NO_DEV_KEY, $messagePrefix . NO_DEV_KEY_STR );
            $this->authenticated = false;
            return false;
        } else {
            $this->devKey = $this->args[self::$devKeyParamName];
        }

        // make sure the key we have is valid
        if(! $this->_isDevKeyValid( $this->devKey )) {
            $this->errors[] = new IXR_Error( INVALID_AUTH, $messagePrefix . INVALID_AUTH_STR );
            $this->authenticated = false;
            return false;
        } else {
            // Load User
            $this->user = tlUser::getByID( $this->dbObj, $this->userID );
            $this->authenticated = true;

            $this->tlLogger->startTransaction( 'DEFAULT', null, $this->userID );
            return true;
        }
    }

    /**
     * checks if a user has requested right on test project, test plan pair.
     *
     * @param string $rightToCheck
     *            one of the rights defined in rights table
     * @param boolean $checkPublicPrivateAttr
     *           (optional)
     * @param map $context
     *           (optional)
     *            keys testprojectid,testplanid(both are also optional)
     *
     * @return boolean
     * @access protected
     *
     */
    protected function userHasRight($rightToCheck, $checkPublicPrivateAttr = false, $context = null) {
        $status_ok = true;
        $tprojectid = intval( isset( $context[self::$testProjectIDParamName] ) ? $context[self::$testProjectIDParamName] : 0 );

        if($tprojectid == 0 && isset( $this->args[self::$testProjectIDParamName] )) {
            $tprojectid = $this->args[self::$testProjectIDParamName];
        }

        if(isset( $context[self::$testPlanIDParamName] )) {
            $tplanid = $context[self::$testPlanIDParamName];
        } else {
            $tplanid = isset( $this->args[self::$testPlanIDParamName] ) ? $this->args[self::$testPlanIDParamName] : null;
        }

        $tprojectid = intval( $tprojectid );
        $tplanid = ! is_null( $tplanid ) ? intval( $tplanid ) : - 1;

        if($tprojectid <= 0 && $tplanid > 0) {
            // get test project from test plan
            $ox = array(
                    'output' => 'minimun'
            );
            $dummy = $this->tplanMgr->get_by_id( $tplanid, $ox );
            $tprojectid = intval( $dummy['tproject_id'] );
        }

        if(! $this->user->hasRight( $this->dbObj, $rightToCheck, $tprojectid, $tplanid, $checkPublicPrivateAttr )) {
            $status_ok = false;
            $msg = sprintf( INSUFFICIENT_RIGHTS_STR, $this->user->login, $rightToCheck, $tprojectid, $tplanid );
            $this->errors[] = new IXR_Error( INSUFFICIENT_RIGHTS, $msg );
        }

        if(isset( $context['updaterID'] )) {
            $updUser = tlUser::getByID( $this->dbObj, intval( $context['updaterID'] ) );

            $sk = $updUser->hasRight( $this->dbObj, $rightToCheck, $tprojectid, $tplanid, $checkPublicPrivateAttr );
            if(! $sk) {
                $status_ok = false;
                $msg = sprintf( UPDATER_INSUFFICIENT_RIGHTS_STR, $updUser->login, $rightToCheck, $tprojectid, $tplanid );
                $this->errors[] = new IXR_Error( UPDATER_INSUFFICIENT_RIGHTS, $msg );
            }
        }

        return $status_ok;
    }

    /**
     * Helper method to see if the testcasename provided is valid
     *
     * This is the only method that should be called directly to check the testcasename
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestCaseName() {
        $status = true;
        if(! $this->_isTestCaseNamePresent()) {
            $this->errors[] = new IXR_Error( NO_TESTCASENAME, NO_TESTCASENAME_STR );
            $status = false;
        } else {
            $testCaseName = $this->args[self::$testCaseNameParamName];
            if(! is_string( $testCaseName )) {
                $this->errors[] = new IXR_Error( TESTCASENAME_NOT_STRING, TESTCASENAME_NOT_STRING_STR );
                $status = false;
            }
        }
        return $status;
    }

    /**
     * Helper method to see if the status provided is valid
     *
     * This is the only method that should be called directly to check the status
     *
     * @return boolean
     * @access protected
     */
    protected function checkStatus() {
        if(($status = $this->_isStatusPresent())) {
            if(!($status = $this->_isStatusValid( $this->args[self::$statusParamName] ))) {
                $msg = sprintf( INVALID_STATUS_STR, $this->args[self::$statusParamName] );
                $this->errors[] = new IXR_Error( INVALID_STATUS, $msg );
            }
        } else {
            $this->errors[] = new IXR_Error( NO_STATUS, NO_STATUS_STR );
        }
        return $status;
    }

    /**
     * Helper method to see if the tcid provided is valid
     *
     * This is the only method that should be called directly to check the tcid
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestCaseID($messagePrefix = '') {
        $msg = $messagePrefix;
        $status_ok = $this->_isTestCaseIDPresent();
        if($status_ok) {
            $tcaseid = $this->args[self::$testCaseIDParamName];
            if(! $this->_isTestCaseIDValid( $tcaseid )) {
                $this->errors[] = new IXR_Error( INVALID_TCASEID, $msg . INVALID_TCASEID_STR );
                $status_ok = false;
            }
        } else {
            $this->errors[] = new IXR_Error( NO_TCASEID, $msg . NO_TCASEID_STR );
        }
        return $status_ok;
    }

    /**
     * Helper method to see if the tplanid provided is valid
     *
     * This is the only method that should be called directly to check the tplanid
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestPlanID($messagePrefix = '') {
        $status = true;
        if(! $this->_isTestPlanIDPresent()) {
            $msg = $messagePrefix . NO_TPLANID_STR;
            $this->errors[] = new IXR_Error( NO_TPLANID, $msg );
            $status = false;
        } else {
            // See if this TPID exists in the db
            $tplanid = $this->dbObj->prepare_int( $this->args[self::$testPlanIDParamName] );
            $query = "SELECT id FROM {$this->tables['testplans']} WHERE id={$tplanid}";
            $result = $this->dbObj->fetchFirstRowSingleColumn( $query, "id" );
            if(null == $result) {
                $msg = $messagePrefix . sprintf( INVALID_TPLANID_STR, $tplanid );
                $this->errors[] = new IXR_Error( INVALID_TPLANID, $msg );
                $status = false;
            } else {
                // tplanid exists and its valid
                // Do we need to try to guess build id ?
                if($this->checkGuess() &&(! $this->_isBuildIDPresent() && ! $this->_isParamPresent( self::$buildNameParamName, $messagePrefix ))) {
                    $status = $this->_setBuildID2Latest();
                }
            }
        }
        return $status;
    }

    /**
     * Helper method to see if the TestProjectID provided is valid
     *
     * This is the only method that should be called directly to check the TestProjectID
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestProjectID($messagePrefix = '') {
        if(!($status = $this->_isTestProjectIDPresent())) {
            $this->errors[] = new IXR_Error( NO_TESTPROJECTID, $messagePrefix . NO_TESTPROJECTID_STR );
        } else {
            // See if this Test Project ID exists in the db
            $testprojectid = $this->dbObj->prepare_int( $this->args[self::$testProjectIDParamName] );
            $query = "SELECT id FROM {$this->tables['testprojects']} WHERE id={$testprojectid}";
            $result = $this->dbObj->fetchFirstRowSingleColumn( $query, "id" );
            if(null == $result) {
                $msg = $messagePrefix . sprintf( INVALID_TESTPROJECTID_STR, $testprojectid );
                $this->errors[] = new IXR_Error( INVALID_TESTPROJECTID, $msg );
                $status = false;
            }
        }
        return $status;
    }

    /**
     * Helper method to see if the testproject identity provided is valid
     * Identity can be specified in one of these modes:
     *
     * - internal id(DB)
     * - prefix
     *
     *
     * If everything OK, test project internal ID is setted.
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestProjectIdentity($messagePrefix = '') {
        $status = false;
        $fromExternal = false;
        $fromInternal = false;

        if($this->_isTestProjectIDPresent()) {
            $fromInternal = true;
            $status = $this->checkTestProjectID( $messagePrefix );
        } else if($this->_isParamPresent( self::$prefixParamName, $messagePrefix, true )) {
            // Go for the prefix
            $fromExternal = true;

            $target = $this->dbObj->prepare_string( $this->args[self::$prefixParamName] );
            $sql = " SELECT id FROM {$this->tables['testprojects']} WHERE prefix='{$target}' ";

            $fieldValue = $this->dbObj->fetchFirstRowSingleColumn( $sql, "id" );
            $status =(! is_null( $fieldValue ) &&(intval( $fieldValue ) > 0));
            if($status) {
                $this->args[self::$testProjectIDParamName] = $fieldValue;
            } else {
                $status = false;
                $msg = $messagePrefix . sprintf( TPROJECT_PREFIX_DOESNOT_EXIST_STR, $target );
                $this->errors[] = new IXR_Error( TPROJECT_PREFIX_DOESNOT_EXIST, $msg );
            }
        } else {
            $status = false;
        }

        return $status;
    }

    /**
     * Helper method to see if the TestSuiteID provided is valid
     *
     * This is the only method that should be called directly to check the TestSuiteID
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestSuiteID($messagePrefix = '') {
        if(!($status = $this->_isTestSuiteIDPresent())) {
            $this->errors[] = new IXR_Error( NO_TESTSUITEID, $messagePrefix . NO_TESTSUITEID_STR );
        } else {
            // See if this Test Suite ID exists in the db
            $tsuiteMgr = new testsuite( $this->dbObj );
            $node_info = $tsuiteMgr->get_by_id( $this->args[self::$testSuiteIDParamName] );
            if(!($status = ! is_null( $node_info ))) {
                $msg = $messagePrefix . sprintf( INVALID_TESTSUITEID_STR, $this->args[self::$testSuiteIDParamName] );
                $this->errors[] = new IXR_Error( INVALID_TESTSUITEID, $msg );
            }
        }
        return $status;
    }

    /**
     * Helper method to see if the guess is set
     *
     * This is the only method that should be called directly to check the guess param
     *
     * Guessing is set to true by default
     *
     * @return boolean
     * @access protected
     */
    protected function checkGuess() {
        // if guess is set return its value otherwise return true to guess by default
        return($this->_isGuessPresent() ? $this->args[self::$guessParamName] : self::BUILD_GUESS_DEFAULT_MODE);
    }

    /**
     * Helper method to see if the buildID provided is valid for testplan
     *
     * if build id has not been provided on call, we can use build name if has been provided.
     *
     * This is the only method that should be called directly to check the buildID
     *
     * @return boolean
     * @access protected
     *
     * @internal revision
     */
    protected function checkBuildID($msg_prefix) {
        $tplan_id = $this->args[self::$testPlanIDParamName];
        $status = true;
        $try_again = false;

        // First thing is to know is test plan has any build
        $buildQty = $this->tplanMgr->getNumberOfBuilds( $tplan_id );
        if($buildQty == 0) {
            $status = false;
            $tplan_info = $this->tplanMgr->get_by_id( $tplan_id );
            $msg = $msg_prefix . sprintf( TPLAN_HAS_NO_BUILDS_STR, $tplan_info['name'], $tplan_info['id'] );
            $this->errors[] = new IXR_Error( TPLAN_HAS_NO_BUILDS, $msg );
        }

        if($status) {
            if(! $this->_isBuildIDPresent()) {
                $try_again = true;
                if($this->_isBuildNamePresent()) {
                    $try_again = false;
                    $bname = trim( $this->args[self::$buildNameParamName] );
                    $buildInfo = $this->tplanMgr->get_build_by_name( $tplan_id, $bname );
                    if(is_null( $buildInfo )) {
                        $msg = $msg_prefix . sprintf( BUILDNAME_DOES_NOT_EXIST_STR, $bname );
                        $this->errors[] = new IXR_Error( BUILDNAME_DOES_NOT_EXIST, $msg );
                        $status = false;
                    } else {
                        $this->args[self::$buildIDParamName] = $buildInfo['id'];
                    }
                }
            }

            if($try_again) {
                // this means we aren't supposed to guess the buildid
                if(false == $this->checkGuess()) {
                    $this->errors[] = new IXR_Error( NO_BUILDID, NO_BUILDID_STR );
                    $status = false;
                } else {
                    $setBuildResult = $this->_setBuildID2Latest();
                    if(false == $setBuildResult) {
                        $this->errors[] = new IXR_Error( NO_BUILD_FOR_TPLANID, NO_BUILD_FOR_TPLANID_STR );
                        $status = false;
                    }
                }
            }

            if($status) {
                $buildID = $this->dbObj->prepare_int( $this->args[self::$buildIDParamName] );
                $buildInfo = $this->tplanMgr->get_build_by_id( $tplan_id, $buildID );
                if(is_null( $buildInfo )) {
                    $tplan_info = $this->tplanMgr->get_by_id( $tplan_id );
                    $msg = sprintf( BAD_BUILD_FOR_TPLAN_STR, $buildID, $tplan_info['name'], $tplan_id );
                    $this->errors[] = new IXR_Error( BAD_BUILD_FOR_TPLAN, $msg );
                    $status = false;
                }
            }
        }
        return $status;
    }

    /**
     * Helper method to see if a param is present
     *
     * @param string $pname
     *            parameter name
     * @param string $messagePrefix
     *            used to be prepended to error message
     * @param boolean $setError
     *            default false
     *            true: add predefined error code to $this->error[]
     *
     * @return boolean
     * @access protected
     *
     *
     */
    protected function _isParamPresent($pname, $messagePrefix = '', $setError = false) {
        $status_ok =(isset( $this->args[$pname] ) ? true : false);
        if(! $status_ok && $setError) {
            $msg = $messagePrefix . sprintf( MISSING_REQUIRED_PARAMETER_STR, $pname );
            $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
        }
        return $status_ok;
    }

    /**
     * Helper method to see if the status provided is valid
     *
     * @return boolean
     * @access protected
     */
    protected function _isStatusValid($status) {
        return(in_array( $status, $this->statusCode ));
    }

    /**
     * Helper method to see if a testcasename is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTestCaseNamePresent() {
        return(isset( $this->args[self::$testCaseNameParamName] ) ? true : false);
    }

    /**
     * Helper method to see if a testcasename is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTestCaseExternalIDPresent() {
        $status = isset( $this->args[self::$testCaseExternalIDParamName] ) ? true : false;
        return $status;
    }

    /**
     * Helper method to see if:
     * a timestamp is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTimeStampPresent() {
        return(isset( $this->args[self::$timeStampParamName] ) ? true : false);
    }

    /**
     * Helper method to see if a buildID is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isBuildIDPresent() {
        return(isset( $this->args[self::$buildIDParamName] ) ? true : false);
    }

    /**
     * Helper method to see if a buildname is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isBuildNamePresent() {
        $status = isset( $this->args[self::$buildNameParamName] ) ? true : false;
        return $status;
    }

    /**
     * Helper method to see if build notes are given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isBuildNotePresent() {
        return(isset( $this->args[self::$buildNotesParamName] ) ? true : false);
    }

    /**
     * Helper method to see if testsuiteid is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTestSuiteIDPresent() {
        return(isset( $this->args[self::$testSuiteIDParamName] ) ? true : false);
    }

    /**
     * Helper method to see if a note is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isNotePresent() {
        return(isset( $this->args[self::$noteParamName] ) ? true : false);
    }

    /**
     * Helper method to see if a tplanid is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTestPlanIDPresent() {
        return(isset( $this->args[self::$testPlanIDParamName] ) ? true : false);
    }

    /**
     * Helper method to see if a TestProjectID is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTestProjectIDPresent() {
        return(isset( $this->args[self::$testProjectIDParamName] ) ? true : false);
    }

    /**
     * Helper method to see if automated is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isAutomatedPresent() {
        return(isset( $this->args[self::$automatedParamName] ) ? true : false);
    }

    /**
     * Helper method to see if testMode is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTestModePresent() {
        return(isset( $this->args[self::$testModeParamName] ) ? true : false);
    }

    /**
     * Helper method to see if a devKey is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isDevKeyPresent() {
        return(isset( $this->args[self::$devKeyParamName] ) ? true : false);
    }

    /**
     * Helper method to see if a tcid is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTestCaseIDPresent() {
        return(isset( $this->args[self::$testCaseIDParamName] ) ? true : false);
    }

    /**
     * Helper method to see if the guess param is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isGuessPresent() {
        $status = isset( $this->args[self::$guessParamName] ) ? true : false;
        return $status;
    }

    /**
     * Helper method to see if the testsuitename param is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isTestSuiteNamePresent() {
        return(isset( $this->args[self::$testSuiteNameParamName] ) ? true : false);
    }

    /**
     * Helper method to see if the deep param is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isDeepPresent() {
        return(isset( $this->args[self::$deepParamName] ) ? true : false);
    }

    /**
     * Helper method to see if the status param is given as one of the arguments
     *
     * @return boolean
     * @access protected
     */
    protected function _isStatusPresent() {
        return(isset( $this->args[self::$statusParamName] ) ? true : false);
    }

    /**
     * Helper method to see if the tcid provided is valid
     *
     * @param struct $tcaseid
     * @param string $messagePrefix
     *            used to be prepended to error message
     * @param boolean $setError
     *            default false
     *            true: add predefined error code to $this->error[]
     * @return boolean
     * @access protected
     */
    protected function _isTestCaseIDValid($tcaseid, $messagePrefix = '', $setError = false) {
        $status_ok = is_numeric( $tcaseid );
        if($status_ok) {
            // must be of type 'testcase' and show up in the nodes_hierarchy
            $tcaseid = $this->dbObj->prepare_int( $tcaseid );
            $query = " SELECT NH.id AS id " . " FROM {$this->tables['nodes_hierarchy']} NH, " . " {$this->tables['node_types']} NT " . " WHERE NH.id={$tcaseid} AND node_type_id=NT.id " . " AND NT.description='testcase'";
            $result = $this->dbObj->fetchFirstRowSingleColumn( $query, "id" );
            $status_ok = is_null( $result ) ? false : true;
        } else if($setError) {
            $this->errors[] = new IXR_Error( TCASEID_NOT_INTEGER, $messagePrefix . TCASEID_NOT_INTEGER_STR );
        }
        return $status_ok;
    }

    /**
     * Helper method to see if a devKey is valid
     *
     * @param string $devKey
     * @return boolean
     * @access protected
     */
    protected function _isDevKeyValid($devKey) {
        if(null == $devKey || "" == $devKey) {
            return false;
        } else {
            $this->userID = null;
            $this->devKey = $this->dbObj->prepare_string( $devKey );
            $query = "SELECT id FROM {$this->tables['users']} WHERE script_key='{$this->devKey}'";
            $this->userID = $this->dbObj->fetchFirstRowSingleColumn( $query, "id" );

            if(null == $this->userID) {
                return false;
            } else {
                return true;
            }
        }
    }

    /**
     * Helper method to set the tcVersion
     *
     *
     * @return boolean
     * @access protected
     */
    protected function _setTCVersion() {
        // TODO: Implement
    }

    /**
     * Helper method to See if the tcid and tplanid are valid together
     *
     * @param map $platformInfo
     *            key: platform ID
     * @param string $messagePrefix
     *            used to be prepended to error message
     * @return boolean
     * @access protected
     */
    protected function _checkTCIDAndTPIDValid($platformInfo = null, $messagePrefix = '') {
        $tplan_id = $this->args[self::$testPlanIDParamName];
        $tcase_id = $this->args[self::$testCaseIDParamName];
        $platform_id = ! is_null( $platformInfo ) ? key( $platformInfo ) : null;

        $filters = array(
                'exec_status' => "ALL",
                'active_status' => "ALL",
                'tplan_id' => $tplan_id,
                'platform_id' => $platform_id
        );
        $info = $this->tcaseMgr->get_linked_versions( $tcase_id, $filters );
        $status_ok = ! is_null( $info );

        if($status_ok) {
            $this->tcVersionID = key( $info );
            $dummy = current( $info );
            $plat = is_null( $platform_id ) ? 0 : $platform_id;
            $this->versionNumber = $dummy[$tplan_id][$plat]['version'];
        } else {
            $tplan_info = $this->tplanMgr->get_by_id( $tplan_id );
            $tcase_info = $this->tcaseMgr->get_by_id( $tcase_id, testcase::ALL_VERSIONS, null, array(
                    'output' => 'essential'
            ) );
            if(is_null( $platform_id )) {
                $msg = sprintf( TCASEID_NOT_IN_TPLANID_STR, $tcase_info[0]['name'], $tcase_id, $tplan_info['name'], $tplan_id );
                $this->errors[] = new IXR_Error( TCASEID_NOT_IN_TPLANID, $msg );
            } else {

                $msg = sprintf( TCASEID_NOT_IN_TPLANID_FOR_PLATFORM_STR, $tcase_info[0]['name'], $tcase_id, $tplan_info['name'], $tplan_id, $platformInfo[$platform_id], $platform_id );
                $this->errors[] = new IXR_Error( TCASEID_NOT_IN_TPLANID_FOR_PLATFORM, $msg );
            }
        }
        return $status_ok;
    }

    /**
     * Run all the necessary checks to see if the createBuild request is valid
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     * @return boolean
     * @access protected
     */
    protected function _checkCreateBuildRequest($messagePrefix = '') {
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix );
        if($status_ok) {
            $status_ok = $this->_isParamPresent( self::$buildNameParamName, $messagePrefix, self::SET_ERROR );
        }

        return $status_ok;
    }

    /**
     * Run all the necessary checks to see if the createBuild request is valid
     *
     * @return boolean
     * @access protected
     */
    protected function _checkGetBuildRequest() {
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions );
        return $status_ok;
    }

    /**
     * Run a set of functions
     *
     * @param array $checkFunctions
     *            set of function to be runned
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function _runChecks($checkFunctions, $messagePrefix = '') {
      foreach( $checkFunctions as $pfn ) {
        if(!($status_ok = $this->$pfn( $messagePrefix ))) {
          break;
        }
      }
      return $status_ok;
    }

    /**
     * Gets the latest build by choosing the maximum build id for a specific test plan
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @return mixed
     *
     * @access public
     */
    public function getLatestBuildForTestPlan($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $status_ok = true;
        $this->_setArgs( $args );
        $resultInfo = array();

        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $testPlanID = $this->args[self::$testPlanIDParamName];
            $build_id = $this->tplanMgr->get_max_build_id( $testPlanID );

            if(($status_ok = $build_id > 0)) {
                $builds = $this->tplanMgr->get_builds( $testPlanID );
                $build_info = $builds[$build_id];
            } else {
                $tplan_info = $this->tplanMgr->get_by_id( $testPlanID );
                $msg = $msg_prefix . sprintf( TPLAN_HAS_NO_BUILDS_STR, $tplan_info['name'], $tplan_info['id'] );
                $this->errors[] = new IXR_Error( TPLAN_HAS_NO_BUILDS, $msg );
            }
        }

        return $status_ok ? $build_info : $this->errors;
    }

    /**
     * _getLatestBuildForTestPlan
     *
     * @param struct $args
     *
     */
    protected function _getLatestBuildForTestPlan($args) {
        $builds = $this->_getBuildsForTestPlan( $args );
        $maxid = - 1;
        $maxkey = - 1;
        foreach( $builds as $key => $build ) {
            if($build['id'] > $maxid) {
                $maxkey = $key;
                $maxid = $build['id'];
            }
        }
        $maxbuild = array();
        $maxbuild[] = $builds[$maxkey];

        return $maxbuild;
    }

    /**
     * Gets the result of LAST EXECUTION for a particular testcase on a test plan.
     * If there are no filter criteria regarding platform and build,
     * result will be get WITHOUT checking for a particular platform and build.
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["tplanid"]
     * @param int $args["testcaseid"]:
     *            Pseudo optional.
     *            if does not is present then testcaseexternalid MUST BE present
     *
     * @param int $args["testcaseexternalid"]:
     *            Pseudo optional.
     *            if does not is present then testcaseid MUST BE present
     *
     * @param string $args["platformid"]:
     *            optional.
     *            ONLY if not present, then $args["platformname"]
     *            will be analized(if exists)
     *
     * @param string $args["platformname"]:
     *            optional(see $args["platformid"])
     *
     * @param int $args["buildid"]:
     *            optional
     *            ONLY if not present, then $args["buildname"] will be analized(if exists)
     *
     * @param int $args["buildname"]
     *            - optional(see $args["buildid"])
     *
     * @param int $args["options"]
     *            - optional
     *            options['getBugs'] = true / false
     *
     *
     * @return mixed $resultInfo
     *         if execution found
     *         array that contains a map with these keys:
     *         id(execution id),build_id,tester_id,execution_ts,
     *         status,testplan_id,tcversion_id,tcversion_number,
     *         execution_type,notes.
     *
     *         If user has requested getbugs, then a key bugs(that is an array)
     *         will also exists.
     *
     *
     *
     *         if test case has not been execute,
     *         array('id' => -1)
     *
     * @access public
     */
    public function getLastExecutionResult($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $resultInfo = array();
        $status_ok = true;

        $options = new stdClass();
        $options->getBugs = 0;

        // Checks are done in order
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID',
                'checkTestCaseIdentity'
        );

        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->_checkTCIDAndTPIDValid( null, $msg_prefix ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        $execContext = array(
                'tplan_id' => $this->args[self::$testPlanIDParamName],
                'platform_id' => null,
                'build_id' => null
        );

        if($status_ok) {
            if($this->_isParamPresent( self::$optionsParamName, $msg_prefix )) {
                $dummy = $this->args[self::$optionsParamName];
                if(is_array( $dummy )) {
                    foreach( $dummy as $key => $value ) {
                        $options->$key =($value > 0) ? 1 : 0;
                    }
                }
            }

            // Now we can check for Optional parameters
            if($this->_isBuildIDPresent() || $this->_isBuildNamePresent()) {
                if(($status_ok = $this->checkBuildID( $msg_prefix ))) {
                    $execContext['build_id'] = $this->args[self::$buildIDParamName];
                }
            }

            if($status_ok) {
                if($this->_isParamPresent( self::$platformIDParamName, $msg_prefix ) || $this->_isParamPresent( self::$platformNameParamName, $msg_prefix )) {
                    $status_ok = $this->checkPlatformIdentity( $this->args[self::$testPlanIDParamName] );

                    if($status_ok) {
                        $execContext['platform_id'] = $this->args[self::$platformIDParamName];
                    }
                }
            }
        }

        if($status_ok) {

            $sql = " SELECT MAX(id) AS exec_id FROM {$this->tables['executions']} " . " WHERE testplan_id = {$this->args[self::$testPlanIDParamName]} " . " AND tcversion_id IN(" . " SELECT id FROM {$this->tables['nodes_hierarchy']} " . " WHERE parent_id = {$this->args[self::$testCaseIDParamName]})";

            if(! is_null( $execContext['build_id'] )) {
                $sql .= " AND build_id = " . intval( $execContext['build_id'] );
            }

            if(! is_null( $execContext['platform_id'] )) {
                $sql .= " AND platform_id = " . intval( $execContext['platform_id'] );
            }

            $rs = $this->dbObj->fetchRowsIntoMap( $sql, 'exec_id' );
            if(is_null( $rs )) {
                // has not been executed
                // execution id = -1 => test case has not been runned.
                $resultInfo[] = array(
                        'id' => - 1
                );
            } else {
                // OK Select * is not a good practice but ...(fman)
                $targetID = intval( key( $rs ) );
                $sql = "SELECT * FROM {$this->tables['executions']} WHERE id=" . $targetID;
                $resultInfo[0] = $this->dbObj->fetchFirstRow( $sql );

                if($options->getBugs) {
                    $resultInfo[0]['bugs'] = array();
                    $sql = " SELECT DISTINCT bug_id FROM {$this->tables['execution_bugs']} " . " WHERE execution_id = " . $targetID;
                    $resultInfo[0]['bugs'] =( array ) $this->dbObj->get_recordset( $sql );
                }
            }
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * Gets ALL EXECUTIONS for a particular testcase on a test plan.
     * If there are no filter criteria regarding platform and build,
     * result will be get WITHOUT checking for a particular platform 
     * and build.
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["tplanid"]
     * @param int $args["testcaseid"]:
     *            Pseudo optional.
     *            if does not is present then 
     *            testcaseexternalid MUST BE present
     *
     * @param int $args["testcaseexternalid"]:
     *            Pseudo optional.
     *            if does not is present then testcaseid 
     *            MUST BE present
     *
     * @param string $args["platform_id"]:
     *            optional.
     *            will be analized ONLY if present and exists
     *
     * @param int $args["build_id"]:
     *            optional
     *            will be analized ONLY if present and exists
     *
     * @param int $args["options"]
     *            - optional
     *            options['getBugs'] = true / false
     *
     *
     * @return mixed $resultInfo
     *         if executions found
     *         array that contains a map for each execution with these keys:
     *         id(execution id),build_id,tester_id,execution_ts,
     *         status,testplan_id,tcversion_id,tcversion_number,
     *         execution_type,notes.
     *
     *         If user has requested getbugs, then a key bugs(that is an array)
     *         will also exists.
     *
     *         if test case has not been execute, the first map 
     *         will be returned with -1 as 'id'
     *
     * @access public
     */
    public function getAllExecutionsResults($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $resultInfo = array();
        $status_ok = true;

        $options = new stdClass();
        $options->getBugs = 0;

        // Checks are done in order
        $checkFunctions = array('authenticate',
                                'checkTestPlanID',
                                'checkTestCaseIdentity');

        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->_checkTCIDAndTPIDValid( null, $msg_prefix ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        $execContext = array(
          'tplan_id' => $this->args[self::$testPlanIDParamName],
          'platform_id' => null, 'build_id' => null);

      if($status_ok) {
        if($this->_isParamPresent( self::$optionsParamName, $msg_prefix )) {
          $dummy = $this->args[self::$optionsParamName];
          if(is_array( $dummy )) {
            foreach( $dummy as $key => $value ) {
                $options->$key =($value > 0) ? 1 : 0;
            }
          }
        }

        // Now we can check for Optional parameters
        if($this->_isBuildIDPresent() || $this->_isBuildNamePresent()) {
          if(($status_ok = $this->checkBuildID( $msg_prefix ))) {
            $execContext['build_id'] = $this->args[self::$buildIDParamName];
          }
        }

        if($status_ok) {
          if($this->_isParamPresent( self::$platformIDParamName, $msg_prefix ) || $this->_isParamPresent( self::$platformNameParamName, $msg_prefix )) {
                $status_ok = $this->checkPlatformIdentity( $this->args[self::$testPlanIDParamName] );

                if($status_ok) {
                    $execContext['platform_id'] = $this->args[self::$platformIDParamName];
                }
          }
        }
      }

      if($status_ok) {

        $sql = " SELECT(id) AS exec_id FROM {$this->tables['executions']} 
                 WHERE testplan_id = {$this->args[self::$testPlanIDParamName]} 
                 AND tcversion_id IN(" . 
                 " SELECT id FROM {$this->tables['nodes_hierarchy']} 
                   WHERE parent_id = {$this->args[self::$testCaseIDParamName]})";

        if(!is_null( $execContext['build_id'] )) {
          $sql .= " AND build_id = " . intval( $execContext['build_id'] );
        }

        if(!is_null( $execContext['platform_id'] )) {
           $sql .= " AND platform_id = " . intval( $execContext['platform_id'] );
        }

        $rs = $this->dbObj->fetchRowsIntoMap( $sql, 'exec_id' );
        if(is_null( $rs )) {
          // has not been executed
          // execution id = -1 => test case has not been runned.
          $resultInfo[0] = array('id' => - 1);
        } else {
          foreach( $rs as $tcExecId => $dummy ) {
            $sql = "SELECT * FROM {$this->tables['executions']} WHERE id=" . 
                   $tcExecId;
            $resultInfo[$tcExecId] = $this->dbObj->fetchFirstRow( $sql );
            if($options->getBugs) {
              $resultInfo[$tcExecId]['bugs'] = array();
              $sql = " SELECT DISTINCT bug_id FROM 
                       {$this->tables['execution_bugs']}
                       WHERE execution_id = " . $tcExecId;
              $resultInfo[$tcExecId]['bugs'] = 
                (array)$this->dbObj->get_recordset( $sql );
            }
          }
        }
      }

      return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * Adds the result to the database
     *
     * @return int
     * @access protected
     */
    protected function _insertResultToDB($user_id = null, $exec_ts = null) {
        $build_id = $this->args[self::$buildIDParamName];
        $status = $this->args[self::$statusParamName];
        $testplan_id = $this->args[self::$testPlanIDParamName];
        $tcversion_id = $this->tcVersionID;
        $version_number = $this->versionNumber;

        $tester_id = is_null( $user_id ) ? $this->userID : $user_id;
        $execTimeStamp = is_null( $exec_ts ) ? $this->dbObj->db_now() : $exec_ts;

        // return $execTimeStamp;

        $platform_id = 0;

        if(isset( $this->args[self::$platformIDParamName] )) {
            $platform_id = $this->args[self::$platformIDParamName];
        }

        $notes = '';
        $notes_field = "";
        $notes_value = "";

        if($this->_isNotePresent()) {
            $notes = $this->dbObj->prepare_string( $this->args[self::$noteParamName] );
        }

        if(trim( $notes ) != "") {
            $notes_field = ",notes";
            $notes_value = ", '{$notes}'";
        }

        $duration_field = '';
        $duration_value = '';
        if(isset( $this->args[self::$executionDurationParamName] )) {
            $duration_field = ',execution_duration';
            $duration_value = ", " . floatval( $this->args[self::$executionDurationParamName] );
        }

        $execution_type = constant( "TESTCASE_EXECUTION_TYPE_AUTO" );

        $query = "INSERT INTO {$this->tables['executions']} " . "(build_id, tester_id, execution_ts, status, testplan_id, tcversion_id, " . " platform_id, tcversion_number," . " execution_type {$notes_field} {$duration_field}) " . " VALUES({$build_id},{$tester_id},{$execTimeStamp}," . " '{$status}',{$testplan_id}," . " {$tcversion_id},{$platform_id}, {$version_number},{$execution_type} " . " {$notes_value} {$duration_value})";

        $this->dbObj->exec_query( $query );
        return $this->dbObj->insert_id( $this->tables['executions'] );
    }

    /**
     * Lets you see if the server is up and running
     *
     * @param
     *            struct not used
     * @return string "Hello!"
     * @access public
     */
    public function sayHello($args) {
        return 'Hello!';
    }

    /**
     * Repeats a message back
     *
     * @param struct $args
     *            should contain $args['str'] parameter
     * @return string
     * @access public
     */
    public function repeat($args) {
        $this->_setArgs( $args );
        $str = "You said: " . $this->args['str'];
        return $str;
    }

    /**
     *
     * @param
     *            -
     * @return string
     * @access public
     */
    public function testLinkVersion() {
        return TL_VERSION_NUMBER;
    }

    /**
     * Gives basic information about the API
     *
     * @param
     *            struct not used
     * @return string
     * @access public
     */
    public function about($args) {
        $this->_setArgs( $args );
        $str = " Testlink API Version: " . self::$version . " initially written by Asiel Brumfield\n" . " with contributions by TestLink development Team";
        return $str;
    }

    /**
     * Creates a new build for a specific test plan
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @param string $args["buildname"];
     * @param string $args["buildnotes"];
     * @param string $args["active"];
     * @param string $args["open"];
     * @param string $args["releasedate"]:
     *            YYYY-MM-DD;
     * @param int $args["copytestersfrombuild"]
     *            OPTIONAL,
     *            if > 0 and valid buildid tester assignments will be copied.
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function createBuild($args) {
        $operation = __FUNCTION__;
        $messagePrefix = "({$operation}) - ";
        $resultInfo = array();
        $resultInfo[0]["status"] = true;
        $resultInfo[0]["operation"] = $operation;
        $insertID = '';
        $returnMessage = GENERAL_SUCCESS_STR;

        $this->_setArgs( $args );

        // check the tpid
        if($this->_checkCreateBuildRequest( $messagePrefix ) && $this->userHasRight( "testplan_create_build", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
            $testPlanID = intval( $this->args[self::$testPlanIDParamName] );
            $buildName = $this->args[self::$buildNameParamName];
            $buildNotes = "";
            if($this->_isBuildNotePresent()) {
                $buildNotes = $this->dbObj->prepare_string( $this->args[self::$buildNotesParamName] );
            }

            if($this->tplanMgr->check_build_name_existence( $testPlanID, $buildName )) {
                // Build exists so just get the id of the existing build
                $insertID = $this->tplanMgr->get_build_id_by_name( $testPlanID, $buildName );
                $returnMessage = sprintf( BUILDNAME_ALREADY_EXISTS_STR, $buildName, $insertID );
                $resultInfo[0]["status"] = false;
            } else {
                // Build doesn't exist so create one
                // ,$active=1,$open=1);
                //($tplan_id,$name,$notes = '',$active=1,$open=1,$release_date='')

                // key 2 check with default value is parameter is missing
                $k2check = array(
                        self::$activeParamName => 1,
                        self::$openParamName => 1,
                        self::$releaseDateParamName => null,
                        self::$copyTestersFromBuildParamName => 0
                );
                foreach( $k2check as $key => $value ) {
                    $opt[$key] = $this->_isParamPresent( $key ) ? $this->args[$key] : $value;
                }

                // check if release date is valid date.
                // do not check relation with now(), i.e can be <,> or =.
                //
                if(! is_null( $opt[self::$releaseDateParamName] )) {
                    if(! $this->validateDateISO8601( $opt[self::$releaseDateParamName] )) {
                        $opt[self::$releaseDateParamName] = null;
                    }
                }

                $bm = new build_mgr( $this->dbObj );
                $insertID = $bm->create( $testPlanID, $buildName, $buildNotes, $opt[self::$activeParamName], $opt[self::$openParamName], $opt[self::$releaseDateParamName] );

                if($insertID > 0) {
                    $sourceBuild = intval( $opt[self::$copyTestersFromBuildParamName] );

                    if($sourceBuild > 0) {
                        // Check if belongs to test plan, otherwise ignore in silence
                        $sql = " SELECT id FROM {$this->tables['builds']} " . " WHERE id = " . $sourceBuild . " AND testplan_id = " . $testPlanID;
                        $rs = $this->dbObj->get_recordset( $sql );

                        if(count( $rs ) == 1) {
                            $taskMgr = new assignment_mgr( $this->dbObj );
                            $taskMgr->copy_assignments( $sourceBuild, $insertID, $this->userID );
                        }
                    }
                }
            }

            $resultInfo[0]["id"] = $insertID;
            $resultInfo[0]["message"] = $returnMessage;
            return $resultInfo;
        } else {
            return $this->errors;
        }
    }

    /**
     * Gets a list of all projects
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @return mixed $resultInfo
     * @access public
     */
    public function getProjects($args) {
        $this->_setArgs( $args );
        // TODO: NEED associated RIGHT
        if($this->authenticate()) {
            return $this->tprojectMgr->get_all();
        } else {
            return $this->errors;
        }
    }

    /**
     * Gets a list of test plans within a project
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testprojectid"]
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getProjectTestPlans($args) {
        $messagePrefix = "(" . __FUNCTION__ . ") - ";

        $this->_setArgs( $args );
        // check the tplanid
        // TODO: NEED associated RIGHT
        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix );

        if($status_ok) {
            $testProjectID = $this->args[self::$testProjectIDParamName];
            $info = $this->tprojectMgr->get_all_testplans( $testProjectID );
            if(! is_null( $info ) && count( $info ) > 0) {
                $info = array_values( $info );
            }
            return $info;
        } else {
            return $this->errors;
        }
    }

    /**
     * Gets a list of builds within a test plan
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @return if no errors
     *         no build present => null
     *         array of builds
     *
     *
     * @access public
     */
    public function getBuildsForTestPlan($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $this->_setArgs( $args );

        $builds = null;
        $status_ok = true;
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $testPlanID = $this->args[self::$testPlanIDParamName];
            $dummy = $this->tplanMgr->get_builds( $testPlanID );

            if(! is_null( $dummy )) {
                $builds = array_values( $dummy );
            }
        }
        return $status_ok ? $builds : $this->errors;
    }

    /**
     * List test suites within a test plan alphabetically
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @return mixed $resultInfo
     */
    public function getTestSuitesForTestPlan($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );
        if($status_ok) {
            $testPlanID = $this->args[self::$testPlanIDParamName];
            $result = $this->tplanMgr->get_testsuites( $testPlanID );
            return $result;
        } else {
            return $this->errors;
        }
    }

    /**
     * create a test project
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testprojectname"]
     * @param string $args["testcaseprefix"]
     * @param string $args["notes"]
     *            OPTIONAL
     * @param map $args["options"]
     *            OPTIONAL ALL int treated as boolean
     *            keys requirementsEnabled,testPriorityEnabled,automationEnabled,inventoryEnabled
     *
     * @param int $args["active"]
     *            OPTIONAL
     * @param int $args["public"]
     *            OPTIONAL
     * @param string $args["itsname"]
     *            OPTIONAL
     * @param boolean $args["itsEnabled"]
     *            OPTIONAL
     *
     *
     * @return mixed $resultInfo
     */
    public function createTestProject($args) {
        $this->_setArgs( $args );
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $checkRequestMethod = '_check' . ucfirst( __FUNCTION__ ) . 'Request';

        $status_ok = false;
        if($this->$checkRequestMethod( $msg_prefix ) && $this->userHasRight( "mgt_modify_product" )) {
            $status_ok = true;

            $item = new stdClass();
            $item->options = new stdClass();
            $item->options->requirementsEnabled = 1;
            $item->options->testPriorityEnabled = 1;
            $item->options->automationEnabled = 1;
            $item->options->inventoryEnabled = 1;

            if($this->_isParamPresent( self::$optionsParamName, $msg_prefix )) {
                // has to be an array ?
                $dummy = $this->args[self::$optionsParamName];
                if(is_array( $dummy )) {
                    foreach( $dummy as $key => $value ) {
                        $item->options->$key = $value > 0 ? 1 : 0;
                    }
                }
            }

            // other optional parameters(not of complex type)
            // key 2 check with default value is parameter is missing
            $keys2check = array(
                    self::$activeParamName => 1,
                    self::$publicParamName => 1,
                    self::$noteParamName => '',
                    self::$itsEnabledParamName => 0,
                    self::$itsNameParamName => ''
            );
            foreach( $keys2check as $key => $value ) {
                $optional[$key] = $this->_isParamPresent( $key ) ? trim( $this->args[$key] ) : $value;
            }

            $item->name = htmlspecialchars( $this->args[self::$testProjectNameParamName] );
            $item->prefix = htmlspecialchars( $this->args[self::$testCasePrefixParamName] );

            $item->notes = htmlspecialchars( $optional[self::$noteParamName] );
            $item->active =($optional[self::$activeParamName] > 0) ? 1 : 0;
            $item->is_public =($optional[self::$publicParamName] > 0) ? 1 : 0;
            $item->color = '';

            $its = null;
            if($optional[self::$itsNameParamName] != "") {
                $this->itsMgr = new tlIssueTracker( $this->dbObj );
                $its = $this->getIssueTrackerSystem( $this->args, 'internal' );

                $itsOK = ! is_null( $its );
                if(! $itsOK) {
                    $status_ok = false;
                }
            }
        }

        // All checks OK => try to create testproject
        if($status_ok) {
            $tproject_id = $this->tprojectMgr->create( $item );

            // link & enable its?
            if($itsOK && $tproject_id > 0) {
                // link
                $this->itsMgr->link( $its["id"], $tproject_id );

                // enable
                if($optional[self::$itsEnabledParamName] > 0) {
                    $this->tprojectMgr->enableIssueTracker( $tproject_id );
                }
            }

            $ret = array();
            $ret[] = array(
                    "operation" => __FUNCTION__,
                    "additionalInfo" => null,
                    "status" => true,
                    "id" => $tproject_id,
                    "message" => GENERAL_SUCCESS_STR
            );
            return $ret;
        }

        return($status_ok ? $ret : $this->errors);
    }

    /**
     * _checkCreateTestProjectRequest
     */
    protected function _checkCreateTestProjectRequest($msg_prefix) {
        $status_ok = $this->authenticate();
        $name = $this->args[self::$testProjectNameParamName];
        $prefix = $this->args[self::$testCasePrefixParamName];

        if($status_ok) {
            $check_op = $this->tprojectMgr->checkNameSintax( $name );
            $status_ok = $check_op['status_ok'];
            if(! $status_ok) {
                $this->errors[] = new IXR_Error( TESTPROJECTNAME_SINTAX_ERROR, $msg_prefix . $check_op['msg'] );
            }
        }

        if($status_ok) {
            $check_op = $this->tprojectMgr->checkNameExistence( $name );
            $status_ok = $check_op['status_ok'];
            if(! $status_ok) {
                $this->errors[] = new IXR_Error( TESTPROJECTNAME_EXISTS, $msg_prefix . $check_op['msg'] );
            }
        }

        if($status_ok) {
            $status_ok = ! empty( $prefix );
            if(! $status_ok) {
                $this->errors[] = new IXR_Error( TESTPROJECT_TESTCASEPREFIX_IS_EMPTY, $msg_prefix . $check_op['msg'] );
            }
        }

        if($status_ok) {
            $info = $this->tprojectMgr->get_by_prefix( $prefix );
            if(!($status_ok = is_null( $info ))) {
                $msg = $msg_prefix . sprintf( TPROJECT_PREFIX_ALREADY_EXISTS_STR, $prefix, $info['name'] );
                $this->errors[] = new IXR_Error( TPROJECT_PREFIX_ALREADY_EXISTS, $msg );
            }
        }

        return $status_ok;
    }

    /**
     * List test cases within a test suite
     *
     * By default test cases that are contained within child suites
     * will be returned.
     * Set the deep flag to false if you only want test cases in the test suite provided
     * and no child test cases.
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testsuiteid"]
     * @param boolean $args["deep"]
     *            - optional(default is true)
     * @param boolean $args["details"]
     *            - optional(default is simple)
     *            use full if you want to get
     *            summary,steps & expected_results
     *
     * @param boolean $args["getkeywords"]
     *            - optional(default false)
     * @return mixed $resultInfo
     *
     *
     */
    public function getTestCasesForTestSuite($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $status_ok = $this->_runChecks( array(
                'authenticate',
                'checkTestSuiteID'
        ), $msg_prefix );

        $details = 'simple';
        $key2search = self::$detailsParamName;
        if($this->_isParamPresent( $key2search )) {
            $details = $this->args[$key2search];
        }

        if($status_ok) {
            $testSuiteID = $this->args[self::$testSuiteIDParamName];
            $dummy = $this->tprojectMgr->tree_manager->get_path( $testSuiteID );
            $this->args[self::$testProjectIDParamName] = $dummy[0]['parent_id'];
            $status_ok = $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );
        }

        if($status_ok) {
            $tsuiteMgr = new testsuite( $this->dbObj );
            if(! $this->_isDeepPresent() || $this->args[self::$deepParamName]) {
                $pfn = 'get_testcases_deep';
            } else {
                $pfn = 'get_children_testcases';
            }

            $opt = null;
            if(isset( $this->args[self::$getKeywordsParamName] ) && $this->args[self::$getKeywordsParamName]) {
                $opt = array(
                        'getKeywords' => true
                );
            }

            return $tsuiteMgr->$pfn( $testSuiteID, $details, $opt );
        } else {
            return $this->errors;
        }
    }

    /**
     * Find a test case by its name
     *
     * <b>Searching is case sensitive.</b> The test case will only be returned if there is a definite match.
     * If possible also pass the string for the test suite name.
     *
     * No results will be returned if there are test cases with the same name that match the criteria provided.
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testcasename"]
     * @param string $args["testsuitename"]
     *            - optional
     * @param string $args["testprojectname"]
     *            - optional
     * @param string $args["testcasepathname"]
     *            - optional
     *            Full test case path name, starts with test project name
     *            pieces separator -> :: -> default value of getByPathName()
     * @return mixed $resultInfo
     */
    public function getTestCaseIDByName($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $status_ok = true;
        $this->_setArgs( $args );
        $result = null;

        $checkFunctions = array(
                'authenticate',
                'checkTestCaseName'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $testCaseName = $this->args[self::$testCaseNameParamName];
            $testCaseMgr = new testcase( $this->dbObj );
            $keys2check = array(
                    self::$testSuiteNameParamName,
                    self::$testCasePathNameParamName,
                    self::$testProjectNameParamName
            );
            foreach( $keys2check as $key ) {
                $optional[$key] = $this->_isParamPresent( $key ) ? trim( $this->args[$key] ) : '';
            }

            if($optional[self::$testCasePathNameParamName] != '') {
                $dummy = $testCaseMgr->getByPathName( $optional[self::$testCasePathNameParamName] );
                if(! is_null( $dummy )) {
                    $result[0] = $dummy;
                }
            } else {
                $result = $testCaseMgr->get_by_name( $testCaseName, $optional[self::$testSuiteNameParamName], $optional[self::$testProjectNameParamName] );
            }

            $match_count = count( $result );
            switch($match_count) {
                case 0 :
                    $status_ok = false;
                    $this->errors[] = new IXR_ERROR( NO_TESTCASE_BY_THIS_NAME, $msg_prefix . NO_TESTCASE_BY_THIS_NAME_STR );
                    break;

                case 1 :
                    $status_ok = true;
                    break;

                default :
                    // multiple matches.
                    $status_ok = true;
                    break;
            }
        }

        // $this->userHasRight("mgt_view_tc",self::CHECK_PUBLIC_PRIVATE_ATTR);
        // if we have multiple matches, we have issues to check test project access for user
        // requesting operation.
        // what to do ?
        // check access for each result and remove result if user has no access to corresponding
        // test project.
        if($status_ok) {
            $out = null;
            foreach( $result as $testcase ) {
                $this->args[self::$testProjectIDParamName] = $this->tcaseMgr->get_testproject( $testcase['id'] );
                if($this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
                    $out[] = $testcase;
                }
            }
        }
        return $status_ok ? $out : $this->errors;
    }

    /**
     * createTestCase
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testcasename"]
     * @param int $args["testsuiteid"]:
     *            test case parent test suite id
     * @param int $args["testprojectid"]:
     *            test case parent test suite id
     *
     * @param string $args["authorlogin"]:
     *            to set test case author
     * @param string $args["summary"]
     * @param array $args["steps"]
     *
     * @param string $args["preconditions"]
     *            - optional
     * @param int $args["importance"]
     *            - optional - see const.inc.php for domain
     * @param int $args["execution"]
     *            - optional - see ... for domain
     * @param int $args["order']
     *            - optional
     * @param int $args["internalid"]
     *            - optional - do not use
     * @param string $args["checkduplicatedname"]
     *            - optional
     * @param string $args["actiononduplicatedname"]
     *            - optional
     * @param int $args["status"]
     *            - optional - see const.inc.php $tlCfg->testCaseStatus
     * @param number $args["estimatedexecduration"]
     *            - optional
     *
     * @return mixed $resultInfo
     * @return string $resultInfo['operation'] - verbose operation
     * @return boolean $resultInfo['status'] - verbose operation
     * @return int $resultInfo['id'] - test case internal ID(Database ID)
     * @return mixed $resultInfo['additionalInfo']
     * @return int $resultInfo['additionalInfo']['id'] same as $resultInfo['id']
     * @return int $resultInfo['additionalInfo']['external_id'] without prefix
     * @return int $resultInfo['additionalInfo']['status_ok'] 1/0
     * @return string $resultInfo['additionalInfo']['msg'] - for debug
     * @return string $resultInfo['additionalInfo']['new_name'] only present if new name generation was needed
     * @return int $resultInfo['additionalInfo']['version_number']
     * @return boolean $resultInfo['additionalInfo']['has_duplicate'] - for debug
     * @return string $resultInfo['message'] operation message
     */
    public function createTestCase($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $wfStatusDomain = config_get( 'testCaseStatus' );

        $keywordSet = '';
        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID',
                'checkTestSuiteID',
                'checkTestCaseName'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->userHasRight( "mgt_modify_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        if($status_ok) {
            $keys2check = array(
                    self::$authorLoginParamName,
                    self::$summaryParamName,
                    self::$stepsParamName
            );
            foreach( $keys2check as $key ) {
                if(! $this->_isParamPresent( $key )) {
                    $status_ok = false;
                    $msg = $msg_prefix . sprintf( MISSING_REQUIRED_PARAMETER_STR, $key );
                    $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
                }
            }
        }

        if($status_ok) {
            $author_id = tlUser::doesUserExist( $this->dbObj, $this->args[self::$authorLoginParamName] );
            if(!($status_ok = ! is_null( $author_id ))) {
                $msg = $msg_prefix . sprintf( NO_USER_BY_THIS_LOGIN_STR, $this->args[self::$authorLoginParamName] );
                $this->errors[] = new IXR_Error( NO_USER_BY_THIS_LOGIN, $msg );
            }
        }

        if($status_ok) {
            $keywordSet = $this->getKeywordSet( $this->args[self::$testProjectIDParamName] );
        }

        if($status_ok) {
            // Optional parameters
            $opt = array(
                    self::$importanceParamName => 2,
                    self::$executionTypeParamName => TESTCASE_EXECUTION_TYPE_MANUAL,
                    self::$orderParamName => testcase::DEFAULT_ORDER,
                    self::$internalIDParamName => testcase::AUTOMATIC_ID,
                    self::$checkDuplicatedNameParamName => testcase::DONT_CHECK_DUPLICATE_NAME,
                    self::$actionOnDuplicatedNameParamName => 'generate_new',
                    self::$preconditionsParamName => '',
                    self::$statusParamName => $wfStatusDomain['draft'],
                    self::$estimatedExecDurationParamName => null
            );

            foreach( $opt as $key => $value ) {
                if($this->_isParamPresent( $key )) {
                    $opt[$key] = $this->args[$key];
                }
            }
        }

        if($status_ok) {
            $options = array(
                    'check_duplicate_name' => $opt[self::$checkDuplicatedNameParamName],
                    'action_on_duplicate_name' => $opt[self::$actionOnDuplicatedNameParamName],
                    'status' => $opt[self::$statusParamName],
                    'estimatedExecDuration' => $opt[self::$estimatedExecDurationParamName]
            );

            $op_result = $this->tcaseMgr->create( $this->args[self::$testSuiteIDParamName], $this->args[self::$testCaseNameParamName], $this->args[self::$summaryParamName], $opt[self::$preconditionsParamName], $this->args[self::$stepsParamName], $author_id, $keywordSet, $opt[self::$orderParamName], $opt[self::$internalIDParamName], $opt[self::$executionTypeParamName], $opt[self::$importanceParamName], $options );

            $resultInfo = array();
            $resultInfo[] = array(
                    "operation" => $operation,
                    "status" => true,
                    "id" => $op_result['id'],
                    "additionalInfo" => $op_result,
                    "message" => GENERAL_SUCCESS_STR
            );
        }
        return($status_ok ? $resultInfo : $this->errors);
    }

    /**
     * Reports a result for a single test case
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testcaseid"]:
     *            optional, if not present
     *            testcaseexternalid must be present
     *
     * @param int $args["testcaseexternalid"]:
     *            optional, if does not is present
     *            testcaseid must be present
     *
     *
     *
     * @param int $args["testplanid"]
     * @param string $args["status"]
     *            - status is {@link $validStatusList}
     *
     * @param array $args["steps"]
     *            - each element is an array with following
     *            structure:
     *            array('step_number' => 1, 'result' => 'p', 'notes' => 'the notes')
     *
     *
     * @param int $args["buildid"]
     *            - optional.
     *            if not present and $args["buildname"] exists
     *            then
     *            $args["buildname"] will be checked and used if valid
     *            else
     *            build with HIGHEST ID will be used
     *
     * @param int $args["buildname"]
     *            - optional.
     *            if not present Build with higher internal ID will be used
     *
     *
     * @param string $args["notes"]
     *            - optional
     * @param string $args["execduration"]
     *            - optional
     *
     * @param bool $args["guess"]
     *            - optional defining whether to guess optinal params or require them
     *            explicitly default is true(guess by default)
     *
     * @param string $args["bugid"]
     *            - optional
     *
     * @param string $args["platformid"]
     *            - optional, if not present platformname must be present
     * @param string $args["platformname"]
     *            - optional, if not present platformid must be present
     *
     *
     * @param string $args["customfields"]
     *            - optional
     *            contains an map with key:Custom Field Name, value: value for CF.
     *            VERY IMPORTANT: value must be formatted in the way it's written to db,
     *            this is important for types like:
     *
     *            DATE: strtotime()
     *            DATETIME: mktime()
     *            MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore
     *
     *
     *            these custom fields must be configured to be writte during execution.
     *            If custom field do not meet condition value will not be written
     *
     * @param boolean $args["overwrite"]
     *            - optional, if present and true, then last execution
     *            for(testcase,testplan,build,platform) will be overwritten.
     *
     * @param boolean $args["user"]
     *            - optional, if present and user is a valid login
     *           (no other check will be done) it will be used when writting execution.
     *
     * @param string $args["timestamp"]
     *            - optional, if not present now is used
     *            format YYYY-MM-DD HH:MM:SS
     *            example 2015-05-22 12:15:45
     * @return mixed $resultInfo
     *        [status] => true/false of success
     *        [id] => result id or error code
     *        [message] => optional message for error message string
     * @access public
     *
     * @internal revisions
     *
     */
    public function reportTCResult($args) {
        $resultInfo = array();
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $this->errors = null;

        $this->_setArgs( $args );
        $resultInfo[0]["status"] = true;

        $checkFunctions = array(
                'authenticate',
                'checkTestCaseIdentity',
                'checkTestPlanID',
                'checkBuildID',
                'checkStatus'
        );

        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            // This check is needed only if test plan has platforms
            $platformSet = $this->tplanMgr->getPlatforms( $this->args[self::$testPlanIDParamName], array(
                    'outputFormat' => 'map'
            ) );
            $targetPlatform = null;

            if(! is_null( $platformSet )) {
                $status_ok = $this->checkPlatformIdentity( $this->args[self::$testPlanIDParamName], $platformSet, $msg_prefix );
                if($status_ok) {
                    $targetPlatform[$this->args[self::$platformIDParamName]] = $platformSet[$this->args[self::$platformIDParamName]];
                }
            }
            $status_ok = $status_ok && $this->_checkTCIDAndTPIDValid( $targetPlatform, $msg_prefix );
        }

        $tester_id = null;
        if($status_ok) {
            $this->errors = null;
            if($this->_isParamPresent( self::$userParamName )) {
                $tester_id = tlUser::doesUserExist( $this->dbObj, $this->args[self::$userParamName] );
                if(!($status_ok = ! is_null( $tester_id ))) {
                    $msg = $msg_prefix . sprintf( NO_USER_BY_THIS_LOGIN_STR, $this->args[self::$userParamName] );
                    $this->errors[] = new IXR_Error( NO_USER_BY_THIS_LOGIN, $msg );
                }
            }
        }

        $exec_ts = null;
        if($status_ok) {
            if($this->_isParamPresent( self::$timeStampParamName )) {
                // Now check if is a valid one
                $exec_ts = $this->args[self::$timeStampParamName];

                try {
                    checkTimeStamp( $exec_ts );
                    $exec_ts = "'{$exec_ts}'";
                } catch( Exception $e ) {
                    $status_ok = false;
                    $this->errors = null;
                    $msg = $msg_prefix . sprintf( INVALID_TIMESTAMP_STR, $exec_ts );
                    $this->errors[] = new IXR_Error( INVALID_TIMESTAMP, $msg );
                }
            }
        }

        if($status_ok && $this->userHasRight( "testplan_execute", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
            $executionID = 0;
            $resultInfo[0]["operation"] = $operation;
            $resultInfo[0]["overwrite"] = false;
            $resultInfo[0]["status"] = true;
            $resultInfo[0]["message"] = GENERAL_SUCCESS_STR;

            $doOverwrite = $this->_isParamPresent( self::$overwriteParamName ) && $this->args[self::$overwriteParamName];

            if($doOverwrite) {
                $resultInfo[0]["overwrite"] = true;
                $executionID = $this->_updateResult( $tester_id, $exec_ts );

                if($executionID != 0 && $this->_isParamPresent( self::$stepsParamName )) {
                    $resultInfo[0]["steps"] = 'yes!';
                    $resultInfo[0]["steps_sql"] = $this->_updateStepsResult( $executionID );
                }
            }

            if($executionID == 0) {
                $executionID = $this->_insertResultToDB( $tester_id, $exec_ts );
            }

            $resultInfo[0]["id"] = $executionID;

            // Do we need to insert a bug ?
            if($this->_isParamPresent( self::$bugIDParamName )) {
                $bugID = $this->args[self::$bugIDParamName];
                $resultInfo[0]["bugidstatus"] = $this->_insertExecutionBug( $executionID, $bugID );
            }

            if($this->_isParamPresent( self::$customFieldsParamName )) {
                $resultInfo[0]["customfieldstatus"] = $this->_insertCustomFieldExecValues( $executionID );
            }

            //
            if($executionID > 0 && ! $resultInfo[0]["overwrite"]) {
                // Get steps info
                // step number, result, notes
                if($this->_isParamPresent( self::$stepsParamName )) {
                    $resultInfo[0]["steps"] = 'yes!';

                    $st = &$this->args[self::$stepsParamName];
                    foreach( $st as $sp ) {
                        $nst[$sp['step_number']] = $sp;
                    }

                    $r2d2 = array(
                            'fields2get' => 'TCSTEPS.step_number,TCSTEPS.id',
                            'accessKey' => 'step_number',
                            'renderGhostSteps' => false,
                            'renderImageInline' => false
                    );

                    $steps = $this->tcaseMgr->getStepsSimple( $this->tcVersionID, 0, $r2d2 );

                    $target = DB_TABLE_PREFIX . 'execution_tcsteps';
                    $resultsCfg = config_get( 'results' );

                    foreach( $nst as $spnum => $spdata ) {
                        // check if step exists, if not ignore
                        if(isset( $steps[$spnum] )) {
                            // if result is not on domain, write it
                            // anyway.
                            $status = strtolower( trim( $spdata['result'] ) );
                            $status = $status[0];

                            $sql = " INSERT INTO {$target}(execution_id,tcstep_id,notes";
                            $sql .= ",status";

                            $values = " VALUES( {$executionID}, {$steps[$spnum]['id']}," . "'" . $this->dbObj->prepare_string( $spdata['notes'] ) . "'";
                            $values .= ",'" . $this->dbObj->prepare_string( $status ) . "'";
                            $sql .= ") " . $values . ")";

                            if($status != $resultsCfg['status_code']['not_run']) {
                                $this->dbObj->exec_query( $sql );
                            }
                        }
                    }
                }
            }

            return $resultInfo;
        } else {
            return $this->errors;
        }
    }

    /**
     * turn on/off testMode
     *
     * This method is meant primarily for testing and debugging during development
     *
     * @param struct $args
     * @return boolean
     * @access protected
     */
    public function setTestMode($args) {
        $this->_setArgs( $args );

        if(! $this->_isTestModePresent()) {
            $this->errors[] = new IXR_ERROR( NO_TEST_MODE, NO_TEST_MODE_STR );
            return false;
        } else {
            // TODO: should probably validate that this is a bool or t/f string
            $this->testMode = $this->args[self::$testModeParamName];
            return true;
        }
    }

    /**
     * Helper method to see if the testcase identity provided is valid
     * Identity can be specified in one of these modes:
     *
     * test case internal id
     * test case external id(PREFIX-NNNN)
     *
     * This is the only method that should be called directly to check test case identity
     *
     * If everything OK, test case internal ID is setted.
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestCaseIdentity($messagePrefix = '') {
        // Three Cases - Internal ID, External ID, No Id
        $status = false;
        $tcaseID = 0;
        $my_errors = array();
        $fromExternal = false;
        $fromInternal = false;

        if($this->_isTestCaseIDPresent()) {
            $fromInternal = true;
            $status =(($tcaseID = intval( $this->args[self::$testCaseIDParamName] )) > 0);

            if(! $status) {
                $this->errors[] = new IXR_Error( $tcaseID, sprintf( $messagePrefix . INVALID_TCASEID_STR, $tcaseID ) );
            }
        } elseif($this->_isTestCaseExternalIDPresent()) {
            $fromExternal = true;
            $tcaseExternalID = $this->args[self::$testCaseExternalIDParamName];
            $tcaseID = intval( $this->tcaseMgr->getInternalID( $tcaseExternalID ) );
            $status = $tcaseID > 0 ? true : false;

            // Invalid TestCase ID
            if(! $status) {
                $this->errors[] = new IXR_Error( INVALID_TESTCASE_EXTERNAL_ID, sprintf( $messagePrefix . INVALID_TESTCASE_EXTERNAL_ID_STR, $tcaseExternalID ) );
            }
        }

        if($status) {
            if($this->_isTestCaseIDValid( $tcaseID, $messagePrefix )) {
                $this->_setTestCaseID( $tcaseID );
            } else {
                $status = false;
                if($fromInternal) {
                    $this->errors[] = new IXR_Error( INVALID_TCASEID, sprintf( $messagePrefix . INVALID_TCASEID_STR, $tcaseID ) );
                } elseif($fromExternal) {
                    $this->errors[] = new IXR_Error( INVALID_TESTCASE_EXTERNAL_ID, sprintf( $messagePrefix . INVALID_TESTCASE_EXTERNAL_ID_STR, $tcaseExternalID ) );
                }
            }
        }

        return $status;
    }

    /**
     * getTestCasesForTestPlan
     * List test cases linked to a test plan
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @param int $args["buildid"]
     *            - optional
     * @param int $args["platformid"]
     *            - optional
     * @param int $args["testcaseid"]
     *            - optional
     * @param int $args["keywordid"]
     *            - optional mutual exclusive with $args["keywords"]
     * @param int $args["keywords"]
     *            - optional mutual exclusive with $args["keywordid"]
     *
     * @param boolean $args["executed"]
     *            - optional
     * @param int $args["$assignedto"]
     *            - optional
     * @param string $args["executestatus"]
     *            - optional
     * @param array $args["executiontype"]
     *            - optional
     * @param array $args["getstepinfo"]
     *            - optional - default false
     * @param string $args["details"]
     *            - optional
     *            'full':(default) get summary,steps,expected_results,test suite name
     *            'simple':
     *            'details':
     * @param array $args["customfields"]
     *             - optional can be a boolean or an array with the requested fields
     * @return mixed $resultInfo
     *
     * @internal revisions
     * @since 1.9.13
     *        20141230 - franciscom - TICKET 6805: platform parameter
     */
    public function getTestCasesForTestPlan($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        // Optional parameters that are not mutual exclusive,
        // DEFAULT value to use if parameter was not provided
        $opt = array(
                self::$testCaseIDParamName => null,
                self::$buildIDParamName => null,
                self::$keywordIDParamName => null,
                self::$executedParamName => null,
                self::$assignedToParamName => null,
                self::$executeStatusParamName => null,
                self::$executionTypeParamName => null,
                self::$getStepsInfoParamName => false,
                self::$customFieldsParamName => false,
                self::$detailsParamName => 'full',
                self::$platformIDParamName => null
        );

        $optMutualExclusive = array(
                self::$keywordIDParamName => null,
                self::$keywordNameParamName => null
        );
        $this->_setArgs( $args );
        if(!($this->_checkGetTestCasesForTestPlanRequest( $msg_prefix ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR ))) {
            return $this->errors;
        }

        $tplanid = $this->args[self::$testPlanIDParamName];
        $tplanInfo = $this->tplanMgr->tree_manager->get_node_hierarchy_info( $tplanid );

        foreach( $opt as $key => $value ) {
            if($this->_isParamPresent( $key )) {
                $opt[$key] = $this->args[$key];
            }
        }

        $keywordSet = $opt[self::$keywordIDParamName];
        if(is_null( $keywordSet )) {
            $keywordSet = null;
            $keywordList = $this->getKeywordSet( $tplanInfo['parent_id'] );
            if(! is_null( $keywordList )) {
                $keywordSet = explode( ",", $keywordList );
            }
        }

        $options = array(
                'executed_only' => $opt[self::$executedParamName],
                'details' => $opt[self::$detailsParamName],
                'output' => 'mapOfMap'
        );

        $filters = array(
                'tcase_id' => $opt[self::$testCaseIDParamName],
                'keyword_id' => $keywordSet,
                'assigned_to' => $opt[self::$assignedToParamName],
                'exec_status' => $opt[self::$executeStatusParamName],
                'build_id' => $opt[self::$buildIDParamName],
                'exec_type' => $opt[self::$executionTypeParamName],
                'platform_id' => $opt[self::$platformIDParamName]
        );

        $recordset = $this->tplanMgr->getLTCVNewGeneration( $tplanid, $filters, $options );

        // Do we need to get Test Case Steps?
        if(! is_null( $recordset ) && $opt[self::$getStepsInfoParamName]) {
            $itemSet = array_keys( $recordset );
            switch($options['output']) {
                case 'mapOfArray' :
                case 'mapOfMap' :
                    foreach( $itemSet as $itemKey ) {
                        $keySet = array_keys( $recordset[$itemKey] );
                        $target = &$recordset[$itemKey];
                        foreach( $keySet as $accessKey ) {
                            $steps = $this->tcaseMgr->get_steps( $target[$accessKey]['tcversion_id'] );
                            $target[$accessKey]['steps'] = $steps;
                        }
                    }
                    break;

                case 'array' :
                case 'map' :
                default :
                    foreach( $itemSet as $accessKey ) {
                        $sts = $this->tcaseMgr->get_steps( $recordset[$accessKey]['tcversion_id'] );
                        $recordset[$accessKey]['steps'] = $sts;
                    }
                    break;
            }
        }

        // Do we need the custom fields?
        if (! is_null( $recordset ) && ($opt[self::$customFieldsParamName] || is_array($opt[self::$customFieldsParamName]))) {
            $itemSet = array_keys( $recordset );
            switch($options['output']) {
                case 'mapOfArray' :
                case 'mapOfMap' :
                    foreach( $itemSet as $itemKey ) {
                        $keySet = array_keys( $recordset[$itemKey] );
                        $target = &$recordset[$itemKey];
                        foreach( $keySet as $accessKey ) {
                            $target[$accessKey]['customfields'] = $this->_testcaseCustomFieldData(
                                $target[$accessKey], $tplanInfo['parent_id'], $opt[self::$customFieldsParamName]);
                        }
                    }
                    break;

                case 'array' :
                case 'map' :
                default :
                    foreach( $itemSet as $accessKey ) {
                        $recordset[$accessKey]['customfields'] = $this->_testcaseCustomFieldData(
                            $recordset[$accessKey], $tplanInfo['parent_id'], $opt[self::$customFieldsParamName]);
                    }
                    break;
            }
        }

        return $recordset;
    }

    /**
     * Get a map of all custom fields from the given testcase.
     *
     * This can be filtered by providing an array with the allowed
     * custom field names as $cf_options.
     *
     * @param mixed $testcase
     *              The testcase to query for.
     * @param int $project_id
     *              The project to query for.
     * @param array $cf_options
     *              The optional name filter.
     * @return array
     * @access private
     */
    private function _testcaseCustomFieldData(&$testcase, $project_id, &$cf_options) {
        $cf = $this->tcaseMgr->get_linked_cfields_at_design(
            $testcase['tcase_id'], $testcase['tcversion_id'],
            null, null, $project_id);
        $filtered_cf = array();

        $cfieds_selected = is_array($cf_options);
        foreach ($cf as $cf_id => $cfield) {
            if ($cfieds_selected && !in_array($cfield['name'], $cf_options)) {
                continue;
            }

            $filtered_cf[$cfield['name']] = array(
              'name' => $cfield['id']['name'],
              'label' => $cfield['id']['label'],
              'type' => $cfield['id']['type'],
              'value' => $cfield['id']['value']
            );
        }

        return $filtered_cf;
    }

    /**
     * Run all the necessary checks to see if a GetTestCasesForTestPlanRequest()
     * can be accepted.
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function _checkGetTestCasesForTestPlanRequest($messagePrefix = '') {
        $status = $this->authenticate();
        if($status) {
            $status &= $this->checkTestPlanID( $messagePrefix );

            if($status && $this->_isTestCaseIDPresent( $messagePrefix )) {
                $status &= $this->_checkTCIDAndTPIDValid( null, $messagePrefix );
            }
            if($status && $this->_isBuildIDPresent( $messagePrefix )) {
                $status &= $this->checkBuildID( $messagePrefix );
            }
        }
        return $status;
    }

    /**
     * Gets value of a Custom Field with scope='design' for a given Test case
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["testcaseexternalid"]:
     * @param string $args["version"]:
     *            version number
     * @param string $args["testprojectid"]:
     * @param string $args["customfieldname"]:
     *            custom field name
     * @param string $args["details"]
     *            optional, changes output information
     *            null or 'value' => just value
     *            'full' => a map with all custom field definition
     *            plus value and internal test case id
     *            'simple' => value plus custom field name, label, and type(as code).
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestCaseCustomFieldDesignValue($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID',
                'checkTestCaseIdentity',
                'checkTestCaseVersionNumber'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $status_ok = $this->_isParamPresent( self::$customFieldNameParamName, $msg_prefix, self::SET_ERROR );
        }

        if($status_ok) {
            $ret = $this->checkTestCaseAncestry();
            $status_ok = $ret['status_ok'];
            if($status_ok) {
                // Check if version number exists for Test Case
                $ret = $this->checkTestCaseVersionNumberAncestry();
                $status_ok = $ret['status_ok'];
            }

            if($status_ok) {
                $status_ok = $this->_checkGetTestCaseCustomFieldDesignValueRequest( $msg_prefix );
            } else {
                $this->errors[] = new IXR_Error( $ret['error_code'], $msg_prefix . $ret['error_msg'] );
            }
        }

        if($status_ok && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
            $details = 'value';
            if($this->_isParamPresent( self::$detailsParamName )) {
                $details = $this->args[self::$detailsParamName];
            }

            $cf_name = $this->args[self::$customFieldNameParamName];
            $tproject_id = $this->args[self::$testProjectIDParamName];
            $tcase_id = $this->args[self::$testCaseIDParamName];

            $cfield_mgr = $this->tprojectMgr->cfield_mgr;
            $cfinfo = $cfield_mgr->get_by_name( $cf_name );
            $cfield = current( $cfinfo );
            $filters = array(
                    'cfield_id' => $cfield['id']
            );
            $cfieldSpec = $this->tcaseMgr->get_linked_cfields_at_design( $tcase_id, $this->tcVersionID, null, $filters, $tproject_id );

            switch($details) {
                case 'full' :
                    $retval = $cfieldSpec[$cfield['id']];
                    break;

                case 'simple' :
                    $retval = array(
                            'name' => $cf_name,
                            'label' => $cfieldSpec[$cfield['id']]['label'],
                            'type' => $cfieldSpec[$cfield['id']]['type'],
                            'value' => $cfieldSpec[$cfield['id']]['value']
                    );
                    break;

                case 'value' :
                default :
                    $retval = $cfieldSpec[$cfield['id']]['value'];
                    break;
            }
            return $retval;
        } else {
            return $this->errors;
        }
    }

    /**
     * Run all the necessary checks to see if GetTestCaseCustomFieldDesignValueRequest()
     * can be accepted.
     *
     * - Custom Field exists ?
     * - Can be used on a test case ?
     * - Custom Field scope includes 'design' ?
     * - is linked to testproject that owns test case ?
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function _checkGetTestCaseCustomFieldDesignValueRequest($messagePrefix = '') {
        // $status_ok=$this->authenticate($messagePrefix);
        $cf_name = $this->args[self::$customFieldNameParamName];

        // $testCaseIDParamName = "testcaseid";
        // public static $testCaseExternalIDParamName = "testcaseexternalid";

        // Custom Field checks:
        // - Custom Field exists ?
        // - Can be used on a test case ?
        // - Custom Field scope includes 'design' ?
        // - is linked to testproject that owns test case ?
        //

        // - Custom Field exists ?
        $cfield_mgr = $this->tprojectMgr->cfield_mgr;
        $cfinfo = $cfield_mgr->get_by_name( $cf_name );
        if(!($status_ok = ! is_null( $cfinfo ))) {
            $msg = sprintf( NO_CUSTOMFIELD_BY_THIS_NAME_STR, $cf_name );
            $this->errors[] = new IXR_Error( NO_CUSTOMFIELD_BY_THIS_NAME, $messagePrefix . $msg );
        }

        // - Can be used on a test case ?
        if($status_ok) {
            $cfield = current( $cfinfo );
            $status_ok =(strcasecmp( $cfield['node_type'], 'testcase' ) == 0);
            if(! $status_ok) {
                $msg = sprintf( CUSTOMFIELD_NOT_APP_FOR_NODE_TYPE_STR, $cf_name, 'testcase', $cfield['node_type'] );
                $this->errors[] = new IXR_Error( CUSTOMFIELD_NOT_APP_FOR_NODE_TYPE, $messagePrefix . $msg );
            }
        }

        // - Custom Field scope includes 'design' ?
        if($status_ok) {
            $status_ok =($cfield['show_on_design'] || $cfield['enable_on_design']);
            if(! $status_ok) {
                $msg = sprintf( CUSTOMFIELD_HAS_NOT_DESIGN_SCOPE_STR, $cf_name );
                $this->errors[] = new IXR_Error( CUSTOMFIELD_HAS_NOT_DESIGN_SCOPE, $messagePrefix . $msg );
            }
        }

        // - is linked to testproject that owns test case ?
        if($status_ok) {
            $allCF = $cfield_mgr->get_linked_to_testproject( $this->args[self::$testProjectIDParamName] );
            $status_ok = ! is_null( $allCF ) && isset( $allCF[$cfield['id']] );
            if(! $status_ok) {
                $tproject_info = $this->tprojectMgr->get_by_id( $this->args[self::$testProjectIDParamName] );
                $msg = sprintf( CUSTOMFIELD_NOT_ASSIGNED_TO_TESTPROJECT_STR, $cf_name, $tproject_info['name'], $this->args[self::$testProjectIDParamName] );
                $this->errors[] = new IXR_Error( CUSTOMFIELD_NOT_ASSIGNED_TO_TESTPROJECT, $messagePrefix . $msg );
            }
        }

        return $status_ok;
    }

    /**
     * getKeywordSet()
     *
     * @param
     *            int tproject_id
     *
     * @return string that represent a list of keyword id(comma is character separator)
     *
     * @access protected
     */
    protected function getKeywordSet($tproject_id) {
        $keywordSet = null;
        $kMethod = null;

        if($this->_isParamPresent( self::$keywordNameParamName )) {
            $kMethod = 'getValidKeywordSetByName';
            $accessKey = self::$keywordNameParamName;
        } else if($this->_isParamPresent( self::$keywordIDParamName )) {
            $kMethod = 'getValidKeywordSetById';
            $accessKey = self::$keywordIDParamName;
        }

        if(! is_null( $kMethod )) {
            $keywordSet = $this->$kMethod( $tproject_id, $this->args[$accessKey] );
        }

        return $keywordSet;
    }

    /**
     * getValidKeywordSetByName()
     *
     * @param int $tproject_id
     * @param $keywords array
     *            of keywords names
     *
     * @return string that represent a list of keyword id(comma is character separator)
     *
     * @access protected
     */
    protected function getValidKeywordSetByName($tproject_id, $keywords) {
        return $this->getValidKeywordSet( $tproject_id, $keywords, true );
    }

    /**
     *
     * @param $tproject_id the
     *            testprojectID the keywords belong
     * @param $keywords array
     *            of keywords or keywordIDs
     * @param $byName set
     *            this to true if $keywords is an array of keywords, false if it's an array of keywordIDs
     * @return string that represent a list of keyword id(comma is character separator)
     */
    protected function getValidKeywordSet($tproject_id, $keywords, $byName, $op = null) {
        $keywordSet = array();

        $sql = " SELECT keyword,id FROM {$this->tables['keywords']} " . " WHERE testproject_id = {$tproject_id} ";

        $keywords = trim( $keywords );
        if($keywords != "") {
            $a_keywords = explode( ",", $keywords );
            $items_qty = count( $a_keywords );
            for($idx = 0; $idx < $items_qty; $idx ++) {
                $a_keywords[$idx] = trim( $a_keywords[$idx] );
            }
            $itemSet = implode( "','", $a_keywords );

            if($byName) {
                $sql .= " AND keyword IN('{$itemSet}')";
            } else {
                $sql .= " AND id IN({$itemSet})";
            }
        }

        $keywordMap = $this->dbObj->fetchRowsIntoMap( $sql, 'keyword' );
        if(! is_null( $keywordMap )) {
            if(is_null( $op )) {
                $a_items = null;
                for($idx = 0; $idx < $items_qty; $idx ++) {
                    if(isset( $keywordMap[$a_keywords[$idx]] )) {
                        $a_items[] = $keywordMap[$a_keywords[$idx]]['id'];
                    }
                }
                if(! is_null( $a_items )) {
                    $keywordSet = implode( ",", $a_items );
                }
            } else {
                foreach( $keywordMap as $kw => $elem ) {
                    $keywordSet[$elem['id']] = $elem['keyword'];
                }
            }
        }

        return $keywordSet;
    }

    /**
     * getValidKeywordSetById()
     *
     * @param int $tproject_id
     * @param $keywords array
     *            of keywords ID
     *
     * @return string that represent a list of keyword id(comma is character separator)
     *
     * @access protected
     */
    protected function getValidKeywordSetById($tproject_id, $keywords) {
        return $this->getValidKeywordSet( $tproject_id, $keywords, false );
    }

    /**
     * checks if test case version number is positive integer
     *
     * @return boolean
     *
     * @access protected
     */
    protected function checkTestCaseVersionNumber() {
        $status = true;
        if(!($status = $this->_isParamPresent( self::$versionNumberParamName ))) {
            $msg = sprintf( MISSING_REQUIRED_PARAMETER_STR, self::$versionNumberParamName );
            $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
        } else {
            $version = $this->args[self::$versionNumberParamName];
            if(!($status = is_int( $version ))) {
                $msg = sprintf( PARAMETER_NOT_INT_STR, self::$versionNumberParamName, $version );
                $this->errors[] = new IXR_Error( PARAMETER_NOT_INT, $msg );
            } else {
                if(!($status =($version > 0))) {
                    $msg = sprintf( VERSION_NOT_VALID_STR, $version );
                    $this->errors[] = new IXR_Error( VERSION_NOT_VALID, $msg );
                }
            }
        }
        return $status;
    }

    /**
     * Add a test case version to a test plan
     *
     * @param
     *            args['testprojectid']
     * @param
     *            args['testplanid']
     * @param
     *            args['testcaseexternalid']
     * @param
     *            args['version']
     * @param
     *            args['platformid'] - OPTIONAL Only if test plan has no platforms
     * @param
     *            args['executionorder'] - OPTIONAL
     * @param
     *            args['urgency'] - OPTIONAL
     * @param
     *            args['overwrite'] - OPTIONAL
     *
     */
    public function addTestCaseToTestPlan($args) {
        $operation = __FUNCTION__;
        $messagePrefix = "({$operation}) - ";
        $this->_setArgs( $args );

        $op_result = null;
        $additional_fields = '';
        $doDeleteLinks = false;
        $doLink = false;
        $hasPlatforms = false;
        $hasPlatformIDArgs = false;
        $platform_id = 0;
        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID',
                'checkTestCaseVersionNumber',
                'checkTestCaseIdentity',
                'checkTestPlanID'
        );

        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix );

        // Test Plan belongs to test project ?
        if($status_ok) {
            $tproject_id = $this->args[self::$testProjectIDParamName];
            $tplan_id = $this->args[self::$testPlanIDParamName];
            $tplan_info = $this->tplanMgr->get_by_id( $tplan_id );

            $sql = " SELECT id FROM {$this->tables['testplans']}" . " WHERE testproject_id={$tproject_id} AND id = {$tplan_id}";

            $rs = $this->dbObj->get_recordset( $sql );

            if(count( $rs ) != 1) {
                $status_ok = false;
                $tproject_info = $this->tprojectMgr->get_by_id( $tproject_id );
                $msg = sprintf( TPLAN_TPROJECT_KO_STR, $tplan_info['name'], $tplan_id, $tproject_info['name'], $tproject_id );
                $this->errors[] = new IXR_Error( TPLAN_TPROJECT_KO, $msg_prefix . $msg );
            }
        }

        // Test Case belongs to test project ?
        if($status_ok) {
            $ret = $this->checkTestCaseAncestry();
            $status_ok = $ret['status_ok'];

            if(! $ret['status_ok']) {
                $this->errors[] = new IXR_Error( $ret['error_code'], $msg_prefix . $ret['error_msg'] );
            }
        }

        // Does this Version number exist for this test case ?
        if($status_ok) {
            $tcase_id = $this->args[self::$testCaseIDParamName];
            $version_number = $this->args[self::$versionNumberParamName];
            $sql = " SELECT TCV.version,TCV.id " . " FROM {$this->tables['nodes_hierarchy']} NH, {$this->tables['tcversions']} TCV " . " WHERE NH.parent_id = {$tcase_id} " . " AND TCV.version = {$version_number} " . " AND TCV.id = NH.id ";

            $target_tcversion = $this->dbObj->fetchRowsIntoMap( $sql, 'version' );
            if(! is_null( $target_tcversion ) && count( $target_tcversion ) != 1) {
                $status_ok = false;
                $tcase_info = $this->tcaseMgr->get_by_id( $tcase_id );
                $msg = sprintf( TCASE_VERSION_NUMBER_KO_STR, $version_number, $tcase_external_id, $tcase_info[0]['name'] );
                $this->errors[] = new IXR_Error( TCASE_VERSION_NUMBER_KO, $msg_prefix . $msg );
            }
        }

        if($status_ok) {
            // Optional parameters
            $additional_fields = null;
            $additional_values = null;
            $opt_fields = array(
                    self::$urgencyParamName => 'urgency',
                    self::$executionOrderParamName => 'node_order'
            );
            $opt_values = array(
                    self::$urgencyParamName => null,
                    self::$executionOrderParamName => 1
            );
            foreach( $opt_fields as $key => $field_name ) {
                if($this->_isParamPresent( $key )) {
                    $additional_values[] = $this->args[$key];
                    $additional_fields[] = $field_name;
                } else {
                    if(! is_null( $opt_values[$key] )) {
                        $additional_values[] = $opt_values[$key];
                        $additional_fields[] = $field_name;
                    }
                }
            }
        }

        if($status_ok) {
            // 20100705 - work in progress - BUGID 3564
            // if test plan has platforms, platformid argument is MANDATORY
            $opt = array(
                    'outputFormat' => 'mapAccessByID'
            );
            $platformSet = $this->tplanMgr->getPlatforms( $tplan_id, $opt );
            $hasPlatforms = ! is_null( $platformSet );
            $hasPlatformIDArgs = $this->_isParamPresent( self::$platformIDParamName );

            if($hasPlatforms) {
                if($hasPlatformIDArgs) {
                    // Check if platform id belongs to test plan
                    $platform_id = $this->args[self::$platformIDParamName];
                    $status_ok = isset( $platformSet[$platform_id] );
                    if(! $status_ok) {
                        $msg = sprintf( PLATFORM_ID_NOT_LINKED_TO_TESTPLAN_STR, $platform_id, $tplan_info['name'] );
                        $this->errors[] = new IXR_Error( PLATFORM_ID_NOT_LINKED_TO_TESTPLAN, $msg );
                    }
                } else {
                    $msg = sprintf( MISSING_PLATFORMID_BUT_NEEDED_STR, $tplan_info['name'], $tplan_id );
                    $this->errors[] = new IXR_Error( MISSING_PLATFORMID_BUT_NEEDED, $msg_prefix . $msg );
                    $status_ok = false;
                }
            }
        }

        if($status_ok && $this->userHasRight( "testplan_planning", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
            // 20100711 - franciscom
            // Because for TL 1.9 link is done to test plan + platform, logic used
            // to understand what to unlink has to be changed.
            // If same version exists on other platforms
            // just add this new record
            // If other version exists on other platforms
            // error -> give message to user
            //
            //

            // Other versions must be unlinked, because we can only link ONE VERSION at a time
            // 20090411 - franciscom
            // As implemented today I'm going to unlink ALL linked versions, then if version
            // I'm asking to link is already linked, will be unlinked and then relinked.
            // May be is not wise, IMHO this must be refactored, and give user indication that
            // requested version already is part of Test Plan.
            //
            $sql = " SELECT TCV.version,TCV.id " . " FROM {$this->tables['nodes_hierarchy']} NH, {$this->tables['tcversions']} TCV " . " WHERE NH.parent_id = " . intval( $tcase_id ) . " AND TCV.id = NH.id ";

            $all_tcversions = $this->dbObj->fetchRowsIntoMap( $sql, 'id' );
            $id_set = array_keys( $all_tcversions );

            // get records regarding all test case versions linked to test plan
            $in_clause = implode( ",", $id_set );
            $sql = " SELECT tcversion_id, platform_id, PLAT.name FROM {$this->tables['testplan_tcversions']} TPTCV " . " LEFT OUTER JOIN {$this->tables['platforms']} PLAT ON PLAT.id = platform_id " . " WHERE TPTCV.testplan_id={$tplan_id} AND TPTCV.tcversion_id IN({$in_clause}) ";

            if($hasPlatforms) {
                $sql .= " AND TPTCV.platform_id=" . intval( $platform_id );
            }

            $rs = $this->dbObj->fetchMapRowsIntoMap( $sql, 'tcversion_id', 'platform_id' );

            $doLink = is_null( $rs );

            if(! $doLink) {
                // Are we going to update ?
                if(isset( $rs[$target_tcversion[$version_number]['id']] )) {
                    if($hasPlatforms) {
                        $plat_keys = array_flip( array_keys( $rs[$target_tcversion[$version_number]['id']] ) );

                        // need to understand what where the linked platforms.
                        $platform_id = $this->args[self::$platformIDParamName];
                        $linkExists = isset( $plat_keys[$platform_id] );
                        $doLink = ! $linkExists;
                        if($linkExists) {
                            $platform_name = $rs[$target_tcversion[$version_number]['id']][$platform_id]['name'];
                            $msg = sprintf( LINKED_FEATURE_ALREADY_EXISTS_STR, $tplan_info['name'], $tplan_id, $platform_name, $platform_id );
                            $this->errors[] = new IXR_Error( LINKED_FEATURE_ALREADY_EXISTS, $msg_prefix . $msg );
                            $status_ok = false;
                        }
                    } else {
                        // do nothing on silence, and say bye!!!
                        $op_result['operation'] = $operation;
                        $op_result['status'] = true;
                        $op_result['message'] = 'Nothing to do - already linked';
                        return $op_result;
                    }
                } else {
                    // Other version than requested done is already linked
                    $doLink = false;
                    if($this->_isParamPresent( self::$overwriteParamName ) && $this->args[self::$overwriteParamName]) {
                        $doLink = $doDeleteLinks = true;
                    }

                    reset( $rs );
                    $linked_tcversion = key( $rs );
                    $other_version = $all_tcversions[$linked_tcversion]['version'];
                    if(! $doLink) {
                        $doLink = false;
                        $msg = sprintf( OTHER_VERSION_IS_ALREADY_LINKED_STR, $other_version, $version_number, $tplan_info['name'], $tplan_id );
                        $this->errors[] = new IXR_Error( OTHER_VERSION_IS_ALREADY_LINKED, $msg_prefix . $msg );
                        $status_ok = false;
                    }
                }
            }

            if($doLink && $hasPlatforms) {
                $additional_values[] = $platform_id;
                $additional_fields[] = 'platform_id';
            }

            if($doDeleteLinks) {
                // $in_clause=implode(",",$id_set);
                $sql = " DELETE FROM {$this->tables['testplan_tcversions']} " . " WHERE testplan_id=" . intval( $tplan_id ) . " AND tcversion_id=" . intval( $linked_tcversion );

                if($hasPlatforms) {
                    $sql .= " AND platform_id=" . intval( $platform_id );
                }
                $this->dbObj->exec_query( $sql );
            }

            if($doLink) {
                $fields = "testplan_id,tcversion_id,author_id,creation_ts";
                if(! is_null( $additional_fields )) {
                    $dummy = implode( ",", $additional_fields );
                    $fields .= ',' . $dummy;
                }

                $sql_values = "{$tplan_id},{$target_tcversion[$version_number]['id']}," . "{$this->userID},{$this->dbObj->db_now()}";
                if(! is_null( $additional_values )) {
                    $dummy = implode( ",", $additional_values );
                    $sql_values .= ',' . $dummy;
                }

                $sql = " INSERT INTO {$this->tables['testplan_tcversions']}({$fields}) VALUES({$sql_values})";
                $this->dbObj->exec_query( $sql );

                $op_result['feature_id'] = $this->dbObj->insert_id( $this->tables['testplan_tcversions'] );
            }
            $op_result['operation'] = $operation;
            $op_result['status'] = true;
            $op_result['message'] = '';
        }

        return($status_ok ? $op_result : $this->errors);
    }

    /**
     * get set of test suites AT TOP LEVEL of tree on a Test Project
     *
     * @param
     *            args['testprojectid']
     *
     * @return array
     *
     */
    public function getFirstLevelTestSuitesForTestProject($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $status_ok = true;
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $result = $this->tprojectMgr->get_first_level_test_suites( $this->args[self::$testProjectIDParamName] );
            if(is_null( $result )) {
                $status_ok = false;
                $tproject_info = $this->tprojectMgr->get_by_id( $this->args[self::$testProjectIDParamName] );
                $msg = $msg_prefix . sprintf( TPROJECT_IS_EMPTY_STR, $tproject_info['name'] );
                $this->errors[] = new IXR_ERROR( TPROJECT_IS_EMPTY, $msg );
            }
        }
        return $status_ok ? $result : $this->errors;
    }

    /**
     * Assign Requirements to a test case.
     * IMPORTANT:
     * Latest Versions of Req & Test Case will be used
     *
     * we can assign multiple requirements.
     * Requirements can belong to different Requirement Spec
     *
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testcaseexternalid"]
     * @param int $args["testprojectid"]
     * @param string $args["requirements"]
     *            array(array('req_spec' => 1,'requirements' => array(2,4)),
     *            array('req_spec' => 3,'requirements' => array(22,42))
     *
     */
    public function assignRequirements($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $status_ok = true;
        $this->_setArgs( $args );
        $resultInfo = array();
        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID',
                'checkTestCaseIdentity'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $ret = $this->checkTestCaseAncestry();
            $status_ok = $ret['status_ok'];
            if(! $status_ok) {
                $this->errors[] = new IXR_Error( $ret['error_code'], $msg_prefix . $ret['error_msg'] );
            }
        }

        if($status_ok) {
            $ret = $this->checkReqSpecQuality();
            $status_ok = $ret['status_ok'];
            if(! $status_ok) {
                $this->errors[] = new IXR_Error( $ret['error_code'], $msg_prefix . $ret['error_msg'] );
            }
        }

        if($status_ok) {
            // assignment
            // Note: when test case identity is checked this args key is setted
            // this does not means that this mut be present on method call.
            //
            $tcase_id = $this->args[self::$testCaseIDParamName];
            foreach( $this->args[self::$requirementsParamName] as $item ) {
                foreach( $item['requirements'] as $req_id ) {
                    $this->reqMgr->assignToTCaseUsingLatestVersions( $req_id, $tcase_id, $this->userID );
                }
            }
            $resultInfo[] = array(
                    "operation" => $operation,
                    "status" => true,
                    "id" => - 1,
                    "additionalInfo" => '',
                    "message" => GENERAL_SUCCESS_STR
            );
        }

        return($status_ok ? $resultInfo : $this->errors);
    }

    /**
     * checks if a test case belongs to test project
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return map with following keys
     *         boolean map['status_ok']
     *         string map['error_msg']
     *         int map['error_code']
     */
    protected function checkTestCaseAncestry($messagePrefix = '') {
        $ret = array(
                'status_ok' => true,
                'error_msg' => '',
                'error_code' => 0
        );
        $tproject_id = $this->args[self::$testProjectIDParamName];
        $tcase_id = $this->args[self::$testCaseIDParamName];
        $tcase_tproject_id = $this->tcaseMgr->get_testproject( $tcase_id );

        if($tcase_tproject_id != $tproject_id) {
            $status_ok = false;
            $tcase_info = $this->tcaseMgr->get_by_id( $tcase_id );
            $dummy = $this->tcaseMgr->getExternalID( $tcase_id );
            $tcase_external_id = $dummy[0];

            $tproject_info = $this->tprojectMgr->get_by_id( $tproject_id );
            $msg = $messagePrefix . sprintf( TCASE_TPROJECT_KO_STR, $tcase_external_id, $tcase_info[0]['name'], $tproject_info['name'], $tproject_id );

            $ret = array(
                    'status_ok' => false,
                    'error_msg' => $msg,
                    'error_code' => TCASE_TPROJECT_KO
            );
        }
        return $ret;
    }

    /*
     * checks Quality of requirements spec
     * checks done on
     * Requirements Specification is present on system
     * Requirements Specification belongs to test project
     *
     * @return map with following keys
     * boolean map['status_ok']
     * string map['error_msg']
     * int map['error_code']
     */
    protected function checkReqSpecQuality() {
        $ret = array(
                'status_ok' => true,
                'error_msg' => '',
                'error_code' => 0
        );
        $tproject_id = $this->args[self::$testProjectIDParamName];
        $nodes_types = $this->tprojectMgr->tree_manager->get_available_node_types();

        foreach( $this->args[self::$requirementsParamName] as $item ) {
            // does it exist ?
            $req_spec_id = $item['req_spec'];
            $reqspec_info = $this->reqSpecMgr->get_by_id( $req_spec_id );
            if(is_null( $reqspec_info )) {
                $status_ok = false;
                $msg = sprintf( REQSPEC_KO_STR, $req_spec_id );
                $error_code = REQSPEC_KO;
                break;
            }

            // does it belongs to test project ?
            $a_path = $this->tprojectMgr->tree_manager->get_path( $req_spec_id );
            $req_spec_tproject_id = $a_path[0]['parent_id'];
            if($req_spec_tproject_id != $tproject_id) {
                $status_ok = false;
                $tproject_info = $this->tprojectMgr->get_by_id( $tproject_id );
                $msg = sprintf( REQSPEC_TPROJECT_KO_STR, $reqspec_info['title'], $req_spec_id, $tproject_info['name'], $tproject_id );
                $error_code = REQSPEC_TPROJECT_KO;
                break;
            }

            // does this specification have requirements ?
            $my_requirements = $this->tprojectMgr->tree_manager->get_subtree_list( $req_spec_id, $nodes_types['requirement'] );
            $status_ok =(trim( $my_requirements ) != "");
            if(! $status_ok) {
                $msg = sprintf( REQSPEC_IS_EMPTY_STR, $reqspec_info['title'], $req_spec_id );
                $error_code = REQSPEC_IS_EMPTY;
                break;
            }

            // if everything is OK, analise requirements
            if($status_ok) {
                $dummy = array_flip( explode( ",", $my_requirements ) );
                foreach( $item['requirements'] as $req_id ) {
                    if(! isset( $dummy[$req_id] )) {
                        $status_ok = false;
                        $req_info = $this->reqMgr->get_by_id( $req_id, requirement_mgr::LATEST_VERSION );

                        if(is_null( $req_info )) {
                            $msg = sprintf( REQ_KO_STR, $req_id );
                            $error_code = REQ_KO;
                        } else {
                            $req_info = $req_inf[0];
                            $msg = sprintf( REQ_REQSPEC_KO_STR, $req_info['req_doc_id'], $req_info['title'], $req_id, $reqspec_info['title'], $req_spec_id );
                            $error_code = REQ_REQSPEC_KO;
                        }
                        break;
                    }
                }
            }

            if(! $status_ok) {
                break;
            }
        }

        if(! $status_ok) {
            $ret = array('status_ok' => false,
                          'error_msg' => $msg,
                          'error_code' => $error_code
            );
        }
        return $ret;
    }

    /**
     * Insert record into execution_bugs table
     *
     * @param int $executionID
     * @param string $bugID
     * @return boolean
     * @access protected
     *         contribution by hnishiyama
     */
    protected function _insertExecutionBug($executionID, $bugID) {
        // Check for existence of executionID
        $sql = "SELECT id FROM {$this->tables['executions']} WHERE id={$executionID}";
        $rs = $this->dbObj->fetchRowsIntoMap( $sql, 'id' );
        $status_ok = !(is_null( $rs ) || $bugID == '');
        if($status_ok) {
            $safeBugID = $this->dbObj->prepare_string( $bugID );
            $sql = "SELECT execution_id FROM {$this->tables['execution_bugs']} " . "WHERE execution_id={$executionID} AND bug_id='{$safeBugID}'";

            if(is_null( $this->dbObj->fetchRowsIntoMap( $sql, 'execution_id' ) )) {
                $sql = "INSERT INTO {$this->tables['execution_bugs']} " . "(execution_id,bug_id) VALUES({$executionID},'{$safeBugID}')";
                $result = $this->dbObj->exec_query( $sql );
                $status_ok = $result ? true : false;
            }
        }
        return $status_ok;
    }

    /**
     * get bugs linked to an execution ID
     *
     * @param int $execution_id
     *
     * @return map indexed by bug_id
     */
    protected function _getBugsForExecutionId($execution_id) {
        $rs = null;
        if(! is_null( $execution_id ) && $execution_id != '') {
            $sql = "SELECT execution_id,bug_id, B.name AS build_name " . "FROM {$this->tables['execution_bugs']} ," . " {$this->tables['executions']} E, {$this->tables['builds']} B " . "WHERE execution_id={$execution_id} " . "AND   execution_id=E.id " . "AND   E.build_id=B.id " . "ORDER BY B.name,bug_id";
            $rs = $this->dbObj->fetchRowsIntoMap( $sql, 'bug_id' );
        }
        return $rs;
    }

    /**
     * Gets attachments for specified test suite.
     * The attachment file content is Base64 encoded. To save the file to disk in client,
     * Base64 decode the content and write file in binary mode.
     *
     * @param struct $args
     * @param string $args["devKey"]
  attachm   *            Developer key
     * @param int $args["testsuiteid"]:
     *            id of the testsuite
     *
     * @return mixed $resultInfo
     * @author dennis@etern-it.de
     */
    public function getTestSuiteAttachments($args) {
        $this->_setArgs( $args );
        $box= null;
        $checkFunctions = array(
                'authenticate',
                'checkTestSuiteID'
        );
        $status_ok = $this->_runChecks( $checkFunctions ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        if($status_ok) {
            $tsuite_id = $this->args[self::$testSuiteIDParamName];
            $docRepo = tlAttachmentRepository::create( $this->dbObj );
            $docSet = $docRepo->getAttachmentInfosFor( $tsuite_id, "nodes_hierarchy" );

            if($docSet) {
                foreach( $docSet as $docu ) {
                    $aID = $docu["id"];
                    $content = $docRepo->getAttachmentContent( $aID, $docu );

                    if($content != null) {
                        $box[$aID]["id"] = $aID;
                        $box[$aID]["name"] = $docu["file_name"];
                        $box[$aID]["file_type"] = $docu["file_type"];
                        $box[$aID]["title"] = $docu["title"];
                        $box[$aID]["date_added"] = $docu["date_added"];
                        $box[$aID]["content"] = base64_encode( $content );
                    }
                }
            }
        }
        return $status_ok ? $box: $this->errors;
    }

    /**
     * Gets attachments for specified test case VERSION.
     * The attachment file content is Base64 encoded. To save the file to disk in client,
     * Base64 decode the content and write file in binary mode.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *            Developer key
     * @param int $args["testcaseid"]:
     *            optional, if does not is present
     *            testcaseexternalid must be present
     *
     * @param int $args["version"]:
     *            optional, if not present, the latest version will be used
     *
     * @param int $args["testcaseexternalid"]:
     *            optional, if does not is present
     *            testcaseid must be present
     *
     * @return mixed $resultInfo
     */
    public function getTestCaseAttachments($args) {
        $this->_setArgs( $args );
        $box= null;
        $checkFunctions = array('authenticate',
                                 'checkTestCaseIdentity'
        );
        $status_ok = $this->_runChecks( $checkFunctions ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        if( !$status_ok ) {
            return $this->errors;
        } 
    
        if( $status_ok && 
            !$this->_isParamPresent( self::$versionNumberParamName ) ) {
          try {
            $tc = $this->getTestCase($args,self::THROW_ON_ERROR);
            $this->args[self::$versionNumberParamName] = $tc[0][self::$versionNumberParamName];
          } catch (Exception $e) {
            return $this->errors;
          }  
        }
        
        $map = $this->checkTestCaseVersionNumberAncestry();
        $status_ok = $map["status_ok"];
        
        if($status_ok) {
            $tcvid = $this->tcVersionID;
            $docRepo = tlAttachmentRepository::create( $this->dbObj );
            $docSet = $docRepo->getAttachmentInfosFor( $tcvid, "tcversions" );

            if($docSet) {
                foreach( $docSet as $docu ) {
                    $aID = $docu["id"];
                    $content = $docRepo->getAttachmentContent( $aID, $docu );

                    if($content != null) {
                        $box[$aID]["id"] = $aID;
                        $box[$aID]["name"] = $docu["file_name"];
                        $box[$aID]["file_type"] = $docu["file_type"];
                        $box[$aID]["title"] = $docu["title"];
                        $box[$aID]["date_added"] = $docu["date_added"];
                        $box[$aID]["content"] = base64_encode( $content );
                    }
                }
            }
        } else {
            $this->errors[] = new IXR_Error( INVALID_TESTCASE_VERSION_NUMBER, $map["error_msg"] );
        }
        return $status_ok ? $box: $this->errors;
    }

    /**
     * update a test suite
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testprojectid"]
     *            OR string $args["prefix"]
     * @param string $args["testsuitename"]
     *            optional
     * @param string $args["details"]
     *            optional
     * @param int $args["parentid"]
     *            optional, if do not provided means test suite must be top level.
     * @param int $args["order"]
     *            optional. Order inside parent container
     *
     * @return mixed $resultInfo
     */
    public function updateTestSuite($args) {
        $args[self::$actionParamName] = 'update';
        return $this->createTestSuite( $args );
    }

    /**
     * create a test suite
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testprojectid"]
     * @param string $args["testsuitename"]
     * @param string $args["details"]
     * @param int $args["parentid"]
     *            optional, if do not provided means test suite must be top level.
     * @param int $args["order"]
     *            optional. Order inside parent container
     * @param int $args["checkduplicatedname"]
     *            optional, default true.
     *            will check if there are siblings with same name.
     *
     * @param int $args["actiononduplicatedname"]
     *            optional
     *            applicable only if $args["checkduplicatedname"]=true
     *            what to do if already a sibling exists with same name.
     *
     * @return mixed $resultInfo
     */
    public function createTestSuite($args) {
        $result = array();
        $this->_setArgs( $args );
        $action = isset( $this->args, self::$actionParamName ) ? $this->args[self::$actionParamName] : 'create';

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectIdentity'
        );

        switch($action) {
            case 'update' :
                $operation = 'updateTestSuite';
                $opt = array(
                        self::$detailsParamName => null,
                        self::$testSuiteNameParamName => null,
                        self::$orderParamName => testsuite::DEFAULT_ORDER,
                        self::$checkDuplicatedNameParamName => testsuite::CHECK_DUPLICATE_NAME,
                        self::$actionOnDuplicatedNameParamName => 'block'
                );
                break;

            case 'create' :
            default :
                $operation = __FUNCTION__;
                $opt = array(
                        self::$orderParamName => testsuite::DEFAULT_ORDER,
                        self::$checkDuplicatedNameParamName => testsuite::CHECK_DUPLICATE_NAME,
                        self::$actionOnDuplicatedNameParamName => 'block'
                );
                $checkFunctions[] = 'checkTestSuiteName';
                break;
        }

        $msg_prefix = "({$operation}) - ";
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        // When working on PRIVATE containers, globalRole Admin is ENOUGH
        // because this is how TestLink works when this action is done on GUI
        if($status_ok && $this->user->globalRole->dbID != TL_ROLES_ADMIN) {
            $status_ok = FALSE;
            if($this->userHasRight( "mgt_modify_tc", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
                $status_ok = true;
            }
        }

        if($status_ok) {
            // Needed After refactoring to use checkTestProjectIdentity()
            $key = self::$testProjectIDParamName;
            $args[$key] = $this->args[$key];

            // Optional parameters
            foreach( $opt as $key => $value ) {
                if($this->_isParamPresent( $key )) {
                    $opt[$key] = $this->args[$key];
                }
            }
        }

        if($status_ok) {
            $parent_id = $args[self::$testProjectIDParamName];
            $tprojectInfo = $this->tprojectMgr->get_by_id( $args[self::$testProjectIDParamName] );

            $tsuiteMgr = new testsuite( $this->dbObj );
            if($this->_isParamPresent( self::$parentIDParamName )) {
                $parent_id = $args[self::$parentIDParamName];

                // if parentid exists it must:
                // be a test suite id
                $node_info = $tsuiteMgr->get_by_id( $args[self::$parentIDParamName] );
                if(!($status_ok = ! is_null( $node_info ))) {
                    $msg = sprintf( INVALID_PARENT_TESTSUITEID_STR, $args[self::$parentIDParamName], $args[self::$testSuiteNameParamName] );
                    $this->errors[] = new IXR_Error( INVALID_PARENT_TESTSUITEID, $msg_prefix . $msg );
                }

                if($status_ok) {
                    // Must belong to target test project
                    $root_node_id = $tsuiteMgr->getTestProjectFromTestSuite( $args[self::$parentIDParamName], null );

                    if(!($status_ok =($root_node_id == $args[self::$testProjectIDParamName]))) {
                        $msg = sprintf( TESTSUITE_DONOTBELONGTO_TESTPROJECT_STR, $args[self::$parentIDParamName], $tprojectInfo['name'], $args[self::$testProjectIDParamName] );
                        $this->errors[] = new IXR_Error( TESTSUITE_DONOTBELONGTO_TESTPROJECT, $msg_prefix . $msg );
                    }
                }
            }
        }

        if($status_ok) {
            switch($action) {
                case 'update' :
                    $op = $tsuiteMgr->update( $args[self::$testSuiteIDParamName], $args[self::$testSuiteNameParamName], $args[self::$detailsParamName], $parent_id, $opt[self::$orderParamName] );

                    /*
                     * $opt[self::$checkDuplicatedNameParamName],
                     * $opt[self::$actionOnDuplicatedNameParamName]);
                     */
                    break;

                case 'create' :
                default :
                    $op = $tsuiteMgr->create( $parent_id, $args[self::$testSuiteNameParamName], $args[self::$detailsParamName], $opt[self::$orderParamName], $opt[self::$checkDuplicatedNameParamName], $opt[self::$actionOnDuplicatedNameParamName] );
                    break;
            }

            if(($status_ok = $op['status_ok'])) {
                $op['status'] = $op['status_ok'] ? true : false;
                $op['operation'] = $operation;
                $op['additionalInfo'] = '';
                $op['message'] = $op['msg'];
                unset( $op['msg'] );
                unset( $op['status_ok'] );
                $result[] = $op;
            } else {
                // @TODO needs refactoring for UPDATE action
                $op['msg'] = sprintf( $op['msg'], $args[self::$testSuiteNameParamName] );
                $this->errors = $op;
            }
        }

        return $status_ok ? $result : $this->errors;
    }

    /**
     * test suite name provided is valid
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestSuiteName($messagePrefix = '') {
        $status_ok = isset( $this->args[self::$testSuiteNameParamName] ) ? true : false;
        if($status_ok) {
            $name = $this->args[self::$testSuiteNameParamName];
            if(! is_string( $name )) {
                $msg = $messagePrefix . TESTSUITENAME_NOT_STRING_STR;
                $this->errors[] = new IXR_Error( TESTSUITENAME_NOT_STRING, $msg );
                $status_ok = false;
            }
        } else {
            $this->errors[] = new IXR_Error( NO_TESTSUITENAME, $messagePrefix . NO_TESTSUITENAME_STR );
        }
        return $status_ok;
    }

    /**
     * Gets info about target test project
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testprojectname"]
     * @return mixed $resultInfo
     * @access public
     */
    public function getTestProjectByName($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $status_ok = false;
        $this->_setArgs( $args );

        if($this->authenticate() && $this->_isParamPresent( self::$testProjectNameParamName, $msg_prefix, self::SET_ERROR )) {
            $op = $this->helperGetTestProjectByName( $msg_prefix );
            $status_ok = $op['status_ok'];
        }
        return $status_ok ? $op['info'] : $this->errors;
    }

    /**
     * Gets info about target test project
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testprojectname"]
     * @param string $args["testplanname"]
     * @return mixed $resultInfo
     * @access public
     */
    public function getTestPlanByName($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $status_ok = true;
        $this->_setArgs( $args );
        if($this->authenticate()) {
            $keys2check = array(
                    self::$testPlanNameParamName,
                    self::$testProjectNameParamName
            );
            foreach( $keys2check as $key ) {
                $names[$key] = $this->_isParamPresent( $key, $msg_prefix, self::SET_ERROR ) ? trim( $this->args[$key] ) : '';
                if($names[$key] == '') {
                    $status_ok = false;
                    break;
                }
            }
        }

        if($status_ok) {
            // need to check name existences
            $name = $names[self::$testProjectNameParamName];
            $check_op = $this->tprojectMgr->checkNameExistence( $name );
            $not_found = $check_op['status_ok'];
            $status_ok = ! $not_found;
            if($not_found) {
                $status_ok = false;
                $msg = $msg_prefix . sprintf( TESTPROJECTNAME_DOESNOT_EXIST_STR, $name );
                $this->errors[] = new IXR_Error( TESTPROJECTNAME_DOESNOT_EXIST, $msg );
            } else {
                $tprojectInfo = current( $this->tprojectMgr->get_by_name( $name ) );
            }
        }

        if($status_ok) {
            $name = trim( $names[self::$testPlanNameParamName] );
            $info = $this->tplanMgr->get_by_name( $name, $tprojectInfo['id'] );
            if(!($status_ok = ! is_null( $info ))) {
                $msg = $msg_prefix . sprintf( TESTPLANNAME_DOESNOT_EXIST_STR, $name, $tprojectInfo['name'] );
                $this->errors[] = new IXR_Error( TESTPLANNAME_DOESNOT_EXIST, $msg );
            }
        }

        return $status_ok ? $info : $this->errors;
    }

    /**
     * get test case specification using external ir internal id
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testcaseid"]:
     *            optional, if does not is present
     *            testcaseexternalid must be present
     *
     * @param int $args["testcaseexternalid"]:
     *            optional, if does not is present
     *            testcaseid must be present
     * @param int $args["version"]:
     *            optional, if does not is present max version number will be
     *            retuned
     *
     * @return mixed $resultInfo
     */
    public function getTestCase($args,$throwOnError=false) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $status_ok = true;
        $this->_setArgs( $args );

        $checkFunctions = array('authenticate',
                                 'checkTestCaseIdentity'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );
        $version_id = testcase::LATEST_VERSION;
        $version_number = - 1;

        if($status_ok) {
            // check optional arguments
            if($this->_isParamPresent( self::$versionNumberParamName )) {
                if(($status_ok = $this->checkTestCaseVersionNumber())) {
                    $version_id = null;
                    $version_number = $this->args[self::$versionNumberParamName];
                }
            }
        }

        if($status_ok) {
            $testCaseMgr = new testcase( $this->dbObj );
            $id = $this->args[self::$testCaseIDParamName];

            $filters = array(
                    'active_status' => 'ALL',
                    'open_status' => 'ALL',
                    'version_number' => $version_number
            );

            $result = $testCaseMgr->get_by_id( $id, $version_id, $filters );

            if(0 == sizeof( $result )) {
                $status_ok = false;
                $this->errors[] = new IXR_ERROR( NO_TESTCASE_FOUND, $msg_prefix . NO_TESTCASE_FOUND_STR );
                return $this->errors;
            } else {
                if(isset( $this->args[self::$testCaseExternalIDParamName] )) {
                    $result[0]['full_tc_external_id'] = $this->args[self::$testCaseExternalIDParamName];
                } else {
                    $dummy = $this->tcaseMgr->getPrefix( $id );
                    $result[0]['full_tc_external_id'] = $dummy[0] . config_get( 'testcase_cfg' )->glue_character . $result[0]['tc_external_id'];
                }
            }
        }

        if($status_ok) {
            // before returning info need to understand if test case belongs to a test project
            // accessible to user requesting info
            // return $result[0]['id'];
            $this->args[self::$testProjectIDParamName] = $this->tcaseMgr->get_testproject( $result[0]['id'] );
            $status_ok = $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );
        }

        if( $status_ok ) {
          return  $result;
        } else {
          if( $throwOnError ) {
            throw new Exception("Test Case Does Not Exist", 1);
          } else {
            return $this->errors;
          }
        }
    }

    /**
     * create a test plan
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanname"]
     * @param int $args["testprojectname"]
     *            use instead of $args["prefix"]
     * @param int $args["prefix"]
     *            use instead of $args["testprojectname"]
     * @param string $args["notes"],
     *            optional
     * @param string $args["active"],
     *            optional default value 1
     * @param string $args["public"],
     *            optional default value 1
     *
     * @return mixed $resultInfo
     * @internal revisions
     */
    public function createTestPlan($args) {
        $this->_setArgs( $args );
        $status_ok = false;
        $msg_prefix = "(" . __FUNCTION__ . ") - ";

        if($this->authenticate()) {
            $keys2check = array(
                    self::$testPlanNameParamName
            );
            $status_ok = true;
            foreach( $keys2check as $key ) {
                $dummy[$key] = $this->_isParamPresent( $key, $msg_prefix, self::SET_ERROR ) ? trim( $this->args[$key] ) : '';
                if($dummy[$key] == '') {
                    $status_ok = false;
                    break;
                }
            }
        }

        if($status_ok) {
            $keys2check = array(
                    self::$testProjectNameParamName,
                    self::$prefixParamName
            );
            $status_ok = true;
            foreach( $keys2check as $key ) {
                $target[$key] = $this->_isParamPresent( $key, $msg_prefix ) ? trim( $this->args[$key] ) : '';
                if($target[$key] == '') {
                    $status_ok = false;
                } else {
                    // first good match is OK
                    $status_ok = true;
                    break;
                }
            }

            if($status_ok == false) {
                // lazy way to generate error
                foreach( $keys2check as $key ) {
                    $dummy[$key] = $this->_isParamPresent( $key, $msg_prefix ) ? trim( $this->args[$key] ) : '';
                    if($dummy[$key] == '') {
                        $status_ok = false;
                        break;
                    }
                }
            }
        }

        if($status_ok) {
            $status_ok = false;

            if(isset( $target[self::$testProjectNameParamName] ) && $target[self::$testProjectNameParamName] != '') {
                $name = trim( $this->args[self::$testProjectNameParamName] );
                $check_op = $this->tprojectMgr->checkNameExistence( $name );
                $status_ok = ! $check_op['status_ok'];
                if($status_ok) {
                    $tprojectInfo = current( $this->tprojectMgr->get_by_name( $name ) );
                } else {
                    $status_ok = false;
                    $msg = $msg_prefix . sprintf( TESTPROJECTNAME_DOESNOT_EXIST_STR, $name );
                    $this->errors[] = new IXR_Error( TESTPROJECTNAME_DOESNOT_EXIST, $msg );
                }
            } else {

                if(isset( $target[self::$prefixParamName] ) && $target[self::$prefixParamName] != '') {
                    $prefix = trim( $this->args[self::$prefixParamName] );
                    $tprojectInfo = $this->tprojectMgr->get_by_prefix( $prefix );

                    if(($status_ok = ! is_null( $tprojectInfo )) == false) {
                        $msg = $msg_prefix . sprintf( TPROJECT_PREFIX_DOESNOT_EXIST_STR, $prefix );
                        $this->errors[] = new IXR_Error( TPROJECT_PREFIX_DOESNOT_EXIST, $msg );
                    }
                }
            }
        }

        // Now we need to check if user has rights to do this action
        if($status_ok) {
            $this->args[self::$testProjectIDParamName] = $tprojectInfo['id'];
            $this->args[self::$testPlanIDParamName] = null;

            // When working on PRIVATE containers, globalRole Admin is ENOUGH
            // because this is how TestLink works when this action is done on GUI
            if($this->user->globalRole->dbID != TL_ROLES_ADMIN) {
                $status_ok = $this->userHasRight( "mgt_testplan_create", self::CHECK_PUBLIC_PRIVATE_ATTR );
            }
        }

        if($status_ok) {
            $name = trim( $this->args[self::$testPlanNameParamName] );
            $info = $this->tplanMgr->get_by_name( $name, $tprojectInfo['id'] );
            $status_ok = is_null( $info );

            if(!($status_ok = is_null( $info ))) {
                $msg = $msg_prefix . sprintf( TESTPLANNAME_ALREADY_EXISTS_STR, $name, $tprojectInfo['name'] );
                $this->errors[] = new IXR_Error( TESTPLANNAME_ALREADY_EXISTS, $msg );
            }
        }

        if($status_ok) {
            $keys2check = array(
                    self::$activeParamName => 1,
                    self::$publicParamName => 1,
                    self::$noteParamName => ''
            );
            foreach( $keys2check as $key => $value ) {
                $optional[$key] = $this->_isParamPresent( $key ) ? trim( $this->args[$key] ) : $value;
            }
            $retval = $this->tplanMgr->create( htmlspecialchars( $name ), htmlspecialchars( $optional[self::$noteParamName] ), $tprojectInfo['id'], $optional[self::$activeParamName], $optional[self::$publicParamName] );

            $resultInfo = array();
            $resultInfo[] = array(
                    "operation" => __FUNCTION__,
                    "additionalInfo" => null,
                    "status" => true,
                    "id" => $retval,
                    "message" => GENERAL_SUCCESS_STR
            );
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * Gets full path from the given node till the top using nodes_hierarchy_table
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param mixed $args["nodeID"]
     *            can be just a single node or an array of INTERNAL(DB) ID
     * @return mixed $resultInfo
     * @access public
     *
     * @internal revision
     *           BUGID 3993
     *           $args["nodeID"] can be just a single node or an array
     *           when path can not be found same date structure will be returned, that on situations
     *           where all is ok, but content for KEY(nodeID) will be NULL instead of rising ERROR
     *
     */
    public function getFullPath($args) {
        $this->_setArgs( $args );
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $checkFunctions = array(
                'authenticate'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->_isParamPresent( self::$nodeIDParamName, $msg_prefix, self::SET_ERROR );

        if($status_ok) {
            $nodeIDSet = $this->args[self::$nodeIDParamName];

            // if is array => OK
            if(!($workOnSet = is_array( $nodeIDSet )) &&(! is_int( $nodeIDSet ) || $nodeIDSet <= 0)) {
                $msg = $msg_prefix . sprintf( NODEID_INVALID_DATA_TYPE );
                $this->errors[] = new IXR_Error( NODEID_INVALID_DATA_TYPE, $msg );
                $status_ok = false;
            }

            if($status_ok && $workOnSet) {
                // do check on each item on set
                foreach( $nodeIDSet as $itemID ) {
                    if(! is_int( $itemID ) || $itemID <= 0) {
                        $msg = $msg_prefix . sprintf( NODEID_IS_NOT_INTEGER_STR, $itemID );
                        $this->errors[] = new IXR_Error( NODEID_IS_NOT_INTEGER, $msg );
                        $status_ok = false;
                    }
                }
            }
        }

        if($status_ok) {
            // IMPORTANT NOTICE:
            //(may be a design problem but ..)
            // If $nodeIDSet is an array and for one of items path can not be found
            // get_full_path_verbose() returns null, no matter if for other items
            // information is available
            //
            $full_path = $this->tprojectMgr->tree_manager->get_full_path_verbose( $nodeIDSet );
        }
        return $status_ok ? $full_path : $this->errors;
    }

    /**
     */
    protected function _insertCustomFieldExecValues($executionID) {
        // // Check for existence of executionID
        $status_ok = true;
        $sql = "SELECT id FROM {$this->tables['executions']} WHERE id={$executionID}";
        $rs = $this->dbObj->fetchRowsIntoMap( $sql, 'id' );
        //
        $cfieldSet = $this->args[self::$customFieldsParamName];
        $tprojectID = $this->tcaseMgr->get_testproject( $this->args[self::$testCaseIDParamName] );
        $tplanID = $this->args[self::$testPlanIDParamName];
        $cfieldMgr = $this->tprojectMgr->cfield_mgr;
        $cfieldsMap = $cfieldMgr->get_linked_cfields_at_execution( $tprojectID, 1, 'testcase', null, null, null, 'name' );
        $status_ok = !(is_null( $rs ) || is_null( $cfieldSet ) || count( $cfieldSet ) == 0);
        $cfield4write = null;
        if($status_ok && ! is_null( $cfieldsMap )) {
            foreach( $cfieldSet as $name => $value ) {
                if(isset( $cfieldsMap[$name] )) {
                    $cfield4write[$cfieldsMap[$name]['id']] = array(
                            "type_id" => $cfieldsMap[$name]['type'],
                            "cf_value" => $value
                    );
                }
            }
            if(! is_null( $cfield4write )) {
                $cfieldMgr->execution_values_to_db( $cfield4write, $this->tcVersionID, $executionID, $tplanID, null, 'write-through' );
            }
        }
        return $status_ok;
    }

    /**
     * delete an execution
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["executionid"]
     *
     * @return mixed $resultInfo
     *        [status] => true/false of success
     *        [id] => result id or error code
     *        [message] => optional message for error message string
     * @access public
     */
    public function deleteExecution($args) {
        $resultInfo = array();
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $resultInfo[0]["status"] = false;

        $checkFunctions = array(
                'authenticate',
                'checkExecutionID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        // missing :
        // we need to get Context => Test plan & Test project to understand if
        // user has the right to do this operation
        if($status_ok) {
            if($this->userHasRight( "exec_delete", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
                $this->tcaseMgr->deleteExecution( $args[self::$executionIDParamName] );
                $resultInfo[0]["status"] = true;
                $resultInfo[0]["id"] = $args[self::$executionIDParamName];
                $resultInfo[0]["message"] = GENERAL_SUCCESS_STR;
                $resultInfo[0]["operation"] = $operation;
            } else {
                $status_ok = false;
                $this->errors[] = new IXR_Error( CFG_DELETE_EXEC_DISABLED, CFG_DELETE_EXEC_DISABLED_STR );
            }
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * Helper method to see if an execution id exists on DB
     * no checks regarding other data like test case , test plam, build, etc are done
     *
     *
     *
     * @return boolean
     * @access protected
     */
    protected function checkExecutionID($messagePrefix = '', $setError = false) {
        $pname = self::$executionIDParamName;
        $status_ok = $this->_isParamPresent( $pname, $messagePrefix, $setError );
        if(! $status_ok) {
            $msg = $messagePrefix . sprintf( MISSING_REQUIRED_PARAMETER_STR, $pname );
            $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
        } else {
            $status_ok = is_int( $this->args[$pname] ) && $this->args[$pname] > 0;
            if(! $status_ok) {
                $msg = $messagePrefix . sprintf( PARAMETER_NOT_INT_STR, $pname, $this->args[$pname] );
                $this->errors[] = new IXR_Error( PARAMETER_NOT_INT, $msg );
            } else {
            }
        }
        return $status_ok;
    }

    /**
     * Helper method to see if the platform identity provided is valid
     * This is the only method that should be called directly to check platform identity
     *
     * If everything OK, platform id is setted.
     *
     * @param int $tplanID
     *            Test Plan ID
     * @param map $platformInfo
     *            key: platform ID
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     *
     * @return boolean
     * @access protected
     */
    protected function checkPlatformIdentity($tplanID, $platformInfo = null, $messagePrefix = '') {
        $status = true;
        $platformID = 0;

        $name_exists = $this->_isParamPresent( self::$platformNameParamName, $messagePrefix );
        $id_exists = $this->_isParamPresent( self::$platformIDParamName, $messagePrefix );
        $status = $name_exists | $id_exists;

        if(! $status) {
            $pname = self::$platformNameParamName . ' OR ' . self::$platformIDParamName;
            $msg = $messagePrefix . sprintf( MISSING_REQUIRED_PARAMETER_STR, $pname );
            $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
        }

        if($status) {
            // get test plan name is useful for error messages
            $tplanInfo = $this->tplanMgr->get_by_id( $tplanID );
            if(is_null( $platformInfo )) {
                $platformInfo = $this->tplanMgr->getPlatforms( $tplanID, array(
                        'outputFormat' => 'map'
                ) );
            }

            if(is_null( $platformInfo )) {
                $status = false;
                $msg = sprintf( $messagePrefix . TESTPLAN_HAS_NO_PLATFORMS_STR, $tplanInfo['name'] );
                $this->errors[] = new IXR_Error( TESTPLAN_HAS_NO_PLATFORMS, $msg );
            }
        }

        if($status) {
            $platform_name = null;
            $platform_id = null;
            if($name_exists) {
                $this->errors[] = $platformInfo;
                $platform_name = $this->args[self::$platformNameParamName];
                $status = in_array( $this->args[self::$platformNameParamName], $platformInfo );
            } else {
                $platform_id = $this->args[self::$platformIDParamName];
                $status = isset( $platformInfo[$this->args[self::$platformIDParamName]] );
            }

            if(! $status) {
                // Platform does not exist in target testplan
                // Can I Try to understand if platform exists on test project ?
                $msg = sprintf( $messagePrefix . PLATFORM_NOT_LINKED_TO_TESTPLAN_STR, $platform_name, $platform_id, $tplanInfo['name'] );
                $this->errors[] = new IXR_Error( PLATFORM_NOT_LINKED_TO_TESTPLAN, $msg );
            }
        }

        if($status) {
            if($name_exists) {
                $dummy = array_flip( $platformInfo );
                $this->args[self::$platformIDParamName] = $dummy[$this->args[self::$platformNameParamName]];
            }
        }
        return $status;
    }

    /**
     * update result of LASTE execution
     *
     * @param
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @param int $args["platformid"]
     * @param int $args["buildid"]
     * @param int $args["testcaseid"]
     *            internal ID
     * @param string $args["status"]
     * @param string $args["notes"]
     *
     * @return mixed $resultInfo
     *
     * @access protected
     */
    protected function _updateResult($user_id = null, $exec_ts = null) {
        $tester_id = is_null( $user_id ) ? $this->userID : $user_id;
        $execTimeStamp = is_null( $exec_ts ) ? $this->dbObj->db_now() : $exec_ts;

        $exec_id = 0;
        $status = $this->args[self::$statusParamName];

        // $platform_id = 0; // hmm here I think we have an issue
        // $testplan_id = $this->args[self::$testPlanIDParamName];
        // $build_id = $this->args[self::$buildIDParamName];

        $tcversion_id = $this->tcVersionID;
        $tcase_id = $this->args[self::$testCaseIDParamName];

        $execContext = array(
                'tplan_id' => $this->args[self::$testPlanIDParamName],
                'platform_id' => $this->args[self::$platformIDParamName],
                'build_id' => $this->args[self::$buildIDParamName]
        );

        // $db_now=$this->dbObj->db_now();

        if(isset( $this->args[self::$platformIDParamName] )) {
            $platform_id = $this->args[self::$platformIDParamName];
        }

        // Here steps and expected results are not needed => do not request => less data on network
        // $options = array('getSteps' => 0);
        $opt = array(
                'output' => 'exec_id'
        );
        $exec_id = $this->tcaseMgr->getLatestExecSingleContext( array(
                'id' => $tcase_id,
                'version_id' => null
        ), $execContext, $opt );
        if(! is_null( $exec_id )) {
            $execution_type = constant( "TESTCASE_EXECUTION_TYPE_AUTO" );
            $notes = '';
            $notes_update = '';

            if($this->_isNotePresent()) {
                $notes = $this->dbObj->prepare_string( $this->args[self::$noteParamName] );
            }

            if(trim( $notes ) != "") {
                $notes_update = ",notes='{$notes}'";
            }

            $duration_update = '';
            if(isset( $this->args[self::$executionDurationParamName] )) {
                $duration_update = ",execution_duration=" . floatval( $this->args[self::$executionDurationParamName] );
            }

            $sql = " UPDATE {$this->tables['executions']} " . " SET tester_id={$tester_id}, execution_ts={$execTimeStamp}," . " status='{$status}', execution_type= {$execution_type} " . " {$notes_update} {$duration_update} WHERE id = {$exec_id}";

            $this->dbObj->exec_query( $sql );
        }
        return $exec_id;
    }

    /**
     * Return a TestSuite by ID
     *
     * @param
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testsuiteid"]
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestSuiteByID($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $status_ok = $this->_runChecks( array(
                'authenticate',
                'checkTestSuiteID'
        ), $msg_prefix );

        $details = 'simple';
        $key2search = self::$detailsParamName;
        if($this->_isParamPresent( $key2search )) {
            $details = $this->args[$key2search];
        }

        if($status_ok && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
            $testSuiteID = $this->args[self::$testSuiteIDParamName];
            $tsuiteMgr = new testsuite( $this->dbObj );
            return $tsuiteMgr->get_by_id( $testSuiteID );
        } else {
            return $this->errors;
        }
    }

    /**
     * get list of TestSuites which are DIRECT children of a given TestSuite
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testsuiteid"]
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestSuitesForTestSuite($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $items = null;

        $this->_setArgs( $args );
        $status_ok = $this->_runChecks( array(
                'authenticate',
                'checkTestSuiteID'
        ), $msg_prefix ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );
        if($status_ok) {
            $testSuiteID = $this->args[self::$testSuiteIDParamName];
            $tsuiteMgr = new testsuite( $this->dbObj );
            $items = $tsuiteMgr->get_children( $testSuiteID );
        }
        return $status_ok ? $items : $this->errors;
    }

    /**
     * Returns the list of platforms associated to a given test plan
     *
     * @param
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestPlanPlatforms($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $this->_setArgs( $args );
        $status_ok = false;
        $items = null;

        // Checks if a test plan id was provided
        $status_ok = $this->_isParamPresent( self::$testPlanIDParamName, $msg_prefix, self::SET_ERROR );

        if($status_ok) {
            // Checks if the provided test plan id is valid
            $status_ok = $this->_runChecks( array(
                    'authenticate',
                    'checkTestPlanID'
            ), $msg_prefix );
        }
        if($status_ok) {
            $tplanID = $this->args[self::$testPlanIDParamName];
            // get test plan name is useful for error messages
            $tplanInfo = $this->tplanMgr->get_by_id( $tplanID );
            $items = $this->tplanMgr->getPlatforms( $tplanID );
            if(!($status_ok = ! is_null( $items ))) {
                $msg = sprintf( $messagePrefix . TESTPLAN_HAS_NO_PLATFORMS_STR, $tplanInfo['name'] );
                $this->errors[] = new IXR_Error( TESTPLAN_HAS_NO_PLATFORMS, $msg );
            }
        }
        return $status_ok ? $items : $this->errors;
    }

    /**
     * Gets the summarized results grouped by platform.
     *
     * @see
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     *            test plan id
     *
     * @return map where every element has:
     *
     *         'type' => 'platform'
     *         'total_tc => ZZ
     *         'details' => array( 'passed' => array( 'qty' => X)
     *         'failed' => array( 'qty' => Y)
     *         'blocked' => array( 'qty' => U)
     *         ....)
     *
     * @access public
     */
    public function getTotalsForTestPlan($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $total = null;

        $this->_setArgs( $args );
        $status_ok = true;

        // Checks are done in order
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        if($status_ok) {
            // $total = $this->tplanMgr->getStatusTotalsByPlatform($this->args[self::$testPlanIDParamName]);
            $total = $this->tplanMetricsMgr->getExecCountersByPlatformExecStatus( $this->args[self::$testPlanIDParamName] );
        }

        return $status_ok ? $total : $this->errors;
    }

    /**
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["user"]
     *            user name
     *
     * @return true if everything OK, otherwise error structure
     *
     * @access public
     */
    public function doesUserExist($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $this->_setArgs( $args );

        $user_id = tlUser::doesUserExist( $this->dbObj, $this->args[self::$userParamName] );
        if(!($status_ok = ! is_null( $user_id ))) {
            $msg = $msg_prefix . sprintf( NO_USER_BY_THIS_LOGIN_STR, $this->args[self::$userParamName] );
            $this->errors[] = new IXR_Error( NO_USER_BY_THIS_LOGIN, $msg );
        }
        return $status_ok ? $status_ok : $this->errors;
    }

    /**
     * check if Developer Key exists.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *
     * @return true if everything OK, otherwise error structure
     *
     * @access public
     */
    public function checkDevKey($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );
        return $status_ok ? $status_ok : $this->errors;
    }

    /**
     * Uploads an attachment for a Requirement Specification.
     *
     * The attachment content must be Base64 encoded by the client before sending it.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *            Developer key
     * @param int $args["reqspecid"]
     *            The Requirement Specification ID
     * @param string $args["title"]
     *           (Optional) The title of the Attachment
     * @param string $args["description"]
     *           (Optional) The description of the Attachment
     * @param string $args["filename"]
     *            The file name of the Attachment(e.g.:notes.txt)
     * @param string $args["filetype"]
     *            The file type of the Attachment(e.g.: text/plain)
     * @param string $args["content"]
     *            The content(Base64 encoded) of the Attachment
     *
     * @since 1.9beta6
     * @return mixed $resultInfo an array containing the fk_id, fk_table, title,
     *         description, file_name, file_size and file_type. If any errors occur it
     *         returns the error map.
     */
    public function uploadRequirementSpecificationAttachment($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $args[self::$foreignKeyTableNameParamName] = 'req_specs';
        $args[self::$foreignKeyIdParamName] = $args['reqspecid'];
        $this->_setArgs( $args );
        return $this->uploadAttachment( $args, $msg_prefix, false );
    }

    /**
     * Uploads an attachment for a Requirement.
     *
     * The attachment content must be Base64 encoded by the client before sending it.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *            Developer key
     * @param int $args["requirementid"]
     *            The Requirement ID
     * @param string $args["title"]
     *           (Optional) The title of the Attachment
     * @param string $args["description"]
     *           (Optional) The description of the Attachment
     * @param string $args["filename"]
     *            The file name of the Attachment(e.g.:notes.txt)
     * @param string $args["filetype"]
     *            The file type of the Attachment(e.g.: text/plain)
     * @param string $args["content"]
     *            The content(Base64 encoded) of the Attachment
     *
     * @since 1.9beta6
     * @return mixed $resultInfo an array containing the fk_id, fk_table, title,
     *         description, file_name, file_size and file_type. If any errors occur it
     *         returns the erros map.
     */
    public function uploadRequirementAttachment($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $args[self::$foreignKeyTableNameParamName] = 'requirements';
        $args[self::$foreignKeyIdParamName] = $args['requirementid'];
        $this->_setArgs( $args );
        return $this->uploadAttachment( $args, $msg_prefix, false );
    }

    /**
     * Uploads an attachment for a Test Project.
     *
     * The attachment content must be Base64 encoded by the client before sending it.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *            Developer key
     * @param int $args["testprojectid"]
     *            The Test Project ID
     * @param string $args["title"]
     *           (Optional) The title of the Attachment
     * @param string $args["description"]
     *           (Optional) The description of the Attachment
     * @param string $args["filename"]
     *            The file name of the Attachment(e.g.:notes.txt)
     * @param string $args["filetype"]
     *            The file type of the Attachment(e.g.: text/plain)
     * @param string $args["content"]
     *            The content(Base64 encoded) of the Attachment
     *
     * @since 1.9beta6
     * @return mixed $resultInfo an array containing the fk_id, fk_table, title,
     *         description, file_name, file_size and file_type. If any errors occur it
     *         returns the erros map.
     */
    public function uploadTestProjectAttachment($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $ret = null;

        $args[self::$foreignKeyTableNameParamName] = 'nodes_hierarchy';
        $args[self::$foreignKeyIdParamName] = $args[self::$testProjectIDParamName];
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID'
        );
        $statusOK = $this->_runChecks( $checkFunctions ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );
        $ret = $statusOK ? $this->uploadAttachment( $args, $msg_prefix, false ) : $this->errors;
        return $ret;
    }

    /**
     * Uploads an attachment for a Test Suite.
     *
     * The attachment content must be Base64 encoded by the client before sending it.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *            Developer key
     * @param int $args["testsuiteid"]
     *            The Test Suite ID
     * @param string $args["title"]
     *           (Optional) The title of the Attachment
     * @param string $args["description"]
     *           (Optional) The description of the Attachment
     * @param string $args["filename"]
     *            The file name of the Attachment(e.g.:notes.txt)
     * @param string $args["filetype"]
     *            The file type of the Attachment(e.g.: text/plain)
     * @param string $args["content"]
     *            The content(Base64 encoded) of the Attachment
     *
     * @since 1.9beta6
     * @return mixed $resultInfo an array containing the fk_id, fk_table, title,
     *         description, file_name, file_size and file_type. If any errors occur it
     *         returns the erros map.
     */
    public function uploadTestSuiteAttachment($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $args[self::$foreignKeyTableNameParamName] = 'nodes_hierarchy';
        $args[self::$foreignKeyIdParamName] = $args[self::$testSuiteIDParamName];
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestSuiteID'
        );
        $statusOK = $this->_runChecks( $checkFunctions ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );
        $ret = $statusOK ? $this->uploadAttachment( $args, $msg_prefix, false ) : $this->errors;
        return $ret;
    }

    /**
     * Uploads an attachment for a Test Case.
     *
     * The attachment content must be Base64 encoded by the client before sending it.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *            Developer key
     * @param int $args["testcaseid"]
     *            Test Case INTERNAL ID
     * @param int $args["version"]
     *            version number
     *
     * @param string $args["title"]
     *           (Optional) The title of the Attachment
     * @param string $args["description"]
     *           (Optional) The description of the Attachment
     * @param string $args["filename"]
     *            The file name of the Attachment(e.g.:notes.txt)
     * @param string $args["filetype"]
     *            The file type of the Attachment(e.g.: text/plain)
     * @param string $args["content"]
     *            The content(Base64 encoded) of the Attachment
     *
     * @return mixed $resultInfo an array containing the fk_id, fk_table, title,
     *         description, file_name, file_size and file_type. If any errors occur it
     *         returns the erros map.
     */
    public function uploadTestCaseAttachment($args) {
        $ret = null;
        $msg_prefix = "(" . __FUNCTION__ . ") - ";

        $this->_setArgs( $args );

        // Mandatory Parameters
        $prm = self::$versionNumberParamName;
        $statusOK = $this->_isParamPresent( $prm, $msgPrefix, self::SET_ERROR );

        if($statusOK) {
          $checkFunctions = array('authenticate',
                                  'checkTestCaseIdentity',
                                  'checkTestCaseVersionNumberAncestry');          
        }

        if($statusOK = $this->_runChecks( $checkFunctions, $msg_prefix )) {

          $args[self::$foreignKeyTableNameParamName] = 'tcversions';
          $args[self::$foreignKeyIdParamName] = $this->tcVersionID;
          $this->_setArgs( $args );
          // Need to get test project information 
          // from test case in order to be able
          // to do RIGHTS check on $this->userHasRight()
          // !!! Important Notice!!!!:
          // method checkTestCaseIdentity sets $this->args[self::$testCaseIDParamName]

          $this->args[self::$testProjectIDParamName] = 
            $this->tcaseMgr->getTestProjectFromTestCase( $this->args[self::$testCaseIDParamName] );

            $statusOK = $this->userHasRight( "mgt_modify_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );
        }

        $ret = $statusOK ? $this->uploadAttachment( $args, $msg_prefix, false ) : $this->errors;
        return $ret;
    }

    /**
     * Uploads an attachment for an execution.
     *
     * The attachment content must be Base64 encoded by the client before sending it.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *            Developer key
     * @param int $args["executionid"]
     *            execution ID
     * @param string $args["title"]
     *           (Optional) The title of the Attachment
     * @param string $args["description"]
     *           (Optional) The description of the Attachment
     * @param string $args["filename"]
     *            The file name of the Attachment(e.g.:notes.txt)
     * @param string $args["filetype"]
     *            The file type of the Attachment(e.g.: text/plain)
     * @param string $args["content"]
     *            The content(Base64 encoded) of the Attachment
     *
     * @since 1.9beta6
     * @return mixed $resultInfo an array containing the fk_id, fk_table, title,
     *         description, file_name, file_size and file_type. If any errors occur it
     *         returns the erros map.
     */
    public function uploadExecutionAttachment($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $args[self::$foreignKeyTableNameParamName] = 'executions';
        $args[self::$foreignKeyIdParamName] = $args['executionid'];
        $this->_setArgs( $args );

        // We need to check that user has right to execute in order to allow
        // him/her to do attachment

        return $this->uploadAttachment( $args, $msg_prefix, false );
    }

    /**
     * Uploads an attachment for specified table.
     * You must specify the table that
     * the attachment is connected(nodes_hierarchy, builds, etc) and the foreign
     * key id in this table.
     *
     * The attachment content must be Base64 encoded by the client before sending it.
     *
     * @param struct $args
     * @param string $args["devKey"]
     *            Developer key
     * @param int $args["fkid"]
     *            The Attachment Foreign Key ID
     * @param string $args["fktable"]
     *            The Attachment Foreign Key Table
     * @param string $args["title"]
     *           (Optional) The title of the Attachment
     * @param string $args["description"]
     *           (Optional) The description of the Attachment
     * @param string $args["filename"]
     *            The file name of the Attachment(e.g.:notes.txt)
     * @param string $args["filetype"]
     *            The file type of the Attachment(e.g.: text/plain)
     * @param string $args["content"]
     *            The content(Base64 encoded) of the Attachment
     *
     * @return mixed $resultInfo an array containing 
     *         the fk_id, fk_table, title,
     *         description, file_name, file_size and file_type. 
     *         If any errors occur it
     *         returns the errors map.
     */
    public function uploadAttachment($args, $messagePrefix = '', $setArgs = true) {
        $resultInfo = array();
        if($setArgs) {
          $this->_setArgs( $args );
        }
        $msg_prefix =($messagePrefix == '') ?("(" . __FUNCTION__ . ") - ") : $messagePrefix;

        $checkFunctions = array();

        // TODO: please, somebody review if this is valid. 
        // I added this property
        // to avoid the upload method of double authenticating the user.
        // Otherwise, when uploadTestCaseAttachment was called, 
        // for instante, it
        // would authenticate, check if the nodes_hierarchy is type TestCase
        // and then call uploadAttachment that would, authenticate again.
        // What do you think?
        if(!$this->authenticated) {
          $checkFunctions[] = 'authenticate';
        }

        // check if :
        // TL has attachments enabled
        // provided FK is valid
        // attachment info is ok
        $checkFunctions[] = 'isAttachmentEnabled';
        $checkFunctions[] = 'checkForeignKey';
        $checkFunctions[] = 'checkUploadAttachmentRequest';

        $statusOK = $this->_runChecks( $checkFunctions, $msg_prefix );
        if($statusOK) {

          $fkId = $this->args[self::$foreignKeyIdParamName];
          $fkTable = $this->args[self::$foreignKeyTableNameParamName];
          $title = $this->args[self::$titleParamName];

          // creates a temp file and returns an array with size and tmp_name
          $fInfo = $this->createAttachmentTempFile();
          if(!$fInfo) {
            // Error creating attachment temp file. Ask user to check temp dir
            // settings in php.ini and security and rights of this dir.
            $msg = $msg_prefix . ATTACH_TEMP_FILE_CREATION_ERROR_STR;
            $this->errors[] = new IXR_ERROR( ATTACH_TEMP_FILE_CREATION_ERROR, $msg );
            $statusOK = false;
          } else {
            // The values have already been validated in the method
            // checkUploadAttachmentRequest()
            $fInfo['name'] = $args[self::$fileNameParamName];
            $fInfo['type'] = $args[self::$fileTypeParamName];
            if( trim($fInfo['type']) == '' ) {
              $fInfo['type'] = mime_content_type($fInfo['tmp_name']);
            }

            $docRepo = tlAttachmentRepository::create( $this->dbObj );
            $uploadOp = $docRepo->insertAttachment( $fkId, $fkTable, $title, $fInfo );
            

            if($uploaOp->statusOK == false) {
              $msg = $msg_prefix . ATTACH_DB_WRITE_ERROR_STR;
              $this->errors[] = new IXR_ERROR( ATTACH_DB_WRITE_ERROR, $msg );
              $statusOK = false;
            } else {
              // We are returning some data that the user originally sent.
              // Perhaps we could return only new data, like the file size?
              $resultInfo['fk_id'] = $args[self::$foreignKeyIdParamName];
              $resultInfo['fk_table'] = $args[self::$foreignKeyTableNameParamName];
              $resultInfo['title'] = $args[self::$titleParamName];
              $resultInfo['description'] = $args[self::$descriptionParamName];
              $resultInfo['file_name'] = $args[self::$fileNameParamName];

              // It would be nice have all info available in db
              // $resultInfo['file_path'] = $args[""];
              // we could also return the tmp_name, but would it be useful?
              $resultInfo['file_size'] = $fInfo['size'];
              $resultInfo['file_type'] = $args[self::$fileTypeParamName];
            }
          }
        }

        return $statusOK ? $resultInfo : $this->errors;
    }

    /**
     * <p>Checks if the attachments feature is enabled in TestLink
     * configuration.</p>
     *
     * @since 1.9beta6
     * @return boolean true if attachments feature is enabled in TestLink
     *         configuration, false otherwise.
     */
    protected function isAttachmentEnabled($msg_prefix = '') {
        $status_ok = true;
        if(! config_get( "attachments" )->enabled) {
            $msg = $msg_prefix . ATTACH_FEATURE_DISABLED_STR;
            $this->errors[] = new IXR_ERROR( ATTACH_FEATURE_DISABLED, $msg );
            $status_ok = false;
        }
        return $status_ok;
    }

    /**
     * <p>Checks if the given foreign key is valid.
     * What this method basically does
     * is query the database looking for the foreign key id in the foreign key
     * table.</p>
     *
     * @since 1.9beta6
     * @return boolean true if the given foreign key exists, false otherwise.
     */
    protected function checkForeignKey($msg_prefix = '') {
        $statusOK = true;

        
        $fkId = $this->args[self::$foreignKeyIdParamName];
        $fkTable = $this->args[self::$foreignKeyTableNameParamName];
        
        if(isset( $fkId ) && isset( $fkTable )) {
            $query = "SELECT id FROM {$this->tables[$fkTable]} 
                      WHERE id={$fkId}";

            $result = $this->dbObj->fetchFirstRowSingleColumn( $query, "id" );
        }

        if(null == $result) {
            $msg = $msg_prefix . sprintf( ATTACH_INVALID_FK_STR, $fkId, $fkTable );
            $this->errors[] = new IXR_ERROR( ATTACH_INVALID_FK, $msg );
            $statusOK = false;
        }

        return $statusOK;
    }

    /**
     * <p>Checks if the attachment parameters are valid.
     * It checks if the
     * <b>file_name</b> parameter is set, if the <b>content</b> is set and if
     * the <b>file type</b> is set. If the <b>file type</b> is not set, then it uses
     * <b>application/octet-stream</b>.
     * This default content type refers to <i>binary</i> files.</p>
     *
     * @since 1.9beta6
     * @return boolean true if the file name and the content are set
     */
    protected function checkUploadAttachmentRequest($msg_prefix = '') {
        // Did the client set file name?
        $status = isset( $this->args[self::$fileNameParamName] );
        if($status) {
            // Did the client set file content?
            $status = isset( $this->args[self::$contentParamName] );
            if($status) {
                // Did the client set the file type? If not so use binary as default file type
                if(isset( $this->args[self::$fileTypeParamName] )) {
                    // By default, if no file type is provided, put it as binary
                    $this->args[self::$fileTypeParamName] = "application/octet-stream";
                }
            }
        }

        if(! $status) {
            $msg = $msg_prefix . sprintf( ATTACH_INVALID_ATTACHMENT_STR, $this->args[self::$fileNameParamName], sizeof( $this->args[self::$contentParamName] ) );
            $this->errors[] = new IXR_ERROR( ATTACH_INVALID_ATTACHMENT, $msg );
        }

        return $status;
    }

    /**
     * <p>Creates a temporary file and writes the attachment content into this file.</p>
     *
     * <p>Before writing to the file it <b>Base64 decodes</b> the file content.</p>
     *
     * @since 1.9beta6
     * @return file handler
     */
    protected function createAttachmentTempFile() {
        $resultInfo = array();
        $filename = tempnam( sys_get_temp_dir(), 'tl-' );

        $resultInfo["tmp_name"] = $filename;
        $handle = fopen( $filename, "w" );
        fwrite( $handle, base64_decode( $this->args[self::$contentParamName] ) );
        fclose( $handle );

        $filesize = filesize( $filename );
        $resultInfo["size"] = $filesize;

        return $resultInfo;
    }

    /**
     * checks if a test case version number is defined for a test case
     * if everything is ok $this->tcVersionID will be setted
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return map with following keys
     *         boolean map['status_ok']
     *         string map['error_msg']
     *         int map['error_code']
     */
    protected function checkTestCaseVersionNumberAncestry($messagePrefix = '') {
        $ret = array('status_ok' => true,
                     'error_msg' => '',
                     'error_code' => 0 );

        $tcase_id = $this->args[self::$testCaseIDParamName];
        $version_number = $this->args[self::$versionNumberParamName];

        $sql = " SELECT TCV.version,TCV.id " . " FROM {$this->tables['nodes_hierarchy']} NH, {$this->tables['tcversions']} TCV " . " WHERE NH.parent_id = {$tcase_id} " . " AND TCV.version = {$version_number} " . " AND TCV.id = NH.id ";

        $target_tcversion = $this->dbObj->fetchRowsIntoMap( $sql, 'version' );
        if(! is_null( $target_tcversion ) && count( $target_tcversion ) == 1) {
            $dummy = current( $target_tcversion );
            $this->tcVersionID = $dummy['id'];
        } else {
            $status_ok = false;
            $tcase_info = $this->tcaseMgr->tree_manager->get_node_hierarchy_info( $tcase_id );
            $msg = sprintf( TCASE_VERSION_NUMBER_KO_STR, $version_number, $this->args[self::$testCaseExternalIDParamName], $tcase_info['name'] );
            $ret = array('status_ok' => false,
                         'error_msg' => $msg,
                         'error_code' => TCASE_VERSION_NUMBER_KO);
        }
        return $ret;
    }

    /**
     * Helper method to see if the a provided custom field is not empty.
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkCustomField($messagePrefix = '') {
        return(isset( $this->args[self::$customFieldNameParamName] ) ? true : false);
    }

    /**
     * Helper method to see if the a provided scope is valid.
     * Valids scopes are
     * design, execution and testplan_design
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkCustomFieldScope($messagePrefix = '') {
        $status = false;
        $domain = array(
                'design' => true,
                'execution' => true,
                'testplan_design' => true
        );
        $scope = $this->args[self::$scopeParamName];

        $status = is_null( $scope ) ? false : isset( $domain[$scope] );
        return $status;
    }

    /**
     * Gets value of a Custom Field for a entity in a given scope(e.g.: a custom
     * field for a test case in design scope).
     *
     * BUGID-4188: feature request - new method - getTestSuiteCustomFieldValue
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["customfieldname"]:
     *            custom field name
     * @param int $args["testprojectid"]:
     *            project id
     * @param string $args["nodetype"]:
     *            note type(testcase, testsuite, ...)
     * @param int $args["nodeid"]:
     *            node id(test case version id, project id, ...)
     * @param string $args["scope"]:
     *            cf scope(execution, design or testplan_design)
     * @param int $args["executionid"]:
     *            execution id
     * @param int $args["testplanid"]:
     *            test plan id
     * @param int $args["linkid"]:
     *            link id for nodes linked at test plan design scope
     *
     * @return mixed $resultInfo
     *
     * @access protected
     */
    protected function getCustomFieldValue($args, $msg_prefix = '') {
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID',
                'checkCustomField',
                'checkCustomFieldScope'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        $scope = $this->args[self::$scopeParamName];

        switch($scope) {
            case 'execution' :

                // test plan id is valid ?
                if(($status_ok = $this->checkTestPlanID( $msg_prefix ))) {
                    // test plan has to belong to test project
                    $tplanid = intval( $this->args[self::$testPlanIDParamName] );
                    $tprojectid = intval( $this->args[self::$testProjectIDParamName] );

                    $sql = " SELECT id FROM {$this->tables['nodes_hierarchy']} " . " WHERE id = " . $tplanid . " AND parent_id = " . $tprojectid;

                    $rs = $this->dbObj->get_recordset( $sql );
                    $status_ok = ! is_null( $rs );
                    if($status_ok == FALSE) {
                        $project = $this->tprojectMgr->get_by_id( $tprojectid );
                        $plan = $this->tplanMgr->get_by_id( $tplanid );
                        $msg = sprintf( TPLAN_TPROJECT_KO_STR, $plan['name'], $tplanid, $project['name'], $tprojectid );
                        $this->errors[] = new IXR_Error( TPLAN_TPROJECT_KO, $msg_prefix . $msg );
                    }
                }
                break;

            case 'design' :
            default :
                break;
        }

        if($status_ok && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
            $cf_name = $this->args[self::$customFieldNameParamName];
            $tproject_id = $this->args[self::$testProjectIDParamName];
            $nodetype = $this->args[self::$nodeTypeParamName];
            $nodeid = $this->args[self::$nodeIDParamName];
            $executionid = $this->args[self::$executionIDParamName];
            $testplanid = $this->args[self::$testPlanIDParamName];
            $linkid = $this->args[self::$linkIDParamName];

            $enabled = 1; // returning only enabled custom fields

            $cfield_mgr = $this->tprojectMgr->cfield_mgr;
            $cfinfo = $cfield_mgr->get_by_name( $cf_name );
            $cfield = current( $cfinfo );

            switch($scope) {
                case 'design' :
                    $filters = array(
                            'cfield_id' => $cfield['id']
                    );
                    $cfieldSpec = $cfield_mgr->get_linked_cfields_at_design( $tproject_id, $enabled, $filters, $nodetype, $nodeid );
                    break;

                case 'execution' :
                    $cfieldSpec = $cfield_mgr->get_linked_cfields_at_execution( $tproject_id, $enabled, $nodetype, $nodeid, $executionid, $testplanid );
                    break;

                case 'testplan_design' :
                    $cfieldSpec = $cfield_mgr->get_linked_cfields_at_testplan_design( $tproject_id, $enabled, $nodetype, $nodeid, $linkid, $testplanid );
                    break;
            }
            return $cfieldSpec[$cfield['id']];
        } else {
            return $this->errors;
        }
    }

    /**
     * Gets a Custom Field of a Test Case in Execution Scope.
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["customfieldname"]:
     *            custom field name
     * @param int $args["testprojectid"]:
     *            project id
     * @param int $args["executionid"]:
     *            execution id
     * @param int $args["version"]:
     *            test case version NUMBER
     * @param int $args["testplanid"]:
     *            test plan id
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestCaseCustomFieldExecutionValue($args) {
        $msgPrefix = "(" . __FUNCTION__ . ") - ";

        $args[self::$nodeTypeParamName] = 'testcase';
        $args[self::$scopeParamName] = 'execution';

        $this->_setArgs( $args );

        $status_ok = true;
        $p2c = array(
                self::$executionIDParamName,
                self::$versionNumberParamName
        );
        foreach( $p2c as $prm ) {
            $status_ok = $this->_isParamPresent( $prm, $msgPrefix, self::SET_ERROR );
            if($status_ok == FALSE) {
                break;
            }
        }

        // version number is related to execution id
        if($status_ok) {
            $sql = " SELECT id,tcversion_id FROM {$this->tables['executions']} " . " WHERE id = " . intval( $args[self::$executionIDParamName] ) . " AND tcversion_number = " . intval( $args[self::$versionNumberParamName] );

            $rs = $this->dbObj->get_recordset( $sql );

            // return $sql;
            if(is_null( $rs )) {
                $status_ok = false;
                $msg = sprintf( NO_MATCH_STR, self::$versionNumberParamName . '/' . self::$executionIDParamName );
                $this->errors[] = new IXR_Error( NO_MATCH, $msg );
            } else {
                $args[self::$nodeIDParamName] = $rs[0]['tcversion_id'];
            }
        }

        if($status_ok) {
            return $this->getCustomFieldValue( $args );
        }
        return $this->errors;
    }

    /**
     * Gets a Custom Field of a Test Case in Test Plan Design Scope.
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["customfieldname"]:
     *            custom field name
     * @param int $args["testcaseid"]:
     *            project id
     * @param int $args["version"]:
     *            test case version id
     * @param int $args["testplanid"]:
     *            test plan id
     * @param int $args["linkid"]:
     *            link id(important!)
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestCaseCustomFieldTestPlanDesignValue($args) {
        $args[self::$nodeTypeParamName] = 'testcase';
        $args[self::$nodeIDParamName] = $args[self::$versionNumberParamName];
        $args[self::$scopeParamName] = 'testplan_design';

        return $this->getCustomFieldValue( $args );
    }

    /**
     * Gets a Custom Field of a Test Suite in Design Scope.
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["customfieldname"]:
     *            custom field name
     * @param int $args["testprojectid"]:
     *            project id
     * @param int $args["testsuiteid"]:
     *            test suite id
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestSuiteCustomFieldDesignValue($args) {
        $args[self::$nodeTypeParamName] = 'testsuite';
        $args[self::$nodeIDParamName] = $args[self::$testSuiteIDParamName];
        $args[self::$scopeParamName] = 'design';

        return $this->getCustomFieldValue( $args );
    }

    /**
     * Gets a Custom Field of a Test Plan in Design Scope.
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["customfieldname"]:
     *            custom field name
     * @param int $args["testprojectid"]:
     *            project id
     * @param int $args["testplanid"]:
     *            test plan id
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestPlanCustomFieldDesignValue($args) {
        $args[self::$nodeTypeParamName] = 'testplan';
        $args[self::$nodeIDParamName] = $args[self::$testPlanIDParamName];
        $args[self::$scopeParamName] = 'design';

        return $this->getCustomFieldValue( $args );
    }

    /**
     * Gets a Custom Field of a Requirement Specification in Design Scope.
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["customfieldname"]:
     *            custom field name
     * @param int $args["testprojectid"]:
     *            project id
     * @param int $args["reqspecid"]:
     *            requirement specification id
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getReqSpecCustomFieldDesignValue($args) {
        $args[self::$nodeTypeParamName] = 'requirement_spec';
        $args[self::$nodeIDParamName] = $args[self::$reqSpecIDParamName];
        $args[self::$scopeParamName] = 'design';

        return $this->getCustomFieldValue( $args );
    }

    /**
     * Gets a Custom Field of a Requirement in Design Scope.
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["customfieldname"]:
     *            custom field name
     * @param int $args["testprojectid"]:
     *            project id
     * @param int $args["requirementid"]:
     *            requirement id
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getRequirementCustomFieldDesignValue($args) {
        $args['nodetype'] = 'requirement';
        $args['nodeid'] = $args[self::$requirementIDParamName];
        $args['scope'] = 'design';

        return $this->getCustomFieldValue( $args );
    }

    /**
     * createTestCaseSteps - can be used also for upgrade(see action)
     *
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testcaseexternalid"]
     *            optional if you provide $args["testcaseid"]
     * @param string $args["testcaseid"]
     *            optional if you provide $args["testcaseexternalid"]
     * @param string $args["version"]
     *            - optional if not provided LAST ACTIVE version will be used
     *            if all versions are INACTIVE, then latest version will be used.
     * @param string $args["action"]
     *            possible values
     *            'create','update','push'
     *            create: if step exist NOTHING WILL BE DONE
     *            update: if step DOES NOT EXIST will be created
     *            else will be updated.
     *            push: shift down all steps with step number >= step number provided
     *            and use provided data to create step number requested.
     *            NOT IMPLEMENTED YET
     * @param array $args["steps"]:
     *            each element is a hash with following keys
     *            step_number,actions,expected_results,execution_type
     *
     * @return mixed $resultInfo
     *
     * @internal revisions
     *           20111018 - franciscom - TICKET 4774: New methods to manage test case steps
     */
    function createTestCaseSteps($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $resultInfo = array();
        $useLatestVersion = true;
        $version = - 1;
        $item = null;
        $stepSet = null;
        $stepNumbers = null;

        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate',
                'checkTestCaseIdentity'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->userHasRight( "mgt_modify_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        if($status_ok) {
            // Important Notice: method checkTestCaseIdentity sets
            // $this->args[self::$testCaseIDParamName]
            $tcaseID = $this->args[self::$testCaseIDParamName];
            $resultInfo[self::$testCaseIDParamName] = $tcaseID;
            $resultInfo['item'] = null;

            // if parameter version does not exits or is < 0
            // then we will on latest version active or not
            if($this->_isParamPresent( self::$versionNumberParamName )) {
                $version = $this->args[self::$versionNumberParamName];
            }

            $resultInfo['version'] = 'exists';
            if($version > 0) {
                $item = $this->tcaseMgr->get_basic_info( $tcaseID, array(
                        'number' => $version
                ) );
            } else {
                $resultInfo['version'] = 'DOES NOT ' . $resultInfo['version'];
                $item = $this->tcaseMgr->get_last_active_version( $tcaseID );
                if(is_null( $item )) {
                    // get last version no matter if is active
                    $dummy = $this->tcaseMgr->get_last_version_info( $tcaseID );
                    $dummy['tcversion_id'] = $dummy['id'];
                    $item[0] = $dummy;
                }
            }

            if(is_null( $item )) {
                $status_ok = false;
                $msg = sprintf( VERSION_NOT_VALID_STR, $version );
                $this->errors[] = new IXR_Error( VERSION_NOT_VALID, $msg );
            }

            if($status_ok) {
                $item = current( $item );
                $tcversion_id = $item['tcversion_id'];
                $resultInfo['tcversion_id'] = $tcversion_id;

                $step_id = 0;
                $stepSet = null;
                $action = isset( $this->args, self::$actionParamName ) ? $this->args[self::$actionParamName] : 'create';

                //
                // id,step_number,actions,expected_results,active,execution_type
                $opt = array(
                        'accessKey' => 'step_number'
                );
                $stepSet =( array ) $this->tcaseMgr->get_steps( $tcversion_id, 0, $opt );
                $stepNumberIDSet = array_flip( array_keys( $stepSet ) );
                foreach( $stepNumberIDSet as $sn => $dummy ) {
                    $stepNumberIDSet[$sn] = $stepSet[$sn]['id'];
                }

                $resultInfo['stepSet'] = $stepSet;
                $resultInfo['stepNumberIDSet'] = $stepNumberIDSet;

                foreach( $this->args[self::$stepsParamName] as $si ) {
                    $execution_type = isset( $si['execution_type'] ) ? $si['execution_type'] : TESTCASE_EXECUTION_TYPE_MANUAL;
                    $stepExists = isset( $stepSet[$si['step_number']] );
                    if($stepExists) {
                        // needed for update op.
                        $step_id = $stepSet[$si['step_number']]['id'];
                        $resultInfo['stepID'][] = array(
                                $step_id,
                                $si['step_number']
                        );
                    }

                    switch($action) {
                        case 'update' :
                            $op = $stepExists ? $action : 'create';
                            break;

                        case 'push' :
                            $op = $stepExists ? $action : 'skip';
                            break;

                        case 'create' :
                            $op = $stepExists ? 'skip' : $action;
                            break;
                    }
                    $resultInfo['feedback'][] = array(
                            'operation' => $op,
                            'step_number' => $si['step_number']
                    );
                    switch($op) {
                        case 'update' :
                            $this->tcaseMgr->update_step( $step_id, $si['step_number'], $si['actions'], $si['expected_results'], $execution_type );
                            break;

                        case 'create' :
                            $this->tcaseMgr->create_step( $tcversion_id, $si['step_number'], $si['actions'], $si['expected_results'], $execution_type );
                            break;

                        case 'push' :
                            // First action renumber existent steps
                            $renumberedSet = null;
                            foreach( $stepNumberIDSet as $tsn => $dim ) {
                                if($tsn < $si['step_number']) {
                                    unset( $stepNumberIDSet[$tsn] );
                                } else {
                                    $renumberedSet[$dim] = $tsn + 1;
                                }
                            }
                            $this->tcaseMgr->set_step_number( $renumberedSet );
                            $this->tcaseMgr->create_step( $tcversion_id, $si['step_number'], $si['actions'], $si['expected_results'], $execution_type );
                            break;

                        case 'skip' :
                        default :
                            break;
                    }
                }
            }
        }
        return($status_ok ? $resultInfo : $this->errors);
    }

    /**
     * deleteTestCaseSteps
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testcaseexternalid"]
     * @param string $args["version"]
     *            - optional if not provided LAST ACTIVE version will be used
     * @param array $args["steps"]:
     *            each element is a step_number
     *
     * @return mixed $resultInfo
     *
     * @internal revisions
     *           20111018 - franciscom - TICKET 4774: New methods to manage test case steps
     */
    function deleteTestCaseSteps($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $resultInfo = array();
        $version = - 1;
        $item = null;
        $stepSet = null;
        $stepNumberIDSet = null;

        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate',
                'checkTestCaseIdentity'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->userHasRight( "mgt_modify_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        if($status_ok) {
            // Important Notice: method checkTestCaseIdentity sets
            // $this->args[self::$testCaseIDParamName]
            $tcaseID = $this->args[self::$testCaseIDParamName];
            $resultInfo[self::$testCaseIDParamName] = $tcaseID;
            $resultInfo['item'] = null;

            // if parameter version does not exits or is < 0
            // then we will on latest version active or not
            if($this->_isParamPresent( self::$versionNumberParamName )) {
                $version = $this->args[self::$versionNumberParamName];
            }

            $resultInfo['version'] = 'exists';
            if($version > 0) {
                $item = $this->tcaseMgr->get_basic_info( $tcaseID, array(
                        'number' => $version
                ) );
            } else {
                $resultInfo['version'] = 'DOES NOT ' . $resultInfo['version'];
                $item = $this->tcaseMgr->get_last_active_version( $tcaseID );
            }

            if(is_null( $item )) {
                $status_ok = false;
                $msg = sprintf( VERSION_NOT_VALID_STR, $version );
                $this->errors[] = new IXR_Error( VERSION_NOT_VALID, $msg );
            }
            // $resultInfo['item'] = is_null($item) ? $msg : $item;

            if($status_ok) {

                // $resultInfo['steps'] = $this->args[self::$stepsParamName];

                $tcversion_id = $item[0]['tcversion_id'];
                $step_id = 0;
                $stepSet = null;
                //
                // id,step_number,actions,expected_results,active,execution_type
                $opt = array(
                        'accessKey' => 'step_number'
                );
                $stepSet =( array ) $this->tcaseMgr->get_steps( $tcversion_id, 0, $opt );
                $resultInfo['stepSet'] = $stepSet;

                foreach( $this->args[self::$stepsParamName] as $step_number ) {
                    if(isset( $stepSet[$step_number] )) {
                        $this->tcaseMgr->delete_step_by_id( $stepSet[$step_number]['id'] );
                    }
                }
            }
        }
        return($status_ok ? $resultInfo : $this->errors);
    }

    /**
     * Update value of Custom Field with scope='design' for a given Test case
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["testcaseexternalid"]:
     * @param string $args["version"]:
     *            version number
     * @param string $args["testprojectid"]:
     * @param string $args["customfields"]
     *            - optional
     *            contains an map with key:Custom Field Name, value: value for CF.
     *            VERY IMPORTANT: value must be formatted in the way it's written to db,
     *            this is important for types like:
     *
     *            DATE: strtotime()
     *            DATETIME: mktime()
     *            MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore
     *
     *
     *            these custom fields must be configured to be writte during execution.
     *            If custom field do not meet condition value will not be written
     *
     * @return mixed null if everything ok, else array of IXR_Error objects
     *
     * @access public
     */
    public function updateTestCaseCustomFieldDesignValue($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );

        $checkFunc = array(
                'authenticate',
                'checkTestProjectID',
                'checkTestCaseIdentity',
                'checkTestCaseVersionNumber'
        );
        $status_ok = $this->_runChecks( $checkFunc, $msg_prefix );

        if($status_ok) {
            if(! $this->_isParamPresent( self::$customFieldsParamName )) {
                $status_ok = false;
                $msg = sprintf( MISSING_REQUIRED_PARAMETER_STR, self::$customFieldsParamName );
                $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
            }
        }

        if($status_ok) {
            // now check if custom fields are ok
            // For each custom field need to check if:
            // 1. is linked to test project
            // 2. is available for test case at design time
            $cfieldMgr = new cfield_mgr( $this->dbObj );

            // Just ENABLED
            $linkedSet = $cfieldMgr->get_linked_cfields_at_design( $this->args[self::$testProjectIDParamName], cfield_mgr::ENABLED, null, 'testcase', null, 'name' );
            if(is_null( $linkedSet )) {
                $status_ok = false;
                $msg = NO_CUSTOMFIELDS_DT_LINKED_TO_TESTCASES_STR;
                $this->errors[] = new IXR_Error( NO_CUSTOMFIELDS_DT_LINKED_TO_TESTCASES, $msg );
            }
        }

        if($status_ok) {
            $accessVersionBy['number'] = $this->args[self::$versionNumberParamName];
            $nodeInfo = $this->tcaseMgr->get_basic_info( $this->args[self::$testCaseIDParamName], $accessVersionBy );
            $cfSet = $args[self::$customFieldsParamName];
            foreach( $cfSet as $cfName => $cfValue ) {
                // $accessKey = "custom_field_" . $item['id'] . <field_type_id>_<cfield_id>
                // design_values_to_db($hash,$node_id,$cf_map=null,$hash_type=null)
                $item = $linkedSet[$cfName];
                $accessKey = "custom_field_" . $item['type'] . '_' . $item['id'];
                $hash[$accessKey] = $cfValue;
                $cfieldMgr->design_values_to_db( $hash, $nodeInfo[0]['tcversion_id'] );
            }
        } else {
            return $this->errors;
        }
    }

    /**
     * Update execution type for a test case version
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["testcaseexternalid"]:
     * @param string $args["version"]:
     *            version number
     * @param string $args["testprojectid"]:
     * @param string $args["executiontype"]:
     *            TESTCASE_EXECUTION_TYPE_MANUAL,
     *            TESTCASE_EXECUTION_TYPE_AUTOMATIC
     *
     * @return mixed null if everything ok, else array of IXR_Error objects
     *
     * @access public
     */
    public function setTestCaseExecutionType($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID',
                'checkTestCaseIdentity',
                'checkTestCaseVersionNumber'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );
        if($status_ok) {
            if(! $this->_isParamPresent( self::$executionTypeParamName )) {
                $status_ok = false;
                $msg = sprintf( MISSING_REQUIRED_PARAMETER_STR, self::$customFieldsParamName );
                $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
            }
        }

        if($status_ok) {
            // if value not on domain, will use TESTCASE_EXECUTION_TYPE_MANUAL
            $accessVersionBy['number'] = $this->args[self::$versionNumberParamName];
            $nodeInfo = $this->tcaseMgr->get_basic_info( $this->args[self::$testCaseIDParamName], $accessVersionBy );
            $dbg = $this->tcaseMgr->setExecutionType( $nodeInfo[0]['tcversion_id'], $this->args[self::$executionTypeParamName] );
            return array(
                    $this->args,
                    $dbg
            );
        } else {
            return $this->errors;
        }
    }

    /**
     */
    public function getExecCountersByBuild($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $total = null;

        $this->_setArgs( $args );
        $status_ok = true;

        // Checks are done in order
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $metrics = $this->tplanMetricsMgr->getExecCountersByBuildExecStatus( $this->args[self::$testPlanIDParamName] );
        }

        if(! is_null( $metrics )) {
            // transform in somethin similar to a simple table
            $out = array();
            foreach( $metrics['with_tester'] as $build_id => &$elem ) {
                $out[$build_id] = array();
                $out[$build_id]['name'] = $metrics['active_builds'][$build_id]['name'];
                $out[$build_id]['notes'] = $metrics['active_builds'][$build_id]['notes'];
                $out[$build_id]['total'] = $metrics['total'][$build_id]['qty'];

                foreach( $elem as $status_code => &$data ) {
                    $out[$build_id][$status_code] = $data['exec_qty'];
                }
            }
            return array(
                    'raw' => $metrics,
                    'table' => $out
            );
        } else {
            return $this->errors;
        }
    }

    /**
     * create platform
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testprojectname"]
     * @param string $args["platformname"]
     * @param string $args["notes"]
     * @return mixed $resultInfo
     * @internal revisions
     */
    public function createPlatform($args) {
        $this->_setArgs( $args );
        $status_ok = false;
        $msg_prefix = "(" . __FUNCTION__ . ") - ";

        if($this->authenticate() && $this->userHasRight( "platform_management", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
            $status_ok = true;
            $keys2check = array(
                    self::$platformNameParamName,
                    self::$testProjectNameParamName
            );
            foreach( $keys2check as $key ) {
                $names[$key] = $this->_isParamPresent( $key, $msg_prefix, self::SET_ERROR ) ? trim( $this->args[$key] ) : '';
                if($names[$key] == '') {
                    $status_ok = false;
                    break;
                }
            }
        }

        if($status_ok) {
            $op = $this->helperGetTestProjectByName( $msg_prefix );
            $status_ok = $op['status_ok'];
        }

        if($status_ok) {
            // now check if platform exists
            if(is_null( $this->platformMgr )) {
                $this->platformMgr = new tlPlatform( $this->dbObj, $op['info']['id'] );
            }
            // lazy way
            $name = trim( $this->args[self::$platformNameParamName] );

            $opx = array('accessKey' => 'name',
                         'output' => 'allinfo');

            $itemSet = $this->platformMgr->getAllAsMap($opx);
            if(isset( $itemSet[$name] )) {
                $status_ok = false;
                $msg = $msg_prefix . sprintf( PLATFORMNAME_ALREADY_EXISTS_STR, $name, $itemSet[$name]['id'] );
                $this->errors[] = new IXR_Error( PLATFORMNAME_ALREADY_EXISTS, $msg );
            }
        }

        if($status_ok) {
            $notes = $this->_isNotePresent() ? $this->args[self::$noteParamName] : '';
            $op = $this->platformMgr->create( $name, $notes );
            $resultInfo = $op;
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     */
    public function getProjectPlatforms($args) {
        $messagePrefix = "(" . __FUNCTION__ . ") - ";

        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate',
                'checkTestProjectIdentity'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix );

        if($status_ok) {
            $testProjectID = $this->args[self::$testProjectIDParamName];
            if(is_null( $this->platformMgr )) {
                $this->platformMgr = new tlPlatform( $this->dbObj, $testProjectID );
            }
            $itemSet = $this->platformMgr->getAllAsMap( 'name', 'allinfo' );
            return $itemSet;
        } else {
            return $this->errors;
        }
    }

    /**
     * addPlatformToTestPlan
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @param map $args["platformname"]
     * @return mixed $resultInfo
     * @internal revisions
     */
    public function addPlatformToTestPlan($args) {
        return $this->platformLinkOp( $args, 'link', "(" . __FUNCTION__ . ") - " );
    }

    /**
     * removePlatformFromTestPlan
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @param map $args["platformname"]
     * @return mixed $resultInfo
     * @internal revisions
     */
    public function removePlatformFromTestPlan($args) {
        return $this->platformLinkOp( $args, 'unlink', "(" . __FUNCTION__ . ") - " );
    }

    /**
     * if everything ok returns an array on just one element with following user data
     *
     * firstName,lastName,emailAddress,locale,isActive,defaultTestprojectID,
     * globalRoleID
     * globalRole array with role info
     * tprojectRoles array
     * tplanRoles array
     * login
     * dbID
     * loginRegExp
     *
     * ATTENTION: userApiKey will be set to NULL, because is worst that access to user password
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["user"]
     *            Login Name
     *
     * @return mixed $ret
     *
     */
    public function getUserByLogin($args) {
        $messagePrefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate'
        );
        $ret = array();

        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix );
        if($status_ok) {
            $user_id = tlUser::doesUserExist( $this->dbObj, $this->args[self::$userParamName] );
            if(!($status_ok = ! is_null( $user_id ))) {
                $msg = $msg_prefix . sprintf( NO_USER_BY_THIS_LOGIN_STR, $this->args[self::$userParamName] );
                $this->errors[] = new IXR_Error( NO_USER_BY_THIS_LOGIN, $msg );
            }
        }

        if($status_ok) {
            $user = tlUser::getByID( $this->dbObj, $user_id );
            $user->userApiKey = null;
            $ret[] = $user;
        }

        return $status_ok ? $ret : $this->errors;
    }

    /**
     * if everything ok returns an array on just one element with following user data
     *
     * firstName,lastName,emailAddress,locale,isActive,defaultTestprojectID,
     * globalRoleID
     * globalRole array with role info
     * tprojectRoles array
     * tplanRoles array
     * login
     * dbID
     * loginRegExp
     *
     * ATTENTION: userApiKey will be set to NULL, because is worst that access to user password
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["userid"]
     *            user ID as present on users table, column ID
     *
     * @return mixed $ret
     *
     */
    public function getUserByID($args) {
        $messagePrefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate'
        );
        $ret = array();

        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix );
        if($status_ok) {
            $user = tlUser::getByID( $this->dbObj, $this->args[self::$userIDParamName] );
            if(is_null( $user )) {
                $status_ok = false;
                $msg = $messagePrefix . sprintf( NO_USER_BY_THIS_ID_STR, $this->args[self::$userIDParamName] );
                $this->errors[] = new IXR_Error( NO_USER_BY_ID_LOGIN, $msg );
            } else {
                $user->userApiKey = null;
                $ret[] = $user;
            }
        }

        return $status_ok ? $ret : $this->errors;
    }

    /**
     */
    private function platformLinkOp($args, $op, $messagePrefix) {
        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix ) && $this->_isParamPresent( self::$platformNameParamName, $messagePrefix, self::SET_ERROR );
        if($status_ok) {
            $testPlanID = $this->args[self::$testPlanIDParamName];

            // get Test project ID in order to check that requested Platform
            // belong to same test project that test plan
            $dummy = $this->tplanMgr->get_by_id( $testPlanID );
            $testProjectID = $dummy['testproject_id'];

            if(is_null( $this->platformMgr )) {
                $this->platformMgr = new tlPlatform( $this->dbObj, $testProjectID );
            } else {
                // extra protection ??(20131307)
                $this->platformMgr->setTestProjectID( $testProjectID );
            }
            $platName = $this->args[self::$platformNameParamName];
            $platform = $this->platformMgr->getByName( $platName );
            if(is_null( $platform )) {
                $status_ok = false;
                $msg = $messagePrefix . sprintf( PLATFORM_NAME_DOESNOT_EXIST_STR, $platName );
                $this->errors[] = new IXR_Error( PLATFORM_NAME_DOESNOT_EXIST, $msg );
            }
        }

        if($status_ok) {
            $linkExists = $this->platformMgr->isLinkedToTestplan( $platform['id'], $testPlanID );
            $ret = array(
                    'operation' => $op,
                    'msg' => 'nothing to do',
                    'linkStatus' => $linkExists
            );
            switch($op) {
                case 'link' :
                    if(! $linkExists) {
                        $this->platformMgr->linkToTestplan( $platform['id'], $testPlanID );
                        $ret['msg'] = 'link done';
                    }
                    break;

                case 'unlink' :
                    if($linkExists) {
                        // If there are test case versions linked to test plan, that use
                        // this platform, operation(as happens on GUI) can not be done
                        $hits = $this->tplanMgr->countLinkedTCVersionsByPlatform( $testPlanID,( array ) $platform['id'] );
                        if($hits[$platform['id']]['qty'] == 0) {
                            $this->platformMgr->unlinkFromTestplan( $platform['id'], $testPlanID );
                            $ret['msg'] = 'unlink done';
                        } else {
                            $status_ok = false;
                            $msg = $messagePrefix . sprintf( PLATFORM_REMOVETC_NEEDED_BEFORE_UNLINK_STR, $platName, $hits[$platform['id']]['qty'] );
                            $this->errors[] = new IXR_Error( PLATFORM_REMOVETC_NEEDED_BEFORE_UNLINK, $msg );
                        }
                    }
                    break;
            }
            if($status_ok) {
                return $ret;
            }
        }

        if(! $tatus_ok) {
            return $this->errors;
        }
    }

    /**
     * Update an existing test case
     * Not all test case attributes will be able to be updated using this method
     * See details below
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testcaseexternalid"]
     *            format PREFIX-NUMBER
     * @param int $args["version"]
     *            optional version NUMBER(human readable)
     * @param string $args["testcasename"]
     *            - optional
     * @param string $args["summary"]
     *            - optional
     * @param string $args["preconditions"]
     *            - optional
     * @param array $args["steps"]
     *            - optional
     *            each element is a hash with following keys
     *            step_number,actions,expected_results,execution_type
     *
     * @param int $args["importance"]
     *            - optional - see const.inc.php for domain
     * @param int $args["executiontype"]
     *            - optional - see ... for domain
     * @param int $args["status']
     *            - optional
     * @param int $args["estimatedexecduration']
     *            - optional
     * @param string $args["user']
     *            - login name used as updater - optional
     *            if not provided will be set to user that request update
     */
    public function updateTestCase($args) {
        // Check test case identity
        // Check if user(devkey) has grants to do operation
        //
        // Check that configuration allow changes on Test Case
        // Check that new test case name do not collide with existent one
        //
        // translate args key to column name
        $updKeys = array(
                "summary" => null,
                "preconditions" => null,
                "importance" => null,
                "status" => null,
                "executiontype" => "execution_type",
                "estimatedexecduration" => "estimated_exec_duration"
        );

        $resultInfo = array();
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $debug_info = null;

        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate',
                'checkTestCaseIdentity'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        $tprojectID = 0;
        if($status_ok) {
            $tcaseID = $this->args[self::$testCaseIDParamName];
            $tprojectID = $this->tcaseMgr->getTestProjectFromTestCase( $tcaseID, null );
        }

        if($status_ok) {
            $updaterID = $this->updateTestCaseGetUpdater( $msg_prefix );
            $status_ok =($updaterID > 0);
        }

        if($status_ok) {
            // we have got internal test case ID on checkTestCaseIdentity
            list( $status_ok, $tcversion_id ) = $this->updateTestCaseGetTCVID( $tcaseID );
        }

        // We will check that:
        // updater has right to update
        // user doing call has also has right to update
        if($status_ok) {
            $ctx[self::$testProjectIDParamName] = $tprojectID;
            if($updaterID != $this->userID) {
                $ctx['updaterID'] = $updaterID;
            }

            $ck = self::CHECK_PUBLIC_PRIVATE_ATTR;

            $r2c = array(
                    'mgt_modify_tc'
            );
            foreach( $r2c as $right ) {
                $status_ok = $this->userHasRight( $right, $ck, $ctx );
                if(! $status_ok) {
                    break;
                }
            }
        }

        // If test case version has been executed, need to check another right
        if($status_ok) {
            $xc = $this->tcaseMgr->get_versions_status_quo( $tcaseID, $tcversion_id );
            $checkRight = false;
            foreach( $xc as $ele ) {
                if($ele['executed']) {
                    $checkRight = true;
                    break;
                }
            }

            if($checkRight) {
                $r2c = array('testproject_edit_executed_testcases' );
                foreach( $r2c as $right ) {
                    $status_ok = $this->userHasRight( $right, $ck, $ctx );
                    if(! $status_ok) {
                        break;
                    }
                }
            }
        }

        // if name update requested, it will be first thing to be udpated
        // because if we got duplicate name, we will not do update
        if($status_ok) {
            if(isset( $this->args[self::$testCaseNameParamName] )) {
                $ret = $this->tcaseMgr->updateName( $tcaseID, trim( $this->args[self::$testCaseNameParamName] ) );
                if(!($status_ok = $ret['status_ok'])) {
                    $this->errors[] = new IXR_Error( constant( $ret['API_error_code'] ), $msg_prefix . $ret['msg'] );
                }
            }
        }

        if($status_ok) {
            $fv = null;
            foreach( $updKeys as $k2s => $field2update ) {
                if(isset( $this->args[$k2s] )) {
                    $fv[(is_null( $field2update ) ? $k2s : $field2update)] = $this->args[$k2s];
                }
            }

            if(! is_null( $fv )) {
                $sql = $this->tcaseMgr->updateSimpleFields( $tcversion_id, $fv );
            }
        }

        // if exist proceed with steps actions / expected results update.
        if($status_ok) {
            if($this->_isParamPresent( self::$stepsParamName ) && ! is_null( $this->args[self::$stepsParamName] )) {
                $this->tcaseMgr->update_tcversion_steps( $tcversion_id, $this->args[self::$stepsParamName] );
            }
        }

        if($status_ok) {
            // update updater and modification time stamp
            $this->tcaseMgr->updateChangeAuditTrial( $tcversion_id, $updaterID );
            return array(
                    'status_ok' => true,
                    'msg' => 'ok',
                    'operation' => __FUNCTION__
            );
        }

        return $this->errors;
    }

    /**
     */
    function updateTestCaseGetUpdater($msg_prefix) {
        $status_ok = true;
        $updaterID = $this->userID;
        if($this->_isParamPresent( self::$userParamName )) {
            $updaterID = tlUser::doesUserExist( $this->dbObj, $this->args[self::$userParamName] );
            if(!($status_ok = ! is_null( $updaterID ))) {
                $msg = $msg_prefix . sprintf( NO_USER_BY_THIS_LOGIN_STR, $this->args[self::$userParamName] );
                $this->errors[] = new IXR_Error( NO_USER_BY_THIS_LOGIN, $msg );
            }
        }
        return $status_ok ? $updaterID : - 1;
    }

    /**
     */
    function updateTestCaseGetTCVID($tcaseID) {
        $status_ok = true;
        $tcversion_id = - 1;

        // if user has not provided version number, get last version
        // no matter if active or not
        if(isset( $this->args[self::$versionNumberParamName] )) {
            if(($status_ok = $this->checkTestCaseVersionNumber())) {
                // Check if version number exists for Test Case
                $ret = $this->checkTestCaseVersionNumberAncestry();
                if(!($status_ok = $ret['status_ok'])) {
                    $this->errors[] = new IXR_Error( $ret['error_code'], $msg_prefix . $ret['error_msg'] );
                }
            }

            if($status_ok) {
                $opt = array(
                        'number' => $this->args[self::$versionNumberParamName]
                );
                $dummy = $this->tcaseMgr->get_basic_info( $tcaseID, $opt );
                $tcversion_id = $dummy[0]['tcversion_id'];
            }
        } else {
            // get latest version info
            $dummy = $this->tcaseMgr->get_last_version_info( $tcaseID );
            $dummy['tcversion_id'] = $dummy['id'];
            $tcversion_id = $dummy['tcversion_id'];
        }

        return array(
                $status_ok,
                $tcversion_id
        );
    }

    /**
     */
    private function helperGetTestProjectByName($msgPrefix = '') {
        $ret = array(
                'status_ok' => true,
                'info' => null
        );

        $name = trim( $this->args[self::$testProjectNameParamName] );
        $check_op = $this->tprojectMgr->checkNameExistence( $name );
        $ret['status_ok'] = ! $check_op['status_ok'];
        if($ret['status_ok']) {
            $ret['info'] = current( $this->tprojectMgr->get_by_name( $name ) );
        } else {
            $msg = $msg_prefix . sprintf( TESTPROJECTNAME_DOESNOT_EXIST_STR, $name );
            $this->errors[] = new IXR_Error( TESTPROJECTNAME_DOESNOT_EXIST, $msg );
        }
        return $ret;
    }

    /**
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @param string $args["testcaseexternalid"]
     *            format PREFIX-NUMBER
     * @param int $args["buildid"]
     *            Mandatory => you can provide buildname as alternative
     * @param int $args["buildname"]
     *            Mandatory => you can provide buildid(DB ID) as alternative
     * @param int $args["platformid"]
     *            optional - BECOMES MANDATORY if Test plan has platforms
     *            you can provide platformname as alternative
     *
     * @param int $args["platformname"]
     *            optional - BECOMES MANDATORY if Test plan has platforms
     *            you can provide platformid as alternative
     * @param string $args["user']
     *            - login name => tester
     *
     */
    public function assignTestCaseExecutionTask($args) {
        $msgPrefix = "(" . __FUNCTION__ . ") - ";
        $args['action'] = 'assignOne';
        return $this->manageTestCaseExecutionTask( $args, $msgPrefix );
    }

    /**
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testplanid"]
     * @param string $args["testcaseexternalid"]
     *            format PREFIX-NUMBER
     * @param int $args["buildid"]
     *            Mandatory => you can provide buildname as alternative
     * @param int $args["buildname"]
     *            Mandatory => you can provide buildid(DB ID) as alternative
     * @param int $args["platformid"]
     *            optional - BECOMES MANDATORY if Test plan has platforms
     *            you can provide platformname as alternative
     *
     * @param int $args["platformname"]
     *            optional - BECOMES MANDATORY if Test plan has platforms
     *            you can provide platformid as alternative
     * @param string $args["user']
     *            - login name => tester
     *            - NOT NEEDED f $args['action'] = 'unassignAll'
     *            ¸
     *
     */
    public function unassignTestCaseExecutionTask($args) {
        $msgPrefix = "(" . __FUNCTION__ . ") - ";
        if(! isset( $args['action'] )) {
            $args['action'] = 'unassignOne';
        }
        return $this->manageTestCaseExecutionTask( $args, $msgPrefix );
    }

    /**
     * Gets the result of LAST EXECUTION for a particular testcase on a test plan.
     * If there are no filter criteria regarding platform and build,
     * result will be get WITHOUT checking for a particular platform and build.
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["tplanid"]
     * @param string $args["testcaseexternalid"]
     *            format PREFIX-NUMBER
     * @param int $args["buildid"]
     *            Mandatory => you can provide buildname as alternative
     * @param int $args["buildname"]
     *            Mandatory => you can provide buildid(DB ID) as alternative
     * @param int $args["platformid"]
     *            optional - BECOMES MANDATORY if Test plan has platforms
     *            you can provide platformname as alternative
     *
     * @param int $args["platformname"]
     *            optional - BECOMES MANDATORY if Test plan has platforms
     *            you can provide platformid as alternative
     *
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestCaseAssignedTester($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";
        $status_ok = true;
        $this->_setArgs( $args );
        $resultInfo = array();

        // Checks are done in order
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID',
                'checkTestCaseIdentity',
                'checkBuildID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        // Check if requested test case is linke to test plan
        // if answer is yes, get link info, in order to be able to check if
        // we need also platform info
        if($status_ok) {
            $execContext = array(
                    'tplan_id' => $this->args[self::$testPlanIDParamName],
                    'platform_id' => null,
                    'build_id' => $this->args[self::$buildIDParamName]
            );

            $tplan_id = $this->args[self::$testPlanIDParamName];
            $tcase_id = $this->args[self::$testCaseIDParamName];
            $filters = array(
                    'exec_status' => "ALL",
                    'active_status' => "ALL",
                    'tplan_id' => $tplan_id,
                    'platform_id' => null
            );

            $info = $this->tcaseMgr->get_linked_versions( $tcase_id, $filters, array(
                    'output' => "feature_id"
            ) );

            // more than 1 item => we have platforms
            // access key => tcversion_id, tplan_id, platform_id
            $link = current( $info );
            $link = $link[$tplan_id];
            $hits = count( $link );
            $check_platform =(count( $hits ) > 1) || ! isset( $link[0] );
        }

        if($status_ok && $check_platform) {
            // this means that platform is MANDATORY
            if(! $this->_isParamPresent( self::$platformIDParamName, $msg_prefix ) && ! $this->_isParamPresent( self::$platformNameParamName, $msg_prefix )) {
                $status_ok = false;
                $pname = self::$platformNameParamName . ' OR ' . self::$platformIDParamName;
                $msg = $messagePrefix . sprintf( MISSING_REQUIRED_PARAMETER_STR, $pname );
                $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
            } else {
                // get platform_id and check it
                if(($status_ok = $this->checkPlatformIdentity( $tplan_id ))) {
                    $platform_set = $this->tplanMgr->getPlatforms( $tplan_id, array(
                            'outputFormat' => 'mapAccessByID',
                            'outputDetails' => 'name'
                    ) );

                    // Now check if link has all 3 components
                    // test plan, test case, platform
                    $platform_id = $this->args[self::$platformIDParamName];
                    $platform_info = array(
                            $platform_id => $platform_set[$platform_id]
                    );

                    if(($status_ok = $this->_checkTCIDAndTPIDValid( $platform_info, $msg_prefix ))) {
                        $execContext['platform_id'] = $platform_id;
                    }
                }
            }
        }

        if($status_ok) {
            $getOpt = array(
                    'output' => 'assignment_info',
                    'build4assignment' => $execContext['build_id']
            );
            $dummy = $this->tplanMgr->getLinkInfo( $tplan_id, $tcase_id, $platform_id, $getOpt );
            $resultInfo[0] = array(
                    'user_id' => $dummy[0]['user_id'],
                    'login' => $dummy[0]['login'],
                    'first' => $dummy[0]['first'],
                    'last' => $dummy[0]['last']
            );
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * Returns all bugs linked to a particular testcase on a test plan.
     * If there are no filter criteria regarding platform and build,
     * result will be get WITHOUT checking for a particular platform and build.
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["tplanid"]
     * @param int $args["testcaseid"]:
     *            Pseudo optional.
     *            if does not is present then testcaseexternalid MUST BE present
     *
     * @param int $args["testcaseexternalid"]:
     *            Pseudo optional.
     *            if does not is present then testcaseid MUST BE present
     *
     * @param string $args["platformid"]:
     *            optional.
     *            ONLY if not present, then $args["platformname"]
     *            will be analized(if exists)
     *
     * @param string $args["platformname"]:
     *            optional(see $args["platformid"])
     *
     * @param int $args["buildid"]:
     *            optional
     *            ONLY if not present, then $args["buildname"] will be analized(if exists)
     *
     * @param int $args["buildname"]
     *            - optional(see $args["buildid"])
     *
     *
     * @return mixed $resultInfo
     *         if execution found
     *         array that contains a map with these keys:
     *         bugs
     *
     *         if test case has not been execute,
     *         array('id' => -1)
     *
     * @access public
     */
    public function getTestCaseBugs($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $resultInfo = array();
        $status_ok = true;

        // Checks are done in order
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID',
                'checkTestCaseIdentity'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->_checkTCIDAndTPIDValid( null, $msg_prefix ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        $execContext = array(
                'tplan_id' => $this->args[self::$testPlanIDParamName],
                'platform_id' => null,
                'build_id' => null
        );

        // Now we can check for Optional parameters
        if($this->_isBuildIDPresent() || $this->_isBuildNamePresent()) {
            if(($status_ok = $this->checkBuildID( $msg_prefix ))) {
                $execContext['build_id'] = $this->args[self::$buildIDParamName];
            }
        }

        if($status_ok) {
            if($this->_isParamPresent( self::$platformIDParamName, $msg_prefix ) || $this->_isParamPresent( self::$platformNameParamName, $msg_prefix )) {
                $status_ok = $this->checkPlatformIdentity( $this->args[self::$testPlanIDParamName] );
                if($status_ok) {
                    $execContext['platform_id'] = $this->args[self::$platformIDParamName];
                }
            }
        }

        if($status_ok) {
            $sql = " SELECT id AS exec_id FROM {$this->tables['executions']} " . " WHERE testplan_id = {$this->args[self::$testPlanIDParamName]} " . " AND tcversion_id IN(" . " SELECT id FROM {$this->tables['nodes_hierarchy']} " . " WHERE parent_id = {$this->args[self::$testCaseIDParamName]})";

            if(! is_null( $execContext['build_id'] )) {
                $sql .= " AND build_id = " . intval( $execContext['build_id'] );
            }

            if(! is_null( $execContext['platform_id'] )) {
                $sql .= " AND platform_id = " . intval( $execContext['platform_id'] );
            }

            $rs = $this->dbObj->fetchRowsIntoMap( $sql, 'exec_id' );
            if(is_null( $rs )) {
                // has not been executed
                // execution id = -1 => test case has not been runned.
                $resultInfo[] = array(
                        'id' => - 1
                );
            } else {
                $targetIDs = array();
                foreach( $rs as $execrun ) {
                    $targetIDs[] = $execrun['exec_id'];
                }
                $resultInfo[0]['bugs'] = array();
                $sql = " SELECT DISTINCT bug_id FROM {$this->tables['execution_bugs']} " . " WHERE execution_id in(" . implode( ',', $targetIDs ) . ")";
                $resultInfo[0]['bugs'] =( array ) $this->dbObj->get_recordset( $sql );
            }
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["action"]:
     *            assignOne, unassignOne, unassignAll
     *
     * @param int $args["testplanid"]
     * @param string $args["testcaseexternalid"]
     *            format PREFIX-NUMBER
     * @param int $args["buildid"]
     *            Mandatory => you can provide buildname as alternative
     * @param int $args["buildname"]
     *            Mandatory => you can provide buildid(DB ID) as alternative
     * @param int $args["platformid"]
     *            optional - BECOMES MANDATORY if Test plan has platforms
     *            you can provide platformname as alternative
     *
     * @param int $args["platformname"]
     *            optional - BECOMES MANDATORY if Test plan has platforms
     *            you can provide platformid as alternative
     * @param string $args["user']
     *            - login name => tester
     *
     */
    private function manageTestCaseExecutionTask($args, $msg_prefix) {
        $status_ok = true;
        $this->_setArgs( $args );
        $resultInfo = array();

        // Checks are done in order
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID',
                'checkTestCaseIdentity',
                'checkBuildID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            switch($args['action']) {
                case 'assignOne' :
                case 'unassignOne' :
                    if(($status_ok = $this->_isParamPresent( self::$userParamName, $msg_prefix, self::SET_ERROR ))) {
                        $tester_id = tlUser::doesUserExist( $this->dbObj, $this->args[self::$userParamName] );
                        if(!($status_ok = ! is_null( $tester_id ))) {
                            $msg = $msg_prefix . sprintf( NO_USER_BY_THIS_LOGIN_STR, $this->args[self::$userParamName] );
                            $this->errors[] = new IXR_Error( NO_USER_BY_THIS_LOGIN, $msg );
                        }
                    }
                    break;

                case 'unassignAll' :
                    break;
            }
        }

        // Check if requested test case is linked to test plan
        // if answer is yes, get link info, in order to be able to check if
        // we need also platform info
        if($status_ok) {
            $execContext = array(
                    'tplan_id' => $this->args[self::$testPlanIDParamName],
                    'platform_id' => null,
                    'build_id' => $this->args[self::$buildIDParamName]
            );

            $tplan_id = $this->args[self::$testPlanIDParamName];
            $tcase_id = $this->args[self::$testCaseIDParamName];
            $filters = array(
                    'exec_status' => "ALL",
                    'active_status' => "ALL",
                    'tplan_id' => $tplan_id,
                    'platform_id' => null
            );

            $info = $this->tcaseMgr->get_linked_versions( $tcase_id, $filters, array(
                    'output' => "feature_id"
            ) );

            // more than 1 item => we have platforms
            // access key => tcversion_id, tplan_id, platform_id
            $link = current( $info );
            $link = $link[$tplan_id]; // Inside test plan, is indexed by platform
            $hits = count( $link );
            $platform_id = 0;
            $check_platform =(count( $hits ) > 1) || ! isset( $link[0] );
        }

        if($status_ok && $check_platform) {
            // this means that platform is MANDATORY
            if(! $this->_isParamPresent( self::$platformIDParamName, $msg_prefix ) && ! $this->_isParamPresent( self::$platformNameParamName, $msg_prefix )) {
                $status_ok = false;
                $pname = self::$platformNameParamName . ' OR ' . self::$platformIDParamName;
                $msg = $messagePrefix . sprintf( MISSING_REQUIRED_PARAMETER_STR, $pname );
                $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
            } else {
                // get platform_id and check it
                if(($status_ok = $this->checkPlatformIdentity( $tplan_id ))) {
                    $platform_set = $this->tplanMgr->getPlatforms( $tplan_id, array(
                            'outputFormat' => 'mapAccessByID',
                            'outputDetails' => 'name'
                    ) );

                    // Now check if link has all 3 components
                    // test plan, test case, platform
                    $platform_id = $this->args[self::$platformIDParamName];
                    $platform_info = array(
                            $platform_id => $platform_set[$platform_id]
                    );

                    if(($status_ok = $this->_checkTCIDAndTPIDValid( $platform_info, $msg_prefix ))) {
                        $execContext['platform_id'] = $platform_id;
                    }
                }
            }
        }

        if($status_ok) {
            $assignment_mgr = new assignment_mgr( $this->dbObj );
            $types = $assignment_mgr->get_available_types();

            // Remove old execution task assignment
            // `id` int(10) unsigned NOT NULL auto_increment,
            // `type` int(10) unsigned NOT NULL default '1',
            // `feature_id` int(10) unsigned NOT NULL default '0',
            // `user_id` int(10) unsigned default '0',
            // `build_id` int(10) unsigned default '0',
            // `deadline_ts` datetime NULL,
            // `assigner_id` int(10) unsigned default '0',
            // `creation_ts` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
            // `status` int(10) unsigned default '1',

            // ATTENTION WITH PLATFORMS
            $link = is_null( $execContext['platform_id'] ) ? $link[0] : $link[$execContext['platform_id']];
            $feature = array(
                    $link['feature_id'] => array(
                            'build_id' => $execContext['build_id']
                    )
            );

            switch($args['action']) {
                case 'unassignOne' :
                    $signature[] = array(
                            'type' => $types['testcase_execution']['id'],
                            'user_id' => $tester_id,
                            'feature_id' => $link['feature_id'],
                            'build_id' => $execContext['build_id']
                    );
                    $assignment_mgr->deleteBySignature( $signature );
                    break;

                case 'assignOne' :
                    // Step 1 - remove if exists
                    $signature[] = array(
                            'type' => $types['testcase_execution']['id'],
                            'user_id' => $tester_id,
                            'feature_id' => $link['feature_id'],
                            'build_id' => $execContext['build_id']
                    );
                    $assignment_mgr->deleteBySignature( $signature );

                    // Step 2 - Now assign
                    $assign_status = $assignment_mgr->get_available_status();

                    $oo[$link['feature_id']]['type'] = $types['testcase_execution']['id'];
                    $oo[$link['feature_id']]['status'] = $assign_status['open']['id'];
                    $oo[$link['feature_id']]['user_id'] = $tester_id;
                    $oo[$link['feature_id']]['assigner_id'] = $this->userID;
                    $oo[$link['feature_id']]['build_id'] = $execContext['build_id'];
                    $assignment_mgr->assign( $oo );
                    break;

                case 'unassignAll' :
                    $oo[$link['feature_id']]['type'] = $types['testcase_execution']['id'];
                    $oo[$link['feature_id']]['build_id'] = $execContext['build_id'];
                    $assignment_mgr->delete_by_feature_id_and_build_id( $oo );
                    break;
            }

            $resultInfo = array(
                    "status" => true,
                    "args" => $this->args
            );
            unset( $resultInfo['args']['devKey'] );
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     */
    public function getProjectKeywords($args) {
        $messagePrefix = "(" . __FUNCTION__ . ") - ";

        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix );

        if($status_ok) {
            $itemSet = $this->getValidKeywordSet( intval( $this->args[self::$testProjectIDParamName] ), '', true, 'getProjectKeywords' );
            return $itemSet;
        } else {
            return $this->errors;
        }
    }

    /**
     * Gets list of keywords for a given Test Case Version
     *
     * @param mixed $testcaseid can be int or array
     *        mixed $testcaseexternalid can be int or array
     *        mixed[$version] can be int or array
     *              if not provided latest version will be used
     *
     * @return map indexed by Access Key
     *             test case internal(DB) ID OR
     *             test case external ID.
     *
     * @access public
     */
    public function getTestCaseKeywords($args) {
        $msgPrefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );

        // Prepare material for checkTestCaseSetIdentity()
        $a2check = array( self::$testCaseIDParamName,
                          self::$testCaseExternalIDParamName );

        foreach( $a2check as $k2c ) {
          if(isset( $this->args[$k2c] )) {
            $retAsArray = is_array( $this->args[$k2c] );
            $this->args[$k2c] =(array) $this->args[$k2c];
            $outBy = $k2c;
            break;
          }
        }

        $checkFunctions = array('authenticate',
                                'checkTestCaseSetIdentity');
        $status_ok = $this->_runChecks( $checkFunctions, $msgPrefix );

        if($status_ok) {
          $hasVersionSet = isset($this->args[self::$versionNumberParamName]);
          if( $hasVersionSet ) {
            $versionSet = $this->args[self::$versionNumberParamName]; 
          }  

          $kwSet = array();
          $itemSet = $this->args[self::$testCaseIDParamName]; 
          foreach( $itemSet as $idx => $tcaseID ) {

            $safeTCID = intval($tcaseID);
            $accessKey =($outBy == self::$testCaseIDParamName) ? 
                          $tcaseID : $this->args[$outBy][$idx];

            if( $hasVersionSet && isset($versionSet[$idx]) ) {
              $vnum = intval($versionSet[$idx]);  
              $tcversion_id = getTCVersionIDFromVersion($safeTCID,$vnum);
            } else {
              $tcversion_id = $this->tcaseMgr->getLatestVersionID($safeTCID);  
            }

            $kwSet[$accessKey] = 
              $this->tcaseMgr->get_keywords_map($safeTCID,$tcversion_id);
          }
          return $kwSet;
        } else {
          return $this->errors;
        }
    }

    /**
     * Delete a test plan and all related link to other items
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["$tplanID"]
     *
     * @return mixed $resultInfo
     *        [status] => true/false of success
     *        [id] => result id or error code
     *        [message] => optional message for error message string
     * @access public
     */
    public function deleteTestPlan($args) {
        $resultInfo = array();
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $resultInfo[0]["status"] = false;

        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            if($this->userHasRight( "exec_delete", self::CHECK_PUBLIC_PRIVATE_ATTR )) {
                $this->tplanMgr->delete( $args[self::$testPlanIDParamName] );
                $resultInfo[0]["status"] = true;
                $resultInfo[0]["id"] = $args[self::$testPlanIDParamName];
                $resultInfo[0]["message"] = GENERAL_SUCCESS_STR;
                $resultInfo[0]["operation"] = $operation;
            } else {
                $status_ok = false;
                $this->errors[] = new IXR_Error( CFG_DELETE_EXEC_DISABLED, CFG_DELETE_EXEC_DISABLED_STR );
            }
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * addTestCaseKeywords
     *
     * links will be added to LATEST Test Case Version
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param array $args["keywords"]:
     *            map key testcaseexternalid
     *            values array of keyword name
     *
     * @return mixed $resultInfo
     *
     */
    function addTestCaseKeywords($args) {
        $ret = $this->checksForManageTestCaseKeywords( $args, 'add' );
        if($ret['status_ok']) {
            $kwSet = $this->args[self::$keywordNameParamName];
            return $this->manageTestCaseKeywords( $kwSet, $ret['tprojectSet'], 'add' );
        }
        return $this->errors;
    }

    /**
     * removeTestCaseKeywords
     *
     * @param struct $args
     * @param string $args["devKey"]
     *
     * @return mixed $resultInfo
     *
     * @internal revisions
     * @since 1.9.14
     */
    function removeTestCaseKeywords($args) {
        $ret = $this->checksForManageTestCaseKeywords( $args, 'remove' );
        if($ret['status_ok']) {
            $kwSet = $this->args[self::$keywordNameParamName];
            return $this->manageTestCaseKeywords( $kwSet, $ret['tprojectSet'], 'remove' );
        }
        return $this->errors;
    }

    /**
     *
     * @used by manageTestCaseKeywords
     */
    protected function checksForManageTestCaseKeywords($args, $action) {
        $operation = str_replace( 'checksForManage', $action, __FUNCTION__ );
        $msg_prefix = "({$operation}) - ";
        $resultInfo = array();

        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate'
        );

        // Check on user rights can have some problems if test cases do not belong
        // to same test project
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );
        if($status_ok) {
            $items = array_keys( $this->args[self::$keywordNameParamName] );
            $status_ok = $this->checkTestCaseSetIdentity( $msg_prefix, $items );
        }

        if($status_ok) {
            // Get test projects
            $idSet = $this->args[self::$testCaseIDParamName];

            foreach( $idSet as $key => $val ) {
                // indexed by same value than keywords
                $tprojectSet[$items[$key]] = $this->tcaseMgr->get_testproject( $val );

                // Do authorization checks, all or nothing
                // userHasRight() on failure set error to return to caller
                $status_ok = $this->userHasRight( "mgt_modify_tc", self::CHECK_PUBLIC_PRIVATE_ATTR, array(
                        self::$testProjectIDParamName => $tprojectSet[$items[$key]]
                ) );
                if(! $status_ok) {
                    break;
                }
            }
        }

        $ret['status_ok'] = $status_ok;
        $ret['tprojectSet'] = $tprojectSet;

        return $ret;
    }

    /**
     * manageTestCaseKeywords
     *
     * @param
     *            struct
     *
     * @param string $action:
     *            domain 'add','remove'
     * @return mixed $resultInfo
     *
     */
    protected function manageTestCaseKeywords($keywords, $tprojects, $action) {
        switch($action) {
            case 'add' :
                $method2call = 'addKeywords';
                break;

            case 'delete' :
            case 'remove' :
                $method2call = 'deleteKeywords';
                break;

            default :
                $resultInfo['status_ok'] = false;
                $resultInfo['verbose'] = __FUNCTION__ . ' :: Banzai!! - No valida method';
                return $resultInfo;
                break;
        }

        $kw = array();
        $resultInfo['validKeywords'] = null;
        $resultInfo['status_ok'] = true;

        foreach( $keywords as $ak => $kwset ) {
            $kw[$ak] = $this->getValidKeywordSet( $tprojects[$ak], implode( ",", $kwset ), true, true );

            $resultInfo['validKeywords'][$ak] = $kw[$ak];
            $resultInfo['status_ok'] = $resultInfo['status_ok'] &&($kw[$ak] != '');
        }

        if($resultInfo['status_ok']) {

            $cacheLTCV = array();
            foreach( $kw as $ak => $val ) {
                $tcaseID = $this->tcaseE2I[$ak];
                if(! isset( $cacheLTCV[$tcaseID] )) {
                    $cacheLTCV[$tcaseID] = $this->tcaseMgr->getLatestVersionID( $tcaseID );
                }
                $this->tcaseMgr->$method2call( $this->tcaseE2I[$ak], $cacheLTCV[$tcaseID], array_keys( $val ) );
            }
        }

        return $resultInfo;
    }

    /**
     * Helper method to see if in a test case set identity provided is valid
     * Identity can be specified in one of these modes:
     *
     * test case internal id
     * test case external id(PREFIX-NNNN)
     *
     * If everything OK, an array of test case internal ID is setted.
     *
     * @param string $messagePrefix
     *            used to be prepended to error message
     *
     * @return boolean
     * @access protected
     */
    protected function checkTestCaseSetIdentity($messagePrefix = '', $itemSet = null) {
        // Three Cases - Internal ID, External ID, No Id
        $status_ok = false;
        $fromExternal = false;
        $fromInternal = false;
        $fromItemSet = false;

        $tcaseID = 0;
        $tcaseIDSet = null;
        $tcaseE2I = null; // External to Internal

        if(! is_null( $itemSet )) {
            $fromExternal = true;
            $fromItemSet = true;
            $errorCode = INVALID_TESTCASE_EXTERNAL_ID;
            $msg = $messagePrefix . INVALID_TESTCASE_EXTERNAL_ID_STR;

            foreach( $itemSet as $tcaseExternalID ) {
                $tcaseE2I[$tcaseExternalID] = $tcaseIDSet[] = intval( $this->tcaseMgr->getInternalID( $tcaseExternalID ) );
            }
        }

        if($this->_isTestCaseExternalIDPresent()) {
            $fromExternal = true;
            $errorCode = INVALID_TESTCASE_EXTERNAL_ID;
            $msg = $messagePrefix . INVALID_TESTCASE_EXTERNAL_ID_STR;

            foreach( $this->args[self::$testCaseExternalIDParamName] as $tcaseExternalID ) {
                $tcaseIDSet[] = intval( $this->tcaseMgr->getInternalID( $tcaseExternalID ) );
            }
        }

        if($this->_isTestCaseIDPresent()) {
            $fromInternal = true;
            $errorCode = INVALID_TESTCASE_EXTERNAL_ID;
            $msg = $messagePrefix . INVALID_TCASEID_STR;
            $tcaseIDSet = $this->args[self::$testCaseIDParamName];
        }

        if(! is_null( $tcaseIDSet )) {
            $status_ok = true;
            foreach( $tcaseIDSet as $idx => $tcaseID ) {
                if((($tcaseID = intval( $tcaseID )) <= 0) ||(! $this->_isTestCaseIDValid( $tcaseID, $messagePrefix ))) {
                    $status_ok = false;

                    if($fromInternal) {
                        $this->errors[] = new IXR_Error( $errorCode, sprintf( $msg, $tcaseID ) );
                    } else {
                        if($fromItemSet) {
                            $tcaseExternalID = $itemSet[$idx];
                        } else {
                            $tcaseExternalID = $this->args[self::$testCaseExternalIDParamName][$idx];
                        }
                        $this->errors[] = new IXR_Error( $errorCode, sprintf( $msg, $tcaseExternalID ) );
                    }
                }
            }
        }

        if($status_ok) {
            $this->_setTestCaseID( $tcaseIDSet );
            $this->tcaseE2I = $tcaseE2I;
        }

        return $status_ok;
    }

    /**
     */
    private function getTcaseDbId($items) {
        $tcaseIDSet = null;
        foreach( $items as $idx => $eID ) {
            $tcaseIDSet[$idx] = intval( $this->tcaseMgr->getInternalID( $eID ) );
        }
        return $tcaseIDSet;
    }

    /**
     * Delete a test project and all related link to other items
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["prefix"]
     *
     * @return mixed $resultInfo
     *        [status] => true/false of success
     *        [message] => optional message for error message string
     * @access public
     */
    public function deleteTestProject($args) {
        $resultInfo = array();
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $resultInfo[0]["status"] = false;

        $checkFunctions = array(
                'authenticate'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $status_ok = $this->userHasRight( "mgt_modify_product" );
        }

        if($status_ok) {
            $status_ok = $this->_isParamPresent( self::$prefixParamName, $msg_prefix, true );
        }

        if($status_ok) {
            if(($info = $this->tprojectMgr->get_by_prefix( $this->args[self::$prefixParamName] ))) {
                $this->tprojectMgr->delete( $info['id'] );
                $resultInfo[0]["status"] = true;
            } else {
                $status_ok = false;
                $msg = $msg_prefix . sprintf( TPROJECT_PREFIX_DOESNOT_EXIST_STR, $this->args[self::$prefixParamName] );
                $this->errors[] = new IXR_Error( TPROJECT_PREFIX_DOESNOT_EXIST, $msg );
            }
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * Update value of Custom Field with scope='design'
     * for a given Test Suite
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["testsuiteid"]:
     * @param string $args["testprojectid"]:
     * @param string $args["customfields"]
     *            contains an map with key:Custom Field Name, value: value for CF.
     *            VERY IMPORTANT: value must be formatted in the way it's written to db,
     *            this is important for types like:
     *
     *            DATE: strtotime()
     *            DATETIME: mktime()
     *            MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore
     *
     *
     *            these custom fields must be configured to be writte during execution.
     *            If custom field do not meet condition value will not be written
     *
     * @return mixed null if everything ok, else array of IXR_Error objects
     *
     * @access public
     */
    public function updateTestSuiteCustomFieldDesignValue($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID',
                'checkTestSuiteID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            if(! $this->_isParamPresent( self::$customFieldsParamName )) {
                $status_ok = false;
                $msg = sprintf( MISSING_REQUIRED_PARAMETER_STR, self::$customFieldsParamName );
                $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
            }
        }

        if($status_ok) {
            // now check if custom fields are ok
            // For each custom field need to check if:
            // 1. is linked to test project
            // 2. is available for Test Suite at design time
            $cfieldMgr = new cfield_mgr( $this->dbObj );

            // Just ENABLED
            $linkedSet = $cfieldMgr->get_linked_cfields_at_design( $this->args[self::$testProjectIDParamName], cfield_mgr::ENABLED, null, 'testsuite', null, 'name' );
            if(is_null( $linkedSet )) {
                $status_ok = false;
                $msg = NO_CUSTOMFIELDS_DT_LINKED_TO_TESTSUITES_STR;
                $this->errors[] = new IXR_Error( NO_CUSTOMFIELDS_DT_LINKED_TO_TESTSUITES, $msg );
            }
        }

        if($status_ok) {
            $cfSet = $args[self::$customFieldsParamName];
            $itemID = $args[self::$testSuiteIDParamName];

            foreach( $cfSet as $cfName => $cfValue ) {
                // $accessKey = "custom_field_" . $item['id'] . <field_type_id>_<cfield_id>
                // design_values_to_db($hash,$node_id,$cf_map=null,$hash_type=null)
                //
                // Simple check: if name is not present on set => ignore
                if(isset( $linkedSet[$cfName] )) {
                    $item = $linkedSet[$cfName];
                    $accessKey = "custom_field_" . $item['type'] . '_' . $item['id'];
                    $hash[$accessKey] = $cfValue;
                    $cfieldMgr->design_values_to_db( $hash, $itemID );
                    $ret[] = array(
                            'status' => 'ok',
                            'msg' => 'Custom Field:' . $cfName . ' processed '
                    );
                } else {
                    $ret[] = array(
                            'status' => 'ko',
                            'msg' => 'Custom Field:' . $cfName . ' skipped '
                    );
                }

                return $ret;
            }
        } else {
            return $this->errors;
        }
    }

    /**
     * Update value of Custom Field with scope='design'
     * for a given Build
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["buildid"]:
     * @param string $args["testprojectid"]:
     * @param string $args["customfields"]
     *            contains an map with key:Custom Field Name, value: value for CF.
     *            VERY IMPORTANT: value must be formatted in the way it's written to db,
     *            this is important for types like:
     *
     *            DATE: strtotime()
     *            DATETIME: mktime()
     *            MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore
     *
     *
     *            these custom fields must be configured to be writte during execution.
     *            If custom field do not meet condition value will not be written
     *
     * @return mixed null if everything ok, else array of IXR_Error objects
     *
     * @access public
     */
    public function updateBuildCustomFieldsValues($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID',
                'checkBuildID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            if(! $this->_isParamPresent( self::$customFieldsParamName )) {
                $status_ok = false;
                $msg = sprintf( MISSING_REQUIRED_PARAMETER_STR, self::$customFieldsParamName );
                $this->errors[] = new IXR_Error( MISSING_REQUIRED_PARAMETER, $msg );
            }
        }

        if($status_ok) {
            // now check if custom fields are ok
            // For each custom field need to check if:
            // 1. is linked to test project
            // 2. is available for Build at design time
            $cfieldMgr = new cfield_mgr( $this->dbObj );

            // Just ENABLED
            $linkedSet = $cfieldMgr->get_linked_cfields_at_design( $this->args[self::$testProjectIDParamName], cfield_mgr::ENABLED, null, 'build', null, 'name' );
            if(is_null( $linkedSet )) {
                $status_ok = false;
                $msg = NO_CUSTOMFIELDS_DT_LINKED_TO_BUILDS_STR;
                $this->errors[] = new IXR_Error( NO_CUSTOMFIELDS_DT_LINKED_TO_BUILDS, $msg );
            }
        }

        if($status_ok) {
            $cfSet = $args[self::$customFieldsParamName];
            $ret = array();
            foreach( $cfSet as $cfName => $cfValue ) {
                // $accessKey = "custom_field_" . $item['id'] . <field_type_id>_<cfield_id>
                // design_values_to_db($hash,$node_id,$cf_map=null,$hash_type=null)
                //
                // Simple check: if name is not present on set => ignore
                if(isset( $linkedSet[$cfName] )) {
                    $item = $linkedSet[$cfName];
                    $accessKey = "custom_field_" . $item['type'] . '_' . $item['id'];
                    $hash[$accessKey] = $cfValue;
                    $cfieldMgr->design_values_to_db( $hash, $args[self::$buildIDParamName], null, null, 'build' );
                    // Add the result for each custom field to the returned array
                    array_push( $ret, array(
                            'status' => 'ok',
                            'msg' => 'Custom Field:' . $cfName . ' processed '
                    ) );
                } else {
                    array_push( $ret, array(
                            'status' => 'ko',
                            'msg' => 'Custom Field:' . $cfName . ' skipped '
                    ) );
                }
            }
            // Return the result after all of the fields have been processed
            return $ret;
        } else {
            return $this->errors;
        }
    }

    /**
     * Returns all test suites inside target
     * test project with target name
     *
     * @param
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["testsuitename"]
     * @param string $args["prefix"]
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function getTestSuite($args) {
        $ope = __FUNCTION__;
        $msg_prefix = "({$ope}) - ";

        $this->_setArgs( $args );
        $status_ok = $this->_runChecks( array(
                'authenticate'
        ), $msg_prefix );

        if($status_ok) {
            // Check for mandatory parameters
            $k2s = array(
                    self::$testSuiteNameParamName,
                    self::$prefixParamName
            );

            foreach( $k2s as $target ) {
                $ok = $this->_isParamPresent( $target, $msg_prefix, self::SET_ERROR );
                $status_ok = $status_ok && $ok;
            }
        }

        if($status_ok) {
            // optionals
            // $details='simple';
            // $k2s=self::$detailsParamName;
            // if( $this->_isParamPresent($k2s) )
            // {
            // $details = $this->args[$k2s];
            // }
        }

        if($status_ok) {
            $tprojectMgr = new testproject( $this->dbObj );

            $pfx = $this->args[self::$prefixParamName];
            $tproj = $tprojectMgr->get_by_prefix( $pfx );

            if(is_null( $tproj )) {
                $status_ok = false;
                $msg = $msg_prefix . sprintf( TPROJECT_PREFIX_DOESNOT_EXIST_STR, $pfx );
                $this->errors[] = new IXR_Error( TPROJECT_PREFIX_DOESNOT_EXIST, $msg );
            } else {
                $ctx[self::$testProjectIDParamName] = $dummy['id'];
            }
        }

        if($status_ok && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR, $ctx )) {
            $opt = array(
                    'recursive' => false,
                    'exclude_testcases' => true
            );

            // $target = $this->dbObj->prepare_string($tg);
            // $filters['additionalWhereClause'] =
            // " AND name = '{$target}' ";
            $filters = null;
            $items = $tprojectMgr->get_subtree( $tproj['id'], $filters, $opt );

            $ni = array();
            if(! is_null( $items ) &&($l2d = count( $items )) > 0) {
                $tg = $this->args[self::$testSuiteNameParamName];
                for($ydx = 0; $ydx <= $l2d; $ydx ++) {
                    if(strcmp( $items[$ydx]['name'], $tg ) == 0) {
                        unset( $items[$ydx]['tcversion_id'] );
                        $ni[] = $items[$ydx];
                    }
                }
            } else {
                $ni = $items;
            }
        }

        return $status_ok ? $ni : $this->errors;
    } // function end

    /**
     * Get Issue Tracker System by name
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["itsname"]
     *            ITS name
     * @return mixed $itsObject
     * @access public
     */
    public function getIssueTrackerSystem($args, $call = null) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );

        $extCall = is_null( $call );
        if($extCall) {
            $this->authenticate();
        }

        $ret = null;
        if(is_null( $this->itsMgr )) {
            $this->itsMgr = new tlIssueTracker( $this->dbObj );
        }

        $ret = $this->itsMgr->getByName( $this->args[self::$itsNameParamName] );
        $status_ok = ! is_null( $ret );
        if(! $status_ok) {
            $msg = $msg_prefix . sprintf( ITS_NOT_FOUND_STR, $this->args[self::$itsNameParamName] );
            $this->errors[] = new IXR_Error( ITS_NOT_FOUND, $msg );
        }

        if($extCall) {
            if(! $status_ok) {
                $ret = $this->errors;
            }
        }
        return $ret;
    }

    /**
     */
    function validateDateISO8601($dateAsString) {
        return $this->validateDate( $dateAsString );
    }

    /**
     */
    function validateDate($dateAsString, $format = 'Y-m-d') {
        $d = DateTime::createFromFormat( $format, $dateAsString );
        return $d && $d->format( $format ) == $dateAsString;
    }

    /**
     * Get requirements
     *
     * @param string $args["testprojectid"]
     * @param string $args["testplanid"]
     *            OPTIONAL
     * @param string $args["platformid"]
     *            OPTIONAL
     *
     * @return mixed error if someting's wrong, else array of test cases
     *
     * @access public
     */
    public function getRequirements($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );

        if($status_ok) {
            $context['tproject_id'] = $this->args[self::$testProjectIDParamName];

            // check if a context(test plan/platform) is provided
            if($this->_isParamPresent( self::$testPlanIDParamName )) {
                $status_ok = $this->checkTestPlanID( $msg_prefix );
                $context['tplan_id'] = $this->args[self::$testPlanIDParamName];

                if($status_ok) {
                    if($this->_isParamPresent( self::$platformIDParamName )) {
                        $status_ok = $this->checkPlatformIdentity( $this->args[self::$testPlanIDParamName], null, $msg_prefix );
                        $context['platform_id'] = $this->args[self::$platformIDParamName];
                    }
                }
            }
        }

        if($status_ok) {
            $dummy = $this->reqMgr->getAllByContext( $context );
            if(! is_null( $dummy ))
                $req = array_values( $dummy );
            else
                $status_ok = false;
        }

        return $status_ok ? $req : $this->errors;
    }

    /**
     * Get requirement coverage
     *
     * Retrieve the test cases associated to a requirement
     *
     * @param struct $args
     * @param string $args["devKey"]:
     *            used to check if operation can be done.
     *            if devKey is not valid => abort.
     *
     * @param string $args["testprojectid"]
     * @param string $args["requirementdocid"]
     *
     * @return mixed error if someting's wrong, else array of test cases
     *
     * @access public
     */
    public function getReqCoverage($args) {
        $msg_prefix = "(" . __FUNCTION__ . ") - ";
        $this->_setArgs( $args );

        $resultInfo = array();
        $checkFunctions = array(
                'authenticate',
                'checkTestProjectID'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->userHasRight( 'mgt_view_req', self::CHECK_PUBLIC_PRIVATE_ATTR );

        if($status_ok) {
            // check req id exists in the project
            $reqDocID = $this->args[self::$requirementDocIDParamName];
            $req = $this->reqMgr->getByDocID( $reqDocID, $this->args[self::$testProjectIDParamName], null, array(
                    'access_key' => 'req_doc_id',
                    'output' => 'minimun'
            ) );
            if(! is_null( $req )) {
                $resultInfo = $this->reqMgr->get_coverage( $req[$reqDocID]['id'] );
            } else {
                $msg = $msg_prefix . sprintf( NO_REQ_IN_THIS_PROJECT_STR, $reqDocID, $this->args[self::$testProjectIDParamName] );
                $this->errors[] = new IXR_Error( NO_REQ_IN_THIS_PROJECT, $msg );
                $status_ok = false;
            }
        }
        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param string $args["testcaseexternalid"]
     *            format PREFIX-NUMBER
     * @param int $args["testsuiteid"]
     *
     */
    public function setTestCaseTestSuite($args) {
        // Check test case identity
        // Check if user(devkey) has grants to do operation
        //
        $ret[] = array(
                "operation" => __FUNCTION__,
                "status" => true,
                "message" => GENERAL_SUCCESS_STR
        );

        $operation = $ret['operation'];
        $msgPrefix = "({$operation}) - ";
        $debug_info = null;

        $this->_setArgs( $args );
        $checkFunctions = array(
                'authenticate',
                'checkTestCaseIdentity',
                'checkTestSuiteID'
        );

        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix );
        if($status_ok) {
            // Test Case & Test Suite belongs to same Test Project?
            $tcaseTProj = $this->args[self::$testProjectIDParamName] = intval( $this->tcaseMgr->getTestProjectFromTestCase( $this->args[self::$testCaseIDParamName], null ) );

            $tsuiteMgr = new testsuite( $this->dbObj );
            $tsuite_id = $this->args[self::$testSuiteIDParamName];
            $tsuiteTProj = intval( $tsuiteMgr->getTestProjectFromTestSuite( $tsuite_id, null ) );

            $status_ok =($tcaseTProj == $tsuiteTProj);
            if(! $status_ok) {
                $msg = $msgPrefix . TSUITE_NOT_ON_TCASE_TPROJ_STR;
                $this->errors[] = new IXR_Error( TSUITE_NOT_ON_TCASE_TPROJ, $msg );
            }
        }

        if($status_ok) {
            $ctx[self::$testProjectIDParamName] = $tcaseTProj;
            $ck = self::CHECK_PUBLIC_PRIVATE_ATTR;
            $r2c = array(
                    'mgt_modify_tc'
            );
            foreach( $r2c as $right ) {
                $status_ok = $this->userHasRight( $right, $ck, $ctx );
                if(! $status_ok) {
                    break;
                }
            }
        }

        if($status_ok) {

            $sql = "/* " . __FUNCTION__ . " */" . " UPDATE " . $this->tables['nodes_hierarchy'] . " SET parent_id=" . $tsuite_id . " WHERE id=" . $this->args['testcaseid'];
            $this->dbObj->exec_query( $sql );
        }

        return $status_ok ? $ret : $this->errors;
    }

    /**
     * Gets a set of EXECUTIONS for a particular testcase on a test plan.
     * If there are no filter criteria regarding platform and build,
     * result will be get WITHOUT checking for a particular platform and build.
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["tplanid"]
     * @param int $args["testcaseid"]:
     *            Pseudo optional.
     *            if is not present then testcaseexternalid MUST BE present
     *
     * @param int $args["testcaseexternalid"]:
     *            Pseudo optional.
     *            if is not present then testcaseid MUST BE present
     *
     * @param string $args["platformid"]:
     *            optional.
     *            ONLY if not present, then $args["platformname"]
     *            will be analized(if exists)
     *
     * @param string $args["platformname"]:
     *            optional(see $args["platformid"])
     * @param int $args["buildid"]:
     *            optional
     *            ONLY if not present, $args["buildname"] will be analized(if exists)
     *
     * @param int $args["buildname"]
     *            - optional(see $args["buildid"])
     * @param int $args["options"]
     *            - optional
     *            options['getOrderDescending']
     *            false(=ascending,default)
     * @return mixed $resultInfo
     *         if execution found
     *         array that contains a map with these keys:
     *         id(execution id),build_id,tester_id,execution_ts,
     *         status,testplan_id,tcversion_id,tcversion_number,
     *         execution_type,notes.
     *
     *         if test case has not been executed,
     *         array('id' => -1)
     * @access public
     */
    public function getExecutionSet($args) {
        $operation = __FUNCTION__;
        $msg_prefix = "({$operation}) - ";

        $this->_setArgs( $args );
        $resultInfo = array();
        $status_ok = true;

        $opt = new stdClass();
        $opt->getOrderDescending = 0;

        // Checks are done in order
        $checkFunctions = array(
                'authenticate',
                'checkTestPlanID',
                'checkTestCaseIdentity'
        );

        $status_ok = $this->_runChecks( $checkFunctions, $msg_prefix ) && $this->_checkTCIDAndTPIDValid( null, $msg_prefix ) && $this->userHasRight( "mgt_view_tc", self::CHECK_PUBLIC_PRIVATE_ATTR );

        $tplan_id = $this->args[self::$testPlanIDParamName];
        $tcase_id = $this->args[self::$testCaseIDParamName];
        $execContext = array(
                'tplan_id' => $tplan_id,
                'platform_id' => null,
                'build_id' => null
        );

        if($status_ok) {
            if($this->_isParamPresent( self::$optionsParamName, $msg_prefix )) {
                $dummy = $this->args[self::$optionsParamName];
                if(is_array( $dummy )) {
                    foreach( $dummy as $key => $value ) {
                        $opt->$key =($value > 0) ? 1 : 0;
                    }
                }
            }

            // Now we can check for Optional parameters
            if($this->_isBuildIDPresent() || $this->_isBuildNamePresent()) {
                if(($status_ok = $this->checkBuildID( $msg_prefix ))) {
                    $execContext['build_id'] = $this->args[self::$buildIDParamName];
                }
            }

            if($status_ok) {
                if($this->_isParamPresent( self::$platformIDParamName, $msg_prefix ) || $this->_isParamPresent( self::$platformNameParamName, $msg_prefix )) {
                    $status_ok = $this->checkPlatformIdentity( $tplan_id );
                    if($status_ok) {
                        $execContext['platform_id'] = $this->args[self::$platformIDParamName];
                    }
                }
            }
        }

        if($status_ok) {
            $sql = " SELECT * FROM  {$this->tables['executions']} WHERE id " . " IN(SELECT id AS exec_id FROM {$this->tables['executions']} " . "     WHERE testplan_id = {$tplan_id} " . "     AND tcversion_id " . "     IN( SELECT id FROM {$this->tables['nodes_hierarchy']} " . "          WHERE parent_id = {$tcase_id} )";

            if(! is_null( $execContext['build_id'] )) {
                $sql .= " AND build_id = " . intval( $execContext['build_id'] );
            }

            if(! is_null( $execContext['platform_id'] )) {
                $sql .= " AND platform_id = " . intval( $execContext['platform_id'] );
            }

            // closing bracket for 1st level SELECT
            $sql .= ")";

            $sql .= " ORDER BY id ";
            $sql .=($opt->getOrderDescending) ? " DESC" : " ASC";

            $rs = $this->dbObj->fetchRowsIntoMap( $sql, 'id' );
            if(is_null( $rs )) {
                // has not been executed
                // execution id = -1 => test case has not been runned.
                $resultInfo[] = array(
                        'id' => - 1
                );
            } else {
                $resultInfo = $rs;
            }
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * Close build
     *
     * @param struct $args
     * @param string $args["devKey"]
     * @param int $args["buildid"]
     *
     * @return mixed $resultInfo
     *
     * @access public
     */
    public function closeBuild($args) {
        $operation = __FUNCTION__;
        $messagePrefix = "({$operation}) - ";

        $resultInfo = array();

        $resultInfo[0]["id"] = 0;
        $resultInfo[0]["status"] = true;
        $resultInfo[0]["operation"] = $operation;
        $resultInfo[0]["message"] = GENERAL_SUCCESS_STR;

        $this->_setArgs( $args );

        $checkFunctions = array(
                'authenticate'
        );
        $status_ok = $this->_runChecks( $checkFunctions, $messagePrefix );

        if($status_ok) {
            $status_ok = $this->_isParamPresent( self::$buildIDParamName, $messagePrefix, self::SET_ERROR );
        }

        if($status_ok) {
            $buildID = $this->args[self::$buildIDParamName];
            if(!($status_ok = is_int( $buildID ))) {
                $msg = sprintf( BUILDID_NOT_INTEGER_STR, $buildID );
                $this->errors[] = new IXR_Error( BUILDID_NOT_INTEGER, $msg );
            }
        }

        if($status_ok) {
            // Get Test Plan ID from Build ID in order to check rights
            $bm = new build_mgr( $this->dbObj );

            $buildID = intval( $this->args[self::$buildIDParamName] );
            $opx = array(
                    'output' => 'fields',
                    'fields' => 'id,testplan_id'
            );
            $buildInfo = $bm->get_by_id( $buildID, $opx );

            if($buildInfo == false || count( $buildInfo ) == 0) {
                $status_ok = false;
                $msg = sprintf( INVALID_BUILDID_STR, $buildID );
                $this->errors[] = new IXR_Error( INVALID_BUILDID, $msg );
            }
        }

        if($status_ok) {
            $context = array();
            $context[self::$testPlanIDParamName] = $buildInfo['testplan_id'];

            $status_ok = $this->userHasRight( "testplan_create_build", self::CHECK_PUBLIC_PRIVATE_ATTR, $context );
        }

        if($status_ok) {
            $bm->setClosed( $buildID );
            $resultInfo[0]["id"] = $buildID;
        }

        return $status_ok ? $resultInfo : $this->errors;
    }

    /**
     * update result of LATEST execution for each
     * step.
     *
     * If step is not present, exec status will not be changed.
     *
     * @access protected
     */
    protected function _updateStepsResult($execID = null) {
        $tcversion_id = $this->tcVersionID;
        $tcase_id = $this->args[self::$testCaseIDParamName];

        $stepExecStatus = $this->args[self::$stepsParamName];

        $exec_id = $execID;

        if(is_null( $exec_id )) {
            $execContext = array(
                    'tplan_id' => $this->args[self::$testPlanIDParamName],
                    'platform_id' => $this->args[self::$platformIDParamName],
                    'build_id' => $this->args[self::$buildIDParamName]
            );

            $opt = array(
                    'output' => 'exec_id'
            );
            $identity = array(
                    'id' => $tcase_id,
                    'version_id' => $tcversion_id
            );
            $exec_id = $this->tcaseMgr->getLatestExecSingleContext( $identity, $execContext, $opt );
        }

        if(! is_null( $exec_id )) {
            $exec_id = intval( $exec_id );
            $execution_type = constant( "TESTCASE_EXECUTION_TYPE_AUTO" );

            $st = &$this->args[self::$stepsParamName];

            // needed to get tcstep_id from step number
            $r2d2 = array(
                    'fields2get' => 'TCSTEPS.step_number,TCSTEPS.id',
                    'accessKey' => 'step_number',
                    'renderGhostSteps' => false,
                    'renderImageInline' => false
            );

            $steps = $this->tcaseMgr->getStepsSimple( $this->tcVersionID, 0, $r2d2 );

            $xx = null;
            foreach( $st as $spdata ) {
                $dbField = null;
                $dbVal = null;
                foreach( $spdata as $keyF => $value ) {
                    switch($keyF) {
                        case 'result' :
                            $status = strtolower( trim( $spdata['result'] ) );
                            $dbField[] = 'status';
                            $dbVal[] = "'" . $status[0] . "'";
                            break;

                        case 'notes' :
                            $dbField[] = $keyF;
                            $dbVal[] = "'" . $this->dbObj->prepare_string( $spdata[$keyF] ) . "'";
                            break;
                    }
                }

                $spnum = $spdata['step_number'];
                if(! is_null( $dbField ) && isset( $steps[$spnum] )) {

                    $target = DB_TABLE_PREFIX . 'execution_tcsteps';
                    $where = " WHERE execution_id=" . $exec_id . " AND " . " tcstep_id=" . intval( $steps[$spnum]['id'] );

                    // Manage Insert
                    $sql = "SELECT id FROM $target $where";
                    $rs = $this->dbObj->get_recordset( $sql );

                    if(is_null( $rs ) or count( $rs ) != 1) {
                        $sql = " INSERT INTO $target(";

                        $dbField[] = 'tcstep_id';
                        $dbVal[] = intval( $steps[$spnum]['id'] );

                        $dbField[] = 'execution_id';
                        $dbVal[] = $exec_id;

                        foreach( $dbField as $idx => $kf ) {
                            if($idx > 0) {
                                $sql .= ',';
                            }
                            $sql .= $kf;
                        }
                        $sql .= ') VALUES( ';

                        foreach( $dbVal as $idx => $kv ) {
                            if($idx > 0) {
                                $sql .= ',';
                            }
                            $sql .= $kv;
                        }
                        $sql .= ") ";
                    } else {
                        $sql = " UPDATE " . DB_TABLE_PREFIX . 'execution_tcsteps' . " SET ";
                        foreach( $dbField as $idx => $kf ) {
                            if($idx > 0) {
                                $sql .= ',';
                            }
                            $sql .= $kf . '=' . $dbVal[$idx];
                        }
                        $sql .= $where;
                    }

                    try {
                        $xx[] = $sql;
                        $this->dbObj->exec_query( $sql );
                    } catch( Exception $e ) {
                        return $e;
                    }
                }
            }
        }
        return $xx;
    }

    /**
     */
    function initMethodYellowPages() {
        $this->methods = array(
                'tl.reportTCResult' => 'this:reportTCResult',
                'tl.setTestCaseExecutionResult' => 'this:reportTCResult',
                'tl.createBuild' => 'this:createBuild',
                'tl.closeBuild' => 'this:closeBuild',
                'tl.createPlatform' => 'this:createPlatform',
                'tl.createTestCase' => 'this:createTestCase',
                'tl.createTestCaseSteps' => 'this:createTestCaseSteps',
                'tl.createTestPlan' => 'this:createTestPlan',
                'tl.createTestProject' => 'this:createTestProject',
                'tl.createTestSuite' => 'this:createTestSuite',
                'tl.deleteTestCaseSteps' => 'this:deleteTestCaseSteps',
                'tl.deleteTestPlan' => 'this:deleteTestPlan',
                'tl.deleteTestProject' => 'this:deleteTestProject',
                'tl.uploadExecutionAttachment' => 'this:uploadExecutionAttachment',
                'tl.uploadRequirementSpecificationAttachment' => 'this:uploadRequirementSpecificationAttachment',
                'tl.uploadRequirementAttachment' => 'this:uploadRequirementAttachment',
                'tl.uploadTestProjectAttachment' => 'this:uploadTestProjectAttachment',
                'tl.uploadTestSuiteAttachment' => 'this:uploadTestSuiteAttachment',
                'tl.uploadTestCaseAttachment' => 'this:uploadTestCaseAttachment',
                'tl.uploadAttachment' => 'this:uploadAttachment',
                'tl.assignRequirements' => 'this:assignRequirements',
                'tl.addTestCaseToTestPlan' => 'this:addTestCaseToTestPlan',
                'tl.addPlatformToTestPlan' => 'this:addPlatformToTestPlan',
                'tl.removePlatformFromTestPlan' => 'this:removePlatformFromTestPlan',
                'tl.getExecCountersByBuild' => 'this:getExecCountersByBuild',
                'tl.getIssueTrackerSystem' => 'this:getIssueTrackerSystem',
                'tl.getProjects' => 'this:getProjects',
                'tl.getProjectKeywords' => 'this:getProjectKeywords',
                'tl.getProjectPlatforms' => 'this:getProjectPlatforms',
                'tl.getProjectTestPlans' => 'this:getProjectTestPlans',
                'tl.getTestCaseAssignedTester' => 'this:getTestCaseAssignedTester',
                'tl.getTestCaseBugs' => 'this:getTestCaseBugs',
                'tl.getTestCaseKeywords' => 'this:getTestCaseKeywords',
                'tl.getTestProjectByName' => 'this:getTestProjectByName',
                'tl.getTestPlanByName' => 'this:getTestPlanByName',
                'tl.getTestPlanPlatforms' => 'this:getTestPlanPlatforms',
                'tl.getTotalsForTestPlan' => 'this:getTotalsForTestPlan',
                'tl.getBuildsForTestPlan' => 'this:getBuildsForTestPlan',
                'tl.getLatestBuildForTestPlan' => 'this:getLatestBuildForTestPlan',
                'tl.getLastExecutionResult' => 'this:getLastExecutionResult',
                'tl.getTestSuitesForTestPlan' => 'this:getTestSuitesForTestPlan',
                'tl.getTestSuitesForTestSuite' => 'this:getTestSuitesForTestSuite',
                'tl.getTestCasesForTestSuite' => 'this:getTestCasesForTestSuite',
                'tl.getTestCasesForTestPlan' => 'this:getTestCasesForTestPlan',
                'tl.getTestCaseIDByName' => 'this:getTestCaseIDByName',
                'tl.getTestCaseCustomFieldDesignValue' => 'this:getTestCaseCustomFieldDesignValue',
                'tl.getTestCaseCustomFieldExecutionValue' => 'this:getTestCaseCustomFieldExecutionValue',
                'tl.getTestCaseCustomFieldTestPlanDesignValue' => 'this:getTestCaseCustomFieldTestPlanDesignValue',
                'tl.getTestSuiteCustomFieldDesignValue' => 'this:getTestSuiteCustomFieldDesignValue',
                'tl.getTestPlanCustomFieldDesignValue' => 'this:getTestPlanCustomFieldDesignValue',
                'tl.getReqSpecCustomFieldDesignValue' => 'this:getReqSpecCustomFieldDesignValue',
                'tl.getRequirementCustomFieldDesignValue' => 'this:getRequirementCustomFieldDesignValue',
                'tl.getFirstLevelTestSuitesForTestProject' => 'this:getFirstLevelTestSuitesForTestProject',
                'tl.getTestCaseAttachments' => 'this:getTestCaseAttachments',
                'tl.getTestSuiteAttachments' => 'this:getTestSuiteAttachments',
                'tl.getTestCase' => 'this:getTestCase',
                'tl.getFullPath' => 'this:getFullPath',
                'tl.getTestSuiteByID' => 'this:getTestSuiteByID',
                'tl.getUserByLogin' => 'this:getUserByLogin',
                'tl.getUserByID' => 'this:getUserByID',
                'tl.deleteExecution' => 'this:deleteExecution',
                'tl.doesUserExist' => 'this:doesUserExist',
                'tl.updateTestCaseCustomFieldDesignValue' => 'this:updateTestCaseCustomFieldDesignValue',
                'tl.updateTestCase' => 'this:updateTestCase',
                'tl.setTestCaseExecutionType' => 'this:setTestCaseExecutionType',
                'tl.assignTestCaseExecutionTask' => 'this:assignTestCaseExecutionTask',
                'tl.unassignTestCaseExecutionTask' => 'this:unassignTestCaseExecutionTask',
                'tl.addTestCaseKeywords' => 'this:addTestCaseKeywords',
                'tl.removeTestCaseKeywords' => 'this:removeTestCaseKeywords',
                'tl.updateTestSuiteCustomFieldDesignValue' => 'this:updateTestSuiteCustomFieldDesignValue',
                'tl.updateBuildCustomFieldsValues' => 'this:updateBuildCustomFieldsValues',
                'tl.getTestSuite' => 'this:getTestSuite',
                'tl.updateTestSuite' => 'this:updateTestSuite',
                'tl.getRequirements' => 'this:getRequirements',
                'tl.getReqCoverage' => 'this:getReqCoverage',
                'tl.setTestCaseTestSuite' => 'this:setTestCaseTestSuite',
                'tl.getExecutionSet' => 'this:getExecutionSet',
                'tl.checkDevKey' => 'this:checkDevKey',
                'tl.about' => 'this:about',
                'tl.testLinkVersion' => 'this:testLinkVersion',
                'tl.setTestMode' => 'this:setTestMode',
                'tl.ping' => 'this:sayHello',
                'tl.sayHello' => 'this:sayHello',
                'tl.repeat' => 'this:repeat',
                'tl.getAllExecutionsResults' => 'this:getAllExecutionsResults'
        );
    }
} // class end
