<?php if (!defined('PmWiki')) exit();
/*  Copyright 2004-2024 Patrick R. Michaud (pmichaud@pobox.com)
    This file is part of PITS (PmWiki Issue Tracking System), you can 
    redistribute it and/or modify it under the terms of the GNU General 
    Public License as published by the Free Software Foundation; either 
    version 2 of the License, or (at your option) any later version.  

    This file defines code needed for the PmWiki Issue Tracking System 
    (PITS).  The code is highly specialized towards the needs of
    pmwiki.org, and may not be useful (or even usable) anywhere else,
    but enough people have asked for it that I'm making it available
    in the Cookbook and wishing people the best of luck with it.

    Eventually there will likely be a "supported" version of this
    capability, and the supported version *may* even use the code below :-).
    But until Pm clears a few other items off of his to-do list, he
    may have limited ability to provide detailed support.  (Offers of
    financial support have been known to significantly affect his
    development/support priorities, however. :)

    Issues with PITS can, of course, be registered in PITS on
    pmwiki.org (http://www.pmwiki.org/PITS).

    Okay, enough disclaimers, here's the code already.

    Script maintained by Petko YOTOV www.pmwiki.org/petko
*/
$RecipeInfo['PITS']['Version'] = '20240518';

if ($VersionNum < 2003001)
  Abort("PITS requires pmwiki-2.3.1 or later (currently $Version)");


SDV($PitsCategories, array('', 'Bug', 'Feature' => 'Feature/Change Request',
  'Documentation', 'Cookbook', 'CoreCandidate','PHP Compatibility', 'Other'));


Markup('pitsform','inline','/\\(:pitsform:\\)/',"PitsForm");

Markup('pitslist','directives','/\\(:(pits2?list)\\s*(.*?):\\)/',
    "MarkupPitsList");
    
function MarkupPitsList($m) {
  extract($GLOBALS["MarkupToHTML"]); # get $pagename
  return ($m[1] == 'pitslist')
    ? FmtPitsList('',$pagename,array('q'=>$m[2]))
    : FmtPits2List('',$pagename,array('q'=>$m[2]));
}

Markup('pits','directives',
  '/^(Summary|Created|Status|Category|From|Assigned|Version|OS|Priority):.*/',
  "<:block><div class='pits'>$0</div>");

## PitsForm() generates the form for entering a new issue.  Note that
## once an issue has been created, it's a normal wikipage and is edited
## according to the normal editing code (i.e., there's no form-based
## editing yet).
function PitsForm() {
  extract($GLOBALS["MarkupToHTML"]); # get $pagename
  global $PitsCategories, $Version;
  $ver = str_replace('pmwiki-', '', $Version);
  $out[] = "<form method='post' name='pitsform' action='{\$PageUrl}'>
    <input type='hidden' name='action' value='postpits' />
    <input type='hidden' name='n' value='{\$FullName}' />
    <table>
      <tr><td class='pitsfield'>Author:</td>
        <td><input type='text' name='author' value='\$Author' /></td></tr>
      <tr><td class='pitsfield'>Summary:</td>
        <td><input type='text' name='summary' size='60'/></td></tr>
      <tr><td class='pitsfield'>Category:</td>
        <td><select name='category'>";
  foreach($PitsCategories as $k=>$v) {
    $x = is_string($k) ? $k : $v;
    $out[] = "<option value='$x'>$v</option>";
  }
  $out[] = "</select></td></tr>
      <tr><td class='pitsfield'>Priority:</td>
      <td>Low";
  for($i=1;$i<=5;$i++) 
    $out[] = " <input type='radio' name='priority' value='$i' />";
  $out[] = "High</td></tr>
      <tr><td class='pitsfield'>PmWiki Version:</td>
        <td id='versioncell'><input type='text' name='version' id='versioninput' /></td></tr>
      <tr><td class='pitsfield'>OS/Webserver/<br />PHP Version:</td>
        <td><input type='text' name='os' /></td></tr>
      <tr><td class='pitsfield' valign='top'>Description:</td>
        <td><textarea name='description' cols='60' rows='15'></textarea><br />
            <input type='submit' value='Submit new issue' accesskey='s' />
        </td></tr></table>
      </form>";
  $out[] = <<<EOF
<script type="text/javascript">
(function(){
  var q = document.getElementById('versionq');// in wiki page
  if(!q) return;
  document.getElementById('versioncell').appendChild(q);

  function cmpver() {
    var ver = input.value;
    var clean = ver.replace(/[^\d.,]+/g, '').replace(/[^\d.]+/g, '.');
     q.style.display = (ver == '' || clean == '$ver') ? 'none': 'inline';
  }
  
  var input = document.getElementById('versioninput');
  input.addEventListener('change', cmpver, false);
})();
</script>
EOF;
  return Keep(FmtPageName(implode('',$out),$pagename));
}
SDVA($HTMLStylesFmt, array('PITS'=>"
  .pitsfield { text-align:right; font-weight:bold; }
  table.pits th a { text-decoration:none; }
  table.pits th { background-color:#eeeeee; }
  dl.pits2 { font-size: 85%; color: #888888; clear:both;border-top: 1px solid #dddddd; }
  dl.pits2 dt { float:left; padding-right:0.5em; }
  dl.pits2 dd { margin-left: 8.5em; }
  dl.pits2 dd em { float: right; }
  dl.pits2 dd strong { font-weight: normal; }
   
"));

include_once("$FarmD/scripts/author.php");

if ($action=='postpits') { 
  Lock(2);
  $g = PageVar($pagename, '$Group');
  $glen = strlen($g)+1;
  foreach(ListPages("/^$g\\.\\d/") as $i)
    $issue = max(@$issue, intval(substr($i,$glen), 10));
  $pagename = sprintf("$g.%05d",@$issue+1);
  $action = 'edit';
  $_REQUEST['post'] = 1;
  $CreateTime = PSFT('%Y-%m-%d %H:%M',$Now);
  $EditMessageFmt = "<p class='vspace'>Please review and make any edits 
    to your issue below, then press 'Save'.</p>";
  $_POST['csum'] = $_REQUEST['summary'];
  $vers = str_replace('pmwiki-', '', $_REQUEST['version']);
  $_POST['text'] = "
Summary: {$_REQUEST['summary']}
Created: $CreateTime
Status: Open
Category: ".implode(' ',(array)@$_REQUEST['category'])."
From: $AuthorLink
Assigned: 
Priority: {$_REQUEST['priority']}

Version: {$vers}
OS: {$_REQUEST['os']}

Description:
{$_REQUEST['description']}
~~~~
";
}

## FmtPitsList creates a table of PITS issues according to various
## criteria.  
function FmtPitsList($fmt,$pagename,$opt) {
  global $EnablePITSAuth;
  $opt = array_merge($opt,@$_REQUEST);
  $g = PageVar($pagename, '$Group');
  $pitslist = ListPages("/^$g\\.\\d+$/");
  $out[] = FmtPageName("<table border='1' cellspacing='0' class='pits'>
    <tr><th><a href='{\$PageUrl}?order=-name'>Issue#</a></th>
      <th><a href='{\$PageUrl}?order=created'>Created</a></th>
      <th><a href='{\$PageUrl}?order=category'>Category</a></th>
      <th><a href='{\$PageUrl}?order=version'>Version</a></th>
      <th><a href='{\$PageUrl}?order=-priority'>Priority</a></th>
      <th><a href='{\$PageUrl}?order=status'>Status</a></th>
      <th><a href='{\$PageUrl}?order=summary'>Summary</a></th></tr>", $pagename);
  $terms = preg_split('/((?<!\\S)[-+]?[\'"].*?[\'"](?!\\S)|\\S+)/',
    $opt['q'],-1,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  foreach($terms as $t) {
    if (trim($t)=='') continue;
    if (preg_match('/([^\'":=]*)[:=]([\'"]?)(.*?)\\2$/',$t,$match))
      $opt[strtolower($match[1])] = $match[3]; 
  }
  $n=0; $plist=array();
  foreach($pitslist as $p) {
    $page = IsEnabled($EnablePITSAuth, 0) ?
      RetrieveAuthPage($p, 'read', false, READPAGE_CURRENT):
      ReadPage($p, READPAGE_CURRENT);
    if(!$page) continue;
    preg_match_all("/(^|\n)([A-Za-z][^:]*):([^\n]*)/",$page['text'],$match);
    $fields = array();
    for($i=0;$i<count($match[2]);$i++) 
      $fields[strtolower($match[2][$i])] = 
        PHSC($match[3][$i],ENT_QUOTES);
    foreach(array('created','category','version','priority','status','summary',
        ) as $h) {
      if (!@$opt[$h]) continue;
      foreach(preg_split('/[ ,]/',$opt[$h]) as $t) {
        if (substr($t,0,1)!='-' && substr($t,0,1)!='!') {
          if (strpos(strtolower(@$fields[$h]),strtolower($t))===false) 
            continue 3;
        } else if (strpos(strtolower(@$fields[$h]),
             strtolower(substr($t,1)))!==false) 
          continue 3;
      }
    }
    $plist[$n] = $fields;
    $plist[$n]['name'] = $p;
    $n++;
  }
  $cmp = CreateOrderFunction(@$opt['order'].',-priority,status,category,name');
  usort($plist,$cmp);
  foreach($plist as $p) {
    $out[] = Keep(FmtPageName("<tr><td><a class='pitslink' href='\$PageUrl'>\$Name</a></td>",$p['name']));
    foreach(array('created','category','version','priority','status','summary',
        ) as $h) 
      $out[] = @"<td>{$p[$h]}</td>";
    $out[] = "</tr>";
  }
  $out[] = "</table>";
  return implode('',$out);
}

## This function creates specialized ordering functions needed to
## (more efficiently) perform sorts on arbitrary sets of criteria.
function CreateOrderFunction($order) { 
  $code = '';
  $orders = array();
  foreach(preg_split('/[\\s,|]+/',strtolower($order),-1,PREG_SPLIT_NO_EMPTY) 
      as $o) {
    if (substr($o,0,1)=='-') { $r=-1; $o=substr($o,1); }
    else $r=1;
    if (preg_match('/\\W/',$o)) continue;
    $orders[] = [ $o, $r ];
  }
  $cmpfn = new PITSCOF($orders);
  return array($cmpfn, 'compare');
}

class PITSCOF {
  public $orders;
  function __construct($orders = false) {
    if ($orders) $this->orders = $orders;
  }
  function compare($x, $y) { 
    $orders = $this->orders;
    foreach ($orders as $order) {
      list($o, $r) = $order;
      $c = strcasecmp($x[$o],$y[$o]);
      if ($c != 0) return $r * $c;
    }
  }
}

## FmtPits2List creates a table of PITS issues according to various
## criteria.
function FmtPits2List($fmt,$pagename,$opt) {
  global $AutoSuspendDaysInactive, $Now, $PITSCache, $EnablePITSAuth;
  SDV($AutoSuspendDaysInactive, 180);
  $opt = array_merge($opt,@$_REQUEST);
  $g = PageVar($pagename, '$Group');
  $pitslist = ListPages("/^$g\\.\\d+$/");

  $terms = preg_split('/((?<!\\S)[-+]?[\'"].*?[\'"](?!\\S)|\\S+)/',
    $opt['q'],-1,PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
  foreach($terms as $t) {
    if (trim($t)=='') continue;
    if (preg_match('/([^\'":=]*)[:=]([\'"]?)(.*?)\\2$/',$t,$match))
      $opt[strtolower($match[1])] = $match[3]; 
  }
  $n=0; $plist=$out=array();
  foreach($pitslist as $p) {
    if(!isset($PITSCache[$p]) ){
      $page = IsEnabled($EnablePITSAuth, 0) ?
        RetrieveAuthPage($p, 'read', false, READPAGE_CURRENT):
        ReadPage($p, READPAGE_CURRENT);
      if(!$page) continue;
      preg_match_all("/(^|\n)([A-Za-z][^:]*):([^\n]*)/",$page['text'],$match);
      $fields = array();
      for($i=0;$i<count($match[2]);$i++) 
        $fields[strtolower($match[2][$i])] = 
          PHSC($match[3][$i],ENT_QUOTES);
      $fields['priority2'] = Priority2(@$fields['priority']);
      $fields['time'] = $page['time'];
      if(!preg_match("/\\b(closed|confirmed|todo|in *progress)\\b/i", $fields['status'])
        && $page['time']+ $AutoSuspendDaysInactive*86400 < $Now)
          $fields['status'] .= ' (inactive)';
      $PITSCache[$p] = $fields;
    }
    else $fields = $PITSCache[$p];

    foreach(array('category','priority','status','summary',
        ) as $h) {
      if (!@$opt[$h]) continue;
      $found = $tested = 0;
      foreach(preg_split('/[ ,]/',$opt[$h]) as $t) {
        if ($t[0]!='-' && $t[0]!='!') { # status=closed,suspended OR
          $tested = 1;
          if (strpos(strtolower(strval(@$fields[$h])),strtolower($t))!==false)
            $found = 1;
            continue;
        } else # status=!closed,!suspended
           if (strpos(strtolower(strval(@$fields[$h])),
             strtolower(substr($t,1)))!==false)
              continue 3;
      }
      if($tested && !$found) continue 2;
    }
    $plist[$n] = $fields;
    $plist[$n]['name'] = $p;
    $n++;
  }
  $cmp = CreateOrderFunction(@$opt['order'].',category,-priority2,status,-name');
  usort($plist,$cmp);
  $cnt = 0;
  $out[] = '<div class="pits-filterable">';
  foreach($plist as $p) {
    $cnt ++;
    $out[] = Keep(FmtPageName("<dl class='pits2'><dt><a class='pitslink' href='\$PageUrl' "
     ."title='$cnt. Created : {$p['created']}'>\$Name</a> : {$p['category']}</dt>",$p['name'])
     ."<dd><em title='Status'>{$p['status']}</em> "
     ."<span title='Priorities: {$p['priority']}'>{$p['priority2']}</span> "
     ." <strong>{$p['summary']}</strong></dd></dl>");
    if(@$opt['count'] && $cnt>$opt['count']) break;
  }
  $out[] = '</div>';
  return implode('',$out);
}
# for {(priority2 "{=$:Priority}")}
SDVA($MarkupExpr, array('priority2' => 'Priority2($args[0])'));
function Priority2($x){
  global $PriorityFmt, $PriorityMinVotes;
  SDV($PriorityFmt, '%1.1f (%d)');
  SDV($PriorityMinVotes, 4);
  if(trim(strval(@$x))=='') return sprintf($PriorityFmt, 0, 0);
  preg_match_all("/[0-5x]/i", $x, $m, PREG_PATTERN_ORDER);
  $count = count($m[0]);
  if($count==0) return sprintf($PriorityFmt, 0, 0);
  $sum = array_sum($m[0]);
  $avg = ($count<$PriorityMinVotes)? 0: $sum/$count;
  return sprintf($PriorityFmt, $avg, $count);
}