lib.php 28.6 KB
Newer Older
1
2
3
<?php
/**
 * Mahara: Electronic portfolio, weblog, resume builder and social networking
4
5
 * Copyright (C) 2006-2009 Catalyst IT Ltd and others; see:
 *                         http://wiki.mahara.org/Contributors
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @package    mahara
 * @subpackage export-html
 * @author     Catalyst IT Ltd
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
24
 * @copyright  (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 *
 */

defined('INTERNAL') || die();

/**
 * HTML export plugin
 */
class PluginExportHtml extends PluginExport {

    /**
     * name of resultant zipfile
     */
    protected $zipfile;

    /**
     * The name of the directory under which all the other directories and 
     * files will be placed in the export
     */
    protected $rootdir;

46
47
48
49
50
51
    /**
     * List of directories of static files provided by artefact plugins
     */
    private $pluginstaticdirs = array();

    /**
52
53
54
55
     * List of stylesheets to include in the export.
     *
     * This is keyed by artefact plugin name, the empty string key contains 
     * stylesheets that will be included on all pages.
56
     */
57
    private $stylesheets = array('' => array());
58

59
60
61
62
63
64
65
    /**
     * Whether the user requested to export just one view. In this case,
     * the generated export doesn't have the home page - just the View is 
     * exported (plus artefacts of course)
     */
    protected $exportingoneview = false;

66
67
68
69
    /**
    * constructor.  overrides the parent class
    * to set up smarty and the attachment directory
    */
70
    public function __construct(User $user, $views, $artefacts, $progresscallback=null) {
71
        global $THEME;
72
        parent::__construct($user, $views, $artefacts, $progresscallback);
73
        $this->rootdir = 'portfolio-for-' . self::text_to_path($user->get('username'));
74
75

        // Create basic required directories
76
        foreach (array('files', 'views', 'static', 'static/smilies', 'static/profileicons') as $directory) {
77
78
79
80
81
82
            $directory = "{$this->exportdir}/{$this->rootdir}/{$directory}/";
            if (!check_dir_exists($directory)) {
                throw new SystemException("Couldn't create the temporary export directory $directory");
            }
        }
        $this->zipfile = 'mahara-export-html-user'
83
            . $this->get('user')->get('id') . '-' . $this->exporttime . '.zip';
84
85

        // Find what stylesheets need to be included
86
        $themedirs = $THEME->get_path('', true, 'export/html');
87
88
89
90
        $stylesheets = array('style.css', 'print.css');
        foreach ($themedirs as $theme => $themedir) {
            foreach ($stylesheets as $stylesheet) {
                if (is_readable($themedir . 'style/' . $stylesheet)) {
91
                    array_unshift($this->stylesheets[''], 'theme/' . $theme . '/static/style/' . $stylesheet);
92
93
94
95
                }
            }
        }

96
97
98
99
100
101
102
        // Don't export the dashboard
        foreach (array_keys($this->views) as $i) {
            if ($this->views[$i]->get('type') == 'dashboard') {
                unset($this->views[$i]);
            }
        }

103
104
105
106
107
108
        $this->exportingoneview = (
            $this->viewexportmode == PluginExport::EXPORT_LIST_OF_VIEWS &&
            $this->artefactexportmode == PluginExport::EXPORT_ARTEFACTS_FOR_VIEWS &&
            count($this->views) == 1
        );

109
        $this->notify_progress_callback(15, 'Setup complete');
110
111
    }

112
113
114
115
116
117
118
119
    public static function get_title() {
        return get_string('title', 'export.html');
    }

    public static function get_description() {
        return get_string('description', 'export.html');
    }

120
    /**
Nigel McNie's avatar
Nigel McNie committed
121
122
     * Main export routine
     */
123
    public function export() {
124
        global $THEME;
125
126
        raise_memory_limit('128M');

127
        $summaries = array();
128
        $plugins = plugins_installed('artefact', true);
129
        $exportplugins = array();
130
        $progressstart = 15;
131
        $progressend   = 25;
132
        $plugincount   = count($plugins);
133
134
135

        // First pass: find out which plugins are exporting like us, and ask 
        // them about the static data they want to include in every template
136
137
        $i = 0;
        foreach ($plugins as $plugin) {
138
            $plugin = $plugin->name;
139
            $this->notify_progress_callback(intval($progressstart + (++$i / $plugincount) * ($progressend - $progressstart)), 'Preparing ' . $plugin);
140

141
            if (safe_require('export', 'html/' . $plugin, 'lib.php', 'require_once', true)) {
142
143
                $exportplugins[] = $plugin;

144
145
146
147
148
149
150
                $classname = 'HtmlExport' . ucfirst($plugin);
                if (!is_subclass_of($classname, 'HtmlExportArtefactPlugin')) {
                    throw new SystemException("Class $classname does not extend HtmlExportArtefactPlugin as it should");
                }

                safe_require('artefact', $plugin);

151
                // Find out whether the plugin has static data for us
152
                $themestaticdirs = array_reverse($THEME->get_path('', true, 'artefact/' . $plugin . '/export/html'));
153
154
155
156
157
                foreach ($themestaticdirs as $dir) {
                    $staticdir = substr($dir, strlen(get_config('docroot') . 'artefact/'));
                    $this->pluginstaticdirs[] = $staticdir;
                    foreach (array('style.css', 'print.css') as $stylesheet) {
                        if (is_readable($dir . 'style/' . $stylesheet)) {
158
                            $this->stylesheets[$plugin][] = str_replace('export/html/', '', $staticdir) . 'style/' . $stylesheet;
159
160
                        }
                    }
161
                }
162
163
164
            }
        }

165
166
        // Second pass: actually dump data for active export plugins
        $progressstart = 25;
167
        $progressend   = 50;
168
169
170
171
172
173
174
175
176
177
178
179
        $i = 0;
        foreach ($exportplugins as $plugin) {
            $this->notify_progress_callback(intval($progressstart + (++$i / $plugincount) * ($progressend - $progressstart)), 'Exporting data for ' . $plugin);
            $classname = 'HtmlExport' . ucfirst($plugin);
            $artefactexporter = new $classname($this);
            $artefactexporter->dump_export_data();
            // If just exporting a list of views, we don't care about the summaries for each artefact plugin
            if (!($this->viewexportmode == PluginExport::EXPORT_LIST_OF_VIEWS && $this->artefactexportmode == PluginExport::EXPORT_ARTEFACTS_FOR_VIEWS)) {
                $summaries[$plugin] = array($artefactexporter->get_summary_weight(), $artefactexporter->get_summary());
            }
        }

Nigel McNie's avatar
Nigel McNie committed
180
        // Get the view data
181
        $this->notify_progress_callback(55, 'Exporting Views');
Nigel McNie's avatar
Nigel McNie committed
182
183
        $this->dump_view_export_data();

184
185
        if (!$this->exportingoneview) {
            $summaries['view'] = array(100, $this->get_view_summary());
186

187
188
189
190
191
192
193
194
195
196
            // Sort by weight (then drop the weight information)
            $this->notify_progress_callback(75, 'Building index page');
            uasort($summaries, create_function('$a, $b', 'return $a[0] > $b[0];'));
            foreach ($summaries as &$summary) {
                $summary = $summary[1];
            }

            // Build index.html
            $this->build_index_page($summaries);
        }
197
198

        // Copy all static files into the export
199
        $this->notify_progress_callback(80, 'Copying extra files');
200
        $this->copy_static_files();
201
202
203
204
205

        // Copy all resized images that were found while rewriting the HTML
        $copyproxy = HtmlExportCopyProxy::singleton();
        $copydata = $copyproxy->get_copy_data();
        foreach ($copydata as $from => $to) {
206
207
208
209
210
            $to = $this->get('exportdir') . '/' . $this->get('rootdir') . $to;
            if (!check_dir_exists(dirname($to))) {
                throw new SystemException("Could not create directory $todir");
            }
            if (!copy($from, $to)) {
211
212
213
                throw new SystemException("Could not copy static file $from");
            }
        }
214
215
216
        

        // zip everything up
217
        $this->notify_progress_callback(90, 'Creating zipfile');
218
219
220
221
222
223
224
225
226
227
228
229
230
231
        $cwd = getcwd();
        $command = sprintf('%s %s %s %s',
            get_config('pathtozip'),
            get_config('ziprecursearg'),
            escapeshellarg($this->exportdir .  $this->zipfile),
            escapeshellarg($this->rootdir)
        );
        $output = array();
        chdir($this->exportdir);
        exec($command, $output, $returnvar);
        chdir($cwd);
        if ($returnvar != 0) {
            throw new SystemException('Failed to zip the export file');
        }
232
        $this->notify_progress_callback(100, 'Done');
233
234
235
236
237
238
239
240
        return $this->zipfile;
    }

    public function cleanup() {
        // @todo remove temporary files and directories
        // @todo maybe move the zip file somewhere else - like to files/export or something
    }

241
242
243
244
245
246
247
    public function get_smarty($rootpath='', $section='') {
        if ($section && isset($this->stylesheets[$section])) {
            $stylesheets = array_merge($this->stylesheets[''], $this->stylesheets[$section]);
        }
        else {
            $stylesheets = $this->stylesheets[''];
        }
248
249
250
        $smarty = smarty_core();
        $smarty->assign('user', $this->get('user'));
        $smarty->assign('rootpath', $rootpath);
251
        $smarty->assign('export_time', $this->exporttime);
252
        $smarty->assign('sitename', get_config('sitename'));
253
        $smarty->assign('stylesheets', $stylesheets);
254
        $smarty->assign('maharalogo', $rootpath . $this->theme_path('images/logo.png'));
255
256
257
258

        return $smarty;
    }

259
260
261
262
263
264
265
    /**
     * Converts a relative path to a static file that the HTML export theme 
     * should have, to a path in the static export where the file will reside.
     *
     * This returns the path in the most appropriate theme.
     */
    private function theme_path($path) {
266
267
        global $THEME;
        $themestaticdirs = $THEME->get_path('', true, 'export/html');
268
269
270
271
272
273
274
        foreach ($themestaticdirs as $theme => $dir) {
            if (is_readable($dir . $path)) {
                return 'static/theme/' . $theme . '/static/' . $path;
            }
        }
    }

275
    /**
276
     * Converts the passed text into a a form that could be used in a URL.
277
278
279
280
281
     *
     * @param string $text The text to convert
     * @return string      The converted text
     */
    public static function text_to_path($text) {
282
        return substr(preg_replace('#[^a-zA-Z0-9_-]+#', '-', $text), 0, 255);
283
284
    }

285
286
287
288
289
290
291
292
293
294
295
    /**
     * Sanitises a string meant to be used as a filesystem path.
     *
     * Mahara allows file/folder artefact names to have slashes in them, which 
     * aren't legal on most real filesystems.
     */
    public static function sanitise_path($path) {
        return substr(str_replace('/', '_', $path), 0, 255);
    }


296
297
    private function build_index_page($summaries) {
        $smarty = $this->get_smarty();
298
        $smarty->assign('page_heading', full_name($this->get('user')));
299
300
        $smarty->assign('summaries', $summaries);
        $content = $smarty->fetch('export:html:index.tpl');
301
302
303
        if (!file_put_contents($this->exportdir . '/' . $this->rootdir . '/index.html', $content)) {
            throw new SystemException("Could not create index.html for the export");
        }
304
305
    }

Nigel McNie's avatar
Nigel McNie committed
306
307
308
309
    /**
     * Dumps all views into the HTML export
     */
    private function dump_view_export_data() {
310
311
312
313
        $progressstart = 55;
        $progressend   = 75;
        $i = 0;
        $viewcount = count($this->views);
314
315
        $rootpath = ($this->exportingoneview) ? './' : '../../';
        $smarty = $this->get_smarty($rootpath);
316
        foreach ($this->views as $viewid => $view) {
317
            $this->notify_progress_callback(intval($progressstart + (++$i / $viewcount) * ($progressend - $progressstart)), "Exporting Views ($i/$viewcount)");
318
            $smarty->assign('page_heading', $view->get('title'));
319
            $smarty->assign('viewdescription', $view->get('description'));
320

321
322
323
324
325
326
327
328
329
330
331
332
333
            if ($this->exportingoneview) {
                $smarty->assign('nobreadcrumbs', true);
                $directory = $this->exportdir . '/' . $this->rootdir;
            }
            else {
                $smarty->assign('breadcrumbs', array(
                    array('text' => get_string('Views', 'view')),
                    array('text' => $view->get('title'), 'path' => 'index.html'),
                ));
                $directory = $this->exportdir . '/' . $this->rootdir . '/views/' . self::text_to_path($view->get('title'));
                if (!check_dir_exists($directory)) {
                    throw new SystemException("Could not create directory for view $viewid");
                }
334
            }
Nigel McNie's avatar
Nigel McNie committed
335

336
            $outputfilter = new HtmlExportOutputFilter($rootpath, $this);
337
338
339
340
            $smarty->assign('view', $outputfilter->filter($view->build_columns()));
            $content = $smarty->fetch('export:html:view.tpl');
            if (!file_put_contents("$directory/index.html", $content)) {
                throw new SystemException("Could not write view page for view $viewid");
Nigel McNie's avatar
Nigel McNie committed
341
342
343
344
345
346
347
348
349
350
351
352
            }
        }
    }

    private function get_view_summary() {
        $smarty = $this->get_smarty('../');

        $views = array();
        foreach ($this->views as $view) {
            if ($view->get('type') != 'profile') {
                $views[] = array(
                    'title' => $view->get('title'),
353
                    'folder' => self::text_to_path($view->get('title')),
Nigel McNie's avatar
Nigel McNie committed
354
355
356
                );
            }
        }
357
358
359
360
        function sort_by_title($a, $b) {
            return strnatcasecmp($a['title'], $b['title']);
        }
        usort($views, 'sort_by_title');
Nigel McNie's avatar
Nigel McNie committed
361
        $smarty->assign('views', $views);
362
363
364
365
366
367
368
369
370
371

        if ($views) {
            $stryouhaveviews = (count($views) == 1)
                ? get_string('youhaveoneview', 'view')
                : get_string('youhaveviews', 'view', count($views));
        }
        else {
            $stryouhaveviews = get_string('youhavenoviews', 'view');
        }
        $smarty->assign('stryouhaveviews', $stryouhaveviews);
Nigel McNie's avatar
Nigel McNie committed
372
373

        return array(
374
            'title' => get_string('Views', 'view'),
Nigel McNie's avatar
Nigel McNie committed
375
376
377
378
            'description' => $smarty->fetch('export:html:viewsummary.tpl'),
        );
    }

379
380
381
382
    /**
     * Copies the static files (stylesheets etc.) into the export
     */
    private function copy_static_files() {
383
        global $THEME;
384
        require_once('file.php');
385
        $staticdir = $this->get('exportdir') . '/' . $this->get('rootdir') . '/static/';
386
387
388
        $directoriestocopy = array();

        // Get static directories from each theme for HTML export
389
        $themestaticdirs = $THEME->get_path('', true, 'export/html');
390
391
392
393
394
395
396
397
398
        foreach ($themestaticdirs as $theme => $dir) {
            $themedir = $staticdir . 'theme/' . $theme . '/static/';
            $directoriestocopy[$dir] = $themedir;
            if (!check_dir_exists($themedir)) {
                throw new SystemException("Could not create theme directory for theme $theme");
            }
        }

        // Smilies
399
        $directoriestocopy[get_config('docroot') . 'js/tinymce/plugins/emotions/img'] = $staticdir . 'smilies/';
400

401
402
403
        $filestocopy = array(
            get_config('docroot') . 'theme/views.css' => $staticdir . 'views.css',
        );
404

405
        foreach ($this->pluginstaticdirs as $dir) {
406
407
408
            $destinationdir = str_replace('export/html/', '', $dir);
            if (!check_dir_exists($staticdir . $destinationdir)) {
                throw new SystemException("Could not create static directory $destinationdir");
409
            }
410
            $directoriestocopy[get_config('docroot') . 'artefact/' . $dir] = $staticdir . $destinationdir;
411
412
        }

413
        foreach ($directoriestocopy as $from => $to) {
414
415
            if (!copyr($from, $to)) {
                throw new SystemException("Could not copy $from to $to");
416
417
            }
        }
418
419
420
421
422
423

        foreach ($filestocopy as $from => $to) {
            if (!copy($from, $to)) {
                throw new SystemException("Could not copy static file $from");
            }
        }
424
425
    }

426
427
428
429
430
431
432
433
434
435
436
437
438
}

abstract class HtmlExportArtefactPlugin {

    protected $exporter;

    protected $fileroot;

    public function __construct(PluginExportHTML $exporter) {
        $this->exporter = $exporter;
        $pluginname = strtolower(substr(get_class($this), strlen('HtmlExport')));
        $this->fileroot = $this->exporter->get('exportdir') . '/' . $this->exporter->get('rootdir') . '/files/' . $pluginname . '/';
        if (!check_dir_exists($this->fileroot)) {
Nigel McNie's avatar
Nigel McNie committed
439
            throw new SystemException("Could not create the temporary export directory $this->fileroot");
440
441
442
443
444
445
446
447
448
449
450
        }
    }

    abstract public function dump_export_data();

    abstract public function get_summary();

    abstract public function get_summary_weight();

}

451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
/**
 * Provides a mechanism for converting the HTML generated by views and 
 * artefacts for the HTML export.
 *
 * Mostly, this means rewriting links to artefacts to point the correct place 
 * in the export.
 */
class HtmlExportOutputFilter {

    /**
     * The relative path to the root of the generated export - used for link munging
     */
    private $basepath = '';

    /**
     * A cache of view titles. See replace_view_link()
     */
    private $viewtitles = array();

    /**
471
     * A cache of folder data. See get_folder_path_for_file()
472
473
474
     */
    private $folderdata = null;

475
476
477
478
    /**
     */
    private $htmlexportcopyproxy = null;

479
480
481
482
    /**
     */
    private $exporter = null;

483
484
485
    /**
     * @param string $basepath The relative path to the root of the generated export
     */
486
    public function __construct($basepath, &$exporter=null) {
487
        $this->basepath = preg_replace('#/$#', '', $basepath);
488
        $this->htmlexportcopyproxy = HtmlExportCopyProxy::singleton();
489
        $this->exporter = $exporter;
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
    }

    /**
     * Filters the given HTML for HTML export purposes
     *
     * @param string $html The HTML to filter
     * @return string      The filtered HTML
     */
    public function filter($html) {
        $wwwroot = preg_quote(get_config('wwwroot'));
        $html = preg_replace(
            array(
                // We don't care about javascript
                '#<script[^>]*>.*?</script>#si',
                // Fix simlies from tinymce
505
                '#<img ([^>]*)src="(' . $wwwroot . ')?/?js/tinymce/plugins/emotions/img/([^"]+)"([^>]+)>#',
506
507
                // No forms
                '#<form[^>]*>.*?</form>#si',
508
509
                // Gratuitous hack for the RSS blocktype
                '#<div id="blocktype_externalfeed_lastupdate">[^<]*</div>#',
510
511
512
            ),
            array(
                '',
513
                '<img $1src="' . $this->basepath . '/static/smilies/$3"$4>',
514
                '',
515
                '',
516
517
518
519
520
521
522
523
524
525
526
527
528
            ),
            $html
        );

        // Links to views
        $html = preg_replace_callback(
            '#' . $wwwroot . 'view/view\.php\?id=(\d+)#',
            array($this, 'replace_view_link'),
            $html
        );

        // Links to artefacts
        $html = preg_replace_callback(
529
            '#<a[^>]+href="(' . $wwwroot . ')?/?view/artefact\.php\?artefact=(\d+)(&amp;view=\d+)?(&amp;offset=\d+)?"[^>]*>([^<]*)</a>#',
530
531
532
533
534
535
            array($this, 'replace_artefact_link'),
            $html
        );

        // Links to download files
        $html = preg_replace_callback(
536
            '#(?<=[\'"])(' . $wwwroot . ')?/?artefact/file/download\.php\?file=(\d+)((&amp;[a-z]+=[x0-9]+)+)*#',
537
538
539
540
            array($this, 'replace_download_link'),
            $html
        );

541
        // Thumbnails
542
        require_once('file.php');
543
        $html = preg_replace_callback(
544
            '#(?<=[\'"])(' . $wwwroot . ')?/?thumb\.php\?type=([a-z]+)((&amp;[a-z]+=[x0-9]+)+)*#',
545
546
547
548
            array($this, 'replace_thumbnail_link'),
            $html
        );

549
550
        // Images out of the theme directory
        $html = preg_replace_callback(
551
            '#(?<=[\'"])(' . $wwwroot . '|/)?((?:[a-z]+/)*)theme/([a-zA-Z0-9_.-]+)/static/images/([a-z0-9_.-]+)#',
552
553
554
555
            array($this, 'replace_theme_image_link'),
            $html
        );

556
557
558
559
560
561
562
563
564
        return $html;
    }

    /**
     * Callback to replace links to views to point to the correct location in 
     * the HTML export
     */
    private function replace_view_link($matches) {
        $viewid = $matches[1];
565
566
567
568
        // Don't rewrite links to views that are not going to be included in the export
        if (!isset($this->exporter->views[$viewid])) {
            return $matches[0];
        }
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
        if (!isset($this->viewtitles[$viewid])) {
            $this->viewtitles[$viewid] = PluginExportHtml::text_to_path(get_field('view', 'title', 'id', $viewid));
        }
        return $this->basepath . '/views/' . $this->viewtitles[$viewid] . '/index.html';
    }

    /**
     * Callback to replace links to artefact to point to the correct location 
     * in the HTML export
     */
    private function replace_artefact_link($matches) {
        $artefactid = $matches[2];
        $artefact = artefact_instance_from_id($artefactid);

        switch ($artefact->get('artefacttype')) {
        case 'blog':
585
586
587
            $offset = ($matches[4]) ? intval(substr($matches[4], strlen('&amp;offset='))) : 0;
            $offset = ($offset == 0) ? 'index' : $offset;
            return '<a href="' . $this->basepath . '/files/blog/' . PluginExportHtml::text_to_path($artefact->get('title')) . '/' . $offset . '.html">' . $matches[5] . '</a>';
588
        case 'file':
589
        case 'folder':
590
        case 'image':
591
        case 'archive':
592
            $folderpath = $this->get_folder_path_for_file($artefact);
593
            return '<a href="' . $this->basepath . '/files/file/' . $folderpath . PluginExportHtml::sanitise_path($artefact->get('title')) . '">' . $matches[5] . '</a>';
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
        default:
            return $matches[5];
        }
    }

    /**
     * Callback to replace links to artefact/file/download.php to point to the 
     * correct file in the HTML export
     */
    private function replace_download_link($matches) {
        $artefactid = $matches[2];
        $artefact = artefact_instance_from_id($artefactid);

        // If artefact type not something that would be served by download.php, 
        // replace link with nothing
        if ($artefact->get_plugin_name() != 'file') {
            return '';
        }

613
614
615
616
617
618
619
620
621
622
623
        $options = array();
        if (isset($matches[3])) {
            $parts = explode('&amp;', substr($matches[3], 5));
            foreach ($parts as $part) {
                list($key, $value) = explode('=', $part);
                $options[$key] = $value;
            }
        }

        $folderpath = $this->get_folder_path_for_file($artefact);
        return $this->get_export_path_for_file($artefact, $options, '/files/file/' . $folderpath);
624
625
626
    }

    /**
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
     * Callback to replace links to thumb.php to point to the correct file in 
     * the HTML export
     */
    private function replace_thumbnail_link($matches) {
        if (isset($matches[3])) {
            $type = $matches[2];

            $parts = explode('&amp;', substr($matches[3], 5));
            foreach ($parts as $part) {
                list($key, $value) = explode('=', $part);
                $options[$key] = $value;
            }

            if (!isset($options['id'])) {
                return '';
            }

            switch ($type) {
            case 'profileicon':
                // Convert the user ID to a profile icon ID
                if (!$options['id'] = get_field_sql('SELECT profileicon FROM {usr} WHERE id = ?', array($options['id']))) {
648
649
650
                    // No profile icon, get the default one
                    list($size, $prefix) = $this->get_size_from_options($options);
                    if ($from = get_dataroot_image_path('artefact/file/profileicons/no_userphoto/' . get_config('theme'), 0, $size)) {
651
                        $to = '/static/profileicons/0-' . $prefix . 'no_userphoto.png';
652
653
654
                        $this->htmlexportcopyproxy->add($from, $to);
                    }
                    return $this->basepath . $to;
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
                }
            case 'profileiconbyid':
                $icon = artefact_instance_from_id($options['id']);
                if ($icon->get_plugin_name() != 'file') {
                    return '';
                }
                $folderpath = $this->get_folder_path_for_file($icon);
                return $this->get_export_path_for_file($icon, $options, '/static/profileicons/');
            default:
                return '';
            }
        }

        return '';
    }

671
672
673
674
    /**
     * Callback
     */
    private function replace_theme_image_link($matches) {
675
        $file = $matches[2] . 'theme/' . $matches[3] . '/static/images/' . $matches[4];
676
677
678
679
680
681
682
        $this->htmlexportcopyproxy->add(
            get_config('docroot') . $file,
            '/static/' . $file
        );
        return $this->basepath . '/static/' . $file;
    }

683
684
    /**
     * Given a file, returns the folder path for it in the Mahara files area
685
     *
686
     * The path is pre-sanitised so it can be used when generating the export
687
     *
688
     * @param  $file The file or folder to get the folder path for
689
690
     * @return string
     */
691
    private function get_folder_path_for_file($file) {
692
693
        if ($this->folderdata === null) {
            $this->folderdata = get_records_select_assoc('artefact', "artefacttype = 'folder' AND owner = ?", array($file->get('owner')));
694
695
696
697
            if ($this->folderdata) {
                foreach ($this->folderdata as &$folder) {
                    $folder->title = PluginExportHtml::sanitise_path($folder->title);
                }
698
            }
699
700
701
702
703
        }
        $folderpath = ArtefactTypeFileBase::get_full_path($file->get('parent'), $this->folderdata);
        return $folderpath;
    }

704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
    /**
     * Generates a path, relative to the root of the export, that the given 
     * file will appear in the export.
     *
     * If the file is a thumbnail, the copy proxy is informed about it so that 
     * the image can later be copied in to place.
     *
     * @param ArtefactTypeFileBase $file The file to get the exported path for
     * @param array $options             Options from the URL that was linking 
     *                                   to the image - most importantly, size 
     *                                   related options about how the image 
     *                                   was thumbnailed, if it was.
     * @param string $basefolder         What folder in the export to dump the 
     *                                   file in
     * @return string                    The relative path to where the file 
     *                                   will be placed
     */
    private function get_export_path_for_file(ArtefactTypeFileBase $file, array $options, $basefolder) {
        unset($options['view']);
        $prefix = '';
        if ($options) {
725
            list($size, $prefix) = $this->get_size_from_options($options);
726
727
            $from = $file->get_path($size);

728
            $to = $basefolder . $file->get('id') . '-' . $prefix . PluginExportHtml::sanitise_path($file->get('title'));
729
730
731
732
733
734
735
736
737
            $this->htmlexportcopyproxy->add($from, $to);
        }
        else {
            $to = $basefolder . PluginExportHtml::sanitise_path($file->get('title'));
        }

        return $this->basepath . $to;
    }

738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
    /**
     * Helper method
     */
    private function get_size_from_options($options) {
        $prefix = '';
        foreach (array('size', 'width', 'height', 'maxsize', 'maxwidth', 'maxheight') as $param) {
            if (isset($options[$param])) {
                $$param = $options[$param];
                $prefix .= $param . '-' . $options[$param] . '-';
            }
            else {
                $$param = null;
            }
        }

        return array(imagesize_data_to_internal_form($size, $width, $height, $maxsize, $maxwidth, $maxheight), $prefix);
    }

756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
}

/**
 * Gathers a list of files that need to be copied into the export, as they're 
 * found by the HtmlExportOutputFilter
 */
class HtmlExportCopyProxy {

    private static $instance = null;
    private $copy = array();

    private function __construct() {
    }

    public static function singleton() {
        if (is_null(self::$instance)) {
            self::$instance = new HtmlExportCopyProxy();
        }
        return self::$instance;
    }

    public function add($from, $to) {
        $this->copy[$from] = $to;
    }

    public function get_copy_data() {
        return $this->copy;
    }
784
785
}

786
?>