iMIS displays generic error when user attempts to download uploaded file

While uploading and linking to PDF files in RiSE with iMIS version 20.2.65.9955, I encountered an interesting bug, but I also identified a workaround. Today, I’ll share both the bug and the workaround here.

The particular page with which I was working uses the Content Collection Organizer iPart to display content from other content records within tabs. I observed that if I create a link to a PDF that has been uploaded in RiSE in the content record for one of the tab content areas, or subpages if you like, then publish the record, the website displays a generic error when I click the link to download the PDF:

An unexpected iMIS error has occurred. Please try your operation again; if you still receive an error, contact the system administrator.

That’s not very helpful, so I took a look at Event Viewer on the server and noted an HttpException with the following message:

Exception message: Failed to load viewstate. The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate during the previous request. For example, when adding controls dynamically, the controls added during a post-back must match the type and position of the controls added during the initial request.

Interesting. Something’s happening in the iMIS/RiSE back-end code, then, which I can’t modify.

I did identify a workaround, however. If I create a download link in the main content record and publish that record, the links in the tabbed areas then function normally! Creating a standard link (e.g., with an href value of “#” or “/”) does not make this work correctly; the link must be in the format that RiSE uses when you link to a PDF that was uploaded to RiSE—i.e., with an href value like “javascript://[*]”.

The link apparently does not have to contain any text, however; it simply must exist. The presence of the following in the main page’s content record is sufficient:

<a href="javascript://[]"></a>

The link is not visible to the user because there’s no text, but it is the “magic sauce” that makes the PDF links within the tab content function as expected.

Using PHP and curl to post JSON data to the iMIS API

As a programming challenge, I recently decided to tackle using PHP and curl to connect to the iMIS API from outside the confines of RiSE. It’s relatively simple to get data from the iMIS API when you’re already logged in to an iMIS website, but I wanted to figure out how to post data to the API from an entirely different server. Documentation refers to this as direct access.

For my experiment, I created a PHP file on an external server. From a webpage within an instance of iMIS, I posted JSON data to my PHP file, which in turn retrieved an authorization token from iMIS and then used that token to submit the data to to the API.

<?php


// full URL of iMIS site
$url = "https://www.example.org";

// iMIS user's credentials
$username = "testuser";
$password = "testpassword";


if ($_SERVER["REQUEST_METHOD"] == "POST") {

    // JSON submitted by POST
    $json = file_get_contents("php://input");
    
    // ensure API URL and JSON are defined
    if ($_REQUEST["url"] != null && $json != null) {
    
        // address from which we get a token
        $tokenURL = $url . "/token";
        // API address to which we post data
        $apiURL = $url . "/api" . $_REQUEST["url"];
        
        callAPI($tokenURL, $username, $password, $apiURL, $json);
    } else {
    
        header("HTTP/1.0 401 Bad Request");
        
    $html = <<<EOT
<!DOCTYPE html>
<html lang="en-US">
    <head>
        <meta charset="utf-8">
        <title>401 Bad Request</title>
    </head>
    <body>
        <p>401 Bad Request</p>
    </body>
</html>
EOT;
        
        echo $html;
    }
}


// used to pass Ajax call to API
function callAPI($thisTokenURL, $thisUsername, $thisPassword, $thisAPIURL, $thisJSON) {

    // grab an authorization token to send to API with POST
    $token = getToken($thisTokenURL, $thisUsername, $thisPassword);
    
    // token length will be this short only if an HTTP error status code was returned
    if (strlen($token) < 5) {
        header("HTTP/1.0 " . $token);
    } else {
    
        // this is the header we will send to API
        $header = array("authorization: Bearer " . $token, "Content-Type: application/json");
        
        // initiate curl instance
        $curl = curl_init();
        
        curl_setopt_array($curl, array(
            CURLOPT_URL => $thisAPIURL,
            CURLOPT_HTTPHEADER => $header,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $thisJSON,
            CURLOPT_FAILONERROR => true
        ));
        
        $response = curl_exec($curl);
        
        // tell browser the result of the call
        header("HTTP/1.0 " . curl_getinfo($curl, CURLINFO_RESPONSE_CODE));
        
        curl_close($curl);
        
        return;
    }
}


// retrieve token for use in API call
function getToken($thisTokenURL, $thisUsername, $thisPassword) {

    // this is the username and password we will send
    $content = "grant_type=password&username=$thisUsername&password=$thisPassword";
    // this is the header we will send
    $header = array("Content-Type: application/x-www-form-urlencoded");
    
    $curl = curl_init();
    
    curl_setopt_array($curl, array(
        CURLOPT_URL => $thisTokenURL,
        CURLOPT_HTTPHEADER => $header,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $content,
        CURLOPT_FAILONERROR => true
    ));
    
    $response = curl_exec($curl);
    
    $json = null;
    $returnStr = "";
    
    // return HTTP status code if there was an error; otherwise, return token
    if (curl_errno($curl)) {
        $returnStr = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
    }
    else {
        $json = json_decode($response, true);
        $returnStr = $json["access_token"];
    }
    
    curl_close($curl);
    
    return $returnStr;
}


?>

Naturally, you wouldn’t use something unsecured like this in a production environment; with the iMIS credentials pre-populated, anyone who hit the page could submit data to the API with no questions asked! Definitely a no-go. In addition, this PHP code retrives a new token every time it runs; that token should be saved and re-used until it expires.

Nevertheless, figuring out how to make this work was an interesting exercise, and I was able to connect to the iMIS API from outside the confines of RiSE. Such knowledge could come in handy somewhere down the road.

iMIS API returns “An error occurred while constructing the query”

While working with business objects, IQA, and the API for iMIS 20.2.65.9955, I recently encountered a strange error or undocumented limitation that had me scratching my head for a bit until I figured out what was happening.

To summarize, I created a business object in RiSE, then used that business object to build an IQA query. I was able to run the IQA query and view the results within RiSE with no problems. I was also able to run the generated SQL query displayed on the IQA Summary tab directly against the database without encountering any errors.

When I attempted to use the iMIS API to retrieve the query results, however, the API returned the message, An error occurred while constructing the query. This didn’t make much sense to me since the query ran just fine within RiSE. What was going on?

After some experimentation, I determined that the API returns that error if the IQA query being called uses a business object with a name greater than 32 characters long. In other words, a business object named “KB1_MyBusinessObjectNameIsTooLong” will cause problems, but a business object named “KB1_MyBusinessObjNameIsJustRight” will not. Without knowing what the API is doing behind the scenes, I can’t explain exactly why this happens.

The solution is, of course, not to use business object names more than 32 characters long if you intend to retrieve the results of an IQA query using the iMIS API.