Moodle
  1. Moodle
  2. MDL-31082

Web service functions report errors even though they have worked. Perhaps because they are not checking returned values properly.

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Minor Minor
    • Resolution: Duplicate
    • Affects Version/s: 2.2
    • Fix Version/s: None
    • Component/s: Web Services
    • Labels:
      None
    • Environment:
      Not relevant.
    • Database:
      Any
    • Testing Instructions:
      Hide

      Run the PHP program that I've attached, after changing some values as explained in the comments at the top. I've also linked it from the URL field in this form.

      Show
      Run the PHP program that I've attached, after changing some values as explained in the comments at the top. I've also linked it from the URL field in this form.
    • Workaround:
      Hide

      I suspect that we can work round it by checking whether the XML returned by these functions contains this string: "Argument 1 passed to external_api::clean_returnvalue()
      must be an instance of external_description, null given, called in
      /var/www/moodle/moodle/webservice/rest/locallib.php on line 88 and
      defined". If it does, call other functions to check Moodle's state and see whether the call did in fact succeed.

      Show
      I suspect that we can work round it by checking whether the XML returned by these functions contains this string: "Argument 1 passed to external_api::clean_returnvalue() must be an instance of external_description, null given, called in /var/www/moodle/moodle/webservice/rest/locallib.php on line 88 and defined". If it does, call other functions to check Moodle's state and see whether the call did in fact succeed.
    • Affected Branches:
      MOODLE_22_STABLE
    • Rank:
      37507

      Description

      Moodle 2.2 reports errors even when Web-service functions have worked. See the discussion at http://moodle.org/mod/forum/discuss.php?d=193402 , "Am I calling core_role_assign_roles correctly? What do these errors mean?". I have discovered this to be a problem with core_user_delete_users, core_role_assign_roles, and enrol_manual_enrol_users. The message I get, as XML, is:
      <EXCEPTION class="coding_exception">
      <MESSAGE>Coding error detected, it must be fixed by a programmer:
      PHP catchable fatal error</MESSAGE>
      <DEBUGINFO>Argument 1 passed to external_api::clean_returnvalue()
      must be an instance of external_description, null given, called in
      /var/www/moodle/moodle/webservice/rest/locallib.php on line 88 and
      defined</DEBUGINFO>
      </EXCEPTION>
      It probably affects other functions that return values.

      My hypothesis, explained in detail in my January 7 2012 posting to the above thread, is that Moodle checks the values returned by Web-service functions against some kind of template, probably a specification of their allowed types, keys, and values. This check is failing, perhaps because the template is missing or corrupt. Consequently, the Web-service interface reports that the functions have returned something illegal.

      I suspect that the error is reported by the catch-all error trap at the end of the function clean_returnvalue in lib/externallib.php. That is, by this statement:
      throw new invalid_response_exception('Invalid external api response description');

      1. Bug1.php
        12 kB
        Jocelyn Ireson-Paine
      2. Bug1.php
        12 kB
        Jocelyn Ireson-Paine

        Issue Links

          Activity

          Jocelyn Ireson-Paine created issue -
          Jocelyn Ireson-Paine made changes -
          Field Original Value New Value
          Testing Instructions Run the program below, after changing the values noted in the comments at the top.

          <?php


          /*
          This program demonstrates a bug whereby Moodle 2.2
          reports errors even when Web-service functions have worked.
          See the discussion at http://moodle.org/mod/forum/discuss.php?d=193402 ,
          "Am I calling core_role_assign_roles correctly? What do these errors mean?".

          The 'curl' that it uses comes from
          https://github.com/moodlehq/sample-ws-clients/blob/master/PHP-REST/curl.php .

          The program uses four global variables which you may
          need to change. They are just under the definition of TRACING
          below:
            $token: Your Web services token. I manually create this in Moodle.
            $domain: Your Moodle URL.
            $user_suffix: An integer that the function for creating test users
                            appends to their names and other details. Change this
                            if you run the program more than once, in case the names
                            clash with users left over from previous runs.
            $course_suffix: The same for courses.

          You may also need to change the role ID constants MANAGER_ROLE_ID and
          STUDENT_ROLE_ID, and the context ID constant SYSTEM_CONTEXT_ID. I
          got these by querying my database. I don't know whether they're
          the same in all Moodles.

          The program is a cut-down version of the program I posted on January
          9th at http://moodle.org/mod/forum/discuss.php?d=193295 , "Example of using
          Web services". It creates a user, then assigns them the Manager
          role, then creates a course, then enrols the user in it, and
          then deletes them. In between these, I've called functions that
          display the data for this user and course ID, to try and show what
          Moodle thinks has happened to them. It appears that role assignment
          works, enrolment works, and deletion also works, even though all
          three report an error.

          The program is more complicated than strictly necessary. That's
          because I've left the XML parsing in, so that the functions that
          interrogate users and courses can work.
          */


          /* If true, the program displays each
             XML response returned by calling Moodle.
          */
          define( "TRACING", true );


          $token = '9bbdcddccb00480ce6c87fa8358c4a54';
          $user_suffix = 200;
          $course_suffix = 200;
          $domain = 'http://moodle.mazegreenyachts.com&#39;;


          /* Returns a structure defining
             a test user whose name, password, etc. end
             in $n.
          */
          function make_test_user( $n )
          {
            $user = new stdClass();
            $user->username = 'testusername' . $n;
            $user->password = 'testpassword' . $n;
            $user->firstname = 'testfirstname' . $n;
            $user->lastname = 'testlastname' . $n;
            $user->email = 'testemail' . $n . '@moodle.com';
            $user->auth = 'manual';
            $user->idnumber = 'testidnumber' . $n;
            $user->lang = 'en';
            $user->theme = 'standard';
            $user->timezone = '0';
            $user->mailformat = 0;
            $user->description = 'Hello World!';
            $user->city = 'testcity' . $n;
            $user->country = 'uk';
            return $user;
          }


          /* Returns a structure defining
             a test course whose name etc. end
             in $n.

             I have set the category ID to 1.
             This works, but is almost certainly wrong.
             I need to find out what it should be.
          */
          function make_test_course( $n )
          {
            $course = new stdClass();
            $course->fullname = 'testcourse' . $n;
            $course->shortname = 'testcourse' . $n;
            $course->categoryid = 1;
            return $course;
          }


          /* Creates a user from a
             structure defining a user. If the
             creation succeeds, returns the
             ID for this user. If not, reports
             an error.
          */
          function create_user( $user, $token )
          {
            $users = array( $user );
            $params = array( 'users' => $users );

            $response = call_moodle( 'core_user_create_users', $params, $token );

            if ( xmlresponse_is_exception( $response ) ) {
              echo "\nFunction returned an error.\n";
            }
            else {
              $user_id = xmlresponse_to_id( $response );
              return $user_id;
            }
          }


          /* Returns a user data structure containing Moodle's
             data for $user_id. It generates this by
             parsing the XML that Moodle returns. If Moodle
             thinks there is no such user, returns NULL.
          */
          function get_user( $user_id, $token )
          {
            $userids = array( $user_id );
            $params = array( 'userids' => $userids );

            $response = call_moodle( 'core_user_get_users_by_id', $params, $token );

            $user = xmlresponse_to_user( $response );

            if ( array_key_exists( 'id', $user ) )
              return $user;
            else
              return NULL;
            // If there is no user with this ID, Moodle
            // returns the same enclosing XML as if there were, but
            // with no values for ID and the other fields. My
            // XML-parsing code therefore creates an object
            // with no fields, which the conditional above
            // detects.
          }


          /* Deletes the user with ID $user_id.
          */
          function delete_user( $user_id, $token )
          {
            $userids = array( $user_id );
            $params = array( 'userids' => $userids );

            $response = call_moodle( 'core_user_delete_users', $params, $token );
          }


          /* Assigns the role with $role_id to the user with $user_id
             in the specified context.
          */
          function assign_role( $user_id, $role_id, $context_id, $token )
          {
            $assignment = array( 'roleid' => $role_id, 'userid' => $user_id, 'contextid' => $context_id );
            $assignments = array( $assignment );
            $params = array( 'assignments' => $assignments );

            $response = call_moodle( 'core_role_assign_roles', $params, $token );
          }


          /* Creates a course from a
             structure defining a course. If the
             creation succeeds, returns the
             ID for this course. If not, reports
             an error.
          */
          function create_course( $course, $token )
          {
            $courses = array( $course );
            $params = array( 'courses' => $courses );

            $response = call_moodle( 'core_course_create_courses', $params, $token );

            if ( xmlresponse_is_exception( $response ) ) {
              echo "\nFunction returned an error.\n";
            }
            else {
              $course_id = xmlresponse_to_id( $response );
              return $course_id;
            }
          }


          /* Enrols the user into the course with the specified role.
          */
          function enrol( $user_id, $course_id, $role_id, $token )
          {
            $enrolment = array( 'roleid' => $role_id, 'userid' => $user_id, 'courseid' => $course_id );
            $enrolments = array( $enrolment );
            $params = array( 'enrolments' => $enrolments );

            $response = call_moodle( 'enrol_manual_enrol_users', $params, $token );
          }


          /* Returns data about users enrolled in the specified course.
             Only works if there is only one user.
          */
          function get_enrolled_users( $course_id, $token )
          {
            $params = array( 'courseid' => $course_id );

            $response = call_moodle( 'core_enrol_get_enrolled_users', $params, $token );

            $user = xmlresponse_to_user( $response );
            return $user;
          }


          /* Calls the Moodle at $domain, invoking the specified
             function on $params. Also takes a token.
             Returns Moodle's response as a string containing XML.
          */
          function call_moodle( $function_name, $params, $token )
          {
            global $domain;

            $serverurl = $domain . '/webservice/rest/server.php'. '?wstoken=' . $token . '&wsfunction='.$function_name;

            require_once( './curl.php' );
            $curl = new curl;

            $response = $curl->post( $serverurl . $restformat, $params );

            if ( TRACING )
              echo "\nResponse from $function_name: \n", $response, ".\n";

            return $response;
          }


          /* Given a string containing XML returned
             by a successful user creation or course
             creation, parses it and returns the user or course ID
             as an integer.
             Undefined if the XML does not contain such an ID,
             for example if it's an error response.
          */
          function xmlresponse_to_id( $xml_string )
          {
            $xml_tree = new SimpleXMLElement( $xml_string );

            $value = $xml_tree->MULTIPLE->SINGLE->KEY->VALUE;
            $id = intval( sprintf( "%s", $value ) );
            // See discussion on http://php.net/manual/es/book.simplexml.php ,
            // especially the posting for "info at kevinbelanger dot com 20-Jan-2011 05:07".
            // There is a bug in the XML parser whereby it doesn't return the
            // text associated with property [0] of a node. The above
            // posting uses sprintf to force a conversion to string.

            return $id;
          }


          /* Given a string containing XML returned
             by a successful call to core_user_get_users_by_id,
             parses it and returns the data as a user
             data structure.
             Undefined if the XML does not contain such an ID,
             for example if it's an error response.
             Does not handle fields with multiple values.
             I think these are customfields, preferences,
             and enrolledcourses.
          */
          function xmlresponse_to_user( $xml_string )
          {
            return xmlresponse_parse_names_and_values( $xml_string );
          }


          /* This parses a string containing the XML returned by
             functions such as core_course_get_courses,
             core_user_get_users_by_id, or core_enrol_get_enrolled_users.
             These strings contain name-value pairs encoded thus:
               <RESPONSE>
               <MULTIPLE>
               <SINGLE>
               <KEY name="id"><VALUE>169</VALUE>
               </KEY>
               <KEY name="username"><VALUE>testusername32</VALUE>
               </KEY>
               </SINGLE>
               </MULTIPLE>
               </RESPONSE>
             The function returns an object with the corresponding
             keys and values.
             Does not convert strings to integers where they
             ought to be converted.
          */
          function xmlresponse_parse_names_and_values( $xml_string )
          {
            $xml_tree = new SimpleXMLElement( $xml_string );

            $struct = new StdClass();

            foreach ( $xml_tree->MULTIPLE->SINGLE->KEY as $key ) {
              $name = $key['name'];
              $value = (string)$key->VALUE;
              $struct->$name = $value;
            }

            return $struct;
          }


          /* True if $xml_string's top-level is
             <EXCEPTION>. I use this to check for error
             responses from Moodle.
          */
          function xmlresponse_is_exception( $xml_string )
          {
            $xml_tree = new SimpleXMLElement( $xml_string );

            $is_exception = $xml_tree->getName() == 'EXCEPTION';
            return $is_exception;
          }


          /* These are the manager and student roles from my Moodle,
             obtained by querying the database with
             the command
               select * from mdl_role;
          */
          define( "MANAGER_ROLE_ID", 1 );
          define( "STUDENT_ROLE_ID", 5 );


          /* This is the context ID from my Moodle,
             obtained by querying the database with
             the command
               select * from mdl_context.
          */
          define( "SYSTEM_CONTEXT_ID", 1 );


          /* Demonstrate the bug.
          */
          function demo()
          {
            global $token, $user_suffix, $course_suffix;

            try {
              echo "Demo of Moodle bug\n";
              echo "==================\n";

              echo "\nUses this token which I created manually: " . $token . ".\n";

              echo "\nWill now create a user from the following data:\n";
              $user_data = make_test_user( $user_suffix );
              print_r( $user_data ); print(".");
              echo "\n";

              $user_id = create_user( $user_data, $token );
              echo "\nUser's ID = " . $user_id . ".\n";

              echo "\nWill now assign a role to make user a manager.\n";
              assign_role( $user_id, MANAGER_ROLE_ID, SYSTEM_CONTEXT_ID, $token );

              echo "\nInspecting my Moodle's 'Assign system roles' showed that although 'enrol' gave an error, the user did receive this role.\n";

              echo "\nWill now create a course from the following data:\n";
              $course_data = make_test_course( $course_suffix );
              print_r( $course_data ); print(".\n");

              $course_id = create_course( $course_data, $token );
              echo "\nCourse ID = " . $course_id . ".\n";

              echo "\nWill now enrol user.\n";
              $role_id = STUDENT_ROLE_ID;
              enrol( $user_id, $course_id, $role_id, $token );

              echo "\nWill now get the ID of who Moodle thinks is enrolled.\n";
              $user_in_course = get_enrolled_users( $course_id, $token );
              echo "\nUser details = \n";
              print_r( $user_in_course ); print(".\n");

              echo "\nThis shows that on my Moodle, although 'enrol' gave an error, the user did get enrolled.\n";

              echo "\nWill now try deleting the user.\n";
              delete_user( $user_id, $token );

              echo "\nWill now get the user details using user's ID.\n";
              $user_data_from_moodle = get_user( $user_id, $token );
              echo "\nUser details = \n";
              print_r( $user_data_from_moodle ); print(".\n");

              echo "\nThis shows that on my Moodle, although 'delete' gave an error, the user no longer exists.\n";
            }
            catch ( Exception $e ) {
              echo "\nCaught exception:\n" . $e->getMessage() . "\n";
            }
          }


          demo();
          Run the PHP program that I've attached, after changing some values as explained in the comments at the top. I've also linked it from the URL field in this form.
          Jocelyn Ireson-Paine made changes -
          Description Moodle 2.2 reports errors even when Web-service functions have worked. See the discussion at http://moodle.org/mod/forum/discuss.php?d=193402 , "Am I calling core_role_assign_roles correctly? What do these errors mean?". I have discovered this to be a problem with core_user_delete_users, core_role_assign_roles, and enrol_manual_enrol_users. The message I get, as XML, is:
          <EXCEPTION class="coding_exception">
            <MESSAGE>Coding error detected, it must be fixed by a programmer:
              PHP catchable fatal error</MESSAGE>
            <DEBUGINFO>Argument 1 passed to external_api::clean_returnvalue()
              must be an instance of external_description, null given, called in
              /var/www/moodle/moodle/webservice/rest/locallib.php on line 88 and
              defined</DEBUGINFO>
            </EXCEPTION>
          It probably affects other functions that return values.

          My hypothesis, explained in detail in my January 7 2012 posting to this thread, is that Moodle checks the values returned by Web-service functions against some kind of template, probably a specification of their allowed types, keys, and values. This check is failing, perhaps because the template is missing or corrupt. Consequently, the Web-service interface reports that the functions have returned something illegal.

          I suspect that the error is reported by the catch-all error trap at the end of the function clean_returnvalue in lib/externallib.php. That is, by this statement:
            throw new invalid_response_exception('Invalid external api response description');
          Moodle 2.2 reports errors even when Web-service functions have worked. See the discussion at http://moodle.org/mod/forum/discuss.php?d=193402 , "Am I calling core_role_assign_roles correctly? What do these errors mean?". I have discovered this to be a problem with core_user_delete_users, core_role_assign_roles, and enrol_manual_enrol_users. The message I get, as XML, is:
          <EXCEPTION class="coding_exception">
            <MESSAGE>Coding error detected, it must be fixed by a programmer:
              PHP catchable fatal error</MESSAGE>
            <DEBUGINFO>Argument 1 passed to external_api::clean_returnvalue()
              must be an instance of external_description, null given, called in
              /var/www/moodle/moodle/webservice/rest/locallib.php on line 88 and
              defined</DEBUGINFO>
            </EXCEPTION>
          It probably affects other functions that return values.

          My hypothesis, explained in detail in my January 7 2012 posting to the above thread, is that Moodle checks the values returned by Web-service functions against some kind of template, probably a specification of their allowed types, keys, and values. This check is failing, perhaps because the template is missing or corrupt. Consequently, the Web-service interface reports that the functions have returned something illegal.

          I suspect that the error is reported by the catch-all error trap at the end of the function clean_returnvalue in lib/externallib.php. That is, by this statement:
            throw new invalid_response_exception('Invalid external api response description');
          Jocelyn Ireson-Paine made changes -
          Attachment Bug1.php [ 26327 ]
          Michael de Raadt made changes -
          Link This issue duplicates MDL-31077 [ MDL-31077 ]
          Michael de Raadt made changes -
          Status Open [ 1 ] Closed [ 6 ]
          Resolution Duplicate [ 3 ]

            People

            • Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: