To receive notifications about scheduled maintenance, please subscribe to the mailing-list gitlab-operations@sympa.ethz.ch. You can subscribe to the mailing-list at https://sympa.ethz.ch

YATA.class.php 47.9 KB
Newer Older
vermeul's avatar
vermeul committed
1
2
3
4
5
6
7
8
9
<?php
/**
 * YATA extension
 * @author Swen Vermeul
 * @file
 * @version 0.1
 * @license GNU General Public Licence 2.0
 */

10
11
12
13
14
use SMW\ApplicationFactory;
#use SMWQuery;
#use SMWQueryProcessor;
#use SMWQueryResult;

vermeul's avatar
vermeul committed
15
class YATA {
16
    public static $annotations = "";
17
    public static $page_annotations = "";
vermeul's avatar
vermeul committed
18
    //
vermeul's avatar
vermeul committed
19
    // Register any render callbacks with the parser
vermeul's avatar
vermeul committed
20
    //
vermeul's avatar
vermeul committed
21
22
23
24
25
    public static function onParserSetup( &$parser ) {

       // Create a function hook associating the "example" magic word with renderExample()
       $parser->setFunctionHook( 'annot', 'YATA::renderStartAnnot' );
       $parser->setFunctionHook( 'annotend', 'YATA::renderEndAnnot' );
vermeul's avatar
vermeul committed
26
       $parser->setFunctionHook( 'annotlist', 'YATA::annotations_list' );
27
       $parser->setFunctionHook( 'annotask', 'YATA::annotation_query' );
vermeul's avatar
vermeul committed
28
       $parser->setFunctionHook( 'annotcat', 'YATA::annotation_categories' );
vermeul's avatar
...    
vermeul committed
29
       $parser->setFunctionHook( 'annotpage', 'YATA::annotation_page' );
vermeul's avatar
vermeul committed
30
31
    }

vermeul's avatar
vermeul committed
32
    //
vermeul's avatar
vermeul committed
33
    // Render the output of {{#annot: comment | category | id}}.
vermeul's avatar
vermeul committed
34
    //
vermeul's avatar
vermeul committed
35
    public static function renderStartAnnot( $parser, $comment='', $category='', $id='' ) {
36
        return "<span id=$id title='$comment'>";
vermeul's avatar
vermeul committed
37
38
    }

vermeul's avatar
vermeul committed
39
    //
vermeul's avatar
vermeul committed
40
    // Render the output of {{#annotend:}}.
vermeul's avatar
vermeul committed
41
    //
vermeul's avatar
vermeul committed
42
    public static function renderEndAnnot( $parser, $id='' ) {
vermeul's avatar
vermeul committed
43
44
45
       return "</span>";
    }
    
vermeul's avatar
vermeul committed
46
    //
vermeul's avatar
vermeul committed
47
    // Render the output of {{#annotask:}}
vermeul's avatar
vermeul committed
48
    //
vermeul's avatar
vermeul committed
49
    public static function annotation_query( $parser, $querystring) {
50
51
        $title = $parser->getTitle();
        $wikiPage = new WikiPage( $title );
vermeul's avatar
vermeul committed
52
53
54
55
56
57
        $queries_found = preg_match_all(
            '/\[\[(?P<query>.*?)\]\]/s',
            $querystring, 
            $query_params, 
            PREG_OFFSET_CAPTURE
        );
vermeul's avatar
vermeul committed
58
59
        $searches = array();
        $in_categories = array();
vermeul's avatar
vermeul committed
60
61
62
63

        $where = array();
        $dbr = wfGetDB( DB_REPLICA );
        $seen = array();
64
        $errors = array();
vermeul's avatar
vermeul committed
65
66
        foreach($query_params["query"] as $query) {
            list($field, $values) = preg_split('/\s*\:\s*/', $query[0]);
67
68
69
70
            # syntax-sugar
            if ($field === 'wikitext') {
                $field = 'wiki_text';
            }
vermeul's avatar
vermeul committed
71
72
73
74
            if( $field === 'category') {
                $categories = preg_split('/\s*\,\s*/', $values);
                $cat_ids = array();
                foreach( $categories as $category ) {
vermeul's avatar
vermeul committed
75
                    array_push($in_categories, $category);
vermeul's avatar
vermeul committed
76
77
78
79
80
81
82
83
84
                    $cat = self::get_category($dbr, $category);
                    if ($cat) {
                        $cat_ids[] = $cat->id;
                        $child_categories = self::get_child_categories($dbr, $cat);
                        foreach($child_categories as $child_category) {
                            $cat_ids[] = $child_category->id;
                        }
                    }
                    else {
85
                        array_push($errors, "no such category found: $category");
vermeul's avatar
vermeul committed
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
                    }
                }
                $where[] = 
                    "EXISTS( 
                        SELECT NULL 
                        FROM yata_annotation_category ac 
                        WHERE ac.annotation_id = a.id
                        AND ac.category_id IN ("
                    . join(',', $cat_ids)
                    . ")
                    )";
            }
            elseif( $field === 'user' ) {
                $where["u.username"] = $values;
            }
            elseif( in_array($field, array('wiki_text', 'comment')) ) {
vermeul's avatar
vermeul committed
102
                array_push($searches, $field . ': ' . $values);
vermeul's avatar
vermeul committed
103
104
105
106
107
108
109
110
                if ( preg_match( '/[\%_]/', $values ) ) {
                    $where[] = "$field LIKE (" . $dbr->addQuotes($values).")";
                }
                else {
                    $where["a.$field"] = $values;
                }
            }
        }
111

112
	if ($errors) {
113
114
115
116
117
            foreach( $errors as $error) {
                $errstr .= $error . "<br/>";
            }
            return $errstr;
        }
vermeul's avatar
vermeul committed
118
119
120
121
122
        

        # fetch the annotation details
        $annotations = $dbr->select(
            array(
vermeul's avatar
vermeul committed
123
124
125
126
127
128
                'a' => 'yata_annotation',
                'u' => 'user',
                'ac'=> 'yata_annotation_category',
                'c' => 'yata_category',
                'pc'=> 'yata_category',
                'p' => 'page',
vermeul's avatar
vermeul committed
129
130
            ),
            array(
vermeul's avatar
vermeul committed
131
132
133
134
135
136
137
138
139
140
                'category'        => "c.name",
                'parent_category' => "pc.name",
                'hashtag'         => "c.hashtag",
                'title'           => "p.page_title",
                'comment'         => "a.comment", 
                'wiki_text'       => "a.wiki_text",
                'start_char'      => "a.start_char",
                'bookmark'        => "a.bookmark",
                'last_edited_by'  => "u.user_name",
                'last_modified'   => "a.modified_date"
vermeul's avatar
vermeul committed
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
            ),
            $where,
            __METHOD__, 
            array(
                'ORDER BY' => 'a.start_char ASC' 
            ), 
            array( 
                'u'  => array( 'LEFT JOIN', array( 'u.user_id = a.user_id') ),
                'ac' => array( 'LEFT JOIN', array( 'ac.annotation_id = a.id' ) ),
                'c'  => array( 'LEFT JOIN', array( 'ac.category_id = c.id' ) ),
                'pc' => array( 'LEFT JOIN', array( 'pc.id = c.parent_id') ),
                'p'  => array( 'INNER JOIN', array( 'p.page_id = a.page_id') ),
            )
        );

156
        $table = "";
vermeul's avatar
vermeul committed
157
158
159
160
161
162
163
164
165
166
167
168
169
        if ($searches) {
            $table .= "<b>Search for term:</b>";
            foreach($searches as $search_string) {
                $table .= "<pre>".$search_string."</pre>";
            }
        }
        if ($in_categories) {
            $table .= "<b>Search in Categories:</b>";
            foreach($in_categories as $category) {
                $table .= "<pre>".$category."</pre>";
            }
        }
        $table .= "<b>Results</b> ";
vermeul's avatar
vermeul committed
170
        $table .= '([' . $title->getFullURL() . '?action=purge' . ' refresh page]):';
vermeul's avatar
vermeul committed
171
172
        if (count($annotations)>0) {
            $table .= '
vermeul's avatar
vermeul committed
173
<table class="wikitable sortable jquery-tablesorter">
vermeul's avatar
vermeul committed
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    <tr>
        <th class="headerSort headerSortUp" tabindex="0" role="columnheader button" title="Sort descending"> Category </th>
        <th class="headerSort" tabindex="0" role="columnheader button" title="Sort ascending"> Comment </th>
        <th class="headerSort" tabindex="0" role="columnheader button" title="Sort ascending"> Link </th>
        <th class="headerSort" tabindex="0" role="columnheader button" title="Sort ascending"> Annotated text </th>
        <th class="headerSort" tabindex="0" role="columnheader button" title="Sort ascending"> Last edited by </th>
        <th class="headerSort" tabindex="0" role="columnheader button" title="Sort ascending"> Modification date </th>
    </tr>';

            # compose the wiki table
            foreach($annotations as $annotation) {
                # if a category has no parent, just show the category name
                # in all other cases show parent_category:child_category
                $cat = $annotation->parent_category ? $annotation->parent_category . "/" . $annotation->category : $annotation->category;


                $wikitext = $annotation->wiki_text;
                $wikitext = htmlentities($wikitext, ENT_QUOTES);

                $a = array('=', '{', '|', '}');
                $b = array('&#61;', '&#123;', '&#124;', '&#125;');
                $wikitext = str_replace($a, $b, $wikitext);
                
                $table .= "<tr><td>".$cat
                       ."</td><td>".$annotation->comment 
                       ."</td><td>[[".$annotation->title."#".$annotation->bookmark . "]]"
                       ."</td><td><pre>" . $wikitext . "</pre>"
                       #." || "
                       ."</td><td>" . $annotation->last_edited_by
                       ."</td><td>" . wfTimestamp( TS_ISO_8601, $annotation->last_modified)
                       ."</td></tr>\n";
            }
            $table .= "</table>";
        }
        else {
            $table .= "<b>No results found.</b>";
vermeul's avatar
vermeul committed
210
        }
vermeul's avatar
vermeul committed
211

vermeul's avatar
vermeul committed
212
        return $table;
vermeul's avatar
vermeul committed
213
    }
vermeul's avatar
vermeul committed
214

vermeul's avatar
vermeul committed
215

vermeul's avatar
vermeul committed
216
    //
vermeul's avatar
vermeul committed
217
    // Render the output of {{#annotlist:}}.
vermeul's avatar
vermeul committed
218
    //
vermeul's avatar
vermeul committed
219
220
221
    public static function annotations_list( $parser ) {
        $title = $parser->getTitle();
        $wikiPage = new WikiPage( $title );
222
223
        #$dbr = wfGetDB( DB_REPLICA );
        $dbr = wfGetDB( DB_MASTER );
vermeul's avatar
vermeul committed
224

vermeul's avatar
vermeul committed
225
226
227
        $table = <<<"EOT"
{| class="wikitable sortable"
|-
vermeul's avatar
vermeul committed
228
! Category !! Hashtag !! Comment !! Link !! Annotated text !! Last edited by !! Modification date
vermeul's avatar
vermeul committed
229

vermeul's avatar
vermeul committed
230
EOT;
231
        # fetch the annotation details
vermeul's avatar
vermeul committed
232
233
234
235
236
        $annotations = $dbr->select(
            array(
                a =>'yata_annotation',
                ac=>'yata_annotation_category',
                c =>'yata_category',
237
238
                pc=>'yata_category',
                u =>'user',
vermeul's avatar
vermeul committed
239
240
            ),
            array(
241
                comment         => "a.comment", 
vermeul's avatar
vermeul committed
242
243
                wiki_text       => "a.wiki_text",
                start_char      => "a.start_char",
vermeul's avatar
vermeul committed
244
                bookmark        => "a.bookmark",
vermeul's avatar
vermeul committed
245
                category        => "c.name",
vermeul's avatar
vermeul committed
246
                hashtag         => "c.hashtag",
247
248
249
                parent_category => "pc.name",
                last_edited_by  => "u.user_name",
                last_modified   => "a.modified_date"
vermeul's avatar
vermeul committed
250
251
            ),
            array(
vermeul's avatar
...    
vermeul committed
252
                'a.page_id' => $wikiPage->getId(),
vermeul's avatar
vermeul committed
253
254
255
256
257
258
            ),
            __METHOD__, 
            array(
                'ORDER BY' => 'a.start_char ASC' 
            ), 
            array( 
259
260
261
262
                'u'  => array( 'LEFT JOIN', array( 'u.user_id = a.user_id') ),
                'ac' => array( 'LEFT JOIN', array( 'ac.annotation_id = a.id' ) ),
                'c'  => array( 'LEFT JOIN', array( 'ac.category_id = c.id' ) ),
                'pc' => array( 'LEFT JOIN', array( 'pc.id = c.parent_id') ),
vermeul's avatar
vermeul committed
263
264
            )
        );
vermeul's avatar
vermeul committed
265

266
        # compose the wiki table
vermeul's avatar
vermeul committed
267
268
269
        foreach($annotations as $annotation) {
            # if a category has no parent, just show the category name
            # in all other cases show parent_category:child_category
270
271
272
273
274
            $wikitext = preg_replace(
                '/{{\s*#annot(?:<end>end)?.*?}}/', 
                '',
                $annotation->wiki_text
            );
275
            $cat = $annotation->parent_category ? $annotation->parent_category . "/" . $annotation->category : $annotation->category;
vermeul's avatar
vermeul committed
276
277
            $table .= "|-\n";
            $table .= "| ".$cat
vermeul's avatar
vermeul committed
278
                   ." || ".$annotation->hashtag
vermeul's avatar
vermeul committed
279
                   ." || ".$annotation->comment 
vermeul's avatar
vermeul committed
280
                   ." || [[$title#".$annotation->bookmark . "]]"
281
282
283
                   ." || " . $wikitext
                   ."\n || " . $annotation->last_edited_by
                   ."\n || " . $annotation->last_modified
vermeul's avatar
vermeul committed
284
                   ."\n";
vermeul's avatar
vermeul committed
285
286
287
288
        }
        $table .= "|}";
        return $table;
        
vermeul's avatar
vermeul committed
289
290
    }

vermeul's avatar
vermeul committed
291
    //
vermeul's avatar
vermeul committed
292
    // Render the output of {{#annotcat: list}}.
vermeul's avatar
vermeul committed
293
    //
294
    public static function annotation_categories( $parser, $method, $start_with=null ) {
295
        if ($method  != "list") {
vermeul's avatar
vermeul committed
296
297
            return "";
        }
298
        $dbr = wfGetDB( DB_MASTER );
vermeul's avatar
vermeul committed
299
300
301
302
303
304
305
306
307
308
309
310

        $where = array('parent_id'=>null);  # start with all top catgegories
        if ( $start_with ) {
            $start_with_cat = self::get_category($dbr, $start_with);
            if ( $start_with_cat ) {
                $where = array(id=>$start_with_cat->id);
            }
        }
                        
        $categories = self::search_categories($dbr, $where, array('ORDER BY' => 'name') );


311
312
313
        $table = <<<"EOT"
{| class="wikitable sortable"
|-
vermeul's avatar
vermeul committed
314
! Category !! Hashtag !! Description !! id !! parent_id
315
316
317
318
319
320
321

EOT;

        foreach($categories as $category) {
            # if a category has no parent, just show the category name
            # in all other cases show parent_category:child_category
            $table .= "|-\n";
vermeul's avatar
vermeul committed
322
            $table .= "| ".$category->name
vermeul's avatar
vermeul committed
323
                   ." || ".$category->hashtag
vermeul's avatar
vermeul committed
324
                   ." || ".$category->description
vermeul's avatar
vermeul committed
325
326
                   ." || ".$category->id
                   ." || ".$category->parent_id
vermeul's avatar
vermeul committed
327
                   ."\n";
vermeul's avatar
vermeul committed
328
329
330
331
332
            $child_categories = self::get_child_categories($dbr, $category, 1);
            if ($child_categories) {
                foreach ($child_categories as $child_category) {
                    $table .= "|-\n";
                    $table .= "|" . str_repeat('&nbsp;&nbsp;&nbsp;', $child_category->level) . $child_category->name
vermeul's avatar
vermeul committed
333
                        ." || ".$child_category->hashtag
vermeul's avatar
vermeul committed
334
335
336
337
338
339
340
                        ." || ".$child_category->description
                        ." || ".$child_category->id
                        ." || ".$child_category->parent_id
                        ."\n";
                }
            }

341
342
343
344
345
346
        }
        $table .= "|}";
        return $table;

    }

vermeul's avatar
...    
vermeul committed
347
    //
348
    // Rendering of {{#annotpage: list}}
vermeul's avatar
...    
vermeul committed
349
    //
vermeul's avatar
vermeul committed
350
    public static function annotation_page( $parser, $method ) {
351
        if ($method != "list") {
vermeul's avatar
vermeul committed
352
353
            return "";
        }
vermeul's avatar
...    
vermeul committed
354
355
356
357
358
359
360
361
362
363
364
365
366
        $title = $parser->getTitle();
        $wikiPage = new WikiPage( $title );
        $dbr = wfGetDB( DB_MASTER );

        $annotations = self::get_page_annotations($dbr, $wikiPage );

        $table = <<<"EOT"
{| class="wikitable sortable"
|-
! Name !! Value !! Creation Date !! Created By

EOT;

vermeul's avatar
vermeul committed
367
        foreach($annotations as $annotation) {
vermeul's avatar
...    
vermeul committed
368
369
370
            # if a annotations has no parent, just show the annotations name
            # in all other cases show parent_annotations:child_annotations
            $table .= "|-\n";
vermeul's avatar
vermeul committed
371
372
373
374
            $table .= "| ".$annotation->name
                ." || ".$annotation->value
                ." || ".$annotation->insert_date
                ." || ".$annotation->user_name
vermeul's avatar
...    
vermeul committed
375
376
377
378
379
380
381
                ."\n";
        }
        $table .= "|}";
        return $table;

    }

vermeul's avatar
vermeul committed
382
383

    public static function onPageContentSave(WikiPage &$wikiPage, User &$user, Content &$content, $summary, $isMinor, $isWatch, $section, $flags, Status &$status ){
vermeul's avatar
vermeul committed
384
    /*
385
386
387
388
389
390
391
        onPageContentSave: Hook on when page is saved
        we first replace syntax sugar where necessary, e.g.
        - change {{#annotend}} to {{#annotend:}}
        - change {{annotlist}} or {{#annotlist}} to {{#annotlist:}}

        next steps:
        1. add/delete/update categories (see manage_categories)
392
393
        2. parse the annotations (see parse_annotations)
        3. replace the wiki_text 
394
395
        
        The annotations are saved in the next hook: onPageContentSaveComplete (see below)
396

vermeul's avatar
vermeul committed
397
398
399
400
401
402
403
404
    */

        # get database handler
        $dbw = wfGetDB( DB_MASTER );
        $dbr = wfGetDB( DB_REPLICA );
        if ( $content instanceof TextContent ) {

            $data = $content->getNativeData();
vermeul's avatar
vermeul committed
405
406
407
            # replace all {{#annotend}} or {{annotend}} with {{#annotend:}}, where necessary
            # replace all {{#annotlist}} or {{annotlist}} with {{#annotlist:}}, where necessary
            $data = preg_replace( '/{{#*(annotend|annotlist)}}/U','{{#$1:}}', $data);
408

vermeul's avatar
...    
vermeul committed
409
            # 1. add / delete categories 
vermeul's avatar
vermeul committed
410
411
412
413
414
415
416
            try {
                $data = self::manage_categories($dbw, $data);
            }
            catch (Exception $e) {
               $status->fatal($e->getMessage());
               return false;
            }
vermeul's avatar
vermeul committed
417

vermeul's avatar
...    
vermeul committed
418
            # 2. get all annotations that exist in the current wikitext
vermeul's avatar
vermeul committed
419
            try {
vermeul's avatar
...    
vermeul committed
420
                list($annotations, $page_annotations) = self::parse_annotations($dbw, $data, $wikiPage);
vermeul's avatar
vermeul committed
421
422
423
424
425
            }
            catch (Exception $e) {
               $status->fatal($e->getMessage());
               return false;
            }
426

427
            # keep the annotations for saving later (onPageContentSaveComplete)
428
            self::$annotations = $annotations;
429
            self::$page_annotations = $page_annotations;
430

vermeul's avatar
...    
vermeul committed
431
            # 3. replace the content and save it
432
433
            $content = new WikitextContent( $data );
        }
vermeul's avatar
vermeul committed
434
435
436
437

        if ( !$status->isOK() ) {
            return false;
        }
438
    }
vermeul's avatar
vermeul committed
439

vermeul's avatar
merge    
vermeul committed
440
441
442
443
    /*
    This function is called after the save page request has been processed.
    @see https://www.mediawiki.org/wiki/Manual:Hooks/PageContentSaveComplete
    
444
445
446
    - save all new or update chaned annotations
    - Annotations which are no longer present should be deleted.
    - all entries in annotation_category which are no longer used should be deleted.
447
448
449
450
    */
    public static function onPageContentSaveComplete($wikiPage, $user, $content, $summary,
    $isMinor, $isWatch, $section, $flags, $revision, $status, $baseRevId ) {
        $dbw = wfGetDB( DB_MASTER );
451
452
453
        $seen_bookmarks = array();
        $seen_annotation_ids = array();
       $i = 0; 
454
455
456
        foreach ( self::$annotations as $bookmark => $annotation ){
            $annotation_id = 0;
            $ex_annot = self::get_annotation($dbw, $wikiPage, $bookmark);
457
458

            # existing annotation found: update wiki_text, comment
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
            if ($ex_annot) {
                $annotation_id = $ex_annot->id;
                if ($ex_annot->wiki_text != $annotation["wiki_text"] 
                     or $ex_annot->comment != $annotation["comment"] ) {
                    $dbw->update(
                        'yata_annotation',
                        array(
                            wiki_text => $annotation["wiki_text"],
                            comment   => $annotation["comment"],
                            user_id   => $user->getId(),
                            modified_date => $dbw->timestamp(),
                        ),
                        array(
                            page_id  => $wikiPage->getId(),
                            bookmark => $bookmark
                        )
                    );
                }
            }
478
            # new annotation found: insert annotation
479
            else {
vermeul's avatar
vermeul committed
480
                # write annotation
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
                try {
                    $data = array(
                            page_id    => $wikiPage->getId(),
                            comment    => $annotation['comment'],
                            wiki_text  => $annotation['wiki_text'],
                            bookmark   => $bookmark,
                            user_id    => $user->getId(),
                            insert_date => $dbw->timestamp(),
                            modified_date => $dbw->timestamp(),
                    );
            
                    $dbw->insert(
                        'yata_annotation',
                        array(
                            page_id    => $wikiPage->getId(),
                            comment    => $annotation['comment'],
                            wiki_text  => $annotation['wiki_text'],
                            bookmark   => $bookmark,
                            user_id    => $user->getId(),
                            insert_date => $dbw->timestamp(),
                            modified_date => $dbw->timestamp(),
                        )
                    );
                    $ex_annot = self::get_annotation($dbw, $wikiPage, $bookmark);
                    $annotation_id = $ex_annot->id;
                }
                catch (Exception $e) {
                    print($e->getMessage());
                    die;
                }
511
            }
512
513
514
515
516
            # keep track of all the annotations we have seen on this page,
            # to delete later all the annotations which have been removed from this page.
            array_push($seen_bookmarks, $bookmark);
            array_push($seen_annotation_ids, $annotation_id);

vermeul's avatar
vermeul committed
517

518
519
520
            # insert / update the annotation categories
            if ( $annotation['categories'] ) {
                $seen_acs = array();
521
                foreach ( $annotation['categories'] as $category ){
vermeul's avatar
vermeul committed
522
523
                    # assign all found categories to this annotation
                    if(! is_null($category)) {
524
525
                        self::insert_annotation_category($dbw, $wikiPage, $annotation_id, $category->id);
                        array_push($seen_acs, $category->id);
vermeul's avatar
vermeul committed
526
527
                    }
                }
528
529
                # delete all annotation categories which are no longer assigned
                # to this annotation
vermeul's avatar
merge    
vermeul committed
530
531
532
533
534
535
536
537
538
                $dbw->delete(
                    'yata_annotation_category',
                    array( 
                        page_id       => $wikiPage->getId(),
                        annotation_id => $annotation_id,
                        'category_id NOT IN(' .$dbw->makeList($seen_acs). ')'
                    )
                );
            }
539
540
541
542
543
544
545
546
547
548
            else {
                # delete all existing annotation categories,
                # since no category is assigned anymore
                $dbw->delete(
                    'yata_annotation_category',
                    array(
                        page_id       => $wikiPage->getId(),
                        annotation_id => $annotation_id,
                    )
                );
vermeul's avatar
vermeul committed
549
            }
vermeul's avatar
vermeul committed
550
        }
551
        
vermeul's avatar
vermeul committed
552

553
        # find all all annotations on that page...
vermeul's avatar
merge    
vermeul committed
554
555
556
        $where = [
            "page_id" => $wikiPage->getId()
        ];
557
558
559
        #...which are no longer present
        if ($seen_annotation_ids) {
            $where[] = 'id NOT IN(' .$dbw->makeList($seen_annotation_ids). ')';
560
561
562
563
564
        }

        $annotations_to_delete = $dbw->select(
            'yata_annotation',
            array( 'id' ),
vermeul's avatar
vermeul committed
565
            $where
566
567
        );

568

569
570
        # delete all annotations which are no longer present
        foreach( $annotations_to_delete as $annotation) {
571
572
573
574
575
576
            $dbw->delete(
                'yata_annotation',
                array(
                    'id' => $annotation->id 
                )
            );
vermeul's avatar
merge    
vermeul committed
577
578
579
580
581
582
583
584
585

            # delete all categories assigned to annotations
            # which do not exist anymore

            $delete_where = [
                page_id => $wikiPage->getId(),
                annotation_id => $annotation->id
            ];

586
587
588
589
            $dbw->delete(
                'yata_annotation_category',
                $delete_where
            );
vermeul's avatar
vermeul committed
590
        }
591
592
593

        foreach (self::$page_annotations as $page_annotation){
        }
vermeul's avatar
vermeul committed
594
595
    }

vermeul's avatar
vermeul committed
596
    public static function add_category($dbw, $category_name, $hashtag, $description, $parent) {
vermeul's avatar
vermeul committed
597
        $category_name = trim($category_name);
598
599
600
        $parent_id = null;
        if (! is_null($parent)) {
            $parent_category = self::get_category($dbw, $parent);
vermeul's avatar
vermeul committed
601
            if(! $parent_category) {
vermeul's avatar
vermeul committed
602
                throw new Exception("Error adding new category: no such parent-category found: $parent");
vermeul's avatar
vermeul committed
603
            }
604
605
606
            $parent_id = $parent_category->id;
        }

vermeul's avatar
vermeul committed
607
608
609
610
611
612
613
614
615
616
617
618
619
        $exists = $dbw->selectRow(
            'yata_category',
            array('id'),
            array(
                name => $category_name,
                parent_id => $parent_id
            )
        );

        if ($exists) {
            return;
        }

620
        # make sure hashtags always start with a hash symbol: #
vermeul's avatar
vermeul committed
621
        if ($hashtag && substr($hashtag, 0,1) != '#') {
622
623
624
            $hashtag = '#'.$hashtag;
        }

625
626
627
        $dbw->insert(
            'yata_category',
            array(
vermeul's avatar
vermeul committed
628
                name        => $category_name,
vermeul's avatar
vermeul committed
629
                hashtag     => $hashtag,
630
631
632
633
634
635
636
                description => trim($description),
                parent_id   => $parent_id
            )
        );

    }

vermeul's avatar
vermeul committed
637
638
639
640
641
642
643
644
645
646
647
648
649
650
    public static function up_category($dbw, $ex_category_name, $args) {
        $category = self::get_category($dbw, $ex_category_name);
        if (!$category) {
            throw new Exception("Error updating category - no such category: $ex_category_name");
        }

        $set = array();
        if ($args["name"]) {
            # make sure this category does not exists yet
            $new_category_name = $args["name"];
            if ($category->parent_name) {
                $new_category_name = $category->parent_name . "/".$new_category;
            }
            $category_exists = self::get_category($dbw, $new_category_name);
651
            if ($category_exists && $category_exists->id != $category->id) {
vermeul's avatar
vermeul committed
652
653
654
655
                throw new Exception("Error updating category - already exists: $new_category_name");
            }
            $set["name"] = $args["name"];
        }
vermeul's avatar
vermeul committed
656
        if (array_key_exists("hashtag", $args) ) {
vermeul's avatar
vermeul committed
657
            if ($args["hashtag"] && substr($args["hashtag"], 0,1) != '#') {
658
659
                $args["hashtag"] = '#'.$args["hashtag"];
            }
vermeul's avatar
vermeul committed
660
661
            $set["hashtag"] = $args["hashtag"];
        }
vermeul's avatar
vermeul committed
662
663
664
665
666
667
668
669
670
671
672
673
674
        if (array_key_exists("description", $args) ) {
            $set["description"] = $args["description"];
        }
        if (array_key_exists("parent", $args) ) {
            # redefine the parent category
            if ($args["parent"]) {
                $parent_category = self::get_category($dbw, $args["parent"]);
                if (! $parent_category) {
                    throw new Exception("Error updating category - no such parent category: ". $args["parent"]);
                }
                else {
                    $set["parent_id"] = $parent_category->id;
                }
vermeul's avatar
vermeul committed
675
            }
vermeul's avatar
vermeul committed
676
677
678
            else {
                # define category as a top category (remove parent)
                $set["parent_id"] = null;
vermeul's avatar
vermeul committed
679
            }
vermeul's avatar
vermeul committed
680
681
        }
        if ($set) {
vermeul's avatar
vermeul committed
682
683
684
685
686
687
688
689
            $dbw->update(
                'yata_category',
                $set,
                array(
                    id => $category->id
                )
            );
        }
vermeul's avatar
vermeul committed
690
691
692
693
        else {
            throw new Exception("Error updating category - nothing needs to be updated.");
        }
        
vermeul's avatar
vermeul committed
694
695
696
    }

    public static function del_category($dbw, $category_name) {
697

vermeul's avatar
vermeul committed
698
        if (substr($category_name, 0, 1) == '#') {
vermeul's avatar
vermeul committed
699
700
701
702
703
704
705
706
707
            # we go a hashtag, which should be unique.
            $dbw->delete(
                'yata_category',
                array(
                    hashtag => $category_name,
                )
            );
            return;
        }
vermeul's avatar
vermeul committed
708
        $cat_entry = self::get_category($dbw, $category_name);
vermeul's avatar
vermeul committed
709
710
711
        if (!$cat_entry) {
            throw new Exception("Error deleting category - no such category: $category_name");
        }
712
713
714
715
716
717
718
719
        $dbw->delete(
            'yata_category',
            array(
                id => $cat_entry->id
            )
        );
    }

vermeul's avatar
vermeul committed
720
721
    private static function split_cat_argstring($arg_string) {
        $args = array();
vermeul's avatar
vermeul committed
722
723
        # name='xxx', description='blabla', hashtag='#blabla', parent='parent/category'
        # splits argumentlist above and returns a dictionary instead.
vermeul's avatar
vermeul committed
724
        $keyvals = preg_split('/\s*\,\s*(?=(name|hashtag|description|parent))/', $arg_string);
vermeul's avatar
vermeul committed
725
726
727
728
        foreach($keyvals as $keyval) {
            list($key, $value) = preg_split('/\s*\=\s*/', $keyval);
            # remove ay leading and trailing  " or '
            # in the value
vermeul's avatar
vermeul committed
729
730
731
            $value = preg_replace('/^\s*(\'|\")/', '', $value);
            $value = preg_replace('/(\'|\")\s*$/', '', $value);
            $value = preg_replace('/\s*$/', '', $value);
vermeul's avatar
vermeul committed
732
733
734
735
736
            $args[$key] = $value;
        }
        return $args;
    }

737
738
    private static function category_add_del_callback($matches) {
        $dbw = wfGetDB( DB_MASTER );
vermeul's avatar
vermeul committed
739
740
        # split the arguments (after #annotcat:)
        #
vermeul's avatar
vermeul committed
741
742
        # {{#annotcat: add | name=new top category, hashtag=#shortcut, description=some meaningful comments, parent=grandparent_category/parent_category }}
        # {{#annotcat: up  | name=new top category, hashtag=#shortcut, description=some meaningful comments, parent=grandparent_category/parent_category }}
vermeul's avatar
vermeul committed
743
        # {{#annotcat: del | parent_category_name/category_name}}
vermeul's avatar
vermeul committed
744
        # {{#annotcat: del | #shortcut }}
vermeul's avatar
vermeul committed
745
746
        
        list($method, $arg1, $arg2) = preg_split('/\s*\|\s*/', $matches["params"]);
747
        if ( $method === "add" ) {
vermeul's avatar
vermeul committed
748
            $args = self::split_cat_argstring($arg1);
vermeul's avatar
vermeul committed
749
            self::add_category($dbw, $args['name'], $args['hashtag'], $args['description'], $args['parent']);
vermeul's avatar
vermeul committed
750
751
        }
        elseif ( $method === "up" ) {
vermeul's avatar
vermeul committed
752
753
            $args = self::split_cat_argstring($arg2);
            self::up_category($dbw, $arg1, $args);
754
755
        }
        elseif ( $method === "del" ) {
vermeul's avatar
vermeul committed
756
757
            # $arg_string already contains the category name, we do not have to split the arguments
            self::del_category($dbw, $arg1);
758
        }
vermeul's avatar
vermeul committed
759
760
761
        else {
            return $matches[0];
        }
vermeul's avatar
vermeul committed
762
763
764

        # remove the {{#annotcat: add|up|del }} commands from the source code
        # by returning an empty string
765
766
767
768
        return "";
    }

    // manage any categories added
769
    public static function manage_categories( $dbw, $data) {
770
771
772
773
774
775
776
777
778
779
780
        # look for all tags {{#annotcat:(.*)}}
        # and pass the matches to the callback function category_add_del_callback
        # which will return an empty string in order to remove this tag before saving the page.
        $data = preg_replace_callback(
            '/({{#annotcat:\s*(?P<params>.*?)}})/s',
            'self::category_add_del_callback',
            $data
        );
        return $data;
    }

781
782
783
784
785
786
787
788
789
790
791
792
    public static function get_annotation($dbr, $wikiPage, $bookmark) {
        $row = $dbr->selectRow(
            'yata_annotation',
            array('id', 'wiki_text', 'comment'),
            array(
                page_id  => $wikiPage->getId(),
                bookmark => $bookmark
            )
        );
        return $row;
    }

vermeul's avatar
vermeul committed
793
794
    public static function get_page_annotations($dbr, $wikiPage) {
        $order_by = array('ORDER BY' => 'name');
vermeul's avatar
...    
vermeul committed
795
796
797
798
799
800
801
802
803
804
805
806
807

        $annotations = $dbr->select(
            array('pa' => 'yata_page_annotation', 
                  'u'  => 'user'),
            array(
                'pa.id',
                'pa.page_id',
                'pa.name',
                'pa.value',
                'pa.insert_date',
                'u.user_name'
            ),
            array(
vermeul's avatar
vermeul committed
808
                'pa.page_id' => $wikiPage->getId()
vermeul's avatar
...    
vermeul committed
809
810
811
812
813
814
815
816
817
818
            ), 
            __METHOD__,
            $order_by,
            array(
                'u' => array( 'LEFT JOIN', array('u.user_id = pa.user_id') ) 
            )
        );
        return $annotations;
    }

819

vermeul's avatar
vermeul committed
820
821
822
823
824
825
    public static function search_categories($dbr, $where, $order_by) {
        if (! $order_by) {
            $order_by = array('ORDER BY' => 'name');
        }

        $categories = $dbr->select(
826
827
828
829
830
831
832
833
            array('c' => 'yata_category'),
            array( 
                'c.id', 
                'c.name', 
                'c.hashtag', 
                'c.description', 
                'c.parent_id'
            ),
vermeul's avatar
vermeul committed
834
835
836
837
838
839
840
            $where,
            __METHOD__,
            $order_by
        );
        return $categories;
    }

841
842
843
    public static function get_category($dbr, $category) {
        $category = trim($category);

vermeul's avatar
vermeul committed
844
845
846
847
848
849
850
851
852
        $selection = array( 
            'id'          => 'c.id', 
            'name'        => 'c.name', 
            'hashtag'     => 'c.hashtag', 
            'description' => 'c.description',
            'parent_name' => "''",
            'parent_id'   => 'c.parent_id' 
        );

vermeul's avatar
vermeul committed
853
        # we received a hashtag: use that.
vermeul's avatar
vermeul committed
854
        if (substr($category, 0,1) == '#') {
vermeul's avatar
vermeul committed
855
            $row = $dbr->selectRow(
vermeul's avatar
vermeul committed
856
857
                array('c' =>'yata_category'),
                $selection,
vermeul's avatar
vermeul committed
858
                array(
vermeul's avatar
vermeul committed
859
                    'c.hashtag' => $category,
vermeul's avatar
vermeul committed
860
861
862
863
864
865
866
867
868
869
870
871
                )
            );
            return $row;
        }

        # get parent and child category: parent_cat/child_cat
        list($parent_cat, $child_cat) = preg_split('/\s*\/\s*/', $category);

        # we might only have a parent (top) category
        if (is_null($child_cat)) {
            # search for a top category (no parent)
            $row = $dbr->selectRow(
vermeul's avatar
vermeul committed
872
873
                array('c' =>'yata_category'),
                $selection,
vermeul's avatar
vermeul committed
874
875
876
                array(
                    'name'        => $parent_cat,
                    'parent_id'   => null
vermeul's avatar
vermeul committed
877
878
879
880
881
882
883
                )
            );
            return $row;
        }
        else {
            # search for category where parent matches
            # by self-joining table via parent_id
vermeul's avatar
vermeul committed
884
            $selection['parent_name'] = 'pc.name';
vermeul's avatar
vermeul committed
885
            $row = $dbr->selectRow(
vermeul's avatar
vermeul committed
886
887
                array('pc'=>'yata_category', 'c'=>'yata_category'),
                $selection,
vermeul's avatar
vermeul committed
888
                array(
vermeul's avatar
vermeul committed
889
                    'c.name' => $child_cat,
890
                    'pc.name' => $parent_cat,
vermeul's avatar
vermeul committed
891
892
893
                ),
                __METHOD__,
                array(),
vermeul's avatar
vermeul committed
894
                array( 'pc' => array( 'INNER JOIN', array ( 'c.parent_id = pc.id' ) ) )
vermeul's avatar
vermeul committed
895
896
897
898
899
            );
            return $row;
        } 
    }

900
901
902
903
    public static function get_category_and_parent_for_id($dbr, $id) {
        $row = $dbr->selectRow(
            array(c=>'yata_category', pc=>'yata_category'),
            array(
904
                'id'          => 'c.id',
vermeul's avatar
vermeul committed
905
906
907
908
909
                'name'        => 'c.name',
                'hashtag'     => 'c.hashtag',
                'description' => 'c.description',
                'parent_name' => 'pc.name',
                'parent_id'   => 'pc.id'
910
911
912
913
914
915
916
917
918
919
920
            ),
            array(
                'c.id' => $id
            ),
            __METHOD__,
            array(),
            array('pc' => array('LEFT JOIN', array('c.parent_id = pc.id') ) )
        );
        return $row;
    }

vermeul's avatar
vermeul committed
921
    public static function get_child_categories($dbr, $category, $level=0) {
vermeul's avatar
vermeul committed
922
923
        $rows = $dbr->select(
            array('yata_category'),
vermeul's avatar
vermeul committed
924
            array('id', 'name', 'hashtag', 'description', 'parent_id', "$level as level"),
vermeul's avatar
vermeul committed
925
926
927
            array(
                "parent_id" => $category->id
            )
vermeul's avatar
vermeul committed
928
        );
vermeul's avatar
vermeul committed
929
930
931
932
933

        $all_child_categories = array();

        foreach($rows as $row) {
            array_push($all_child_categories, $row);
vermeul's avatar
vermeul committed
934
            $child_categories = self::get_child_categories($dbr, $row, $level+1);
vermeul's avatar
vermeul committed
935
936
937
938
939
940

            foreach($child_categories as $child_category) {
                array_push($all_child_categories, $child_category);
            }
        }
        return $all_child_categories;
vermeul's avatar
vermeul committed
941
942
    }

vermeul's avatar
vermeul committed
943
944
945
946
947
948
949
950
951
    public static function delete_annotations($dbw, $wikiPage) {
        $dbw->delete(
            'yata_annotation',
            array(
                page_id => $wikiPage->getId()
            )
        );
    }

952
953
954
955
956
957
958
959
960
961
962
963
    public static function check_annotation_bookmark_exist($dbr, $wikiPage, $bookmark) {
        $row = $dbr->selectRow(
            array('yata_annotation'),
            array('id'),
            array(
                bookmark => $bookmark,
                page_id  => $wikiPage->getId()
            )
        );
        return $row;
    }

vermeul's avatar
vermeul committed
964
965
966
967
968
969
970
971
972
    public static function delete_annotation_categories($dbw, $wikiPage) {
        $dbw->delete(
            'yata_annotation_category',
            array(
                page_id => $wikiPage->getId()
            )
        );
    }

973
974
    public static function insert_annotation_category($dbw, $wikiPage, $annotation_id, $category_id) {
        $ex_ac = $dbw->selectRow(
vermeul's avatar
vermeul committed
975
            'yata_annotation_category',
976
            array('anz' => 'COUNT(*)'),
vermeul's avatar
vermeul committed
977
            array(
978
                page_id       => $wikiPage->getId(),
vermeul's avatar
vermeul committed
979
980
981
982
                annotation_id => $annotation_id,
                category_id   => $category_id
            )
        );
983
984
985
986
987
988
989
990
991
992
993
994
995
        if ($ex_ac->anz) {
            return;
        }
        else {
            $dbw->insert(
                'yata_annotation_category',
                array(
                    page_id       => $wikiPage->getId(),
                    annotation_id => $annotation_id,
                    category_id   => $category_id
                )
            );
        }
vermeul's avatar
vermeul committed
996
997
    }

998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
    # create a unique identifier / bookmark for all
    # annotations which don't have one.
    # It should be long enough to be 99.999% unique on a page
    public static function create_bookmark() {
        $id = substr(
                str_shuffle(
                    str_repeat(
                        '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
                        mt_rand(1,10)
                    )
                
                ),
        1,4);
vermeul's avatar
vermeul committed
1011
1012
1013
        return $id;
    }

1014
1015
1016
    # Main function to parse all annotations,
    # insert a random id (where it doesn't exist)
    public static function parse_annotations($dbw, &$data, $wikiPage){
1017
1018
1019
1020
        /*
        1. get all existing annotations which exist on that page.
        2. find out which annotations have been updated (using the unique identifier)
        3. find out which annotations have been added -> assign a unique identifier
vermeul's avatar
merge    
vermeul committed
1021
        4. find out which annotations have been removed from the wikitext and delete them
1022
1023
1024
        5. assign every annotation to one or more categories
        6. assign every annotation to this page
        */
vermeul's avatar
vermeul committed
1025

1026
1027
1028
        # match either #annot: or #annotend:
        # fetch all options too
        $annotations_found = preg_match_all(
vermeul's avatar
...    
vermeul committed
1029
            '/(?P<annotation>{{\s*#annot((?P<end>end)|(?P<page>page))?\s*:\s*(?P<opts>.*?)\s*}})/s', 
vermeul's avatar
vermeul committed
1030
            $data, 
1031
            $reg_params, 
vermeul's avatar
vermeul committed
1032
1033
            PREG_OFFSET_CAPTURE
        );
1034
1035
1036
1037
1038
1039

        # we can have annotations with an id or without.
        # The id was either entered by hand (for creating overlapping annotations)
        # or it was automatically generated.
        $annotations_with_id = array();
        $annotations = array();
1040
        $page_annotations = array();
1041
1042
1043
1044
1045
1046
1047

        # as we crawl through the found annotations and give them new ids,
        # we also recreate the wikitext containing all the new ids.
        $new_data = "";  
        $starts = array();
        $start_loc = 0;
        $end_loc   = 0;
vermeul's avatar
vermeul committed
1048
        
1049
1050
1051
1052
1053
1054
1055
        foreach($reg_params["annotation"] as $index => $value) {
            # $value[1] contains the location where the current tag has been found
            $end_loc = $value[1];
            # rewrite the source code to include new ids in the code
            $new_data = $new_data . substr($data, $start_loc, $end_loc-$start_loc);
            $start_loc = $value[1] + strlen($value[0]);

vermeul's avatar
...    
vermeul committed
1056
            # we encountered an annotation ending
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
            if ($reg_params["page"][$index][0] === "page") {
               //print($reg_params["page"][$index][0]);
                $opts = $reg_params["opts"][$index][0];

                // Match key-value pairs.
                // see https://regex101.com/r/wK0eD2/1 for detailed explanations
                // Will match key and values that are:
                // within single quotes '     \'.*?(?<!\\\\)\'
                // within double quotes "     \".*?(?<!\\\\)\"
                // with no quotes at all (and no spaces): [^\s]+
                $re =
                 '/(?P<key>\'.*?(?<!\\\\)\'|\".*?(?<!\\\\)\"|[^\s]+)'
                 .'\s*?=\s*?'
                 .'(?P<val>\'.*?(?<!\\\\)\'|\".*?(?<!\\\\)\"|[^\s]+)/';
                preg_match_all($re, $opts, $matches, PREG_SET_ORDER, 0);
                foreach($matches as $match) {
                    # trim keys and values: remove any spaces and quotes
                    $key = trim($match["key"], " '\"");
                    $val = trim($match["val"], " '\"");
                    $page_annotations[$key] = $val;
1077
                }
1078
                //var_dump($page_annotations);
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
            }

            # we encountered an annotation ending
            elseif ($reg_params["end"][$index][0] === "end") {
                $bookmark = $reg_params["opts"][$index][0];
                # we encountered an id: try to find its beginning
                if ($bookmark) {
                    if ( $annotations_with_id[$bookmark] ) {
                        # we fetch the new id we created.
                        $new_bookmark = $annotations_with_id[$bookmark];
                        $annotations[ $new_bookmark ]["end"] = 
                            $reg_params["end"][$index];

                        # extract the wiki text between the annotations
                        #$start_char = $annotations[$new_bookmark]["start"][1] 
                        #            + strlen($annotations[ $new_bookmark ]["start"][0]);
                        $start_char = $annotations[$new_bookmark]["start_char"];
                        $end_char   = $reg_params["annotation"][$index][1];
                        $annotations[$new_bookmark]["wiki_text"]  = 
                            substr($data, $start_char, $end_char-$start_char);

                        $new_data = $new_data . "{{#annotend:$new_bookmark}}";
                    }
                    else {
vermeul's avatar
vermeul committed
1103
                        throw new Exception("found #annotend with bookmark=$bookmark but no annotation starts with this bookmark");
1104
1105
1106
1107
                    }
                }
                # we found an ending, but have not enough starts: there seems to be a start-ending mismatch
                elseif ( count($starts) == 0 ) {
vermeul's avatar
vermeul committed
1108
                    throw new Exception("starts and ends do not match: I encountered an ending but no corresponding start.");
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
                }
                # we pop the last start from our stack to match it with its ending
                else {
                    $start = array_pop($starts);
                    $start_char = $start[1] + strlen($start[0]);
                    $end_char   = $reg_params["annotation"][$index][1];
        
                    $new_bookmark = $start["new_bookmark"];
                    $annotations[$new_bookmark] = array(
                        "is_new"     => true,
                        "categories" => $start["categories"],
                        "comment"    => $start["comment"],
                        "wiki_text"  => substr($data, $start_char, $end_char-$start_char)
                    );
                    $reg_params["annotation"][$index][2] = $new_bookmark;
                    $reg_params["annotation"][$index]["new_annot"] = "{{#annotend:$new_bookmark}}";
                    $new_data = $new_data . "{{#annotend:$new_bookmark}}";
                }
            }
vermeul's avatar
...    
vermeul committed
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199

            # we encountered an annotation START
            elseif ($reg_params["end"][$index][0] === "" && $reg_params["page"][$index][0] === "") {
                # split comment|$category|$bookmark by vertical bar
                list($comment, $category, $bookmark) = preg_split(
                    '/\s*\|\s*/', 
                    $reg_params["opts"][$index][0]
                ); 
                # more than one category can be split using a comma: 
                #   category1, category2
                # categories are by default hierarchically organized and must appear
                # in following formats:
                #   parent_category/child_category
                #   /top_category
                #   top_category
				$cat_strs = preg_split("/\s*\,\s*/", $category, -1, PREG_SPLIT_NO_EMPTY);
                $cats = array();
                foreach($cat_strs as $cat_str) {
                    # we got a category id, which consists only of numbers
                    if ( preg_match("/^\s*\d*\s*$/", $cat_str) ) {
                        $cat = self::get_category_and_parent_for_id($dbw, $cat_str);
                    }
                    else {
                        $cat = self::get_category($dbw, $cat_str);
                    }
                    if ($cat) {
                        array_push($cats, $cat);
                    }
                    else {
                        throw new Exception("no such category: $cat_str");
                    }
                }
                # we encountered an id: replace it with a unique one,
                # if it doesn't exist yet.
                $new_bookmark = 0;
                if ($bookmark) {
                    if ( self::check_annotation_bookmark_exist($dbw, $wikiPage, $bookmark))
                        {
                        # it is an existing annotation, we keep the id
                        $new_bookmark = $bookmark;
                    } 
                    else {
                        # we replace the manually entered id by a random one
                        $new_bookmark = self::create_bookmark();
                    }
                    $annotations_with_id[$bookmark] = $new_bookmark;
                    $annotations[$new_bookmark] = array();
                    $annotations[$new_bookmark]["start_char"] = $value[1] + strlen($value[0]);
                    $annotations[$new_bookmark]["is_new"] = true;
                    $annotations[$new_bookmark]["categories"] = $cats;
                    $annotations[$new_bookmark]["comment"] = $comment;
                }
                # we found no id, push it on the stack to find its 
                # ending counterpart later
                else {
                    $new_bookmark = self::create_bookmark();
                    $value["new_bookmark"] = $new_bookmark;
                    $value["categories"] = $cats;
                    $value["comment"] = $comment;
                    array_push($starts, $value);
                }

                # replace category names with their corresponding id's
                $new_category = join(',',
                    array_map(
                        function($row) { return $row->id; },
                        $cats
                    )
                );
                $new_data = $new_data . "{{#annot:$comment|$new_category|$new_bookmark}}";
            }

1200
1201
1202
1203
1204
1205
        }
        # add the end of the text
        $new_data = $new_data . substr($data, $start_loc, strlen($data)-$start_loc);

        # replace the original wikiText
        $data = $new_data;
vermeul's avatar
...    
vermeul committed
1206
        return array($annotations, $page_annotations);
vermeul's avatar
vermeul committed
1207
1208
1209
    }


1210
1211
1212
1213
    //
    // Schema updates, called on Vagrant machine with: mwscript update.php
    // otherwise: cd maintenance; php update.php
    // creates the necessary tables for YATA.
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
    public static function onLoadExtensionSchemaUpdates( DatabaseUpdater $updater = null ) {

        $map = [ 'mysql', 'postgres' ];
        $type = $updater->getDB()->getType();

        if ( !in_array( $type, $map ) ) {
            throw new Exception( "YATA extension does not currently support $type database." );
        }
        $sql = __DIR__ . '/sql/yata.' . $type . '.sql';
        $updater->addExtensionTable( 'yata', $sql );

        return true;
 }
vermeul's avatar
vermeul committed
1227

1228
1229
    // called right after «Save Changes»
    // when an error is present, returns to edit page and prints the error
vermeul's avatar
vermeul committed
1230
    // may be used to check whether the syntax of the annotations is correct
1231
1232
1233
    public static function onEditFilter($editor, $text, $section, &$error, $summary) {
    }

vermeul's avatar
...    
vermeul committed
1234
    // this is called after �Save changes�
1235
    // and after onEditFilter
vermeul's avatar
vermeul committed
1236
    public static function onArticlePrepareTextForEdit($wikiPage, $popts) {
1237
1238
1239
1240
    }

    // first hook called when editing a page
    // even before any checks whether a user is allowed to
1241
    public static function onAlternateEdit($editPage) {
1242
1243
    }

1244
1245
    // This hook is called after hitting the «edit» button of a page.
    // It transforms all categories from their internal ID into readable text.
1246
1247
    // This allows us to change the category names later without having
    // to change all source code where this category occurs.
1248
    public static function onEditFormInitialText($editPage) {
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
        $data = $editPage->textbox1;
        $data =  preg_replace_callback(
            '/(?:{{#annot:(?P<params>.*?)}})/s',
            'self::replace_id_with_category',
            $data
        );
        // assign the new content
        $editPage->textbox1 = $data;
    }

    // preload edit forms with some content
    public static function onEditFormPreloadText(&$text, &$title) {
    }

    // callback function for onEditFormInitialText hook
    // all category_id's are replaced with the category names themselves
1265
    // using the hashtag (if available) or the parent_category/child_category naming scheme (fallback).
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
    private static function replace_id_with_category($matches) {
        $dbw = wfGetDB( DB_MASTER );
        list($comment, $cat, $bookmark) = preg_split('/\s*\|\s*/', $matches["params"]);
        
        if (!$cat) {
            return $matches[0];
        }
        # TODO: check whether category is numeric
        $categories = preg_split('/\s*,\s*/', $cat);
        $cat_and_parents = array();
        foreach($categories as $category) {
            $cat_and_parent = self::get_category_and_parent_for_id($dbw, $category);
            if ($cat_and_parent) {
1279
1280
1281
1282
                if ($cat_and_parent->hashtag) {
		    array_push($cat_and_parents, $cat_and_parent->hashtag);
                }
                elseif ($cat_and_parent->parent_name) {
vermeul's avatar
vermeul committed
1283
1284
1285
1286
1287
                    array_push($cat_and_parents, $cat_and_parent->parent_name .'/'. $cat_and_parent->name);
                }
                else {
                    array_push($cat_and_parents, $cat_and_parent->name);
                }
1288
1289
1290
1291
1292
1293
1294
1295
1296
            }
        }
        $annot = "{{#annot: " . $comment." | ";
        $annot.= join(', ', $cat_and_parents);
        $annot.= " | $bookmark}}";

        return $annot;
    }

vermeul's avatar
vermeul committed
1297
}