Completed
Push — master ( ee4e21...0d80c1 )
by Seth
02:07
created

Toolbox   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 234
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 2

Importance

Changes 0
Metric Value
wmc 36
lcom 5
cbo 2
dl 0
loc 234
rs 8.8
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A getGenerator() 0 10 1
A loadSchema() 0 16 3
A urlExists() 0 5 1
B getCanvasContext() 0 52 7
C filterEvent() 0 27 7
A postMessage() 0 10 2
A getPairingHash() 0 4 1
B getEventHash() 0 19 6
A getSyncTimestamp() 0 11 3
A lockFileName() 0 4 1
A lock() 0 9 3
A unlock() 0 4 1
1
<?php
2
namespace smtech\CanvasICSSync;
3
4
use smtech\LTI\Configuration\Option;
5
use Battis\HierarchicalSimpleCache;
6
use Battis\BootstrapSmarty\NotificationMessage;
7
use DateTime;
8
9
/**
10
 * St. Marks Reflexive Canvas LTI Example toolbox
11
 *
12
 * Adds some common, useful methods to the St. Mark's-styled
13
 * ReflexiveCanvasLTI Toolbox
14
 *
15
 * @author  Seth Battis <[email protected]>
16
 * @version v1.2
17
 */
18
class Toolbox extends \smtech\StMarksReflexiveCanvasLTI\Toolbox
19
{
20
    private $FIELD_MAP = array(
21
        'calendar_event[title]' => 'SUMMARY',
22
        'calendar_event[description]' => 'DESCRIPTION',
23
        'calendar_event[start_at]' => array(
24
            0 => 'X-CURRENT-DTSTART',
25
            1 => 'DTSTART'
26
        ),
27
        'calendar_event[end_at]' => array(
28
            0 => 'X-CURRENT-DTEND',
29
            1 => 'DTEND'
30
        ),
31
        'calendar_event[location_name]' => 'LOCATION'
32
    );
33
34
    private $LOCK_FILE_EXPIRATION = 60 * 60; /* 1 hour */
35
36
    private $SYNC_TIMESTAMP = null;
37
38
    /**
39
     * Configure course and account navigation placements
40
     *
41
     * @return Generator
42
     */
43
    public function getGenerator()
44
    {
45
        parent::getGenerator();
46
        $this->generator->setOptionProperty(
47
            Option::COURSE_NAVIGATION(),
48
            'visibility',
49
            'admins'
50
        );
51
        return $this->generator;
52
    }
53
54
    /**
55
     * Load the app schema into the database
56
     *
57
     * @return void
58
     */
59
    public function loadSchema()
60
    {
61
        /* ...so that we can find the LTI_Tool_Provider database schema (oy!) */
62
        foreach (explode(';', file_get_contents(dirname(__DIR__) . '/schema.sql')) as $query) {
63
            if (!empty(trim($query))) {
64
                /*
65
                 * TODO should there be some sort of testing or logging here?
66
                 *      If _some_ tables are present, that will trigger
67
                 *      reloading all tables, which will generate ignorable
68
                 *      errors.
69
                 */
70
                $this->mysql_query($query);
71
            }
72
        }
73
        $this->log('Application database schema loaded.');
74
    }
75
76
    /**
77
     * Check to see if a URL exists
78
     **/
79
    public function urlExists($url)
80
    {
81
        $handle = fopen($url, 'r');
82
        return $handle !== false;
83
    }
84
85
    /**
86
     * compute the calendar context for the canvas object based on its URL
87
     **/
88
    public function getCanvasContext($canvasUrl)
89
    {
90
        /*
91
         * TODO: accept calendar2?contexts links too (they would be an intuitively
92
         * obvious link to use, after all)
93
         */
94
        /*
95
         * FIXME: users aren't working
96
         */
97
        /*
98
         * TODO: it would probably be better to look up users by email address than
99
         * URL
100
         */
101
        /* get the context (user, course or group) for the canvas URL */
102
        $canvasContext = array();
103
        if (preg_match(
104
            '%(https?://)?(' .
105
            parse_url($this->config('TOOL_CANVAS_API')['url'], PHP_URL_HOST) .
106
            '/((about/(\d+))|(courses/(\d+)(/groups/(\d+))?)|(accounts/\d+/groups/(\d+))))%',
107
            $canvasUrl,
108
            $matches
109
        )) {
110
            $canvasContext['canonical_url'] = "https://{$matches[2]}"; // https://stmarksschool.instructure.com/courses/953
111
112
            // course or account groups
113
            if (isset($matches[9]) || isset($matches[11])) {
114
                $canvasContext['context'] = 'group'; // used to for context_code in events
115
                $canvasContext['id'] = ($matches[9] > $matches[11] ? $matches[9] : $matches[11]);
116
117
                /* used once to look up the object to be sure it really exists */
118
                $canvasContext['verification_url'] = "groups/{$canvasContext['id']}";
119
120
            // courses
121
            } elseif (isset($matches[7])) {
122
                $canvasContext['context'] = 'course';
123
                $canvasContext['id'] = $matches[7];
124
                $canvasContext['verification_url'] = "courses/{$canvasContext['id']}";
125
126
            // users
127
            } elseif (isset($matches[5])) {
128
                $canvasContext['context'] = 'user';
129
                $canvasContext['id'] = $matches[5];
130
                $canvasContext['verification_url'] = "users/{$canvasContext['id']}/profile";
131
132
            // we're somewhere where we don't know where we are
133
            } else {
134
                return false;
135
            }
136
            return $canvasContext;
137
        }
138
        return false;
139
    }
140
141
    /**
142
     * Filter and clean event data before posting to Canvas
143
     *
144
     * This must happen AFTER the event hash has been calculated!
145
     **/
146
    public function filterEvent($event, $calendarCache)
147
    {
148
            return (
149
            (
150
                // TODO actual multi-day events would be nice
151
                // only include first day of multi-day events
152
                $event->getProperty('X-OCCURENCE') == false ||
153
                preg_match('/^day 1 of \d+$/i', $event->getProperty('X-OCCURENCE')[1])
154
            ) &&
155
            (
156
                // include this event if filtering is off...
157
                    $calendarCache['enable_regexp_filter'] == false ||
158
                 (
159
                    (
160
                        ( // if filtering is on, and there's an include pattern test that pattern...
161
                            !empty($calendarCache['include_regexp']) &&
162
                            preg_match("%{$calendarCache['include_regexp']}%", $event->getProperty('SUMMARY'))
163
                        )
164
                    ) &&
165
                    !( // if there is an exclude pattern, make sure that this event is NOT excluded
166
                        !empty($calendarCache['exclude_regexp']) &&
167
                        preg_match("%{$calendarCache['exclude_regexp']}%", $event->getProperty('SUMMARY'))
168
                    )
169
                )
170
            )
171
        );
172
    }
173
174
    public function postMessage($subject, $body, $flag = NotificationMessage::INFO)
175
    {
176
        global $toolbox;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
177
        if (php_sapi_name() != 'cli') {
178
            $this->smarty_addMessage($subject, $body, $flag);
179
        } else {
180
            $logEntry = "[$flag] $subject: $body";
181
            $this->log($logEntry);
182
        }
183
    }
184
185
    /**
186
     * Generate a unique ID to identify this particular pairing of ICS feed and
187
     * Canvas calendar
188
     **/
189
    public function getPairingHash($icsUrl, $canvasContext)
190
    {
191
        return md5($icsUrl . $canvasContext . $this->config('CANVAS_INSTANCE_URL'));
192
    }
193
194
    /**
195
     * Generate a hash of this version of an event to cache in the database
196
     **/
197
    public function getEventHash($event)
198
    {
199
        $blob = '';
200
        foreach ($this->FIELD_MAP as $field) {
201
            if (is_array($field)) {
202
                foreach ($field as $option) {
203
                    if (!empty($property = $event->getProperty($option))) {
204
                        $blob .= serialize($property);
205
                        break;
206
                    }
207
                }
208
            } else {
209
                if (!empty($property = $event->getProperty($field))) {
210
                    $blob .= serialize($property);
211
                }
212
            }
213
        }
214
        return md5($blob);
215
    }
216
217
    /**
218
     * Generate a unique identifier for this synchronization pass
219
     **/
220
    public function getSyncTimestamp()
221
    {
222
        if ($this->SYNC_TIMESTAMP) {
223
            return $this->SYNC_TIMESTAMP;
224
        } else {
225
            $timestamp = new DateTime();
226
            $this->SYNC_TIMESTAMP = $timestamp->format(SYNC_TIMESTAMP_FORMAT) . SEPARATOR .
227
                md5((php_sapi_name() == 'cli' ? 'cli' : $_SERVER['REMOTE_ADDR']) . time());
228
            return $this->SYNC_TIMESTAMP;
229
        }
230
    }
231
232
    protected function lockFileName($pairingHash)
233
    {
234
        return "$pairingHash.lock";
235
    }
236
237
    public function lock($pairingHash)
238
    {
239
        if (empty($this->config($this->lockFileName($pairingHash))) ||
240
            strtotime($this->config($this->lockFileName($pairingHash))) < time() - $this->LOCK_FILE_EXPIRATION) {
241
            $this->config($this->lockFileName($pairingHash), date('c'));
242
            return true;
243
        }
244
        return false;
245
    }
246
247
    public function unlock($pairingHash)
248
    {
249
        $this->config($this->lockFileName($pairingHash), false);
250
    }
251
}
252