ILIAS  Release_4_1_x_branch Revision 61804
 All Data Structures Namespaces Files Functions Variables Groups Pages
class.ilWikiUtil.php
Go to the documentation of this file.
1 <?php
2 /*
3  +-----------------------------------------------------------------------------+
4  | ILIAS open source |
5  +-----------------------------------------------------------------------------+
6  | Copyright (c) 1998-2006 ILIAS open source, University of Cologne |
7  | |
8  | This program is free software; you can redistribute it and/or |
9  | modify it under the terms of the GNU General Public License |
10  | as published by the Free Software Foundation; either version 2 |
11  | of the License, or (at your option) any later version. |
12  | |
13  | This program is distributed in the hope that it will be useful, |
14  | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16  | GNU General Public License for more details. |
17  | |
18  | You should have received a copy of the GNU General Public License |
19  | along with this program; if not, write to the Free Software |
20  | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
21  +-----------------------------------------------------------------------------+
22 */
23 
24 
38 define ("IL_WIKI_MODE_REPLACE", "replace");
39 define ("IL_WIKI_MODE_COLLECT", "collect");
40 
50 {
51 
61  static function replaceInternalLinks($s, $a_wiki_id)
62  {
63  return ilWikiUtil::processInternalLinks($s, $a_wiki_id);
64  }
65 
72  static function collectInternalLinks($s, $a_wiki_id, $a_collect_non_ex = false)
73  {
75  $a_collect_non_ex);
76  }
77 
85  static function processInternalLinks($s, $a_wiki_id,
86  $a_mode = IL_WIKI_MODE_REPLACE, $a_collect_non_ex = false)
87  {
88  $collect = array();
89  // both from mediawiki DefaulSettings.php
90  $wgLegalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+";
91 
92  // Adapter for media wiki classes
93  include_once("./Modules/Wiki/classes/class.ilMediaWikiAdapter.php");
94  $GLOBALS["wgContLang"] = new ilMediaWikiAdapter();
95  $GLOBALS["wgInterWikiCache"] = false;
96 
97  # the % is needed to support urlencoded titles as well
98  //$tc = Title::legalChars().'#%';
99  $tc = $wgLegalTitleChars.'#%';
100 
101  //$sk = $this->mOptions->getSkin();
102 
103  #split the entire text string on occurences of [[
104  $a = explode( '[[', ' ' . $s );
105  #get the first element (all text up to first [[), and remove the space we added
106  $s = array_shift( $a );
107  $s = substr( $s, 1 );
108 
109  # Match a link having the form [[namespace:link|alternate]]trail
110  $e1 = "/^([{$tc}]+)(?:\\|(.+?))?]](.*)\$/sD";
111 
112  # Match cases where there is no "]]", which might still be images
113 // static $e1_img = FALSE;
114 // if ( !$e1_img ) { $e1_img = "/^([{$tc}]+)\\|(.*)\$/sD"; }
115 
116  # Match the end of a line for a word that's not followed by whitespace,
117  # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
118 // $e2 = wfMsgForContent( 'linkprefix' );
119 
120 /* $useLinkPrefixExtension = $wgContLang->linkPrefixExtension();
121  if( is_null( $this->mTitle ) ) {
122  throw new MWException( __METHOD__.": \$this->mTitle is null\n" );
123  }
124  $nottalk = !$this->mTitle->isTalkPage();*/
125  $nottalk = true;
126 
127 /* if ( $useLinkPrefixExtension ) {
128  $m = array();
129  if ( preg_match( $e2, $s, $m ) ) {
130  $first_prefix = $m[2];
131  } else {
132  $first_prefix = false;
133  }
134  } else {*/
135  $prefix = '';
136 // }
137 
138 /* if($wgContLang->hasVariants()) {
139  $selflink = $wgContLang->convertLinkToAllVariants($this->mTitle->getPrefixedText());
140  } else {
141  $selflink = array($this->mTitle->getPrefixedText());
142  }
143  $useSubpages = $this->areSubpagesAllowed();
144  wfProfileOut( $fname.'-setup' );
145 */
146  $useSubpages = false;
147 
148  # Loop for each link
149  for ($k = 0; isset( $a[$k] ); $k++)
150  {
151  $line = $a[$k];
152 
153 /* if ( $useLinkPrefixExtension ) {
154  wfProfileIn( $fname.'-prefixhandling' );
155  if ( preg_match( $e2, $s, $m ) ) {
156  $prefix = $m[2];
157  $s = $m[1];
158  } else {
159  $prefix='';
160  }
161  # first link
162  if($first_prefix) {
163  $prefix = $first_prefix;
164  $first_prefix = false;
165  }
166  wfProfileOut( $fname.'-prefixhandling' );
167  }*/
168 
169  $might_be_img = false;
170 
171  //wfProfileIn( "$fname-e1" );
172  if ( preg_match( $e1, $line, $m ) ) { # page with normal text or alt
173  $text = $m[2];
174  # If we get a ] at the beginning of $m[3] that means we have a link that's something like:
175  # [[Image:Foo.jpg|[http://example.com desc]]] <- having three ] in a row fucks up,
176  # the real problem is with the $e1 regex
177  # See bug 1300.
178  #
179  # Still some problems for cases where the ] is meant to be outside punctuation,
180  # and no image is in sight. See bug 2095.
181  #
182  if( $text !== '' &&
183  substr( $m[3], 0, 1 ) === ']' &&
184  strpos($text, '[') !== false
185  )
186  {
187  $text .= ']'; # so that replaceExternalLinks($text) works later
188  $m[3] = substr( $m[3], 1 );
189  }
190  # fix up urlencoded title texts
191  if( strpos( $m[1], '%' ) !== false ) {
192  # Should anchors '#' also be rejected?
193  $m[1] = str_replace( array('<', '>'), array('&lt;', '&gt;'), urldecode($m[1]) );
194  }
195  $trail = $m[3];
196 /* } elseif( preg_match($e1_img, $line, $m) ) { # Invalid, but might be an image with a link in its caption
197  $might_be_img = true;
198  $text = $m[2];
199  if ( strpos( $m[1], '%' ) !== false ) {
200  $m[1] = urldecode($m[1]);
201  }
202  $trail = "";*/
203  } else { # Invalid form; output directly
204  $s .= $prefix . '[[' . $line ;
205  //wfProfileOut( "$fname-e1" );
206  continue;
207  }
208  //wfProfileOut( "$fname-e1" );
209  //wfProfileIn( "$fname-misc" );
210 
211  # Don't allow internal links to pages containing
212  # PROTO: where PROTO is a valid URL protocol; these
213  # should be external links.
214  if (preg_match('/^\b(?:' . ilWikiUtil::wfUrlProtocols() . ')/', $m[1])) {
215  $s .= $prefix . '[[' . $line ;
216  continue;
217  }
218 
219  # Make subpage if necessary
220 /* if( $useSubpages ) {
221  $link = $this->maybeDoSubpageLink( $m[1], $text );
222  } else {*/
223  $link = $m[1];
224 // }
225 
226  $noforce = (substr($m[1], 0, 1) != ':');
227  if (!$noforce) {
228  # Strip off leading ':'
229  $link = substr($link, 1);
230  }
231 
232 // wfProfileOut( "$fname-misc" );
233 // wfProfileIn( "$fname-title" );
234 
235  // todo
236  include_once("./Modules/Wiki/mediawiki/Title.php");
237  include_once("./Services/Utilities/classes/Sanitizer.php");
238  //$nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) );
239 
240  // todo: check step by step
241 //echo "<br>".htmlentities($link)."---";
242  $nt = Title::newFromText($link);
243 
244  if( !$nt ) {
245  $s .= $prefix . '[[' . $line;
246  //wfProfileOut( "$fname-title" );
247  continue;
248  }
249 
250 /* $ns = $nt->getNamespace();
251  $iw = $nt->getInterWiki();
252  wfProfileOut( "$fname-title" );
253 
254 /* if ($might_be_img) { # if this is actually an invalid link
255  wfProfileIn( "$fname-might_be_img" );
256  if ($ns == NS_IMAGE && $noforce) { #but might be an image
257  $found = false;
258  while (isset ($a[$k+1]) ) {
259  #look at the next 'line' to see if we can close it there
260  $spliced = array_splice( $a, $k + 1, 1 );
261  $next_line = array_shift( $spliced );
262  $m = explode( ']]', $next_line, 3 );
263  if ( count( $m ) == 3 ) {
264  # the first ]] closes the inner link, the second the image
265  $found = true;
266  $text .= "[[{$m[0]}]]{$m[1]}";
267  $trail = $m[2];
268  break;
269  } elseif ( count( $m ) == 2 ) {
270  #if there's exactly one ]] that's fine, we'll keep looking
271  $text .= "[[{$m[0]}]]{$m[1]}";
272  } else {
273  #if $next_line is invalid too, we need look no further
274  $text .= '[[' . $next_line;
275  break;
276  }
277  }
278  if ( !$found ) {
279  # we couldn't find the end of this imageLink, so output it raw
280  #but don't ignore what might be perfectly normal links in the text we've examined
281  $text = $this->replaceInternalLinks($text);
282  $s .= "{$prefix}[[$link|$text";
283  # note: no $trail, because without an end, there *is* no trail
284  wfProfileOut( "$fname-might_be_img" );
285  continue;
286  }
287  } else { #it's not an image, so output it raw
288  $s .= "{$prefix}[[$link|$text";
289  # note: no $trail, because without an end, there *is* no trail
290  wfProfileOut( "$fname-might_be_img" );
291  continue;
292  }
293  wfProfileOut( "$fname-might_be_img" );
294  }
295 */
296 
297  $wasblank = ( '' == $text );
298  if( $wasblank ) $text = $link;
299 
300  # Link not escaped by : , create the various objects
301  if( $noforce ) {
302  # Interwikis
303  /*wfProfileIn( "$fname-interwiki" );
304  if( $iw && $this->mOptions->getInterwikiMagic() && $nottalk && $wgContLang->getLanguageName( $iw ) ) {
305  $this->mOutput->addLanguageLink( $nt->getFullText() );
306  $s = rtrim($s . $prefix);
307  $s .= trim($trail, "\n") == '' ? '': $prefix . $trail;
308  wfProfileOut( "$fname-interwiki" );
309  continue;
310  }
311  wfProfileOut( "$fname-interwiki" );*/
312 
313 /* if ( $ns == NS_IMAGE ) {
314  wfProfileIn( "$fname-image" );
315  if ( !wfIsBadImage( $nt->getDBkey(), $this->mTitle ) ) {
316  # recursively parse links inside the image caption
317  # actually, this will parse them in any other parameters, too,
318  # but it might be hard to fix that, and it doesn't matter ATM
319  $text = $this->replaceExternalLinks($text);
320  $text = $this->replaceInternalLinks($text);
321 
322  # cloak any absolute URLs inside the image markup, so replaceExternalLinks() won't touch them
323  $s .= $prefix . $this->armorLinks( $this->makeImage( $nt, $text ) ) . $trail;
324  $this->mOutput->addImage( $nt->getDBkey() );
325 
326  wfProfileOut( "$fname-image" );
327  continue;
328  } else {
329  # We still need to record the image's presence on the page
330  $this->mOutput->addImage( $nt->getDBkey() );
331  }
332  wfProfileOut( "$fname-image" );
333 
334  }
335 */
336 /* if ( $ns == NS_CATEGORY ) {
337  wfProfileIn( "$fname-category" );
338  $s = rtrim($s . "\n"); # bug 87
339 
340  if ( $wasblank ) {
341  $sortkey = $this->getDefaultSort();
342  } else {
343  $sortkey = $text;
344  }
345  $sortkey = Sanitizer::decodeCharReferences( $sortkey );
346  $sortkey = str_replace( "\n", '', $sortkey );
347  $sortkey = $wgContLang->convertCategoryKey( $sortkey );
348  $this->mOutput->addCategory( $nt->getDBkey(), $sortkey );
349 */
354 // $s .= trim($prefix . $trail, "\n") == '' ? '': $prefix . $trail;
355 
356 // wfProfileOut( "$fname-category" );
357 // continue;
358 // }
359  }
360 
361  # Self-link checking
362 /* if( $nt->getFragment() === '' ) {
363  if( in_array( $nt->getPrefixedText(), $selflink, true ) ) {
364  $s .= $prefix . $sk->makeSelfLinkObj( $nt, $text, '', $trail );
365  continue;
366  }
367  }*/
368 
369  # Special and Media are pseudo-namespaces; no pages actually exist in them
370 /* if( $ns == NS_MEDIA ) {
371  $link = $sk->makeMediaLinkObj( $nt, $text );
372  # Cloak with NOPARSE to avoid replacement in replaceExternalLinks
373  $s .= $prefix . $this->armorLinks( $link ) . $trail;
374  $this->mOutput->addImage( $nt->getDBkey() );
375  continue;
376  } elseif( $ns == NS_SPECIAL ) {
377  $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
378  continue;
379  } elseif( $ns == NS_IMAGE ) {
380  $img = new Image( $nt );
381  if( $img->exists() ) {
382  // Force a blue link if the file exists; may be a remote
383  // upload on the shared repository, and we want to see its
384  // auto-generated page.
385  $s .= $this->makeKnownLinkHolder( $nt, $text, '', $trail, $prefix );
386  $this->mOutput->addLink( $nt );
387  continue;
388  }
389  }*/
390 
391  // Media wiki performs an intermediate step here (Parser->makeLinkHolder)
392  if ($a_mode == IL_WIKI_MODE_REPLACE)
393  {
394  $s .= ilWikiUtil::makeLink($nt, $a_wiki_id, $text, '', $trail, $prefix);
395 //echo "<br>-".htmlentities($s)."-";
396  }
397  else
398  {
399  $url_title = ilWikiUtil::makeUrlTitle($nt->mTextform);
400  $db_title = ilWikiUtil::makeDbTitle($nt->mTextform);
401 
402  //$s .= ilWikiUtil::makeLink($nt, $a_wiki_id, $text, '', $trail, $prefix);
403  include_once("./Modules/Wiki/classes/class.ilWikiPage.php");
404  if ((ilWikiPage::_wikiPageExists($a_wiki_id, $db_title) ||
405  $a_collect_non_ex)
406  &&
407  !in_array($db_title, $collect))
408  {
409  $collect[] = $db_title;
410  }
411  }
412  }
413 
414  //wfProfileOut( $fname );
415 
416  if ($a_mode == IL_WIKI_MODE_COLLECT)
417  {
418  return $collect;
419  }
420  else
421  {
422  return $s;
423  }
424  }
425 
429  function removeUnsafeCharacters($a_str)
430  {
431  return str_replace(array("\x00", "\n", "\r", "\\", "'", '"', "\x1a"), "", $a_str);
432  }
433 
437  static function makeLink( &$nt, $a_wiki_id, $text = '', $query = '', $trail = '', $prefix = '' )
438  {
439  global $ilCtrl;
440 
441  //wfProfileIn( __METHOD__ );
442  if ( ! is_object($nt) ) {
443  # Fail gracefully
444  $retVal = "<!-- ERROR -->{$prefix}{$text}{$trail}";
445  } else {
446 
447 //var_dump($trail);
448 
449  # Separate the link trail from the rest of the link
450  list( $inside, $trail ) = ilWikiUtil::splitTrail( $trail );
451 
452  //$retVal = '***'.$text."***".$trail;
453  $url_title = ilWikiUtil::makeUrlTitle($nt->mTextform);
454  $db_title = ilWikiUtil::makeDbTitle($nt->mTextform);
455  $wiki_link_class = (!ilWikiPage::_wikiPageExists($a_wiki_id, $db_title))
456  ? ' class="ilWikiPageMissing" ' : "";
457 
458  $ilCtrl->setParameterByClass("ilobjwikigui", "page", $url_title);
459  $retVal = '<a '.$wiki_link_class.' href="'.
460  $ilCtrl->getLinkTargetByClass("ilobjwikigui", "gotoPage").
461  '">'.$text.'</a>'.$trail;
462 
463 //$ilCtrl->debug("ilWikiUtil::makeLink:-$inside-$trail-");
464 /* if ( $nt->isExternal() ) {
465  $nr = array_push( $this->mInterwikiLinkHolders['texts'], $prefix.$text.$inside );
466  $this->mInterwikiLinkHolders['titles'][] = $nt;
467  $retVal = '<!--IWLINK '. ($nr-1) ."-->{$trail}";
468  } else {
469  $nr = array_push( $this->mLinkHolders['namespaces'], $nt->getNamespace() );
470  $this->mLinkHolders['dbkeys'][] = $nt->getDBkey();
471  $this->mLinkHolders['queries'][] = $query;
472  $this->mLinkHolders['texts'][] = $prefix.$text.$inside;
473  $this->mLinkHolders['titles'][] = $nt;
474 
475  $retVal = '<!--LINK '. ($nr-1) ."-->{$trail}";
476  }
477 */
478  }
479  //wfProfileOut( __METHOD__ );
480  return $retVal;
481  }
482 
486  static function wfUrlProtocols()
487  {
488  $wgUrlProtocols = array(
489  'http://',
490  'https://',
491  'ftp://',
492  'irc://',
493  'gopher://',
494  'telnet://', // Well if we're going to support the above.. -ævar
495  'nntp://', // @bug 3808 RFC 1738
496  'worldwind://',
497  'mailto:',
498  'news:'
499  );
500 
501  // Support old-style $wgUrlProtocols strings, for backwards compatibility
502  // with LocalSettings files from 1.5
503  if ( is_array( $wgUrlProtocols ) ) {
504  $protocols = array();
505  foreach ($wgUrlProtocols as $protocol)
506  $protocols[] = preg_quote( $protocol, '/' );
507 
508  return implode( '|', $protocols );
509  } else {
510  return $wgUrlProtocols;
511  }
512  }
513 
517  public static function wfUrlencode ( $s )
518  {
519  $s = urlencode( $s );
520 // $s = preg_replace( '/%3[Aa]/', ':', $s );
521 // $s = preg_replace( '/%2[Ff]/', '/', $s );
522 
523  return $s;
524  }
525 
526 
530  static function makeDbTitle($a_par)
531  {
532  $a_par = ilWikiUtil::removeUnsafeCharacters($a_par);
533  return str_replace("_", " ", $a_par);
534  }
535 
539  static function makeUrlTitle($a_par)
540  {
541  $a_par = ilWikiUtil::removeUnsafeCharacters($a_par);
542  $a_par = str_replace(" ", "_", $a_par);
543  return ilWikiUtil::wfUrlencode($a_par);
544  }
545 
546  // from Linker.php
547  static function splitTrail( $trail )
548  {
549  /*static $regex = false;
550  if ( $regex === false ) {
551  global $wgContLang;
552  $regex = $wgContLang->linkTrail();
553  }*/
554  $regex = '/^([a-z]+)(.*)$/sD';
555 
556  $inside = '';
557  if ( '' != $trail ) {
558  $m = array();
559 
560  if ( preg_match( $regex, $trail, $m ) ) {
561  $inside = $m[1];
562  $trail = $m[2];
563  }
564  }
565 
566  return array( $inside, $trail );
567  }
568 
569  static function sendNotification($a_action, $a_type, $a_wiki_ref_id, $a_page_id)
570  {
571  global $ilUser, $ilObjDataCache;
572 
573  include_once "./Services/Notification/classes/class.ilNotification.php";
574  include_once "./Modules/Wiki/classes/class.ilObjWiki.php";
575  include_once "./Modules/Wiki/classes/class.ilWikiPage.php";
576 
577  $wiki_id = $ilObjDataCache->lookupObjId($a_wiki_ref_id);
578  $wiki = new ilObjWiki($a_wiki_ref_id, true);
579  $page = new ilWikiPage($a_page_id);
580 
581  if($a_type == ilNotification::TYPE_WIKI_PAGE)
582  {
583  $users = ilNotification::getNotificationsForObject($a_type, $a_page_id);
584  $wiki_users = ilNotification::getNotificationsForObject(ilNotification::TYPE_WIKI, $wiki_id, $a_page_id);
585  $users = array_merge($users, $wiki_users);
586  if(!sizeof($users))
587  {
588  return;
589  }
590 
591  include_once "./Modules/Wiki/classes/class.ilObjWikiGUI.php";
592  $link = ILIAS_HTTP_PATH."/".ilObjWikiGui::getGotoLink($a_wiki_ref_id, $page->getTitle());
593 
595  }
596  else
597  {
599  if(!sizeof($users))
600  {
601  return;
602  }
603 
604  include_once "./classes/class.ilLink.php";
605  $link = ilLink::_getLink($a_wiki_ref_id);
606  }
607 
609 
610  include_once "./Services/Mail/classes/class.ilMail.php";
611  include_once "./Services/User/classes/class.ilObjUser.php";
612  include_once "./Services/Language/classes/class.ilLanguageFactory.php";
613 
614  foreach(array_unique($users) as $idx => $user_id)
615  {
616  if($user_id != $ilUser->getId())
617  {
618  // use language of recipient to compose message
619  $ulng = ilLanguageFactory::_getLanguageOfUser($user_id);
620  $ulng->loadLanguageModule('wiki');
621 
622  $subject = sprintf($ulng->txt('wiki_change_notification_subject'), $wiki->getTitle());
623  $message = sprintf($ulng->txt('wiki_change_notification_salutation'), ilObjUser::_lookupFullname($user_id))."\n\n";
624 
625  if($a_type == ilNotification::TYPE_WIKI_PAGE)
626  {
627  // update/delete
628  $message .= $ulng->txt('wiki_change_notification_page_body_'.$a_action).":\n\n";
629  $message .= $ulng->txt('wiki').": ".$wiki->getTitle()."\n";
630  $message .= $ulng->txt('page').": ".$page->getTitle()."\n\n";
631  $message .= $ulng->txt('wiki_change_notification_page_link').": ".$link;
632  }
633  else
634  {
635  // new
636  $message .= $ulng->txt('wiki_change_notification_body_'.$a_action).":\n\n";
637  $message .= $ulng->txt('wiki').": ".$wiki->getTitle()."\n";
638  $message .= $ulng->txt('page').": ".$page->getTitle()."\n\n";
639  $message .= $ulng->txt('wiki_change_notification_link').": ".$link;
640  }
641 
642  $mail_obj = new ilMail(ANONYMOUS_USER_ID);
643  $mail_obj->appendInstallationSignature(true);
644  $mail_obj->sendMail(ilObjUser::_lookupLogin($user_id),
645  "", "", $subject, $message, array(), array("system"));
646  }
647  else
648  {
649  unset($users[$idx]);
650  }
651  }
652  }
653 }
654 
655 ?>