Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Represents a Zoom invitation.
19
 *
20
 * @package    mod_zoom
21
 * @author     Andrew Madden <andrewmadden@catalyst-au.net>
22
 * @copyright  2021 Catalyst IT
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace mod_zoom;
27
 
28
use coding_exception;
29
use context_module;
30
use moodle_exception;
31
 
32
/**
33
 * Invitation class.
34
 */
35
class invitation {
36
    /**
37
     * Invitation settings prefix.
38
     * @var string
39
     */
40
    public const PREFIX = 'invitation_';
41
 
42
    /** @var string|null $invitation The unaltered zoom invitation text. */
43
    private $invitation;
44
 
45
    /** @var array $configregex Array of regex patterns defined in plugin settings. */
46
    private $configregex;
47
 
48
    /**
49
     * invitation constructor.
50
     *
51
     * @param string|null $invitation Zoom invitation returned from
52
     * https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetinginvitation.
53
     */
54
    public function __construct($invitation) {
55
        $this->invitation = $invitation;
56
    }
57
 
58
    /**
59
     * Get the display string to show on the module page.
60
     *
61
     * @param int $coursemoduleid Course module where the user will view the invitation.
62
     * @param int|null $userid Optionally supply the intended user to view the string. Defaults to global $USER.
63
     * @return ?string
64
     */
65
    public function get_display_string(int $coursemoduleid, ?int $userid = null) {
66
        if (empty($this->invitation)) {
67
            return null;
68
        }
69
 
70
        // If regex patterns are disabled, return the raw zoom meeting invitation.
71
        if (!get_config('zoom', 'invitationregexenabled')) {
72
            return $this->invitation;
73
        }
74
 
75
        $displaystring = $this->invitation;
76
        try {
77
            // If setting enabled, strip the invite message.
78
            if (get_config('zoom', 'invitationremoveinvite')) {
79
                $displaystring = $this->remove_element($displaystring, 'invite');
80
            }
81
 
82
            // If setting enabled, strip the iCal link.
83
            if (get_config('zoom', 'invitationremoveicallink')) {
84
                $displaystring = $this->remove_element($displaystring, 'icallink');
85
            }
86
 
87
            // Check user capabilities, and remove parts of the invitation they don't have permission to view.
88
            if (!has_capability('mod/zoom:viewjoinurl', context_module::instance($coursemoduleid), $userid)) {
89
                $displaystring = $this->remove_element($displaystring, 'joinurl');
90
            }
91
 
92
            if (!has_capability('mod/zoom:viewdialin', context_module::instance($coursemoduleid), $userid)) {
93
                $displaystring = $this->remove_element($displaystring, 'onetapmobile');
94
                $displaystring = $this->remove_element($displaystring, 'dialin');
95
                $displaystring = $this->remove_element($displaystring, 'sip');
96
                $displaystring = $this->remove_element($displaystring, 'h323');
97
            } else {
98
                // Fix the formatting of the onetapmobile section if it exists.
99
                $displaystring = $this->add_paragraph_break_above_element($displaystring, 'onetapmobile');
100
            }
101
        } catch (moodle_exception $e) {
102
            // If the regex parsing fails, log a debugging message and return the whole invitation.
103
            debugging($e->getMessage(), DEBUG_DEVELOPER);
104
            return $this->invitation;
105
        }
106
 
107
        $displaystring = trim($this->clean_paragraphs($displaystring));
108
        return $displaystring;
109
    }
110
 
111
    /**
112
     * Remove instances of a zoom invitation element using a regex pattern.
113
     *
114
     * @param string $invitation
115
     * @param string $element
116
     * @return string
117
     *
118
     * @throws \coding_exception
119
     * @throws \dml_exception
120
     * @throws \moodle_exception
121
     */
122
    private function remove_element(string $invitation, string $element): string {
123
        global $PAGE;
124
 
125
        $configregex = $this->get_config_invitation_regex();
126
        if (!array_key_exists($element, $configregex)) {
127
            throw new coding_exception('Cannot remove element: ' . $element
128
                    . '. See mod/zoom/classes/invitation.php:get_default_invitation_regex for valid elements.');
129
        }
130
 
131
        // If the element pattern is intentionally empty, return the invitation string unaltered.
132
        if (empty($configregex[$element])) {
133
            return $invitation;
134
        }
135
 
136
        $count = 0;
137
        $invitation = @preg_replace($configregex[$element], "", $invitation, -1, $count);
138
 
139
        // If invitation is null, an error occurred in preg_replace.
140
        if ($invitation === null) {
141
            throw new moodle_exception(
142
                'invitationmodificationfailed',
143
                'mod_zoom',
144
                $PAGE->url,
145
                ['element' => $element, 'pattern' => $configregex[$element]]
146
            );
147
        }
148
 
149
        // Add debugging message to assist site administrator in testing regex patterns if no match is found.
150
        if (empty($count)) {
151
            debugging(
152
                get_string(
153
                    'invitationmatchnotfound',
154
                    'mod_zoom',
155
                    ['element' => $element, 'pattern' => $configregex[$element]]
156
                ),
157
                DEBUG_DEVELOPER
158
            );
159
        }
160
 
161
        return $invitation;
162
    }
163
 
164
    /**
165
     * Add a paragraph break above an element defined by a regex pattern in a zoom invitation.
166
     *
167
     * @param string $invitation
168
     * @param string $element
169
     * @return string
170
     *
171
     * @throws \coding_exception
172
     * @throws \dml_exception
173
     */
174
    private function add_paragraph_break_above_element(string $invitation, string $element): string {
175
        $matches = [];
176
        $configregex = $this->get_config_invitation_regex();
177
        // If no pattern found for element, return the invitation string unaltered.
178
        if (empty($configregex[$element])) {
179
            return $invitation;
180
        }
181
 
182
        $result = preg_match($configregex[$element], $invitation, $matches, PREG_OFFSET_CAPTURE);
183
        // If error occurred in preg_match, show debugging message to help site administrator.
184
        if ($result === false) {
185
            debugging(
186
                get_string(
187
                    'invitationmodificationfailed',
188
                    'mod_zoom',
189
                    ['element' => $element, 'pattern' => $configregex[$element]]
190
                ),
191
                DEBUG_DEVELOPER
192
            );
193
        }
194
 
195
        // No match found, so return invitation string unaltered.
196
        if (empty($matches)) {
197
            return $invitation;
198
        }
199
 
200
        // Get the position of the element in the full invitation string.
201
        $pos = $matches[0][1];
202
        // Inject a paragraph break above element. Use $this->clean_paragraphs() to fix uneven breaks between paragraphs.
203
        return substr_replace($invitation, "\r\n\r\n", $pos, 0);
204
    }
205
 
206
    /**
207
     * Ensure that paragraphs in string have correct spacing.
208
     *
209
     * @param string $invitation
210
     * @return string
211
     */
212
    private function clean_paragraphs(string $invitation): string {
213
        // Replace partial paragraph breaks with exactly two line breaks.
214
        $invitation = preg_replace("/\r\n\n/m", "\r\n\r\n", $invitation);
215
        // Replace breaks of more than two line breaks with exactly two.
216
        $invitation = preg_replace("/\r\n\r\n[\r\n]+/m", "\r\n\r\n", $invitation);
217
        return $invitation;
218
    }
219
 
220
    /**
221
     * Get regex patterns from site config to find the different zoom invitation elements.
222
     *
223
     * @return array
224
     * @throws \dml_exception
225
     */
226
    private function get_config_invitation_regex(): array {
227
        if ($this->configregex !== null) {
228
            return $this->configregex;
229
        }
230
 
231
        $config = get_config('zoom');
232
        $this->configregex = [];
233
        // Get the regex defined in the plugin settings for each element.
234
        foreach (self::get_default_invitation_regex() as $element => $pattern) {
235
            $settingname = self::PREFIX . $element;
236
            $this->configregex[$element] = $config->$settingname;
237
        }
238
 
239
        return $this->configregex;
240
    }
241
 
242
    /**
243
     * Get default regex patterns to find the different zoom invitation elements.
244
     *
245
     * @return string[]
246
     */
247
    public static function get_default_invitation_regex(): array {
248
        return [
249
            'invite' => '/^.+is inviting you to a scheduled zoom meeting.+$/mi',
250
            'joinurl' => '/^join zoom meeting.*(\n.*)+?(\nmeeting id.+\npasscode.+)$/mi',
251
            'onetapmobile' => '/^one tap mobile.*(\n\s*\+.+)+$/mi',
252
            'dialin' => '/^dial by your location.*(\n\s*\+.+)+(\n.*)+find your local number.+$/mi',
253
            'sip' => '/^join by sip.*\n.+$/mi',
254
            'h323' => '/^join by h\.323.*(\n.*)+?(\nmeeting id.+\npasscode.+)$/mi',
255
            'icallink' => '/^.+download and import the following iCalendar.+$\n.+$/mi',
256
        ];
257
    }
258
}