1<?php 2/** 3 * ABC Plugin (http://dokuwiki.org/plugin:abc) 4 * for ABC notation (http://abcnotation.org.uk/) 5 * in DokuWiki (http://dokuwiki.org/) 6 * 7 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 8 * @author Anika Henke <anika@selfthinker.org> 9 */ 10 11if(!defined('DOKU_INC')) die(); 12if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 13require_once(DOKU_PLUGIN.'syntax.php'); 14 15class syntax_plugin_abc extends DokuWiki_Syntax_Plugin { 16 17 function getType(){ 18 return 'protected'; 19 } 20 function getPType(){ 21 return 'block'; 22 } 23 function getSort(){ 24 return 192; 25 } 26 27 function connectTo($mode) { 28 $this->Lexer->addEntryPattern('<abc(?=.*\x3C/abc\x3E)',$mode,'plugin_abc'); 29 } 30 function postConnect() { 31 $this->Lexer->addExitPattern('</abc>','plugin_abc'); 32 } 33 34 35 function handle($match, $state, $pos, Doku_Handler $handler){ 36 if ( $state == DOKU_LEXER_UNMATCHED ) { 37 $matches = preg_split('/>/u',$match,2); 38 $matches[0] = trim($matches[0]); 39 return array($matches[1],$matches[0]); 40 } 41 return true; 42 } 43 44 /** 45 * Create output 46 */ 47 function render($mode, Doku_Renderer $renderer, $data) { 48 global $INFO; 49 global $ACT; 50 global $conf; 51 if($mode == 'xhtml' && strlen($data[0]) > 1){ 52 $src = $data[0]; 53 $origSrc = $src; 54 $trans = "0 ".$data[1]; // "0" includes the original key 55 $debug = $conf['allowdebug']; 56 57 $error = $this->_checkExecs(); 58 if($this->getConf('abcok') && (!$INFO['rev'] || ($INFO['rev'] && ($ACT=='preview'))) && !$error){ 59 //do not create/show files if an old revision is viewed, but always if the page is previewed and never when there is an error 60 61 $entitiesFile = dirname(__FILE__).'/conf/entities.conf'; 62 if (@file_exists($entitiesFile)) { 63 $entities = confToHash($entitiesFile); 64 $src = strtr($src,$entities); 65 } 66 $fileBase = $this->_getFileBase($origSrc); 67 $srcFile = $fileBase.'.abc'; 68 $srcChanged = (!file_exists($srcFile) || (file_exists($srcFile) && $src!=io_readFile($srcFile))); 69 if ($srcChanged) io_saveFile($srcFile, $src); 70 71 if ($this->getConf('abc2abc') && is_executable($this->getConf('abc2abc'))) { 72 $transSrc = $this->_getTransSrc($trans); 73 $transNew = $this->_getTransNew($fileBase, $transSrc); 74 } else { 75 $transSrc = array(0); 76 $transNew = array(); 77 } 78 $renderList = $srcChanged ? $transSrc : $transNew; 79 if($debug || $_REQUEST['purge']) $renderList = $transSrc; 80 81 // create files 82 foreach ($renderList as $transMode) { 83 // if no transposition is allowed and the tune shall be transposed 84 // by 0 semitones (= not at all), then nothing is appended to the fileBase; 85 // else append the amount of semitiones to the fileBase 86 $curFileBase = ($transMode==0) ? $fileBase : $fileBase."_".$transMode; 87 $abcFile = $curFileBase.'.abc'; 88 io_saveFile($abcFile, $src); 89 90 ob_start(); 91 92 if ($transMode!=0) { 93 $this->_transpose($abcFile, $srcFile, $transMode); 94 } 95 $debugLog = $this->_createImgFile($abcFile, $curFileBase); 96 97 if ($this->getConf('displayType')==1 || $this->getConf('displayType')==2) { 98 $this->_createMidiFile($abcFile, $curFileBase); 99 } 100 if ($this->getConf('displayType')==2) { 101 $this->_createPsFile($abcFile, $curFileBase); 102 if ($this->getConf('ps2pdf')) { 103 $this->_createPdfFile($abcFile, $curFileBase); 104 } 105 } 106 $errorLog = ob_get_contents(); 107 ob_end_clean(); 108 } 109 if (($this->getConf('displayErrorlog') || $debug) && $errorLog) { 110 $errorLog = str_replace($this->_getAbc2psVersion(), "abc2ps", $errorLog); 111 //hide abc2ps version for security reasons 112 //TODO: hide lines starting with "writing MIDI file", "File", "Output written on", ... for boring reasons 113 msg(nl2br($errorLog), 2); 114 } 115 if ($debugLog) { 116 msg($debugLog); 117 } 118 // display files 119 foreach ($transSrc as $transMode) { 120 $curFileBase = ($transMode==0) ? $fileBase : $fileBase."_".$transMode; 121 $renderer->doc .= $this->_showFiles($curFileBase); 122 } 123 124 // always have the abc source in the html source (for search engine optimization) 125 // only per css visible when displaySource = 1 126 if ($this->getConf('displaySource')) $visible = " visible"; 127 $renderer->doc .= '<div class="abc_src'.$visible.'">'.NL; 128 $renderer->doc .= $renderer->file($origSrc); 129 $renderer->doc .= '</div>'.NL; 130 } else { 131 if ($error && $this->getConf('abcok')) { 132 msg($error, -1); 133 } 134 $renderer->doc .= $renderer->file($origSrc); 135 } 136 return true; 137 } 138 return false; 139 } 140 141 /** 142 * check if all needed programs are set, existent and executable 143 */ 144 function _checkExecs() { 145 global $conf; 146 $error .= $this->_checkExec($this->getConf('abc2ps'), 'abc2ps'); 147 $error .= $this->_checkExec($conf['im_convert'], 'im_convert'); 148 if (($this->getConf('displayType')==1) || ($this->getConf('displayType')==2)) { 149 $tmpError1 = $this->_checkExec($this->getConf('abc2midi'), 'abc2midi'); 150 if ($tmpError1) $error .= $tmpError1."If you do not wish to install it, you can change the 'displayType' to '0' ('image only').<br />"; 151 } 152 if($this->getConf('ps2pdf') && ($this->getConf('displayType')==2)) { 153 $tmpError2 = $this->_checkExec($this->getConf('ps2pdf'), 'ps2pdf'); 154 if ($tmpError2) $error .= $tmpError2."If you do not wish to install it, you can leave it blank and a ps file will be generated instead.<br />"; 155 } 156 return $error; 157 } 158 /** 159 * check if a program is set, existent and executable 160 */ 161 function _checkExec($execFile, $execName) { 162 if (!$execFile) { 163 $error .= "The '<strong>".$execName."</strong>' config option is <strong>not set</strong>.<br />"; 164 } else if (!file_exists($execFile)) { 165 $error .= "'".$execFile."' (<strong>".$execName."</strong>) is <strong>not existent</strong>.<br />"; 166 } else if (!is_executable($execFile)) { 167 $error .= "'".$execFile."' (<strong>".$execName."</strong>) is <strong>not executable</strong>.<br />"; 168 } 169 return $error; 170 } 171 172 /** 173 * get to-be directory and filename (without extension) 174 * 175 * all files are stored in the media directory into 'plugin_abc/<namespaces>/' 176 * and the filename is a mixture of abc-id and abc-title (e.g. 42_the_title.abc|...) 177 * 178 */ 179 function _getFileBase($src) { 180 global $ID; 181 global $ACT; 182 global $conf; 183 $mediadir = $conf['mediadir']; 184 185 // where to store the abc media files 186 $abcdir = $this->getConf('mediaNS') ? $mediadir.'/'.$this->getConf('mediaNS') : $mediadir; 187 io_makeFileDir($abcdir); 188 $fileDir = $abcdir.'/'.utf8_encodeFN(str_replace(':','/',getNS($ID))); 189 190 // the abcID is what comes after the 'X:' 191 preg_match("/\s?X\s?:(.*?)\n/s", $src, $matchesX); 192 $abcID = preg_replace('/\s?X\s?:/', '', $matchesX[0]); 193 // the abcTitle is what comes after the (first) 'T:' 194 preg_match("/\s?T\s?:(.*?)\n/s", $src, $matchesT); 195 $abcTitle = preg_replace('/\s?T\s?:/', '', $matchesT[0]); 196 $fileName = cleanID($abcID."_".$abcTitle); 197 198 // no double slash when in root namespace 199 $slashStr = (getNS($ID)) ? "/" : ""; 200 // have different fileBase for previewing 201 $previewPrefix = ($ACT!='preview') ? "" : "x"; 202 203 $fileBase = $fileDir.$slashStr.$previewPrefix.$fileName; 204 // unfortunately abcm2ps seems not to be able to handle 205 // file names (realpath) of more than 120 characters 206 $realFileBaseLen = (strlen(fullpath($abcdir)) - strlen($abcdir)) + strlen($fileBase); 207 $char_len = 114; 208 if ($realFileBaseLen >= $char_len) { 209 $truncLen = strlen($fileBase) + ($char_len - $realFileBaseLen); 210 $fileBase = substr($fileBase, 0, $truncLen); 211 } 212 return $fileBase; 213 } 214 215 /** 216 * get transposition parameters from the source into a reasonable array 217 */ 218 function _getTransSrc($trans) { 219 $transSrc = explode(" ", $trans); 220 // the semitones to transpose have to be integers 221 $transSrc = array_map("intval", $transSrc); 222 // do not transpose by the same amount of semitones more than once 223 $transSrc = array_unique($transSrc); 224 // do not transpose higher or lower than 24 semitones 225 $transSrc = array_filter($transSrc, create_function('$t', 'return($t<24 && $t >-24);')); 226 // do not allow transposition into more than 8 keys 227 array_splice($transSrc, 8); 228 return $transSrc; 229 } 230 231 /** 232 * get all new and old trans params 233 * return the new params, delete the corresponding old files 234 */ 235 function _getTransNew($fileBase, $transSrc) { 236 // get all abc files belonging to the fileBase 237 $filesArrABC = glob(dirname($fileBase)."/{".basename($fileBase)."*.abc}", GLOB_BRACE); 238 $transFS = array(0); // always include the original key 239 // get all numbers after the '_' and before the '.abc' 240 foreach ($filesArrABC as $f) { 241 $f = basename($f, ".abc"); 242 $tr = substr(strrchr($f,'_'),1); 243 if (intval($tr)) $transFS[] = $tr; 244 } 245 246 $transNew = array_diff($transSrc, $transFS); 247 $transOld = array_diff($transFS, $transSrc); 248 249 // delete old transposed files 250 foreach ($transOld as $o) { 251 $filesArrAll = glob(dirname($fileBase)."/{".basename($fileBase)."_".$o."*}", GLOB_BRACE); 252 foreach ($filesArrAll as $d) { 253 unlink($d); 254 } 255 } 256 return $transNew; 257 } 258 259 /** 260 * transpose and create transposed abc 261 */ 262 function _transpose($abcFile, $srcFile, $trans) { 263 passthru(fullpath($this->getConf('abc2abc'))." $srcFile -e -t $trans > $abcFile"); 264 } 265 266 /** 267 * create img file 268 */ 269 function _createImgFile($abcFile, $fileBase) { 270 global $conf; 271 $epsFile = $fileBase.'001.eps'; 272 $imgFile = $fileBase.'.png'; 273 $debug = $conf['allowdebug']; 274 $debugOutput = ''; 275 276 // create eps file 277 $epsCommand = fullpath($this->getConf('abc2ps'))." $abcFile ".$this->getConf('params4img')." -E -O $fileBase."; 278 passthru($epsCommand." 2>&1"); 279 280 if($debug) { 281 $debugOutput .= "<h3>Debug Info for $abcFile</h3>"; 282 $debugOutput .= "<p>EPS file '".$epsFile."'"; 283 $debugOutput .= file_exists($epsFile) ? " <strong>exists</strong>" : " <strong>does not exist</strong>"; 284 $debugOutput .= ", command used to create it:</p>"; 285 $debugOutput .= "<pre>".$epsCommand."</pre>"; 286 } 287 288 // convert eps to png file 289 $pngCommand = fullpath($conf['im_convert'])." $epsFile $imgFile"; 290 passthru($pngCommand); 291 292 if($debug) { 293 $debugOutput .= "<p>PNG file '".$imgFile."'"; 294 $debugOutput .= file_exists($imgFile) ? " <strong>exists</strong>" : " <strong>does not exist</strong>"; 295 $debugOutput .= ", command used to create it:</p>"; 296 $debugOutput .= "<pre>".$pngCommand."</pre>"; 297 } else { 298 if(file_exists($epsFile)) unlink($epsFile); 299 } 300 301 return $debugOutput; 302 } 303 /** 304 * create ps file 305 */ 306 function _createPsFile($abcFile, $fileBase) { 307 $psFile = $fileBase.'.ps'; 308 $fmt = $this->getConf('fmt'); 309 $addFmt = ($fmt && file_exists($fmt)) ? " -F ".fullpath($fmt) : ""; 310 passthru(fullpath($this->getConf('abc2ps'))." $abcFile $addFmt ".$this->getConf('params4ps')." -O $psFile 2>&1"); 311 } 312 /** 313 * create pdf file 314 */ 315 function _createPdfFile($abcFile, $fileBase) { 316 $psFile = $fileBase.'.ps'; 317 $pdfFile = $fileBase.'.pdf'; 318 passthru(fullpath($this->getConf('ps2pdf'))." $psFile $pdfFile"); 319 if(file_exists($psFile)) unlink($psFile); 320 } 321 /** 322 * create midi file 323 */ 324 function _createMidiFile($abcFile, $fileBase) { 325 $midFile = $fileBase.'.mid'; 326 passthru(fullpath($this->getConf('abc2midi'))." $abcFile -o $midFile"); 327 } 328 /** 329 * get abc2ps version 330 */ 331 function _getAbc2psVersion() { 332 ob_start(); 333 passthru(fullpath($this->getConf('abc2ps'))." -V 2>&1"); 334 $version = ob_get_contents(); 335 $version = explode("\n",$version); 336 ob_end_clean(); 337 return $version[0]; 338 } 339 340 /** 341 * get file and check if it exists 342 */ 343 function _getFile($fileBase, $ext) { 344 $file = $fileBase.$ext; 345 return (file_exists($file)) ? $file : 0; 346 } 347 348 /** 349 * get ID that has to be called from fetch.php 350 */ 351 function _getFileID($file) { 352 global $ID; 353 return (getNS($ID)) ? getNS($ID).":".basename($file) : basename($file); 354 } 355 356 /** 357 * html for internal media 358 */ 359 function _showFile($file) { 360 if (!$file) { 361 return "Error: The file could not be generated."; 362 } 363 364 $mediaNS = $this->getConf('mediaNS').":"; 365 $name = $this->_getFileID($file); 366 $id = $mediaNS.$name; 367 $url = ml($id, array('t' => time())); // add timestamp for cache busting 368 list($ext, $mime) = mimetype($file, false); 369 370 if(substr($mime, 0, 5) == 'image') { 371 $imgSize = getimagesize($file); 372 $imgSize = $imgSize[3]; 373 return '<img src="'.$url.'" '.$imgSize.' alt="" />'; 374 } else { 375 $class = 'mediafile mf_'.preg_replace('/[^_\-a-z0-9]+/i', '_', $ext); 376 return '<a href="'.$url.'" class="'.$class.'">'.$name.'</a>'; 377 } 378 } 379 380 381 /** 382 * html for displaying all files; dependant on displayType 383 */ 384 function _showFiles($fileBase) { 385 $imgFile = $this->_getFile($fileBase, '.png'); 386 $midFile = $this->_getFile($fileBase, '.mid'); 387 $abcFile = $this->_getFile($fileBase, '.abc'); 388 $psFile = $this->_getFile($fileBase, '.ps'); 389 $pdfFile = $this->_getFile($fileBase, '.pdf'); 390 $mediaNS = $this->getConf('mediaNS').":"; 391 $showImg = $this->_showFile($imgFile); 392 393 switch ($this->getConf('displayType')) { 394 // image only (case 0 and default) 395 default: 396 case 0: 397 $display = '<p>'.$showImg.'</p>'.NL; 398 break; 399 400 // image linked to midi 401 case 1: 402 $display = $showImg; 403 if($midFile) { 404 $display = '<a href="'.ml($mediaNS.$this->_getFileID($midFile)).'">'.$display.'</a>'; 405 } 406 $display .= '<p>'.$display.'</p>'.NL; 407 break; 408 409 // image with list of abc, midi, ps/pdf 410 case 2: 411 $display = '<ul>'; 412 // abc file is always there 413 $display .= '<li>'.$this->_showFile($abcFile).'</li>'; 414 // midi file 415 $display .= '<li>'.$this->_showFile($midFile).'</li>'; 416 // display pdf file if there is any, else display ps file 417 if ($this->getConf('ps2pdf') && $pdfFile) { 418 $display .= '<li>'.$this->_showFile($pdfFile).'</li>'; 419 } else { 420 $display .= '<li>'.$this->_showFile($psFile).'</li>'; 421 } 422 $display .= '</ul>'.NL; 423 $display .= '<p>'.$showImg.'</p>'.NL; 424 break; 425 } 426 $display = '<div class="abc">'.NL.$display.'</div>'.NL; 427 return $display; 428 } 429 430 431} 432