PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/forum/includes/mod_parser.php

https://github.com/GreyTeardrop/socionicasys-forum
PHP | 833 lines | 593 code | 114 blank | 126 comment | 90 complexity | 7d5e9991dbc1545f40779018a4438395 MD5 | raw file
Possible License(s): AGPL-1.0, LGPL-3.0, MPL-2.0-no-copyleft-exception
  1. <?php
  2. /**
  3. *
  4. * @package automod
  5. * @version $Id: mod_parser.php 242 2010-04-29 00:56:35Z jelly_doughnut $
  6. * @copyright (c) 2008 phpBB Group
  7. * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
  8. *
  9. */
  10. /**
  11. */
  12. if (!defined('IN_PHPBB'))
  13. {
  14. exit;
  15. }
  16. /**
  17. * MOD Parser class
  18. * Basic wrapper to run individual parser functions
  19. * Also contains some parsing functions that are global (i.e. needed for all parsers)
  20. * @package automod
  21. *
  22. * Each parser requires the following functions:
  23. * ~ set_file($path_to_mod_file)
  24. * ~ a means of setting the data to be acted upon
  25. * ~ get_details()
  26. * ~ returns an array of information about the MOD
  27. * ~ get_actions()
  28. * ~ returns an array of the MODs actions
  29. * ~ get_modx_version
  30. * ~ returns the MODX version of the MOD being looked at
  31. *
  32. */
  33. class parser
  34. {
  35. var $parser;
  36. /**
  37. * constructor, sets type of parser
  38. */
  39. function parser($ext)
  40. {
  41. switch ($ext)
  42. {
  43. case 'xml':
  44. default:
  45. $this->parser = new parser_xml();
  46. break;
  47. }
  48. }
  49. function set_file($file)
  50. {
  51. $this->parser->set_file($file);
  52. }
  53. function get_details()
  54. {
  55. return $this->parser->get_details();
  56. }
  57. function get_actions()
  58. {
  59. return $this->parser->get_actions();
  60. }
  61. function get_modx_version()
  62. {
  63. if (!$this->parser->modx_version)
  64. {
  65. $this->get_details();
  66. }
  67. return $this->parser->modx_version;
  68. }
  69. /**
  70. * Returns the needed sql query to reverse the actions taken by the given query
  71. * @todo: Add more
  72. */
  73. function reverse_query($orig_query)
  74. {
  75. if (preg_match('#ALTER TABLE\s([a-z_]+)\sADD(COLUMN|)\s([a-z_]+)#i', $orig_query, $matches))
  76. {
  77. return "ALTER TABLE {$matches[1]} DROP COLUMN {$matches[3]};";
  78. }
  79. else if (preg_match('#CREATE TABLE\s([a-z_])+#i', $orig_query, $matches))
  80. {
  81. return "DROP TABLE {$matches[1]};";
  82. }
  83. return false;
  84. }
  85. /**
  86. * Parse sql
  87. *
  88. * @param array $sql_query
  89. */
  90. function parse_sql(&$sql_query)
  91. {
  92. global $dbms, $table_prefix;
  93. if (!function_exists('get_available_dbms'))
  94. {
  95. global $phpbb_root_path, $phpEx;
  96. include($phpbb_root_path . 'includes/functions_install.' . $phpEx);
  97. }
  98. static $available_dbms;
  99. if (!isset($available_dbms))
  100. {
  101. $available_dbms = get_available_dbms($dbms);
  102. }
  103. $remove_remarks = $available_dbms[$dbms]['COMMENTS'];
  104. $delimiter = $available_dbms[$dbms]['DELIM'];
  105. if (sizeof($sql_query) == 1)
  106. {
  107. // do some splitting here
  108. $sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query);
  109. $remove_remarks($sql_query[0]);
  110. $sql_query = split_sql_file($sql_query[0], $delimiter);
  111. }
  112. else
  113. {
  114. $query_count = sizeof($sql_query);
  115. for ($i = 0; $i < $query_count; $i++)
  116. {
  117. $sql_query[$i] = preg_replace('#phpbb_#i', $table_prefix, $sql_query[$i]);
  118. $remove_remarks($sql_query[$i]);
  119. }
  120. }
  121. //return $sql_query;
  122. }
  123. /**
  124. * Returns the edits array, but now filled with edits to reverse the given array
  125. * @todo: Add more
  126. */
  127. function reverse_edits($actions)
  128. {
  129. $reverse_edits = array();
  130. foreach ($actions['EDITS'] as $file => $edit_ary)
  131. {
  132. foreach ($edit_ary as $edit_id => $edit)
  133. {
  134. foreach ($edit as $find => $action_ary)
  135. {
  136. foreach ($action_ary as $type => $command)
  137. {
  138. // it is possible for a single edit in the install process
  139. // to become more than one in the uninstall process
  140. while (isset($reverse_edits['EDITS'][$file][$edit_id]))
  141. {
  142. $edit_id++;
  143. }
  144. switch (strtoupper($type))
  145. {
  146. // for before and after adds, we use the find as a tool for more precise finds
  147. // this isn't perfect, but it seems better than having
  148. // finds of only a couple characters, like "/*"
  149. case 'AFTER ADD':
  150. $total_find = rtrim($find, "\n") . "\n" . trim($command, "\n");
  151. $reverse_edits['EDITS'][$file][$edit_id][$total_find]['replace with'] = $find;
  152. break;
  153. case 'BEFORE ADD':
  154. $total_find = rtrim($command, "\n") . "\n" . trim($find, "\n");
  155. // replace with the find
  156. $reverse_edits['EDITS'][$file][$edit_id][$total_find]['replace with'] = $find;
  157. break;
  158. case 'REPLACE WITH':
  159. case 'REPLACE, WITH':
  160. case 'REPLACE-WITH':
  161. case 'REPLACE':
  162. // replace $command (new code) with $find (original code)
  163. $reverse_edits['EDITS'][$file][$edit_id][$command]['replace with'] = $find;
  164. break;
  165. case 'IN-LINE-EDIT':
  166. $action_id = 0;
  167. // build the reverse just like the normal action
  168. foreach ($command as $inline_find => $inline_action_ary)
  169. {
  170. foreach ($inline_action_ary as $inline_action => $inline_command)
  171. {
  172. $inline_command = $inline_command[0];
  173. switch (strtoupper($inline_action))
  174. {
  175. case 'IN-LINE-AFTER-ADD':
  176. case 'IN-LINE-BEFORE-ADD':
  177. // Replace with a blank string
  178. $reverse_edits['EDITS'][$file][$edit_id][$find]['in-line-edit'][$action_id][$inline_command]['in-line-replace'][] = '';
  179. break;
  180. case 'IN-LINE-REPLACE':
  181. // replace with the inline find
  182. $reverse_edits['EDITS'][$file][$edit_id][$find]['in-line-edit'][$action_id][$inline_command][$inline_action][] = $inline_find;
  183. break;
  184. default:
  185. // For the moment, we do nothing. What about increment?
  186. break;
  187. }
  188. $action_id++;
  189. }
  190. }
  191. break;
  192. default:
  193. // again, increment
  194. break;
  195. }
  196. }
  197. }
  198. }
  199. }
  200. if (empty($actions['SQL']))
  201. {
  202. return $reverse_edits;
  203. }
  204. if (sizeof($actions['SQL']) == 1)
  205. {
  206. $actions['SQL'] = explode("\n", $actions['SQL'][0]);
  207. }
  208. foreach ($actions['SQL'] as $query)
  209. {
  210. $reverse_edits['SQL'][] = parser::reverse_query($query);
  211. }
  212. return $reverse_edits;
  213. }
  214. }
  215. /**
  216. * XML parser
  217. * @package automod
  218. */
  219. class parser_xml
  220. {
  221. var $data;
  222. var $file;
  223. var $modx_version;
  224. /**
  225. * set data to read from
  226. */
  227. function set_file($file)
  228. {
  229. // Shouldn't ever happen since the master class reads file names from
  230. // the file system and lists them
  231. if (!file_exists($file))
  232. {
  233. trigger_error('Cannot locate File: ' . $file);
  234. }
  235. $this->file = $file;
  236. $this->data = trim(@file_get_contents($file));
  237. $this->data = str_replace(array("\r\n", "\r"), "\n", $this->data);
  238. $XML = new xml_array();
  239. $this->data = $XML->parse($this->file, $this->data);
  240. return;
  241. }
  242. /**
  243. * return array of the basic MOD details
  244. */
  245. function get_details()
  246. {
  247. global $user;
  248. if (empty($this->data))
  249. {
  250. $this->set_file($this->file);
  251. }
  252. $header = array(
  253. 'MOD-VERSION' => array(0 => array('children' => array())),
  254. 'INSTALLATION' => array(0 => array('children' => array('TARGET-VERSION' => array(0 => array('data' => ''))))),
  255. 'AUTHOR-GROUP' => array(0 => array('children' => array('AUTHOR' => array()))),
  256. 'HISTORY' => array(0 => array('children' => array('ENTRY' => array()))),
  257. );
  258. $version = $phpbb_version = '';
  259. $header = $this->data[0]['children']['HEADER'][0]['children'];
  260. // get MOD version information
  261. // This is also our first opportunity to differentiate MODX 1.0.x from
  262. // MODX 1.2.0.
  263. if (isset($header['MOD-VERSION'][0]['children']))
  264. {
  265. $this->modx_version = 1.0;
  266. $version_info = $header['MOD-VERSION'][0]['children'];
  267. $version = (isset($version_info['MAJOR'][0]['data'])) ? trim($version_info['MAJOR'][0]['data']) : 0;
  268. $version .= '.' . ((isset($version_info['MINOR'][0]['data'])) ? trim($version_info['MINOR'][0]['data']) : 0);
  269. $version .= '.' . ((isset($version_info['REVISION'][0]['data'])) ? trim($version_info['REVISION'][0]['data']) : 0);
  270. $version .= (isset($version_info['RELEASE'][0]['data'])) ? trim($version_info['RELEASE'][0]['data']) : '';
  271. }
  272. else
  273. {
  274. $this->modx_version = 1.2;
  275. $version = trim($header['MOD-VERSION'][0]['data']);
  276. }
  277. // get phpBB version recommendation
  278. switch ($this->modx_version)
  279. {
  280. case 1.0:
  281. if (isset($header['INSTALLATION'][0]['children']['TARGET-VERSION'][0]['children']))
  282. {
  283. $version_info = $header['INSTALLATION'][0]['children']['TARGET-VERSION'][0]['children'];
  284. $phpbb_version = (isset($version_info['MAJOR'][0]['data'])) ? trim($version_info['MAJOR'][0]['data']) : 0;
  285. $phpbb_version .= '.' . ((isset($version_info['MINOR'][0]['data'])) ? trim($version_info['MINOR'][0]['data']) : 0);
  286. $phpbb_version .= '.' . ((isset($version_info['REVISION'][0]['data'])) ? trim($version_info['REVISION'][0]['data']) : 0);
  287. $phpbb_version .= (isset($version_info['RELEASE'][0]['data'])) ? trim($version_info['RELEASE'][0]['data']) : '';
  288. }
  289. break;
  290. case 1.2:
  291. default:
  292. $phpbb_version = (isset($header['INSTALLATION'][0]['children']['TARGET-VERSION'][0]['data'])) ? $header['INSTALLATION'][0]['children']['TARGET-VERSION'][0]['data'] : 0;
  293. break;
  294. }
  295. $author_info = $header['AUTHOR-GROUP'][0]['children']['AUTHOR'];
  296. $author_details = array();
  297. for ($i = 0; $i < sizeof($author_info); $i++)
  298. {
  299. $author_details[] = array(
  300. 'AUTHOR_NAME' => isset($author_info[$i]['children']['USERNAME'][0]['data']) ? trim($author_info[$i]['children']['USERNAME'][0]['data']) : '',
  301. 'AUTHOR_EMAIL' => isset($author_info[$i]['children']['EMAIL'][0]['data']) ? trim($author_info[$i]['children']['EMAIL'][0]['data']) : '',
  302. 'AUTHOR_REALNAME' => isset($author_info[$i]['children']['REALNAME'][0]['data']) ? trim($author_info[$i]['children']['REALNAME'][0]['data']) : '',
  303. 'AUTHOR_WEBSITE' => isset($author_info[$i]['children']['HOMEPAGE'][0]['data']) ? trim($author_info[$i]['children']['HOMEPAGE'][0]['data']) : '',
  304. );
  305. }
  306. // history
  307. $history_info = (!empty($header['HISTORY'][0]['children']['ENTRY'])) ? $header['HISTORY'][0]['children']['ENTRY'] : array();
  308. $history_size = sizeof($history_info);
  309. $mod_history = array();
  310. for ($i = 0; $i < $history_size; $i++)
  311. {
  312. $changes = array();
  313. $entry = $history_info[$i]['children'];
  314. $changelog = isset($entry['CHANGELOG']) ? $entry['CHANGELOG'] : array();
  315. $changelog_size = sizeof($changelog);
  316. $changelog_id = 0;
  317. for ($j = 0; $j < $changelog_size; $j++)
  318. {
  319. // Ignore changelogs in foreign languages except in the case that there is no
  320. // match for the current user's language
  321. // TODO: Look at modifying localise_tags() for use here.
  322. if (match_language($user->data['user_lang'], $changelog[$j]['attrs']['LANG']))
  323. {
  324. $changelog_id = $j;
  325. }
  326. }
  327. $change_count = isset($changelog[$changelog_id]['children']['CHANGE']) ? sizeof($changelog[$changelog_id]['children']['CHANGE']) : 0;
  328. for ($j = 0; $j < $change_count; $j++)
  329. {
  330. $changes[] = $changelog[$changelog_id]['children']['CHANGE'][$j]['data'];
  331. }
  332. switch ($this->modx_version)
  333. {
  334. case 1.0:
  335. $changelog_version_ary = (isset($entry['REV-VERSION'][0]['children'])) ? $entry['REV-VERSION'][0]['children'] : array();
  336. $changelog_version = (isset($changelog_version_ary['MAJOR'][0]['data'])) ? trim($changelog_version_ary['MAJOR'][0]['data']) : 0;
  337. $changelog_version .= '.' . ((isset($changelog_version_ary['MINOR'][0]['data'])) ? trim($changelog_version_ary['MINOR'][0]['data']) : 0);
  338. $changelog_version .= '.' . ((isset($changelog_version_ary['REVISION'][0]['data'])) ? trim($changelog_version_ary['REVISION'][0]['data']) : 0);
  339. $changelog_version .= (isset($changelog_version_ary['RELEASE'][0]['data'])) ? trim($changelog_version_ary['RELEASE'][0]['data']) : '';
  340. break;
  341. case 1.2:
  342. default:
  343. $changelog_version = (isset($entry['REV-VERSION'][0]['data'])) ? $entry['REV-VERSION'][0]['data'] : '0.0.0';
  344. break;
  345. }
  346. $mod_history[] = array(
  347. 'DATE' => $entry['DATE'][0]['data'],
  348. 'VERSION' => $changelog_version,
  349. 'CHANGES' => $changes,
  350. );
  351. }
  352. $children = array();
  353. // Parse links
  354. if ($this->modx_version == 1.2)
  355. {
  356. $link_group = (isset($header['LINK-GROUP'][0]['children'])) ? $header['LINK-GROUP'][0]['children'] : array();
  357. if (isset($link_group['LINK']))
  358. {
  359. for ($i = 0, $size = sizeof($link_group['LINK']); $i <= $size; $i++)
  360. {
  361. // do some stuff with attrs
  362. // commented out due to a possible PHP bug. When using this,
  363. // sizeof($link_group) changed each time ...
  364. // $attrs = &$link_group[$i]['attrs'];
  365. if (!isset($link_group['LINK'][$i]))
  366. {
  367. continue;
  368. }
  369. if ($link_group['LINK'][$i]['attrs']['TYPE'] == 'text')
  370. {
  371. continue;
  372. }
  373. $children[$link_group['LINK'][$i]['attrs']['TYPE']][] = array(
  374. 'href' => $link_group['LINK'][$i]['attrs']['HREF'],
  375. 'realname' => isset($link_group['LINK'][$i]['attrs']['REALNAME']) ? $link_group['LINK'][$i]['attrs']['REALNAME'] : core_basename($link_group['LINK'][$i]['attrs']['HREF']),
  376. 'title' => localise_tags($link_group, 'LINK', $i),
  377. );
  378. }
  379. }
  380. }
  381. // try not to hardcode schema?
  382. $details = array(
  383. 'MOD_PATH' => $this->file,
  384. 'MOD_NAME' => localise_tags($header, 'TITLE'),
  385. 'MOD_DESCRIPTION' => nl2br(localise_tags($header, 'DESCRIPTION')),
  386. 'MOD_VERSION' => htmlspecialchars(trim($version)),
  387. // 'MOD_DEPENDENCIES' => (isset($header['TITLE'][0]['data'])) ? htmlspecialchars(trim($header['TITLE'][0]['data'])) : '',
  388. 'AUTHOR_DETAILS' => $author_details,
  389. 'AUTHOR_NOTES' => nl2br(localise_tags($header, 'AUTHOR-NOTES')),
  390. 'MOD_HISTORY' => $mod_history,
  391. 'PHPBB_VERSION' => $phpbb_version,
  392. 'CHILDREN' => $children,
  393. );
  394. return $details;
  395. }
  396. /**
  397. * returns complex array containing all mod actions
  398. */
  399. function get_actions()
  400. {
  401. global $db, $user;
  402. $actions = array();
  403. $xml_actions = $this->data[0]['children']['ACTION-GROUP'][0]['children'];
  404. // sql
  405. $actions['SQL'] = array();
  406. $sql_info = (!empty($xml_actions['SQL'])) ? $xml_actions['SQL'] : array();
  407. $match_dbms = array();
  408. switch ($db->sql_layer)
  409. {
  410. case 'firebird':
  411. case 'oracle':
  412. case 'postgres':
  413. case 'sqlite':
  414. case 'mssql':
  415. case 'db2':
  416. $match_dbms = array($db->sql_layer);
  417. break;
  418. case 'mssql_odbc':
  419. $match_dbms = array('mssql');
  420. break;
  421. // and now for the MySQL fun
  422. // This will generate an array of things we can probably use, but
  423. // will not have any priority
  424. case 'mysqli':
  425. $match_dbms = array('mysql_41', 'mysqli', 'mysql');
  426. break;
  427. case 'mysql4':
  428. case 'mysql':
  429. if (version_compare($db->sql_server_info(true), '4.1.3', '>='))
  430. {
  431. $match_dbms = array('mysql_41', 'mysql4', 'mysql', 'mysqli');
  432. }
  433. else if (version_compare($db->sql_server_info(true), '4.0.0', '>='))
  434. {
  435. $match_dbms = array('mysql_40', 'mysql4', 'mysql', 'mysqli');
  436. }
  437. else
  438. {
  439. $match_dbms = array('mysql');
  440. }
  441. break;
  442. // Should never happen
  443. default:
  444. break;
  445. }
  446. for ($i = 0; $i < sizeof($sql_info); $i++)
  447. {
  448. if ($this->modx_version == 1.0)
  449. {
  450. $actions['SQL'][] = (!empty($sql_info[$i]['data'])) ? trim($sql_info[$i]['data']) : '';
  451. }
  452. else if ($this->modx_version == 1.2)
  453. {
  454. // Make a slightly shorter name.
  455. $xml_dbms = &$sql_info[$i]['attrs']['DBMS'];
  456. if (!isset($sql_info[$i]['attrs']['DBMS']) || in_array($xml_dbms, $match_dbms))
  457. {
  458. $actions['SQL'][] = (!empty($sql_info[$i]['data'])) ? trim($sql_info[$i]['data']) : '';
  459. }
  460. else
  461. {
  462. // NOTE: skipped SQL is not currently useful
  463. $sql_skipped = true;
  464. }
  465. }
  466. }
  467. // new files
  468. $new_files_info = (!empty($xml_actions['COPY'])) ? $xml_actions['COPY'] : array();
  469. for ($i = 0; $i < sizeof($new_files_info); $i++)
  470. {
  471. $new_files = $new_files_info[$i]['children']['FILE'];
  472. for ($j = 0; $j < sizeof($new_files); $j++)
  473. {
  474. $from = str_replace('\\', '/', $new_files[$j]['attrs']['FROM']);
  475. $to = str_replace('\\', '/', $new_files[$j]['attrs']['TO']);
  476. $actions['NEW_FILES'][$from] = $to;
  477. }
  478. }
  479. $delete_files_info = (!empty($xml_actions['DELETE'])) ? $xml_actions['DELETE'] : array();
  480. for ($i = 0; $i < sizeof($delete_files_info); $i++)
  481. {
  482. $delete_files = $delete_files_info[$i]['children']['FILE'];
  483. for ($j = 0; $j < sizeof($delete_files); $j++)
  484. {
  485. $name = str_replace('\\', '/', $delete_files[$j]['attrs']['NAME']);
  486. $actions['DELETE_FILES'][] = $name;
  487. }
  488. }
  489. // open
  490. $open_info = (!empty($xml_actions['OPEN'])) ? $xml_actions['OPEN'] : array();
  491. for ($i = 0; $i < sizeof($open_info); $i++)
  492. {
  493. $current_file = str_replace('\\', '/', trim($open_info[$i]['attrs']['SRC']));
  494. $actions['EDITS'][$current_file] = array();
  495. $edit_info = (!empty($open_info[$i]['children']['EDIT'])) ? $open_info[$i]['children']['EDIT'] : array();
  496. // find, after add, before add, replace with
  497. for ($j = 0; $j < sizeof($edit_info); $j++)
  498. {
  499. $action_info = (!empty($edit_info[$j]['children'])) ? $edit_info[$j]['children'] : array();
  500. // store some array information to help decide what kind of operation we're doing
  501. $action_count = $total_action_count = $remove_count = $find_count = 0;
  502. if (isset($action_info['ACTION']))
  503. {
  504. $action_count += sizeof($action_info['ACTION']);
  505. }
  506. if (isset($action_info['INLINE-EDIT']))
  507. {
  508. $total_action_count += sizeof($action_info['INLINE-EDIT']);
  509. }
  510. if (isset($action_info['REMOVE']))
  511. {
  512. $remove_count = sizeof($action_info['REMOVE']); // should be an integer bounded between zero and one
  513. }
  514. if (isset($action_info['FIND']))
  515. {
  516. $find_count = sizeof($action_info['FIND']);
  517. }
  518. // the basic idea is to transform a "remove" tag into a replace-with action
  519. if ($remove_count && !$find_count)
  520. {
  521. // but we still support it if $remove_count is > 1
  522. for ($k = 0; $k < $remove_count; $k++)
  523. {
  524. // if there is no find tag associated, handle it directly
  525. $actions['EDITS'][$current_file][$j][trim($action_info['REMOVE'][$k]['data'], "\n\r")]['replace with'] = '';
  526. }
  527. }
  528. else if ($remove_count && $find_count)
  529. {
  530. // if there is a find and a remove, transform into a replace-with
  531. // action, and let the logic below sort out the relationships.
  532. for ($k = 0; $k < $remove_count; $k++)
  533. {
  534. $insert_index = (isset($action_info['ACTION'])) ? sizeof($action_info['ACTION']) : 0;
  535. $action_info['ACTION'][$insert_index] = array(
  536. 'data' => '',
  537. 'attrs' => array('TYPE' => 'replace with'),
  538. );
  539. }
  540. }
  541. else if (!$find_count)
  542. {
  543. trigger_error(sprintf($user->lang['INVALID_MOD_NO_FIND'], htmlspecialchars($action_info['ACTION'][0]['data'])), E_USER_WARNING);
  544. }
  545. // first we try all the possibilities for a FIND/ACTION combo, then look at inline possibilities.
  546. if (isset($action_info['ACTION']))
  547. {
  548. for ($k = 0; $k < $find_count; $k++)
  549. {
  550. // is this anything but the last iteration of the loop?
  551. if ($k < ($find_count - 1))
  552. {
  553. // NULL has special meaning for an action ... no action to be taken; advance pointer
  554. $actions['EDITS'][$current_file][$j][$action_info['FIND'][$k]['data']] = NULL;
  555. }
  556. else
  557. {
  558. // this is the last iteration, assign the action tags
  559. for ($l = 0; $l < $action_count; $l++)
  560. {
  561. $type = str_replace('-', ' ', $action_info['ACTION'][$l]['attrs']['TYPE']);
  562. $actions['EDITS'][$current_file][$j][trim($action_info['FIND'][$k]['data'], "\n\r")][$type] = (isset($action_info['ACTION'][$l]['data'])) ? preg_replace("#^(\s)+\n#", '', rtrim(trim($action_info['ACTION'][$l]['data'], "\n"))) : '';
  563. }
  564. }
  565. }
  566. }
  567. else
  568. {
  569. if (!$remove_count && !$total_action_count)
  570. {
  571. trigger_error(sprintf($user->lang['INVALID_MOD_NO_ACTION'], htmlspecialchars($action_info['FIND'][0]['data'])), E_USER_WARNING);
  572. }
  573. }
  574. // add comment to the actions array
  575. $actions['EDITS'][$current_file][$j]['comment'] = localise_tags($action_info, 'COMMENT');
  576. // inline
  577. if (isset($action_info['INLINE-EDIT']))
  578. {
  579. $inline_info = (!empty($action_info['INLINE-EDIT'])) ? $action_info['INLINE-EDIT'] : array();
  580. if (isset($inline_info[0]['children']['INLINE-REMOVE']) && sizeof($inline_info[0]['children']['INLINE-REMOVE']))
  581. {
  582. // overwrite the existing array with the new one
  583. $inline_info[0]['children'] = array(
  584. 'INLINE-FIND' => $inline_info[0]['children']['INLINE-REMOVE'],
  585. 'INLINE-ACTION' => array(
  586. 0 => array(
  587. 'attrs' => array('TYPE' => 'replace-with'),
  588. 'data' => '',
  589. ),
  590. ),
  591. );
  592. }
  593. if ($find_count > $total_action_count)
  594. {
  595. // Yeah, $k is used more than once for different information
  596. for ($k = 0; $k < $find_count; $k++)
  597. {
  598. // is this anything but the last iteration of the loop?
  599. if ($k < ($find_count - 1))
  600. {
  601. // NULL has special meaning for an action ... no action to be taken; advance pointer
  602. $actions['EDITS'][$current_file][$j][trim($action_info['FIND'][$k]['data'], "\r\n")] = NULL;
  603. }
  604. }
  605. }
  606. /*
  607. * This loop attaches the in-line information to the _last
  608. * find_ in the <edit> tag. This is the intended behavior
  609. * Any additional finds ought to be in a different edit tag
  610. */
  611. for ($k = 0; $k < sizeof($inline_info); $k++)
  612. {
  613. $inline_data = (!empty($inline_info[$k]['children'])) ? $inline_info[$k]['children'] : array();
  614. $inline_find_count = (isset($inline_data['INLINE-FIND'])) ? sizeof($inline_data['INLINE-FIND']) : 0;
  615. $inline_comment = localise_tags($inline_data, 'INLINE-COMMENT');
  616. $actions['EDITS'][$current_file][$j][trim($action_info['FIND'][$find_count - 1]['data'], "\r\n")]['in-line-edit']['inline-comment'] = $inline_comment;
  617. $inline_actions = (!empty($inline_data['INLINE-ACTION'])) ? $inline_data['INLINE-ACTION'] : array();
  618. if (empty($inline_actions))
  619. {
  620. trigger_error(sprintf($user->lang['INVALID_MOD_NO_ACTION'], htmlspecialchars($inline_data['INLINE-FIND'][0]['data'])), E_USER_WARNING);
  621. }
  622. if (empty($inline_find_count))
  623. {
  624. trigger_error(sprintf($user->lang['INVALID_MOD_NO_FIND'], htmlspecialchars($inline_actions[0]['data'])), E_USER_WARNING);
  625. }
  626. for ($l = 0; $l < $inline_find_count; $l++)
  627. {
  628. $inline_find = $inline_data['INLINE-FIND'][$l]['data'];
  629. // trying to reduce the levels of arrays without impairing features.
  630. // need to keep the "full" edit intact.
  631. //
  632. // inline actions must be trimmed in case the MOD author
  633. // inserts a new line by mistake
  634. if ($l < ($inline_find_count - 1))
  635. {
  636. $actions['EDITS'][$current_file][$j][trim($action_info['FIND'][$find_count - 1]['data'], "\r\n")]['in-line-edit'][$k][$inline_find]['in-line-'][] = null;
  637. }
  638. else
  639. {
  640. for ($m = 0; $m < sizeof($inline_actions); $m++)
  641. {
  642. $type = str_replace(',', '-', str_replace(' ', '', $inline_actions[$m]['attrs']['TYPE']));
  643. if (!empty($inline_actions[$m]['data']))
  644. {
  645. $actions['EDITS'][$current_file][$j][trim($action_info['FIND'][$find_count - 1]['data'], "\r\n")]['in-line-edit'][$k][$inline_find]['in-line-' . $type][] = trim($inline_actions[$m]['data'], "\n");
  646. }
  647. else
  648. {
  649. $actions['EDITS'][$current_file][$j][trim($action_info['FIND'][$find_count - 1]['data'], "\r\n")]['in-line-edit'][$k][$inline_find]['in-line-' . $type][] = '';
  650. }
  651. }
  652. }
  653. }
  654. }
  655. }
  656. }
  657. }
  658. if (!empty($xml_actions['PHP-INSTALLER']))
  659. {
  660. $actions['PHP_INSTALLER'] = $xml_actions['PHP-INSTALLER'][0]['data'];
  661. }
  662. if (!empty($xml_actions['DIY-INSTRUCTIONS']))
  663. {
  664. $actions['DIY_INSTRUCTIONS'] = localise_tags($xml_actions, 'DIY-INSTRUCTIONS');
  665. }
  666. return $actions;
  667. }
  668. }
  669. /**
  670. * XML processing
  671. * @package automod
  672. */
  673. class xml_array
  674. {
  675. var $output = array();
  676. var $parser;
  677. var $XML;
  678. function parse($file, $XML)
  679. {
  680. $this->parser = xml_parser_create();
  681. xml_set_object($this->parser, $this);
  682. xml_set_element_handler($this->parser, "tag_open", "tag_closed");
  683. xml_set_character_data_handler($this->parser, "tag_data");
  684. $this->XML = xml_parse($this->parser, $XML);
  685. if (!$this->XML)
  686. {
  687. die(sprintf("<strong>XML error</strong>: %s at line %d. View the file %s in a web browser for a more detailed error message.",
  688. xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser), $file));
  689. }
  690. xml_parser_free($this->parser);
  691. return $this->output;
  692. }
  693. function tag_open($parser, $name, $attrs)
  694. {
  695. $tag = array("name" => $name, "attrs" => $attrs);
  696. array_push($this->output, $tag);
  697. }
  698. function tag_data($parser, $tag_data)
  699. {
  700. if ($tag_data)
  701. {
  702. if (isset($this->output[sizeof($this->output) - 1]['data']))
  703. {
  704. $this->output[sizeof($this->output) - 1]['data'] .= $tag_data;
  705. }
  706. else
  707. {
  708. $this->output[sizeof($this->output) - 1]['data'] = $tag_data;
  709. }
  710. }
  711. }
  712. function tag_closed($parser, $name)
  713. {
  714. $this->output[sizeof($this->output) - 2]['children'][$name][] = $this->output[sizeof($this->output) - 1];
  715. array_pop($this->output);
  716. }
  717. }
  718. ?>