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

src/Toolbox.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 $SYNC_TIMESTAMP = null;
35
36
    /**
37
     * Configure course and account navigation placements
38
     *
39
     * @return Generator
40
     */
41
    public function getGenerator()
42
    {
43
        parent::getGenerator();
44
        $this->generator->setOptionProperty(
45
            Option::COURSE_NAVIGATION(),
46
            'visibility',
47
            'admins'
48
        );
49
        return $this->generator;
50
    }
51
52
    /**
53
     * Load the app schema into the database
54
     *
55
     * @return void
56
     */
57
    public function loadSchema()
58
    {
59
        /* ...so that we can find the LTI_Tool_Provider database schema (oy!) */
60
        foreach (explode(';', file_get_contents(dirname(__DIR__) . '/schema.sql')) as $query) {
61
            if (!empty(trim($query))) {
62
                /*
63
                 * TODO should there be some sort of testing or logging here?
64
                 *      If _some_ tables are present, that will trigger
65
                 *      reloading all tables, which will generate ignorable
66
                 *      errors.
67
                 */
68
                $this->mysql_query($query);
69
            }
70
        }
71
        $this->log('Application database schema loaded.');
72
    }
73
74
    /**
75
     * Check to see if a URL exists
76
     **/
77
    public function urlExists($url)
78
    {
79
        $handle = fopen($url, 'r');
80
        return $handle !== false;
81
    }
82
83
    /**
84
     * compute the calendar context for the canvas object based on its URL
85
     **/
86
    public function getCanvasContext($canvasUrl)
87
    {
88
        /*
89
         * TODO: accept calendar2?contexts links too (they would be an intuitively
90
         * obvious link to use, after all)
91
         */
92
        /*
93
         * FIXME: users aren't working
94
         */
95
        /*
96
         * TODO: it would probably be better to look up users by email address than
97
         * URL
98
         */
99
        /* get the context (user, course or group) for the canvas URL */
100
        $canvasContext = array();
101
        if (preg_match(
102
            '%(https?://)?(' .
103
            parse_url($this->config('TOOL_CANVAS_API')['url'], PHP_URL_HOST) .
104
            '/((about/(\d+))|(courses/(\d+)(/groups/(\d+))?)|(accounts/\d+/groups/(\d+))))%',
105
            $canvasUrl,
106
            $matches
107
        )) {
108
            $canvasContext['canonical_url'] = "https://{$matches[2]}"; // https://stmarksschool.instructure.com/courses/953
109
110
            // course or account groups
111
            if (isset($matches[9]) || isset($matches[11])) {
112
                $canvasContext['context'] = 'group'; // used to for context_code in events
113
                $canvasContext['id'] = ($matches[9] > $matches[11] ? $matches[9] : $matches[11]);
114
115
                /* used once to look up the object to be sure it really exists */
116
                $canvasContext['verification_url'] = "groups/{$canvasContext['id']}";
117
118
            // courses
119
            } elseif (isset($matches[7])) {
120
                $canvasContext['context'] = 'course';
121
                $canvasContext['id'] = $matches[7];
122
                $canvasContext['verification_url'] = "courses/{$canvasContext['id']}";
123
124
            // users
125
            } elseif (isset($matches[5])) {
126
                $canvasContext['context'] = 'user';
127
                $canvasContext['id'] = $matches[5];
128
                $canvasContext['verification_url'] = "users/{$canvasContext['id']}/profile";
129
130
            // we're somewhere where we don't know where we are
131
            } else {
132
                return false;
133
            }
134
            return $canvasContext;
135
        }
136
        return false;
137
    }
138
139
    /**
140
     * Filter and clean event data before posting to Canvas
141
     *
142
     * This must happen AFTER the event hash has been calculated!
143
     **/
144
    public function filterEvent($event, $calendarCache)
145
    {
146
            return (
147
            (
148
                // TODO actual multi-day events would be nice
149
                // only include first day of multi-day events
150
                $event->getProperty('X-OCCURENCE') == false ||
151
                preg_match('/^day 1 of \d+$/i', $event->getProperty('X-OCCURENCE')[1])
152
            ) &&
153
            (
154
                // include this event if filtering is off...
155
                    $calendarCache['enable_regexp_filter'] == false ||
156
                 (
157
                    (
158
                        ( // if filtering is on, and there's an include pattern test that pattern...
159
                            !empty($calendarCache['include_regexp']) &&
160
                            preg_match("%{$calendarCache['include_regexp']}%", $event->getProperty('SUMMARY'))
161
                        )
162
                    ) &&
163
                    !( // if there is an exclude pattern, make sure that this event is NOT excluded
164
                        !empty($calendarCache['exclude_regexp']) &&
165
                        preg_match("%{$calendarCache['exclude_regexp']}%", $event->getProperty('SUMMARY'))
166
                    )
167
                )
168
            )
169
        );
170
    }
171
172
    public function postMessage($subject, $body, $flag = NotificationMessage::INFO)
173
    {
174
        global $toolbox;
175
        if (php_sapi_name() != 'cli') {
176
            $this->smarty_addMessage($subject, $body, $flag);
177
        } else {
178
            $logEntry = "[$flag] $subject: $body";
179
            $this->log($logEntry);
180
        }
181
    }
182
183
    /**
184
     * Generate a unique ID to identify this particular pairing of ICS feed and
185
     * Canvas calendar
186
     **/
187
    public function getPairingHash($icsUrl, $canvasContext)
188
    {
189
        return md5($icsUrl . $canvasContext . $this->config('CANVAS_INSTANCE_URL'));
190
    }
191
192
    /**
193
     * Generate a hash of this version of an event to cache in the database
194
     **/
195
    public function getEventHash($event)
196
    {
197
        $blob = '';
198
        foreach ($this->FIELD_MAP as $field) {
199
            if (is_array($field)) {
200
                foreach ($field as $option) {
201
                    if (!empty($property = $event->getProperty($option))) {
202
                        $blob .= serialize($property);
203
                        break;
204
                    }
205
                }
206
            } else {
207
                if (!empty($property = $event->getProperty($field))) {
208
                    $blob .= serialize($property);
209
                }
210
            }
211
        }
212
        return md5($blob);
213
    }
214
215
    /**
216
     * Generate a unique identifier for this synchronization pass
217
     **/
218
    public function getSyncTimestamp()
0 ignored issues
show
getSyncTimestamp uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
219
    {
220
        if ($this->SYNC_TIMESTAMP) {
221
            return $this->SYNC_TIMESTAMP;
222
        } else {
223
            $timestamp = new DateTime();
224
            $this->SYNC_TIMESTAMP = $timestamp->format(SYNC_TIMESTAMP_FORMAT) . SEPARATOR .
225
                md5((php_sapi_name() == 'cli' ? 'cli' : $_SERVER['REMOTE_ADDR']) . time());
226
            return $this->SYNC_TIMESTAMP;
227
        }
228
    }
229
}
230