module_awards.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <?php
  2. // This file is part of the Sloodle project (www.sloodle.org)
  3. /**
  4. * This file defines the Sloodle awards module.
  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 Peter R. Bloomfield
  11. * @contributor Paul G. Preibisch - aka Fire Centaur
  12. */
  13. /** The Sloodle module base. */
  14. require_once(SLOODLE_LIBROOT.'/modules/module_base.php');
  15. /** General Sloodle functions. */
  16. require_once(SLOODLE_LIBROOT.'/general.php');
  17. /** SLOODLE course object data structure */
  18. require_once(SLOODLE_LIBROOT.'/sloodlecourseobject.php');
  19. /**
  20. * The Sloodle StipendGiver module class.
  21. * @package sloodle
  22. */
  23. class SloodleModuleAwards extends SloodleModule
  24. {
  25. // DATA //
  26. /**
  27. * Internal for Moodle only - course module instance.
  28. * Corresponds to one record from the Moodle 'course_modules' table.
  29. * @var object
  30. * @access private
  31. */
  32. var $cm = null;
  33. /**
  34. * Internal only - Sloodle module instance database object.
  35. * Corresponds to one record from the Moodle 'sloodle' table.
  36. * @var object
  37. * @access private
  38. */
  39. var $sloodle_module_instance = null;
  40. /**
  41. * Internal only - Sloodle StipendGiver instance database object.
  42. * Corresponds to one record from the Moodle 'sloodle_awards' table.
  43. * @var object
  44. * @access private
  45. */
  46. var $sloodle_awards_instance = null;
  47. var $sCourseObj = null;
  48. var $sloodleid = null;
  49. // FUNCTIONS //
  50. /**
  51. * Constructor
  52. */
  53. function SloodleModuleAwards(&$_session)
  54. {
  55. $constructor = get_parent_class($this);
  56. parent::$constructor($_session);
  57. }
  58. /**
  59. * Loads data from the database.
  60. * Note: even if the function fails, it may still have overwritten some or all existing data in the object.
  61. * @param mixed $id The site-wide unique identifier for all modules. Type depends on VLE. On Moodle, it is an integer course module identifier ('id' field of 'course_modules' table)
  62. * @return bool True if successful, or false otherwise
  63. */
  64. function load($id)
  65. {
  66. // Make sure the ID is valid
  67. if (!is_int($id) || $id <= 0) return false;
  68. // Fetch the course module data
  69. if (!($this->cm = get_coursemodule_from_id('sloodle', $id))) return false;
  70. // Load from the primary table: Sloodle instance
  71. if (!($this->sloodle_module_instance = sloodle_get_record('sloodle', 'id', $this->cm->instance))) return false;
  72. // Load from the primary table: sloodle instance
  73. if (!($this->sloodle_instance = sloodle_get_record('sloodle', 'id', $this->cm->instance))) {
  74. sloodle_debug("Failed to load Sloodle module with instance ID #{$cm->instance}.<br/>");
  75. return false;
  76. }
  77. if ($this->sloodle_module_instance->type != SLOODLE_TYPE_AWARDS) return false;
  78. // Load from the secondary table: StipendGiver instance
  79. if (!($this->sloodle_awards_instance = sloodle_get_record('sloodle_awards', 'sloodleid', $this->cm->instance))) return false;
  80. return true;
  81. }
  82. function ProcessActions( $relevant_configs, $controllerid, $multiplier, $userid, $useruuid, $objuuid) {
  83. global $CFG;
  84. $controller = new SloodleController();
  85. if (!$controller->load_by_course_module_id($controllerid)) {
  86. return false;
  87. }
  88. if (!$roundid = $controller->get_active_roundid(true)) {
  89. return false;
  90. }
  91. $time = time();
  92. if ( isset($relevant_configs['sloodleawardsdeposit_numpoints']) && isset($relevant_configs['sloodleawardsdeposit_currency']) ) {
  93. $numpoints = intval($relevant_configs['sloodleawardsdeposit_numpoints']);
  94. $currencyid = intval($relevant_configs['sloodleawardsdeposit_currency']);
  95. if (!$currencyid) {
  96. return false;
  97. }
  98. $award = new stdClass();
  99. $award->userid = $userid;
  100. $award->currencyid = $currencyid;
  101. $award->amount = $numpoints * $multiplier;
  102. $award->timeawarded = $time;
  103. $award->roundid = $roundid;
  104. if ( !sloodle_insert_record('sloodle_award_points', $award) ) {
  105. return false;
  106. }
  107. $need_notify = true;
  108. // Notify any active objects that care about changes to scores
  109. // We'll check this in the object definition.
  110. // TODO: We should probably do this with a notification_request table,
  111. // ...where the scoreboard etc will register with Sloodle that it's interested in certain events.
  112. // TODO: Figure out the round/controller filters
  113. $user_point_total_recs = sloodle_get_records_sql_params( "select sum(amount) as balance from {$CFG->prefix}sloodle_award_points where currencyid=? and roundid=? and userid=?;", array($currencyid, $roundid, $userid));
  114. if (!$user_point_total_recs && (count($user_point_total_recs) == 0 ) ) {
  115. return false;
  116. }
  117. $first_rec = array_shift($user_point_total_recs);
  118. $balance = $first_rec->balance;
  119. // This sends a single points change notification to an object.
  120. // It's used by the shared media scoreboard to prompt a re-fetch of score data.
  121. SloodleModuleAwards::NotifyRealtimeBroadcastClients('awards_points_change', 10601, $controllerid, $userid, array('balance' => $balance, 'roundid' => $roundid, 'userid' => $userid, 'currencyid' => $currencyid, 'timeawarded' => $time ) );;
  122. SloodleActiveObject::NotifySubscriberObjects( 'awards_points_change', 10601, $controllerid, $userid, array('balance' => $balance, 'roundid' => $roundid, 'userid' => $userid, 'currencyid' => $currencyid, 'timeawarded' => $time ) );
  123. }
  124. if ( isset($relevant_configs['sloodleawardswithdraw_numpoints']) && isset($relevant_configs['sloodleawardswithdraw_currency']) ) {
  125. $numpoints = intval($relevant_configs['sloodleawardswithdraw_numpoints']) * -1;
  126. $currencyid = intval($relevant_configs['sloodleawardswithdraw_currency']);
  127. if (!$currencyid) {
  128. return false;
  129. }
  130. $award = new stdClass();
  131. $award->userid = $userid;
  132. $award->currencyid = $currencyid;
  133. $award->amount = $numpoints * $multiplier;
  134. $award->timeawarded = $time;
  135. $award->roundid = $roundid;
  136. if ( !sloodle_insert_record('sloodle_award_points', $award) ) {
  137. return false;
  138. }
  139. $need_notify = true;
  140. // Notify any active objects that care about changes to scores
  141. // We'll check this in the object definition.
  142. // TODO: We should probably do this with a notification_request table,
  143. // ...where the scoreboard etc will register with Sloodle that it's interested in certain events.
  144. // TODO: Figure out the round/controller filters
  145. $user_point_total_recs = sloodle_get_records_sql_params( "select sum(amount) as balance from {$CFG->prefix}sloodle_award_points where currencyid=? and roundid=? and userid=?;", array($currencyid, $roundid, $userid));
  146. if (!$user_point_total_recs && (count($user_point_total_recs) == 0 ) ) {
  147. return false;
  148. }
  149. $first_rec = array_shift($user_point_total_recs);
  150. $balance = $first_rec->balance;
  151. // This sends a single points change notification to an object.
  152. // It's used by the shared media scoreboard to prompt a re-fetch of score data.
  153. SloodleModuleAwards::NotifyRealtimeBroadcastClients('awards_points_change', 10601, $controllerid, $userid, array('balance' => $balance, 'roundid' => $roundid, 'userid' => $userid, 'currencyid' => $currencyid, 'timeawarded' => $time ) );;
  154. SloodleActiveObject::NotifySubscriberObjects( 'awards_points_change', 10601, $controllerid, $userid, array('balance' => $balance, 'roundid' => $roundid, 'userid' => $userid, 'currencyid' => $currencyid, 'timeawarded' => $time ) );
  155. }
  156. return true;
  157. }
  158. function UserCurrencyBalance( $userid, $currencyid) {
  159. global $CFG;
  160. $userid = intval($userid);
  161. $currencyid = intval($currencyid);
  162. $balancesql = "select sum(amount) as balance from {$CFG->prefix}sloodle_award_points where userid = ? and currencyid = ?";
  163. $results = sloodle_get_records_sql_params( $balancesql, array($userid, $currencyid) );
  164. if (!$results || ( count($results) == 0) ) {
  165. //SloodleDebugLogger::log('DEBUG', "nothing found for $balancesql");
  166. return 0;
  167. }
  168. $result = array_shift($results);
  169. //SloodleDebugLogger::log('DEBUG', "balance for sql $balancesql was ".$result->balance);
  170. return $result->balance;
  171. }
  172. /*
  173. Returns an array of error messages for requirements that haven't been satisfied.
  174. ...eg. If an object has been configured to require 3 gold coins, and the user doesn't have enough, it'll return a message saying you don't have enough gold coins.
  175. */
  176. function RequirementFailures( $relevant_configs, $controllerid, $multiplier, $userid, $useruuid ) {
  177. //SloodleDebugLogger::log('DEBUG', "in ProcessRequirements");
  178. global $CFG;
  179. $minimum_balances = array();
  180. $failures = array();
  181. if ( isset($relevant_configs['sloodleawardsrequire_numpoints']) && isset($relevant_configs['sloodleawardsrequire_currency']) ) {
  182. $numpoints = intval($relevant_configs['sloodleawardsrequire_numpoints']);
  183. $currencyid = intval($relevant_configs['sloodleawardsrequire_currency']);
  184. if ($currencyid && $numpoints) {
  185. if ( SloodleModuleAwards::UserCurrencyBalance($userid, $currencyid) < $numpoints ) {
  186. if (isset($relevant_configs['sloodleawardsrequire_notenoughmessage']) && $relevant_configs['sloodleawardsrequire_notenoughmessage'] != '') {
  187. $failures = array_merge( $failures, array($relevant_configs['sloodleawardsrequire_notenoughmessage']));
  188. } else {
  189. $failures = array_merge( $failures, array(get_string('awards:notenoughcurrency', 'sloodle')) );
  190. }
  191. }
  192. }
  193. }
  194. if ( isset($relevant_configs['sloodleawardswithdraw_numpoints']) && isset($relevant_configs['sloodleawardswithdraw_currency']) ) {
  195. $numpoints = intval($relevant_configs['sloodleawardswithdraw_numpoints']);
  196. $currencyid = intval($relevant_configs['sloodleawardswithdraw_currency']);
  197. if ($currencyid && $numpoints) {
  198. if ( SloodleModuleAwards::UserCurrencyBalance($userid, $currencyid) < $numpoints ) {
  199. if (isset($relevant_configs['sloodleawardswithdraw_notenoughmessage']) && $relevant_configs['sloodleawardswithdraw_notenoughmessage'] != '') {
  200. $failures = array_merge( $failures, array($relevant_configs['sloodleawardswithdraw_notenoughmessage']));
  201. } else {
  202. $failures = array_merge( $failures, array(get_string('awards:notenoughcurrency', 'sloodle')) );
  203. }
  204. }
  205. }
  206. }
  207. return $failures;
  208. }
  209. /*
  210. An array of the names of config parameters that are understood by this module to mean it should do something.
  211. Will have the name of the specific interaction appended to it.
  212. eg. awards makes available an interaction config called "sloodleawardsdeposit_numpoints".
  213. The quiz would then have a config name=>value pair like sloodleawards_deposit_numpoints_answerquestion => 3
  214. Via the ActiveObject, the quiz will tell the awards module that answerquestion has happened to a particular user
  215. ...and the awards module will give them the points.
  216. */
  217. function ActionConfigNames() {
  218. return array(
  219. 'sloodleawardsdeposit_numpoints',
  220. 'sloodleawardsdeposit_currency',
  221. 'sloodleawardswithdraw_numpoints',
  222. 'sloodleawardswithdraw_currency'
  223. );
  224. }
  225. /*
  226. An array of the names of config parameters that are understood by this module to check something before doing whatever it would normally do.
  227. Will have the name of the specific interaction appended to it.
  228. */
  229. function RequirementConfigNames() {
  230. return array(
  231. 'sloodleawardsrequire_numpoints',
  232. 'sloodleawardsrequire_currency',
  233. 'sloodleawardsrequire_notenoughmessage',
  234. 'sloodleawardswithdraw_numpoints',
  235. 'sloodleawardswithdraw_currency',
  236. 'sloodleawardswithdraw_notenoughmessage'
  237. );
  238. }
  239. /**
  240. * Gets the short type name of this instance.
  241. * @return string
  242. */
  243. function get_type()
  244. {
  245. return 'awards';
  246. }
  247. /**
  248. * Gets the full type name of this instance, according to the current language pack, if available.
  249. * Note: should be overridden by sub-classes.
  250. * @return string Full type name if possible, or the short name otherwise.
  251. */
  252. function get_type_full()
  253. {
  254. return get_string('modulename', 'awards');
  255. }
  256. /*
  257. Prepare a page full of scoreboard results and send it to the specified active objects.
  258. This was originally designed for the zztext scoreboard.
  259. If it finds a config parameter called sloodleactivepage it will limit to that page.
  260. */
  261. function PageScoreMessage( $aoarr, $notification_action, $success_code, $controllerid, $userid, $params, $addtimestampparams ) {
  262. global $CFG;
  263. if (count($aoarr) == 0) {
  264. return true;
  265. }
  266. $roundid = $params['roundid'];
  267. $currencyid = $params['currencyid'];
  268. $currencyname = '';
  269. $aoarr = SloodleActiveObject::FilterForConfigNameValue( $aoarr, 'sloodlecurrencyid', $currencyid);
  270. if ($currencyid) {
  271. // Ge the name of the currency
  272. $currency_recs = sloodle_get_records_sql_params( "select id, name as currency_name from {$CFG->prefix}sloodle_currency_types where id = ?;", array($currencyid));
  273. $currency_rec = array_shift($currency_recs);
  274. $currencyname = $currency_rec->currency_name;
  275. }
  276. $user_point_total_recs = sloodle_get_records_sql_params( "select p.userid as userid, sum(amount) as balance, su.avname as avname, su.uuid as uuid from {$CFG->prefix}sloodle_award_points p inner join {$CFG->prefix}sloodle_users su on p.userid=su.userid where currencyid=? and roundid=? group by userid order by balance desc;", array($currencyid, $roundid));
  277. foreach($aoarr as $ao) {
  278. $response = new SloodleResponse();
  279. //$response->set_status_code($success_code);
  280. $response->set_status_code(1639271140);
  281. $response->set_status_descriptor('NOTIFICATION');
  282. $response->set_request_descriptor('NOTIFICATION');
  283. $response->set_http_in_password($ao->httpinpassword);
  284. $def = $ao->objectDefinition();
  285. $linesperpage = 10;
  286. if ( isset($def->fixed_parameters) && isset($def->fixed_parameters['linesperpage']) ) {
  287. $linesperpage = $def->fixed_parameters['linesperpage'];
  288. }
  289. $charactersperline = 40;
  290. if ( isset($def->fixed_parameters) && isset($def->fixed_parameters['charactersperline']) ) {
  291. $charactersperline = $def->fixed_parameters['charactersperline'];
  292. }
  293. $page = $ao->config_value( 'sloodleactivepage' );
  294. if (!$page) {
  295. $page = 1;
  296. }
  297. $scoreboardtitle= $ao->config_value('sloodleobjecttitle');
  298. /*
  299. foreach($params as $n=>$v) {
  300. $response->add_data_line($n.'|'.$v);
  301. }
  302. */
  303. $num_pages = ceil( count($user_point_total_recs) / $linesperpage );
  304. $response->add_data_line("status|$page|$num_pages|$scoreboardtitle||$currencyid|$currencyname");
  305. $offset = ( ($page-1) * $linesperpage );
  306. $page_points = array_slice( $user_point_total_recs, $offset, $linesperpage );
  307. foreach($page_points as $up) {
  308. $uuid = $up->uuid;
  309. $avname = $up->avname;
  310. $score = $up->balance;
  311. $availablechars = $charactersperline - ( strlen($score) + 1 ); // leave room for a space folowed by the score
  312. $displayavname = substr($avname, 0, $availablechars); // truncate the name if it's too long to fit.
  313. $displayavname = str_pad( $displayavname, $availablechars, " ");
  314. $displayline = $displayavname." ".$score;
  315. $response->add_data_line("$uuid|$score|$displayline");
  316. }
  317. $renderStr="";
  318. $response->render_to_string($renderStr);
  319. $async = ( defined('SLOODLE_ASYNC_SEND_MESSAGES') && SLOODLE_ASYNC_SEND_MESSAGES );
  320. // If this stuff fails, tough. We did our best.
  321. if ($resarr = $ao->sendMessage($renderStr, $async, $async)) {
  322. if($resarr['info']['http_code'] == 200){
  323. $ao->lastmessagetimestamp = time();
  324. $ao->save();
  325. }
  326. }
  327. }
  328. return true;
  329. }
  330. function NotifyRealtimeBroadcastClients($event_type, $some_code, $controllerid, $userid, $currencyid, $params) {
  331. //
  332. /*Params should look like:
  333. array('balance' => $balance, 'roundid' => $roundid, 'userid' => $userid, 'currencyid' => $currencyid, 'timeawarded' => $time )
  334. */
  335. if (!defined('SLOODLE_REALTIME_BROADCAST_SERVER_UPDATE') || SLOODLE_REALTIME_BROADCAST_SERVER_UPDATE == '') {
  336. return false;
  337. }
  338. global $CFG;
  339. $currencyid = isset($params['currencyid']) ? intval($params['currencyid']) : 0;
  340. $controllerid = isset($params['controllerid']) ? intval($params['controllerid']) : 0;
  341. $useruuid = SloodleUser::AvatarUUIDForUserID($userid);
  342. SloodleDebugLogger::log('DEBUG', "got user uuid $useruuid for user $userid");
  343. $site = $CFG->wwwroot;
  344. $room = $site.'/scores/avatar/'.$useruuid.'/controller/'.$controllerid;
  345. if (preg_match('#^(http|https)://(.*)$#', $room, $matches)) {
  346. $room = $matches[2];
  347. }
  348. $payloadobj = new stdClass();
  349. $payloadobj->currencyid = $currencyid;
  350. $payloadobj->points = $points;
  351. $payloadjson = json_encode($payloadobj);
  352. $url = SLOODLE_REALTIME_BROADCAST_SERVER_UPDATE.'/?cmd=update&uuid='.urlencode($useruuid).'&payload='.urlencode($payloadjson);
  353. SloodleDebugLogger::log('DEBUG', "sending to node server at url $url");
  354. $ch = curl_init(); // initialize curl handle
  355. curl_setopt($ch, CURLOPT_URL, $url); // set url to post to
  356. curl_setopt($ch, CURLOPT_FAILONERROR,0);
  357. curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // return into a variable
  358. curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  359. // TODO:
  360. //curl_setopt($ch, CURLOPT_POST, 1); // set POST method
  361. //curl_setopt($ch, CURLOPT_POSTFIELDS,$msg); // add POST fields
  362. $result = curl_exec($ch); // run the whole process
  363. $info = curl_getinfo($ch);
  364. curl_close($ch);
  365. return array('info'=>$info,'result'=>$result);
  366. }
  367. }
  368. ?>