active_object.php 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  1. <?php
  2. // This file is part of the Sloodle project (www.sloodle.org)
  3. /**
  4. * Defines a structure to store information about an active object
  5. *
  6. * @package sloodle
  7. * @copyright Copyright (c) 2008 Sloodle (various contributors)
  8. * @license http://www.gnu.org/licenses/gpl-3.0.html GNU GPL v3
  9. *
  10. * @contributor Edmund Edgar
  11. * @contributor Paul Preibisch
  12. * sloodle/lib/object_httpin_linker.php
  13. */
  14. /**
  15. * An active object, relating to the Sloodle active objects DB table.
  16. * @package sloodle
  17. */
  18. class SloodleActiveObject
  19. {
  20. /**
  21. * The UUID of this object.
  22. * @var string
  23. * @access public
  24. */
  25. var $uuid = '';
  26. /**
  27. * The ID of this object.
  28. * @var integer
  29. * @access public
  30. */
  31. var $id = 0;
  32. /**
  33. * The name of this object.
  34. * @var string
  35. * @access public
  36. */
  37. var $name = '';
  38. /**
  39. * The password of this object.
  40. * Used for the server to check a message from the object is legitimate.
  41. * @var string
  42. * @access public
  43. */
  44. var $password = '';
  45. /**
  46. * The http-in password of this object.
  47. * Used for the object to check a message from the server is legitimate.
  48. * @var string
  49. * @access public
  50. */
  51. var $httpinpassword= '';
  52. /**
  53. * The type of this object. This corresponds to the mod/ directory its linker lives in.
  54. * @var string
  55. * @access public
  56. */
  57. var $type = '';
  58. /**
  59. * The code of this object.
  60. * If we have multiple objects sharing the same linked, they will have different codes.
  61. * Corresponds to the name of the directory inside objects
  62. * eg. Two quiz chairs, one of which was a rocket would both have the type of quiz-1.0
  63. * ...and be distinguished as 'quiz-chair' and 'quiz-rocket'.
  64. * NB For backwards compatibility, this can be undefined
  65. * ...in which case we'll expect a definition called 'default'.
  66. * @var string
  67. * @access public
  68. */
  69. var $object_code = null;
  70. /**
  71. * The course/controller which this object is authorised for.
  72. * @var SloodleCourse
  73. * @access public
  74. */
  75. var $course = null;
  76. /**
  77. * The user who authorised this object.
  78. * @var SloodleUser
  79. * @access public
  80. */
  81. var $user = null;
  82. /**
  83. * The httpin for this object
  84. * For SL, this will look like: http://sim5395.agni.lindenlab.com:12046/cap/e93d6ad8-b75d-83ff-dac2-979ec82633a1
  85. * @var string
  86. * @access public
  87. */
  88. var $httpinurl = null;
  89. /**
  90. * The layoutentryid this object was rezzed to represent, if there is one.
  91. * @var string
  92. * @access public
  93. */
  94. var $layoutentryid = null;
  95. /**
  96. * The rezzeruuid of the object that controls this object, if there is one.
  97. * @var string
  98. * @access public
  99. */
  100. var $rezzeruuid = null;
  101. /**
  102. * The position of the object relative to its rezzer, or the position the object should go to if it has just been configured.
  103. * @var string
  104. * @access public
  105. */
  106. var $position = null;
  107. /**
  108. * The rotation of the object relative to its rezzer, or the rotation the object should adopt if it has just been configured.
  109. * @var string
  110. * @access public
  111. */
  112. var $rotation = null;
  113. /**
  114. * The ID of the controller the object is attached to
  115. * @var string
  116. * @access public
  117. */
  118. var $controllerid = null;
  119. /**
  120. * The ID of the user who rezzed the object
  121. * @var string
  122. * @access public
  123. */
  124. var $userid= null;
  125. /**
  126. * The media key that prompts a shared media screen for the object
  127. * Intended for if we need a shared media non-public identifier.
  128. * Not yet in use as of 2012-04-06
  129. * @var string
  130. * @access public
  131. */
  132. var $mediakey = null;
  133. /**
  134. * The date of the last successful message sent to the object
  135. * Not yet in use as of 2012-04-06
  136. * @var string
  137. * @access public
  138. */
  139. var $lastmessagetimestamp = null;
  140. var $response = null;
  141. // The following functions are used to allow the Moodle server to connect to an OpenSim server through a tunnel or proxy.
  142. // For example, if your Moodle server is on the public internet, but your OpenSim server is behind a firewall...
  143. // ...your Moodle server will not usually be able to send HTTP-In messages to your OpenSim server.
  144. // You can work around this with a reverse SSH tunnel from your OpenSim server to your Moodle server...
  145. // ...so your Moodle server will send messages to a local port
  146. // ...which will then be forwarded through your SSH tunnel to your OpenSim server.
  147. // For now, we'll turn this on by define()ing a constant in sl_config.
  148. // Ultimately, we may want to make this configurable for a particular controller or rezzer
  149. // ...as you may need different values for different OpenSim servers.
  150. function httpProxyURL() {
  151. if ( defined( 'SLOODLE_HTTP_IN_PROXY_OR_TUNNEL' ) ) {
  152. return SLOODLE_HTTP_IN_PROXY_OR_TUNNEL;
  153. }
  154. return null;
  155. }
  156. function objectDefinition() {
  157. if ($this->type == '') {
  158. return null;
  159. }
  160. return SloodleObjectConfig::ForObjectType( $this->type );
  161. }
  162. /*
  163. Queues an object
  164. */
  165. function queue($address, $task, $msg, $ttr = 30, $clear = false, $priority=1000) {
  166. require_once(SLOODLE_LIBROOT.'/beanstalk/Beanstalk.php');
  167. global $CFG;
  168. // Make a unique name for the sites queue
  169. $sitequeue = defined('SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_PREFIX') ? SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_PREFIX : '';
  170. $tube = $sitequeue.'-'.md5($address);
  171. //$tube = 'default';
  172. //$tube = md5($address);
  173. /*
  174. $sbhost = ( defined(SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_HOST) && SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_HOST ) ? SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_HOST : '127.0.0.1';
  175. $sbport = ( defined(SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_PORT) && SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_PORT ) ? SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_PORT : 11300;
  176. $sbtimeout = ( defined(SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_TIMEOUT) && SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_TIMEOUT ) ? SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_TIMEOUT : 1;
  177. $sbpersistent = ( defined(SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_PERSISTENT) && SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_PERSISTENT ) ? SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK_PERSISTENT : true;
  178. $sbconfig = array(
  179. 'persistent' => $sbpersistent,
  180. 'host' => $sbhost,
  181. 'port' => $sbport,
  182. 'timeout' => $sbtimeout
  183. );
  184. */
  185. //$sb = new Socket_Beanstalk( $sbconfig );
  186. $sb = new Socket_Beanstalk();
  187. if (!$sb->connect()) {
  188. return false;
  189. }
  190. $sb->choose($tube);
  191. // Jobs will be handled more-or-less first-in, first-out, unless superseced by a new job and cleared.
  192. //
  193. //$priority = time();
  194. $header = $task.'|'.$address;
  195. $msg = $header."\n".$msg;
  196. if (!$pid = $sb->put($priority, 0, $ttr, $msg)) {
  197. return false;
  198. }
  199. // Delete all jobs with an early pid than the one we just put in there.
  200. if ($clear) {
  201. while ($job = $sb->peekReady()) {
  202. if ($job['id'] >= $pid) {
  203. break;
  204. }
  205. // Only delete jobs for the same task.
  206. if (strpos( $job['body'], $header) == 0) {
  207. $sb->delete($job['id']);
  208. }
  209. }
  210. }
  211. return true;
  212. }
  213. // Returns true if the install has a functioning method for queuing messages to be sent asynchronously.
  214. // As of 2012-03-30, this will be a message queue using Beanstalk.
  215. // In future we may do a more Moodle-standard mysql queue, plus a cron like chatd.php
  216. function isQueueActive() {
  217. if ( defined('SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK') && (SLOODLE_MESSAGE_QUEUE_SERVER_BEANSTALK != '') ) {
  218. return true;
  219. }
  220. return false;
  221. }
  222. // sends a curl message to our objects httpinurl
  223. // If async is set, if possible it will be queued for sending later.
  224. //
  225. public function sendMessage($msg, $async = false, $replaceQueued = false, $task = 'async_message', $priority=1000, $timeout = 20){
  226. SloodleDebugLogger::log('HTTP-IN', $msg);
  227. if ($async && $this->isQueueActive()) {
  228. //$this->httpinurl
  229. if ($this->queue($this->httpinurl, $task, $msg, 3, $replaceQueued)) {
  230. return array('info'=>null, 'result'=>'QUEUED');
  231. }
  232. }
  233. $ch = curl_init(); // initialize curl handle
  234. curl_setopt($ch, CURLOPT_URL, $this->httpinurl ); // set url to post to
  235. curl_setopt($ch, CURLOPT_FAILONERROR,0);
  236. curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // return into a variable
  237. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  238. curl_setopt($ch, CURLOPT_POST, 1); // set POST method
  239. curl_setopt($ch, CURLOPT_POSTFIELDS,$msg); // add POST fields
  240. if ($proxy = $this->httpProxyURL()) {
  241. curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
  242. curl_setopt($ch, CURLOPT_PROXY, $this->httpProxyURL() );
  243. }
  244. $result = curl_exec($ch); // run the whole process
  245. $info = curl_getinfo($ch);
  246. curl_close($ch);
  247. return array('info'=>$info,'result'=>$result);
  248. }
  249. /*
  250. Request a list of inventory from the rezzer.
  251. NB The format is slightly different to the one used by the vending machine.
  252. $include_sloodle can be set to false to ignore things beginning with SLOODLE.
  253. Hopefully this will give us a list of third-party objects that we don't already know about.
  254. */
  255. public function listInventory( $include_sloodle = true ) {
  256. $response = new SloodleResponse();
  257. $response->set_status_code(1);
  258. $response->set_status_descriptor('SYSTEM');
  259. $response->set_request_descriptor('LIST_INVENTORY');
  260. $response->add_data_line('do:list_inventory');
  261. //create message - NB for some reason render_to_string changes the string by reference instead of just returning it.
  262. $renderStr="";
  263. $response->render_to_string($renderStr);
  264. //send message to httpinurl
  265. $reply = $this->sendMessage($renderStr);
  266. if ( !( $reply['info']['http_code'] == 200 ) ) {
  267. return false;
  268. }
  269. $result = $reply['result'];
  270. $ret = array();
  271. $lines = explode("\n", $result);
  272. array_shift($lines);
  273. foreach($lines as $l) {
  274. if (!$include_sloodle) {
  275. if (preg_match('/^SLOODLE/', $l)) {
  276. continue;
  277. }
  278. }
  279. $ret[] = $l;
  280. }
  281. return $ret;
  282. }
  283. // Sends a message to the object telling it to derez itself.
  284. // Doesn't deletes the active_object record, because it doesn't know whether the message will get through or not.
  285. // Return true on successful queuing, false on failure
  286. public function queueDeRez() {
  287. if (!$this->isQueueActive()) {
  288. return false;
  289. }
  290. if (!$message = $this->deRezMessage()) {
  291. return false;
  292. }
  293. $async = true;
  294. $reply = $this->sendMessage($message, $async, $async, 'derez');
  295. return true;
  296. }
  297. private function deRezMessage() {
  298. if (!$this->httpinurl) {
  299. return false;
  300. }
  301. //build response string
  302. $response = new SloodleResponse();
  303. $response->set_status_code(1);
  304. $response->set_status_descriptor('SYSTEM');
  305. $response->set_request_descriptor('DEREZ');
  306. $response->set_http_in_password($this->httpinpassword);
  307. //$response->set_return_url();
  308. $response->add_data_line('do:derez');
  309. //create message - NB for some reason render_to_string changes the string by reference instead of just returning it.
  310. $renderStr="";
  311. $response->render_to_string($renderStr);
  312. //send message to httpinurl
  313. return $renderStr;
  314. }
  315. // Sends a message to the object telling it to derez itself.
  316. // Deletes the active_object record if successful.
  317. // NB the object can't report back whether it successfully derezzed itself, because it no longer exists.
  318. // We'll go by whether it acknowledged the derez command, which is as close as we can get.
  319. // Return true on success, false on failure
  320. public function deRez() {
  321. if (!$message = $this->deRezMessage()) {
  322. return false;
  323. }
  324. $async = false;
  325. $reply = $this->sendMessage($message, $async, $async, 'derez');
  326. if (!$async) {
  327. if ( !( $reply['info']['http_code'] == 200 ) ) {
  328. return false;
  329. }
  330. }
  331. return $this->delete();
  332. }
  333. // Sends a message to the object telling it to refresh its configuration.
  334. // Return true on success, false on failure
  335. public function refreshConfig($async = false) {
  336. if (!$this->httpinurl) {
  337. return false;
  338. }
  339. //build response string
  340. $response = new SloodleResponse();
  341. $response->set_status_code(1);
  342. $response->set_status_descriptor('SYSTEM');
  343. $response->set_request_descriptor('REFRESH_CONFIG');
  344. $response->add_data_line('do:requestconfig');
  345. //create message - NB for some reason render_to_string changes the string by reference instead of just returning it.
  346. $renderStr="";
  347. $response->render_to_string($renderStr);
  348. //send message to httpinurl
  349. $reply = $this->sendMessage($renderStr, $async, $async);
  350. if ( !$async && ( !( $reply['info']['http_code'] == 200 ) ) ) {
  351. return false;
  352. }
  353. return true;
  354. }
  355. /*
  356. * writes config data to a response object, and sends it this objects httpinurl
  357. * @var $extraParameters array()
  358. *
  359. */
  360. public function sendConfig( $extraParameters = NULL, $async = false ) {
  361. global $CFG;
  362. //construct the body
  363. if ($extraParameters === NULL) {
  364. $extraParameters = array();
  365. }
  366. //build response string
  367. $response = new SloodleResponse();
  368. $response->add_data_line(array('set:sloodlecontrollerid', $this->controllerid));
  369. $response->add_data_line(array('set:sloodlecoursename_short', $this->course->get_short_name()));
  370. $response->add_data_line(array('set:sloodlecoursename_full', $this->course->get_short_name()));
  371. $response->add_data_line(array('set:sloodlepwd', $this->uuid.'|'.$this->password)); // NB We need to prepend the UUID - otherwise Sloodle treats it like a prim password
  372. $response->add_data_line(array('set:sloodleserverroot', $CFG->wwwroot));
  373. $response->add_data_line(array('set:sloodleobjtype', $this->type));
  374. if ($this->layoutentryid) {
  375. $response->add_data_line(array('set:position', $this->position, $this->rotation, $this->rezzeruuid));
  376. //$response->add_data_line(array('set:rezzeruuid', $this->rezzeruuid));
  377. //$response->add_data_line(array('set:rotation', $this->rotation));
  378. $response->add_data_line(array('set:layoutentryid', $this->layoutentryid));
  379. }
  380. //search for setings for this object
  381. $settings = sloodle_get_records('sloodle_object_config', 'object', $this->id);
  382. if (!$settings) {
  383. // Error: no configuration settings... there should be at least one indicating the type
  384. /*
  385. $response->set_status_code(-103);
  386. $response->set_status_descriptor('SYSTEM');
  387. $response->add_data_line('Object not configured yet.');
  388. $renderStr="";
  389. //create message
  390. $response->render_to_string($renderStr);
  391. //send message to httpinurl
  392. return $this->sendMessage($renderStr);
  393. */
  394. }
  395. $layoutentry = NULL;
  396. $position = NULL;
  397. $rotation = NULL;
  398. // Output each setting
  399. foreach ($settings as $s) {
  400. $response->add_data_line(array('set:'.$s->name, $s->value));
  401. }//end foreach
  402. foreach( $extraParameters as $n => $v) {
  403. $response->add_data_line( 'set:'.$n.'|'.$v );
  404. }//endforeach
  405. $response->set_status_code(1);
  406. $response->set_refresh_seconds(SLOODLE_PING_INTERVAL);
  407. /*
  408. If we send the object a CONFIG_PERSISTENT descriptor, it will keep the config and use it if re-rezzed or copied.
  409. This is set in init.php.
  410. Normally this would be on, but if you're developing a set to give to other people, you want it off
  411. Otherwise it will use your server details, rather than getting new ones.
  412. */
  413. $request_descriptor = (defined('SLOODLE_ENABLE_OBJECT_PERSISTANCE') && SLOODLE_ENABLE_OBJECT_PERSISTANCE) ? 'CONFIG_PERSISTENT' : 'CONFIG';
  414. $response->set_status_descriptor('CONFIG');
  415. $response->set_request_descriptor($request_descriptor);
  416. $response->set_http_in_password($this->httpinpassword);
  417. $renderStr="";
  418. $response->render_to_string($renderStr);
  419. //curl send this to our object's httpinurl
  420. return $this->sendMessage( $renderStr, $async, $async);
  421. }
  422. // Deletes a record from the active object table.
  423. // Returns true on success.
  424. // NB If you need to get rid of the record from world as well, use deRez().
  425. // deRez will call this function in turn.
  426. public function delete() {
  427. if (!$this->id) {
  428. return false;
  429. }
  430. // Delete all config entries and the object record itself
  431. return ( sloodle_delete_records('sloodle_object_config', 'object', $this->id) && sloodle_delete_records('sloodle_active_object', 'id', $this->id) );
  432. }
  433. public function save(){
  434. //write local data to a new or existing record
  435. //search for id
  436. //if exists update
  437. $result = sloodle_get_record("sloodle_active_object",'id',$this->id);
  438. if (!$result ) {
  439. //id,controllerid,userid,uuid,password,name,type,timeupdated
  440. // No - insert a new record
  441. $result = new stdClass();
  442. $result->controllerid = $this->controllerid;
  443. $result->userid = $this->userid;
  444. $result->uuid = $this->uuid;
  445. $result->httpinurl = $this->httpinurl;
  446. $result->httpinpassword = $this->httpinpassword;
  447. $result->position = $this->position;
  448. $result->rotation = $this->rotation;
  449. $result->rezzeruuid = $this->rezzeruuid;
  450. $result->layoutentryid = $this->layoutentryid;
  451. $result->password = $this->password;
  452. $result->name = $this->name;
  453. $result->type = $this->type;
  454. $result->mediakey = $this->mediakey;
  455. $result->lastmessagetimestamp = $this->lastmessagetimestamp;
  456. $success = sloodle_insert_record('sloodle_active_object', $result );
  457. } else {
  458. $result->controllerid = $this->controllerid;
  459. $result->userid = $this->userid;
  460. $result->uuid = $this->uuid;
  461. $result->httpinurl = $this->httpinurl;
  462. $result->position = $this->position;
  463. $result->rotation = $this->rotation;
  464. $result->rezzeruuid = $this->rezzeruuid;
  465. $result->layoutentryid = $this->layoutentryid;
  466. $result->password = $this->password;
  467. $result->name = $this->name;
  468. $result->type = $this->type;
  469. $result->httpinpassword = $this->httpinpassword;
  470. $result->mediakey = $this->mediakey;
  471. $result->lastmessagetimestamp = $this->lastmessagetimestamp;
  472. if (sloodle_update_record('sloodle_active_object', $result)) {
  473. $success = $result->id;
  474. }
  475. }//end else
  476. $this->course = new SloodleCourse();
  477. $this->course->load_by_controller($this->controllerid);
  478. return $success;
  479. }//end function
  480. // Load data for the specified id
  481. // Return true on success, false on fail
  482. public function load( $id) {
  483. $rec = sloodle_get_record('sloodle_active_object','id',$id);
  484. if ($rec) {
  485. $this->loadFromRecord($rec);
  486. return true;
  487. }
  488. return false;
  489. }
  490. // Load data for the specified UUID
  491. // Return true on success, false on fail
  492. public function loadByUUID( $uuid, $lazy = false ) {
  493. //SloodleDebugLogger::log('DEBUG', "loading for uuid :$uuid:");
  494. $rec = sloodle_get_record('sloodle_active_object','uuid',$uuid);
  495. if ($rec) {
  496. //SloodleDebugLogger::log('DEBUG', "loaded for uuid :$uuid:");
  497. $this->loadFromRecord($rec, $lazy);
  498. return true;
  499. }
  500. return false;
  501. }
  502. public function loadFromRecord($rec, $lazy = false) {
  503. $this->id = $rec->id;
  504. $this->controllerid = $rec->controllerid;
  505. $this->userid = $rec->userid;
  506. $this->uuid = $rec->uuid;
  507. $this->password = $rec->password;
  508. $this->name = $rec->name;
  509. $this->type = $rec->type;
  510. $this->timeupdated = $rec->timeupdated;
  511. $this->httpinurl = $rec->httpinurl;
  512. $this->httpinpassword = $rec->httpinpassword;
  513. $this->layoutentryid = $rec->layoutentryid;
  514. $this->position = $rec->position;
  515. $this->rotation = $rec->rotation;
  516. $this->rezzeruuid = $rec->rezzeruuid;
  517. $this->mediakey = $rec->mediakey;
  518. $this->lastmessagetimestamp = $rec->lastmessagetimestamp;
  519. // TODO: This is probably mostly not needed.
  520. // We should fetch this lazily when we need the course name or whatever it is we want here.
  521. if ($this->controllerid && !$lazy) {
  522. $this->course = new SloodleCourse();
  523. $this->course->load_by_controller($this->controllerid);
  524. }
  525. return true;
  526. }
  527. // Configures an activeobject for its layoutentryid, which should already have been set, if there is one.
  528. // TODO: This functionality is partially duplicated in controller->configure_object_from_layout_entry($authid, $layout_entry_id, $rezzeruuid = null)
  529. // Remove it from there, and do it all here.
  530. // NB This doesn't actually send the config to the object - you need to call sendConfig separately to do that.
  531. public function configure_for_layout() {
  532. // We should already have been inserted when this gets called.
  533. if (!$id = $this->id) {
  534. return false;
  535. }
  536. if (!$layoutentryid = $this->layoutentryid) {
  537. return false;
  538. }
  539. $existingconfigs = $this->config_name_config_hash();
  540. $done_configs = array();
  541. $configs = sloodle_get_records('sloodle_layout_entry_config','layout_entry', $layoutentryid);
  542. $ok = true;
  543. if (count($configs) > 0) {
  544. foreach($configs as $config) {
  545. $name = $config->name;
  546. if ( isset($existingconfigs[ $name ])) {
  547. // No change
  548. if ($existingconfigs[ $name ]->value == $config->value) {
  549. $done_configs[ $name ] = true;
  550. continue;
  551. } else {
  552. $updated_config = $existingconfigs[ $name ];
  553. $updated_config->value = $config->value;
  554. sloodle_update_record( 'sloodle_object_config', $updated_config);
  555. $done_configs[ $name ] = true;
  556. }
  557. } else {
  558. $config->id = null;
  559. $config->object = $this->id;
  560. if (!sloodle_insert_record('sloodle_object_config',$config)) {
  561. $ok = false;
  562. }
  563. }
  564. }
  565. }
  566. // Original configs that are no longer in the layout, so we'll kill them.
  567. if (count($existingconfigs) > 0) {
  568. foreach($existingconfigs as $config) {
  569. if (!isset($done_configs[ $config->name ] )) {
  570. sloodle_delete_records('sloodle_object_config', 'id', $config->id);
  571. }
  572. }
  573. }
  574. return $ok;
  575. }
  576. /**
  577. * Updates the last active timer on an object.
  578. * @return bool True if successful, or false if not.
  579. * Replaces controller->ping_object().
  580. */
  581. public function recordAccess() {
  582. if (!$this->id) {
  583. return false;
  584. }
  585. $this->timeupdated = time();
  586. return sloodle_update_record('sloodle_active_object', $this);
  587. }
  588. public function config_value( $name ) {
  589. if ($config = sloodle_get_record('sloodle_object_config', 'object', $this->id, 'name', $name)) {
  590. return $config->value;
  591. }
  592. $config = sloodle_get_record('sloodle_object_config', 'object', $this->id);
  593. return null;
  594. }
  595. public function requirement_failures( $interaction, $multiplier, $userid, $useruuid) {
  596. if ( !$userid = intval($userid) ) {
  597. return array();
  598. }
  599. if ( $multiplier == 0 ) {
  600. return array();
  601. }
  602. if (!$controllerid = intval($this->controllerid) ) {
  603. return array();
  604. }
  605. $all_configs = sloodle_get_records('sloodle_object_config', 'object', $this->id);
  606. if (count($all_configs) == 0) {
  607. // Nothing to do here
  608. return array();
  609. }
  610. $all_module_classes = sloodle_available_modules();
  611. if (count($all_module_classes) == 0) {
  612. return array();
  613. }
  614. $failures = array();
  615. foreach($all_module_classes as $module_class) {
  616. // Find each of the tasks we have to handle for the interaction.
  617. // This information is stored in the object config
  618. $relevant_configs = array();
  619. if (!method_exists($module_class,'RequirementConfigNames')) {
  620. continue;
  621. }
  622. $relevant_config_names = call_user_func(array($module_class, 'RequirementConfigNames'));
  623. if (count($relevant_config_names) == 0) {
  624. continue;
  625. }
  626. foreach($relevant_config_names as $configname) {
  627. $fieldname = $configname.'_'.$interaction;
  628. foreach($all_configs as $c) {
  629. if ($c->name == $fieldname) {
  630. $relevant_configs[ $configname] = $c->value;
  631. }
  632. }
  633. }
  634. if (count($relevant_configs) == 0) {
  635. // Nothing to do here
  636. continue;
  637. }
  638. //SloodleDebugLogger::log('DEBUG', "about to check RequirementFailures for $module_class, ".join($relevant_configs,':') );
  639. $module_failures = call_user_func_array( array($module_class, 'RequirementFailures'), array( $relevant_configs, $controllerid, $multiplier, $userid, $this->uuid ));
  640. //SloodleDebugLogger::log('DEBUG', "done checking RequirementFailures for $module_class");
  641. if($module_failures) {
  642. $failures = array_merge($failures, $module_failures);
  643. }
  644. }
  645. //SloodleDebugLogger::log('DEBUG', "done checking all RequirementFailures ");
  646. return $failures;
  647. }
  648. /*
  649. Modules (awards, tracker etc) may specify that they can handle certain kinds of events.
  650. The object can be configred to trigger those events when particular interactions happen.
  651. For example, the awards module has an event called "sloodleawardsdeposit_numpoints".
  652. If this is called, we should give the user that number of points.
  653. */
  654. public function process_interaction( $interaction, $multiplier, $userid, $useruuid ) {
  655. if ( !$userid = intval($userid) ) {
  656. return false;
  657. }
  658. if ( $multiplier == 0 ) {
  659. return false;
  660. }
  661. if (!$controllerid = intval($this->controllerid) ) {
  662. return false;
  663. }
  664. $all_configs = sloodle_get_records('sloodle_object_config', 'object', $this->id);
  665. if (count($all_configs) == 0) {
  666. return true;
  667. }
  668. $all_module_classes = sloodle_available_modules();
  669. foreach($all_module_classes as $module_class) {
  670. if (!class_exists($module_class)) {
  671. continue;
  672. }
  673. if (!method_exists($module_class, 'ActionConfigNames')) {
  674. continue;
  675. }
  676. // Find each of the tasks we have to handle for the interaction.
  677. // This information is stored in the object config
  678. $relevant_configs = array();
  679. $relevant_config_names = call_user_func(array($module_class, 'ActionConfigNames'));
  680. if (count($relevant_config_names) == 0) {
  681. continue;
  682. }
  683. /*
  684. $relevant_config_names = array(
  685. 'sloodleawardsdeposit_numpoints',
  686. 'sloodleawardsdeposit_currency',
  687. 'sloodleawardswithdraw_numpoints',
  688. 'sloodleawardswithdraw_currency'
  689. );
  690. */
  691. foreach($relevant_config_names as $configname) {
  692. $fieldname = $configname.'_'.$interaction;
  693. //SloodleDebugLogger::log('DEBUG', "looking for confir $fieldname");
  694. foreach($all_configs as $c) {
  695. if ($c->name == $fieldname) {
  696. $relevant_configs[ $configname] = $c->value;
  697. }
  698. }
  699. }
  700. if (!method_exists($module_class, 'ProcessActions')) {
  701. continue;
  702. }
  703. //SloodleDebugLogger::log('DEBUG', "calling ProcessActions for $module_class");
  704. call_user_func_array( array($module_class, 'ProcessActions'), array( $relevant_configs, $controllerid, $multiplier, $userid, $useruuid, $this->uuid ));
  705. }
  706. return true;
  707. }
  708. /*
  709. Notify any active objects that are interested in the action $action.
  710. ...so that an object can tell us what it's interested in hearing about.
  711. @notification_action - the name of whatever action just happened. Interested objects state this in their definition.php.
  712. @success_code - the code to be sent to the object.
  713. @controllerid - the controller we should filter for, if there is one.
  714. @userid - the ID of the user concerned. Probably only used in callbacks.
  715. @params - any parameters to be sent to the objects, in name=>value pairs.
  716. @addtimestampparams - tells us to append a timestamp to the bottom of the message to help the object tell if it's getting the latest message, or if messages are out of order.
  717. @exactlyonce - tells the function to return as soon as it successfully sends. This is used when trying to send an IM to the user, which we only want to do once even if there are multiple objects in-world that could handle it.
  718. */
  719. function NotifySubscriberObjects( $notification_action, $success_code, $controllerid, $userid, $params, $addtimestampparams, $exactlyonce = false ) {
  720. global $CFG;
  721. $interested_object_types = SloodleObjectConfig::TypesOfObjectsRequiringNotification( $notification_action );
  722. //$interested_object_types = array('SLOODLE Scoreboard');
  723. if (count($interested_object_types) == 0) {
  724. // Nobody cares.
  725. // This is a failure if we had to send exactly one message, as with instant message notifications.
  726. // Otherwise it's a success: We notified everybody who cared, which turned out to be nobody.
  727. return !$exactlyonce;
  728. }
  729. $instr = '';
  730. $delim = '';
  731. $queryparams = $controllerid ? array(intval($controllerid)) : array();
  732. foreach($interested_object_types as $ot) {
  733. $queryparams[] = $ot;
  734. $instr .= $delim.'?';
  735. $delim = ',';
  736. }
  737. $queryparams[] = time() - (3600+600); // Ping time and then some. All objects that are alive should have updated within this time.
  738. $sql = "select a.* from {$CFG->prefix}sloodle_active_object a where a.controllerid=? and a.httpinurl IS NOT NULL and a.type in ($instr) and a.timeupdated>? order by a.timeupdated desc;";
  739. if (!$controllerid) {
  740. $sql = "select a.* from {$CFG->prefix}sloodle_active_object a WHERE a.httpinurl IS NOT NULL and a.type in ($instr) and a.timeupdated>? order by a.timeupdated desc;";
  741. }
  742. $recs = sloodle_get_records_sql_params($sql, $queryparams);
  743. $msg = "$success_code\n";
  744. $callback_objects = array();
  745. foreach($recs as $rec) {
  746. $ao = new SloodleActiveObject();
  747. $ao->loadFromRecord( $rec );
  748. /*
  749. If we just have a notify message specified, send a simple NOTIFICATION saying what just happened.
  750. If we want to trigger something more substantial, we should have a notify_callback defined.
  751. This means we'll want to pass control to a function specified in the object's definition.
  752. We'll do these in bulk later, so that if there are a lot of objects that all require the same message they can be done together.
  753. */
  754. $def = $ao->objectDefinition();
  755. if ( isset($def->notify_callbacks) && isset($def->notify_callbacks[$notification_action]) ) {
  756. $callback = $def->notify_callbacks[$notification_action];
  757. if (!isset($callback_objects[ $callback] )) {
  758. $callback_objects[ $callback ] = array();
  759. }
  760. $callback_objects[ $callback ][] = $ao;
  761. continue;
  762. }
  763. /*
  764. There's no callback specified for this object, so just send it a simple message.
  765. */
  766. $response = new SloodleResponse();
  767. $response->set_status_code($success_code);
  768. $response->set_status_descriptor('NOTIFICATION');
  769. $response->set_request_descriptor('NOTIFICATION');
  770. $response->set_http_in_password($ao->httpinpassword);
  771. foreach($params as $n=>$v) {
  772. $response->add_data_line($n.'|'.$v);
  773. }
  774. // We can set extra fields to allow the object to keep track of messages being sent.
  775. // That way if a message fails to get delivered, the lastmessagesendtimestamp won't match what the object remembers
  776. // ...and the object will know it needs to do something to catch up, like polling the server.
  777. $ts = time();
  778. if ($addtimestampparams) {
  779. $response->add_data_line("lastmessagetimestamp|".$ao->lastmessagetimestamp);
  780. $response->add_data_line("messagetimestamp|".$ts);
  781. }
  782. $renderStr="";
  783. $response->render_to_string($renderStr);
  784. // If we're doing exactly once, we'll need to send synchronously so we can check the notification worked.
  785. // Otherwise, send asynchronously if it's turned on.
  786. $async = (defined('SLOODLE_ASYNC_SEND_MESSAGES') && SLOODLE_ASYNC_SEND_MESSAGES && !$exactlyonce);
  787. // If this stuff fails, tough. We did our best.
  788. if ($resarr = $ao->sendMessage($renderStr, $async, $async)) {
  789. if($resarr['info']['http_code'] == 200){
  790. $ao->lastmessagetimestamp = time();
  791. $ao->save();
  792. if ($exactlyonce) {
  793. return true;
  794. }
  795. }
  796. }
  797. }
  798. //SloodleDebugLogger::log('DEBUG', "calling callbacs for ".count($callback_objects)." callbacks");
  799. if (count($callback_objects) > 0) {
  800. foreach($callback_objects as $callback => $aoarr) {
  801. //SloodleDebugLogger::log('DEBUG', "calling callback $callback for ".count($aoarr)." objects");
  802. $result = call_user_func_array( $callback, array( $aoarr, $notification_action, $success_code, $controllerid, $userid, $params, $addtimestampparams ) );
  803. if ($result && $exactlyonce) {
  804. return true;
  805. }
  806. }
  807. }
  808. // If we were supposed to do this exactly once and not have succeeded, that's a failure.
  809. if ($exactlyonce) {
  810. return false;
  811. }
  812. // If we didn't specify the number and nothing went wrong, that's a success, even if we managed to contact zero objects.
  813. return true;
  814. }
  815. // Return objects that say they have the capability to do the task
  816. // Since restricts to how recently they've been active.
  817. // Criteria allows for config filters, eg. by sloodle module id.
  818. //$objects = SloodleActiveObject::ObjectsCapableOfTaskActiveSince('asdf',time()-60,array('sloodlemoduleid',123));
  819. function ObjectsCapableOfTaskActiveSince($task, $sincets, $criteria) {
  820. global $CFG;
  821. $defs = SloodleObjectConfig::DefinitionsOfObjectsCapableOf($task);
  822. if (empty($defs)) {
  823. return array();
  824. }
  825. $instr = '';
  826. $delim = '';
  827. $queryparams = array(); //array(intval($controllerid));
  828. foreach($defs as $ot) {
  829. $queryparams[] = $ot->type();
  830. $instr .= $delim.'?';
  831. $delim = ',';
  832. }
  833. $queryparams[] = $sincets; // time() - (3600+600); // Ping time and then some. All objects that are alive should have updated within this time.
  834. $wherepairs = array();
  835. $criteriastring = '';
  836. if (count($criteria) > 0) {
  837. foreach($criteria as $field => $value) {
  838. $wherepairs[] = "c.name=? and c.value=?";
  839. $queryparams[] = $field;
  840. $queryparams[] = $value;
  841. }
  842. $criteriastring = ' AND '.implode(' AND ', $wherepairs);
  843. }
  844. $sql = '';
  845. if ($criteriastring == '') {
  846. $sql = "select a.* from {$CFG->prefix}sloodle_active_object a where a.httpinurl IS NOT NULL and a.type in ($instr) and a.timeupdated>? $criteriastring order by a.timeupdated desc;";
  847. } else {
  848. $sql = "select a.* from {$CFG->prefix}sloodle_active_object a inner join {$CFG->prefix}sloodle_object_config c on a.id=c.object where a.httpinurl IS NOT NULL and a.type in ($instr) and a.timeupdated>? $criteriastring order by a.timeupdated desc;";
  849. }
  850. $recs = sloodle_get_records_sql_params($sql, $queryparams);
  851. $ret = array();
  852. foreach($recs as $rec) {
  853. $ao = new SloodleActiveObject();
  854. $ao->loadFromRecord( $rec, $lazy = true );
  855. $ret[] = $ao;
  856. }
  857. return $ret;
  858. }
  859. // An array of config objects, keyed by config name
  860. function config_name_config_hash() {
  861. $id = $this->id;
  862. // If the ID is empty, then we have no configuration settings to get
  863. if (empty($id)) return array();
  864. $recs = sloodle_get_records('sloodle_object_config', 'object', $id);
  865. if (!$recs) return false;
  866. $config = array();
  867. foreach ($recs as $r) {
  868. $config[$r->name] = $r;
  869. }
  870. return $config;
  871. }
  872. // An array of config values, keyed by config name
  873. function config_name_value_hash() {
  874. if (!$config_hash = $this->config_name_config_hash()) {
  875. return false;
  876. }
  877. if (count($config_hash) == 0) {
  878. return array();
  879. }
  880. $config = array();
  881. foreach ($config_hash as $r) {
  882. $config[$r->name] = $r->value;
  883. }
  884. return $config;
  885. }
  886. function has_custom_config() {
  887. return ( $this->objectDefinition() != null );
  888. }
  889. function uuids_of_children_missing_since($oldestts, $youngestts) {
  890. global $CFG;
  891. $sql = "select id, uuid from {$CFG->prefix}sloodle_active_object where rezzeruuid=? and timeupdated > ? and timeupdated < ? order by timeupdated asc;";
  892. $recs = sloodle_get_records_sql_params($sql, array($this->uuid, $oldestts, $youngestts));
  893. $uuids = array();
  894. if ( ($recs) && (count($recs) > 0) ) {
  895. foreach($recs as $rec) {
  896. $uuids[] = $rec->uuid;
  897. }
  898. }
  899. return $uuids;
  900. }
  901. function layoutentryids_to_uuids_of_currently_rezzed($layoutid) {
  902. global $CFG;
  903. $sql = "select ao.id as id, ao.layoutentryid as layoutentryid, ao.uuid as uuid from {$CFG->prefix}sloodle_active_object ao inner join {$CFG->prefix}sloodle_layout_entry le on ao.layoutentryid=le.id where ao.rezzeruuid=? and le.layout=? order by timeupdated desc";
  904. $recs = sloodle_get_records_sql_params($sql, array($this->uuid, $layoutid));
  905. $layoutentryhash = array();
  906. if ( ($recs) && (count($recs) > 0) ) {
  907. foreach($recs as $rec) {
  908. $leid = $rec->layoutentryid;
  909. $uuid = $rec->uuid;
  910. if (!isset($layoutentryhash[ $leid ] )){
  911. $layoutentryhash[$leid] = array();
  912. }
  913. $layoutentryhash[$leid][] = $rec->uuid;
  914. }
  915. }
  916. return $layoutentryhash;
  917. }
  918. /*
  919. Take an array of active objects
  920. ...and return only the ones with the specified name/value config.
  921. */
  922. function FilterForConfigNameValue( $aos, $name, $value ) {
  923. global $CFG;
  924. if (!count($aos)) {
  925. return array();
  926. }
  927. $params = array($name, $value);
  928. $placeholders = array();
  929. foreach($aos as $ao) {
  930. if (!$id = intval($ao->id)) {
  931. continue;
  932. }
  933. $params[] = $id;
  934. $placeholders[] = '?';
  935. }
  936. if (!count($placeholders)) {
  937. return array();
  938. }
  939. $query = "select object as objid from {$CFG->prefix}sloodle_object_config where name=? and value=? and object in (".join(',',$placeholders).");";
  940. $recs = sloodle_get_records_sql_params($query, $params);
  941. if (!is_array($recs) || (!count($recs))) {
  942. return array();
  943. }
  944. $wantobjids = array();
  945. foreach($recs as $rec) {
  946. $wantobjids[ $rec->objid ] = true;
  947. }
  948. $ret = array();
  949. foreach($aos as $ao) {
  950. $id = $ao->id;
  951. if (isset($wantobjids[$id])) {
  952. $ret[] = $ao;
  953. }
  954. }
  955. return $ret;
  956. }
  957. }
  958. ?>