1<?php
2
3namespace ceLTIc\LTI\DataConnector;
4
5use ceLTIc\LTI;
6use ceLTIc\LTI\ConsumerNonce;
7use ceLTIc\LTI\Context;
8use ceLTIc\LTI\ResourceLink;
9use ceLTIc\LTI\ResourceLinkShare;
10use ceLTIc\LTI\ResourceLinkShareKey;
11use ceLTIc\LTI\ToolConsumer;
12use ceLTIc\LTI\UserResult;
13
14/**
15 * Class to provide a connection to a persistent store for LTI objects
16 *
17 * This class assumes no data persistence - it should be extended for specific database connections.
18 *
19 * @author  Stephen P Vickers <stephen@spvsoftwareproducts.com>
20 * @copyright  SPV Software Products
21 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3
22 */
23class DataConnector
24{
25
26    /**
27     * Default name for database table used to store tool consumers.
28     */
29    const CONSUMER_TABLE_NAME = 'lti2_consumer';
30
31    /**
32     * Default name for database table used to store contexts.
33     */
34    const CONTEXT_TABLE_NAME = 'lti2_context';
35
36    /**
37     * Default name for database table used to store resource links.
38     */
39    const RESOURCE_LINK_TABLE_NAME = 'lti2_resource_link';
40
41    /**
42     * Default name for database table used to store users.
43     */
44    const USER_RESULT_TABLE_NAME = 'lti2_user_result';
45
46    /**
47     * Default name for database table used to store resource link share keys.
48     */
49    const RESOURCE_LINK_SHARE_KEY_TABLE_NAME = 'lti2_share_key';
50
51    /**
52     * Default name for database table used to store nonce values.
53     */
54    const NONCE_TABLE_NAME = 'lti2_nonce';
55
56    /**
57     * Database connection.
58     *
59     * @var object|resource $db
60     */
61    protected $db = null;
62
63    /**
64     * Prefix for database table names.
65     *
66     * @var string $dbTableNamePrefix
67     */
68    protected $dbTableNamePrefix = '';
69
70    /**
71     * SQL date format (default = 'Y-m-d')
72     *
73     * @var string $dateFormat
74     */
75    protected $dateFormat = 'Y-m-d';
76
77    /**
78     * SQL time format (default = 'H:i:s')
79     *
80     * @var string $timeFormat
81     */
82    protected $timeFormat = 'H:i:s';
83
84    /**
85     * Class constructor
86     *
87     * @param object|resource $db                 Database connection object
88     * @param string $dbTableNamePrefix  Prefix for database table names (optional, default is none)
89     */
90    public function __construct($db, $dbTableNamePrefix = '')
91    {
92        $this->db = $db;
93        $this->dbTableNamePrefix = $dbTableNamePrefix;
94    }
95
96###
97###  ToolConsumer methods
98###
99
100    /**
101     * Load tool consumer object.
102     *
103     * @param ToolConsumer $consumer ToolConsumer object
104     *
105     * @return bool    True if the tool consumer object was successfully loaded
106     */
107    public function loadToolConsumer($consumer)
108    {
109        $consumer->secret = 'secret';
110        $consumer->enabled = true;
111        $now = time();
112        $consumer->created = $now;
113        $consumer->updated = $now;
114
115        return true;
116    }
117
118    /**
119     * Save tool consumer object.
120     *
121     * @param ToolConsumer $consumer Consumer object
122     *
123     * @return bool    True if the tool consumer object was successfully saved
124     */
125    public function saveToolConsumer($consumer)
126    {
127        $consumer->updated = time();
128
129        return true;
130    }
131
132    /**
133     * Delete tool consumer object.
134     *
135     * @param ToolConsumer $consumer Consumer object
136     *
137     * @return bool    True if the tool consumer object was successfully deleted
138     */
139    public function deleteToolConsumer($consumer)
140    {
141        $consumer->initialize();
142
143        return true;
144    }
145
146    /**
147     * Load tool consumer objects.
148     *
149     * @return ToolConsumer[] Array of all defined ToolConsumer objects
150     */
151    public function getToolConsumers()
152    {
153        return array();
154    }
155
156###
157###  Context methods
158###
159
160    /**
161     * Load context object.
162     *
163     * @param Context $context Context object
164     *
165     * @return bool    True if the context object was successfully loaded
166     */
167    public function loadContext($context)
168    {
169        $now = time();
170        $context->created = $now;
171        $context->updated = $now;
172
173        return true;
174    }
175
176    /**
177     * Save context object.
178     *
179     * @param Context $context Context object
180     *
181     * @return bool    True if the context object was successfully saved
182     */
183    public function saveContext($context)
184    {
185        $context->updated = time();
186
187        return true;
188    }
189
190    /**
191     * Delete context object.
192     *
193     * @param Context $context Context object
194     *
195     * @return bool    True if the Context object was successfully deleted
196     */
197    public function deleteContext($context)
198    {
199        $context->initialize();
200
201        return true;
202    }
203
204###
205###  ResourceLink methods
206###
207
208    /**
209     * Load resource link object.
210     *
211     * @param ResourceLink $resourceLink ResourceLink object
212     *
213     * @return bool    True if the resource link object was successfully loaded
214     */
215    public function loadResourceLink($resourceLink)
216    {
217        $now = time();
218        $resourceLink->created = $now;
219        $resourceLink->updated = $now;
220
221        return true;
222    }
223
224    /**
225     * Save resource link object.
226     *
227     * @param ResourceLink $resourceLink ResourceLink object
228     *
229     * @return bool    True if the resource link object was successfully saved
230     */
231    public function saveResourceLink($resourceLink)
232    {
233        $resourceLink->updated = time();
234
235        return true;
236    }
237
238    /**
239     * Delete resource link object.
240     *
241     * @param ResourceLink $resourceLink ResourceLink object
242     *
243     * @return bool    True if the resource link object was successfully deleted
244     */
245    public function deleteResourceLink($resourceLink)
246    {
247        $resourceLink->initialize();
248
249        return true;
250    }
251
252    /**
253     * Get array of user objects.
254     *
255     * Obtain an array of UserResult objects for users with a result sourcedId.  The array may include users from other
256     * resource links which are sharing this resource link.  It may also be optionally indexed by the user ID of a specified scope.
257     *
258     * @param ResourceLink $resourceLink      Resource link object
259     * @param bool         $localOnly True if only users within the resource link are to be returned (excluding users sharing this resource link)
260     * @param int          $idScope     Scope value to use for user IDs
261     *
262     * @return UserResult[] Array of UserResult objects
263     */
264    public function getUserResultSourcedIDsResourceLink($resourceLink, $localOnly, $idScope)
265    {
266        return array();
267    }
268
269    /**
270     * Get array of shares defined for this resource link.
271     *
272     * @param ResourceLink $resourceLink ResourceLink object
273     *
274     * @return ResourceLinkShare[] Array of ResourceLinkShare objects
275     */
276    public function getSharesResourceLink($resourceLink)
277    {
278        return array();
279    }
280
281###
282###  ConsumerNonce methods
283###
284
285    /**
286     * Load nonce object.
287     *
288     * @param ConsumerNonce $nonce Nonce object
289     *
290     * @return bool    True if the nonce object was successfully loaded
291     */
292    public function loadConsumerNonce($nonce)
293    {
294        return false;  // assume the nonce does not already exist
295    }
296
297    /**
298     * Save nonce object.
299     *
300     * @param ConsumerNonce $nonce Nonce object
301     *
302     * @return bool    True if the nonce object was successfully saved
303     */
304    public function saveConsumerNonce($nonce)
305    {
306        return true;
307    }
308
309###
310###  ResourceLinkShareKey methods
311###
312
313    /**
314     * Load resource link share key object.
315     *
316     * @param ResourceLinkShareKey $shareKey ResourceLink share key object
317     *
318     * @return bool    True if the resource link share key object was successfully loaded
319     */
320    public function loadResourceLinkShareKey($shareKey)
321    {
322        return true;
323    }
324
325    /**
326     * Save resource link share key object.
327     *
328     * @param ResourceLinkShareKey $shareKey Resource link share key object
329     *
330     * @return bool    True if the resource link share key object was successfully saved
331     */
332    public function saveResourceLinkShareKey($shareKey)
333    {
334        return true;
335    }
336
337    /**
338     * Delete resource link share key object.
339     *
340     * @param ResourceLinkShareKey $shareKey Resource link share key object
341     *
342     * @return bool    True if the resource link share key object was successfully deleted
343     */
344    public function deleteResourceLinkShareKey($shareKey)
345    {
346        return true;
347    }
348
349###
350###  UserResult methods
351###
352
353    /**
354     * Load user object.
355     *
356     * @param UserResult $userresult UserResult object
357     *
358     * @return bool    True if the user object was successfully loaded
359     */
360    public function loadUserResult($userresult)
361    {
362        $now = time();
363        $userresult->created = $now;
364        $userresult->updated = $now;
365
366        return true;
367    }
368
369    /**
370     * Save user object.
371     *
372     * @param UserResult $userresult UserResult object
373     *
374     * @return bool    True if the user object was successfully saved
375     */
376    public function saveUserResult($userresult)
377    {
378        $userresult->updated = time();
379
380        return true;
381    }
382
383    /**
384     * Delete user object.
385     *
386     * @param UserResult $userresult UserResult object
387     *
388     * @return bool    True if the user object was successfully deleted
389     */
390    public function deleteUserResult($userresult)
391    {
392        $userresult->initialize();
393
394        return true;
395    }
396
397###
398###  Other methods
399###
400
401    /**
402     * Create data connector object.
403     *
404     * A data connector provides access to persistent storage for the different objects.
405     *
406     * Names of tables may be given a prefix to allow multiple versions to share the same schema.  A separate sub-class is defined for
407     * each different database connection - the class to use is determined by inspecting the database object passed, but this can be overridden
408     * (for example, to use a bespoke connector) by specifying a type.  If no database is passed then this class is used which acts as a dummy
409     * connector with no persistence.
410     *
411     * @param object|resource  $db                 A database connection object or string (optional, default is no persistence)
412     * @param string           $dbTableNamePrefix  Prefix for database table names (optional, default is none)
413     * @param string           $type               The type of data connector (optional, default is based on $db parameter)
414     *
415     * @return DataConnector Data connector object
416     */
417    public static function getDataConnector($db = null, $dbTableNamePrefix = '', $type = '')
418    {
419        if (is_null($dbTableNamePrefix)) {
420            $dbTableNamePrefix = '';
421        }
422        if (!is_null($db) && empty($type)) {
423            if (is_object($db)) {
424                $type = get_class($db);
425            } elseif (is_resource($db)) {
426                $type = strtok(get_resource_type($db), ' ');
427            }
428        }
429        $type = strtolower($type);
430        if ($type === 'pdo') {
431            if ($db->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'pgsql') {
432                $type .= '_pgsql';
433            } elseif ($db->getAttribute(\PDO::ATTR_DRIVER_NAME) === 'oci') {
434                $type .= '_oci';
435            }
436        }
437        if (!empty($type)) {
438            $type = "DataConnector_{$type}";
439        } else {
440            $type = 'DataConnector';
441        }
442        $type = "\\ceLTIc\\LTI\\DataConnector\\{$type}";
443        $dataConnector = new $type($db, $dbTableNamePrefix);
444
445        return $dataConnector;
446    }
447
448    /**
449     * Generate a random string.
450     *
451     * The generated string will only comprise letters (upper- and lower-case) and digits.
452     *
453     * @param int $length Length of string to be generated (optional, default is 8 characters)
454     *
455     * @return string Random string
456     */
457    public static function getRandomString($length = 8)
458    {
459        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
460
461        $value = '';
462        $charsLength = strlen($chars) - 1;
463
464        for ($i = 1; $i <= $length; $i++) {
465            $value .= $chars[rand(0, $charsLength)];
466        }
467
468        return $value;
469    }
470
471    /**
472     * Escape a string for use in a database query.
473     *
474     * Any single quotes in the value passed will be replaced with two single quotes.  If a null value is passed, a string
475     * of 'null' is returned (which will never be enclosed in quotes irrespective of the value of the $addQuotes parameter.
476     *
477     * @param string $value     Value to be escaped
478     * @param bool $addQuotes If true the returned string will be enclosed in single quotes (optional, default is true)
479     *
480     * @return string The escaped string.
481     */
482    public function escape($value, $addQuotes = true)
483    {
484        return static::quoted($value, $addQuotes);
485    }
486
487    /**
488     * Quote a string for use in a database query.
489     *
490     * Any single quotes in the value passed will be replaced with two single quotes.  If a null value is passed, a string
491     * of 'null' is returned (which will never be enclosed in quotes irrespective of the value of the $addQuotes parameter.
492     *
493     * @param string $value     Value to be quoted
494     * @param bool $addQuotes If true the returned string will be enclosed in single quotes (optional, default is true)
495     *
496     * @return string The quoted string.
497     */
498    public static function quoted($value, $addQuotes = true)
499    {
500        if (is_null($value)) {
501            $value = 'null';
502        } else {
503            $value = str_replace('\'', '\'\'', $value);
504            if ($addQuotes) {
505                $value = "'{$value}'";
506            }
507        }
508
509        return $value;
510    }
511
512    /**
513     * Return a hash of a consumer key for values longer than 255 characters.
514     *
515     * @param string $key
516     * @return string
517     */
518    protected static function getConsumerKey($key)
519    {
520        $len = strlen($key);
521        if ($len > 255) {
522            $key = 'sha512:' . hash('sha512', $key);
523        }
524
525        return $key;
526    }
527
528}
529