1<?php 2/** 3 * DokuWiki Plugin linkfix (Admin Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12class admin_plugin_linkfix extends DokuWiki_Admin_Plugin { 13 14 protected $searchin = ''; 15 protected $changefrom = ''; 16 protected $changeto = ''; 17 protected $qchangefrom = ''; 18 protected $dryrun = false; 19 protected $type = 'links'; 20 protected $isextern = false; 21 22 public function getMenuSort() { 23 return 3000; 24 } 25 26 public function handle() { 27 global $INPUT; 28 29 $this->searchin = cleanID($INPUT->str('searchin')); 30 $this->changefrom = $INPUT->str('changefrom'); 31 $this->changeto = $INPUT->str('changeto'); 32 $this->type = $INPUT->valid('type', array('links', 'media'), 'links'); 33 $this->dryrun = $INPUT->bool('dryrun'); 34 $this->isextern = preg_match('/^(\w+:\/\/|\\\\)/i', $this->changefrom); 35 } 36 37 public function html() { 38 global $ID; 39 global $INPUT; 40 41 if($INPUT->has('go') && checkSecurityToken()) { 42 echo '<h3>' . $this->getLang('processing') . '</h3>'; 43 tpl_flush(); 44 45 ignore_user_abort(true); 46 set_time_limit(0); 47 if($this->execute()) { 48 echo '<h3>'.$this->getLang('processingdone').'</h3>'; 49 50 if($this->dryrun) { 51 echo '<a href="' . wl( 52 '', array( 53 'do' => 'admin', 54 'page' => 'linkfix', 55 'searchin' => $this->searchin, 56 'changefrom' => $this->changefrom, 57 'changeto' => $this->changeto, 58 'dryrun' => 0, 59 'go' => 'go', 60 'sectok' => getSecurityToken() 61 ) 62 ) . '">'.$this->getLang('rerunhot').'</a>'; 63 } 64 tpl_flush(); 65 } 66 } else { 67 68 echo $this->locale_xhtml('intro'); 69 70 $form = new Doku_Form(array('action' => wl(), 'class' => 'plugin_linkfix')); 71 $form->addHidden('go', 'go'); 72 $form->addHidden('page', 'linkfix'); 73 $form->addHidden('id', $ID); 74 $form->startFieldset($this->getLang('menu')); 75 $form->addElement(form_makeTextField('searchin', '', $this->getLang('searchin'), '', 'block')); 76 $form->addElement(form_makeTextField('changefrom', '', $this->getLang('changefrom'), '', 'block')); 77 $form->addElement(form_makeTextField('changeto', '', $this->getLang('changeto'), '', 'block')); 78 $form->addElement(form_makeRadioField('type', 'links', $this->getLang('links'), '', 'block tick', array('checked' => 'checked'))); 79 $form->addElement(form_makeRadioField('type', 'media', $this->getLang('media'), '', 'block tick')); 80 $form->addElement(form_makeCheckboxField('dryrun', '1', $this->getLang('dryrun'), '', 'block tick', array('checked' => 'checked'))); 81 $form->addElement(form_makeButton('submit', 'admin', $this->getLang('submit'))); 82 $form->printForm(); 83 } 84 } 85 86 /** 87 * Search for all pages in the set namespace and update them 88 * 89 * @returns bool false if no correct namespace was given 90 */ 91 protected function execute() { 92 global $conf; 93 94 // make sure the given search namespace exists 95 $searchin = str_replace(':', '/', $this->searchin); 96 if(!is_dir($conf['datadir'] . '/' . $searchin)) { 97 msg(sprintf($this->getLang('badnamespace'), hsc($this->searchin)), -1); 98 return false; 99 } 100 101 // use indexer to find all possibly affected pages 102 if($this->isextern) { 103 $null = ''; 104 $data = ft_pageSearch('"' . $this->changefrom . '"*', $null); 105 $data = array_keys($data); 106 } elseif($this->type == 'media') { 107 $query = $this->changefrom . '*'; 108 $data = idx_get_indexer()->lookupKey('relation_media', $query); 109 } else { 110 $query = $this->changefrom . '*'; 111 $data = idx_get_indexer()->lookupKey('relation_references', $query); 112 } 113 $data = array_unique($data); 114 115 $len = strlen($this->searchin); 116 foreach($data as $id) { 117 118 // skip everything that's not in the wanted namespace 119 if($len && substr($id, 0, $len + 1) != $this->searchin . ':') continue; 120 // skip non existing pages 121 if(!page_exists($id)) continue; 122 123 $this->prnt('<ul>'); 124 $this->updatepage($id); 125 $this->prnt('</ul>'); 126 127 } 128 129 return true; 130 } 131 132 /** 133 * Rewrite all links and media items in the given page 134 * 135 * @param string $currentpage the page to rewrite 136 */ 137 protected function updatepage($currentpage) { 138 $currentns = getNS($currentpage); 139 140 $text = rawWiki($currentpage); 141 $crc = md5($text); 142 $instructions = p_get_instructions($text); 143 $instructions = array_reverse($instructions); 144 145 $this->prnt('<li><div class="li">'); 146 $this->prnt($this->getLang('checking') . ' <b>' . hsc($currentpage) . "</b><br />"); 147 148 tpl_flush(); 149 150 151 $this->prnt('<ul>'); 152 foreach($instructions as $instruction) { 153 if( 154 ($this->type == 'links' && $this->isextern && ($instruction[0] == 'externallink' || $instruction[0] == 'windowssharelink')) || 155 ($this->type == 'links' && !$this->isextern && $instruction[0] == 'internallink') || 156 ($this->type == 'media' && $this->isextern && $instruction[0] == 'externalmedia') || 157 ($this->type == 'media' && !$this->isextern && $instruction[0] == 'internalmedia') 158 ) { 159 $link = $instruction[1][0]; 160 $pos = $instruction[2] - 1; 161 162 while( 163 $text[$pos] == '[' || 164 $text[$pos] == '{' || 165 $text[$pos] == ' ' || 166 $text[$pos] == "\t" 167 ) { 168 $pos++; 169 } 170 171 $full = $link; 172 $exists = false; 173 if(!$this->isextern) { 174 if($this->type == 'links') { 175 resolve_pageid($currentns, $full, $exists); 176 } else { 177 resolve_mediaid($currentns, $full, $exists); 178 } 179 } 180 181 // create the new link 182 $newlink = $this->changeLink($link, $full, $currentns); 183 184 // replace the link 185 if(($link != $newlink) && ($full != cleanID($newlink))) { 186 $this->prnt('<li><div class="li">'); 187 $this->prnt(hsc($full) . ' → ' . hsc($newlink) . '<br />'); 188 $this->prnt('</div></li>'); 189 190 $text = substr($text, 0, $pos) . 191 $newlink . // new link 192 substr($text, $pos + strlen($link)); // continue after old link 193 } 194 195 } 196 // everything else is ignored 197 } 198 $this->prnt('</ul>'); 199 200 if($crc == md5($text)) { 201 $this->prnt('✗ '.$this->getLang('fail').'<br />'); 202 } else { 203 if($this->dryrun) { 204 $this->prnt('✓ '.$this->getLang('successdry').'<br />'); 205 } else { 206 saveWikiText($currentpage, $text, $this->getLang('summary'), true); 207 $this->prnt('✓ '.$this->getLang('success').'<br />'); 208 } 209 } 210 $this->prnt('</div></li>'); 211 tpl_flush(); 212 } 213 214 /** 215 * Wrapper around echo, for better testability 216 * 217 * @param $string 218 */ 219 protected function prnt($string) { 220 echo $string; 221 } 222 223 /** 224 * Rewrite the given link according to the given settings 225 * 226 * @param string $link the current link as found in the page source 227 * @param string $full the full, resolved version of the above link 228 * @param string $currentNS the namespace of the page the link was found in 229 * @return string the corrected link 230 */ 231 protected function changeLink($link, $full, $currentNS) { 232 // make sure the prefix matches (caseinsensitive) 233 if($this->changefrom){ 234 if(!$this->qchangefrom) { 235 $this->qchangefrom = preg_quote($this->qchangefrom, '/'); 236 } 237 if(!preg_match('/^'.$this->qchangefrom.'/ui', $full)) return $link; 238 } 239 240 // strip prefix 241 $new = substr($full, strlen($this->changefrom)); 242 243 // add prefix 244 $new = $this->changeto . $new; 245 246 if(!$this->isextern) { 247 // strip left over colons 248 $new = ltrim($new, ':'); 249 250 // make absolute if needed 251 if($currentNS && strpos($new, ':') === false) $new = ":$new"; 252 } 253 254 return $new; 255 } 256 257} 258 259// vim:ts=4:sw=4:et: 260