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
 * This file contains the moodle format implementation of the content writer.
19
 *
20
 * @package core_privacy
21
 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
22
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
namespace core_privacy\tests\request;
25
 
26
defined('MOODLE_INTERNAL') || die();
27
 
28
/**
29
 * An implementation of the content_writer for use in unit tests.
30
 *
31
 * This implementation does not export any data but instead stores it in
32
 * structures within the instance which can be easily queried for use
33
 * during unit tests.
34
 *
35
 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
36
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 */
38
class content_writer implements \core_privacy\local\request\content_writer {
39
    /**
40
     * @var \context The context currently being exported.
41
     */
42
    protected $context;
43
 
44
    /**
45
     * @var \stdClass The collection of metadata which has been exported.
46
     */
47
    protected $metadata;
48
 
49
    /**
50
     * @var \stdClass The data which has been exported.
51
     */
52
    protected $data;
53
 
54
    /**
55
     * @var \stdClass The related data which has been exported.
56
     */
57
    protected $relateddata;
58
 
59
    /**
60
     * @var \stdClass The list of stored files which have been exported.
61
     */
62
    protected $files;
63
 
64
    /**
65
     * @var \stdClass The custom files which have been exported.
66
     */
67
    protected $customfiles;
68
 
69
    /**
70
     * @var \stdClass The user preferences which have been exported.
71
     */
72
    protected $userprefs;
73
 
74
    /**
75
     * Whether any data has been exported at all within the current context.
76
     *
77
     * @param array $subcontext The location within the current context that this data belongs -
78
     *   in this method it can be partial subcontext path (or none at all to check presence of any data anywhere).
79
     *   User preferences never have subcontext, if $subcontext is specified, user preferences are not checked.
80
     * @return  bool
81
     */
82
    public function has_any_data($subcontext = []) {
83
        if (empty($subcontext)) {
84
            // When subcontext is not specified check presence of user preferences in this context and in system context.
85
            $hasuserprefs = !empty($this->userprefs->{$this->context->id});
86
            $systemcontext = \context_system::instance();
87
            $hasglobaluserprefs = !empty($this->userprefs->{$systemcontext->id});
88
            if ($hasuserprefs || $hasglobaluserprefs) {
89
                return true;
90
            }
91
        }
92
 
93
        foreach (['data', 'relateddata', 'metadata', 'files', 'customfiles'] as $datatype) {
94
            if (!property_exists($this->$datatype, $this->context->id)) {
95
                // No data of this type for this context at all. Continue to the next data type.
96
                continue;
97
            }
98
            $basepath = $this->$datatype->{$this->context->id};
99
            foreach ($subcontext as $subpath) {
100
                if (!isset($basepath->children->$subpath)) {
101
                    // No data of this type is present for this path. Continue to the next data type.
102
                    continue 2;
103
                }
104
                $basepath = $basepath->children->$subpath;
105
            }
106
            if (!empty($basepath)) {
107
                // Some data found for this type for this subcontext.
108
                return true;
109
            }
110
        }
111
        return false;
112
    }
113
 
114
    /**
115
     * Whether any data has been exported for any context.
116
     *
117
     * @return  bool
118
     */
119
    public function has_any_data_in_any_context() {
120
        $checkfordata = function($location) {
121
            foreach ($location as $context => $data) {
122
                if (!empty($data)) {
123
                    return true;
124
                }
125
            }
126
 
127
            return false;
128
        };
129
 
130
        $hasanydata = $checkfordata($this->data);
131
        $hasanydata = $hasanydata || $checkfordata($this->relateddata);
132
        $hasanydata = $hasanydata || $checkfordata($this->metadata);
133
        $hasanydata = $hasanydata || $checkfordata($this->files);
134
        $hasanydata = $hasanydata || $checkfordata($this->customfiles);
135
        $hasanydata = $hasanydata || $checkfordata($this->userprefs);
136
 
137
        return $hasanydata;
138
    }
139
 
140
    /**
141
     * Constructor for the content writer.
142
     *
143
     * Note: The writer_factory must be passed.
144
     * @param   \core_privacy\local\request\writer          $writer    The writer factory.
145
     */
146
    public function __construct(\core_privacy\local\request\writer $writer) {
147
        $this->data = (object) [];
148
        $this->relateddata = (object) [];
149
        $this->metadata = (object) [];
150
        $this->files = (object) [];
151
        $this->customfiles = (object) [];
152
        $this->userprefs = (object) [];
153
    }
154
 
155
    /**
156
     * Set the context for the current item being processed.
157
     *
158
     * @param   \context        $context    The context to use
159
     */
160
    public function set_context(\context $context): \core_privacy\local\request\content_writer {
161
        $this->context = $context;
162
 
163
        if (isset($this->data->{$this->context->id}) && empty((array) $this->data->{$this->context->id})) {
164
            $this->data->{$this->context->id} = (object) [
165
                'children' => (object) [],
166
                'data' => [],
167
            ];
168
        }
169
 
170
        if (isset($this->relateddata->{$this->context->id}) && empty((array) $this->relateddata->{$this->context->id})) {
171
            $this->relateddata->{$this->context->id} = (object) [
172
                'children' => (object) [],
173
                'data' => [],
174
            ];
175
        }
176
 
177
        if (isset($this->metadata->{$this->context->id}) && empty((array) $this->metadata->{$this->context->id})) {
178
            $this->metadata->{$this->context->id} = (object) [
179
                'children' => (object) [],
180
                'data' => [],
181
            ];
182
        }
183
 
184
        if (isset($this->files->{$this->context->id}) && empty((array) $this->files->{$this->context->id})) {
185
            $this->files->{$this->context->id} = (object) [
186
                'children' => (object) [],
187
                'data' => [],
188
            ];
189
        }
190
 
191
        if (isset($this->customfiles->{$this->context->id}) && empty((array) $this->customfiles->{$this->context->id})) {
192
            $this->customfiles->{$this->context->id} = (object) [
193
                'children' => (object) [],
194
                'data' => [],
195
            ];
196
        }
197
 
198
        if (isset($this->userprefs->{$this->context->id}) && empty((array) $this->userprefs->{$this->context->id})) {
199
            $this->userprefs->{$this->context->id} = (object) [
200
                'children' => (object) [],
201
                'data' => [],
202
            ];
203
        }
204
 
205
        return $this;
206
    }
207
 
208
    /**
209
     * Return the current context.
210
     *
211
     * @return  \context
212
     */
213
    public function get_current_context(): \context {
214
        return $this->context;
215
    }
216
 
217
    /**
218
     * Export the supplied data within the current context, at the supplied subcontext.
219
     *
220
     * @param   array           $subcontext The location within the current context that this data belongs.
221
     * @param   \stdClass       $data       The data to be exported
222
     */
223
    public function export_data(array $subcontext, \stdClass $data): \core_privacy\local\request\content_writer {
224
        $current = $this->fetch_root($this->data, $subcontext);
225
        $current->data = $data;
226
 
227
        return $this;
228
    }
229
 
230
    /**
231
     * Get all data within the subcontext.
232
     *
233
     * @param   array           $subcontext The location within the current context that this data belongs.
234
     * @return  \stdClass|array             The metadata as a series of keys to value + description objects.
235
     */
236
    public function get_data(array $subcontext = []) {
237
        return $this->fetch_data_root($this->data, $subcontext);
238
    }
239
 
240
    /**
241
     * Export metadata about the supplied subcontext.
242
     *
243
     * Metadata consists of a key/value pair and a description of the value.
244
     *
245
     * @param   array           $subcontext The location within the current context that this data belongs.
246
     * @param   string          $key        The metadata name.
247
     * @param   string          $value      The metadata value.
248
     * @param   string          $description    The description of the value.
249
     * @return  $this
250
     */
251
    public function export_metadata(
252
        array $subcontext,
253
        string $key,
254
        $value,
255
        string $description
256
    ): \core_privacy\local\request\content_writer {
257
        $current = $this->fetch_root($this->metadata, $subcontext);
258
        $current->data[$key] = (object) [
259
                'value' => $value,
260
                'description' => $description,
261
            ];
262
 
263
        return $this;
264
    }
265
 
266
    /**
267
     * Get all metadata within the subcontext.
268
     *
269
     * @param   array           $subcontext The location within the current context that this data belongs.
270
     * @return  \stdClass|array             The metadata as a series of keys to value + description objects.
271
     */
272
    public function get_all_metadata(array $subcontext = []) {
273
        return $this->fetch_data_root($this->metadata, $subcontext);
274
    }
275
 
276
    /**
277
     * Get the specified metadata within the subcontext.
278
     *
279
     * @param   array           $subcontext The location within the current context that this data belongs.
280
     * @param   string          $key        The metadata to be fetched within the context + subcontext.
281
     * @param   boolean         $valueonly  Whether to fetch only the value, rather than the value + description.
282
     * @return  \stdClass|array|null        The metadata as a series of keys to value + description objects.
283
     */
284
    public function get_metadata(array $subcontext, $key, $valueonly = true) {
285
        $keys = $this->get_all_metadata($subcontext);
286
 
287
        if (isset($keys[$key])) {
288
            $metadata = $keys[$key];
289
        } else {
290
            return null;
291
        }
292
 
293
        if ($valueonly) {
294
            return $metadata->value;
295
        } else {
296
            return $metadata;
297
        }
298
    }
299
 
300
    /**
301
     * Export a piece of related data.
302
     *
303
     * @param   array           $subcontext The location within the current context that this data belongs.
304
     * @param   string          $name       The name of the file to be exported.
305
     * @param   \stdClass       $data       The related data to export.
306
     */
307
    public function export_related_data(array $subcontext, $name, $data): \core_privacy\local\request\content_writer {
308
        $current = $this->fetch_root($this->relateddata, $subcontext);
309
        $current->data[$name] = $data;
310
 
311
        return $this;
312
    }
313
 
314
    /**
315
     * Get all data within the subcontext.
316
     *
317
     * @param   array           $subcontext The location within the current context that this data belongs.
318
     * @param   string          $filename   The name of the intended filename.
319
     * @return  \stdClass|array             The metadata as a series of keys to value + description objects.
320
     */
321
    public function get_related_data(array $subcontext = [], $filename = null) {
322
        $current = $this->fetch_data_root($this->relateddata, $subcontext);
323
 
324
        if (null === $filename) {
325
            return $current;
326
        }
327
 
328
        if (isset($current[$filename])) {
329
            return $current[$filename];
330
        }
331
 
332
        return [];
333
    }
334
 
335
    /**
336
     * Export a piece of data in a custom format.
337
     *
338
     * @param   array           $subcontext The location within the current context that this data belongs.
339
     * @param   string          $filename   The name of the file to be exported.
340
     * @param   string          $filecontent    The content to be exported.
341
     */
342
    public function export_custom_file(array $subcontext, $filename, $filecontent): \core_privacy\local\request\content_writer {
343
        $filename = clean_param($filename, PARAM_FILE);
344
 
345
        $current = $this->fetch_root($this->customfiles, $subcontext);
346
        $current->data[$filename] = $filecontent;
347
 
348
        return $this;
349
    }
350
 
351
    /**
352
     * Get the specified custom file within the subcontext.
353
     *
354
     * @param   array           $subcontext The location within the current context that this data belongs.
355
     * @param   string          $filename   The name of the file to be fetched within the context + subcontext.
356
     * @return  string                      The content of the file.
357
     */
358
    public function get_custom_file(array $subcontext = [], $filename = null) {
359
        $current = $this->fetch_data_root($this->customfiles, $subcontext);
360
 
361
        if (null === $filename) {
362
            return $current;
363
        }
364
 
365
        if (isset($current[$filename])) {
366
            return $current[$filename];
367
        }
368
 
369
        return null;
370
    }
371
 
372
    /**
373
     * Prepare a text area by processing pluginfile URLs within it.
374
     *
375
     * Note that this method does not implement the pluginfile URL rewriting. Such a job tightly depends on how the
376
     * actual writer exports files so it can be reliably tested only in real writers such as
377
     * {@link core_privacy\local\request\moodle_content_writer}.
378
     *
379
     * However we have to remove @@PLUGINFILE@@ since otherwise {@link format_text()} shows debugging messages
380
     *
381
     * @param   array           $subcontext The location within the current context that this data belongs.
382
     * @param   string          $component  The name of the component that the files belong to.
383
     * @param   string          $filearea   The filearea within that component.
384
     * @param   string          $itemid     Which item those files belong to.
385
     * @param   string          $text       The text to be processed
386
     * @return  string                      The processed string
387
     */
388
    public function rewrite_pluginfile_urls(array $subcontext, $component, $filearea, $itemid, $text): string {
389
        return str_replace('@@PLUGINFILE@@/', 'files/', $text ?? '');
390
    }
391
 
392
    /**
393
     * Export all files within the specified component, filearea, itemid combination.
394
     *
395
     * @param   array           $subcontext The location within the current context that this data belongs.
396
     * @param   string          $component  The name of the component that the files belong to.
397
     * @param   string          $filearea   The filearea within that component.
398
     * @param   string          $itemid     Which item those files belong to.
399
     */
400
    public function export_area_files(array $subcontext, $component, $filearea, $itemid): \core_privacy\local\request\content_writer  {
401
        $fs = get_file_storage();
402
        $files = $fs->get_area_files($this->context->id, $component, $filearea, $itemid);
403
        foreach ($files as $file) {
404
            $this->export_file($subcontext, $file);
405
        }
406
 
407
        return $this;
408
    }
409
 
410
    /**
411
     * Export the specified file in the target location.
412
     *
413
     * @param   array           $subcontext The location within the current context that this data belongs.
414
     * @param   \stored_file    $file       The file to be exported.
415
     */
416
    public function export_file(array $subcontext, \stored_file $file): \core_privacy\local\request\content_writer  {
417
        if (!$file->is_directory()) {
418
            $filepath = $file->get_filepath();
419
            // Directory separator in the stored_file class should always be '/'. The following line is just a fail safe.
420
            $filepath = str_replace(DIRECTORY_SEPARATOR, '/', $filepath);
421
            $filepath = explode('/', $filepath);
422
            $filepath[] = $file->get_filename();
423
            $filepath = array_filter($filepath);
424
            $filepath = implode('/', $filepath);
425
            $current = $this->fetch_root($this->files, $subcontext);
426
            $current->data[$filepath] = $file;
427
        }
428
 
429
        return $this;
430
    }
431
 
432
    /**
433
     * Get all files in the specfied subcontext.
434
     *
435
     * @param   array           $subcontext The location within the current context that this data belongs.
436
     * @return  \stored_file[]              The list of stored_files in this context + subcontext.
437
     */
438
    public function get_files(array $subcontext = []) {
439
        return $this->fetch_data_root($this->files, $subcontext);
440
    }
441
 
442
    /**
443
     * Export the specified user preference.
444
     *
445
     * @param   string          $component  The name of the component.
446
     * @param   string          $key        The name of th key to be exported.
447
     * @param   string          $value      The value of the preference
448
     * @param   string          $description    A description of the value
449
     * @return  \core_privacy\local\request\content_writer
450
     */
451
    public function export_user_preference(
452
        string $component,
453
        string $key,
454
        string $value,
455
        string $description
456
    ): \core_privacy\local\request\content_writer {
457
        $prefs = $this->fetch_root($this->userprefs, []);
458
 
459
        if (!isset($prefs->{$component})) {
460
            $prefs->{$component} = (object) [];
461
        }
462
 
463
        $prefs->{$component}->$key = (object) [
464
            'value' => $value,
465
            'description' => $description,
466
        ];
467
 
468
        return $this;
469
    }
470
 
471
    /**
472
     * Get all user preferences for the specified component.
473
     *
474
     * @param   string          $component  The name of the component.
475
     * @return  \stdClass
476
     */
477
    public function get_user_preferences(string $component) {
478
        $context = \context_system::instance();
479
        $prefs = $this->fetch_root($this->userprefs, [], $context->id);
480
        if (isset($prefs->{$component})) {
481
            return $prefs->{$component};
482
        } else {
483
            return (object) [];
484
        }
485
    }
486
 
487
    /**
488
     * Get all user preferences for the specified component.
489
     *
490
     * @param   string          $component  The name of the component.
491
     * @return  \stdClass
492
     */
493
    public function get_user_context_preferences(string $component) {
494
        $prefs = $this->fetch_root($this->userprefs, []);
495
        if (isset($prefs->{$component})) {
496
            return $prefs->{$component};
497
        } else {
498
            return (object) [];
499
        }
500
    }
501
 
502
    /**
503
     * Perform any required finalisation steps and return the location of the finalised export.
504
     *
505
     * @return  string
506
     */
507
    public function finalise_content(): string {
508
        return 'mock_path';
509
    }
510
 
511
    /**
512
     * Fetch the entire root record at the specified location type, creating it if required.
513
     *
514
     * @param   \stdClass   $base The base to use - e.g. $this->data
515
     * @param   array       $subcontext The subcontext to fetch
516
     * @param   int         $temporarycontextid A temporary context ID to use for the fetch.
517
     * @return  \stdClass|array
518
     */
519
    protected function fetch_root($base, $subcontext, $temporarycontextid = null) {
520
        $contextid = !empty($temporarycontextid) ? $temporarycontextid : $this->context->id;
521
        if (!isset($base->{$contextid})) {
522
            $base->{$contextid} = (object) [
523
                'children' => (object) [],
524
                'data' => [],
525
            ];
526
        }
527
 
528
        $current = $base->{$contextid};
529
        foreach ($subcontext as $node) {
530
            if (!isset($current->children->{$node})) {
531
                $current->children->{$node} = (object) [
532
                    'children' => (object) [],
533
                    'data' => [],
534
                ];
535
            }
536
            $current = $current->children->{$node};
537
        }
538
 
539
        return $current;
540
    }
541
 
542
    /**
543
     * Fetch the data region of the specified root.
544
     *
545
     * @param   \stdClass   $base The base to use - e.g. $this->data
546
     * @param   array       $subcontext The subcontext to fetch
547
     * @return  \stdClass|array
548
     */
549
    protected function fetch_data_root($base, $subcontext) {
550
        $root = $this->fetch_root($base, $subcontext);
551
 
552
        return $root->data;
553
    }
554
}