ElasticsearchType_artefact.php 20.7 KB
Newer Older
1
<?php
2
class ElasticsearchType_artefact extends ElasticsearchType {
3 4 5 6 7
    // New style v6 mapping
    public static $mappingconfv6 = array (
            'type' => array(
                'type' => 'keyword',
            ),
8 9
            'mainfacetterm' => array ( // this is the 2nd level in the hierarchy artefacttype|artefactgroup|mainfacetterm
                    'type' => 'keyword',
10
            ),
11 12
            'secfacetterm' => array ( // this is the 1st level in the hierarchy artefacttype|artefactgroup|mainfacetterm
                    'type' => 'keyword',
13
            ),
14
            'id' => array (
15 16
                    'type' => 'long',
            ),
17 18
            'artefacttype' => array (
                    'type' => 'keyword',
19
            ),
20 21
            'title' => array (
                    'type' => 'text',
22
                    'copy_to' => 'catch_all'
23
            ),
24 25
            'description' => array (
                    'type' => 'text',
26
                    'copy_to' => 'catch_all'
27
            ),
28 29
            'tags' => array (
                    'type' => 'keyword',
30
                    'copy_to' => ['tag', 'catch_all']
31
            ),
32 33 34
            'tag' => array (
                    'type' => 'keyword'
            ),
35
            // the owner can be owner (user), group, or institution
36
            'owner' => array (
37 38
                    'type' => 'long',
            ),
39
            'group' => array (
40 41
                    'type' => 'long',
            ),
42 43
            'institution' => array (
                    'type' => 'keyword',
44
            ),
45
            'access' => array (
46 47 48
                    'type' => 'object',
                    // public - logged - friends: if artefact is visible to public or logged-in users
                    // if public or logged, the other properties are ignored
49 50 51 52 53 54 55 56 57 58 59 60 61 62
                    'properties' => array (
                            'general' => array (
                                    'type' => 'keyword',
                            ),
                            // array of institutions that have access to the artefact
                            'institutions' => array (
                                    'type' => 'keyword',
                                    'copy_to' => 'institution',
                            ),
                            'institution' => array (
                                    'type' => 'keyword',
                            ),
                            // array of groups that have access to the artefact - empty (all), member, admin
                            'groups' => array (
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
                                    'type' => 'object',
                                    'properties' => array (
                                        'all' => array (
                                            'type' => 'integer',
                                            'copy_to' => 'group',
                                        ),
                                        'admin' => array (
                                            'type' => 'integer',
                                            'copy_to' => 'group',
                                        ),
                                        'member' => array (
                                            'type' => 'integer',
                                            'copy_to' => 'group',
                                        ),
                                        'tutor' => array (
                                            'type' => 'integer',
                                            'copy_to' => 'group',
                                        )
                                    )
82
                            ),
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
                            'group' => array (
                                    'type' => 'integer'
                            ),
                            // array of user ids that have access to the artefact
                            'usrs' => array (
                                    'type' => 'integer',
                                    'copy_to' => 'usr',
                            ),
                            'usr' => array (
                                    'type' => 'integer'
                            )
                    )
            )
            ,
            'ctime' => array (
98 99 100 101
                    'type' => 'date',
                    'format' => 'YYYY-MM-dd HH:mm:ss',
            ),
            // sort is the field that will be used to sort the results alphabetically
102 103
            'sort' => array (
                    'type' => 'keyword',
104 105
            ),
            // artefact_license.name is used to store the license
106 107 108
            'license' => array (
                    'type' => 'keyword',
            )
109
    );
110

111 112
    public static $mainfacetterm = 'Text'; // can be Text or Media depending on artefacttype
    public static $subfacetterm = 'artefacttype';
113 114
    public function __construct($data) {
        $this->conditions = array ();
115

116
        $this->mapping = array (
117
                'mainfacetterm' => NULL,
118 119 120 121 122 123 124 125 126 127 128 129 130
                'secfacetterm' => NULL,
                'id' => NULL,
                'artefacttype' => NULL,
                'title' => NULL,
                'description' => NULL,
                'tags' => NULL,
                'owner' => NULL,
                'group' => NULL,
                'institution' => NULL,
                'access' => NULL,
                'ctime' => NULL,
                'sort' => NULL,
                'license' => NULL
131 132
        );

133
        parent::__construct ( $data );
134 135 136
    }

    /**
137
     * set if the record has to be indexed or removed from the index
138
     */
139 140
    public function setisDeleted() {
        parent::setisDeleted ();
141
        // artefacts: if artefacttype is not selected, mark as deleted
142 143
        $artefacttypes = explode ( ',', get_config_plugin ( 'search', 'elasticsearch', 'artefacttypes' ) );
        if (! in_array ( $this->mapping ['artefacttype'], $artefacttypes )) {
144 145 146
            $this->isDeleted = true;
        }
    }
147
    public static function getRecordById($type, $id, $map = null) {
148 149
        $record = parent::getRecordById ( $type, $id );
        if (! $record) {
150 151 152 153
            return false;
        }

        // Tags
154 155 156 157
        $tags = get_records_array ( 'artefact_tag', 'artefact', $id );
        if ( $tags != false ) {
            foreach ( $tags as $tag ) {
                $record->tags [] = $tag->tag;
158 159 160 161 162 163
            }
        }
        else {
            $record->tags = null;
        }
        // Access: get all the views where the artefact is included
164 165 166
        $access = self::view_access_records ( $id );
        $accessObj = self::access_process ( $access );
        if (! $access) {
167
            // File access: get viewable group media not attached to a view
168
            $groupaccess = self::group_artefact_access_records ( $id );
169
            if ($groupaccess) {
170 171
                foreach ( $groupaccess as $access ) {
                    $accessObj ['groups'] [$access->role] [] = $access->can_view;
172 173 174
                }
            }
        }
175 176 177
        $record->access = $accessObj;

        // set 'mainfacetterm' & 'artefactgroup'
178 179 180 181
        if (! empty ( $map ) && isset ( $map [$record->artefacttype] )) {
            $terms = explode ( "|", $map [$record->artefacttype] );
            $record->mainfacetterm = $terms [2];
            $record->secfacetterm = $terms [1];
182 183 184 185
            if ($record->artefacttype == 'country') {
                // We need to index the actual name and not the iso code
                $record->title = get_string("country.{$record->title}");
            }
186

187 188
            require_once( get_config ( 'docroot' ) . 'artefact/resume/lib.php' );
            if (PluginArtefactResume::is_active ()) {
189 190 191 192 193 194
                // If the artefacttype is one of the résumé ones we need to get the description
                // from this artefact's related résumé table. There is a one -> many relationship between
                // the artefact and the items but seen as all resume items are added
                // to a page when choosing One résumé field, rather than selecting them individually,
                // we can just blob together all the info for this résumé artefact into $record->description.

195 196
                $resumetypes = ArtefactTypeResumeComposite::get_composite_artefact_types ();
                if (in_array ( $terms [0], $resumetypes )) {
197
                    try {
198 199 200 201
                        $query = "SELECT * FROM {artefact_resume_" . $terms [0] . "} WHERE artefact = ?";
                        $results = get_records_sql_assoc ( $query, array (
                                $record->id
                        ) );
202
                    }
203
                    catch ( SQLException $e ) {
204
                        // Table doesn't exist
205
                        $results = array ();
206
                    }
207 208 209 210 211 212 213 214
                    foreach ( $results as $result ) {
                        $items = get_object_vars ( $result );
                        foreach ( $items as $key => $item ) {
                            if (! in_array ( $key, array (
                                    'id',
                                    'artefact',
                                    'displayorder'
                            ) )) {
215 216
                                $record->description .= $item . ' ';
                            }
217 218 219 220 221
                        }
                    }
                }
            }
        }
222
        // AS the field "sort" is not analyzed, we need to clean it (remove html tags & lowercase)
223
        $record->sort = strtolower ( strip_tags ( $record->title ) );
224 225 226

        return $record;
    }
227
    public static function getRecordDataById($type, $id) {
228 229 230 231
        global $USER;

        $sql = 'SELECT  a.id, a.artefacttype, a.parent, a.owner, a.title, a.description, a.institution, a.group, a.author,
        p.artefacttype AS parent_artefacttype, p.title AS parent_title, p.description  AS parent_description, a.license,
232
        afi.width, afi.height, a.note
233 234 235 236 237
        FROM {artefact} a
        LEFT OUTER JOIN {artefact} p ON p.id = a.parent
        LEFT OUTER JOIN {artefact_file_image} afi ON afi.artefact = a.id
        WHERE a.id = ?';

238 239 240 241
        $record = get_record_sql ( $sql, array (
                $id
        ) );
        if (! $record) {
242 243 244
            return false;
        }

245 246 247 248 249
        $record->title = str_replace ( array (
                "\r\n",
                "\n",
                "\r"
        ), ' ', strip_tags ( $record->title ) );
250 251 252 253
        if ($record->artefacttype == 'country') {
            // We need to display the actual name and not the iso code
            $record->title = get_string("country.{$record->title}");
        }
254 255 256 257 258
        $record->description = str_replace ( array (
                "\r\n",
                "\n",
                "\r"
        ), ' ', strip_tags ( $record->description ) );
259
        // If user is owner
260
        if ($USER->get ( 'id' ) == $record->owner) {
261
            switch ($record->artefacttype) {
262 263 264 265
                case 'image' :
                case 'video' :
                case 'audio' :
                case 'file' :
266
                    $record->link = 'artefact/file';
267
                    if (isset ( $record->parent ) && intval ( $record->parent ) > 0) {
268 269 270
                        $record->link .= '/index.php?folder=' . $record->parent;
                    }
                    break;
271 272
                case 'blogpost' :
                    if (isset ( $record->parent ) && intval ( $record->parent ) > 0) {
273 274 275
                        $record->link = 'artefact/blog/view/index.php?id=' . $record->parent;
                    }
                    break;
276
                case 'blog' :
277
                    $record->link = 'artefact/blog/view/index.php';
278
                    if ($USER->get_account_preference ( 'multipleblogs' )) {
279 280 281
                        $record->link .= '?id=' . $record->id;
                    }
                    break;
282 283
                case 'coverletter' :
                case 'personalinformation' :
284 285
                    $record->link = 'artefact/resume/index.php';
                    break;
286 287
                case 'educationhistory' :
                case 'employmenthistory' :
288 289
                    $record->link = 'artefact/resume/employment.php';
                    break;
290 291 292
                case 'book' :
                case 'certification' :
                case 'membership' :
293 294
                    $record->link = 'artefact/resume/achievements.php';
                    break;
295 296 297 298 299 300 301
                case 'academicgoal' :
                case 'careergoal' :
                case 'personalgoal' :
                case 'personalinformation' :
                case 'academicskill' :
                case 'personalskill' :
                case 'workskill' :
302 303
                    $record->link = 'artefact/resume/goalsandskills.php';
                    break;
304
                case 'interest' :
305 306
                    $record->link = 'artefact/resume/interests.php';
                    break;
307
                case 'plan' :
308 309
                    $record->link = 'artefact/plans/plan.php?id=' . $record->id;
                    break;
310 311
                case 'task' :
                    if (isset ( $record->parent ) && intval ( $record->parent ) > 0) {
312
                        $record->link = 'artefact/plans/plan.php?id=' . $record->parent;
313 314
                    }
                    break;
315 316 317 318 319 320 321
            }
        }

        // The socialprofile link is for external sites so we can display it for all users.
        if ($record->artefacttype == 'socialprofile') {
            safe_require ( 'artefact', 'internal' );
            $record->note = str_replace ( array (
322 323 324
                            "\r\n",
                            "\n",
                            "\r"
325 326 327
            ), ' ', strip_tags ( $record->note ) );
            $socialprofile = new ArtefactTypeSocialprofile ( $record->id );
            $icons = $socialprofile->get_profile_icons ( array (
328
                            $record
329 330 331 332 333 334 335 336
            ) );
            if (! empty ( $icons )) {
                $record->link = $icons [0]->link;
                $record->icon = $icons [0]->icon;
            }
            else {
                // Instantiate the attribute used by the template.
                $record->icon = '';
337 338 339
            }
        }

340 341
        require_once( get_config ( 'docroot' ) . 'artefact/resume/lib.php' );
        if (PluginArtefactResume::is_active ()) {
342
            // If the artefacttype is one of the résumé ones we need to fetch the related item info
343 344
            $resumetypes = PluginArtefactResume::composite_tabs ();
            if (array_key_exists ( $record->artefacttype, $resumetypes )) {
345 346
                try {
                    $query = "SELECT * FROM {artefact_resume_" . $record->artefacttype . "} WHERE artefact = ?";
347 348 349
                    $results = get_records_sql_assoc ( $query, array (
                            $record->id
                    ) );
350
                }
351
                catch ( SQLException $e ) {
352
                    // Table doesn't exist
353
                    $results = array ();
354 355 356 357 358
                }
                $record->resumeitems = $results;
            }
        }

359 360 361 362 363 364 365 366 367 368 369
        // VIEWS get all the views the artefact is included into.
        // artefact parents are folder, blog, plan, cpd
        $sql = 'SELECT COALESCE(v.id, vp.id) AS id, COALESCE(v.title, vp.title) AS title
        FROM {artefact} a
        LEFT OUTER JOIN {view_artefact} va ON va.artefact = a.id
        LEFT OUTER JOIN {view} v ON v.id = va.view
        LEFT OUTER JOIN {artefact} parent ON parent.id = a.parent
        LEFT OUTER JOIN {view_artefact} vap ON vap.artefact = parent.id
        LEFT OUTER JOIN {view} vp ON vp.id = vap.view
        WHERE a.id = ?';

370 371 372
        $views = get_records_sql_array ( $sql, array (
                $id
        ) );
373

374
        if ($views) {
375 376 377
            $record_views = array ();
            foreach ( $views as $view ) {
                if (isset ( $view->id )) {
378
                    $record_views[$view->id] = $view->title;
379 380
                }
            }
381
            $record_views = self::views_by_artefact_acl_filter($record_views);
382 383 384
            $record->views = $record_views;
        }

385 386
        // Tags
        $tags = get_records_array ( 'artefact_tag', 'artefact', $id );
387
        if ($tags != false) {
388 389
            foreach ( $tags as $tag ) {
                $record->tags [] = $tag->tag;
390 391 392 393 394 395
            }
        }
        else {
            $record->tags = null;
        }

396 397 398 399
        // Created by
        if (intval ( $record->author ) > 0) {
            $record->createdby = get_record ( 'usr', 'id', $record->author );
            $record->createdbyname = display_name ( $record->createdby );
400 401
        }

402
        // Thumb
403
        if ($record->artefacttype == 'image' || $record->artefacttype == 'profileicon') {
404
            if (isset ( $record->width ) && isset ( $record->height ) && intval ( $record->width ) > 0 && intval ( $record->height ) > 0) {
405
                if ($record->width > $record->height) {
406
                    $size = '80x' . intval ( $record->height * 80 / $record->width );
407 408
                }
                else {
409
                    $size = intval ( $record->width * 80 / $record->height ) . 'x80';
410 411
                }
            }
412 413 414 415 416 417 418 419
            $vars = array (
                'id' => $id,
                'size' => $size
            );
            if (!empty($record->views)) {
                $vars['viewid'] = key($record->views); // use first view we are can see
            }
            $record->thumb = ArtefactTypeImage::get_icon ($vars);
420 421 422 423 424 425 426 427 428
        }

        return $record;
    }

    /**
     * Get all access records of the views in which the artefact is included (UNION with parent artefact -> files and blog posts)
     */
    public static function view_access_records($artefactid) {
429
        $records = get_records_sql_array ( '
430 431 432 433 434 435 436 437 438 439 440 441 442 443
                SELECT vac.view AS view_id, vac.accesstype, vac.group, vac.role, vac.usr, vac.institution
                FROM {view_access} vac
                INNER JOIN {view_artefact} vart ON vac.view = vart.view
                WHERE   vart.artefact = ?
                AND (vac.startdate IS NULL OR vac.startdate < current_timestamp)
                AND (vac.stopdate IS NULL OR vac.stopdate > current_timestamp)
                UNION
                SELECT vac.view AS view_id, vac.accesstype, vac.group, vac.role, vac.usr, vac.institution
                FROM {artefact} art
                INNER JOIN {view_artefact} vart ON art.parent = vart.artefact
                INNER JOIN {view_access} vac ON vac.view = vart.view
                WHERE   art.id = ?
                AND (vac.startdate IS NULL OR vac.startdate < current_timestamp)
                AND (vac.stopdate IS NULL OR vac.stopdate > current_timestamp)
444 445 446 447
                ', array (
                $artefactid,
                $artefactid
        ) );
448 449 450 451

        return $records;
    }

452 453 454 455
    /**
     * Get all access records of the group artefacts (called if not attached to view)
     */
    public static function group_artefact_access_records($artefactid) {
456
        $records = get_records_sql_array ( '
457
                SELECT role, can_view FROM {artefact_access_role} WHERE artefact = ?
458 459 460
               ', array (
                $artefactid
        ) );
461 462 463
        return $records;
    }

464 465 466 467 468 469 470 471
    /**
     * Get views linked to a particular artefact, applying ACL
     * This is used to display the list of views in an artefact result, because it's faster to retrieve the info
     * from Elastic search that running the SQL query.
     */
    public static function views_by_artefact_acl_filter($views = array()) {
        global $USER;

472 473 474 475
        $acl = new ElasticsearchFilterAcl($USER);
        $viewmap = function($value) {
            return 'view' . $value;
        };
476

477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
        $filter = array(
            "bool" => array(
                "must" => array(
                    array (
                        array (
                            'term' => array(
                                '_type' => 'doc'
                            )
                        ),
                        array (
                            'terms' => array(
                                '_id' => array_map($viewmap, array_keys($views))
                            )
                        )
                    )
                ),
                "should" => $acl->get_params() ['should']
            )
        );
496

497
        $client = PluginSearchElasticsearch::make_client();
498
        $params = array (
499 500 501 502 503 504 505
            'index' => PluginSearchElasticsearch::get_write_indexname(),
            'body' => array (
                'size' => 10,
                'query' => array (
                    'bool' => array (
                        'filter' => $filter
                    )
506
                )
507
            )
508
        );
509

510
        $results = $client->search ($params);
511 512 513 514 515 516 517
        $valid = array();
        if (!empty($results['hits'])) {
            foreach($results['hits']['hits'] as $item) {
                $valid[$item['_source']['id']] = $views[$item['_source']['id']];
            }
        }
        return $valid;
518 519
    }
}