PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/batchedit/admin.php

http://dwp-forge.googlecode.com/
PHP | 638 lines | 418 code | 115 blank | 105 comment | 73 complexity | 13ebea70f56997d075dbb2d60d82f52b MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. /**
  3. * Plugin BatchEdit
  4. *
  5. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  6. * @author Mykola Ostrovskyy <spambox03@mail.ru>
  7. */
  8. /* Must be run within Dokuwiki */
  9. if(!defined('DOKU_INC')) die();
  10. if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
  11. require_once(DOKU_PLUGIN . 'admin.php');
  12. class admin_plugin_batchedit extends DokuWiki_Admin_Plugin {
  13. var $error;
  14. var $warning;
  15. var $command;
  16. var $namespace;
  17. var $regexp;
  18. var $replacement;
  19. var $summary;
  20. var $minorEdit;
  21. var $pageIndex;
  22. var $match;
  23. var $indent;
  24. function admin_plugin_batchedit() {
  25. $this->error = '';
  26. $this->warning = array();
  27. $this->command = 'hello';
  28. $this->namespace = '';
  29. $this->regexp = '';
  30. $this->replacement = '';
  31. $this->summary = '';
  32. $this->minorEdit = FALSE;
  33. $this->pageIndex = array();
  34. $this->match = array();
  35. $this->indent = 0;
  36. }
  37. /**
  38. * Return some info
  39. */
  40. function getInfo() {
  41. return array(
  42. 'author' => 'Mykola Ostrovskyy',
  43. 'email' => 'spambox03@mail.ru',
  44. 'date' => '2009-02-14',
  45. 'name' => 'BatchEdit',
  46. 'desc' => 'Edit wiki pages using regular expressions.',
  47. 'url' => 'http://www.dokuwiki.org/plugin:batchedit',
  48. );
  49. }
  50. /**
  51. *
  52. */
  53. function getLang($id) {
  54. $string = parent::getLang($id);
  55. if (func_num_args() > 1) {
  56. $search = array();
  57. $replace = array();
  58. for ($i = 1; $i < func_num_args(); $i++) {
  59. $search[$i-1] = '{' . $i . '}';
  60. $replace[$i-1] = func_get_arg($i);
  61. }
  62. $string = str_replace($search, $replace, $string);
  63. }
  64. return $string;
  65. }
  66. /**
  67. * Handle user request
  68. */
  69. function handle() {
  70. if (!isset($_REQUEST['cmd'])) {
  71. // First time - nothing to do
  72. return;
  73. }
  74. try {
  75. $this->_parseRequest();
  76. switch ($this->command) {
  77. case 'preview':
  78. $this->_preview();
  79. break;
  80. case 'apply':
  81. $this->_apply();
  82. break;
  83. }
  84. }
  85. catch (Exception $error) {
  86. $this->error = $this->getLang($error->getMessage());
  87. }
  88. }
  89. /**
  90. * Output appropriate html
  91. */
  92. function html() {
  93. global $ID;
  94. ptln('<!-- batchedit -->');
  95. ptln('<div id="batchedit">');
  96. $this->_printMessages();
  97. ptln('<form action="' . wl($ID) . '" method="post">');
  98. if ($this->error == '') {
  99. switch ($this->command) {
  100. case 'preview':
  101. $this->_printMatches();
  102. break;
  103. case 'apply':
  104. $this->_printMatches();
  105. break;
  106. }
  107. }
  108. $this->_printMainForm();
  109. ptln('</form>');
  110. ptln('</div>');
  111. ptln('<!-- /batchedit -->');
  112. }
  113. /**
  114. *
  115. */
  116. function _parseRequest() {
  117. $this->command = $this->_getCommand();
  118. $this->namespace = $this->_getNamespace();
  119. $this->regexp = $this->_getRegexp();
  120. $this->replacement = $this->_getReplacement();
  121. $this->summary = $this->_getSummary();
  122. $this->minorEdit = isset($_REQUEST['minor']);
  123. }
  124. /**
  125. *
  126. */
  127. function _getCommand() {
  128. if (!is_array($_REQUEST['cmd'])) {
  129. throw new Exception('err_invreq');
  130. }
  131. $command = key($_REQUEST['cmd']);
  132. if (($command != 'preview') && ($command != 'apply')) {
  133. throw new Exception('err_invreq');
  134. }
  135. return $command;
  136. }
  137. /**
  138. *
  139. */
  140. function _getNamespace() {
  141. if (!isset($_REQUEST['namespace'])) {
  142. throw new Exception('err_invreq');
  143. }
  144. $namespace = trim($_REQUEST['namespace']);
  145. if ($namespace != '') {
  146. global $ID;
  147. $namespace = resolve_id(getNS($ID), $namespace . ':');
  148. if ($namespace != '') {
  149. $namespace .= ':';
  150. }
  151. }
  152. return $namespace;
  153. }
  154. /**
  155. *
  156. */
  157. function _getRegexp() {
  158. if (!isset($_REQUEST['regexp'])) {
  159. throw new Exception('err_invreq');
  160. }
  161. $regexp = trim($_REQUEST['regexp']);
  162. if ($regexp == '') {
  163. throw new Exception('err_noregexp');
  164. }
  165. if (preg_match('/^([^\w\\\\]|_).+?\1[imsxeADSUXJu]*$/', $regexp) != 1) {
  166. throw new Exception('err_invregexp');
  167. }
  168. return $regexp;
  169. }
  170. /**
  171. *
  172. */
  173. function _getReplacement() {
  174. if (!isset($_REQUEST['replace'])) {
  175. throw new Exception('err_invreq');
  176. }
  177. return $_REQUEST['replace'];
  178. }
  179. /**
  180. *
  181. */
  182. function _getSummary() {
  183. if (!isset($_REQUEST['summary'])) {
  184. throw new Exception('err_invreq');
  185. }
  186. return $_REQUEST['summary'];
  187. }
  188. /**
  189. *
  190. */
  191. function _preview() {
  192. $this->_loadPageIndex();
  193. $this->_findMatches();
  194. }
  195. /**
  196. *
  197. */
  198. function _loadPageIndex() {
  199. global $conf;
  200. if (@file_exists($conf['indexdir'] . '/page.idx')) {
  201. require_once(DOKU_INC . 'inc/indexer.php');
  202. $this->pageIndex = idx_getIndex('page', '');
  203. if (count($this->pageIndex) == 0) {
  204. throw new Exception('err_emptyidx');
  205. }
  206. }
  207. else {
  208. throw new Exception('err_idxaccess');
  209. }
  210. }
  211. /**
  212. *
  213. */
  214. function _findMatches() {
  215. if ($this->namespace != '') {
  216. $pattern = '/^' . $this->namespace . '/';
  217. }
  218. else {
  219. $pattern = '';
  220. }
  221. foreach ($this->pageIndex as $p) {
  222. $page = trim($p);
  223. if (($pattern == '') || (preg_match($pattern, $page) == 1)) {
  224. $this->_findPageMatches($page);
  225. }
  226. }
  227. if (count($this->match) == 0) {
  228. $this->warning[] = $this->getLang('war_nomatches');
  229. }
  230. }
  231. /**
  232. *
  233. */
  234. function _findPageMatches($page) {
  235. $text = rawWiki($page);
  236. $count = @preg_match_all($this->regexp, $text, $match, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
  237. if ($count === FALSE) {
  238. throw new Exception('err_pregfailed');
  239. }
  240. for ($i = 0; $i < $count; $i++) {
  241. $info['original'] = $match[$i][0][0];
  242. $info['replaced'] = preg_replace($this->regexp, $this->replacement, $info['original']);
  243. $info['offest'] = $match[$i][0][1];
  244. $info['before'] = $this->_getBeforeContext($text, $match[$i]);
  245. $info['after'] = $this->_getAfterContext($text, $match[$i]);
  246. $info['apply'] = FALSE;
  247. $this->match[$page][$i] = $info;
  248. }
  249. }
  250. /**
  251. *
  252. */
  253. function _getBeforeContext($text, $match) {
  254. $length = 50;
  255. $offset = $match[0][1] - $length;
  256. if ($offset < 0) {
  257. $length += $offset;
  258. $offset = 0;
  259. }
  260. $text = substr($text, $offset, $length);
  261. $count = preg_match_all('/\n/', $text, $match, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
  262. if ($count > 3) {
  263. $text = substr($text, $match[$count - 4][0][1] + 1);
  264. }
  265. return $text;
  266. }
  267. /**
  268. *
  269. */
  270. function _getAfterContext($text, $match) {
  271. $offset = $match[0][1] + strlen($match[0][0]);
  272. $text = substr($text, $offset, 50);
  273. $count = preg_match_all('/\n/', $text, $match, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
  274. if ($count > 3) {
  275. $text = substr($text, 0, $match[3][0][1]);
  276. }
  277. return $text;
  278. }
  279. /**
  280. *
  281. */
  282. function _apply() {
  283. $this->_loadPageIndex();
  284. $this->_findMatches();
  285. if (isset($_REQUEST['apply'])) {
  286. if (!is_array($_REQUEST['apply'])) {
  287. throw new Exception('err_invcmd');
  288. }
  289. $this->_markRequested(array_keys($_REQUEST['apply']));
  290. $this->_applyMatches();
  291. }
  292. }
  293. /**
  294. *
  295. */
  296. function _markRequested($request) {
  297. foreach ($request as $r) {
  298. list($page, $offset) = explode('#', $r);
  299. if (array_key_exists($page, $this->match)) {
  300. $count = count($this->match[$page]);
  301. for ($i = 0; $i < $count; $i++) {
  302. if ($this->match[$page][$i]['offest'] == $offset) {
  303. $this->match[$page][$i]['apply'] = TRUE;
  304. break;
  305. }
  306. }
  307. }
  308. }
  309. }
  310. /**
  311. *
  312. */
  313. function _applyMatches() {
  314. $page = array_keys($this->match);
  315. foreach ($page as $p) {
  316. if ($this->_requiresChanges($p)) {
  317. if ($this->_isEditAllowed($p)) {
  318. $this->_editPage($p);
  319. }
  320. else {
  321. $this->_unmarkDenied($p);
  322. }
  323. }
  324. }
  325. }
  326. /**
  327. *
  328. */
  329. function _requiresChanges($page) {
  330. $result = FALSE;
  331. foreach ($this->match[$page] as $info) {
  332. if ($info['apply']) {
  333. $result = TRUE;
  334. break;
  335. }
  336. }
  337. return $result;
  338. }
  339. /**
  340. *
  341. */
  342. function _isEditAllowed($page) {
  343. $allowed = TRUE;
  344. if (auth_quickaclcheck($page) < AUTH_EDIT) {
  345. $this->warning[] = $this->getLang('war_norights', $page);
  346. $allowed = FALSE;
  347. }
  348. if ($allowed) {
  349. $lockedBy = checklock($page);
  350. if ($lockedBy != FALSE) {
  351. $this->warning[] = $this->getLang('war_pagelock', $page, $lockedBy);
  352. $allowed = FALSE;
  353. }
  354. }
  355. return $allowed;
  356. }
  357. /**
  358. *
  359. */
  360. function _editPage($page) {
  361. lock($page);
  362. $text = rawWiki($page);
  363. $offset = 0;
  364. foreach ($this->match[$page] as $info) {
  365. if ($info['apply']) {
  366. $originalLength = strlen($info['original']);
  367. $before = substr($text, 0, $info['offest'] + $offset);
  368. $after = substr($text, $info['offest'] + $offset + $originalLength);
  369. $text = $before . $info['replaced'] . $after;
  370. $offset += strlen($info['replaced']) - $originalLength;
  371. }
  372. }
  373. saveWikiText($page, $text, $this->summary, $this->minorEdit);
  374. unlock($page);
  375. }
  376. /**
  377. *
  378. */
  379. function _unmarkDenied($page) {
  380. $count = count($this->match[$page]);
  381. for ($i = 0; $i < $count; $i++) {
  382. $this->match[$page][$i]['apply'] = FALSE;
  383. }
  384. }
  385. /**
  386. *
  387. */
  388. function _printMatches() {
  389. $view = $this->getLang('lnk_view');
  390. $edit = $this->getLang('lnk_edit');
  391. foreach ($this->match as $page => $match) {
  392. foreach ($match as $info) {
  393. $original = $this->_prepareText($info['original'], 'search_hit');
  394. $replaced = $this->_prepareText($info['replaced'], $info['apply'] ? 'applied' : 'search_hit');
  395. $before = $this->_prepareText($info['before']);
  396. $after = $this->_prepareText($info['after']);
  397. $link = wl($page);
  398. $id = $page . '#' . $info['offest'];
  399. $this->_ptln('<div class="file">', +2);
  400. if (!$info['apply']) {
  401. $this->_ptln('<input type="checkbox" id="' . $id . '" name="apply[' . $id . ']" value="on" />');
  402. }
  403. $this->_ptln('<label for="' . $id . '">' . $id . '</label>');
  404. $this->_ptln('<a class="view" href="' . $link . '" title="' . $view . '"></a>');
  405. $this->_ptln('<a class="edit" href="' . $link . '&do=edit" title="' . $edit . '"></a>');
  406. $this->_ptln('<table><tr>', +2);
  407. $this->_ptln('<td class="text">' . $before . $original . $after . '</td>');
  408. $this->_ptln('<td class="arrow"></td>');
  409. $this->_ptln('<td class="text">' . $before . $replaced . $after . '</td>');
  410. $this->_ptln('</tr></table>', -2);
  411. $this->_ptln('</div>', -2);
  412. ptln('');
  413. }
  414. }
  415. }
  416. /**
  417. * Prepare wiki text to be displayed as html
  418. */
  419. function _prepareText($text, $highlight = '') {
  420. $html = htmlspecialchars($text);
  421. $html = str_replace( "\n", '<br />', $html);
  422. if ($highlight != '') {
  423. $html = '<span class="' . $highlight . '">' . $html . '</span>';
  424. }
  425. return $html;
  426. }
  427. /**
  428. *
  429. */
  430. function _printMessages() {
  431. if ((count($this->warning) > 0) || ($this->error != '')) {
  432. $this->_ptln('<div id="messages">', +2);
  433. $this->_printWarnings();
  434. if ($this->error != '') {
  435. $this->_printError();
  436. }
  437. $this->_ptln('</div>', -2);
  438. ptln('');
  439. }
  440. }
  441. /**
  442. *
  443. */
  444. function _printWarnings() {
  445. foreach($this->warning as $w) {
  446. $this->_ptln('<div class="notify">', +2);
  447. $this->_ptln('<b>Warning:</b> ' . $w);
  448. $this->_ptln('</div>', -2);
  449. }
  450. }
  451. /**
  452. *
  453. */
  454. function _printError() {
  455. $this->_ptln('<div class="error">', +2);
  456. $this->_ptln('<b>Error:</b> ' . $this->error);
  457. $this->_ptln('</div>', -2);
  458. }
  459. /**
  460. *
  461. */
  462. function _printMainForm() {
  463. $this->_ptln('<div id="mainform">', +2);
  464. // Output hidden values to ensure dokuwiki will return back to this plugin
  465. $this->_ptln('<input type="hidden" name="do" value="admin" />');
  466. $this->_ptln('<input type="hidden" name="page" value="' . $this->getPluginName() . '" />');
  467. $this->_ptln('<table>', +2);
  468. $this->_printFormEdit('lbl_ns', 'namespace');
  469. $this->_printFormEdit('lbl_regexp', 'regexp');
  470. $this->_printFormEdit('lbl_replace', 'replace');
  471. $this->_printFormEdit('lbl_summary', 'summary');
  472. $this->_ptln('</table>', -2);
  473. $this->_ptln('<input type="submit" class="button" name="cmd[preview]" value="' . $this->getLang('btn_preview') . '" />');
  474. $this->_ptln('<input type="submit" class="button" name="cmd[apply]" value="' . $this->getLang('btn_apply') . '" />');
  475. $this->_ptln('</div>', -2);
  476. }
  477. /**
  478. *
  479. */
  480. function _printFormEdit($title, $name) {
  481. $value = '';
  482. if (isset($_REQUEST[$name])) {
  483. $value = $_REQUEST[$name];
  484. }
  485. $this->_ptln( '<tr>', +2);
  486. $this->_ptln( '<td class="title"><nobr><b>' . $this->getLang($title) . ':</b></nobr></td>');
  487. $this->_ptln( '<td class="edit"><input type="text" class="edit" name="' . $name . '" value="' . $value . '" /></td>');
  488. switch ($name) {
  489. case 'summary':
  490. $this->_ptln( '<td style="padding-left: 2em">', +2);
  491. $this->_printCheckBox('lbl_minor', 'minor');
  492. $this->_ptln( '</td>', -2);
  493. break;
  494. default:
  495. $this->_ptln( '<td></td>');
  496. break;
  497. }
  498. $this->_ptln( '</tr>', -2);
  499. }
  500. /**
  501. *
  502. */
  503. function _printCheckBox($title, $name) {
  504. $html = '<input type="checkbox" id="' . $name . '" name="' . $name . '" value="on"';
  505. if (isset($_REQUEST[$name])) {
  506. $html .= ' checked="checked"';
  507. }
  508. $this->_ptln($html . ' />');
  509. $this->_ptln('<label for="' . $name . '">' . $this->getLang($title) . '</label>');
  510. }
  511. /**
  512. *
  513. */
  514. function _ptln($string, $indentDelta = 0) {
  515. if ($indentDelta < 0) {
  516. $this->indent += $indentDelta;
  517. }
  518. ptln($string, $this->indent);
  519. if ($indentDelta > 0) {
  520. $this->indent += $indentDelta;
  521. }
  522. }
  523. }