1<?php
2/**
3  @author  Todd Augsburger <todd@rollerorgans.com>
4  @date    2007.08.07
5*/
6
7if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
8if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
9require_once(DOKU_PLUGIN.'admin.php');
10require_once(DOKU_INC.'inc/io.php');
11
12/**
13 * All DokuWiki plugins to extend the admin function
14 * need to inherit from this class
15 */
16class admin_plugin_darcspatch extends DokuWiki_Admin_Plugin {
17  var $log=array();
18  /**
19   * return some info
20   */
21  function getInfo(){
22    return array(
23      'author' => 'Todd Augsburger',
24      'email'  => 'todd@rollerorgans.com',
25      'date'   => '2007-08-07',
26      'name'   => 'darcspatch',
27      'desc'   => 'manually apply darcs-style raw patches',
28      'url'    => 'http://wiki.splitbrain.org/plugin:darcspatch',
29    );
30  }
31
32  /**
33   * return sort order for position in admin menu
34   */
35  function getMenuSort() {
36    return 2;
37  }
38
39  /**
40   * handle user request
41   */
42  function handle() {
43  }
44
45  /**
46   * output appropriate html
47   */
48  function html() {
49    global $conf;
50    $this->setupLocale();
51
52    $conf['htmlok']=1;
53
54    $my_page.='====== '.$this->lang['menu']." ======\n";
55
56    switch ($_REQUEST['darcspatch_do']) {
57      case 'apply_patch':
58        if(isset($_REQUEST['darcspatch_url']) && ($_REQUEST['darcspatch_url'] != '') && ($_REQUEST['darcspatch_url'] != 'http://')) {
59          $my_page.=$this->apply_raw_darcs_patch($_REQUEST['darcspatch_url'],DOKU_INC,isset($_REQUEST['test_only']),isset($_REQUEST['reverse']));
60        } else {
61        	$my_page.='  '.$this->lang['msg_errurl']."\n\n";
62        }
63        $my_page.=$this->wiki_admin_page();
64        break;
65      default:
66        $my_page.=$this->wiki_admin_page();
67    }
68
69    echo $this->plugin_render($my_page, $format='xhtml') ;
70  }
71
72  function wiki_admin_page () {
73    global $ID;
74
75    $my_page.='^  '.$this->lang['head']."  ^^^\n";
76    $my_page.='| //'.$this->lang['instructions']."//  |||\n";
77    $my_page.='| <html><form action="'.wl($ID).'" method="post">'.
78      '<input type="hidden" name="id" value="'.$ID.'" />'.
79      '<input type="hidden" name="do" value="'.$_REQUEST['do'].'"/>'.
80      '<input type="hidden" name="page" value="'.$_REQUEST['page'].'"/>'.
81      '<input type="hidden" name="darcspatch_do" value="apply_patch" />'.
82      '<input name="darcspatch_url" size="80" value="'.((isset($_REQUEST['darcspatch_url']) && ($_REQUEST['darcspatch_url'] != '')) ?$_REQUEST['darcspatch_url']:'http://').'" /></html>  | <html>'.
83      '<input type="checkbox" name="test_only" '.(isset($_REQUEST['test_only'])?'checked':'').'/>'.$this->lang['bt_test'].'<br />'.
84      '<input type="checkbox" name="reverse" '.(isset($_REQUEST['reverse'])?'checked':'').'/>'.$this->lang['bt_reverse'].'</html>  |  <html>'.
85      '<input type="submit" value="'.$this->lang['bt_apply'].'" class="edit"/>'.
86      $patch['name']."</form></html> |\n";
87
88    $patchlist = explode("\n",trim($this->getConf('patchlist')));
89    $my_page.='^  '.$this->lang['head_stored']."  ^^^\n";
90    $my_page.='| //'.str_replace('%1',wl($ID,'do=admin,page=config').'#plugin_settings',$this->lang['setup'])."//  |||\n";
91    foreach($patchlist as $patch_url) {
92      $patch_url = trim($patch_url);
93      if($patch_url == '') continue;
94      $my_page.='| '.$patch_url.'  | <html><form action="'.wl($ID).'" method="post">'.
95      '<input type="hidden" name="id" value="'.$ID.'" />'.
96      '<input type="hidden" name="do" value="'.$_REQUEST['do'].'"/>'.
97      '<input type="hidden" name="page" value="'.$_REQUEST['page'].'"/>'.
98      '<input type="hidden" name="darcspatch_do" value="apply_patch" />'.
99      '<input type="hidden" name="darcspatch_url" value="'.$patch_url.'"/>'.
100      '<input type="checkbox" name="test_only" '.(isset($_REQUEST['test_only'])?'checked':'').'/>'.$this->lang['bt_test'].'<br />'.
101      '<input type="checkbox" name="reverse" '.(isset($_REQUEST['reverse'])?'checked':'').'/>'.$this->lang['bt_reverse'].'</html>  |  <html>'.
102      '<input type="submit" value="'.$this->lang['bt_apply'].'" class="edit"/>'.
103      $patch['name']."</form></html> |\n";
104   }
105
106    $my_page.='<html><form action="'.wl($ID).'" method="post">'.
107      '<input type="hidden" name="id" value="'.$ID.'" />'.
108      '<input type="hidden" name="do" value="'.$_REQUEST['do'].'"/>'.
109      '<input type="hidden" name="page" value="'.$_REQUEST['page'].'"/>'.
110      '<input type="submit" value="'.$this->lang['bt_back'].'" class="edit"/>'.
111      '</form></html>';
112    return $my_page;
113  }
114
115  function apply_raw_darcs_patch($patch,$repo,$test_only=false,$reverse=false) {
116    $plus = ($reverse ? '-' : '+');
117    $minus = ($reverse ? '+' : '-');
118    $return_string = '';
119    chdir($repo);
120    if(($contents = file_get_contents($patch)) !== false) {
121      $contents = str_replace("\r",'',$contents);
122      $sections = array();
123      if(preg_match_all('/(^\[.*?\])\s*\{(.*?)^\}/ms',$contents,$sections) > 0) {
124        if($reverse) $sections[1] = array_reverse($sections[1]);
125        for($c=0;$c<count($sections[1]);$c++) {
126          $section = $sections[2][$c];
127          $return_string .= '  '.str_replace("\n","\n  ",$sections[1][$c])."\n";
128          $error = false;
129          $hunks = preg_split('/^hunk\s/ms',$section);
130          array_shift($hunks); // the first will be empty
131          $files = array();
132          if($reverse) $hunks = array_reverse($hunks);
133          foreach($hunks as $hunk) {
134            $lines = explode("\n",$hunk);
135            if($line = trim(array_shift($lines))) {
136              if($pos = strrpos($line,' ')) {
137                $fname = substr($line,0,$pos);
138                $offset = intval(substr($line,$pos+1));
139                $fname = realpath(dirname($fname)).'/'.basename($fname);
140                if (isset($files[$fname])) {
141                  $target_lines = explode("\n",$files[$fname]);
142                  for($i=0;$i<count($target_lines);$i++) {
143                    $target_lines[$i] .= "\n";
144                  }
145                }
146                if(($offset > 0) && (isset($files[$fname]) || (($target_lines = @file($fname)) !== false))) {
147                  foreach($lines as $line) {
148                    if($line[0] == $minus) {
149                      if(($offset <= count($target_lines)) && (strcmp(rtrim(substr($line,1)),rtrim($target_lines[$offset-1])) == 0)) {
150                        array_splice($target_lines,$offset-1,1);
151                      } else {
152                        $return_string .= '  '.$this->lang['msg_errmatch'].': '.$fname.' line '.$offset."\n";
153                        $return_string .= '  '.rtrim(substr($line,1))."\n";
154                        $return_string .= '  '.rtrim($target_lines[$offset-1])."\n";
155                        $error = true;
156                        break;
157                      }
158                    }
159                    elseif($line[0] == $plus) {
160                      if(($offset-1) <= count($target_lines)) {
161                        array_splice($target_lines,$offset-1,0,substr($line,1)."\n");
162                        $offset++;
163                      } else {
164                        $return_string .= '  '.$this->lang['msg_errmatch'].': '.$fname.' line '.$offset."\n";
165                        $error = true;
166                        break;
167                      }
168                    } elseif(trim($line) == '') {
169                    } else {
170                      $error = true;
171                      $return_string .= '  '.$this->lang['msg_errline'].': '.$line."\n";
172                    }
173                    if($error) break;
174                  }
175                  $files[$fname] = implode('',$target_lines);
176                } else {
177                  $error = true;
178                  $return_string .= '  '.$this->lang['msg_erropen'].': '.$fname."\n";
179                }
180              }
181            }
182            if($error) break;
183          }
184          if(!$error) {
185            foreach($files as $fname => $contents) {
186              if($test_only)
187                $return_string .= '  '.$this->lang['msg_test'].': '.$fname."\n";
188              elseif(io_saveFile($fname,$contents))
189                $return_string .= '  '.$this->lang['msg_success'].': '.$fname."\n";
190              else
191                $return_string .= '  '.$this->lang['msg_errwrite'].': '.$fname."\n";
192            }
193          }
194          $return_string .= "\n\n";
195        }
196      }
197    }
198    return $return_string;
199  }
200}
201