ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilWikiUtil.php
Go to the documentation of this file.
1<?php
2
32// From include/Unicode/UtfNormal.php
33if (!defined('UTF8_REPLACEMENT')) {
34 define('UTF8_REPLACEMENT', "\xef\xbf\xbd" /*codepointToUtf8( UNICODE_REPLACEMENT )*/);
35}
36
37const IL_WIKI_MODE_REPLACE = "replace";
38const IL_WIKI_MODE_COLLECT = "collect";
39const IL_WIKI_MODE_EXT_COLLECT = "ext_collect";
40
47{
52 public static function replaceInternalLinks(
53 string $s,
54 int $a_wiki_id,
55 bool $a_offline = false,
56 string $lang = "-"
57 ): string {
59 $s,
60 $a_wiki_id,
62 false,
63 $a_offline,
64 $lang
65 );
66 }
67
71 public static function collectInternalLinks(
72 string $s,
73 int $a_wiki_id,
74 bool $a_collect_non_ex = false,
75 string $mode = IL_WIKI_MODE_COLLECT
76 ): array {
77 $log = ilLoggerFactory::getLogger("wiki");
78
79 $log->debug("collect interna links wiki id: " . $a_wiki_id . ", collect nonex: " . $a_collect_non_ex);
80
81 $result = self::processInternalLinks(
82 $s,
83 $a_wiki_id,
84 $mode,
85 $a_collect_non_ex
86 );
87 $log->debug("content: " . $s);
88 $log->debug("found: " . print_r($result, true));
89 return $result;
90 }
91
97 protected static function processInternalLinks(
98 string $s,
99 int $a_wiki_id,
100 string $a_mode = IL_WIKI_MODE_REPLACE,
101 bool $a_collect_non_ex = false,
102 bool $a_offline = false,
103 string $lang = "-"
104 ) {
105 global $DIC;
106 $page_repo = $DIC->wiki()->internal()->repo()->page();
107
108 $collect = array();
109 // both from mediawiki DefaulSettings.php
110 $wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+";
111
112 // dummies for wiki globals
113 $GLOBALS["wgContLang"] = new class () {
114 public function getNsIndex($a_p): bool
115 {
116 return false;
117 }
118 public function lc($a_key): bool
119 {
120 return false;
121 }
122 };
123 $GLOBALS["wgInterWikiCache"] = false;
124
125 # the % is needed to support urlencoded titles as well
126 //$tc = Title::legalChars().'#%';
127 $tc = $wgLegalTitleChars . '#%';
128
129 //$sk = $this->mOptions->getSkin();
130
131 #split the entire text string on occurences of [[
132 $a = explode('[[', ' ' . $s);
133 #get the first element (all text up to first [[), and remove the space we added
134 $s = array_shift($a);
135 $s = substr($s, 1);
136
137 # Match a link having the form [[namespace:link|alternate]]trail
138 $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
139
140 # Match cases where there is no "]]", which might still be images
141 // static $e1_img = FALSE;
142 // if ( !$e1_img ) { $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; }
143
144 # Match the end of a line for a word that's not followed by whitespace,
145 # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
146 // $e2 = wfMsgForContent( 'linkprefix' );
147
148 /* $useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
149 if( is_null( $this->mTitle ) ) {
150 throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
151 }
152 $nottalk = !$this->mTitle->isTalkPage();*/
153 $nottalk = true;
154
155 /* if ( $useLinkPrefixExtension ) {
156 $m = array();
157 if ( preg_match( $e2, $s, $m ) ) {
158 $first_prefix = $m[2];
159 } else {
160 $first_prefix = false;
161 }
162 } else {*/
163 $prefix = '';
164 // }
165
166 $useSubpages = false;
167
168 # Loop for each link
169 for ($k = 0; isset($a[$k]); $k++) {
170 $line = $a[$k];
171
172
173 $might_be_img = false;
174
175 //wfProfileIn( "$fname-e1" );
176 if (preg_match($e1, $line, $m)) { # page with normal text or alt
177 $text = $m[2];
178 # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
179 # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
180 # the real problem is with the $e1 regex
181 # See bug 1300.
182 #
183 # Still some problems for cases where the ] is meant to be outside punctuation,
184 # and no image is in sight. See bug 2095.
185 #
186 if ($text !== '' &&
187 strpos($m[3], ']') === 0 &&
188 strpos($text, '[') !== false
189 ) {
190 $text .= ']'; # so that replaceExternalLinks($text) works later
191 $m[3] = substr($m[3], 1);
192 }
193 # fix up urlencoded title texts
194 if (strpos($m[1], '%') !== false) {
195 # Should anchors '#' also be rejected?
196 $m[1] = str_replace(array('<', '>'), array('&lt;', '&gt;'), urldecode($m[1]));
197 }
198 $trail = $m[3];
199 /* } elseif( preg_match($e1_img, $line, $m) ) { # Invalid, but might be an image with a link in its caption
200 $might_be_img = true;
201 $text = $m[2];
202 if ( strpos( $m[1], '%' ) !== false ) {
203 $m[1] = urldecode($m[1]);
204 }
205 $trail = "";*/
206 } else { # Invalid form; output directly
207 $s .= $prefix . '[[' . $line ;
208 //wfProfileOut( "$fname-e1" );
209 continue;
210 }
211 //wfProfileOut( "$fname-e1" );
212 //wfProfileIn( "$fname-misc" );
213
214 # Don't allow internal links to pages containing
215 # PROTO: where PROTO is a valid URL protocol; these
216 # should be external links.
217 if (preg_match('/^\b%na' . self::wfUrlProtocols() . 'me/', $m[1])) {
218 $s .= $prefix . '[[' . $line ;
219 continue;
220 }
221
222 # Make subpage if necessary
223 /* if( $useSubpages ) {
224 $link = $this->maybeDoSubpageLink( $m[1], $text );
225 } else {*/
226 $link = $m[1];
227 // }
228
229 $noforce = (strpos($m[1], ':') !== 0);
230 if (!$noforce) {
231 # Strip off leading ':'
232 $link = substr($link, 1);
233 }
234
235 // wfProfileOut( "$fname-misc" );
236 // wfProfileIn( "$fname-title" );
237
238 $nt = Title::newFromText($link);
239
240 if (!$nt) {
241 $s .= $prefix . '[[' . $line;
242 continue;
243 }
244
245 $wasblank = ('' == $text);
246 if ($wasblank) {
247 $text = $link;
248 }
249
250 // Media wiki performs an intermediate step here (Parser->makeLinkHolder)
251 if ($a_mode === IL_WIKI_MODE_REPLACE) {
252 $s .= self::makeLink(
253 $nt,
254 $a_wiki_id,
255 $text,
256 '',
257 $trail,
258 $prefix,
259 $a_offline,
260 $lang
261 );
262 }
263 if ($a_mode === IL_WIKI_MODE_EXT_COLLECT) {
264 if (is_object($nt)) {
265 $url_title = self::makeUrlTitle($nt->mTextform);
266 $db_title = self::makeDbTitle($nt->mTextform);
267 [$inside, $trail] = self::splitTrail($trail);
268 $collect[] = array("nt" => $nt, "text" => $text,
269 "trail" => $trail, "db_title" => $db_title,
270 "url_title" => $url_title);
271 }
272 } else {
273 $db_title = self::makeDbTitle($nt->mTextform);
274
275 if (($a_collect_non_ex || $page_repo->existsByTitle($a_wiki_id, $db_title, $lang))
276 &&
277 !in_array($db_title, $collect)) {
278 $collect[] = $db_title;
279 }
280 }
281 }
282
283 //wfProfileOut( $fname );
284
285 if ($a_mode === IL_WIKI_MODE_COLLECT ||
286 $a_mode === IL_WIKI_MODE_EXT_COLLECT) {
287 return $collect;
288 } else {
289 return $s;
290 }
291 }
292
293 protected static function removeUnsafeCharacters(
294 string $a_str
295 ): string {
296 return str_replace(array("\x00", "\n", "\r", "\\", "'", '"', "\x1a"), "", $a_str);
297 }
298
308 protected static function makeLink(
309 object $nt,
310 int $a_wiki_id,
311 string $text = '',
312 string $query = '',
313 string $trail = '',
314 string $prefix = '',
315 bool $a_offline = false,
316 string $lang = "-"
317 ): string {
318 global $DIC;
319 $request = $DIC
320 ->wiki()
321 ->internal()
322 ->gui()
323 ->request();
324 $page_repo = $DIC->wiki()->internal()->repo()->page();
325
326 $ilCtrl = $DIC->ctrl();
327
328 if (!is_object($nt)) {
329 # Fail gracefully
330 $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
331 } else {
332 // remove anchor from text, define anchor
333 $anc = "";
334 if ($nt->mFragment != "") {
335 if (substr($text, strlen($text) - strlen("#" . $nt->mFragment)) === "#" . $nt->mFragment) {
336 $text = substr($text, 0, strlen($text) - strlen("#" . $nt->mFragment));
337 }
338 $anc = "#copganc_" . $nt->mFragment;
339 }
340
341 # Separate the link trail from the rest of the link
342 // outcommented due to bug #14590
343 // list( $inside, $trail ) = ilWikiUtil::splitTrail( $trail );
344
345 $retVal = '***' . $text . "***" . $trail;
346 $url_title = self::makeUrlTitle($nt->mTextform);
347 $db_title = self::makeDbTitle($nt->mTextform);
348 if ($db_title != "") {
349 $pg_exists = $page_repo->existsByTitle($a_wiki_id, $db_title, $lang);
350 } else {
351 // links on same page (only anchor used)
352 $pg_exists = true;
353 }
354
355 //var_dump($nt);
356 //var_dump($inside);
357 //var_dump($trail);
358 $wiki_link_class = (!$pg_exists)
359 ? ' class="ilc_link_IntLink ilWikiPageMissing" '
360 : ' class="ilc_link_IntLink" ';
361
362 if (!$a_offline) {
363 if ($url_title != "") {
364 $ilCtrl->setParameterByClass("ilobjwikigui", "wpg_id", null);
365 $ilCtrl->setParameterByClass("ilwikipagegui", "wpg_id", null);
366 $ilCtrl->setParameterByClass("ilobjwikigui", "page", $url_title);
367 $retVal = '<a ' . $wiki_link_class . ' href="' .
368 $ilCtrl->getLinkTargetByClass("ilobjwikigui", "gotoPage") . $anc .
369 '">' . $text . '</a>' . $trail;
370 $ilCtrl->setParameterByClass(
371 "ilobjwikigui",
372 "page",
373 $request->getPage()
374 );
375 } else {
376 $retVal = '<a ' . $wiki_link_class . ' href="' .
377 $anc .
378 '">' . $text . '</a>' . $trail;
379 }
380 } else {
381 if ($pg_exists) {
382 if ($db_title != "") {
383 $pg_id = ilWikiPage::getPageIdForTitle($a_wiki_id, $db_title);
384 $retVal = '<a ' . $wiki_link_class . ' href="' .
385 "wpg_" . $pg_id . ".html" . $anc .
386 '">' . $text . '</a>' . $trail;
387 } else {
388 $retVal = '<a ' . $wiki_link_class . ' href="' .
389 $anc .
390 '">' . $text . '</a>' . $trail;
391 }
392 } else {
393 $retVal = $text . $trail;
394 }
395 }
396 }
397 return $retVal;
398 }
399
404 public static function wfUrlProtocols(): string
405 {
406 $wgUrlProtocols = array(
407 'http://',
408 'https://',
409 'ftp://',
410 'irc://',
411 'gopher://',
412 'telnet://', // Well if we're going to support the above.. -ævar
413 'nntp://', // @bug 3808 RFC 1738
414 'worldwind://',
415 'mailto:',
416 'news:'
417 );
418
419 // Support old-style $wgUrlProtocols strings, for backwards compatibility
420 // with LocalSettings files from 1.5
421 $protocols = array();
422 foreach ($wgUrlProtocols as $protocol) {
423 $protocols[] = preg_quote($protocol, '/');
424 }
425
426 return implode('|', $protocols);
427 }
428
429 public static function wfUrlencode(
430 string $s
431 ): string {
432 $s = urlencode($s);
433 return $s;
434 }
435
436 public static function makeDbTitle(
437 string $a_par
438 ): string {
439 $a_par = self::removeUnsafeCharacters($a_par);
440 return str_replace("_", " ", $a_par);
441 }
442
443 public static function makeUrlTitle(
444 string $a_par
445 ): string {
446 $a_par = self::removeUnsafeCharacters($a_par);
447 $a_par = str_replace(" ", "_", $a_par);
448 return self::wfUrlencode($a_par);
449 }
450
451 public static function splitTrail(
452 string $trail
453 ): array {
454 $regex = '/^([a-z]+)(.*)$/sD';
455
456 $inside = '';
457 if ('' != $trail) {
458 $m = array();
459
460 if (preg_match($regex, $trail, $m)) {
461 $inside = $m[1];
462 $trail = $m[2];
463 }
464 }
465
466 return array( $inside, $trail );
467 }
468
469 public static function sendNotification(
470 string $a_action,
471 int $a_type,
472 int $a_wiki_ref_id,
473 int $a_page_id,
474 ?string $a_comment = null
475 ): void {
476 global $DIC;
477
479 $log->debug("start... vvvvvvvvvvvvvvvvvvvvvvvvvvv");
480
481 $ilUser = $DIC->user();
482 $ilObjDataCache = $DIC["ilObjDataCache"];
483 $ilAccess = $DIC->access();
484
485 if ($a_wiki_ref_id === 0) {
486 return;
487 }
488
489 $wiki_id = $ilObjDataCache->lookupObjId($a_wiki_ref_id);
490 $wiki = new ilObjWiki($a_wiki_ref_id, true);
491 $page = new ilWikiPage($a_page_id);
492
493 // #11138
494 $ignore_threshold = ($a_action === "comment");
495
496 // 1st update will be converted to new - see below
497 if ($a_action === "new") {
498 return;
499 }
500
501 $log->debug("-- get notifications");
502 if ($a_type == ilNotification::TYPE_WIKI_PAGE) {
503 $users = ilNotification::getNotificationsForObject($a_type, $a_page_id, null, $ignore_threshold);
504 $wiki_users = ilNotification::getNotificationsForObject(ilNotification::TYPE_WIKI, $wiki_id, $a_page_id, $ignore_threshold);
505 $users = array_merge($users, $wiki_users);
506 if (!count($users)) {
507 $log->debug("no notifications... ^^^^^^^^^^^^^^^^^^");
508 return;
509 }
510 } else {
511 $users = ilNotification::getNotificationsForObject(ilNotification::TYPE_WIKI, $wiki_id, $a_page_id, $ignore_threshold);
512 $log->debug("--->" . print_r($users));
513 if (!count($users)) {
514 $log->debug("no notifications... ^^^^^^^^^^^^^^^^^^");
515 return;
516 }
517 }
520
521 // #15192 - should always be present
522 if ($a_page_id) {
523 // #18804 - see ilWikiPageGUI::preview()
524 $link = ilLink::_getLink(null, "wiki", [], "wpage_" . $a_page_id . "_" . $a_wiki_ref_id);
525 } else {
526 $link = ilLink::_getLink($a_wiki_ref_id);
527 }
528
529 $log->debug("-- prepare content");
530 $pgui = new ilWikiPageGUI($page->getId());
531 $pgui->setRawPageContent(true);
532 $pgui->setAbstractOnly(true);
533 $pgui->setFileDownloadLink(".");
534 $pgui->setFullscreenLink(".");
535 $pgui->setSourcecodeDownloadScript(".");
536 $snippet = $pgui->showPage();
537 $snippet = ilPageObject::truncateHTML($snippet, 500, "...");
538
539 // making things more readable
540 $snippet = str_replace(['<br/>', '<br />', '</p>', '</div>'], "\n", $snippet);
541
542 $snippet = trim(strip_tags($snippet));
543
544 // "fake" new (to enable snippet - if any)
545 $hist = $page->getHistoryEntries();
546 $current_version = array_shift($hist);
547 $current_version = $current_version["nr"] ?? 0;
548 if (!$current_version && $a_action !== "comment") {
550 $a_action = "new";
551 }
552
553 $log->debug("-- sending mails");
554 $mails = [];
555 foreach (array_unique($users) as $idx => $user_id) {
556 if ($user_id != $ilUser->getId() &&
557 $ilAccess->checkAccessOfUser($user_id, 'read', '', $a_wiki_ref_id)) {
558 // use language of recipient to compose message
560 $ulng->loadLanguageModule('wiki');
561
562 if ($a_action === "comment") {
563 $subject = sprintf($ulng->txt('wiki_notification_comment_subject'), $wiki->getTitle(), $page->getTitle());
564 $message = sprintf($ulng->txt('wiki_change_notification_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
565
566 $message .= $ulng->txt('wiki_notification_' . $a_action) . ":\n\n";
567 $message .= $ulng->txt('wiki') . ": " . $wiki->getTitle() . "\n";
568 $message .= $ulng->txt('page') . ": " . $page->getTitle() . "\n";
569 $message .= $ulng->txt('wiki_commented_by') . ": " . ilUserUtil::getNamePresentation($ilUser->getId()) . "\n";
570
571 // include comment/note text
572 if ($a_comment) {
573 $message .= "\n" . $ulng->txt('comment') . ":\n\"" . trim($a_comment) . "\"\n";
574 }
575
576 $message .= "\n" . $ulng->txt('wiki_change_notification_page_link') . ": " . $link;
577 } else {
578 $subject = sprintf($ulng->txt('wiki_change_notification_subject'), $wiki->getTitle(), $page->getTitle());
579 $message = sprintf($ulng->txt('wiki_change_notification_salutation'), ilObjUser::_lookupFullname($user_id)) . "\n\n";
580
581 if ($a_type == ilNotification::TYPE_WIKI_PAGE) {
582 // update/delete
583 $message .= $ulng->txt('wiki_change_notification_page_body_' . $a_action) . ":\n\n";
584 $message .= $ulng->txt('wiki') . ": " . $wiki->getTitle() . "\n";
585 $message .= $ulng->txt('page') . ": " . $page->getTitle() . "\n";
586 $message .= $ulng->txt('wiki_changed_by') . ": " . ilUserUtil::getNamePresentation($ilUser->getId()) . "\n";
587
588 if ($snippet) {
589 $message .= "\n" . $ulng->txt('content') . "\n" .
590 "----------------------------------------\n" .
591 $snippet . "\n" .
592 "----------------------------------------\n";
593 }
594
595 // include comment/note text
596 if ($a_comment) {
597 $message .= "\n" . $ulng->txt('comment') . ":\n\"" . trim($a_comment) . "\"\n";
598 }
599
600 $message .= "\n" . $ulng->txt('wiki_change_notification_page_link') . ": " . $link;
601 } else {
602 // new
603 $message .= $ulng->txt('wiki_change_notification_body_' . $a_action) . ":\n\n";
604 $message .= $ulng->txt('wiki') . ": " . $wiki->getTitle() . "\n";
605 $message .= $ulng->txt('page') . ": " . $page->getTitle() . "\n";
606 $message .= $ulng->txt('wiki_changed_by') . ": " . ilUserUtil::getNamePresentation($ilUser->getId()) . "\n\n";
607
608 if ($snippet) {
609 $message .= $ulng->txt('content') . "\n" .
610 "----------------------------------------\n" .
611 $snippet . "\n" .
612 "----------------------------------------\n\n";
613 }
614
615 $message .= $ulng->txt('wiki_change_notification_link') . ": " . $link;
616 }
617 }
618
619 $mail_obj = new ilMail(ANONYMOUS_USER_ID);
620 $mail_obj->appendInstallationSignature(true);
621 $log->debug("before enqueue ($user_id)");
622 /*
623 $mail_obj->enqueue(
624 ilObjUser::_lookupLogin($user_id),
625 "",
626 "",
627 $subject,
628 $message,
629 array()
630 );*/
632 $mails[] = new ilMailValueObject(
633 '',
635 '',
636 '',
637 $subject,
638 $message,
639 [],
640 false,
641 false
642 );
643 $log->debug("after enqueue");
644 } else {
645 unset($users[$idx]);
646 }
647 }
648 if (count($mails) > 0) {
649 $processor = new ilMassMailTaskProcessor();
650 $processor->run(
651 $mails,
653 "",
654 []
655 );
656 }
657 $log->debug("end... ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
658 }
659}
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:115
const IL_WIKI_MODE_COLLECT
if(!defined('UTF8_REPLACEMENT')) const IL_WIKI_MODE_REPLACE
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const IL_WIKI_MODE_EXT_COLLECT
static _getLanguageOfUser(int $a_usr_id)
Get language object of user.
static getLogger(string $a_component_id)
Get component logger.
static _getInstallationSignature()
static updateNotificationTime(int $type, int $id, array $user_ids, ?int $page_id=null, bool $activate_new_entries=true)
Update the last mail timestamp for given object and users.
static getNotificationsForObject(int $type, int $id, ?int $page_id=null, bool $ignore_threshold=false)
Get all users/recipients for given object.
static _lookupFullname(int $a_user_id)
static _lookupLogin(int $a_user_id)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static truncateHTML(string $a_text, int $a_length=100, string $a_ending='...', bool $a_exact=false, bool $a_consider_html=true)
Truncate (html) string.
static getNamePresentation( $a_user_id, bool $a_user_image=false, bool $a_profile_link=false, string $a_profile_back_link='', bool $a_force_first_lastname=false, bool $a_omit_login=false, bool $a_sortable=true, bool $a_return_data_array=false, $a_ctrl_path=null)
Default behaviour is:
Class ilWikiPage GUI class.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getPageIdForTitle(int $a_wiki_id, string $a_title, string $lang="-")
Get wiki page object for id and title.
Utility class for wiki.
static makeUrlTitle(string $a_par)
static wfUrlProtocols()
From mediawiki GlobalFunctions.php.
static replaceInternalLinks(string $s, int $a_wiki_id, bool $a_offline=false, string $lang="-")
This one is based on Mediawiki Parser->replaceInternalLinks since we display images in another way,...
static processInternalLinks(string $s, int $a_wiki_id, string $a_mode=IL_WIKI_MODE_REPLACE, bool $a_collect_non_ex=false, bool $a_offline=false, string $lang="-")
Process internal links (internal)
static splitTrail(string $trail)
static wfUrlencode(string $s)
static makeLink(object $nt, int $a_wiki_id, string $text='', string $query='', string $trail='', string $prefix='', bool $a_offline=false, string $lang="-")
Make a wiki link, the following formats are supported:
static removeUnsafeCharacters(string $a_str)
static sendNotification(string $a_action, int $a_type, int $a_wiki_ref_id, int $a_page_id, ?string $a_comment=null)
static collectInternalLinks(string $s, int $a_wiki_id, bool $a_collect_non_ex=false, string $mode=IL_WIKI_MODE_COLLECT)
Collect internal wiki links of a string.
static makeDbTitle(string $a_par)
const ANONYMOUS_USER_ID
Definition: constants.php:27
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$log
Definition: ltiresult.php:34
form( $class_path, string $cmd, string $submit_caption="")
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
global $DIC
Definition: shib_login.php:26
$GLOBALS["DIC"]
Definition: wac.php:54
$lang
Definition: xapiexit.php:25
$message
Definition: xapiexit.php:31