lib.php 79.2 KB
Newer Older
Penny Leach's avatar
Penny Leach committed
1
2
3
4
<?php
/**
 *
 * @package    mahara
Penny Leach's avatar
Penny Leach committed
5
 * @subpackage artefact-resume
6
 * @author     Catalyst IT Ltd
7
8
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
Penny Leach's avatar
Penny Leach committed
9
10
11
12
13
 *
 */

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

14
class PluginArtefactResume extends PluginArtefact {
15

Penny Leach's avatar
Penny Leach committed
16
17
    public static function get_artefact_types() {
        return array(
18
            'coverletter',
Penny Leach's avatar
Penny Leach committed
19
20
21
22
23
24
25
26
            'contactinformation',
            'personalinformation',
            'employmenthistory',
            'educationhistory',
            'certification',
            'book',
            'membership',
            'interest',
27
28
29
30
31
32
            'personalgoal',
            'academicgoal',
            'careergoal',
            'personalskill',
            'academicskill',
            'workskill'
Penny Leach's avatar
Penny Leach committed
33
34
        );
    }
35

36
    public static function get_block_types() {
37
        return array();
38
    }
Penny Leach's avatar
Penny Leach committed
39
40
41
42
43

    public static function get_plugin_name() {
        return 'resume';
    }

44
45
46
47
    public static function is_active() {
        return get_field('artefact_installed', 'active', 'name', 'resume');
    }

Penny Leach's avatar
Penny Leach committed
48
49
    public static function menu_items() {
        return array(
50
51
            'create/resume' => array(
                'path' => 'create/resume',
52
                'title' => get_string('resume', 'artefact.resume'),
53
                'url' => 'artefact/resume/index.php',
54
                'weight' => 60,
55
            ),
Penny Leach's avatar
Penny Leach committed
56
57
58
        );
    }

59
60
    public static function submenu_items() {
        $tabs = array(
61
62
63
            'subnav' => array(
                'class' => 'resume'
            ),
64
65
            'index' => array(
                'page'  => 'index',
66
                'url'   => 'artefact/resume/index.php',
67
68
                'title' => get_string('introduction', 'artefact.resume'),
            ),
69
70
71
72
73
74
75
76
77
78
            'employment' => array(
                'page'  => 'employment',
                'url'   => 'artefact/resume/employment.php',
                'title' => get_string('educationandemployment', 'artefact.resume'),
            ),
            'achievements' => array(
                'page'  => 'achievements',
                'url'   => 'artefact/resume/achievements.php',
                'title' => get_string('achievements', 'artefact.resume'),
            ),
79
80
81
82
            'goalsandskills' => array(
                'page'  => 'goalsandskills',
                'url'   => 'artefact/resume/goalsandskills.php',
                'title' => get_string('goalsandskills', 'artefact.resume'),
83
84
85
86
87
88
            ),
            'interests' => array(
                'page'  => 'interests',
                'url'   => 'artefact/resume/interests.php',
                'title' => get_string('interests', 'artefact.resume'),
            ),
89
90
91
92
93
            'license' => array(
                'page'  => 'license',
                'url'   => 'artefact/resume/license.php',
                'title' => get_string('license', 'artefact.resume'),
            ),
94
        );
95
96
97
        if (!get_config('licensemetadata')) {
            unset($tabs['license']);
        }
98
99
        if (defined('MENUITEM_SUBPAGE') && isset($tabs[MENUITEM_SUBPAGE])) {
            $tabs[MENUITEM_SUBPAGE]['selected'] = true;
100
101
102
        }
        return $tabs;
    }
103
104
105
106
107
108
109
110
111
112

    public static function composite_tabs() {
        return array(
            'educationhistory'  => 'employment',
            'employmenthistory' => 'employment',
            'certification'     => 'achievements',
            'book'              => 'achievements',
            'membership'        => 'achievements',
        );
    }
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

    public static function artefact_export_extra_artefacts($artefactids) {
        if (!$artefacts = get_column_sql("
            SELECT artefact
            FROM {artefact_attachment}
            WHERE artefact IN (" . join(',', $artefactids) . ')', array())) {
            return array();
        }
        if ($attachments = get_column_sql('
            SELECT attachment
            FROM {artefact_attachment}
            WHERE artefact IN (' . join(',', $artefacts). ')')) {
            $artefacts = array_merge($artefacts, $attachments);
        }
        return $artefacts;
    }
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159

    public static function progressbar_link($artefacttype) {
        switch ($artefacttype) {
            case 'coverletter':
            case 'personalinformation':
                return 'artefact/resume/index.php';
                break;
            case 'educationhistory':
            case 'employmenthistory':
                return 'artefact/resume/employment.php';
                break;
            case 'certification':
            case 'book':
            case 'membership':
                return 'artefact/resume/achievements.php';
                break;
            case 'personalgoal':
            case 'academicgoal':
            case 'careergoal':
            case 'personalskill':
            case 'academicskill':
            case 'workskill':
                return 'artefact/resume/goalsandskills.php';
                break;
            case 'interest':
                return 'artefact/resume/interests.php';
                break;
            default:
                return '';
        }
    }
Penny Leach's avatar
Penny Leach committed
160
161
162
163
}

class ArtefactTypeResume extends ArtefactType {

164
    public static function get_icon($options=null) {}
165
166
167
168
169
170
171

    public function __construct($id=0, $data=array()) {
        if (empty($id)) {
            $data['title'] = get_string($this->get_artefact_type(), 'artefact.resume');
        }
        parent::__construct($id, $data);
    }
172

Penny Leach's avatar
Penny Leach committed
173
174
175
176
177
    public static function is_singular() {
        return false;
    }

    public static function format_child_data($artefact, $pluginname) {
178
        $a = new stdClass();
Penny Leach's avatar
Penny Leach committed
179
180
181
182
183
184
185
186
        $a->id         = $artefact->id;
        $a->isartefact = true;
        $a->title      = '';
        $a->text       = get_string($artefact->artefacttype, 'artefact.resume'); // $artefact->title;
        $a->container  = (bool) $artefact->container;
        $a->parent     = $artefact->id;
        return $a;
    }
Penny Leach's avatar
Penny Leach committed
187
188

    public static function get_links($id) {
189
        // @todo Catalyst IT Ltd
Penny Leach's avatar
Penny Leach committed
190
    }
191
192
193
194
195

    /**
     * Default render method for resume fields - show their description
     */
    public function render_self($options) {
196
        return array('html' => clean_html($this->description));
197
    }
198
199

    /**
200
201
     * Overrides the default commit to make sure that any 'entireresume' blocks
     * in views the user have know about this artefact - but only if necessary.
202
203
204
205
     * Goals and skills are not in the entireresume block
     *
     * @param boolean $updateresumeblocks Whether to update any resume blockinstances
     */
206
207
208
209
    public function commit() {
        parent::commit();

        if ($blockinstances = get_records_sql_array('
Francois Marier's avatar
Francois Marier committed
210
            SELECT id, "view", configdata
211
212
213
214
215
216
217
218
219
220
221
222
223
            FROM {block_instance}
            WHERE blocktype = \'entireresume\'
            AND "view" IN (
                SELECT id
                FROM {view}
                WHERE "owner" = ?)', array($this->owner))) {
            foreach ($blockinstances as $blockinstance) {
                $whereobject = (object)array(
                    'view' => $blockinstance->view,
                    'artefact' => $this->get('id'),
                    'block' => $blockinstance->id,
                );
                ensure_record_exists('view_artefact', $whereobject, $whereobject);
224
225
226
            }
        }
    }
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250

    public function get_license_artefact() {
        if ($this->get_artefact_type() == 'personalinformation')
            return $this;

        $pi = get_record('artefact',
                         'artefacttype', 'personalinformation',
                         'owner', $this->owner);
        if (!$pi)
            return null;

        require_once(get_config('docroot') . 'artefact/lib.php');
        return artefact_instance_from_id($pi->id);
    }


    public function render_license($options, &$smarty) {
        if (!empty($options['details']) and get_config('licensemetadata')) {
            $smarty->assign('license', render_license($this->get_license_artefact()));
        }
        else {
            $smarty->assign('license', false);
        }
    }
251
252
253
254

    /**
     * Render the import entry request for resume fields
     */
255
    public static function render_import_entry_request($entry_content, $renderfields=array()) {
256
257
        return clean_html($entry_content['description']);
    }
Penny Leach's avatar
Penny Leach committed
258
259
260
}

class ArtefactTypeCoverletter extends ArtefactTypeResume {
261

Penny Leach's avatar
Penny Leach committed
262
263
264
265
    public static function is_singular() {
        return true;
    }

266
267
268
269
270
271
272
    public function __construct($id=0, $data=array()) {
        if (empty($id)) {
            $data['title'] = get_string($this->get_artefact_type(), 'artefact.resume');
        }
        parent::__construct($id, $data);
    }

Penny Leach's avatar
Penny Leach committed
273
274
}

275
276
277
278
279
280
281
282
class ArtefactTypeInterest extends ArtefactTypeResume {

    public static function is_singular() {
        return true;
    }

}

Penny Leach's avatar
Penny Leach committed
283
284
class ArtefactTypeContactinformation extends ArtefactTypeResume {

285
    public function render_self($options) {
286
        $smarty = smarty_core();
Penny Leach's avatar
Penny Leach committed
287
288
289
        $fields = ArtefactTypeContactinformation::get_profile_fields();
        foreach ($fields as $f) {
            try {
290
                $$f = artefact_instance_from_type($f, $this->get('owner'));
291
                $rendered = $$f->render_self(array());
292
                $smarty->assign($f, $rendered['html']);
293
                $smarty->assign('hascontent', true);
Penny Leach's avatar
Penny Leach committed
294
295
296
297
            }
            catch (Exception $e) { }
        }

298
299
        $this->render_license($options, $smarty);

300
        return array('html' => $smarty->fetch('artefact:resume:fragments/contactinformation.tpl'));
Penny Leach's avatar
Penny Leach committed
301
302
303
304
305
306
307
    }

    public static function is_singular() {
        return true;
    }

    public static function setup_new($userid) {
308
        try {
309
310
311
312
313
314
315
            return artefact_instance_from_type('contactinformation', $userid);
        } catch (ArtefactNotFoundException $e) {
            $artefact = new ArtefactTypeContactinformation(null, array(
                'owner' => $userid,
                'title' => get_string('contactinformation', 'artefact.resume')
            ));
            $artefact->commit();
316
        }
Penny Leach's avatar
Penny Leach committed
317
318
319
320
321
        return $artefact;
    }

    public static function get_profile_fields() {
        static $fields = array(
322
            'address',
Penny Leach's avatar
Penny Leach committed
323
            'town',
324
325
            'city',
            'country',
Penny Leach's avatar
Penny Leach committed
326
327
328
329
330
331
332
333
            'faxnumber',
            'businessnumber',
            'homenumber',
            'mobilenumber'
        );
        return $fields;
    }

334
335
336
    public static function is_allowed_in_progressbar() {
        return false;
    }
Penny Leach's avatar
Penny Leach committed
337
338
339
}

class ArtefactTypePersonalinformation extends ArtefactTypeResume {
340

341
    protected $composites;
Penny Leach's avatar
Penny Leach committed
342
343
344
345
346
347
348
349

    public function __construct($id=0, $data=null) {
        if (empty($id)) {
            $data['title'] = get_string('personalinformation', 'artefact.resume');
        }
        parent::__construct($id, $data);
        $this->composites = ArtefactTypePersonalinformation::get_composite_fields();
        if (!empty($id)) {
350
351
            $this->composites = (array)get_record('artefact_resume_personal_information', 'artefact', $id,
                null, null, null, null, '*, ' . db_format_tsfield('dateofbirth'));
Penny Leach's avatar
Penny Leach committed
352
353
354
355
356
357
358
359
360
361
362
363
364
365
        }
    }

    public function set_composite($field, $value) {
        if (!array_key_exists($field, $this->composites)) {
            throw new InvalidArgumentException("Tried to set a non existant composite, $field");
        }
        if ($this->composites[$field] == $value) {
            return true;
        }
        // only set it to dirty if it's changed
        $this->dirty = true;
        $this->mtime = time();
        $this->composites[$field] = $value;
366
    }
Penny Leach's avatar
Penny Leach committed
367
368
369
370
371
372
373
374
375
376

    public function get_composite($field) {
        return $this->composites[$field];
    }

    public function commit() {
        if (empty($this->dirty)) {
            return true;
        }

377
        db_begin();
Penny Leach's avatar
Penny Leach committed
378

379
        $data = new stdClass();
380
        $have_composites = false;
Penny Leach's avatar
Penny Leach committed
381
        foreach ($this->composites as $field => $value) {
382
383
384
            if ($field != 'artefact' && !empty($value)) {
                $have_composites = true;
            }
385
386
387
            if ($field == 'dateofbirth' && !empty($value)) {
                $value = db_format_timestamp($value);
            }
388
389
390
            if ($field == 'gender' && $value=='') {
                $value = null;
            }
Penny Leach's avatar
Penny Leach committed
391
            $data->{$field} = $value;
392
393
394
395
396
397
        }
        if (!$have_composites) {
            if (!empty($this->id)) {
                // need to delete empty personal information
                $this->delete();
            }
398
            db_commit();
399
400
            return true;
        }
Penny Leach's avatar
Penny Leach committed
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
        $inserting = empty($this->id);
        parent::commit();
        $data->artefact = $this->id;
        if ($inserting) {
            insert_record('artefact_resume_personal_information', $data);
        }
        else {
            update_record('artefact_resume_personal_information', $data, 'artefact');
        }

        db_commit();
    }

    public static function get_composite_fields() {
        static $composites = array(
416
            'dateofbirth' => null,
417
            'placeofbirth' => null,
418
419
420
421
            'citizenship' => null,
            'visastatus' => null,
            'gender' => null,
            'maritalstatus' => null,
Penny Leach's avatar
Penny Leach committed
422
423
424
425
426
427
428
429
        );
        return $composites;
    }

    public static function is_singular() {
        return true;
    }

430
    public static function render_fields(ArtefactTypePersonalInformation $a=null, $options=array(), $values=null) {
431
        $smarty = smarty_core();
432
433
        $fields = array();
        foreach (array_keys(ArtefactTypePersonalInformation::get_composite_fields()) as $field) {
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
            if ($values && isset($values[$field])) {
                $value = $values[$field];
                // TODO: Make this be a call to a subclass instead of a hard-coded listing
                // of special behaviors for particular fields
                if ($field == 'dateofbirth') {
                    if (empty($value)) {
                        $value = '';
                    }
                    else {
                        $value = strtotime($value);
                    }
                }
            }
            else if ($a) {
                $value = $a->get_composite($field);
            }
            else {
                continue;
            }
453
            if ($field == 'gender' && !empty($value)) {
454
455
456
457
458
459
460
461
                // lang strings changed so need to make changes to deal with new lang string identifiers
                $field .= '1';
                if ($value == 'male') {
                    $value = get_string('man', 'artefact.resume');
                }
                else if ($value == 'female') {
                    $value = get_string('woman', 'artefact.resume');
                }
462
            }
463
            if ($field == 'dateofbirth' && !empty($value)) {
464
                $value = format_date($value+3600, 'strftimedate');
465
            }
466
            $fields[get_string($field, 'artefact.resume')] = $value;
467
468
        }
        $smarty->assign('fields', $fields);
469
470
471
472
473
474
475
476
477
478
        if ($a) {
            $a->render_license($options, $smarty);
        }
        return $smarty->fetch('artefact:resume:fragments/personalinformation.tpl');
    }

    public function render_self($options) {
        return array('html' => self::render_fields($this, $options), 'javascript' => '');
    }

479
    public static function render_import_entry_request($entry_content, $renderfields=array()) {
480
        return self::render_fields(null, array(), $entry_content);
481
482
    }

483
484
485
486
487
488
489
490
    public function delete() {
        db_begin();

        delete_records('artefact_resume_personal_information', 'artefact', $this->id);
        parent::delete();

        db_commit();
    }
491

492
    public static function bulk_delete($artefactids, $log=false) {
493
494
495
496
497
498
499
500
501
502
503
        if (empty($artefactids)) {
            return;
        }

        $idstr = join(',', array_map('intval', $artefactids));

        db_begin();
        delete_records_select('artefact_resume_personal_information', 'artefact IN (' . $idstr . ')');
        parent::bulk_delete($artefactids);
        db_commit();
    }
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542

    /**
     * returns duplicated artefacts which have the same values of the following fields:
     *  - owner
     *  - type
     *  - content which has:
     *      - dateofbirth
     *      - placeofbirth
     *      - citizenship
     *      - visastatus
     *      - gender
     *      - maritalstatus
     *
     * @param array $values
     */
    public static function get_duplicated_artefacts(array $values) {
        $fields = array('dateofbirth', 'placeofbirth', 'citizenship', 'visastatus', 'gender', 'maritalstatus');
        $where = array();
        $wherevalues = array($values['owner'], $values['type']);
        $wherestr = 'WHERE a.owner = ? AND a.artefacttype = ?';
        $contentvalues = $values['content'];
        foreach ($fields as $f) {
            if (!isset($contentvalues[$f])) {
                $wherestr .= ' AND ar.' . $f . ' IS NULL';
            }
            if (!empty($contentvalues[$f])) {
                $where[] = "ar.$f = ?";
                $wherevalues[] = $contentvalues[$f];
            }
        }
        $wherestr .= (!empty($where) ? ' AND ' . join(' AND ', $where) : '');
        return get_column_sql('
            SELECT DISTINCT a.id
            FROM {artefact} AS a
            INNER JOIN {artefact_resume_personal_information} AS ar
            ON a.id = ar.artefact
            ' . $wherestr, $wherevalues
        );
    }
Penny Leach's avatar
Penny Leach committed
543
544
}

545
546
547
548
549
550
551
552
553
554
555
556
/**
 * Helper interface to hold ArtefactTypeResumeComposite's abstract static methods
 */
interface IArtefactTypeResumeComposite {
    /**
    * This function should return a snippet of javascript
    * to be plugged into a table renderer instantiation
    * it comprises the cell function definition
    */
    public static function get_tablerenderer_js();

    public static function get_tablerenderer_title_js_string();
Penny Leach's avatar
Penny Leach committed
557

558
559
560
561
562
563
564
565
566
    public static function get_tablerenderer_body_js_string();

    /**
    * This function should return an array suitable to
    * put into the 'elements' part of a pieform array
    * to generate a form to add an instance
    */
    public static function get_addform_elements();
}
Penny Leach's avatar
Penny Leach committed
567

568
abstract class ArtefactTypeResumeComposite extends ArtefactTypeResume implements IArtefactTypeResumeComposite {
Penny Leach's avatar
Penny Leach committed
569

570
    public static function is_singular() {
571
        return true;
572
573
    }

574
575
576
577
    public static function is_wysiwyg() {
        return false;
    }

578
579
580
581
    public function can_have_attachments() {
        return true;
    }

Penny Leach's avatar
Penny Leach committed
582
583
584
585
586
587
588
589
590
    public static function get_composite_artefact_types() {
        return array(
            'employmenthistory',
            'educationhistory',
            'certification',
            'book',
            'membership'
        );
    }
Penny Leach's avatar
Penny Leach committed
591

592
593
594
595
    public static function get_tablerenderer_extra_js_string() {
        return '';
    }

596
597
598
    public static function get_tablerenderer_attachments_js_string(){
        return '';
    }
599

600
    /**
601
     * Can be overridden to format data retrieved from artefact tables for
602
603
604
605
606
607
     * display of the resume artefact by render_self
     */
    public static function format_render_self_data($data) {
        return $data;
    }

Penny Leach's avatar
Penny Leach committed
608
609
610
611
612
    /**
    * This function processes the form for the composite
    * @throws Exception
    */
    public static function process_compositeform(Pieform $form, $values) {
613
        global $USER;
614
615
616
617
618
        $result = self::ensure_composite_value($values, $values['compositetype'], $USER->get('id'));
        if (isset($result['error'])) {
            $form->reply(PIEFORM_ERR, array('message' => $result['error']));
            if (isset($result['goto'])) {
                redirect($result['goto']);
619
620
            }
        }
621
622
623
        else {
            return $result;
        }
624
    }
625

626
627
628
    /**
     * Ensures that the given value for the given composite is present
     * TODO: expand on these docs.
629
630
631
     * @param unknown_type $values
     * @param unknown_type $compositetype
     * @param unknown_type $owner
632
633
     * @return array If successful, an array contaning 'artefactid' and 'itemid'
     *               Otherwise, an array containing 'error' and optionally 'goto'
634
     * @throws SystemException
635
     */
636
    public static function ensure_composite_value($values, $compositetype, $owner) {
637
        global $USER;
638
639
640
        if (!in_array($compositetype, self::get_composite_artefact_types())) {
            throw new SystemException("ensure_composite_value called with invalid composite type");
        }
641
        try {
642
            $a = artefact_instance_from_type($compositetype, $owner);
643
644
645
            $a->set('mtime', time());
        }
        catch (Exception $e) {
646
            $classname = generate_artefact_class_name($compositetype);
647
            $a = new $classname(0, array(
648
649
                'owner' => $owner,
                'title' => get_string($compositetype, 'artefact.resume'),
650
651
                )
            );
652
        }
653
654
655
656
657

        $a->commit();

        $values['artefact'] = $a->get('id');

658
        $table = 'artefact_resume_' . $compositetype;
659
        if (!empty($values['id'])) {
660
            $itemid = $values['id'];
661
            update_record($table, (object)$values, 'id');
Penny Leach's avatar
Penny Leach committed
662
663
        }
        else {
664
665
666
667
668
669
670
            if (isset($values['displayorder'])) {
                $values['displayorder'] = intval($values['displayorder']);
            }
            else {
                $max = get_field($table, 'MAX(displayorder)', 'artefact', $values['artefact']);
                $values['displayorder'] = is_numeric($max) ? $max + 1 : 0;
            }
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
            $itemid = insert_record($table, (object)$values, 'id', true);
        }

        // If there are any attachments, attach them to your Resume...
        if ($compositetype == 'educationhistory' || $compositetype == 'employmenthistory') {
            $goto = get_config('wwwroot') . 'artefact/resume/employment.php';
        }
        else {
            $goto = get_config('wwwroot') . 'artefact/resume/achievements.php';
        }

        // Attachments via 'files' pieform element
        // This happens when adding new resume composite...
        if (array_key_exists('attachments', $values)) {
            require_once(get_config('libroot') . 'uploadmanager.php');
            safe_require('artefact', 'file');

            $folderid = null;
            $attachment = (object) array(
                'owner'         => $owner,
                'group'         => null, // Group
                'institution'   => null, // Institution
                'author'        => $owner,
                'allowcomments' => 0,
                'parent'        => $folderid,
                'description'   => null,
            );

            foreach ($values['attachments'] as $filesindex) {
                $originalname = $_FILES[$filesindex]['name'];
                $attachment->title = ArtefactTypeFileBase::get_new_file_title(
                    $originalname,
                    $folderid,
                    $owner,
                    null, // Group
                    null  // Institution
                );

                try {
                    $fileid = ArtefactTypeFile::save_uploaded_file($filesindex, $attachment);
                }
                catch (QuotaExceededException $e) {
713
                    return array('error'=>$e->getMessage(), 'goto'=>$goto);
714
715
                }
                catch (UploadException $e) {
716
                    return array('error'=>$e->getMessage(), 'goto'=>$goto);
717
718
719
720
721
722
723
724
725
726
727
                }

                $a->attach($fileid, $itemid);
            }
        }

        // Attachments via 'filebrowser' pieform element
        // This happens when editing resume composite...
        if (array_key_exists('filebrowser', $values)) {
            $old = $a->attachment_id_list_with_item($itemid);
            $new = is_array($values['filebrowser']) ? $values['filebrowser'] : array();
728
729
730
731
732
733
734
            // only allow the attaching of files that exist and are editable by user
            foreach ($new as $key => $fileid) {
                $file = artefact_instance_from_id($fileid);
                if (!($file instanceof ArtefactTypeFile) || !$USER->can_publish_artefact($file)) {
                    unset($new[$key]);
                }
            }
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
            if (!empty($new) || !empty($old)) {
                foreach ($old as $o) {
                    if (!in_array($o, $new)) {
                        try {
                            $a->detach($o, $itemid);
                        }
                        catch (ArtefactNotFoundException $e) {}
                    }
                }
                $is_error = false;
                foreach ($new as $n) {
                    if (!in_array($n, $old)) {
                        // check the new item is not already attached to the
                        // artefact under a different $itemid
                        if (record_exists('artefact_attachment', 'artefact', $a->get('id'), 'attachment', $n)) {
                            $artefactfile = artefact_instance_from_id($n);
                            $is_error[] = $artefactfile->get('title');
                        }
                        else {
                            try {
                                $a->attach($n, $itemid);
                            }
                            catch (ArtefactNotFoundException $e) {}
                        }
                    }
                }
                if (!empty($is_error)) {
                    if (sizeof($is_error) > 1) {
763
                        $error = get_string('duplicateattachments', 'artefact.resume', implode('\', \'', $is_error));
764
765
                    }
                    else {
766
                        $error = get_string('duplicateattachment', 'artefact.resume', implode(', ', $is_error));
767
                    }
768
                    return array('error'=>$error);
769
770
                }
            }
Penny Leach's avatar
Penny Leach committed
771
        }
772
        return array('artefactid' => $a->id, 'itemid' => $itemid);
Penny Leach's avatar
Penny Leach committed
773
    }
774
775
776
777
778
779
780
781
782
783
784

    public function delete() {
        $table = $this->get_other_table_name();
        db_begin();

        delete_records($table, 'artefact', $this->id);
        parent::delete();

        db_commit();
    }

785
786
787
788
789
790
791
792
793
794
795
796
797
    public static function bulk_delete_composite($artefactids, $compositetype) {
        $table = 'artefact_resume_' . $compositetype;
        if (empty($artefactids)) {
            return;
        }

        $idstr = join(',', array_map('intval', $artefactids));

        db_begin();
        delete_records_select($table, 'artefact IN (' . $idstr . ')');
        parent::bulk_delete($artefactids);
        db_commit();
    }
798
799

    /**
800
    * Takes a pieform that's been set up by all the
801
802
803
    * subclass get_addform_elements functions
    * and puts the default values in (and hidden id field)
    * ready to be an edit form
804
    *
805
806
807
    * @param $form pieform structure (before calling pieform() on it
    * passed by _reference_
    */
808
809
810
811
812
    public static function populate_form(&$form, $id, $type) {
        if (!$composite = get_record('artefact_resume_' . $type, 'id', $id)) {
            throw new InvalidArgumentException("Couldn't find composite of type $type with id $id");
        }
        $datetypes = array('date', 'startdate', 'enddate');
813
        foreach ($form['elements'] as $k => $element) {
814
            if ($k == 'submit' || $k == 'submitform' ||$k == 'compositetype') {
815
816
                continue;
            }
817
            if (isset($composite->{$k})) {
818
                $form['elements'][$k]['defaultvalue'] = $composite->{$k};
819
820
821
822
            }
        }
        $form['elements']['id'] = array(
            'type' => 'hidden',
823
824
825
826
827
            'value' => $id,
        );
        $form['elements']['artefact'] = array(
            'type' => 'hidden',
            'value' => $composite->artefact,
828
829
830
831
        );
    }


832
    /**
833
834
835
836
837
838
    * call the parent constructor
    * and then load up the stuff from the supporting table
    */
    public function __construct($id=0, $data=array()) {
        if (empty($id)) {
            $data['container'] = 0;
839
            $data['title'] = get_string($this->get_artefact_type(), 'artefact.resume');
840
841
        }
        parent::__construct($id, $data);
842
    }
843

844
    /**
845
846
847
848
849
    * returns the name of the supporting table
    */
    public function get_other_table_name() {
        return 'artefact_resume_' . $this->get_artefact_type();
    }
850

851
    public function render_self($options) {
852
        global $USER;
853
        $suffix = '_' . substr(md5(microtime()), 0, 4);
854
855
        $attachmessage = get_string('fileattachmessage', 'artefact.resume',
                         get_string('fileattachdirname', 'artefact.resume'));
856
        $smarty = smarty_core();
857
        $smarty->assign('user', $USER->get('id'));
858
        $smarty->assign('hidetitle', true);
859
        $smarty->assign('suffix', $suffix);
860
        $smarty->assign('attachmessage', $attachmessage);
861
        $type = $this->get('artefacttype');
862
863
864
865
        $othertable = 'artefact_resume_' . $type;
        $owner = $USER->get('id');

        $sql = 'SELECT ar.*, a.owner
866
            FROM {artefact} a
867
868
869
870
            JOIN {' . $othertable . '} ar ON ar.artefact = a.id
            WHERE a.owner = ? AND a.artefacttype = ?
            ORDER BY ar.displayorder';

871
        if (!empty($options['viewid'])) {
872
            require_once('view.php');
873
            $smarty->assign('viewid', $options['viewid']);
874
875
876
877
            $v = new View($options['viewid']);
            $owner = $v->get('owner');
        }

Pat Kira's avatar
Pat Kira committed
878
879
880
881
        if (!empty($options['artefactid'])) {
            $smarty->assign('artefactid', $options['artefactid']);
        }

882
883
884
885
886
887
        if (!$data = get_records_sql_array($sql, array($owner, $type))) {
            $data = array();
        }

        // Give the artefact type a chance to format the data how it sees fit
        $data = call_static_method(generate_artefact_class_name($type), 'format_render_self_data', $data);
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907

        // Add artefact attachments it there are any
        $datawithattachments = array();
        foreach ($data as $record) {
            // Cannot use $this->get_attachments() as it would return
            // all the attachments for specified resume composite.
            // Instead we want only attachments for single item of the
            // specified resume composite...
            $sql = 'SELECT a.title, a.id, af.size
                    FROM {artefact} a
                    JOIN {artefact_file_files} af ON af.artefact = a.id
                    JOIN {artefact_attachment} at ON at.attachment = a.id
                    WHERE at.artefact = ? AND at.item = ?
                    ORDER BY a.title';
            $attachments = get_records_sql_array($sql, array($record->artefact, $record->id));
            if ($attachments) {
                foreach ($attachments as &$attachment) {
                    $f = artefact_instance_from_id($attachment->id);
                    $attachment->size = $f->describe_size();
                    $attachment->iconpath = $f->get_icon(array('id' => $attachment->id, 'viewid' => isset($options['viewid']) ? $options['viewid'] : 0));
Pat Kira's avatar
Pat Kira committed
908
                    $attachment->artefacttype = $f->get_artefact_type($attachment->id);
909
                    $attachment->viewpath = get_config('wwwroot') . 'artefact/artefact.php?artefact=' . $attachment->id . '&view=' . (isset($options['viewid']) ? $options['viewid'] : 0);
910
911
912
913
914
915
916
917
918
919
920
                    $attachment->downloadpath = get_config('wwwroot') . 'artefact/file/download.php?file=' . $attachment->id;
                    $attachment->description = $f->description;
                }
            }
            $record->attachments = $attachments;
            if (!is_array($attachments)) {
                $record->clipcount = 0;
            }
            else {
                $record->clipcount = count($attachments);
            }
921
922
923
924
925
926
927
928
929
930
931
            // Clean up description before displaying it
            if (isset($record->qualdescription)) {
                $record->qualdescription = clean_html($record->qualdescription);
            }
            else if (isset($record->positiondescription)) {
                $record->positiondescription = clean_html($record->positiondescription);
            }
            else {
                $record->description = clean_html($record->description);
            }

932
933
934
935
            $datawithattachments[] = $record;
        }

        $smarty->assign('rows', $datawithattachments);
936
        $this->render_license($options, $smarty);
937

938
939
        $content = array(
            'html'         => $smarty->fetch('artefact:resume:fragments/' . $type . '.tpl'),
940
941
            'javascript'   => $this->get_showhide_composite_js()
        );
942
943
944
        return $content;
    }

945
    public static function render_import_entry_request($entry_content, $renderfields=array()) {
946
947
948
949
950
951
952
953
954
        $smarty = smarty_core();
        $fields = array();
        foreach ($renderfields as $field) {
            $fields[get_string($field, 'artefact.resume')] = isset($entry_content[$field]) ? $entry_content[$field] : '';
        }
        $smarty->assign('fields', $fields);
        return $smarty->fetch('artefact:resume:import/resumecompositefields.tpl');
    }

955
956
957
958
959
960
961
962
963
964
965
966
967
    public static function get_js(array $compositetypes) {
        $js = self::get_common_js();
        foreach ($compositetypes as $compositetype) {
            $js .= call_static_method(
                generate_artefact_class_name($compositetype),
                'get_artefacttype_js',
                $compositetype
            );
        }
        return $js;
    }

    public static function get_common_js() {
968
969
        $cancelstr = json_encode(get_string('cancel'));
        $addstr = json_encode(get_string('add'));
970
971
972
973
974
975
        $confirmdelstr = get_string('compositedeleteconfirm', 'artefact.resume');
        $js = <<<EOF
var tableRenderers = {};

function compositeSaveCallback(form, data) {
    key = form.id.substr(3);
976

977
    // Can't reset() the form here, because its values are what were just submitted,
978
    // thanks to pieforms
979
980
    \$j('#' + form.id + ' input:text, #' + form.id + ' textarea').each(function() {
        \$j(this).attr('value', '');
981
    });
982
983
984
985
    // Also need to clear the innerHTML for textareas
    \$j('#' + form.id + ' textarea').each(function() {
        document.getElementById(\$j(this).attr('id')).innerHTML = '';
    });
986
987
988

    \$j('#' + key + 'form').collapse('hide');

989
    tableRenderers[key].doupdate(null, { focusid: data['focusid'] });
990
    \$j('#add' + key + 'button').trigger("focus");
991
992
993
994
995
996
997
998
999
    // Do a double check to make sure the formchange checker for the submitted form is actually reset
    tableRenderers[key].postupdatecallback = function(response) {
        var checkers = formchangemanager.formcheckers;
        for (var i=0; i < checkers.length; i ++) {
            if (checkers[i].id == form.id) {
                checkers[i].state = FORM_INIT;
            }
        }
    }
1000
    formSuccess(form, data);
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
}

function deleteComposite(type, id, artefact) {
    if (confirm('{$confirmdelstr}')) {
        sendjsonrequest('compositedelete.json.php',
            {'id': id, 'artefact': artefact},
            'GET',
            function(data) {
                tableRenderers[type].doupdate();
            },
            function() {
                // @todo error
            }
        );
    }
    return false;
}

function moveComposite(type, id, artefact, direction) {
    sendjsonrequest('compositemove.json.php',
        {'id': id, 'artefact': artefact, 'direction':direction},
        'GET',
        function(data) {
1024
            tableRenderers[type].doupdate(null, { focusid: id });
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
        },
        function() {
            // @todo error
        }
    );
    return false;
}
EOF;
        $js .= self::get_showhide_composite_js();
        return $js;
    }

1037
    static function get_tablerenderer_title_js($titlestring, $extrastring, $bodystring, $attachstring, $addressstring='false') {
1038
1039
        return <<<EOF
                function (row, data) {
1040
                    if (!{$bodystring} && !{$attachstring} && !{$addressstring}) {
1041
1042
1043
1044
                      return jQuery('<td>').append(
                        jQuery('<span>').append({$titlestring}),
                        jQuery('<div>', {'class': 'detail text-midtone'}).append({$extrastring})
                      )[0];
1045
                    }
1046
1047
1048
                    var link = jQuery('<a>', {'class': 'toggle textonly', 'href': ''}).append({$titlestring})[0];
                    jQuery(link).on('click', function (e) {
                        e.preventDefault();
1049
                        return showhideComposite(row, {$bodystring}, {$attachstring}, {$addressstring});
1050
                    });
1051
1052
1053
1054
                    var extra = jQuery('<div>', {'class': 'detail text-midtone'}).append({$extrastring});
                    return jQuery('<td>', {'id': 'composite-' + row.artefact + '-' + row.id}).append(
                        jQuery('<div>', {'class': 'expandable-head'}).append(link, extra)
                    )[0];
1055
                },
1056
EOF;
1057
1058
1059
    }

    static function get_showhide_composite_js() {
1060
        return <<<EOF
1061
            function showhideComposite(row, content, attachments, address) {
1062
                // get the reference for the title we just clicked on
1063
1064
1065
                var titleTD = jQuery('#composite-' + row.artefact + '-' + row.id);
                var bodyNode = jQuery('#composite-body-' + row.artefact +  '-' + row.id);
                if (bodyNode.length) {
1066
                    bodyNode.toggleClass('d-none');
1067
1068
                    return false;
                }
1069
1070
                    var newNode = jQuery('<div>', {'id': 'composite-body-' + row.artefact + '-' + row.id}).append(
                      jQuery('<div>', {'class':'content-text'}).append(content),
1071
                      address,
1072
1073
1074
                      attachments
                      );
                newNode.insertAfter(titleTD.find('.expandable-head').first());
1075
            }
1076
EOF;
1077
    }
1078

1079
1080
    static function get_artefacttype_js($compositetype) {
        global $THEME;
1081
1082
1083
1084
1085
1086
        $titlestring = call_static_method(generate_artefact_class_name($compositetype), 'get_tablerenderer_title_js_string');
        $editstr = json_encode(get_string('edit'));
        $delstr = json_encode(get_string('delete'));
        $editjsstr = json_encode(get_string('editspecific', 'mahara', '%s')) . ".replace('%s', {$titlestring})";
        $deljsstr = json_encode(get_string('deletespecific', 'mahara', '%s')) . ".replace('%s', {$titlestring})";

1087
        $upstr = get_string('moveup', 'artefact.resume');
1088
        $upjsstr = json_encode(get_string('moveupspecific', 'artefact.resume', '%s')) . ".replace('%s', {$titlestring})";
1089
        $downstr = get_string('movedown', 'artefact.resume');
1090
        $downjsstr = json_encode(get_string('movedownspecific', 'artefact.resume', '%s')) . ".replace('%s', {$titlestring})";
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102

        $js = call_static_method(generate_artefact_class_name($compositetype), 'get_composite_js');

        $js .= <<<EOF
tableRenderers.{$compositetype} = new TableRenderer(
    '{$compositetype}list',
    'composite.json.php',
    [
EOF;

        $js .= <<<EOF

1103
        function (row, data) {
1104
            var buttons = [];
1105
            if (row._rownumber > 1) {
1106
                var up =
1107
1108
1109
1110
1111
1112
1113
                    jQuery('<a>', {'href': '', 'class': 'moveup'}).append(
                        jQuery('<span>',{'class': 'icon icon-long-arrow-up','role':'presentation'}),
                        jQuery('<span>',{'class': 'sr-only', 'text': {$upjsstr}})
                    );
                    up.on('click', function (e) {
                    e.preventDefault();
                    return moveComposite(data.type, row.id, row.artefact, 'up');
1114
1115
1116
                });
                buttons.push(up);
            }
1117
            if (!row._last) {
1118
                var down =
1119
1120
1121
1122
1123
1124
1125
                    jQuery('<a>', {'href': '', 'class':'movedown'}).append(
                      jQuery('<span>',{'class': 'icon icon-long-arrow-down','role':'presentation'}),
                      jQuery('<span>',{'class': 'sr-only', 'text': {$downjsstr}})
                    );
                    down.on('click', function (e) {
                    e.preventDefault();
                    return moveComposite(data.type, row.id, row.artefact, 'down');
1126
1127
1128
1129
                });
                buttons.push(' ');
                buttons.push(down);
            }
1130
            return jQuery('<td>',{'class':'movebuttons'}).append(buttons)[0];
1131
        },
1132
1133
1134
1135
1136
EOF;

        $js .= call_static_method(generate_artefact_class_name($compositetype), 'get_tablerenderer_js');

        $js .= <<<EOF
1137
        function (row, data) {
1138
            var editlink =
1139
                jQuery('<a>', {'href': 'editcomposite.php?id=' + row.id + '&artefact=' + row.artefact,
1140
                               'title': {$editstr}, 'class': 'btn btn-secondary btn-sm'}).append(
1141
1142
1143
                                    jQuery('<span>',{'class': 'icon icon-pencil icon-lg', 'role':'presentation'}),
                                    jQuery('<span>',{'class': 'sr-only'}).append({$editjsstr})
                               );
1144
            var dellink =
1145
                jQuery('<a>', {'href': '', 'title': {$delstr}, 'class': 'btn btn-secondary btn-sm'}).append(
1146
1147
1148
1149
1150
1151
                    jQuery('<span>',{'class': 'icon icon-trash text-danger icon-lg','role':'presentation'}),
                    jQuery('<span>',{'class': 'sr-only'}).append({$deljsstr})
                );
                dellink.on('click', function (e) {
                e.preventDefault();
                return deleteComposite(data.type, row.id, row.artefact);
1152
            });
1153
1154
            return jQuery('<td>', {'class':'control-buttons'}).append(
                jQuery('<div>', {'class':'btn-group'}).append( null, editlink, ' ', dellink))[0];
1155
        }
1156
1157
1158
1159
    ],
    {
        focusElement: 'a:first'
    }
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
);

tableRenderers.{$compositetype}.type = '{$compositetype}';
tableRenderers.{$compositetype}.statevars.push('type');
tableRenderers.{$compositetype}.emptycontent = '';
tableRenderers.{$compositetype}.updateOnLoad();

EOF;
        return $js;
    }

1171
    static function get_composite_js() {
1172
1173
        $attachmentsstr = json_encode(get_string('Attachments', 'artefact.resume'));
        $downloadstr = json_encode(get_string('Download', 'artefact.file'));
1174
        return <<<EOF
1175
function formatSize(size) {
1176
    size = parseInt(size, 10);
1177
1178
1179
1180
1181
1182
1183
1184
    if (size < 1024) {
        return size <= 0 ? '0' : size.toFixed(1).replace(/\.0$/, '') + 'b';
    }
    if (size < 1048576) {
        return (size / 1024).toFixed(1).replace(/\.0$/, '') + 'K';
    }
    return (size / 1048576).toFixed(1).replace(/\.0$/, '') + 'M';
}
1185
1186
function listAttachments(attachments) {
    if (attachments.length > 0) {
1187
1188
1189
        var togglelink = jQuery('<span>').append({$attachmentsstr});
        var thead = jQuery('<thead>').append(jQuery('<tr>').append(jQuery('<th>').append(togglelink)));
        var tbody = jQuery('<tbody>');
1190
        for (var i=0; i < attachments.length; i++) {
1191
1192
            var item = attachments[i];
            var href = self.config.wwwroot + 'artefact/file/download.php?file=' + attachments[i].id;
1193
1194
1195
1196
1197
1198
1199
1200
1201
            var link = jQuery('<a>', {'href': href, 'text': '{$downloadstr}' });
            tbody.append(
              jQuery('<tr>').append(
                jQuery('<td>').append(
                  item.title + ' (' + formatSize(item.size) + ') - ',
                  jQuery('<span>').append(link)
                )
              )
            );
1202
        }
1203
        return jQuery('<table>', {'class': 'table'}).append(thead, tbody)[0];
1204
1205
1206
    }
    else {
        // No attachments
1207
1208
        return '';
    }
1209
1210
1211
}
EOF;
    }
1212
1213
1214
1215
1216
1217
1218

    static function get_forms(array $compositetypes) {
        $compositeforms = array();
        foreach ($compositetypes as $compositetype) {
            $elements = call_static_method(generate_artefact_class_name($compositetype), 'get_addform_elements');
            $elements['submit'] = array(
                'type' => 'submit',
1219
                'class' => 'btn-primary',
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
                'value' => get_string('save'),
            );
            $elements['compositetype'] = array(
                'type' => 'hidden',
                'value' => $compositetype,
            );
            $cform = array(
                'name' => 'add' . $compositetype,
                'plugintype' => 'artefact',
                'pluginname' => 'resume',
                'elements' => $elements,
                'jsform' => true,
                'successcallback' => 'compositeform_submit',
                'jssuccesscallback' => 'compositeSaveCallback',
            );
            $compositeforms[$compositetype] = pieform($cform);
        }
        return $compositeforms;
    }

1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
    public function get_license_artefact() {
        $pi = get_record('artefact',
                         'artefacttype', $this->artefacttype,
                         'owner', $this->owner);
        if (!$pi)
            return null;

        require_once(get_config('docroot') . 'artefact/lib.php');
        return artefact_instance_from_id($pi->id);
    }
Penny Leach's avatar
Penny Leach committed
1250
}
Penny Leach's avatar
Penny Leach committed
1251

1252
class ArtefactTypeEmploymenthistory extends ArtefactTypeResumeComposite {
Penny Leach's avatar
Penny Leach committed
1253
1254
1255
1256
1257

    protected $startdate;
    protected $enddate;
    protected $employer;

Penny Leach's avatar
Penny Leach committed
1258
    public static function get_tablerenderer_js() {
1259
        return ArtefactTypeResumeComposite::get_tablerenderer_title_js(
1260
                    self::get_tablerenderer_title_js_string(),
1261
                    self::get_tablerenderer_date_js_string(),
1262
                    self::get_tablerenderer_body_js_string(),
1263
1264
                    self::get_tablerenderer_attachments_js_string(),
                    self::get_tablerenderer_address_js_string()
1265
                ) . ",