1<?php
2/*
3description : Dokuwiki PubMed2020 plugin
4author      : Eric Maeker
5email       : eric.maeker[at]gmail.com
6lastupdate  : 2020-06-05
7license     : Public-Domain
8*/
9
10if(!defined('DOKU_INC')) die();
11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
12require_once(DOKU_PLUGIN.'syntax.php');
13
14class syntax_plugin_pubmed2020 extends DokuWiki_Syntax_Plugin {
15  var $pubmed2020;
16  var $pubmedCache;
17  var $doiUrl = 'http://dx.doi.org/'; //+doi
18  var $pmcUrl = 'https://www.ncbi.nlm.nih.gov/pmc/articles/%s/pdf'; //+pmc
19  var $outputTpl = array(
20      "short" => '%first_author%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url%',
21      "long" => '%authors%. %title%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url%',
22      "long_tt" => '%authors%. %title_tt%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url%',
23      "long_pdf" => '%authors%. %title%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url% %localpdf%',
24      "long_tt_pdf" => '%authors%. %title_tt%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url% %localpdf%',
25      "long_abstract" => '%authors%. %title%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url% %abstract% %abstractFr% %pmid% %doi%',
26      "long_tt_abstract" => '%authors%. %title_tt%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url% %abstract% %abstractFr% %pmid% %doi%',
27      "long_abstract_pdf" => '%authors%. %title%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url% %abstract% %abstractFr% %pmid% %doi% %localpdf%',
28      "long_tt_abstract_pdf" => '%authors%. %title_tt%. %iso%. %pmid% %pmcid% %journal_url% %pmc_url% %abstract% %abstractFr% %pmid% %doi% %localpdf%',
29      "vancouver" => '%vancouver%',
30      "vancouver_links" => '%vancouver% %pmid% %pmcid% %pmc_url%',
31      );
32
33  // Constructor
34  public function __construct(){
35    if (!class_exists('pubmed2020_cache'))
36      @require_once(DOKU_PLUGIN.'pubmed2020/classes/cache.php');
37    if (!class_exists('PubMed2020'))
38      @require_once(DOKU_PLUGIN.'pubmed2020/classes/pubmed2020.php');
39    $this->pubmed2020  = new PubMed2020();
40    $this->pubmedCache = new pubmed2020_cache("pubmed","pubmed","nbib");
41  }
42
43  function getType(){ return 'substition'; }
44  function getSort(){ return 158; }
45
46  /**
47   * Plugin tag format: {{pmid>command:arg}}
48   */
49  function connectTo($mode) {
50    $this->Lexer->addSpecialPattern('\{\{(?:pmid|pmcid)>[^}]*\}\}',
51                                    $mode,'plugin_pubmed2020');
52  }
53
54 /**
55  * Handle the match.
56  */
57  function handle($match, $state, $pos, Doku_Handler $handler){
58    $match = str_replace("{{", "", $match);
59    $match = str_replace("}}", "", $match);
60    return array($state, explode('>', $match, 2));
61  }
62
63  /**
64   * Replace tokens in the string \e $outputString using the array $refs.
65   * \returns Replaced string content.
66   */
67  function replaceTokens($outputString, $refs) {
68      // Empty array -> exit
69      if (count($refs) < 2) { // PMID is always included
70        return sprintf($this->getLang('pubmed_not_found'), $refs["pmid"]);
71      }
72      $outputString = str_replace("%authors%", '<span class="authors">'.implode(', ',$refs["authors"]).'</span>', $outputString);
73      $outputString = str_replace("%first_author%", '<span class="authors">'.$refs["first_author"].'</span>', $outputString);
74      if (count($refs["authorsVancouver"]) > 0)
75        $outputString = str_replace("%authorsVancouver%", '<span class="vancouver authors">'.implode(', ',$refs["authorsVancouver"]).'</span>', $outputString);
76      $outputString = str_replace("%collectif%", '<span class="authors">'.$refs["collectif"].'</span>', $outputString);
77
78      if (!empty($refs["pmid"]))
79          $outputString = str_replace("%pmid%", '<a href="'.$refs["url"].'" class="pmid" target="_blank" title="PMID: '.$refs["pmid"].'">PMID: '.$refs["pmid"].'</a>', $outputString);
80      else
81          $outputString = str_replace("%pmid%", "", $outputString);
82
83      if (!empty($refs["pmcid"]))
84          $outputString = str_replace("%pmcid%", '<a href="'.$refs["pmcurl"].'" class="pmcid" target="_blank" title="PMCID: '.$refs["pmcid"].'">PMCID: '.$refs["pmcid"].'</a>', $outputString);
85      else
86          $outputString = str_replace("%pmcid%", "", $outputString);
87
88      $outputString = str_replace("%type%", '<span class="type">'.$refs["type"].'</span>', $outputString);
89
90      $outputString = str_replace("%title%", '<span class="title">'.$refs["title"].'</span>', $outputString);
91      if ($refs["translated_title"])
92          $outputString = str_replace("%title_tt%", '<span class="title">'.$refs["translated_title"].'</span>', $outputString);
93      else
94          $outputString = str_replace("%title_tt%", '<span class="title">'.$refs["title"].'</span>', $outputString);
95
96      $outputString = str_replace("%lang%", '<span class="lang">'.$refs["lang"].'</span>', $outputString);
97      $outputString = str_replace("%journal_iso%", '<span class="journal_iso">'.$refs["journal_iso"].'</span>', $outputString);
98      $outputString = str_replace("%journal_title%", '<span class="journal_title">'.$refs["journal_title"].'</span>', $outputString);
99      $outputString = str_replace("%iso%", '<span class="iso">'.$refs["iso"].'</span>', $outputString);
100      $outputString = str_replace("%vol%", '<span class="vol">'.$refs["vol"].'</span>', $outputString);
101      $outputString = str_replace("%issue%", '<span class="issue">'.$refs["issue"].'</span>', $outputString);
102      $outputString = str_replace("%year%", '<span class="year">'.$refs["year"].'</span>', $outputString);
103      $outputString = str_replace("%month%", '<span class="month">'.$refs["month"].'</span>', $outputString);
104      $outputString = str_replace("%pages%", '<span class="pages">'.$refs["pages"].'</span>', $outputString);
105      $outputString = str_replace("%abstract%", '<br/><span class="abstract">'.$refs["abstract"].'</span>', $outputString);
106
107      $refs["abstractFr"] = $this->pubmedCache->GetTranslatedAbstract($refs["pmid"]);
108      if (empty($refs["abstractFr"])) {
109        $gg =  "https://translate.google.com/#view=home";
110        $gg .= "&op=translate&sl=auto&tl=fr&text=";
111        $gg .= urlencode($refs["abstract"]);
112        $outputString = str_replace("%abstractFr%", '<a class="abstractFr" href="'.$gg.'" target="_blank">FR</a>', $outputString);
113      } else {
114        // TODO: Create a form to send french abstrat to this class
115        // TODO: Allow to store it in a separate file abstractfr_{pmid}.txt
116          $outputString = str_replace("%abstractFr%", '<span class="abstract">'.$refs["abstractFr"].'</span>', $outputString);
117      }
118
119      if (empty($refs["doi"])) {
120        $outputString = str_replace("%doi%", "", $outputString);
121        $outputString = str_replace("%journal_url%", "", $outputString);
122      } else {
123        $outputString = str_replace("%doi%", '<span class="doi">'.$refs["doi"].'</span>', $outputString);
124        $outputString = str_replace("%journal_url%", '<a href="'.$this->doiUrl.$refs["doi"].'" class="journal_url" target="_blank" title="'.$refs["iso"].'"></a>', $outputString);
125      }
126      if (empty($refs["pmc"]))
127        $outputString = str_replace("%pmc_url%", "", $outputString);
128      else
129        $outputString = str_replace("%pmc_url%", '<a href="'.sprintf($this->pmcUrl, $refs["pmc"]).'" class="pmc_url" target="_blank" title="'.$refs["pmc"].'"></a>', $outputString);
130
131    // Check local PDF using cache
132    $localPdf = $this->pubmedCache->GetLocalPdfPath($refs["pmid"], $refs["doi"]);
133    if (empty($localPdf)) {
134        $outputString = str_replace("%localpdf%", 'No PDF', $outputString);
135    } else {
136        $outputString = str_replace("%localpdf%", ' <a href="'.$localPdf.'" class="localPdf" target="_blank" title="'.$localPdf.'">PDF</a>', $outputString);
137    }
138
139      $outputString = str_replace("%vancouver%",  '<span class="vancouver">'.$refs["vancouver"].'</span>', $outputString);
140
141      // Remove ..
142      $outputString = str_replace(".</span>.",  '.</span>', $outputString);
143      return $outputString;
144  }
145
146  /**
147   * Create output
148   * We have different database to extract data
149   * "pmid" = pubmed
150   * "pmcid" = pmc
151   */
152  function render($mode, Doku_Renderer $renderer, $data) {
153    if ($mode != 'xhtml')
154      return false;
155    // Get the command and its arg(s)
156    list($state, $query) = $data;
157    list($base, $req) = $query;
158    list($cmd, $id) = explode(':', $req, 2);
159    $cmd = strtolower($cmd);
160
161    // If command is empty (in this case, command is the numeric pmids)
162    // Catch default command in plugin's preferences
163    $regex = '/^[0-9,]+$/';
164    if (preg_match($regex, $cmd) === 1) {
165      $id = $cmd;
166      $cmd = $this->getConf('default_command');
167    }
168
169    // Manage the article reference commands in :
170    //   short, long, long_abstract, vancouver,
171    //   or user
172    $this->outputTpl["user"] = $this->getConf('user_defined_output');
173
174    if (array_key_exists($cmd, $this->outputTpl)) {
175      $multipleIds = strpos($id, ",");
176      if ($multipleIds) {
177        $renderer->doc .= "<ul>";
178      }
179      $id = explode(",", $id);
180      foreach ($id as $curId) {
181        $renderer->doc .= $this->getIdOutput($cmd, $base, $curId, $multipleIds);
182      }
183      if ($multipleIds) {
184        $renderer->doc .= "</ul>";
185      }
186    } else {
187      // Manage all other commands
188      switch($cmd) {
189        case 'test': // Ok PubMed2020
190            $this->runTests();
191            return true;
192        case 'raw_medline': // Ok PubMed2020
193          // Check multiple PMIDs (PMIDs can be passed in a coma separated list)
194          $multipleIds = strpos($id, ",");
195          $id = explode(",", $id);
196          foreach ($id as $curId) {
197            if (!is_numeric($curId)){
198              $renderer->doc .= sprintf($this->getLang('pubmed_wrong_format'));
199              return false;
200            }
201            $raw = $this->getMedlineContent($base, $curId);
202            if (empty($raw)) {
203              $renderer->doc .= sprintf($this->getLang('pubmed_not_found'), $curId);
204              return false;
205            }
206            $renderer->doc .= "<pre>".htmlspecialchars($raw, ENT_QUOTES)."</pre>";
207          }  // Foreach PMIDs
208          return true;
209        case 'clear_raw_medline':
210          $this->pubmedCache->clearCache();
211          $renderer->doc .= 'Cleared.';
212          return true;
213        case 'remove_dir':
214          $this->pubmedCache->removeDir();
215          $renderer->doc .= 'Directory cleared.';
216          return true;
217        case 'search':
218          $renderer->doc .='<div class="pubmed">';
219          $renderer->doc .= '<a class="pmid" target="_blank" href="';
220          $renderer->doc .= $this->pubmed2020->getPubmedSearchURL($id);
221          $renderer->doc .= '">'.$id.'</a>';
222          $renderer->doc .='</div>';
223          return true;
224        case 'recreate_cross_refs':
225          $this->pubmedCache->recreateCrossRefFile();
226          return true;
227        case 'full_pdf_list':
228          // Get all PMID from cache
229          $mediaList = array_keys($this->pubmedCache->getAllMediaPaths());
230          // Get all PMID using the local PDF filename
231          $pdfPmids = $this->pubmedCache->GetAllAvailableLocalPdfByPMIDs();
232          // Remove all local PDF PMIDs already in the media list
233          $pdfPmids = array_diff($pdfPmids, $mediaList);
234          // Remove all pdfPmid if present in the mediaList
235          $pdfDois = $this->pubmedCache->GetAllAvailableLocalPdfByDOIs();
236          // Get PMIDs from DOIs
237          $pmids = $this->pubmedCache->PmidFromDoi($pdfDois);
238
239//           $i = 0;
240          foreach($pdfDois as $doi) {
241//             if (++$i == 5)
242//                break;
243            $raw = $this->pubmed2020->getDataFromCtxp($base, "", $doi);
244            if (!empty($raw)) {
245              $this->pubmedCache->saveRawMedlineContent($base, $raw);
246            }
247          }
248
249          // Create a complete list of PMIDs to show
250          //$fullPmids = array_merge($pdfPmids, $pmids, $mediaList);
251          $fullPmids = array_merge($pdfPmids, $pmids);
252          // Check multiple PMIDs (PMIDs can be passed in a coma separated list)
253          $renderer->doc .= "<ul>";
254          foreach($fullPmids as $currentPmid) {
255            $renderer->doc .= $this->getIdOutput("long_abstract", $base, $currentPmid, true);
256          }  // Foreach PMIDs
257          foreach($pdfDois as $doi) {
258            $renderer->doc .=
259                "<a href='".$this->pubmedCache->GetDoiPdfUrl($doi).
260                "' title='".$doi.
261                "'><img src='".$this->pubmedCache->GetDoiPdfThumbnailUrl($doi).
262                "' alt='".$doi.
263                "'/></a>";
264          }  // Foreach PMIDs
265          $renderer->doc .= "</ul>";
266          return true;
267
268        default: // Ok PubMed2020
269          // Command was not found..
270          $renderer->doc .= '<div class="pdb_plugin">';
271          $renderer->doc .= sprintf($this->getLang('plugin_cmd_not_found'),$cmd);
272          $renderer->doc .= '</div>';
273          $renderer->doc .= '<div class="pdb_plugin_text">';
274          $renderer->doc .= $this->getLang('pubmed_available_cmd');
275          $renderer->doc .= '</div>';
276          return true;
277      }
278    }
279  }
280
281
282  /**
283  * Get Medline raw data from cache or get it from NCBI
284  */
285  function getMedlineContent($base, $id) {
286    global $conf;
287    $cached = $this->pubmedCache->getMedlineContent($base, $id);
288    if ($cached !== false) {
289      return $cached;
290    }
291    // Get content from PubMed website
292    $raw = $this->pubmed2020->getDataFromCtxp($base, $id);
293    // Save to cache
294    $this->pubmedCache->saveRawMedlineContent($base, $raw);
295    return $raw;
296  }
297
298  /**
299   * Check PMID format
300   */
301  function checkIdFormat($base, $id) {
302    // Check PMID/PMCID format (numeric, 7 or 8 length)
303    if (!is_numeric($id) || (strlen($id) < 6 || strlen($id) > 8)) {
304      return false;
305    }
306    return true;
307  } // Ok pubmed2020
308
309  /**
310   * Get pubmed string output according to the given unique
311   * ID code passed and the command.
312   * $multipleIds : boolean, use it if the output in inside a multiple ids request
313   */
314  function getIdOutput($cmd, $base, $id, $multipleIds) {
315     if (!$this->checkIdFormat($base, $id)) {
316        return sprintf($this->getLang('pubmed_wrong_format'));
317      }
318
319      // Get article content (from cache or web)
320      $raw = $this->getMedlineContent($base, $id);
321      if (empty($raw)) {
322        return sprintf($this->getLang('pubmed_not_found'), $id);
323        return false;
324      }
325
326      // Get the abstract of the article
327      $refs = $this->pubmed2020->readMedlineContent($raw, $this);
328
329      // Catch updated user output template
330      $outputTpl['user'] = $this->getConf('user_defined_output');
331
332      // Construct reference to article (author.title.rev.year..) according to command
333      $output = "";
334      if ($multipleIds)
335        $output .= "<li>";
336
337      if (empty($this->outputTpl[$cmd]))
338          $cmd = "long_abstract";
339
340      // $cmd contains abstract -> use div instead of span
341      $block = "span";
342      if (strpos($cmd, 'abstract') !== false) {
343        $block = "div";
344      }
345
346      $output .= "<{$block} class=\"pubmed\"><{$block} class=\"{$cmd}\"";
347      if ($multipleIds)
348        $output .= ' style="margin-bottom:1em"';
349      $output .= ">";
350
351      $output .= $this->replaceTokens($this->outputTpl[$cmd], $refs);
352      $output .= "</{$block}></{$block}>";
353      if ($multipleIds)
354        $output .= "</li>";
355
356      return $output;
357  } // Ok pubmed2020
358
359  /**
360   * Only for dev usage
361   */
362  function runTests() {
363    echo "Starting PubMed2020 Tests<br>";
364    // Test CTXP URLs
365    $retrieved = $this->pubmed2020->getDataFromCtxp("pmid", "15924077", "doi");
366
367    // Test MedLine Format Reader
368    $myfile = fopen(DOKU_PLUGIN.'pubmed/tests/PM15924077.nbib', "r") or die("Unable to open file!");
369    $s = fread($myfile, filesize(DOKU_PLUGIN.'pubmed/tests/PM15924077.nbib'));
370    fclose($myfile);
371
372    // Check retrieved files
373    if ($retrieved === $s)
374      echo "File Content: Ok".PHP_EOL;
375    else
376      echo "File Content: NOT Ok".PHP_EOL;
377
378    $this->pubmed2020->readMedlineContent($s, "PMID", $this);
379  }
380
381}
382
383?>