Completed
Push — master ( e2bffb...4608b7 )
by Seth
05:07 queued 03:26
created

Toolbox::resetSession()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
c 0
b 0
f 0
rs 9.4285
cc 2
eloc 9
nc 2
nop 0
1
<?php
2
/** Toolbox class */
3
4
namespace smtech\ReflexiveCanvasLTI;
5
6
use mysqli;
7
use Serializable;
8
9
use Log;
10
11
use Battis\AppMetadata;
12
use Battis\ConfigXML;
13
use Battis\DataUtilities;
14
15
use smtech\CanvasPest\CanvasPest;
16
use smtech\ReflexiveCanvasLTI\LTI\ToolProvider;
17
use smtech\ReflexiveCanvasLTI\Exception\ConfigurationException;
18
use smtech\LTI\Configuration\Generator;
19
use smtech\LTI\Configuration\LaunchPrivacy;
20
use smtech\LTI\Configuration\Exception\ConfigurationException as LTIConfigGeneratorException;
21
22
/**
23
 * A toolbox of tools for quickly constructing LTI tool providers that hook
24
 * back into the Canvas API reflexively.
25
 *
26
 * The basic idea is that you need an XML configuration file of credentials and
27
 * use that to instantiate the Toolbox. The toolbox can then perform LTI
28
 * authentication, handle API requests, generate an LTI Configuration XML file,
29
 * etc. for you.
30
 *
31
 * @author Seth Battis
32
 * @version v1.0
33
 */
34
class Toolbox implements Serializable
35
{
36
    /** Default level of information-sharing privacy between consumer and provider */
37
    const DEFAULT_LAUNCH_PRIVACY = 'public';
38
39
    /** Name of the database table backing the tool metadata */
40
    const TOOL_METADATA_TABLE = 'tool_metadata';
41
42
    /** The path to the configuration file from which this toolbox was generated */
43
    const TOOL_CONFIG_FILE = 'TOOL_CONFIG_FILE';
44
45
    /**
46
     * The (ideally globally unique) identifier for the LTI tool provider
47
     */
48
    const TOOL_ID = 'TOOL_ID';
49
50
    /** The human-readable name of the tool */
51
    const TOOL_NAME = 'TOOL_NAME';
52
53
    /** The human-readable description of the tool */
54
    const TOOL_DESCRIPTION = 'TOOL_DESCRIPTION';
55
56
    /** The URL of the tool's icon image (if present) */
57
    const TOOL_ICON_URL = 'TOOL_ICON_URL';
58
59
    /** The domain from which Tool Consumer requests may emanate for the tool */
60
    const TOOL_DOMAIN = 'TOOL_DOMAIN';
61
62
    /** The URL of the script that will handle LTI authentication */
63
    const TOOL_LAUNCH_URL = 'TOOL_LAUNCH_URL';
64
65
    /** The level of information sharing between the LMS and the tool */
66
    const TOOL_LAUNCH_PRIVACY = 'TOOL_LAUNCH_PRIVACY';
67
68
    /** The path to the tool's log file */
69
    const TOOL_LOG = 'TOOL_LOG';
70
71
    /** An associative array of LTI request types and the URL that handles that request. */
72
    const TOOL_HANDLER_URLS = 'TOOL_HANDLER_URLS';
73
74
    /** An associative array of Canvas API credentials (`url` and `token`) */
75
    const TOOL_CANVAS_API = 'TOOL_CANVAS_API';
76
77
    /**
78
     * Persistent metadata storage
79
     * @var AppMetadata
80
     * @see Toolbox::config() Toolbox::config()
81
     */
82
    protected $metadata = false;
83
84
    /**
85
     * Object-oriented access to the Canvas API
86
     * @var CanvasPest
87
     */
88
    protected $api = false;
89
90
    /**
91
     * MySQL database connection
92
     * @var mysqli
93
     */
94
    protected $mysql = false;
95
96
    /**
97
     * LTI Tool Provider for handling authentication and consumer/user management
98
     * @var ToolProvider
99
     */
100
    protected $toolProvider;
101
102
    /**
103
     * Generator for LTI Configuration XML files
104
     * @var Generator
105
     */
106
    protected $generator;
107
108
    /**
109
     * Log file manager
110
     * @var Log
111
     */
112
    protected $logger = false;
113
114
    /**
115
     * Queue of delayed log messages (waiting for a logger instance)
116
     * @return array
117
     */
118
    protected $logQueue = [];
119
120
    /**
121
     * Provide serialization support for the Toolbox
122
     *
123
     * This allows a Toolbox to be stored in the `$_SESSION` variables.
124
     *
125
     * Caveat emptor: because `mysqli` objects can not be serialized,
126
     * serialization is limited to storing a reference to the configuration file
127
     * that generated this object, which will be reaccessed (along with cached
128
     * configuration metadata) when the object is unserialized.
129
     *
130
     * @return string
131
     */
132
    public function serialize()
133
    {
134
        return serialize([
135
            'config' => $this->config(static::TOOL_CONFIG_FILE)
136
        ]);
137
    }
138
139
    /**
140
     * Provide serialization support for Toolbox
141
     *
142
     * This allows a Toolbox to be stored in the `$_SESSION` variables.
143
     *
144
     * @see Toolbox::serialize() `Toolbox::serialize()` has more information on the
145
     *      specifics of the serialization approach.
146
     *
147
     * @param  string $serialized A Toolbox object serialized by `Toolbox::serialize()`
148
     * @return Toolbox
149
     */
150
    public function unserialize($serialized)
151
    {
152
        $data = unserialize($serialized);
153
        $this->loadConfiguration($data['config']);
154
    }
155
156
    /**
157
     * Create a Toolbox instance from a configuration file
158
     *
159
     * @param  string $configFilePath Path to the configuration file
160
     * @param  boolean $forceRecache Whether or not to rely on cached
161
     *     configuration metadata or to force a refresh from the configuration
162
     *     file
163
     * @return Toolbox
164
     */
165
    public static function fromConfiguration($configFilePath, $forceRecache = false)
166
    {
167
        return new static($configFilePath, $forceRecache);
168
    }
169
170
    /**
171
     * Construct a Toolbox instance from a configuration file
172
     *
173
     * @see Toolbox::fromConfiguration() Use `Toolbox::fromConfiguration()`
174
     *
175
     * @param string $configFilePath
176
     * @param boolean $forceRecache
177
     */
178
    private function __construct($configFilePath, $forceRecache = false)
179
    {
180
        $this->loadConfiguration($configFilePath, $forceRecache);
181
    }
182
183
    /**
184
     * Update a Toolbox instance from a configuration file
185
     *
186
     * @see Toolbox::fromConfiguration() Use `Toolbox::fromConfiguration()`
187
     *
188
     * @param  string $configFilePath
189
     * @param  boolean $forceRecache
190
     * @return void
191
     */
192
    protected function loadConfiguration($configFilePath, $forceRecache = false)
193
    {
194
        if ($forceRecache) {
195
            $this->log("Resetting LTI configuration from $configFilePath");
196
            $this->config(static::TOOL_CONFIG_FILE, realpath($configFilePath));
197
        }
198
199
        /* load the configuration file */
200
        $config = new ConfigXML($configFilePath);
201
202
        /* configure database connections */
203
        $this->setMySQL($config->newInstanceOf(mysqli::class, '/config/mysql'));
204
205
        /* configure metadata caching */
206
        $id = $config->toString('/config/tool/id');
207
        if (empty($id)) {
208
            $id = basename(dirname($configFilePath)) . '_' . md5(__DIR__ . file_get_contents($configFilePath));
209
            $this->log("    Automatically generated ID $id");
210
        }
211
        $this->setMetadata(new AppMetadata($this->mysql, $id, self::TOOL_METADATA_TABLE));
212
213
        /* update metadata */
214
        if ($forceRecache ||
215
            empty($this->config(static::TOOL_ID)) ||
216
            empty($this->config(static::TOOL_LAUNCH_URL)) ||
217
            empty($this->config(static::TOOL_CONFIG_FILE))) {
218
            $this->configToolMetadata($config, $id);
219
        }
220
221
        /* configure logging */
222
        if ($forceRecache || empty($this->config(static::TOOL_LOG))) {
223
            $this->configLog($config);
224
        }
225
        $this->setLog(Log::singleton('file', $this->config(static::TOOL_LOG)));
226
227
        /* configure tool provider */
228
        if ($forceRecache || empty($this->config(static::TOOL_HANDLER_URLS))) {
229
            $this->configToolProvider($config);
230
        }
231
232
        /* configure API access */
233
        if ($forceRecache || empty($this->config(static::TOOL_CANVAS_API))) {
234
            $this->configApi($config);
235
        }
236
    }
237
238
    /**
239
     * Configure the tool metadata from a configuration file
240
     * @param ConfigXML $config Configuration file object
241
     * @param string $id Unique, potentially auto-generated tool ID
242
     * @return void
243
     */
244
    protected function configToolMetadata(ConfigXML $config, $id)
245
    {
246
        $tool = $config->toArray('/config/tool')[0];
247
248
        $this->config(static::TOOL_ID, $id);
249
        $this->config(static::TOOL_NAME, (empty($tool['name']) ? $id : $tool['name']));
250
        $configPath = dirname($this->config(static::TOOL_CONFIG_FILE));
251
252
        $params = [
253
            'description' => static::TOOL_DESCRIPTION,
254
            'icon' => static::TOOL_ICON_URL,
255
            'domain' => static::TOOL_DOMAIN,
256
            'launch-privacy' => static::TOOL_LAUNCH_PRIVACY
257
        ];
258
259
        foreach ($params as $src => $dest) {
260
            if (empty($tool[$src])) {
261
                $this->clearConfig($dest);
262
            } else {
263
                if (substr($dest, -4) == '_URL') {
264
                    $this->config($dest, DataUtilities::URLfromPath(
265
                        $tool[$src],
266
                        $_SERVER,
267
                        $configPath)
268
                    );
269
                } else {
270
                    $this->config($dest, $tool[$src]);
271
                }
272
            }
273
        }
274
275
        if (empty($this->config(static::TOOL_LAUNCH_PRIVACY))) {
276
            $this->config(static::TOOL_LAUNCH_PRIVACY, static::DEFAULT_LAUNCH_PRIVACY);
277
        }
278
279
        if (empty($this->config(static::TOOL_LAUNCH_URL))) {
280
            $this->config(static::TOOL_LAUNCH_URL, DataUtilities::URLfromPath($_SERVER['SCRIPT_FILENAME']));
281
        }
282
283
        $this->log('Tool metadata configured');
284
    }
285
286
    /**
287
     * Configure the logger object from a configuration file
288
     *
289
     * This will also flush any backlog of queued messages that have been
290
     * waiting for a logger object to be ready.
291
     *
292
     * @param ConfigXML $config Configuration file object
293
     * @return void
294
     */
295
    protected function configLog(ConfigXML $config)
296
    {
297
        $configPath = dirname($this->config(static::TOOL_CONFIG_FILE));
298
        $log = "$configPath/" . $config->toString('/config/tool/log');
299
        shell_exec("touch \"$log\"");
300
        $this->config(static::TOOL_LOG, realpath($log));
301
        $this->flushLogQueue();
302
    }
303
304
    /**
305
     * Configure tool provider object from configuration file
306
     * @param ConfigXML $config Configuration file object
307
     * @return void
308
     */
309
    protected function configToolProvider(ConfigXML $config)
310
    {
311
        $handlers = $config->toArray('/config/tool/handlers')[0];
312
        if (empty($handlers) || !is_array($handlers)) {
313
            throw new ConfigurationException(
314
                'At least one handler/URL pair must be specified',
315
                ConfigurationException::TOOL_PROVIDER
316
            );
317
        }
318
        foreach ($handlers as $request => $path) {
319
            $handlers[$request] = DataUtilities::URLfromPath(
320
                dirname($this->config(static::TOOL_CONFIG_FILE)) . "/$path"
321
            );
322
        }
323
        $this->config(static::TOOL_HANDLER_URLS, $handlers);
324
        $this->log('Tool provider handler URLs configured');
325
    }
326
327
    /**
328
     * Configure API access object from configuration file
329
     * @param ConfigXML $config Configuration file object
330
     * @return void
331
     */
332
    protected function configApi(ConfigXML $config)
333
    {
334
        $this->config(static::TOOL_CANVAS_API, $config->toArray('/config/canvas')[0]);
335
        if (empty($this->config(static::TOOL_CANVAS_API))) {
336
            throw new ConfigurationException(
337
                'Canvas API credentials must be provided',
338
                ConfigurationException::CANVAS_API_MISSING
339
            );
340
        }
341
        $this->log('Canvas API credentials configured');
342
    }
343
344
    /**
345
     * Update toolbox configuration metadata object
346
     *
347
     * @param AppMetadata $metadata
348
     */
349
    public function setMetadata(AppMetadata $metadata)
350
    {
351
        $this->metadata = $metadata;
352
    }
353
354
    /**
355
     * Get the toolbox configuration metadata object
356
     *
357
     * @return AppMetadata
358
     */
359
    public function getMetadata()
360
    {
361
        return $this->metadata;
362
    }
363
364
    /**
365
     * Access or update a specific configuration metadata key/value pair
366
     *
367
     * The `TOOL_*` constants refer to keys used by the Toolbox by default.
368
     *
369
     * @param  string $key The metadata key to look up/create/update
370
     * @param  mixed $value (Optional) If not present (or `null`), the current
371
     *     metadata is returned. If present, the metadata is created/updated
372
     * @return mixed If not updating the metadata, the metadata (if any)
373
     *     currently stored
374
     */
375
    public function config($key, $value = null)
376
    {
377
        if ($value !== null) {
378
            $this->metadata[$key] = $value;
379
        } elseif (isset($this->metadata[$key])) {
380
            return $this->metadata[$key];
381
        } else {
382
            return null;
383
        }
384
    }
385
386
    /**
387
     * Wipe a particular configuration key from storage
388
     * @param string $key
389
     * @return boolean `TRUE` if cleared, `FALSE` if not found
390
     */
391
    public function clearConfig($key)
392
    {
393
        if (isset($this->metadata[$key])) {
394
            unset($this->metadata[$key]);
395
            return true;
396
        }
397
        return false;
398
    }
399
400
401
    /**
402
     * Reset PHP session
403
     *
404
     * Handy for starting LTI authentication. Resets the session and stores a
405
     * reference to this toolbox object in `$_SESSION[Toolbox::class]`.
406
     *
407
     * @link http://stackoverflow.com/a/14329752 StackOverflow discussion
408
     *
409
     * @return void
410
     */
411
    public function resetSession()
412
    {
413
        /*
414
         * TODO not in love with suppressing errors
415
         */
416
        if (session_status() !== PHP_SESSION_ACTIVE) {
417
            @session_start();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
418
        }
419
        @session_destroy();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
420
        @session_unset();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
421
        @session_start();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
422
        session_regenerate_id(true);
423
        $_SESSION[__CLASS__] =& $this;
424
        session_write_close();
425
    }
426
427
    /**
428
     * Update the ToolProvider object
429
     *
430
     * @param ToolProvider $toolProvider
431
     */
432
    public function setToolProvider(ToolProvider $toolProvider)
433
    {
434
        $this->toolProvider = $toolProvider;
435
    }
436
437
    /**
438
     * Get the ToolProvider object
439
     *
440
     * This does some just-in-time initialization, so that if the ToolProvider
441
     * has not yet been accessed, it will be instantiated and initialized by this
442
     * method.
443
     *
444
     * @return ToolProvider
445
     */
446
    public function getToolProvider()
447
    {
448
        if (empty($this->toolProvider)) {
449
            $this->setToolProvider(
450
                new ToolProvider(
451
                    $this->mysql,
452
                    $this->metadata['TOOL_HANDLER_URLS']
453
                )
454
            );
455
        }
456
        return $this->toolProvider;
457
    }
458
459
    /**
460
     * Authenticate an LTI launch request
461
     *
462
     * @return void
463
     * @codingStandardsIgnoreStart
464
     */
465
    public function lti_authenticate()
466
    {
467
        /* @codingStandardsIgnoreEnd */
468
        $this->getToolProvider()->handle_request();
469
    }
470
471
    /**
472
     * Are we (or should we be) in the midst of authenticating an LTI launch request?
473
     *
474
     * @return boolean
475
     * @codingStandardsIgnoreStart
476
     */
477
    public function lti_isLaunching()
478
    {
479
        /* @codingStandardsIgnoreEnd */
480
        return !empty($_POST['lti_message_type']);
481
    }
482
483
    /**
484
     * Create a new Tool consumer
485
     *
486
     * @see ToolProvider::createConsumer() Pass-through to `ToolProvider::createConsumer()`
487
     *
488
     * @param  string $name Human-readable name
489
     * @param  string $key (Optional) Consumer key (unique within the tool provider)
490
     * @param  string $secret (Optional) Shared secret
491
     * @return boolean Whether or not the consumer was created
492
     * @codingStandardsIgnoreStart
493
     */
494
    public function lti_createConsumer($name, $key = false, $secret = false)
495
    {
496
        /* @codingStandardsIgnoreEnd */
497
        if ($this->getToolProvider()->createConsumer($name, $key, $secret)) {
498
            $this->log("Created consumer $name");
499
            return true;
500
        } else {
501
            $this->log("Could not recreate consumer '$name', consumer already exists");
502
            return false;
503
        }
504
    }
505
506
    /**
507
     * Get the list of consumers for this tool
508
     *
509
     * @see ToolProvider::getConsumers() Pass-through to `ToolProvider::getConsumers()`
510
     *
511
     * @return LTI_Consumer[]
512
     * @codingStandardsIgnoreStart
513
     */
514
    public function lti_getConsumers()
515
    {
516
        /* @codingStandardsIgnoreEnd */
517
        return $this->getToolProvider()->getConsumers();
518
    }
519
520
    /**
521
     * Update the API interaction object
522
     *
523
     * @param CanvasPest $api
524
     */
525
    public function setAPI(CanvasPest $api)
526
    {
527
        $this->api = $api;
528
    }
529
530
    /**
531
     * Get the API interaction object
532
533
     * @return CanvasPest
534
     */
535
    public function getAPI()
536
    {
537
        if (empty($this->api)) {
538
            if (!empty($this->config(static::TOOL_CANVAS_API)['token'])) {
539
                $this->setAPI(new CanvasPest(
540
                    'https://' . $_SESSION[ToolProvider::class]['canvas']['api_domain'] . '/api/v1',
541
                    $this->config(static::TOOL_CANVAS_API)['token']
542
                ));
543
            } else {
544
                throw new ConfigurationException(
545
                    'Canvas URL and Token required',
546
                    ConfigurationException::CANVAS_API_INCORRECT
547
                );
548
            }
549
        }
550
        return $this->api;
551
    }
552
553
    /**
554
     * Make a GET request to the API
555
     *
556
     * @codingStandardsIgnoreStart
557
     * @link https://htmlpreview.github.io/?https://raw.githubusercontent.com/smtech/canvaspest/master/doc/classes/smtech.CanvasPest.CanvasPest.html#method_get Pass-through to CanvasPest::get()
558
     * @codingStandardsIgnoreEnd
559
     *
560
     * @param  string $url
561
     * @param  string[] $data (Optional)
562
     * @param  string[] $headers (Optional)
563
     * @return \smtech\CanvasPest\CanvasObject|\smtech\CanvasPest\CanvasArray
564
     * @codingStandardsIgnoreStart
565
     */
566
    public function api_get($url, $data = [], $headers = [])
567
    {
568
        /* @codingStandardsIgnoreEnd */
569
        return $this->getAPI()->get($url, $data, $headers);
570
    }
571
572
    /**
573
     * Make a POST request to the API
574
     *
575
     * @codingStandardsIgnoreStart
576
     * @link https://htmlpreview.github.io/?https://raw.githubusercontent.com/smtech/canvaspest/master/doc/classes/smtech.CanvasPest.CanvasPest.html#method_post Pass-through to CanvasPest::post()
577
     * @codingStandardsIgnoreEnd
578
     *
579
     * @param  string $url
580
     * @param  string[] $data (Optional)
581
     * @param  string[] $headers (Optional)
582
     * @return \smtech\CanvasPest\CanvasObject|\smtech\CanvasPest\CanvasArray
583
     * @codingStandardsIgnoreStart
584
     */
585
    public function api_post($url, $data = [], $headers = [])
586
    {
587
        /* @codingStandardsIgnoreEnd */
588
        return $this->getAPI()->post($url, $data, $headers);
589
    }
590
591
    /**
592
     * Make a PUT request to the API
593
     *
594
     * @codingStandardsIgnoreStart
595
     * @link https://htmlpreview.github.io/?https://raw.githubusercontent.com/smtech/canvaspest/master/doc/classes/smtech.CanvasPest.CanvasPest.html#method_put Pass-through to CanvasPest::put()
596
     * @codingStandardsIgnoreEnd
597
     *
598
     * @param  string $url
599
     * @param  string[] $data (Optional)
600
     * @param  string[] $headers (Optional)
601
     * @return \smtech\CanvasPest\CanvasObject|\smtech\CanvasPest\CanvasArray
602
     * @codingStandardsIgnoreStart
603
     */
604
    public function api_put($url, $data = [], $headers = [])
605
    {
606
        /* @codingStandardsIgnoreEnd */
607
        return $this->getAPI()->put($url, $data, $headers);
608
    }
609
610
    /**
611
     * Make a DELETE request to the API
612
     *
613
     * @codingStandardsIgnoreStart
614
     * @link https://htmlpreview.github.io/?https://raw.githubusercontent.com/smtech/canvaspest/master/doc/classes/smtech.CanvasPest.CanvasPest.html#method_delete Pass-through to CanvasPest::delete()
615
     * @codingStandardsIgnoreEnd
616
     *
617
     * @param  string $url
618
     * @param  string[] $data (Optional)
619
     * @param  string[] $headers (Optional)
620
     * @return \smtech\CanvasPest\CanvasObject|\smtech\CanvasPest\CanvasArray
621
     * @codingStandardsIgnoreStart
622
     */
623
    public function api_delete($url, $data = [], $headers = [])
624
    {
625
        /* @codingStandardsIgnoreEnd */
626
        return $this->getAPI()->delete($url, $data, $headers);
627
    }
628
629
    /**
630
     * Set MySQL connection object
631
     *
632
     * @param mysqli $mysql
633
     */
634
    public function setMySQL(mysqli $mysql)
635
    {
636
        $this->mysql = $mysql;
637
    }
638
639
    /**
640
     * Get MySQL connection object
641
     *
642
     * @return mysqli
643
     */
644
    public function getMySQL()
645
    {
646
        return $this->mysql;
647
    }
648
649
    /**
650
     * Make a MySQL query
651
     *
652
     * @link http://php.net/manual/en/mysqli.query.php Pass-through to `mysqli::query()`
653
     * @param string $query
654
     * @param int $resultMode (Optional, defaults to `MYSQLI_STORE_RESULT`)
655
     * @return mixed
656
     * @codingStandardsIgnoreStart
657
     */
658
    public function mysql_query($query, $resultMode = MYSQLI_STORE_RESULT)
659
    {
660
        /* @codingStandardsIgnoreEnd */
661
        return $this->getMySQL()->query($query, $resultMode);
662
    }
663
664
    /**
665
     * Check if the logger object is ready for use
666
     * @return boolean `TRUE` if ready, `FALSE` otherwise
667
     */
668
    protected function logReady()
669
    {
670
        return is_a($this->logger, Log::class);
671
    }
672
673
    /**
674
     * Set log file manager
675
     *
676
     * @param Log $log
677
     */
678
    public function setLog(Log $log)
679
    {
680
        $this->logger = $log;
681
    }
682
683
    /**
684
     * Get log file manager
685
     *
686
     * @return Log
687
     */
688
    public function getLog()
689
    {
690
        return $this->logger;
691
    }
692
693
    /**
694
     * Queue a message for delayed logging
695
     * @param string $message
696
     * @param string $priority
697
     * @return void
698
     */
699
    protected function queueLog($message, $priority = null)
700
    {
701
        $this->logQueue[] = ['message' => $message, 'priority' => $priority];
702
    }
703
704
    /**
705
     * Flush the delayed log queue
706
     * @return void
707
     */
708
    protected function flushLogQueue()
709
    {
710
        if ($this->logReady() && !empty($this->logQueue)) {
711
            foreach ($this->logQueue as $entry) {
712
                $this->getLog()->log($entry['message'], $entry['priority']);
713
            }
714
            $this->logQueue = [];
715
        }
716
    }
717
718
    /**
719
     * Add a message to the tool log file
720
     *
721
     * If no logger object is ready, the message will be queued for delayed
722
     * logging until a logger object is ready.
723
     *
724
     * @link https://pear.php.net/package/Log/docs/1.13.1/Log/Log_file.html#methodlog
725
     *      Pass-throgh to `Log_file::log()`
726
     *
727
     * @param string $message
728
     * @param string $priority (Optional, defaults to `PEAR_LOG_INFO`)
729
     * @return boolean Success
730
     */
731
    public function log($message, $priority = null)
732
    {
733
        if ($this->logReady()) {
734
            $this->flushLogQueue();
735
            return $this->getLog()->log($message, $priority);
736
        } else {
737
            $this->queueLog($message, $priority);
738
        }
739
    }
740
741
    /**
742
     * Set the LTI Configuration generator
743
     *
744
     * @param Generator $generator
745
     */
746
    public function setGenerator(Generator $generator)
747
    {
748
        $this->generator = $generator;
749
    }
750
751
    /**
752
     * Get the LTI Configuration generator
753
     *
754
     * @return Generator
755
     */
756
    public function getGenerator()
757
    {
758
        try {
759
            if (empty($this->generator)) {
760
                $this->setGenerator(
761
                    new Generator(
762
                        $this->config(static::TOOL_NAME),
763
                        $this->config(static::TOOL_ID),
764
                        $this->config(static::TOOL_LAUNCH_URL),
765
                        (empty($this->config(static::TOOL_DESCRIPTION)) ?
766
                            false : $this->config(static::TOOL_DESCRIPTION)),
767
                        (empty($this->config(static::TOOL_ICON_URL)) ?
768
                            false : $this->config(static::TOOL_ICON_URL)),
769
                        (empty($this->config(static::TOOL_LAUNCH_PRIVACY)) ?
770
                            LaunchPrivacy::USER_PROFILE() : $this->config(static::TOOL_LAUNCH_PRIVACY)),
771
                        (empty($this->config(static::TOOL_DOMAIN)) ? false : $this->config(static::TOOL_DOMAIN))
772
                    )
773
                );
774
            }
775
        } catch (LTIConfigGeneratorException $e) {
776
            throw new ConfigurationException(
777
                $e->getMessage(),
778
                ConfigurationException::TOOL_PROVIDER
779
            );
780
        }
781
        return $this->generator;
782
    }
783
784
    /**
785
     * Get the LTI configuration XML
786
     *
787
     * @codingStandardsIgnoreStart
788
     * @link https://htmlpreview.github.io/?https://raw.githubusercontent.com/smtech/lti-configuration-xml/master/doc/classes/smtech.LTI.Configuration.Generator.html#method_saveXML Pass-through to `Generator::saveXML()`
789
     * @codingStandardsIgnoreEnd
790
     *
791
     * @return string
792
     */
793
    public function saveConfigurationXML()
794
    {
795
        try {
796
            return $this->getGenerator()->saveXML();
797
        } catch (LTIConfigGeneratorException $e) {
798
            throw new ConfigurationException(
799
                $e->getMessage(),
800
                ConfigurationException::TOOL_PROVIDER
801
            );
802
        }
803
    }
804
}
805