PageRenderTime 49ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/sloodle/lib/io.php

http://sloodle.googlecode.com/
PHP | 1192 lines | 541 code | 130 blank | 521 comment | 193 complexity | ce6e1e824f01d7a84fb7fe644bc56d11 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. // This file is part of the Sloodle project (www.sloodle.org) and is released under the GNU GPL v3.
  3. /**
  4. * Sloodle input/output library.
  5. *
  6. * Provides general request and response functionality for interacting with in-world LSL scripts.
  7. *
  8. * @package sloodle
  9. * @copyright Copyright (c) 2007-8 Sloodle (various contributors)
  10. * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
  11. * @since Sloodle 0.2
  12. *
  13. * @contributor Peter R. Bloomfield
  14. *
  15. */
  16. // NOTE: this file requires that the Sloodle "config.php" file already be included
  17. /** Include our general library. */
  18. require_once(SLOODLE_DIRROOT . '/lib/general.php');
  19. /** Defines the HTTP parameter name for a Sloodle password. */
  20. define('SLOODLE_PARAM_PASSWORD', 'sloodlepwd');
  21. /** Defines the HTTP parameter name for a course ID. */
  22. define('SLOODLE_PARAM_COURSE_ID', 'sloodlecourseid');
  23. /** Defines the HTTP parameter name for a Sloodle controller ID. */
  24. define('SLOODLE_PARAM_CONTROLLER_ID', 'sloodlecontrollerid');
  25. /** Defines the HTTP parameter name for a module ID. */
  26. define('SLOODLE_PARAM_MODULE_ID', 'sloodlemoduleid');
  27. /** Defines the HTTP parameter name for an avatar UUID. */
  28. define('SLOODLE_PARAM_AVATAR_UUID', 'sloodleuuid');
  29. /** Defines the HTTP parameter name for an avatar name. */
  30. define('SLOODLE_PARAM_AVATAR_NAME', 'sloodleavname');
  31. /** Defines the HTTP parameter name for a request descriptor. */
  32. define('SLOODLE_PARAM_REQUEST_DESC', 'sloodlerequestdesc');
  33. /** Defines the HTTP parameter name for indicating a request which relates to an object instead of a user. */
  34. define('SLOODLE_PARAM_IS_OBJECT', 'sloodleisobject');
  35. /** Defines the HTTP parameter name for specifying server access level. */
  36. define('SLOODLE_PARAM_SERVER_ACCESS_LEVEL', 'sloodleserveraccesslevel');
  37. /**
  38. * A helper class to validate and structure data for output according to the {@link http://slisweb.sjsu.edu/sl/index.php/Sloodle_communications_specification Sloodle communications specification}.
  39. * @package sloodle
  40. */
  41. class SloodleResponse
  42. {
  43. // DATA //
  44. /**
  45. * The separation string between lines of the response (typically just a newline).
  46. * @var string
  47. * @access private
  48. */
  49. var $line_separator = "\n";
  50. /**
  51. * The separation string between fields of a response line (by default, a pipe character |).
  52. * @var string
  53. * @access private
  54. */
  55. var $field_separator = "|";
  56. /**
  57. * Integer status code of the response.
  58. * Refer to the {@link http://slisweb.sjsu.edu/sl/index.php/Sloodle_status_codes status codes} page on the Sloodle wiki for a reference.
  59. * <b>Required.</b>
  60. * @var int
  61. * @access private
  62. */
  63. var $status_code = null;
  64. /**
  65. * Status descriptor string.
  66. * Should contain a generalised description/category of the status code.
  67. * <b>Optional but recommended. Ignored if null.</b>
  68. * @var string
  69. * @access private
  70. */
  71. var $status_descriptor = null;
  72. /**
  73. * Integer side effect(s) codes.
  74. * Status code(s) of side effect(s) incurred during the operation.
  75. * Can be a single integer, or an array of integers.
  76. * <b>Optional. Ignored if null.</b>
  77. * @var mixed
  78. * @access private
  79. */
  80. var $side_effects = null;
  81. /**
  82. * Request descriptor.
  83. * A brief string passed into the request by an LSL script (via HTTP parameter 'sloodlerequestdesc'),
  84. * which is returned so that it can correctly distinguish one request from anotehr.
  85. * <b>Optional. Ignored if null.</b>
  86. * @var string
  87. * @access private
  88. */
  89. var $request_descriptor = null;
  90. /**
  91. * Timestamp when the request was originally made by the LSL script.
  92. * This is <i>not</i> filled-in automatically. You must do it manually if you need it.
  93. * <b>Optional. Ignored if null.</b>
  94. * @var integer
  95. * @access private
  96. */
  97. var $request_timestamp = null;
  98. /**
  99. * Timestamp when the response was generated on the Moodle site.
  100. * This is <i>not</i> filled-in automatically. You must do it manually if you need it.
  101. * <b>Optional. Ignored if null.</b>
  102. * @var integer
  103. * @access private
  104. */
  105. var $response_timestamp = null;
  106. /**
  107. * Avatar UUID.
  108. * Should be a string specifying the UUID key of the agent in-world being handled. (Typically of the user who initiated the request).
  109. * <b>Optional. Ignored if null.</b>
  110. * @var string
  111. * @access private
  112. */
  113. var $avatar_uuid = null;
  114. /**
  115. * Tracking code of the request.
  116. * Use of this value is undefined. Please do not use it.
  117. * <b>Optional. Ignored if null.</b>
  118. * @var mixed
  119. * @access private
  120. */
  121. var $tracking_code = null;
  122. /**
  123. * Total number of pages.
  124. * If a response requires multiple pages, this value indicates how many pages there are.
  125. * <b>Optional, unless $page_number is specified. Ignored if null.</b> <i>Not yet supported.</i>
  126. * @var integer
  127. * @access private
  128. */
  129. var $page_total = null;
  130. /**
  131. * Current page number.
  132. * If a response requires multiple pages, this value indicates which page is being returned in this response.
  133. * <b>Optional, unless $page_total is specified. Ignored if null.</b> <i>Not yet supported.</i>
  134. * @var integer
  135. * @access private
  136. */
  137. var $page_number = null;
  138. /**
  139. * Data to render following the status line in the response.
  140. * This value can either be a scalar (single value, e.g. int, string, float), or an array.
  141. * If it is a single scalar, it is rendered as a single line.
  142. * If it is an array, then each element becomes one line.
  143. * If an element is a scalar, then it is directly output onto the line.
  144. * If an element is an array, then each child element is output as a separate field on the same line.
  145. * <b>Optional. Ignored if null.</b>
  146. * @see SloodleResponse::set_data()
  147. * @see SloodleResponse::add_data_line()
  148. * @see SloodleResponse::clear_data()
  149. * @var mixed
  150. * @access private
  151. */
  152. var $data = null;
  153. // ACCESSORS //
  154. /**
  155. * Sets the line separator.
  156. * @param string $sep A string to separate lines
  157. * @return void
  158. */
  159. function set_line_separator($sep)
  160. {
  161. $this->line_separator = $sep;
  162. }
  163. /**
  164. * Gets the line separator.
  165. * @return string The current line separator string
  166. */
  167. function get_line_separator()
  168. {
  169. return $this->line_separator;
  170. }
  171. /**
  172. * Sets the field separator.
  173. * @param string $sep A string to separate fields
  174. * @return void
  175. */
  176. function set_field_separator($sep)
  177. {
  178. $this->field_separator = $sep;
  179. }
  180. /**
  181. * Gets the field separator.
  182. * @return string The current field separator string
  183. */
  184. function get_field_separator()
  185. {
  186. return $this->field_separator;
  187. }
  188. /**
  189. * Accessor function to set member value {@link $status_code}
  190. * @param integer $par A non-zero status code
  191. * @return void
  192. */
  193. function set_status_code($par)
  194. {
  195. // Validate
  196. if (is_int($par) == false || $par == 0) {
  197. $this->_internal_validation_error("Sloodle - LSL response: invalid status code specified; should be non-zero integer", 0);
  198. }
  199. // Store
  200. $this->status_code = $par;
  201. }
  202. /**
  203. * Accessor function to set member value {@link $status_descriptor}
  204. * @param mixed $par A status descriptor string, or null to clear it
  205. * @return void
  206. */
  207. function set_status_descriptor($par)
  208. {
  209. // Validate
  210. if (is_string($par) == false && is_null($par) == false) {
  211. $this->_internal_validation_error("Sloodle - LSL response: invalid status descriptor specified; should be a string or null", 0);
  212. } else {
  213. $this->status_descriptor = $par;
  214. }
  215. }
  216. /**
  217. * Accessor function to set member value {@link $side_effects}. <b>Note:</b> it is recommended that you use {@link add_side_effect()} or {@link add_side_effects()} instead.
  218. * @param mixed $par An integer side effect code, an array of integer side effect codes, or null to clear it
  219. * @return void
  220. */
  221. function set_side_effects($par)
  222. {
  223. // We'll use a variable to store the validity
  224. $valid = true;
  225. if (is_array($par)) {
  226. // Array types are acceptable
  227. // Make sure each array element is valid
  228. foreach ($par as $elem) {
  229. if (!is_int($elem)) $valid = false;
  230. }
  231. // Were all elements valid?
  232. if ($valid == false) {
  233. $this->_internal_validation_error("Sloodle - LSL response: invalid element in array of side effect codes; all elements should be integers", 0);
  234. }
  235. } else if (is_int($par) == false && is_null($par) == false) {
  236. // It's not an array, an integer or null
  237. $valid = false;
  238. $this->_internal_validation_error("Sloodle - LSL response: invalid side effect type; should be an integer, an array of integers, or null", 0);
  239. }
  240. // Was it valid?
  241. if ($valid) {
  242. $this->side_effects = $par;
  243. }
  244. }
  245. /**
  246. * Adds one or more integer side effect codes to member {@link $status_code}.
  247. * @param mixed $par An integer side effect code, or an array of them.
  248. * @return void
  249. */
  250. function add_side_effects($par)
  251. {
  252. // We'll use a variable to store the validity
  253. $valid = true;
  254. if (is_array($par)) {
  255. // Array types are acceptable
  256. // Make sure each array element is valid
  257. foreach ($par as $elem) {
  258. if (!is_int($elem)) $valid = false;
  259. }
  260. // Were all elements valid?
  261. if ($valid == false) {
  262. $this->_internal_validation_error("Sloodle - LSL response: cannot add side effects. Invalid element in array of side effect codes. All elements should be integers", 0);
  263. }
  264. } else if (is_int($par) == false) {
  265. // It's not an array or an integer
  266. $valid = false;
  267. $this->_internal_validation_error("Sloodle - LSL response: cannot add side effect. Invalid side effect type. should be an integer or an array of integers", 0);
  268. }
  269. // Was it valid?
  270. if ($valid) {
  271. // If we were passed just a single side effect, then convert it to an array
  272. if (is_int($par)) {
  273. $par = array($par);
  274. }
  275. // Make sure our existing side effect member is an array
  276. if (is_null($this->side_effects)) $this->side_effects = array();
  277. else if (is_int($this->side_effects)) $this->side_effects = array($this->side_effects);
  278. // Append our new side effect(s)
  279. foreach ($par as $cur) {
  280. $this->side_effects[] = $cur;
  281. }
  282. }
  283. }
  284. /**
  285. * Adds a single side effect code to member {@link $status_code}
  286. * @param integer $par An integer side-effect code.
  287. * @return void
  288. */
  289. function add_side_effect($par)
  290. {
  291. // Make sure the parameter is valid
  292. if (!is_int($par))
  293. $this->_internal_validation_error("Sloodle - LSL response: cannot add side effect. Invalid side effect type. Should be an integer.", 0);
  294. $this->add_side_effects($par);
  295. }
  296. /**
  297. * Accessor function to set member value {@link $request_descriptor}
  298. * @param mixed $par A string request descriptor, or null to clear it
  299. * @return void
  300. */
  301. function set_request_descriptor($par)
  302. {
  303. // Validate
  304. if (is_string($par) == false && is_null($par) == false) {
  305. $this->_internal_validation_error("Sloodle - LSL response: invalid request descriptor specified; should be a string or null", 0);
  306. } else {
  307. $this->request_descriptor = $par;
  308. }
  309. }
  310. /**
  311. * Accessor function to set member value {@link $request_timestamp}
  312. * @param mixed $par An integer timestamp, or null to clear it
  313. * @return void
  314. */
  315. function set_request_timestamp($par)
  316. {
  317. // Validate
  318. if (is_int($par) == false && is_null($par) == false) {
  319. $this->_internal_validation_error("Sloodle - LSL response: invalid request timestamp; should be an integer, or null", 0);
  320. } else {
  321. $this->request_timestamp = $par;
  322. }
  323. }
  324. /**
  325. * Accessor function to set member value {@link $response_timestamp}
  326. * @param mixed $par An integer timestamp, or null to clear it
  327. * @return void
  328. */
  329. function set_response_timestamp($par)
  330. {
  331. // Validate
  332. if (is_int($par) == false && is_null($par) == false) {
  333. $this->_internal_validation_error("Sloodle - LSL response: invalid response timestamp; should be an integer, or null", 0);
  334. } else {
  335. $this->response_timestamp = $par;
  336. }
  337. }
  338. /**
  339. * Accessor function to set member value {@link $avatar_uuid}
  340. * @param mixed $par A string containing a UUID, or null to clear it
  341. * @return void
  342. */
  343. function set_avatar_uuid($par)
  344. {
  345. // Validate
  346. if (is_string($par) == false && is_null($par) == false) {
  347. $this->_internal_validation_error("Sloodle - LSL response: invalid avatar UUID specified; should be a string or null", 0);
  348. } else {
  349. $this->avatar_uuid = $par;
  350. }
  351. }
  352. /**
  353. * Accessor function to set member value {@link $tracking_code}
  354. * @param mixed $par Any scalar value
  355. * @return void
  356. */
  357. function set_tracking_code($par)
  358. {
  359. $this->tracking_code = $par;
  360. }
  361. /**
  362. * Accessor function to set member value {@link $page_total}
  363. * @param mixed $par A positive page total count, or null to clear it
  364. * @return void
  365. */
  366. function set_page_total($par)
  367. {
  368. // Validate
  369. if ((is_int($par) == false || $par < 0) && is_null($par) == false) {
  370. $this->_internal_validation_error("Sloodle - LSL response: invalid page total; should be a positive integer, or null", 0);
  371. } else {
  372. $this->page_total = $par;
  373. }
  374. }
  375. /**
  376. * Accessor function to set member value {@link $page_number}
  377. * @param mixed $par A positive page number, or null to clear it
  378. * @return void
  379. */
  380. function set_page_number($par)
  381. {
  382. // Validate
  383. if ((is_int($par) == false || $par < 0) && is_null($par) == false) {
  384. $this->_internal_validation_error("Sloodle - LSL response: invalid page number; should be a positive integer, or null", 0);
  385. } else {
  386. $this->page_number = $par;
  387. }
  388. }
  389. /**
  390. * Accessor function to set member value {@link $data}. <b>Note: it is recommended that you use the {@link add_data_line()} and {@link clear_data()} functions instead of this.</b>
  391. * @param mixed $par Any scalar value, or a mixed array of scalars or scalar arrays, or null to clear it
  392. * @return void
  393. */
  394. function set_data($par)
  395. {
  396. // We'll use a variable to store validity
  397. $valid = true;
  398. if (is_array($par)) {
  399. // Check each element
  400. foreach ($par as $elem) {
  401. // Is this element another array? Or is it a scalar/null value?
  402. if (is_array($elem)) {
  403. // Check each inner element for validity
  404. foreach ($elem as $innerelem) {
  405. // Is this element scalar or null? If not, it is invalid
  406. if (is_scalar($innerelem) == false && is_null($innerelem) == false) {
  407. $valid = false;
  408. }
  409. }
  410. } else if (is_scalar($elem) == false && is_null($elem) == false) {
  411. // Not an array, nor a scalar/null value - it is invalid
  412. $valid = false;
  413. }
  414. }
  415. if ($valid == false) {
  416. $this->_internal_validation_error("Sloodle - LSL response: non-scalar element in array of items for a data line");
  417. }
  418. } else if (is_scalar($par) == false && is_null($par) == false) {
  419. $valid = false;
  420. $this->_internal_validation_error("Sloodle - LSL response: each line of data must be a scalar type, or an array of scalars");
  421. }
  422. // Store it if it is valid
  423. if ($valid) {
  424. $this->data = $par;
  425. }
  426. }
  427. /**
  428. * Adds one line of data to the {@link $data} member
  429. * @param mixed $par A scalar, or an array of scalars
  430. * @return void
  431. */
  432. function add_data_line($par)
  433. {
  434. // We'll use a variable to store validity
  435. $valid = true;
  436. if (is_array($par)) {
  437. // Check each element
  438. foreach ($par as $elem) {
  439. if (is_scalar($elem) == false && is_null($elem) == false) $valid = false;
  440. }
  441. if ($valid == false) {
  442. $this->_internal_validation_error("Sloodle - LSL response: non-scalar element in array of items for a data line");
  443. }
  444. } else if (is_scalar($par) == false && is_null($par) == false) {
  445. $valid = false;
  446. $this->_internal_validation_error("Sloodle - LSL response: each line of data must be a scalar type, or an array of scalars");
  447. }
  448. // Store it if it is valid
  449. if ($valid) {
  450. // Remove line separators
  451. $par = str_replace(array($this->line_separator, "\r"), ' ', $par); // We'll remove carriage returns, as they screw everything up... thanks to Microsoft...
  452. $this->data[] = $par;
  453. }
  454. }
  455. /**
  456. * Clears all data from member {@link $data}
  457. * @return void
  458. */
  459. function clear_data()
  460. {
  461. $this->data = null;
  462. }
  463. // OTHER FUNCTIONS //
  464. /**
  465. * <i>Constructor</i> - can intialise some variables
  466. * @param int $status_code The initial status code for the response (optional - ignore if null)
  467. * @param string $status_descriptor The initial status descriptor for the response (optional - ignore if null)
  468. * @param mixed $data The initial data for the response, which can be a scalar, or a mixed array of scalars/scalar-arrays (see {@link SloodleResponse::$data}) (optional - ignore if null)
  469. * @return void
  470. * @access public
  471. */
  472. function SloodleResponse($status_code = null, $status_descriptor = null, $data = null)
  473. {
  474. // Store the data
  475. if (!is_null($status_code)) $this->status_code = (int)$status_code;
  476. if (!is_null($status_descriptor)) $this->status_descriptor = (string)$status_descriptor;
  477. if (!is_null($data)) $this->data = $data;
  478. }
  479. /**
  480. * Renders the response to a string.
  481. * Prior to rendering, this function will perform final validation on all the data.
  482. * If anything fails, then the script will terminate with an LSL-friendly error message.
  483. *
  484. * @param string &$str Reference to a string object which the response should be rendered to.
  485. * @return void
  486. * @access public
  487. */
  488. function render_to_string(&$str)
  489. {
  490. // Clear the string
  491. $str = "";
  492. // We can omit any unnecessary items of data, but the number of field-separators must be correct
  493. // E.g. if item 4 is specified, but items 2 and 3 are not, then empty field-separators must be output as if items 2 and 3 were present, e.g.:
  494. // 1|||AVATAR_LIST
  495. // (where the pipe-character | is the field separator)
  496. // We will step backwards through out list of fields, and as soon as one item is specified, all of them should be
  497. $showall = false;
  498. // Make sure that if the page number is specified, that the total is as well
  499. if (is_null($this->page_number) xor is_null($this->page_total)) {
  500. $this->_internal_validation_error("Sloodle - LSL response: script must specify both \"page_total\" *and* \"page_number\", or specify neither");
  501. } else if ($showall || is_null($this->page_number) == false) {
  502. $showall = true;
  503. $str = $this->field_separator . (string)$this->page_total . $this->field_separator . (string)$this->page_number . $str;
  504. }
  505. // Do we have a tracking code?
  506. if ($showall || is_null($this->tracking_code) == false) {
  507. $showall = true;
  508. $str = $this->field_separator . (string)$this->tracking_code . $str;
  509. }
  510. // User key?
  511. if ($showall || is_null($this->avatar_uuid) == false) {
  512. $showall = true;
  513. $str = $this->field_separator . $this->avatar_uuid . $str;
  514. }
  515. // Response timestamp?
  516. if ($showall || is_null($this->response_timestamp) == false) {
  517. $showall = true;
  518. $str = $this->field_separator . (string)$this->response_timestamp . $str;
  519. }
  520. // Request timestamp?
  521. if ($showall || is_null($this->request_timestamp) == false) {
  522. $showall = true;
  523. $str = $this->field_separator . (string)$this->request_timestamp . $str;
  524. }
  525. // Request descriptor?
  526. if ($showall || is_null($this->request_descriptor) == false) {
  527. $showall = true;
  528. $str = $this->field_separator . $this->request_descriptor . $str;
  529. }
  530. // Side-effects?
  531. if ($showall || is_null($this->side_effects) == false) {
  532. $showall = true;
  533. // Is this an array?
  534. if (is_array($this->side_effects)) {
  535. // Yes - output each side effect code in a comma-separated list
  536. $selist = "";
  537. $isfirst = true;
  538. foreach ($this->side_effects as $cur_side_effect) {
  539. if (!$isfirst) $selist .= ",";
  540. else $isfirst = false;
  541. $selist .= (string)$cur_side_effect;
  542. }
  543. // Add that list to the output
  544. $str = $this->field_separator . $selist . $str;
  545. } else {
  546. // Not at an array - output the single item
  547. $str = $this->field_separator . (string)$this->side_effects . $str;
  548. }
  549. }
  550. // Status descriptor?
  551. if ($showall || is_null($this->status_descriptor) == false) {
  552. $showall = true;
  553. $str = $this->field_separator . $this->status_descriptor . $str;
  554. }
  555. // Ensure that a status code has been specified
  556. if (is_null($this->status_code)) {
  557. // Not specified - report an error
  558. $this->_internal_validation_error("Sloodle - LSL response: no status code specified");
  559. } else {
  560. // Output the status code
  561. $str = (string)$this->status_code . $str;
  562. }
  563. // Has any data been specified?
  564. if (is_null($this->data) == false) {
  565. // Do we have an outer array?
  566. if (is_array($this->data)) {
  567. // Go through each element in the outer array
  568. foreach ($this->data as $outer_elem) {
  569. // Do we have an inner array on this element?
  570. if (is_array($outer_elem)) {
  571. // Construct the line, piece-at-a-time
  572. $line = "";
  573. $isfirst = true;
  574. foreach ($outer_elem as $inner_elem) {
  575. // Use the standard field separator
  576. if (!$isfirst) $line .= $this->field_separator;
  577. else $isfirst = false;
  578. $line .= (string)$inner_elem;
  579. }
  580. // Append the new line of data
  581. $str .= $this->line_separator . (string)$line;
  582. } else {
  583. // Output the single item
  584. $str .= $this->line_separator . (string)$outer_elem;
  585. }
  586. }
  587. } else {
  588. // Output the single item
  589. $str .= $this->line_separator . (string)$this->data;
  590. }
  591. }
  592. }
  593. /**
  594. * Outputs the response directly to the HTTP response.
  595. *
  596. * @access public
  597. * @return void
  598. * @uses SloodleResponse::render_to_string() Outputs the result from this function directly to the HTTP response stream.
  599. */
  600. function render_to_output()
  601. {
  602. // Attempt to render the output to a string, and then copy that string to the HTTP response
  603. $str = "";
  604. $this->render_to_string($str);
  605. SloodleDebugLogger::log('RESPONSE', $str);
  606. echo $str;
  607. }
  608. // Quick-output
  609. // Can be called statically to allow simple output of basic data
  610. // The status code is required, but the other parameters are optional
  611. // If an error occurs, the LSL-friendly error message is output to the HTTP response, and the script terminated
  612. // If $static is true (default) then this will be treated as a static call, and a new response object will be used
  613. // If $static is false then this is treated as adding data to an existing response object
  614. /**
  615. * Quick output of data to avoid several accessor calls if the response is very basic.
  616. * Can be called statically to allow simple output of basic data.
  617. * The status code is required, but the other parameters are optional
  618. * If an error occurs, the LSL-friendly error message is output to the HTTP response, and the script terminated
  619. *
  620. * @param int $status_code The status code for the response (required)
  621. * @param string $status_descriptor The status descriptor for the response (optional - ignored if null)
  622. * @param mixed $data The data for the response, which can be a scalar, or a mixed array of scalars/scalar-arrays (see {@link SloodleResponse::$data}) (optional - ignored if null)
  623. * @param bool $static If true (default), then this function will assume it is being call statically, and construct its own response object. Otherwise, it will all the existing member data to render the output.
  624. * @return void
  625. * @access public
  626. */
  627. function quick_output($status_code, $status_descriptor = null, $data = null, $static = true)
  628. {
  629. // Is this s static call?
  630. if ($static) {
  631. // Construct and render the output of a response object
  632. $response = new SloodleResponse($status_code, $status_descriptor, $data);
  633. $response->render_to_output();
  634. } else {
  635. // Set all our data
  636. $this->status_code = $status_code;
  637. if ($status_descriptor != null) $this->status_descriptor = $status_descriptor;
  638. if ($data != null) $this->add_data_line($data);
  639. // Output it
  640. $this->render_to_output();
  641. }
  642. }
  643. /**
  644. * Internal function to report a data validation error.
  645. * Outputs an LSL-friendly error message, and terminates the script
  646. *
  647. * @param string $msg The error message to output.
  648. * @return void
  649. * @access private
  650. */
  651. function _internal_validation_error($msg)
  652. {
  653. exit("-104".$this->field_separator."SYSTEM".$this->line_separator.$msg);
  654. }
  655. }
  656. /**
  657. * Obtains a named HTTP request parameter, and terminates script with an error message if it was not provided.
  658. * This is a 'Sloodle-friendly' version of the Moodle "required_param" function.
  659. * Instead of terminate the script with an HTML-formatted error message, it will terminate with a message
  660. * which conforms for the {@link http://slisweb.sjsu.edu/sl/index.php/Sloodle_communications_specification Sloodle communications specification},
  661. * making it suitable for use in {@link http://slisweb.sjsu.edu/sl/index.php/Linker_Script linker scripts}.
  662. *
  663. * @param string $parname Name of the HTTP request parameter to fetch.
  664. * @param int $type Type of parameter expected, such as "PARAM_RAW". See Moodle documentation for a complete list.
  665. * @return mixed The appropriately parsed and/or cleaned parameter value, if it was found.
  666. * @deprecated
  667. */
  668. function sloodle_required_param($parname, $type)
  669. {
  670. exit('ERROR: deprecated function \'sloodle_required_param()\' called.');
  671. // Attempt to get the parameter
  672. $par = optional_param($parname, null, $type);
  673. // Was it provided?
  674. if (is_null($par)) {
  675. // No - report the error
  676. SloodleResponse::quick_output(-811, "SYSTEM", "Expected request parameter '$parname'.");
  677. exit();
  678. }
  679. return $par;
  680. }
  681. // This class handles an HTTP request
  682. /**
  683. * Handles incoming HTTP requests, typically from LSL if dealing with Second Life.
  684. * This class will perform much of the complex and repetitive processing required for handling HTTP requests.
  685. *
  686. * @uses SloodleResponse Outputs error messages in appropriate format if an error occurs.
  687. * @uses SloodleUser Stores and processes user data incoming from an HTTP request
  688. *
  689. * @package sloodle
  690. */
  691. class SloodleRequest
  692. {
  693. // DATA //
  694. /**
  695. * Reference to the containing {@link SloodleSession} object.
  696. * If null, then this module is being used outwith the framework.
  697. * <b>Always check the status of the variable before using it!</b>
  698. * Note: if not provided, then this object will not render any response information.
  699. * @var object
  700. * @access protected
  701. */
  702. var $_session = null;
  703. /**
  704. * Indicates whether or not the basic request data has already been processed.
  705. * This is used to ensure data is processed.
  706. * @var bool
  707. * @access private
  708. */
  709. var $request_data_processed = false;
  710. // ACCESSORS //
  711. /**
  712. * Checks whether or not the request data has already been processed.
  713. * @return bool
  714. */
  715. function is_request_data_processed()
  716. {
  717. return $this->request_data_processed;
  718. }
  719. /**
  720. * Fetches the password request parameter.
  721. * @param bool $required If true (default) then the function will terminate the script with an error message if the HTTP request parameter was not specified.
  722. * @return string|null The password provided in the request parameters, or null if there wasn't one
  723. */
  724. function get_password($required = true)
  725. {
  726. return $this->get_param(SLOODLE_PARAM_PASSWORD, $required);
  727. }
  728. /**
  729. * Fetches the course ID request parameter.
  730. * @param bool $required If true (default) then the function will terminate the script with an error message if the HTTP request parameter was not specified.
  731. * @return int|null The course ID provided in the request parameters, or null if there wasn't one
  732. */
  733. function get_course_id($required = true)
  734. {
  735. return (int)$this->get_param(SLOODLE_PARAM_COURSE_ID, $required);
  736. }
  737. /**
  738. * Fetches the controller ID request parameter.
  739. * @param bool $required If true (default) then the function will terminate the script with an error message if the HTTP request parameter was not specified.
  740. * @return int|null The controller ID provided in the request parameters, or null if there wasn't one
  741. */
  742. function get_controller_id($required = true)
  743. {
  744. return (int)$this->get_param(SLOODLE_PARAM_CONTROLLER_ID, $required);
  745. }
  746. /**
  747. * Fetches the module ID request parameter.
  748. * @param bool $required If true (default) then the function will terminate the script with an error message if the HTTP request parameter was not specified.
  749. * @return int|null The module ID provided in the request parameters, or null if there wasn't one
  750. */
  751. function get_module_id($required = true)
  752. {
  753. return (int)$this->get_param(SLOODLE_PARAM_MODULE_ID, $required);
  754. }
  755. /**
  756. * Fetches the avatar UUID request parameter.
  757. * @param bool $required If true (default) then the function will terminate the script with an error message if the HTTP request parameter was not specified.
  758. * @return string|null The avatar UUID provided in the request parameters, or null if there wasn't one
  759. */
  760. function get_avatar_uuid($required = true)
  761. {
  762. return $this->get_param(SLOODLE_PARAM_AVATAR_UUID, $required);
  763. }
  764. /**
  765. * Fetches the avatar name request parameter.
  766. * @param bool $required If true (default) then the function will terminate the script with an error message if the HTTP request parameter was not specified.
  767. * @return string|null The avatar name provided in the request parameters, or null if there wasn't one
  768. */
  769. function get_avatar_name($required = true)
  770. {
  771. return $this->get_param(SLOODLE_PARAM_AVATAR_NAME, $required);
  772. }
  773. /**
  774. * Fetches the request descriptor request parameter.
  775. * @param bool $required If true (default) then the function will terminate the script with an error message if the HTTP request parameter was not specified.
  776. * @return string|null The request descriptor provided in the request parameters, or null if there wasn't one
  777. */
  778. function get_request_descriptor($required = true)
  779. {
  780. return $this->get_param(SLOODLE_PARAM_REQUEST_DESC, $required);
  781. }
  782. /**
  783. * Checks the parameters to determine if the request relates to an object rather than a user
  784. * @return bool True if the request seems to have come from an object, or false otherwise.
  785. */
  786. function is_object_request()
  787. {
  788. $par = $this->get_param(SLOODLE_PARAM_IS_OBJECT, false, false);
  789. if (strcasecmp($par, 'true') == 0 || strcasecmp($par, 'yes') == 0 || $par == '1') return true;
  790. return false;
  791. }
  792. /**
  793. * Fetches the server access level parameter, if specified.
  794. * @param bool $required If true (default) then the function will terminate the script with an error message if the HTTP request parameter was not specified.
  795. * @return string|null The server access level provided in the request parameters, or null if there wasn't one
  796. */
  797. function get_server_access_level($required = true)
  798. {
  799. return (int)$this->get_param(SLOODLE_PARAM_SERVER_ACCESS_LEVEL, $required);
  800. }
  801. // FUNCTIONS //
  802. /**
  803. * <i>Constructor</i> - initialises the {@link SloodleSession} object.
  804. * If the session parameter is null, then this object simply does not render response information.
  805. *
  806. * @param SloodleUser $_session A reference to the {@link SloodleSession} object which this request should use, or null
  807. */
  808. function SloodleRequest(&$_session)
  809. {
  810. // Store our session object
  811. $this->_session = &$_session;
  812. }
  813. /**
  814. * Process all of the standard data provided by the HTTP request, and write it into our {@link SloodleSession} object.
  815. * Requires that a {@link SloodleSession} object was provided at construction, and is stored in the $_session member.
  816. * NOTE: this does not load the module part of the session. That must be done separately, using the {@link SloodleSession::load_module()} member function.
  817. * @param bool $require_auth If true, then the function will terminate the script with an error message if it cannot authenticate the request through a course, controller and password
  818. * @param bool $require_user If true, then the function will terminate the script with an error message if a legitimate user was not identified or could not be auto-registered
  819. * @return bool True if successful, or false otherwise.
  820. */
  821. function process_request_data($require_auth = true, $require_user = true)
  822. {
  823. SloodleDebugLogger::log('REQUEST', null);
  824. // Do we have a session object?
  825. if (!isset($this->_session)) return false;
  826. // Store the request descriptor
  827. $this->_session->response->set_request_descriptor($this->get_request_descriptor(false));
  828. // Attempt to load the controller, then the course
  829. // (there is a shortcut, using course->load_by_controller(),
  830. // however, that makes it harder to locate problems)
  831. if ($this->_session->course->controller->load( $this->get_controller_id(false) )) {
  832. // Got the controller... now the course
  833. $this->_session->course->load( $this->_session->course->controller->get_course_id() );
  834. } else {
  835. // Perhaps a course was specified in the request instead?
  836. $this->_session->course->load( $this->get_course_id(false) );
  837. }
  838. // Get the avatar details
  839. $uuid = $this->get_avatar_uuid(false);
  840. $avname = $this->get_avatar_name(false);
  841. // Attempt to load an avatar
  842. if ($this->_session->user->load_avatar($uuid, $avname)) {
  843. // Success - now attempt to load the linked VLE user
  844. $this->_session->user->load_linked_user();
  845. // If we didn't already have a UUID then get it from the user data
  846. if (empty($uuid)) {
  847. $uuid = $this->_session->user->get_avatar_uuid();
  848. }
  849. // Update the user's activity listing
  850. $this->_session->user->set_avatar_last_active();
  851. $this->_session->user->write_avatar();
  852. }
  853. // If we now have a UUID, then add it to our response data
  854. if (!empty($uuid)) $this->_session->response->set_avatar_uuid($uuid);
  855. $this->request_data_processed = true;
  856. return true;
  857. }
  858. /**
  859. * Gets a database record for the course identified in the request.
  860. * (Note: this function does not check whether or not the user is enrolled in the course)
  861. *
  862. * @param bool $require If true, the function will NOT return failure. Rather, it will terminate the script with an error message.
  863. * @return object A record directly from the database, or null if the course is not found.
  864. */
  865. function get_course_record($require = true)
  866. {
  867. // Make sure the request data is processed
  868. $this->process_request_data();
  869. // Make sure the course ID was specified
  870. if (is_null($this->course_id)) {
  871. if ($require) {
  872. $this->response->set_status_code(-501);
  873. $this->response->set_status_descriptor('COURSE');
  874. $this->response->add_data_line('No course specified in request.');
  875. $this->response->render_to_output();
  876. exit();
  877. }
  878. return null;
  879. }
  880. // Attempt to get the course data
  881. $course_record = get_record('course', 'id', $this->course_id);
  882. if ($course_record === false) {
  883. // Course not found
  884. if ($require) {
  885. $this->response->set_status_code(-512);
  886. $this->response->set_status_descriptor('COURSE');
  887. $this->response->add_data_line("Course {$this->course_id} not found.");
  888. $this->response->render_to_output();
  889. exit();
  890. }
  891. return null;
  892. }
  893. // Make sure the course is visible
  894. // TODO: any availability other checks here?
  895. if ((int)$course_record->visible == 0) {
  896. // Course not available
  897. if ($require) {
  898. $this->response->set_status_code(-513);
  899. $this->response->set_status_descriptor('COURSE');
  900. $this->response->add_data_line("Course {$this->course_id} is not available.");
  901. $this->response->render_to_output();
  902. exit();
  903. }
  904. return null;
  905. }
  906. // TODO: in future, we need to check that the course is Sloodle-enabled
  907. // TODO: in future, make sure we are authenticated for this particular course
  908. // Seems fine... return the object
  909. return $course_record;
  910. }
  911. /**
  912. * Get a course module instance for the module specified in the request
  913. * Uses the ID specified in {@link $module_id}.
  914. *
  915. * @param string $type specifies the name of the module type (e.g. 'forum', 'choice' etc.) - ignored if blank (default).
  916. * @param bool $require If true, the function will NOT return failure. Rather, it will terminate the script with an error message.
  917. * @return object A database record if successful, or false if not (e.g. if instance is not found, is not visible, or is not of the correct type)
  918. */
  919. function get_course_module_instance( $type = '', $require = true )
  920. {
  921. // Make sure the request data is processed
  922. $this->process_request_data();
  923. // Make sure the module ID was specified
  924. if ($this->module_id == null) {
  925. if ($require) {
  926. $this->response->set_status_code(-711);
  927. $this->response->set_status_descriptor('MODULE_DESCRIPTOR');
  928. $this->response->add_data_line('Course module instance ID not specified.');
  929. $this->response->render_to_output();
  930. exit();
  931. }
  932. return false;
  933. }
  934. // Attempt to get the instance
  935. if (!($cmi = sloodle_get_course_module_instance($this->module_id))) {
  936. if ($require) {
  937. $this->response->set_status_code(-712);
  938. $this->response->set_status_descriptor('MODULE_DESCRIPTOR');
  939. $this->response->add_data_line('Could not find course module instance.');
  940. $this->response->render_to_output();
  941. exit();
  942. }
  943. return false;
  944. }
  945. // If the type was specified, then verify it
  946. if (!empty($type)) {
  947. if (!sloodle_check_course_module_instance_type($cmi, strtolower($type))) {
  948. if ($require) {
  949. $this->response->set_status_code(-712);
  950. $this->response->set_status_descriptor('MODULE_DESCRIPTOR');
  951. $this->response->add_data_line("Course module instance not of expected type. (Expected: '$type').");
  952. $this->response->render_to_output();
  953. exit();
  954. }
  955. return false;
  956. }
  957. }
  958. // Make sure the instance is visible
  959. if (!sloodle_is_course_module_instance_visible($cmi)) {
  960. if ($require) {
  961. $this->response->set_status_code(-713);
  962. $this->response->set_status_descriptor('MODULE_DESCRIPTOR');
  963. $this->response->add_data_line('Specified course module instance is not available.');
  964. $this->response->render_to_output();
  965. exit();
  966. }
  967. return false;
  968. }
  969. // Everything looks fine
  970. return $cmi;
  971. }
  972. // UTILITY FUNCTIONS //
  973. /**
  974. * Obtains a named HTTP request parameter, or NULL if it has not been provided.
  975. * Return values are always strings.
  976. * @param string $parname The name of the parameter to fetch
  977. * @param mixed $default The value to return if the parameter cannot be found
  978. * @return string|mixed The raw parameter value (will be a string if the parameter was found, or the value of parameter $default otherwise)
  979. */
  980. function optional_param($parname, $default = null)
  981. {
  982. if (isset($_REQUEST[$parname])) return (string)$_REQUEST[$parname];
  983. return $default;
  984. }
  985. /**
  986. * Obtains a named HTTP request parameter, or terminates with an error message if it has not been provided.
  987. * Note: for linker scripts, this should *always* be used instead of the standard Moodle function, as this will
  988. * render appropriately formatted error messages, which scripts can understand.
  989. * Also note that this function always returned values in the string type. They must be cast.
  990. *
  991. * @param string $parname The name of the HTTP request parameter to get.
  992. * @return string The raw parameter value
  993. */
  994. function required_param($parname)
  995. {
  996. // Is the parameter provided?
  997. if (!isset($_REQUEST[$parname])) {
  998. // No - report the error
  999. if (isset($this->_session)) {
  1000. $this->_session->response->set_status_code(-811);
  1001. $this->_session->response->set_status_descriptor('SYSTEM');
  1002. $this->_session->response->add_data_line("Required parameter not provided: '$parname'.");
  1003. $this->_session->response->render_to_output();
  1004. }
  1005. exit();
  1006. }
  1007. return $_REQUEST[$parname];
  1008. }
  1009. /**
  1010. * Obtains a named HTTP request parameter, optionally requiring it or not
  1011. * @param string $parname The name of the parameter to get
  1012. * @param bool $required Indicates whether or not to 'require' the parameter (if it is required, but cannot be found, then the script is terminated with an error message)
  1013. * @param mixed $default If the $require parameter is false, and the HTTP parameter cannot be found, then this value will be returned instead
  1014. * @return string|mixed The raw value of the HTTP parameter if found, or the value of parameter $default if it was not found and parameter $require was false
  1015. */
  1016. function get_param($parname, $required, $default = null)
  1017. {
  1018. // Use the existing functions to fetch the parameter
  1019. if ($required) return $this->required_param($parname);
  1020. return $this->optional_param($parname, $default);
  1021. }
  1022. }
  1023. class SloodleDebugLogger {
  1024. // Write the contents to the debug log, if one is defined in SLOODLE_DEBUG_REQUEST_LOG.
  1025. // Return true if we write something, false if we don't.
  1026. function log($type, $contents = null) {
  1027. $str = '';
  1028. $str = '------START-'.$type.'-'.$_SERVER['REQUEST_URI'].'---'.$_SERVER['REMOTE_ADDR'].'---'.$_SERVER['REMOTE_PORT'].'---'.$_SERVER['REQUEST_TIME'].'------'."\n";
  1029. if ( SLOODLE_DEBUG_REQUEST_LOG == '' ) {
  1030. return false;
  1031. }
  1032. if ( ($type == 'REQUEST') && ($contents == null) ) {
  1033. if (!empty($_GET)) {
  1034. foreach($_GET as $n=>$v) {
  1035. $str .= "GET: ".$n." => ".$v."\n";
  1036. }
  1037. }
  1038. if (!empty($_POST)) {
  1039. foreach($_POST as $n=>$v) {
  1040. $str .= "POST: ".$n." => ".$v."\n";
  1041. }
  1042. }
  1043. if (!empty($_SERVER)) {
  1044. $interesting_server_vars = array('HTTP_X_SECONDLIFE_OBJECT_NAME', 'REQUEST_URI');
  1045. foreach($_SERVER as $n=>$v) {
  1046. if (in_array($n, $interesting_server_vars)) {
  1047. $str .= "SERVER: ".$n." => ".$v."\n";
  1048. }
  1049. }
  1050. }
  1051. } else {
  1052. $str .= $contents."\n";
  1053. }
  1054. $str .= '------END-'.$type.'-'.$_SERVER['REQUEST_URI'].'---'.$_SERVER['REMOTE_ADDR'].'---'.$_SERVER['REMOTE_PORT'].'---'.$_SERVER['REQUEST_TIME'].'------'."\n";
  1055. if ($fh = fopen(SLOODLE_DEBUG_REQUEST_LOG, 'a')) {
  1056. fwrite($fh, $str);
  1057. fclose($fh);
  1058. }
  1059. return false;
  1060. }
  1061. }
  1062. ?>