PageRenderTime 66ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/includes/library/automod/acp_mods.php

https://github.com/phpbb/customisation-db
PHP | 2955 lines | 2150 code | 414 blank | 391 comment | 414 complexity | 8880f6bf54492fe40d789c61a9dc5aa2 MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * @package automod
  5. * @copyright (c) 2008 phpBB Group
  6. * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License, version 2
  7. *
  8. */
  9. /**
  10. */
  11. if (!defined('IN_PHPBB'))
  12. {
  13. exit;
  14. }
  15. /**
  16. * @package automod
  17. */
  18. class acp_mods
  19. {
  20. var $u_action;
  21. var $parser;
  22. var $mod_root = '';
  23. var $store_dir = '';
  24. var $mods_dir = '';
  25. var $edited_root = '';
  26. var $backup_root = '';
  27. var $sort_key = '';
  28. var $sort_dir = '';
  29. function main($id, $mode)
  30. {
  31. global $config, $db, $user, $auth, $template, $cache;
  32. global $phpbb_root_path, $phpEx;
  33. global $ftp_method, $test_ftp_connection, $test_connection, $sort_key, $sort_dir;
  34. include("{$phpbb_root_path}includes/functions_transfer.$phpEx");
  35. include("{$phpbb_root_path}includes/editor.$phpEx");
  36. include("{$phpbb_root_path}includes/functions_mods.$phpEx");
  37. include("{$phpbb_root_path}includes/mod_parser.$phpEx");
  38. // start the page
  39. $user->add_lang(array('install', 'acp/mods'));
  40. $this->tpl_name = 'acp_mods';
  41. $this->page_title = 'ACP_CAT_MODS';
  42. $this->store_dir = $phpbb_root_path . 'store';
  43. $this->mods_dir = $phpbb_root_path . 'store/mods';
  44. // get any url vars
  45. $action = request_var('action', '');
  46. $mod_id = request_var('mod_id', 0);
  47. $mod_url = request_var('mod_url', '');
  48. $parent = request_var('parent', 0);
  49. //sort keys
  50. $sort_key = request_var('sk','t');
  51. $sort_dir = request_var('sd', 'a');
  52. $mod_path = request_var('mod_path', '');
  53. if ($mod_path)
  54. {
  55. $mod_path = htmlspecialchars_decode($mod_path); // "/my_mod/install.xml" or "/./contrib/blah.xml"
  56. $mod_dir = substr($mod_path, 1, strpos($mod_path, '/', 1)); // "my_mod/"
  57. $this->mod_root = $this->mods_dir . '/' . $mod_dir; // "./../store/mods/my_mod/"
  58. $this->backup_root = "{$this->mod_root}_backups/"; // "./../store/mods/my_mod/_backups/"
  59. $this->edited_root = "{$this->mod_root}_edited/"; // "./../store/mods/my_mod/_edited/"
  60. }
  61. switch ($mode)
  62. {
  63. case 'config':
  64. $ftp_method = request_var('ftp_method', $config['ftp_method']);
  65. if (!$ftp_method || !class_exists($ftp_method))
  66. {
  67. $ftp_method = 'ftp';
  68. $ftp_methods = transfer::methods();
  69. if (!in_array('ftp', $ftp_methods))
  70. {
  71. $ftp_method = $ftp_methods[0];
  72. }
  73. }
  74. if (isset($_POST['submit']) && check_form_key('acp_mods'))
  75. {
  76. $ftp_host = request_var('host', '');
  77. $ftp_username = request_var('username', '');
  78. $ftp_password = request_var('password', ''); // not stored, used to test connection
  79. $ftp_root_path = request_var('root_path', '');
  80. $ftp_port = request_var('port', 21);
  81. $ftp_timeout = request_var('timeout', 10);
  82. $write_method = request_var('write_method', 0);
  83. $file_perms = request_var('file_perms', '0644');
  84. $dir_perms = request_var('dir_perms', '0755');
  85. $compress_method = request_var('compress_method', '');
  86. $preview_changes = request_var('preview_changes', 0);
  87. $error = '';
  88. if ($write_method == WRITE_DIRECT)
  89. {
  90. // the very best method would be to check every file for is_writable
  91. if (!is_writable("{$phpbb_root_path}common.$phpEx") || !is_writable("{$phpbb_root_path}adm/style/acp_groups.html"))
  92. {
  93. $error = 'FILESYSTEM_NOT_WRITABLE';
  94. }
  95. }
  96. else if ($write_method == WRITE_FTP)
  97. {
  98. // check the correctness of FTP infos
  99. $test_ftp_connection = true;
  100. $test_connection = false;
  101. test_ftp_connection($ftp_method, $test_ftp_connection, $test_connection);
  102. if ($test_connection !== true)
  103. {
  104. $error = $test_connection;
  105. }
  106. }
  107. else if ($write_method == WRITE_MANUAL)
  108. {
  109. // the compress class requires write access to the store/ dir
  110. if (!is_writable($this->store_dir))
  111. {
  112. $error = 'STORE_NOT_WRITABLE';
  113. }
  114. }
  115. if (empty($error))
  116. {
  117. set_config('ftp_method', $ftp_method);
  118. set_config('ftp_host', $ftp_host);
  119. set_config('ftp_username', $ftp_username);
  120. set_config('ftp_root_path', $ftp_root_path);
  121. set_config('ftp_port', $ftp_port);
  122. set_config('ftp_timeout', $ftp_timeout);
  123. set_config('write_method', $write_method);
  124. set_config('compress_method', $compress_method);
  125. set_config('preview_changes', $preview_changes);
  126. set_config('am_file_perms', $file_perms);
  127. set_config('am_dir_perms', $dir_perms);
  128. trigger_error($user->lang['MOD_CONFIG_UPDATED'] . adm_back_link($this->u_action));
  129. }
  130. else
  131. {
  132. $template->assign_var('ERROR', $user->lang[$error]);
  133. }
  134. }
  135. else if (isset($_POST['submit']) && !check_form_key('acp_mods'))
  136. {
  137. trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
  138. }
  139. add_form_key('acp_mods');
  140. // implicit else
  141. include("{$phpbb_root_path}includes/functions_compress.$phpEx");
  142. foreach (compress::methods() as $compress_method)
  143. {
  144. $template->assign_block_vars('compress', array(
  145. 'METHOD' => $compress_method,
  146. ));
  147. }
  148. $requested_data = call_user_func(array($ftp_method, 'data'));
  149. foreach ($requested_data as $data => $default)
  150. {
  151. $default = (!empty($config['ftp_' . $data])) ? $config['ftp_' . $data] : $default;
  152. $template->assign_block_vars('data', array(
  153. 'DATA' => $data,
  154. 'NAME' => $user->lang[strtoupper($ftp_method . '_' . $data)],
  155. 'EXPLAIN' => $user->lang[strtoupper($ftp_method . '_' . $data) . '_EXPLAIN'],
  156. 'DEFAULT' => (!empty($_REQUEST[$data])) ? request_var($data, '') : $default
  157. ));
  158. }
  159. $template->assign_vars(array(
  160. 'S_CONFIG' => true,
  161. 'U_CONFIG' => $this->u_action . '&amp;mode=config',
  162. 'UPLOAD_METHOD_FTP' => ($config['ftp_method'] == 'ftp') ? ' checked="checked"' : '',
  163. 'UPLOAD_METHOD_FSOCK'=> ($config['ftp_method'] == 'ftp_fsock') ? ' checked="checked"' : '',
  164. 'WRITE_DIRECT' => ($config['write_method'] == WRITE_DIRECT) ? ' checked="checked"' : '',
  165. 'WRITE_FTP' => ($config['write_method'] == WRITE_FTP) ? ' checked="checked"' : '',
  166. 'WRITE_MANUAL' => ($config['write_method'] == WRITE_MANUAL) ? ' checked="checked"' : '',
  167. 'WRITE_METHOD_DIRECT' => WRITE_DIRECT,
  168. 'WRITE_METHOD_FTP' => WRITE_FTP,
  169. 'WRITE_METHOD_MANUAL' => WRITE_MANUAL,
  170. 'AUTOMOD_VERSION' => $config['automod_version'],
  171. 'COMPRESS_METHOD' => $config['compress_method'],
  172. 'DIR_PERMS' => $config['am_dir_perms'],
  173. 'FILE_PERMS' => $config['am_file_perms'],
  174. 'PREVIEW_CHANGES_YES' => ($config['preview_changes']) ? ' checked="checked"' : '',
  175. 'PREVIEW_CHANGES_NO' => (!$config['preview_changes']) ? ' checked="checked"' : '',
  176. 'S_HIDE_FTP' => ($config['write_method'] == WRITE_FTP) ? false : true,
  177. ));
  178. break;
  179. case 'frontend':
  180. if ($config['write_method'] == WRITE_FTP)
  181. {
  182. $ftp_method = basename(request_var('method', $config['ftp_method']));
  183. if (!$ftp_method || !class_exists($ftp_method))
  184. {
  185. $ftp_method = 'ftp';
  186. $ftp_methods = transfer::methods();
  187. if (!in_array('ftp', $ftp_methods))
  188. {
  189. $ftp_method = $ftp_methods[0];
  190. }
  191. }
  192. $test_connection = false;
  193. $test_ftp_connection = request_var('test_connection', '');
  194. if (!empty($test_ftp_connection) || in_array($action, array('install', 'uninstall', 'upload_mod', 'delete_mod')))
  195. {
  196. test_ftp_connection($ftp_method, $test_ftp_connection, $test_connection);
  197. // Make sure the login details are correct before continuing
  198. if ($test_connection !== true || !empty($test_ftp_connection))
  199. {
  200. $action = 'pre_' . $action;
  201. }
  202. }
  203. }
  204. // store/ needs to be world-writable even when FTP is the write method,
  205. // for extracting uploaded mod zip files
  206. if (!is_writable($this->store_dir))
  207. {
  208. $template->assign_var('S_STORE_WRITABLE_WARN', true);
  209. }
  210. // Otherwise, store/mods/ needs to be writable
  211. else if ($config['write_method'] != WRITE_FTP && !is_writable($this->mods_dir))
  212. {
  213. $template->assign_var('S_MODS_WRITABLE_WARN', true);
  214. }
  215. switch ($action)
  216. {
  217. case 'pre_install':
  218. case 'install':
  219. $this->install($action, $mod_path, $parent);
  220. break;
  221. case 'pre_uninstall':
  222. case 'uninstall':
  223. $this->uninstall($action, $mod_id, $parent);
  224. break;
  225. case 'details':
  226. $mod_ident = ($mod_id) ? $mod_id : $mod_path;
  227. $this->list_details($mod_ident);
  228. break;
  229. case 'pre_delete_mod':
  230. case 'delete_mod':
  231. $this->delete_mod($action, $mod_path);
  232. break;
  233. case 'pre_upload_mod':
  234. case 'upload_mod':
  235. default:
  236. $action = (isset($action)) ? $action : '';
  237. if (!$this->upload_mod($action))
  238. {
  239. $this->list_installed();
  240. $this->list_uninstalled();
  241. }
  242. break;
  243. case 'download':
  244. include ($phpbb_root_path . "includes/functions_compress.$phpEx");
  245. $editor = new editor_manual();
  246. $time = request_var('time', 0);
  247. // if for some reason the MOD isn't found in the DB...
  248. $download_name = 'mod_' . $time;
  249. $sql = 'SELECT mod_name FROM ' . MODS_TABLE . '
  250. WHERE mod_time = ' . $time;
  251. $result = $db->sql_query($sql);
  252. if ($row = $db->sql_fetchrow($result))
  253. {
  254. // Always use the English name except for showing the user.
  255. $mod_name = localize_title($row['mod_name'], 'en');
  256. $download_name = str_replace(' ', '_', $mod_name);
  257. }
  258. $editor->compress->download("{$this->store_dir}/mod_$time", $download_name);
  259. exit;
  260. break;
  261. }
  262. return;
  263. break;
  264. }
  265. }
  266. /**
  267. * List all the installed mods
  268. */
  269. function list_installed()
  270. {
  271. global $db, $template, $user, $sort_key, $sort_dir;
  272. $sort_by_text = array('u' => $user->lang['SORT_NAME'], 't' => $user->lang['SORT_DATE']);
  273. ($sort_key == 't')? $sort='mod_time' & $s_sort_key='mod_time': $sort='mod_name' & $s_sort_key='mod_name';
  274. ($sort_dir == 'd') ? $dir='DESC' & $s_sort_dir='DESC' : $dir='ASC' & $s_sort_dir='ASC';
  275. $limit_days = array();
  276. $s_limit_days=$sort_days=$s_limit_days = $u_sort_param = '';
  277. gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param);
  278. $template->assign_vars(array(
  279. 'S_SORT_KEY' => $s_sort_key,
  280. 'S_SORT_DIR' => $s_sort_dir,
  281. 'U_SORT_ACTION' => $this->u_action ."&amp;$u_sort_param"
  282. ));
  283. // The MOD name is a array so it can't be used as sort key directly.
  284. $sql_sort = ($sort_key == 't') ? " ORDER BY mod_time $dir" : '';
  285. $sql = 'SELECT mod_name, mod_id, mod_time
  286. FROM ' . MODS_TABLE .
  287. $sql_sort;
  288. $result = $db->sql_query($sql);
  289. $mod_ary = $db->sql_fetchrowset($result);
  290. $db->sql_freeresult($result);
  291. foreach ($mod_ary as $key => $row)
  292. {
  293. if (($name_ary = @unserialize($row['mod_name'])) === false)
  294. {
  295. $name_ary['en'] = $row['mod_name'];
  296. }
  297. $mod_ary[$key]['mod_name'] = $name_ary;
  298. }
  299. if ($sort_key != 't')
  300. {
  301. $sort_ary = array();
  302. foreach ($mod_ary as $key => $row)
  303. {
  304. $sort_ary[$key] = $row['mod_name']['en'];
  305. }
  306. if ($sort_dir == 'd')
  307. {
  308. arsort($sort_ary, SORT_STRING);
  309. }
  310. else
  311. {
  312. asort($sort_ary, SORT_STRING);
  313. }
  314. foreach ($sort_ary as $key => $name)
  315. {
  316. $sort_ary[$key] = $mod_ary[$key];
  317. }
  318. $mod_ary = $sort_ary;
  319. unset($sort_ary);
  320. }
  321. foreach ($mod_ary as $row)
  322. {
  323. $mod_name = localize_title($row['mod_name'], $user->data['user_lang']);
  324. $template->assign_block_vars('installed', array(
  325. 'MOD_ID' => $row['mod_id'],
  326. 'MOD_NAME' => htmlspecialchars($mod_name),
  327. 'MOD_TIME' => $user->format_date($row['mod_time']),
  328. 'U_DETAILS' => $this->u_action . '&amp;action=details&amp;mod_id=' . $row['mod_id'],
  329. 'U_UNINSTALL' => $this->u_action . '&amp;action=pre_uninstall&amp;mod_id=' . $row['mod_id'])
  330. );
  331. }
  332. return;
  333. }
  334. /**
  335. * List all mods available locally
  336. */
  337. function list_uninstalled()
  338. {
  339. global $phpbb_root_path, $db, $template, $config, $user;
  340. // get available MOD paths
  341. $available_mods = $this->find_mods($this->mods_dir, 1);
  342. if (!sizeof($available_mods['main']))
  343. {
  344. return;
  345. }
  346. // get installed MOD paths
  347. $installed_paths = array();
  348. $sql = 'SELECT mod_path
  349. FROM ' . MODS_TABLE;
  350. $result = $db->sql_query($sql);
  351. while ($row = $db->sql_fetchrow($result))
  352. {
  353. $installed_paths[] = $row['mod_path'];
  354. }
  355. $db->sql_freeresult($result);
  356. $mod_paths = array();
  357. foreach ($available_mods['main'] as $mod_info)
  358. {
  359. $mod_paths[] = $mod_info['href'];
  360. }
  361. // we don't care about any xml files not in the main directory
  362. $available_mods = array_diff($mod_paths, $installed_paths);
  363. unset($installed_paths);
  364. unset($mod_paths);
  365. // show only available MODs that paths aren't in the DB
  366. foreach ($available_mods as $file)
  367. {
  368. $details = $this->mod_details($file, false);
  369. $short_path = urlencode(str_replace($this->mods_dir, '', $details['MOD_PATH']));
  370. $mod_name = localize_title($details['MOD_NAME'], $user->data['user_lang']);
  371. $template->assign_block_vars('uninstalled', array(
  372. 'MOD_NAME' => htmlspecialchars($mod_name),
  373. 'MOD_PATH' => $short_path,
  374. 'PHPBB_VERSION' => $details['PHPBB_VERSION'],
  375. 'S_PHPBB_VESION' => ($details['PHPBB_VERSION'] != $config['version']) ? true : false,
  376. 'U_INSTALL' => $this->u_action . "&amp;action=pre_install&amp;mod_path=$short_path",
  377. 'U_DELETE' => $this->u_action . "&amp;action=pre_delete_mod&amp;mod_path=$short_path",
  378. 'U_DETAILS' => $this->u_action . "&amp;action=details&amp;mod_path=$short_path",
  379. ));
  380. }
  381. return;
  382. }
  383. /**
  384. * Lists mod details
  385. */
  386. function list_details($mod_ident)
  387. {
  388. global $template, $config, $user;
  389. $template->assign_vars(array(
  390. 'S_DETAILS' => true,
  391. 'U_BACK' => $this->u_action,
  392. ));
  393. $details = $this->mod_details($mod_ident, true);
  394. if (!is_int($mod_ident) && $details['PHPBB_VERSION'] != $config['version'])
  395. {
  396. $version_warnig = sprintf($user->lang['VERSION_WARNING'], $details['PHPBB_VERSION'], $config['version']);
  397. $template->assign_vars(array(
  398. 'VERSION_WARNING' => $version_warnig,
  399. 'S_PHPBB_VESION' => true,
  400. ));
  401. }
  402. if (!empty($details['AUTHOR_DETAILS']))
  403. {
  404. foreach ($details['AUTHOR_DETAILS'] as $author_details)
  405. {
  406. $template->assign_block_vars('author_list', $author_details);
  407. }
  408. unset($details['AUTHOR_DETAILS']);
  409. }
  410. // Display Do-It-Yourself Actions...per the MODX spec,
  411. // Need to handle the languag later, but it's not saved for now.
  412. if (!empty($details['DIY_INSTRUCTIONS']))
  413. {
  414. $template->assign_var('S_DIY', true);
  415. if (!is_array($details['DIY_INSTRUCTIONS']))
  416. {
  417. $details['DIY_INSTRUCTIONS'] = array($details['DIY_INSTRUCTIONS']);
  418. }
  419. foreach ($details['DIY_INSTRUCTIONS'] as $instruction)
  420. {
  421. $template->assign_block_vars('diy_instructions', array(
  422. 'DIY_INSTRUCTION' => nl2br($instruction),
  423. ));
  424. }
  425. }
  426. if (!empty($details['MOD_HISTORY']))
  427. {
  428. $template->assign_var('S_CHANGELOG', true);
  429. foreach ($details['MOD_HISTORY'] as $mod_version)
  430. {
  431. $template->assign_block_vars('changelog', array(
  432. 'VERSION' => $mod_version['VERSION'],
  433. 'DATE' => $mod_version['DATE'],
  434. ));
  435. foreach ($mod_version['CHANGES'] as $changes)
  436. {
  437. $template->assign_block_vars('changelog.changes', array(
  438. 'CHANGE' => $changes,
  439. ));
  440. }
  441. }
  442. }
  443. unset($details['MOD_HISTORY']);
  444. $details['MOD_NAME'] = localize_title($details['MOD_NAME'], $user->data['user_lang']);
  445. $details['MOD_NAME'] = htmlspecialchars($details['MOD_NAME']);
  446. $template->assign_vars($details);
  447. if (!empty($details['AUTHOR_NOTES']))
  448. {
  449. $template->assign_var('S_AUTHOR_NOTES', true);
  450. }
  451. if (!empty($details['MOD_INSTALL_TIME']))
  452. {
  453. $template->assign_var('S_INSTALL_TIME', true);
  454. }
  455. return;
  456. }
  457. /**
  458. * Returns array of mod information
  459. */
  460. function mod_details($mod_ident, $find_children = true, $uninstall = false)
  461. {
  462. global $phpbb_root_path, $phpEx, $user, $template, $parent_id;
  463. if (is_int($mod_ident))
  464. {
  465. global $db, $user;
  466. $mod_id = (int) $mod_ident;
  467. $sql = 'SELECT *
  468. FROM ' . MODS_TABLE . "
  469. WHERE mod_id = $mod_id";
  470. $result = $db->sql_query($sql);
  471. if ($row = $db->sql_fetchrow($result))
  472. {
  473. // TODO: Yuck, get rid of this.
  474. $author_details = array();
  475. $author_details[0] = array(
  476. 'AUTHOR_NAME' => $row['mod_author_name'],
  477. 'AUTHOR_EMAIL' => $row['mod_author_email'],
  478. 'AUTHOR_WEBSITE' => $row['mod_author_url'],
  479. );
  480. $actions = unserialize($row['mod_actions']);
  481. $details = array(
  482. 'MOD_ID' => $row['mod_id'],
  483. 'MOD_PATH' => $row['mod_path'],
  484. 'MOD_INSTALL_TIME' => $user->format_date($row['mod_time']),
  485. // 'MOD_DEPENDENCIES' => unserialize($row['mod_dependencies']), // ?
  486. 'MOD_NAME' => $row['mod_name'],
  487. 'MOD_DESCRIPTION' => nl2br($row['mod_description']),
  488. 'MOD_VERSION' => $row['mod_version'],
  489. 'DIY_INSTRUCTIONS' => (!empty($actions['DIY_INSTRUCTIONS'])) ? $actions['DIY_INSTRUCTIONS'] : '',
  490. 'AUTHOR_NOTES' => nl2br($row['mod_author_notes']),
  491. 'AUTHOR_DETAILS' => $author_details,
  492. );
  493. // This is a check for any further XML files to go with this MOD.
  494. // Obviously, the files must not have been removed for this to work.
  495. if (($find_children || $uninstall) && file_exists($row['mod_path']))
  496. {
  497. $parent_id = $mod_id;
  498. $mod_path = $row['mod_path'];
  499. $actions = array();
  500. $mod_dir = dirname($mod_path);
  501. $this->mod_root = $mod_dir . '/';
  502. $ext = substr(strrchr($mod_path, '.'), 1);
  503. $this->parser = new parser($ext);
  504. $this->parser->set_file($mod_path);
  505. // Find and display the available MODX files
  506. $children = $this->find_children($mod_path);
  507. $elements = array('language' => array(), 'template' => array());
  508. $found_prosilver = false;
  509. if (!$uninstall)
  510. {
  511. $this->handle_contrib($children);
  512. $this->handle_language_prompt($children, $elements, 'details');
  513. $this->handle_template_prompt($children, $elements, 'details');
  514. // Now offer to install additional templates
  515. if (isset($children['template']) && sizeof($children['template']))
  516. {
  517. // These are the instructions included with the MOD
  518. foreach ($children['template'] as $template_name)
  519. {
  520. if (!is_array($template_name))
  521. {
  522. continue;
  523. }
  524. if ($template_name['realname'] == 'prosilver')
  525. {
  526. $found_prosilver = true;
  527. }
  528. if (file_exists($this->mod_root . $template_name['href']))
  529. {
  530. $xml_file = $template_name['href'];
  531. }
  532. else
  533. {
  534. $xml_file = str_replace($this->mods_dir, '', $mod_dir) . '/' . $template_name['href'];
  535. }
  536. $template->assign_block_vars('avail_templates', array(
  537. 'TEMPLATE_NAME' => $template_name['realname'],
  538. 'XML_FILE' => urlencode($xml_file),
  539. ));
  540. }
  541. }
  542. }
  543. else
  544. {
  545. if (isset($children['uninstall']) && sizeof($children['uninstall']))
  546. {
  547. // Override already exising actions with the ones
  548. global $rev_actions;
  549. $xml_file = $mod_dir . '/' . ltrim($children['uninstall'][0]['href'], './');
  550. $this->parser->set_file($xml_file);
  551. $rev_actions = $this->parser->get_actions();
  552. }
  553. }
  554. if (!$found_prosilver)
  555. {
  556. $template->assign_block_vars('avail_templates', array(
  557. 'TEMPLATE_NAME' => 'prosilver',
  558. 'XML_FILE' => basename($mod_path),
  559. ));
  560. }
  561. $processed_templates = array('prosilver');
  562. $processed_templates += explode(',', $row['mod_template']);
  563. /*
  564. // now grab the templates that have not already been processed
  565. $sql = 'SELECT template_id, template_path FROM ' . STYLES_TEMPLATE_TABLE . '
  566. WHERE ' . $db->sql_in_set('template_name', $processed_templates, true);
  567. $result = $db->sql_query($sql);
  568. while ($row = $db->sql_fetchrow($result))
  569. {
  570. $template->assign_block_vars('board_templates', array(
  571. 'TEMPLATE_ID' => $row['template_id'],
  572. 'TEMPLATE_NAME' => $row['template_path'],
  573. ));
  574. }
  575. */
  576. $s_hidden_fields = build_hidden_fields(array(
  577. 'action' => 'install',
  578. 'parent' => $parent_id,
  579. ));
  580. $template->assign_vars(array(
  581. 'S_FORM_ACTION' => $this->u_action,
  582. 'S_HIDDEN_FIELDS' => $s_hidden_fields,
  583. ));
  584. add_form_key('acp_mods');
  585. }
  586. }
  587. else
  588. {
  589. trigger_error($user->lang['NO_MOD'] . adm_back_link($this->u_action), E_USER_WARNING);
  590. }
  591. $db->sql_freeresult($result);
  592. }
  593. else
  594. {
  595. $parent = request_var('parent', 0);
  596. if ($parent)
  597. {
  598. global $db;
  599. // reset the class parameters to refelect the proper directory
  600. $sql = 'SELECT mod_path FROM ' . MODS_TABLE . '
  601. WHERE mod_id = ' . (int) $parent;
  602. $result = $db->sql_query($sql);
  603. if ($row = $db->sql_fetchrow($result))
  604. {
  605. $this->mod_root = dirname($row['mod_path']) . '/';
  606. }
  607. }
  608. if (strpos($mod_ident, $this->mods_dir) === false)
  609. {
  610. $mod_ident = $this->mods_dir . $mod_ident;
  611. }
  612. if (!file_exists($mod_ident))
  613. {
  614. $mod_ident = str_replace($this->mods_dir, $this->mod_root, $mod_ident);
  615. }
  616. $mod_path = $mod_ident;
  617. $mod_parent = 0;
  618. $ext = substr(strrchr($mod_path, '.'), 1);
  619. $this->parser = new parser($ext);
  620. $this->parser->set_file($mod_path);
  621. $details = $this->parser->get_details();
  622. if ($find_children)
  623. {
  624. $actions = array();
  625. $children = $this->find_children($mod_path);
  626. $elements = array('language' => array(), 'template' => array());
  627. $this->handle_contrib($children);
  628. $this->handle_language_prompt($children, $elements, 'details');
  629. $this->handle_template_prompt($children, $elements, 'details');
  630. }
  631. }
  632. return $details;
  633. }
  634. /**
  635. * Returns complex array of all mod actions
  636. */
  637. function mod_actions($mod_ident)
  638. {
  639. global $phpbb_root_path, $phpEx;
  640. if (is_int($mod_ident))
  641. {
  642. global $db, $user;
  643. $sql = 'SELECT mod_actions, mod_name
  644. FROM ' . MODS_TABLE . "
  645. WHERE mod_id = $mod_ident";
  646. $result = $db->sql_query($sql);
  647. $row = $db->sql_fetchrow($result);
  648. $db->sql_freeresult($result);
  649. if ($row)
  650. {
  651. $mod_actions = unserialize($row['mod_actions']);
  652. if (@unserialize($row['mod_name']) === false)
  653. {
  654. // On version 1.0.1 and later the mod name is a serialized array.
  655. // Earlier it was a string so unserialize will fail.
  656. $mod_actions['EDITS'] = $this->update_edits($mod_actions['EDITS']);
  657. }
  658. return($mod_actions);
  659. }
  660. else
  661. {
  662. trigger_error($user->lang['NO_MOD'] . adm_back_link($this->u_action), E_USER_WARNING);
  663. }
  664. }
  665. else
  666. {
  667. if (strpos($mod_ident, $this->mods_dir) === false)
  668. {
  669. $mod_ident = $this->mods_dir . $mod_ident;
  670. }
  671. if (!file_exists($mod_ident))
  672. {
  673. $mod_ident = str_replace($this->mods_dir, $this->mod_root, $mod_ident);
  674. }
  675. $this->parser->set_file($mod_ident);
  676. $actions = $this->parser->get_actions();
  677. }
  678. return $actions;
  679. }
  680. /**
  681. * Updates inline edits for MODs installed before AutoMOD 1.0.1.
  682. *
  683. * @param array $mod_edits, MOD actions directly from the DB.
  684. * @return mixed uppdated array or false on error.
  685. */
  686. function update_edits($mod_edits)
  687. {
  688. if (empty($mod_edits))
  689. {
  690. return(false);
  691. }
  692. $updated_ary = array();
  693. foreach ($mod_edits as $file => $edits)
  694. {
  695. $updated_ary[$file] = array();
  696. $inline = false;
  697. $key = 0;
  698. $old_find = $find_line = '';
  699. foreach ($edits as $edit)
  700. {
  701. foreach ($edit as $find => $action)
  702. {
  703. $first_key = key($action); // The first key contains the action or "in-line-edit".
  704. if ($first_key != 'in-line-edit')
  705. {
  706. if ($inline)
  707. {
  708. $updated_ary[$file][$key++][$find_line] = array('in-line-edit' => $inline_edit);
  709. $inline_edit = array();
  710. $inline = false;
  711. $old_find = $find_line = '';
  712. }
  713. $updated_ary[$file][$key++][$find] = $action;
  714. $inline = false;
  715. continue;
  716. }
  717. $inline = true;
  718. $inline_edit = (empty($inline_edit)) ? array() : $inline_edit;
  719. if (!empty($old_find) && !$this->same_line($old_find, $find, $action['in-line-edit']))
  720. {
  721. $updated_ary[$file][$key++][$find_line] = array('in-line-edit' => $inline_edit);
  722. $inline_edit = array();
  723. }
  724. $find_line = $find;
  725. $old_find = $find;
  726. $inline_edit[] = $action['in-line-edit'];
  727. }
  728. }
  729. if ($inline && !empty($inline_edit))
  730. {
  731. $updated_ary[$file][$key++][$find_line] = array('in-line-edit' => $inline_edit);
  732. $inline = false;
  733. $inline_edit = array();
  734. $old_find = $find_line = '';
  735. }
  736. }
  737. return($updated_ary);
  738. }
  739. /**
  740. * Tries to check if two inline edits are editing the same line.
  741. *
  742. * @param string $prev, the find from the previous in-line-edit.
  743. * @param string $find, the find for the current in-line-edit.
  744. * @param array $action, the current edit array.
  745. * @return bool true for identical lines, otherwise false
  746. */
  747. function same_line($prev_find, $find, $action)
  748. {
  749. if (empty($prev_find) || empty($find))
  750. {
  751. // If both are empty something is wrong.
  752. return(false);
  753. }
  754. // The first key in $action is the in-line-find string
  755. $edit_ary = reset($action); // Array with what to do and what to remove.
  756. $inline_find = key($action); // String to find in $find.
  757. $add_ary = reset($edit_ary); // $add_ary[0] contains the string to remove from $find
  758. $add_str = $add_ary[0];
  759. $inline_action = key($edit_ary); // What to do.
  760. // The actions currently supported are in-line-before-add and in-line-after-add.
  761. // replace and delete will be added later.
  762. switch ($inline_action)
  763. {
  764. case 'in-line-replace':
  765. // There are no positions stored in the DB so we can not be sure that there
  766. // is only one occasion of the string added instead of the search string.
  767. // Keeping count on the previous edits still don't give a 100% guaranty that
  768. // we are in the right place in the string.
  769. $compare = str_replace($add_str, $inline_find, $find);
  770. break;
  771. case 'in-line-before-add':
  772. $pos = strpos($find, $inline_find);
  773. $len = strlen($add_str);
  774. $start = $pos - $len;
  775. $compare = substr_replace($find, '', $start, $len);
  776. break;
  777. case 'in-line-after-add':
  778. $pos = strpos($find, $inline_find);
  779. $start = $pos + strlen($inline_find);
  780. $compare = substr_replace($find, '', $start, strlen($add_str));
  781. break;
  782. case 'inline-remove':
  783. default:
  784. // inline-remove don't yet work to install with AutoMOD so I assume nobody
  785. // is trying to remove it either in MODs installed with 1.0.0.1 or earlier.
  786. return(false);
  787. break;
  788. }
  789. $check = ($compare == $prev_find) ? true : false;
  790. return($check);
  791. }
  792. /**
  793. * Install/pre-install a mod
  794. * Preforms all Edits, Copies, and SQL queries
  795. */
  796. function install($action, $mod_path, $parent = 0)
  797. {
  798. global $phpbb_root_path, $phpEx, $db, $template, $user, $config, $cache, $dest_template;
  799. global $force_install, $mod_installed;
  800. // Are we forcing a template install?
  801. $dest_template = $mod_contribs = $mod_language = '';
  802. if (isset($_POST['template_submit']))
  803. {
  804. if (!check_form_key('acp_mods'))
  805. {
  806. trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
  807. }
  808. $mod_path = urldecode(request_var('source', ''));
  809. $dest_template = request_var('dest', '');
  810. if (preg_match('#.*install.*xml$#i', $mod_path))
  811. {
  812. $src_template = 'prosilver';
  813. }
  814. else
  815. {
  816. preg_match('#([a-z0-9]+)$#i', core_basename($mod_path), $match);
  817. $src_template = $match[1];
  818. unset ($match);
  819. }
  820. }
  821. if (empty($mod_path))
  822. {
  823. return false; // ERROR
  824. }
  825. $details = $this->mod_details($mod_path, false);
  826. if (!$parent)
  827. {
  828. // $details['MOD_NAME'] is a array and not knowing what language was used when the MOD was installed,
  829. // if it was installed with AutoMOD 1.0.0. So need to check all.
  830. $sql_where = '';
  831. foreach ($details['MOD_NAME'] as $mod_name)
  832. {
  833. $sql_where .= (($sql_where == '') ? ' mod_name ' : ' OR mod_name ') . $db->sql_like_expression($db->any_char . $mod_name . $db->any_char);
  834. }
  835. $sql = 'SELECT mod_name FROM ' . MODS_TABLE . "
  836. WHERE $sql_where";
  837. $result = $db->sql_query($sql);
  838. if ($row = $db->sql_fetchrow($result))
  839. {
  840. trigger_error('AM_MOD_ALREADY_INSTALLED');
  841. }
  842. }
  843. else if ($dest_template) // implicit && $parent
  844. {
  845. // Has this template already been processed?
  846. $sql = 'SELECT mod_name
  847. FROM ' . MODS_TABLE . "
  848. WHERE mod_id = $parent
  849. AND mod_template " . $db->sql_like_expression($db->any_char . $dest_template . $db->any_char);
  850. $result = $db->sql_query($sql);
  851. if ($row = $db->sql_fetchrow($result))
  852. {
  853. trigger_error('AM_MOD_ALREADY_INSTALLED');
  854. }
  855. $db->sql_freeresult($result);
  856. }
  857. // NB: There could and should be cases to check for duplicated MODs and contribs
  858. // However, there is not appropriate book-keeping in place for those in 1.0.x
  859. // grab installed contrib and language items from the database
  860. if ($parent)
  861. {
  862. $modx_type = request_var('type', '');
  863. if ($modx_type == 'lang')
  864. {
  865. $sql = 'SELECT mod_name
  866. FROM ' . MODS_TABLE . "
  867. WHERE mod_id = $parent
  868. AND mod_languages " . $db->sql_like_expression($db->any_char . $mod_path . $db->any_char);
  869. $result = $db->sql_query($sql);
  870. if ($row = $db->sql_fetchrow($result))
  871. {
  872. trigger_error('AM_MOD_ALREADY_INSTALLED');
  873. }
  874. else
  875. {
  876. $mod_language = $mod_path;
  877. }
  878. $db->sql_freeresult($result);
  879. }
  880. elseif ($modx_type == 'contrib')
  881. {
  882. $sql = 'SELECT mod_name
  883. FROM ' . MODS_TABLE . "
  884. WHERE mod_id = $parent
  885. AND mod_contribs " . $db->sql_like_expression($db->any_char . $mod_path . $db->any_char);
  886. $result = $db->sql_query($sql);
  887. if ($row = $db->sql_fetchrow($result))
  888. {
  889. trigger_error('AM_MOD_ALREADY_INSTALLED');
  890. }
  891. else
  892. {
  893. $mod_contribs = $mod_path;
  894. }
  895. $db->sql_freeresult($result);
  896. }
  897. }
  898. $execute_edits = ($action == 'pre_install') ? false : true;
  899. $write_method = 'editor_' . determine_write_method(!$execute_edits);
  900. $editor = new $write_method();
  901. // get FTP information if we need it (or initialize array $hidden_ary)
  902. $hidden_ary = get_connection_info(!$execute_edits);
  903. $actions = $this->mod_actions($mod_path);
  904. if ($dest_template)
  905. {
  906. $sql = 'SELECT template_inherit_path FROM ' . STYLES_TEMPLATE_TABLE . "
  907. WHERE template_path = '" . $db->sql_escape($dest_template) . "'";
  908. $result = $db->sql_query($sql);
  909. global $dest_inherits;
  910. $dest_inherits = '';
  911. if ($row = $db->sql_fetchrow($result))
  912. {
  913. $dest_inherits = $row['template_inherit_path'];
  914. }
  915. $db->sql_freeresult($result);
  916. if (!empty($actions['EDITS']))
  917. {
  918. foreach ($actions['EDITS'] as $file => $edits)
  919. {
  920. if (strpos($file, 'styles/') === false)
  921. {
  922. unset($actions['EDITS'][$file]);
  923. }
  924. else if ($src_template != $dest_template)
  925. {
  926. $file_new = str_replace($src_template, $dest_template, $file);
  927. $actions['EDITS'][$file_new] = $edits;
  928. unset($actions['EDITS'][$file]);
  929. }
  930. }
  931. }
  932. if (!empty($actions['NEW_FILES']))
  933. {
  934. foreach ($actions['NEW_FILES'] as $src_file => $dest_file)
  935. {
  936. if (strpos($src_file, 'styles/') === false)
  937. {
  938. unset($actions['NEW_FILES']);
  939. }
  940. else
  941. {
  942. $actions['NEW_FILES'][$src_file] = str_replace($src_template, $dest_template, $dest_file);
  943. }
  944. }
  945. }
  946. }
  947. // only supporting one level of hierarchy here
  948. if (!$parent)
  949. {
  950. // check for "child" MODX files and attempt to decide which ones we need
  951. $children = $this->find_children($mod_path);
  952. $elements = array('language' => array(), 'template' => array());
  953. if ($execute_edits)
  954. {
  955. global $mode;
  956. $this->handle_dependency($children, $mode, $mod_path);
  957. }
  958. $this->handle_language_prompt($children, $elements, $action);
  959. $this->handle_merge('language', $actions, $children, $elements['language']);
  960. $this->handle_template_prompt($children, $elements, $action);
  961. $this->handle_merge('template', $actions, $children, $elements['template']);
  962. }
  963. else
  964. {
  965. if ($dest_template)
  966. {
  967. $elements['template'] = array($dest_template);
  968. }
  969. elseif ($mod_language)
  970. {
  971. $elements['language'] = array($mod_path);
  972. }
  973. else
  974. {
  975. $elements['contrib'] = array($mod_path);
  976. }
  977. }
  978. $template->assign_vars(array(
  979. 'S_INSTALL' => $execute_edits,
  980. 'S_PRE_INSTALL' => !$execute_edits,
  981. 'MOD_PATH' => str_replace($this->mod_root, '', $mod_path),
  982. 'U_INSTALL' => $this->u_action . '&amp;action=install' . ($parent ? "&amp;parent=$parent" : ''),
  983. 'U_RETURN' => $this->u_action,
  984. 'U_BACK' => $this->u_action,
  985. ));
  986. if ($execute_edits)
  987. {
  988. $editor->create_edited_root($this->edited_root);
  989. $force_install = request_var('force', false);
  990. }
  991. $display = ($execute_edits || $config['preview_changes']) ? true : false;
  992. // process the actions
  993. $mod_installed = $this->process_edits($editor, $actions, $details, $execute_edits, $display, false);
  994. if (!$execute_edits)
  995. {
  996. $s_hidden_fields = array('dependency_confirm' => !empty($_REQUEST['dependency_confirm']));
  997. if ($dest_template)
  998. {
  999. $s_hidden_fields['dest'] = $dest_template;
  1000. $s_hidden_fields['source'] = $mod_path;
  1001. $s_hidden_fields['template_submit'] = true;
  1002. }
  1003. if ($parent)
  1004. {
  1005. $s_hidden_fields['type'] = $modx_type;
  1006. }
  1007. $template->assign_var('S_HIDDEN_FIELDS', build_hidden_fields($s_hidden_fields));
  1008. add_form_key('acp_mods');
  1009. return;
  1010. } // end pre_install
  1011. // Display Do-It-Yourself Actions...per the MODX spec, these should be displayed last
  1012. if (!empty($actions['DIY_INSTRUCTIONS']))
  1013. {
  1014. $template->assign_var('S_DIY', true);
  1015. if (!is_array($actions['DIY_INSTRUCTIONS']))
  1016. {
  1017. $actions['DIY_INSTRUCTIONS'] = array($actions['DIY_INSTRUCTIONS']);
  1018. }
  1019. foreach ($actions['DIY_INSTRUCTIONS'] as $instruction)
  1020. {
  1021. $template->assign_block_vars('diy_instructions', array(
  1022. 'DIY_INSTRUCTION' => nl2br($instruction),
  1023. ));
  1024. }
  1025. }
  1026. if (!empty($actions['PHP_INSTALLER']))
  1027. {
  1028. $template->assign_vars(array(
  1029. 'U_PHP_INSTALLER' => $phpbb_root_path . $actions['PHP_INSTALLER'],
  1030. ));
  1031. }
  1032. if ($mod_installed || $force_install)
  1033. {
  1034. // Move edited files back
  1035. $status = $editor->commit_changes($this->edited_root, '');
  1036. if (is_string($status))
  1037. {
  1038. $mod_installed = false;
  1039. $template->assign_block_vars('error', array(
  1040. 'ERROR' => $status,
  1041. ));
  1042. }
  1043. }
  1044. // The editor class provides more pertinent information regarding edits
  1045. // so we store that as the canonical version, used for uninstalling
  1046. $actions['EDITS'] = $editor->mod_actions;
  1047. $editor->clear_actions();
  1048. // if MOD installed successfully, make a record.
  1049. if (($mod_installed || $force_install) && !$parent)
  1050. {
  1051. $mod_name = (is_array($details['MOD_NAME'])) ? serialize($details['MOD_NAME']) : $details['MOD_NAME'];
  1052. // Insert database data
  1053. $sql = 'INSERT INTO ' . MODS_TABLE . ' ' . $db->sql_build_array('INSERT', array(
  1054. 'mod_time' => (int) $editor->install_time,
  1055. // @todo: Are dependencies part of the MODX Spec?
  1056. 'mod_dependencies' => '', //(string) serialize($details['MOD_DEPENDENCIES']),
  1057. 'mod_name' => (string) $mod_name,
  1058. 'mod_description' => (string) $details['MOD_DESCRIPTION'],
  1059. 'mod_version' => (string) $details['MOD_VERSION'],
  1060. 'mod_path' => (string) $details['MOD_PATH'],
  1061. 'mod_author_notes' => (string) $details['AUTHOR_NOTES'],
  1062. 'mod_author_name' => (string) $details['AUTHOR_DETAILS'][0]['AUTHOR_NAME'],
  1063. 'mod_author_email' => (string) $details['AUTHOR_DETAILS'][0]['AUTHOR_EMAIL'],
  1064. 'mod_author_url' => (string) $details['AUTHOR_DETAILS'][0]['AUTHOR_WEBSITE'],
  1065. 'mod_actions' => (string) serialize($actions),
  1066. 'mod_languages' => (string) (isset($elements['language']) && sizeof($elements['language'])) ? implode(',', $elements['language']) : '',
  1067. 'mod_template' => (string) (isset($elements['template']) && sizeof($elements['template'])) ? implode(',', $elements['template']) : '',
  1068. 'mod_contribs' => (string) (isset($elements['contrib']) && sizeof($elements['contrib'])) ? implode(',', $elements['contrib']) : '',
  1069. ));
  1070. $db->sql_query($sql);
  1071. $cache->purge();
  1072. // Add log
  1073. $mod_name = localize_title($details['MOD_NAME'], 'en');
  1074. add_log('admin', 'LOG_MOD_ADD', $mod_name);
  1075. }
  1076. // in this case, we are installing an additional template or language
  1077. else if (($mod_installed || $force_install) && $parent)
  1078. {
  1079. $sql = 'SELECT * FROM ' . MODS_TABLE . " WHERE mod_id = $parent";
  1080. $result = $db->sql_query($sql);
  1081. $row = $db->sql_fetchrow($result);
  1082. $db->sql_freeresult($result);
  1083. if (!$row)
  1084. {
  1085. trigger_error($user->lang['NO_MOD'] . adm_back_link($this->u_action));
  1086. }
  1087. $sql_ary = array(
  1088. 'mod_version' => $details['MOD_VERSION'],
  1089. );
  1090. if (!empty($elements['language']))
  1091. {
  1092. $sql_ary['mod_languages'] = (!empty($row['mod_languages'])) ? $row['mod_languages'] . ',' : '';
  1093. $sql_ary['mod_languages'] .= implode(',', $elements['language']);
  1094. }
  1095. else
  1096. {
  1097. $sql_ary['mod_languages'] = $row['mod_languages'];
  1098. }
  1099. if (!empty($elements['template']))
  1100. {
  1101. $sql_ary['mod_template'] = $row['mod_template'] . ',' . implode(',', $elements['template']);
  1102. }
  1103. else
  1104. {
  1105. $sql_ary['mod_template'] = $row['mod_template'];
  1106. }
  1107. if (!empty($elements['contrib']))
  1108. {
  1109. $sql_ary['mod_contribs'] = (!empty($row['mod_contribs'])) ? $row['mod_contribs'] . ',' : '';
  1110. $sql_ary['mod_contribs'] .= implode(',', $elements['contrib']);
  1111. }
  1112. else
  1113. {
  1114. $sql_ary['mod_contribs'] = $row['mod_contribs'];
  1115. }
  1116. $sql_ary['mod_time'] = $editor->install_time;
  1117. $prior_mod_actions = unserialize($row['mod_actions']);
  1118. $sql_ary['mod_actions'] = serialize(array_merge_recursive($prior_mod_actions, $actions));
  1119. unset($prior_mod_actions);
  1120. $sql = 'UPDATE ' . MODS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . "
  1121. WHERE mod_id = $parent";
  1122. $db->sql_query($sql);
  1123. add_log('admin', 'LOG_MOD_CHANGE', htmlspecialchars_decode($row['mod_name']));
  1124. }
  1125. // there was an error we need to tell the user about
  1126. else
  1127. {
  1128. add_form_key('acp_mods');
  1129. if ($parent)
  1130. {
  1131. $hidden_ary['parent'] = $parent;
  1132. }
  1133. if ($dest_template)
  1134. {
  1135. $hidden_ary['dest'] = $dest_template;
  1136. $hidden_ary['source'] = $mod_path;
  1137. $hidden_ary['template_submit'] = true;
  1138. }
  1139. if ($mod_language || $mod_contribs)
  1140. {
  1141. $hidden_ary['type'] = $modx_type;
  1142. }
  1143. $template->assign_vars(array(
  1144. 'S_ERROR' => true,
  1145. 'S_HIDDEN_FIELDS' => build_hidden_fields($hidden_ary),
  1146. 'U_RETRY' => $this->u_action . '&amp;action=install&amp;mod_path=' . $mod_path,
  1147. ));
  1148. }
  1149. // if we forced the install of the MOD, we need to let the user know their board could be broken
  1150. if ($force_install)
  1151. {
  1152. $template->assign_var('S_FORCE', true);
  1153. }
  1154. if ($mod_installed || $force_install)
  1155. {
  1156. // $editor->commit_changes_final don't do anything ATM, but to be compatible with future versions
  1157. $mod_name = localize_title($details['MOD_NAME'], 'en');
  1158. $editor->commit_changes_final('mod_' . $editor->install_time, str_replace(' ', '_', $mod_name));
  1159. }
  1160. }
  1161. /**
  1162. * Uninstall/pre uninstall a mod
  1163. */
  1164. function uninstall($action, $mod_id, $parent)
  1165. {
  1166. global $phpbb_root_path, $phpEx, $db, $template, $user, $config;
  1167. global $force_install, $mod_uninstalled;
  1168. if (!$mod_id && !$parent)
  1169. {
  1170. return false; // ERROR
  1171. }
  1172. // the MOD is more important than additional MODx files
  1173. if ($parent == $mod_id)
  1174. {
  1175. $parent = 0;
  1176. }
  1177. if ($parent)
  1178. {
  1179. // grab installed contrib and language items from the database
  1180. $sql = 'SELECT mod_languages, mod_contribs
  1181. FROM ' . MODS_TABLE . "
  1182. WHERE mod_id = $parent";
  1183. $result = $db->sql_query($sql);
  1184. if ($row = $db->sql_fetchrow($result))
  1185. {
  1186. $mod_path = request_var('mod_path', '');
  1187. if (in_array($mod_path, explode(',', $row['mod_languages'])))
  1188. {
  1189. $elements['languages'] = $mod_path;
  1190. }
  1191. elseif (in_array($mod_path, explode(',', $row['mod_contribs'])))
  1192. {
  1193. $elements['contrib'] = $mod_path;
  1194. }
  1195. else
  1196. {
  1197. trigger_error('AM_MOD_NOT_INSTALLED');
  1198. }
  1199. }
  1200. else
  1201. {
  1202. return false;
  1203. }
  1204. }
  1205. // set the class parameters to reflect the proper directory
  1206. $sql = 'SELECT mod_path FROM ' . MODS_TABLE . '
  1207. WHERE mod_id = ' . (($mod_id) ? $mod_id : $parent);
  1208. $result = $db->sql_query($sql);
  1209. if ($row = $db->sql_fetchrow($result))
  1210. {
  1211. $this->mod_root = dirname($row['mod_path']) . '/';
  1212. $this->edited_root = "{$this->mod_root}_edited/";
  1213. }
  1214. else
  1215. {
  1216. return false; // ERROR
  1217. }
  1218. $execute_edits = ($action == 'pre_uninstall') ? false : true;
  1219. $write_method = 'editor_' . determine_write_method(!$execute_edits);
  1220. $editor = new $write_method();
  1221. // get FTP information if we need it (or initialize array $hidden_ary)
  1222. $hidden_ary = get_connection_info(!$execute_edits);
  1223. if ($parent)
  1224. {
  1225. $hidden_ary['parent'] = $parent;
  1226. $hidden_ary['mod_path'] = $mod_path;
  1227. }
  1228. $template->assign_vars(array(
  1229. 'S_UNINSTALL' => $execute_edits,
  1230. 'S_PRE_UNINSTALL' => !$execute_edits,
  1231. 'L_FORCE_INSTALL' => $user->lang['FORCE_UNINSTALL'],
  1232. 'MOD_ID' => $mod_id,
  1233. 'U_UNINSTALL' => ($parent) ? $this->u_action . "&amp;action=uninstall&amp;parent=$parent&mod_path=$mod_path" : $this->u_action . '&amp;action=uninstall&amp;mod_id=' . $mod_id,
  1234. 'U_RETURN' => $this->u_action,
  1235. 'U_BACK' => $this->u_action,
  1236. 'S_HIDDEN_FIELDS' => build_hidden_fields($hidden_ary),
  1237. ));
  1238. // grab actions and details
  1239. if (!$parent)
  1240. {
  1241. $details = $this->mod_details($mod_id, false, true);
  1242. $actions = $this->mod_actions($mod_id);
  1243. }
  1244. else
  1245. {
  1246. $details = $this->mod_details($mod_path, false);
  1247. $actions = $this->mod_actions($mod_path);
  1248. }
  1249. if ($execute_edits)
  1250. {
  1251. $editor->create_edited_root($this->edited_root);
  1252. $force_install = $force_uninstall = request_var('force', false);
  1253. }
  1254. $display = ($execute_edits || $config['preview_changes']) ? true : false;
  1255. // cleanup edits if we forced the install on a contrib or language
  1256. if ($parent)
  1257. {
  1258. if (isset($actions['EDITS']))
  1259. {
  1260. foreach ($actions['EDITS'] as $file => $edit_ary)
  1261. {
  1262. foreach ($edit_ary as $edit_id => $edit)
  1263. {
  1264. foreach ($edit as $find => $action_ary)
  1265. {
  1266. if (empty($action_ary))
  1267. {
  1268. unset($actions['EDITS'][$file][$edit_id][$find]);
  1269. }
  1270. }
  1271. }
  1272. }
  1273. }
  1274. }
  1275. // process the actions
  1276. $mod_uninstalled = $this->process_edits($editor, $actions, $details, $execute_edits, $display, true);
  1277. if (!$execute_edits)
  1278. {
  1279. return;
  1280. } // end pre_uninstall
  1281. if (($mod_uninstalled || $force_uninstall) && !$parent)
  1282. {
  1283. // Move edited files back
  1284. $status = $editor->commit_changes($this->edited_root, '');
  1285. if (is_string($status))
  1286. {
  1287. $mod_uninstalled = false;
  1288. $template->assign_block_vars('error', array(
  1289. 'ERROR' => $status,
  1290. ));
  1291. }
  1292. }
  1293. /*
  1294. elseif (($mod_uninstalled || $force_uninstall) && $parent)
  1295. {
  1296. // Only update the database entries and don't move any files back
  1297. $sql = 'SELECT * FROM ' . MODS_TABLE . " WHERE mod_id = $parent";
  1298. $result = $db->sql_query($sql);
  1299. $row = $db->sql_fetchrow($result);
  1300. $db->sql_freeresult($result);
  1301. if (!$row)
  1302. {
  1303. trigger_error($user->lang['NO_MOD'] . adm_back_link($this->u_action));
  1304. }
  1305. $sql_ary = array(
  1306. 'mod_version' => $details['MOD_VERSION'],
  1307. );
  1308. if (!empty($elements['languages']))
  1309. {
  1310. $sql_ary['mod_languages'] = explode(',', $row['mod_languages']);
  1311. foreach ($sql_ary['mod_languages'] as $key => $value)
  1312. {
  1313. if ($value != $elements['languages']);
  1314. {
  1315. unset($sql_ary['mod_languages'][$key]);
  1316. }
  1317. }
  1318. $sql_ary['mod_languages'] = implode(',', $sql_ary['mod_languages']);
  1319. }
  1320. else
  1321. {
  1322. $sql_ary['mod_languages'] = $row['mod_languages'];
  1323. }
  1324. // let's just not support uninstalling styles edits
  1325. $sql_ary['mod_template'] = $row['mod_template'];
  1326. if (!empty($elements['contrib']))
  1327. {
  1328. $sql_ary['mod_contribs'] = explode(',', $row['mod_contribs']);
  1329. foreach ($sql_ary['mod_contribs'] as $key => $value)
  1330. {
  1331. if ($value != $elements['contrib']);
  1332. {
  1333. unset($sql_ary['mod_contribs'][$key]);
  1334. }
  1335. }
  1336. $sql_ary['mod_contribs'] = implode(',', $sql_ary['mod_contribs']);
  1337. }
  1338. else
  1339. {
  1340. $sql_ary['mod_contribs'] = $row['mod_contribs'];
  1341. }
  1342. $sql_ary['mod_time'] = $row['mod_time'];
  1343. //$prior_mod_actions = unserialize($row['mod_actions']);
  1344. //$sql_ary['mod_actions'] = serialize(array_merge_recursive($prior_mod_actions, $actions));
  1345. $sql_ary['mod_actions'] = $row['mod_actions'];
  1346. //unset($prior_mod_actions);
  1347. $sql = 'UPDATE ' . MODS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . "
  1348. WHERE mod_id = $parent";
  1349. $db->sql_query($sql);
  1350. $mod_name = localize_title($row['mod_name'], $user->data['user_lang']);
  1351. add_log('admin', 'LOG_MOD_CHANGE', htmlspecialchars_decode($mod_name));
  1352. }
  1353. */
  1354. // if we forced uninstall of the MOD, we need to let the user know their board could be broken
  1355. if ($force_uninstall)
  1356. {
  1357. $template->assign_var('S_FORCE', true);
  1358. }
  1359. /*
  1360. else if (!$mod_uninstalled)
  1361. {
  1362. add_form_key('acp_mods');
  1363. $template->assign_vars(array(
  1364. 'S_ERROR' => true,
  1365. 'S_HIDDEN_FIELDS' => build_hidden_fields($hidden_ary),
  1366. 'U_RETRY' => $this->u_action . '&amp;action=uninstall&amp;mod_id=' . $mod_id,
  1367. ));
  1368. }
  1369. */
  1370. if ($mod_uninstalled || $force_uninstall)
  1371. {
  1372. // Delete from DB
  1373. $sql = 'DELETE FROM ' . MODS_TABLE . '
  1374. WHERE mod_id = ' . $mod_id;
  1375. $db->sql_query($sql);
  1376. // Add log
  1377. $mod_name = localize_title($details['MOD_NAME'], 'en');
  1378. $mod_name = htmlspecialchars_decode($mod_name);
  1379. add_log('admin', 'LOG_MOD_REMOVE', $mod_name);
  1380. $mod_name = localize_title($details['MOD_NAME'], 'en');
  1381. $editor->commit_changes_final('mod_' . $editor->install_time, str_replace(' ', '_', $mod_name));
  1382. }
  1383. }
  1384. /**
  1385. * Returns array of available mod install files in dir (Recursive)
  1386. * @param $dir string - dir to search
  1387. * @param $recurse int - number of levels to recurse
  1388. */
  1389. function find_mods($dir, $recurse = false)
  1390. {
  1391. global $user;
  1392. if ($recurse === false)
  1393. {
  1394. $mods = array('main' => array(), 'contrib' => array(), 'template' => array(), 'language' => array());
  1395. $recurse = 0;
  1396. }
  1397. else
  1398. {
  1399. static $mods = array('main' => array(), 'contrib' => array(), 'template' => array(), 'language' => array());
  1400. }
  1401. // ltrim shouldn't be needed, but some users had problems. See #44305
  1402. $dir = ltrim($dir, '/');
  1403. if (!file_exists($dir))
  1404. {
  1405. return array();
  1406. }
  1407. if (!is_readable($dir))
  1408. {
  1409. trigger_error(sprintf($user->lang['NEED_READ_PERMISSIONS'], $dir), E_USER_WARNING);
  1410. }
  1411. $dp = opendir($dir);
  1412. while (($file = readdir($dp)) !== false)
  1413. {
  1414. if ($file[0] != '.' && strpos("$dir/$file", '_edited') === false && strpos("$dir/$file", '_backups') === false)
  1415. {
  1416. // recurse - we don't want anything within the MODX "root" though
  1417. if ($recurse && !is_file("$dir/$file") && strpos("$dir/$file", 'root') === false)
  1418. {
  1419. $mods = array_merge($mods, $this->find_mods("$dir/$file", $recurse - 1));
  1420. }
  1421. // this might be better as an str function, especially being in a loop
  1422. else if (preg_match('#.*install.*xml$#i', $file) || (preg_match('#(contrib|templates|languages)#i', $dir, $match)) || ($recurse === 0 && strpos($file, '.xml') !== false))
  1423. {
  1424. // if this is an "extra" MODX file, make a record of it as such
  1425. // we are assuming the MOD follows MODX packaging standards here
  1426. if (strpos($file, '.xml') !== false && preg_match('#(contrib|templates|languages)#i', $dir, $match))
  1427. {
  1428. // Get rid of the S. This is a side effect of understanding
  1429. // MODX 1.0.x and 1.2.x.
  1430. $match[1] = rtrim($match[1], 's');
  1431. $mods[$match[1]][] = array(
  1432. 'href' => "$dir/$file",
  1433. 'realname' => core_basename($file),
  1434. 'title' => core_basename($file),
  1435. );
  1436. }
  1437. else
  1438. {
  1439. $check = end($mods['main']);
  1440. $check = $check['href'];
  1441. // we take the first file alphabetically with install in the filename
  1442. if (!$check || dirname($check) == $dir)
  1443. {
  1444. if (preg_match('#.*install.*xml$#i', $file) && preg_match('#.*install.*xml$#i', $check) && strnatcasecmp(basename($check), $file) > 0)
  1445. {
  1446. $index = max(0, sizeof($mods['main']) - 1);
  1447. $mods['main'][$index] = array(
  1448. 'href' => "$dir/$file",
  1449. 'realname' => core_basename($file),
  1450. 'title' => core_basename($file),
  1451. );
  1452. break;
  1453. }
  1454. else if (preg_match('#.*install.*xml$#i', $file) && !preg_match('#.*install.*xml$#i', $check))
  1455. {
  1456. $index = max(0, sizeof($mods['main']) - 1);
  1457. $mods['main'][$index] = array(
  1458. 'href' => "$dir/$file",
  1459. 'realname' => core_basename($file),
  1460. 'title' => core_basename($file),
  1461. );
  1462. break;
  1463. }
  1464. }
  1465. else
  1466. {
  1467. if (strpos($file, '.xml') !== false)
  1468. {
  1469. $mods['main'][] = array(
  1470. 'href' => "$dir/$file",
  1471. 'realname' => core_basename($file),
  1472. 'title' => core_basename($file),
  1473. );
  1474. }
  1475. }
  1476. }
  1477. }
  1478. }
  1479. }
  1480. closedir($dp);
  1481. return $mods;
  1482. }
  1483. function process_edits($editor, $actions, $details, $change = false, $display = true, $reverse = false)
  1484. {
  1485. global $template, $user, $db, $phpbb_root_path, $force_install, $mod_installed;
  1486. global $dest_inherits, $dest_template, $children, $config;
  1487. $mod_installed = true;
  1488. if ($reverse)
  1489. {
  1490. global $rev_actions;
  1491. if (empty($rev_actions))
  1492. {
  1493. // maybe should allow for potential extensions here
  1494. $actions = parser::reverse_edits($actions);
  1495. }
  1496. else
  1497. {
  1498. $actions = $rev_actions;
  1499. unset($rev_actions);
  1500. }
  1501. }
  1502. $template->assign_vars(array(
  1503. 'S_DISPLAY_DETAILS' => (bool) $display,
  1504. 'S_CHANGE_FILES' => (bool) $change,
  1505. ));
  1506. /*
  1507. if (!empty($details['PHPBB_VERSION']) && $details['PHPBB_VERSION'] != $config['version'])
  1508. {
  1509. $version_warnig = sprintf($user->lang['VERSION_WARNING'], $details['PHPBB_VERSION'], $config['version']);
  1510. $template->assign_vars(array(
  1511. 'VERSION_WARNING' => $version_warnig,
  1512. 'S_PHPBB_VESION' => true,
  1513. ));
  1514. }
  1515. */
  1516. if (!empty($details['AUTHOR_NOTES']) && $details['AUTHOR_NOTES'] != $user->lang['UNKNOWN_MOD_AUTHOR-NOTES'])
  1517. {
  1518. $template->assign_vars(array(
  1519. 'S_AUTHOR_NOTES' => true,
  1520. 'AUTHOR_NOTES' => nl2br($details['AUTHOR_NOTES']),
  1521. ));
  1522. }
  1523. // not all MODs will have edits (!)
  1524. if (isset($actions['EDITS']))
  1525. {
  1526. $template->assign_var('S_EDITS', true);
  1527. foreach ($actions['EDITS'] as $filename => $edits)
  1528. {
  1529. // see if the file to be opened actually exists
  1530. if (!file_exists("$phpbb_root_path$filename"))
  1531. {
  1532. $is_inherit = (strpos($filename, 'styles/') !== false && !empty($dest_inherits)) ? true : false;
  1533. $template->assign_block_vars('edit_files', array(
  1534. 'S_MISSING_FILE' => ($is_inherit) ? false : true,
  1535. 'INHERIT_MSG' => ($is_inherit) ? sprintf($user->lang['INHERIT_NO_CHANGE'], $dest_template, $dest_inherits) : '',
  1536. 'FILENAME' => $filename,
  1537. ));
  1538. $mod_installed = ($is_inherit) ? $mod_installed : false;
  1539. continue;
  1540. }
  1541. else
  1542. {
  1543. $template->assign_block_vars('edit_files', array(
  1544. 'S_SUCCESS' => false,
  1545. 'FILENAME' => $filename,
  1546. ));
  1547. // If installing - not pre_install nor (pre_)uninstall, backup the file
  1548. // This is to make sure it works with editor_ftp because write_method is
  1549. // forced to direct when in preview modes, and ignored in editor_manual!
  1550. if ($change && !$reverse)
  1551. {
  1552. $status = $editor->open_file($filename, $this->backup_root);
  1553. }
  1554. else
  1555. {
  1556. $status = $editor->open_file($filename);
  1557. }
  1558. $edit_success = true;
  1559. if (is_string($status))
  1560. {
  1561. $template->assign_block_vars('error', array(
  1562. 'ERROR' => $status,
  1563. ));
  1564. $mod_installed = false;
  1565. continue;
  1566. }
  1567. $edit_success = true;
  1568. foreach ($edits as $finds)
  1569. {
  1570. $comment = '';
  1571. foreach ($finds as $find => $commands)
  1572. {
  1573. if (isset($finds['comment']) && !$comment && $finds['comment'] != $user->lang['UNKNOWN_MOD_COMMENT'])
  1574. {
  1575. $comment = $finds['comment'];
  1576. unset($finds['comment']);
  1577. }
  1578. if ($find == 'comment')
  1579. {
  1580. continue;
  1581. }
  1582. $find_tpl = array(
  1583. 'FIND_STRING' => htmlspecialchars($find),
  1584. 'COMMENT' => htmlspecialchars($comment),
  1585. );
  1586. $offset_ary = $editor->find($find);
  1587. // special case for FINDs with no action associated
  1588. if (is_null($commands))
  1589. {
  1590. continue;
  1591. }
  1592. foreach ($commands as $type => $contents)
  1593. {
  1594. if (!$offset_ary)
  1595. {
  1596. $offset_ary['start'] = $offset_ary['end'] = false;
  1597. }
  1598. $status = false;
  1599. $inline_template_ary = array();
  1600. $contents_orig = $contents;
  1601. switch (strtoupper($type))
  1602. {
  1603. case 'AFTER ADD':
  1604. $status = $editor->add_string($find, $contents, 'AFTER', $offset_ary['start'], $offset_ary['end']);
  1605. break;
  1606. case 'BEFORE ADD':
  1607. $status = $editor->add_string($find, $contents, 'BEFORE', $offset_ary['start'], $offset_ary['end']);
  1608. break;
  1609. case 'INCREMENT':
  1610. case 'OPERATION':
  1611. //$contents = "";
  1612. $status = $editor->inc_string($find, '', $contents);
  1613. break;
  1614. case 'REPLACE WITH':
  1615. $status = $editor->replace_string($find, $contents, $offset_ary['start'], $offset_ary['end']);
  1616. break;
  1617. case 'IN-LINE-EDIT':
  1618. // these aren't quite as straight forward. Still have multi-level arrays to sort through
  1619. $inline_comment = '';
  1620. foreach ($contents as $inline_edit_id => $inline_edit)
  1621. {
  1622. if ($inline_edit_id === 'inline-comment')
  1623. {
  1624. // This is a special case for tucking comments in the array
  1625. if ($inline_edit != $user->lang['UNKNOWN_MOD_INLINE-COMMENT'])
  1626. {
  1627. $inline_comment = $inline_edit;
  1628. }
  1629. continue;
  1630. }
  1631. foreach ($inline_edit as $inline_find => $inline_commands)
  1632. {
  1633. foreach ($inline_commands as $inline_action => $inline_contents)
  1634. {
  1635. // inline finds are pretty cantankerous, so do them in the loop
  1636. $line = $editor->inline_find($find, $inline_find, $offset_ary['start'], $offset_ary['end']);
  1637. if (!$line)
  1638. {
  1639. // find failed
  1640. $status = $mod_installed = false;
  1641. $inline_template_ary[] = array(
  1642. 'FIND' => array(
  1643. 'S_SUCCESS' => $status,
  1644. 'NAME' => $user->lang[$type],
  1645. 'COMMAND' => htmlspecialchars($inline_find),
  1646. ),
  1647. 'ACTION' => array());
  1648. continue 2;
  1649. }
  1650. $inline_contents = $inline_contents[0];
  1651. $contents_orig = $inline_find;
  1652. switch (strtoupper($inline_action))
  1653. {
  1654. case 'IN-LINE-':
  1655. $editor->last_string_offset = $line['string_offset'] + $line['find_length'] - 1;
  1656. $status = true;
  1657. continue 2;
  1658. break;
  1659. case 'IN-LINE-BEFORE-ADD':
  1660. $status = $editor->inline_add($find, $inline_find, $inline_contents, 'BEFORE', $line['array_offset'], $line['string_offset'], $line['find_length']);
  1661. break;
  1662. case 'IN-LINE-AFTER-ADD':
  1663. $status = $editor->inline_add($find, $inline_find, $inline_contents, 'AFTER', $line['array_offset'], $line['string_offset'], $line['find_length']);
  1664. break;
  1665. case 'IN-LINE-REPLACE':
  1666. case 'IN-LINE-REPLACE-WITH':
  1667. $status = $editor->inline_replace($find, $inline_find, $inline_contents, $line['array_offset'], $line['string_offset'], $line['find_length']);
  1668. break;
  1669. case 'IN-LINE-OPERATION':
  1670. $status = $editor->inc_string($find, $inline_find, $inline_contents);
  1671. break;
  1672. default:
  1673. $message = sprintf($user->lang['UNRECOGNISED_COMMAND'], $inline_action);
  1674. trigger_error($message, E_USER_WARNING); // ERROR!
  1675. break;
  1676. }
  1677. $inline_template_ary[] = array(
  1678. 'FIND' => array(
  1679. 'S_SUCCESS' => $status,
  1680. 'NAME' => $user->lang[$type],
  1681. 'COMMAND' => (is_array($contents_orig)) ? $user->lang['INVALID_MOD_INSTRUCTION'] : htmlspecialchars($contents_orig),
  1682. ),
  1683. 'ACTION' => array(
  1684. 'S_SUCCESS' => $status,
  1685. 'NAME' => $user->lang[$inline_action],
  1686. 'COMMAND' => (is_array($inline_contents)) ? $user->lang['INVALID_MOD_INSTRUCTION'] : htmlspecialchars($inline_contents),
  1687. // 'COMMENT' => $inline_comment, (inline comments aren't actually part of the MODX spec)
  1688. ),
  1689. );
  1690. }
  1691. if (!$status)
  1692. {
  1693. $mod_installed = false;
  1694. }
  1695. $editor->close_inline_edit();
  1696. }
  1697. }
  1698. break;
  1699. default:
  1700. $message = sprintf($user->lang['UNRECOGNISED_COMMAND'], $type);
  1701. trigger_error($message, E_USER_WARNING); // ERROR!
  1702. break;
  1703. }
  1704. $template->assign_block_vars('edit_files.finds', array_merge($find_tpl, array(
  1705. 'S_SUCCESS' => $status,
  1706. )));
  1707. if (!$status)
  1708. {
  1709. $edit_success = false;
  1710. $mod_installed = false;
  1711. }
  1712. if (sizeof($inline_template_ary))
  1713. {
  1714. foreach ($inline_template_ary as $inline_template)
  1715. {
  1716. // We must assign the vars for the FIND first
  1717. $template->assign_block_vars('edit_files.finds.actions', $inline_template['FIND']);
  1718. // And now the vars for the ACTION
  1719. $template->assign_block_vars('edit_files.finds.actions.inline', $inline_template['ACTION']);
  1720. }
  1721. $inline_template_ary = array();
  1722. }
  1723. else if (!is_array($contents_orig))
  1724. {
  1725. $template->assign_block_vars('edit_files.finds.actions', array(
  1726. 'S_SUCCESS' => $status,
  1727. 'NAME' => $user->lang[$type],
  1728. 'COMMAND' => htmlspecialchars($contents_orig),
  1729. ));
  1730. }
  1731. }
  1732. }
  1733. $editor->close_edit();
  1734. }
  1735. $template->alter_block_array('edit_files', array(
  1736. 'S_SUCCESS' => $edit_success,
  1737. ), true, 'change');
  1738. }
  1739. if ($change)
  1740. {
  1741. $status = $editor->close_file("{$this->edited_root}$filename");
  1742. if (is_string($status))
  1743. {
  1744. $template->assign_block_vars('error', array(
  1745. 'ERROR' => $status,
  1746. ));
  1747. $mod_installed = false;
  1748. }
  1749. }
  1750. }
  1751. } // end foreach
  1752. if (!$mod_installed)
  1753. {
  1754. $template->assign_var('S_DISPLAY_FILE_EDITS', true);
  1755. }
  1756. // Move included files
  1757. if (isset($actions['NEW_FILES']) && !empty($actions['NEW_FILES']))
  1758. {
  1759. $template->assign_var('S_NEW_FILES', true);
  1760. // Because foreach operates on a copy of the specified array and not the array itself,
  1761. // we cannot rely on the array pointer while using it, so we use a while loop w/ each()
  1762. // We need array pointer to rewind the loop when is_array($target) (See Ticket #62341)
  1763. while (list($source, $target) = each($actions['NEW_FILES']))
  1764. {
  1765. if (is_array($target))
  1766. {
  1767. // If we've shifted off all targets, we're done w/ that element
  1768. if (empty($target))
  1769. {
  1770. continue;
  1771. }
  1772. // Shift off first target, then rewind array pointer to get next target
  1773. $target = array_shift($actions['NEW_FILES'][$source]);
  1774. prev($actions['NEW_FILES']);
  1775. }
  1776. /* if ($change && ($mod_installed || $force_install))
  1777. {
  1778. */ $status = $editor->copy_content($this->mod_root . str_replace('*.*', '', $source), str_replace('*.*', '', $target));
  1779. if ($status !== true && !is_null($status))
  1780. {
  1781. $mod_installed = false;
  1782. $template->assign_var('S_DISPLAY_NEW_FILES', true);
  1783. }
  1784. $template->assign_block_vars('new_files', array(
  1785. 'S_SUCCESS' => ($status === true) ? true : false,
  1786. 'S_NO_COPY_ATTEMPT' => (is_null($status)) ? true : false,
  1787. 'SOURCE' => $source,
  1788. 'TARGET' => $target,
  1789. ));
  1790. /* }
  1791. else if ($display && !$change)
  1792. {
  1793. $template->assign_block_vars('new_files', array(
  1794. 'SOURCE' => $source,
  1795. 'TARGET' => $target,
  1796. ));
  1797. }
  1798. // To avoid "error" on install page when being asked to force install
  1799. else if ($change && $display && !$mod_installed && !$force_install)
  1800. {
  1801. $template->assign_block_vars('new_files', array(
  1802. 'S_NO_COPY_ATTEMPT' => true,
  1803. 'FILENAME' => $target,
  1804. ));
  1805. }
  1806. */ }
  1807. }
  1808. // Delete (or reverse-delete) installed files
  1809. if (!empty($actions['DELETE_FILES']))
  1810. {
  1811. $template->assign_var('S_REMOVING_FILES', true);
  1812. // Dealing with a reverse-delete, must heed to the dangers ahead!
  1813. if ($reverse)
  1814. {
  1815. $directories = array();
  1816. $directories['src'] = array();
  1817. $directories['dst'] = array();
  1818. $directories['del'] = array();
  1819. // Because foreach operates on a copy of the specified array and not the array itself,
  1820. // we cannot rely on the array pointer while using it, so we use a while loop w/ each()
  1821. // We need array pointer to rewind the loop when is_array($target) (See Ticket #62341)
  1822. while (list($source, $target) = each($actions['DELETE_FILES']))
  1823. {
  1824. if (is_array($target))
  1825. {
  1826. // If we've shifted off all targets, we're done w/ that element
  1827. if (empty($target))
  1828. {
  1829. continue;
  1830. }
  1831. // Shift off first target, then rewind array pointer to get next target
  1832. $target = array_shift($actions['DELETE_FILES'][$source]);
  1833. prev($actions['DELETE_FILES']);
  1834. }
  1835. // Some MODs include 'umil/', avoid deleting!
  1836. if (strpos($target, 'umil/') === 0)
  1837. {
  1838. unset($actions['DELETE_FILES'][$source]);
  1839. continue;
  1840. }
  1841. // MODX used '*.*' or 'dir/*.*' (Fun!)
  1842. else if (strpos($source, '*.*') !== false)
  1843. {
  1844. // This could be phpbb_root_path, if "Copy: root/*.* to: *.*" syntax was used
  1845. // or root/custom_dir if "Copy: root/custom/*.* to: custom/*.*", etc.
  1846. $source = $this->mod_root . str_replace('*.*', '', $source);
  1847. $target = str_replace('*.*', '', $target);
  1848. // Get all of the files in the source directory
  1849. $files = find_files($source, '.*');
  1850. // And translate into destination files
  1851. $files = str_replace($source, $target, $files);
  1852. // Get all of the sub-directories in the source directory
  1853. $directories['src'] = find_files($source, '.*', 20, true);
  1854. // And translate it into destination sub-directories
  1855. $directories['dst'] = str_replace($source, $target, $directories['src']);
  1856. // Compare source and destination subdirs, if any, in _reverse_ order! (array_pop)
  1857. for ($i=0, $cnt = count($directories['dst']); $i < $cnt; $i++)
  1858. {
  1859. $dir_source = array_pop($directories['src']);
  1860. $dir_target = array_pop($directories['dst']);
  1861. // Some MODs include 'umil/', avoid deleting!
  1862. if (strpos($dir_target, 'umil/') === 0)
  1863. {
  1864. continue;
  1865. }
  1866. $src_file_cnt = directory_num_files($dir_source, false, true);
  1867. $dst_file_cnt = directory_num_files($phpbb_root_path . $dir_target, false, true);
  1868. $src_dir_cnt = directory_num_files($dir_source, true, true);
  1869. $dst_dir_cnt = directory_num_files($phpbb_root_path . $dir_target, true, true);
  1870. // Do we have a match in recursive file count and match in recursive subdir count?
  1871. // This could be vastly improved..
  1872. if ($src_file_cnt == $dst_file_cnt && $src_dir_cnt == $dst_dir_cnt)
  1873. {
  1874. $directories['del'][] = $dir_target;
  1875. }
  1876. unset($dir_source, $dir_target, $src_file_cnt, $dst_file_cnt, $src_dir_cnt, $dst_dir_cnt); //cleanup
  1877. }
  1878. foreach ($files as $file)
  1879. {
  1880. // Some MODs include 'umil/', avoid deleting!
  1881. if (strpos($file, 'umil/') === 0)
  1882. {
  1883. continue;
  1884. }
  1885. else if (!file_exists($phpbb_root_path . $file) && ($change || $display))
  1886. {
  1887. $template->assign_block_vars('removing_files', array(
  1888. 'S_MISSING_FILE' => true,
  1889. 'S_NO_DELETE_ATTEMPT' => true,
  1890. 'FILENAME' => $file,
  1891. ));
  1892. }
  1893. else if ($change && ($mod_installed || $force_install))
  1894. {
  1895. $status = $editor->remove($file);
  1896. $template->assign_block_vars('removing_files', array(
  1897. 'S_SUCCESS' => ($status === true) ? true : false,
  1898. 'S_NO_DELETE_ATTEMPT' => (is_null($status)) ? true : false,
  1899. 'FILENAME' => $file,
  1900. ));
  1901. }
  1902. else if ($display && !$change)
  1903. {
  1904. $template->assign_block_vars('removing_files', array(
  1905. 'FILENAME' => $file,
  1906. ));
  1907. }
  1908. // To avoid "error" on uninstall page when being asked to force
  1909. else if ($change && $display && !$mod_installed && !$force_install)
  1910. {
  1911. $template->assign_block_vars('removing_files', array(
  1912. 'S_NO_DELETE_ATTEMPT' => true,
  1913. 'FILENAME' => $file,
  1914. ));
  1915. }
  1916. }
  1917. unset($files); //cleanup
  1918. }
  1919. else if (!file_exists($phpbb_root_path . $target) && ($change || $display))
  1920. {
  1921. $template->assign_block_vars('removing_files', array(
  1922. 'S_MISSING_FILE' => true,
  1923. 'S_NO_DELETE_ATTEMPT' => true,
  1924. 'FILENAME' => $target,
  1925. ));
  1926. }
  1927. else if ($change && ($mod_installed || $force_install))
  1928. {
  1929. $status = $editor->remove($target);
  1930. $template->assign_block_vars('removing_files', array(
  1931. 'S_SUCCESS' => ($status === true) ? true : false,
  1932. 'S_NO_DELETE_ATTEMPT' => (is_null($status)) ? true : false,
  1933. 'FILENAME' => $target,
  1934. ));
  1935. }
  1936. else if ($display && !$change)
  1937. {
  1938. $template->assign_block_vars('removing_files', array(
  1939. 'FILENAME' => $target,
  1940. ));
  1941. }
  1942. // To avoid "error" on uninstall page when being asked to force
  1943. else if ($change && $display && !$mod_installed && !$force_install)
  1944. {
  1945. $template->assign_block_vars('removing_files', array(
  1946. 'S_NO_DELETE_ATTEMPT' => true,
  1947. 'FILENAME' => $target,
  1948. ));
  1949. }
  1950. }
  1951. // Delete wildcard directories, if any, which should now be empty anyway (no recursive delete needed)
  1952. if ($cnt = count($directories['del']))
  1953. {
  1954. for ($i=0; $i < $cnt; $i++)
  1955. {
  1956. if ($change && ($mod_installed || $force_install))
  1957. {
  1958. $status = $editor->remove($directories['del'][$i]);
  1959. $template->assign_block_vars('removing_files', array(
  1960. 'S_SUCCESS' => ($status === true) ? true : false,
  1961. 'S_NO_DELETE_ATTEMPT' => (is_null($status)) ? true : false,
  1962. 'FILENAME' => $directories['del'][$i],
  1963. ));
  1964. }
  1965. else if ($display && !$change)
  1966. {
  1967. $template->assign_block_vars('removing_files', array(
  1968. 'FILENAME' => $directories['del'][$i],
  1969. ));
  1970. }
  1971. // To avoid "error" on uninstall page when being asked to force
  1972. else if ($change && $display && !$mod_installed && !$force_install)
  1973. {
  1974. $template->assign_block_vars('removing_files', array(
  1975. 'S_NO_DELETE_ATTEMPT' => true,
  1976. 'FILENAME' => $directories['del'][$i],
  1977. ));
  1978. }
  1979. }
  1980. unset($directories['del']); //cleanup
  1981. }
  1982. }
  1983. // Normal deleting functionality (not in reverse edits mode)
  1984. else if ($mod_installed || $force_install)
  1985. {
  1986. foreach ($actions['DELETE_FILES'] as $file)
  1987. {
  1988. $wildcards = strpos($file, '*.*');
  1989. $file = str_replace('*.*', '', $file);
  1990. if (!file_exists($phpbb_root_path . $file) && ($change || $display))
  1991. {
  1992. $template->assign_block_vars('removing_files', array(
  1993. 'S_MISSING_FILE' => true,
  1994. 'S_NO_DELETE_ATTEMPT' => true,
  1995. 'FILENAME' => $file,
  1996. ));
  1997. }
  1998. // purposely do not use !== false here, because we don't expect wildcards at position 0
  1999. // if there's no wildcard, make sure it's a file to avoid recursively deleting a directory!!!
  2000. else if ($wildcards || is_file($phpbb_root_path . $file))
  2001. {
  2002. if ($change)
  2003. {
  2004. // Delete, recursively if needed
  2005. $status = $editor->remove($file, true);
  2006. $template->assign_block_vars('removing_files', array(
  2007. 'S_SUCCESS' => ($status === true) ? true : false,
  2008. 'S_NO_DELETE_ATTEMPT' => (is_null($status)) ? true : false,
  2009. 'FILENAME' => $file,
  2010. ));
  2011. }
  2012. else if ($display)
  2013. {
  2014. $template->assign_block_vars('removing_files', array(
  2015. 'FILENAME' => $file,
  2016. ));
  2017. }
  2018. }
  2019. }
  2020. }
  2021. }
  2022. // Perform SQL queries last -- Queries usually cannot be done a second
  2023. // time, so do them only if the edits were successful. Still complies
  2024. // with the MODX spec in this location
  2025. if (!empty($actions['SQL']) && ($mod_installed || $force_install || ($display && !$change)))
  2026. {
  2027. $template->assign_var('S_SQL', true);
  2028. // parser::parse_sql($actions['SQL']);
  2029. $db->sql_return_on_error(true);
  2030. foreach ($actions['SQL'] as $query)
  2031. {
  2032. if ($change)
  2033. {
  2034. $query_success = $db->sql_query($query);
  2035. if ($query_success)
  2036. {
  2037. $template->assign_block_vars('sql_queries', array(
  2038. 'S_SUCCESS' => true,
  2039. 'QUERY' => $query,
  2040. ));
  2041. }
  2042. else
  2043. {
  2044. $error = $db->sql_error();
  2045. $template->assign_block_vars('sql_queries', array(
  2046. 'S_SUCCESS' => false,
  2047. 'QUERY' => $query,
  2048. 'ERROR_MSG' => $error['message'],
  2049. 'ERROR_CODE'=> $error['code'],
  2050. ));
  2051. $mod_installed = false;
  2052. }
  2053. }
  2054. else if ($display)
  2055. {
  2056. $template->assign_block_vars('sql_queries', array(
  2057. 'QUERY' => $query,
  2058. ));
  2059. }
  2060. }
  2061. $db->sql_return_on_error(false);
  2062. }
  2063. else
  2064. {
  2065. $template->assign_var('S_SQL', false);
  2066. }
  2067. return $mod_installed;
  2068. }
  2069. /**
  2070. * Search on the file system for other .xml files that belong to this MOD
  2071. * @param string $mod_path - path to the "main" MODX file, relative to phpBB Root
  2072. */
  2073. function find_children($mod_path)
  2074. {
  2075. $children = array();
  2076. if ($this->parser->get_modx_version() == 1.2)
  2077. {
  2078. // TODO: eww, yuck ... processing the XML again?
  2079. $details = $this->mod_details($mod_path, false);
  2080. $children = $details['CHILDREN'];
  2081. if (isset($children['template-lang']))
  2082. {
  2083. if (isset($children['template']))
  2084. {
  2085. $children['template'] = array_merge($children['template'], $children['template-lang']);
  2086. }
  2087. else
  2088. {
  2089. $children['template'] = $children['template-lang'];
  2090. }
  2091. unset($children['template-lang']);
  2092. }
  2093. $child_types = array('contrib', 'template', 'language', 'dependency', 'uninstall');
  2094. foreach ($child_types as $type)
  2095. {
  2096. if (empty($children[$type]))
  2097. {
  2098. continue;
  2099. }
  2100. $child_count = sizeof($children[$type]);
  2101. // Remove duplicate hrefs if they exist (links in multiple languages can cause this)
  2102. for ($i = 1; $i < $child_count; $i++)
  2103. {
  2104. if (isset($children[$type][$i - 1]) && $children[$type][$i - 1]['href'] == $children[$type][$i]['href'])
  2105. {
  2106. unset($children[$type][$i]);
  2107. }
  2108. }
  2109. }
  2110. }
  2111. else if ($this->parser->get_modx_version() == 1.0)
  2112. {
  2113. $search_dir = (strpos($mod_path, $this->mods_dir) === 0) ? dirname($mod_path) : $this->mods_dir . dirname($mod_path);
  2114. $children = $this->find_mods($search_dir, 5);
  2115. }
  2116. return $children;
  2117. }
  2118. function handle_dependency(&$children, $mode, $mod_path)
  2119. {
  2120. if (isset($children['dependency']) && sizeof($children['dependency']))
  2121. {
  2122. // TODO: check for the chance that the MOD has been installed by AutoMOD
  2123. // previously
  2124. if (confirm_box(true))
  2125. {
  2126. // do nothing
  2127. }
  2128. else if (empty($_REQUEST['dependency_confirm']) && empty($_REQUEST['force']))
  2129. {
  2130. global $user, $id;
  2131. $full_url_list = array();
  2132. $message = '';
  2133. $children['dependency'] = array_unique($children['dependency']);
  2134. foreach ($children['dependency'] as $dependency)
  2135. {
  2136. //$full_url_list[] = $dependency_url;
  2137. $message .= sprintf($user->lang['DEPENDENCY_INSTRUCTIONS'], $dependency['href'], $dependency['title']) . '<br /><br />';
  2138. }
  2139. confirm_box(false, $message, build_hidden_fields(array(
  2140. 'dependency_confirm' => true,
  2141. 'mode' => $mode,
  2142. 'action' => 'install',
  2143. 'mod_path' => $mod_path,
  2144. )));
  2145. }
  2146. }
  2147. }
  2148. /**
  2149. * Get all contrib links for the selected language.
  2150. * This functions will be removed.
  2151. */
  2152. function get_contrib_lang($contrib, $lang = 'en')
  2153. {
  2154. $ary = array();
  2155. foreach ($contrib as $element)
  2156. {
  2157. if (match_language($lang, $element['lang']))
  2158. {
  2159. $ary[] = $element;
  2160. }
  2161. }
  2162. return($ary);
  2163. }
  2164. function handle_contrib(&$children)
  2165. {
  2166. global $template, $parent_id, $phpbb_root_path, $user, $db;
  2167. if (isset($children['contrib']) && sizeof($children['contrib']))
  2168. {
  2169. $template->assign_var('S_CONTRIB_AVAILABLE', true);
  2170. // Do we have links in the users selected language.
  2171. // Start with getting the Enlgish links.
  2172. $contrib_en = $this->get_contrib_lang($children['contrib']);
  2173. if ($user->data['user_lang'] == 'en' || sizeof($contrib_en) == sizeof($children['contrib']))
  2174. {
  2175. // Our user has either English or there is only English links.
  2176. $children['contrib'] = $contrib_en;
  2177. }
  2178. else
  2179. {
  2180. // If there are any links in the users language, let's get them.
  2181. $contrib_lang = $this->get_contrib_lang($children['contrib'], $user->data['user_lang']);
  2182. if (!sizeof($contrib_lang))
  2183. {
  2184. // There is no links in the right language, give them the English links.
  2185. $children['contrib'] = $contrib_en;
  2186. }
  2187. else
  2188. {
  2189. $children['contrib'] = $contrib_lang;
  2190. }
  2191. }
  2192. if (!empty($parent_id))
  2193. {
  2194. // get installed contribs from the database
  2195. $sql = 'SELECT mod_contribs FROM ' . MODS_TABLE . '
  2196. WHERE mod_id = ' . $parent_id;
  2197. $result = $db->sql_query($sql);
  2198. $mod_contribs = $db->sql_fetchfield('mod_contribs');
  2199. $db->sql_freeresult();
  2200. }
  2201. $mod_contribs = (!empty($mod_contribs)) ? explode(',', $mod_contribs) : array();
  2202. // there are things like upgrades...we don't care unless the MOD has previously been installed.
  2203. foreach ($children['contrib'] as $xml_file)
  2204. {
  2205. // Another hack for supporting both versions of MODX
  2206. $xml_file = (is_array($xml_file)) ? $xml_file['href'] : str_replace($this->mod_root, '', $xml_file);
  2207. $child_details = $this->mod_details($xml_file, false);
  2208. // don't do the urlencode until after the file is looked up on the
  2209. // filesystem
  2210. $xml_file = urlencode('/' . $xml_file);
  2211. if (in_array(urldecode($xml_file), $mod_contribs))
  2212. {
  2213. $child_details['U_UNINSTALL'] = ($parent_id) ? $this->u_action . "&amp;action=pre_uninstall&amp;parent=$parent_id&amp;mod_path=$xml_file&amp;type=contrib" : '';
  2214. }
  2215. else
  2216. {
  2217. $child_details['U_INSTALL'] = ($parent_id) ? $this->u_action . "&amp;action=pre_install&amp;parent=$parent_id&amp;mod_path=$xml_file&amp;type=contrib" : '';
  2218. }
  2219. $child_details['MOD_NAME'] = localize_title($child_details['MOD_NAME'], $user->data['user_lang']);
  2220. $template->assign_block_vars('contrib', $child_details);
  2221. }
  2222. }
  2223. }
  2224. function handle_merge($type, &$actions, &$children, $process_files)
  2225. {
  2226. if (!isset($children[$type]) || !sizeof($process_files))
  2227. {
  2228. return;
  2229. }
  2230. // add the actions to our $actions array...give praise to array_merge_recursive
  2231. foreach ($process_files as $key => $name)
  2232. {
  2233. foreach ($children[$type] as $child_key => $child_data)
  2234. {
  2235. $root_position = strpos($child_data['href'], $this->mod_root);
  2236. if ($child_data['realname'] == $name && $root_position === false)
  2237. {
  2238. $child_filename = $this->mod_root . ltrim($child_data['href'], './');
  2239. break;
  2240. }
  2241. else if ($child_data['realname'] == $name && $root_position === 0)
  2242. {
  2243. $child_filename = $child_data['href'];
  2244. }
  2245. }
  2246. $actions_ary = $this->mod_actions($child_filename);
  2247. if (!isset($actions_ary['NEW_FILES']))
  2248. {
  2249. $actions = array_merge_recursive($actions, $actions_ary);
  2250. continue;
  2251. }
  2252. // perform some cleanup if the MOD author didn't specify the proper root directory
  2253. foreach ($actions_ary['NEW_FILES'] as $source => $destination)
  2254. {
  2255. // if the source file does not exist, and languages/ is not at the beginning
  2256. // this is probably only applicable with MODX 1.0.
  2257. if (!file_exists($this->mod_root . $source) && strpos("{$type}s/", $source) === false)
  2258. {
  2259. // and it does exist if we force a languages/ into the path
  2260. if (file_exists($this->mod_root . "{$type}s/" . $source))
  2261. {
  2262. // change the array key to include languages
  2263. unset($actions_ary['NEW_FILES'][$source]);
  2264. $actions_ary['NEW_FILES']["{$type}s/$source"] = $destination;
  2265. }
  2266. // else we let the error handling do its thing
  2267. }
  2268. }
  2269. $actions = array_merge_recursive($actions, $actions_ary);
  2270. }
  2271. }
  2272. function handle_language_prompt(&$children, &$elements, $action)
  2273. {
  2274. global $db, $template, $parent_id, $phpbb_root_path;
  2275. if (isset($children['language']) && sizeof($children['language']))
  2276. {
  2277. // additional languages are available...find out which ones we may want to apply
  2278. $sql = 'SELECT lang_id, lang_iso FROM ' . LANG_TABLE;
  2279. $result = $db->sql_query($sql);
  2280. $installed_languages = array();
  2281. while ($row = $db->sql_fetchrow($result))
  2282. {
  2283. $installed_languages[$row['lang_id']] = $row['lang_iso'];
  2284. }
  2285. $db->sql_freeresult($result);
  2286. foreach ($children['language'] as $key => $tag)
  2287. {
  2288. // remove useless title from MODX 1.2.0 tags
  2289. $children['language'][$tag['realname']] = is_array($tag) ? $tag['href'] : $tag;
  2290. }
  2291. $available_languages = array_keys($children['language']);
  2292. $process_languages = $elements['language'] = array_intersect($available_languages, $installed_languages);
  2293. // $unknown_languages are provided for by the MOD, but not installed on the board
  2294. $unknown_languages = array_diff($available_languages, $installed_languages);
  2295. // Inform the user if there are languages provided for by the MOD
  2296. if (sizeof($children['language']))
  2297. {
  2298. if (!empty($parent_id))
  2299. {
  2300. // get installed languages from the database
  2301. $sql = 'SELECT mod_languages FROM ' . MODS_TABLE . '
  2302. WHERE mod_id = ' . $parent_id;
  2303. $result = $db->sql_query($sql);
  2304. $mod_languages = $db->sql_fetchfield('mod_languages');
  2305. $db->sql_freeresult();
  2306. }
  2307. $mod_languages = (!empty($mod_languages)) ? explode(',', $mod_languages) : array();
  2308. foreach ($children['language'] as $row)
  2309. {
  2310. if (is_string($row))
  2311. {
  2312. continue;
  2313. }
  2314. $xml_file = (!empty($row['href'])) ? urlencode($row['href']) : '';
  2315. $template->assign_block_vars('unknown_lang', array(
  2316. 'ENGLISH_NAME' => $row['title'],
  2317. 'LOCAL_NAME' => $row['realname'],
  2318. 'U_INSTALL' => (!empty($xml_file) && !in_array($row['href'], $mod_languages) && $parent_id) ? $this->u_action . "&amp;action=pre_install&amp;parent=$parent_id&amp;mod_path=$xml_file&amp;type=lang" : '',
  2319. 'U_UNINSTALL' => (!empty($xml_file) && $parent_id) ? $this->u_action . "&amp;action=pre_uninstall&amp;parent=$parent_id&amp;mod_path=$xml_file&amp;type=lang" : '',
  2320. ));
  2321. unset($row);
  2322. }
  2323. }
  2324. return $process_languages;
  2325. }
  2326. }
  2327. function handle_template_prompt(&$children, &$elements, $action)
  2328. {
  2329. return;
  2330. /*
  2331. global $db, $template, $phpbb_root_path, $parent_id;
  2332. if (isset($children['template']) && sizeof($children['template']))
  2333. {
  2334. // additional styles are available for this MOD
  2335. $sql = 'SELECT template_id, template_name FROM ' . STYLES_TEMPLATE_TABLE;
  2336. $result = $db->sql_query($sql);
  2337. $installed_templates = array();
  2338. while ($row = $db->sql_fetchrow($result))
  2339. {
  2340. $installed_templates[$row['template_id']] = $row['template_name'];
  2341. }
  2342. $db->sql_freeresult($result);
  2343. foreach ($children['template'] as $key => $tag)
  2344. {
  2345. // remove useless title from MODX 1.2.0 tags
  2346. $children['template'][$tag['realname']] = is_array($tag) ? $tag['href'] : $tag;
  2347. }
  2348. $available_templates = array_keys($children['template']);
  2349. // $process_templates are those that are installed on the board and provided for by the MOD
  2350. $process_templates = $elements['template'] = array_intersect($available_templates, $installed_templates);
  2351. }
  2352. */
  2353. }
  2354. function upload_mod($action)
  2355. {
  2356. global $phpbb_root_path, $phpEx, $template, $user;
  2357. $can_upload = (@ini_get('file_uploads') == '0' || strtolower(@ini_get('file_uploads')) == 'off' || !@extension_loaded('zlib')) ? false : true;
  2358. // get FTP information if we need it
  2359. $hidden_ary = get_connection_info(false);
  2360. if (!isset($_FILES['modupload']) || $action != 'upload_mod')
  2361. {
  2362. $template->assign_vars(array(
  2363. 'S_FRONTEND' => true,
  2364. 'S_MOD_UPLOAD' => ($can_upload) ? true : false,
  2365. 'U_UPLOAD' => $this->u_action . '&amp;action=upload_mod',
  2366. 'S_FORM_ENCTYPE' => ($can_upload) ? ' enctype="multipart/form-data"' : '',
  2367. 'S_HIDDEN_FIELDS' => build_hidden_fields($hidden_ary),
  2368. ));
  2369. add_form_key('acp_mods_upload');
  2370. return false;
  2371. } // end pre_upload_mod
  2372. if (!check_form_key('acp_mods_upload'))
  2373. {
  2374. trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
  2375. }
  2376. $user->add_lang('posting'); // For error messages
  2377. include($phpbb_root_path . 'includes/functions_upload.' . $phpEx);
  2378. $upload = new fileupload();
  2379. $upload->set_allowed_extensions(array('zip')); // Only allow ZIP files
  2380. $write_method = 'editor_' . determine_write_method(false);
  2381. // For Direct & Manual write methods, make sure store/mods/ directory is writable
  2382. if ($write_method == 'editor_direct' || $write_method == 'editor_manual')
  2383. {
  2384. if (!is_writable($this->mods_dir))
  2385. {
  2386. trigger_error($user->lang['MODS_NOT_WRITABLE'] . adm_back_link($this->u_action), E_USER_WARNING);
  2387. }
  2388. $write_method = 'editor_direct'; // Force Direct method, in the case of manual
  2389. $upload_dir = $this->mods_dir;
  2390. }
  2391. // FTP method: we still need a known world-writable directory (store/) for zip extraction
  2392. else if (is_writable($this->store_dir))
  2393. {
  2394. $upload_dir = $this->store_dir;
  2395. }
  2396. else
  2397. {
  2398. trigger_error($user->lang['STORE_NOT_WRITABLE'] . adm_back_link($this->u_action), E_USER_WARNING);
  2399. }
  2400. $editor = new $write_method();
  2401. // Make sure the store/mods/ directory exists and if it doesn't, create it
  2402. if (!is_dir($this->mods_dir))
  2403. {
  2404. $editor->recursive_mkdir($this->mods_dir);
  2405. }
  2406. // Proceed with the upload
  2407. $file = $upload->form_upload('modupload');
  2408. if (empty($file->filename))
  2409. {
  2410. trigger_error($user->lang['NO_UPLOAD_FILE'] . adm_back_link($this->u_action), E_USER_WARNING);
  2411. }
  2412. else if ($file->init_error || sizeof($file->error))
  2413. {
  2414. $file->remove();
  2415. trigger_error((sizeof($file->error) ? implode('<br />', $file->error) : $user->lang['MOD_UPLOAD_INIT_FAIL']) . adm_back_link($this->u_action), E_USER_WARNING);
  2416. }
  2417. $file->clean_filename('real');
  2418. $file->move_file(str_replace($phpbb_root_path, '', $upload_dir), true, true);
  2419. if (sizeof($file->error))
  2420. {
  2421. $file->remove();
  2422. trigger_error(implode('<br />', $file->error) . adm_back_link($this->u_action), E_USER_WARNING);
  2423. }
  2424. include($phpbb_root_path . 'includes/functions_compress.' . $phpEx);
  2425. $mod_dir = $upload_dir . '/' . str_replace('.zip', '', $file->get('realname'));
  2426. $compress = new compress_zip('r', $file->destination_file);
  2427. $compress->extract($mod_dir . '_tmp/');
  2428. $compress->close();
  2429. $folder_contents = scandir($mod_dir . '_tmp/', 1); // This ensures dir is at index 0
  2430. $folder_contents = array_diff($folder_contents, array('.', '..'));
  2431. // We need to check if there's only one (main) directory inside the temp MOD directory
  2432. if (sizeof($folder_contents) == 1)
  2433. {
  2434. $folder_contents = implode(null, $folder_contents);
  2435. $from_dir = $mod_dir . '_tmp/' . $folder_contents;
  2436. $to_dir = $this->mods_dir . '/' . $folder_contents;
  2437. }
  2438. // Otherwise assume the temp directory is the main directroy, so change the directory
  2439. // name by moving to a directory without the '_tmp' suffix
  2440. else if (!is_dir($mod_dir))
  2441. {
  2442. $from_dir = $mod_dir . '_tmp/';
  2443. $to_dir = $mod_dir . '/';
  2444. }
  2445. // We should never really get here, but you never know!
  2446. else
  2447. {
  2448. trigger_error($user->lang['MOD_UPLOAD_UNRECOGNIZED'] . adm_back_link($this->u_action), E_USER_WARNING);
  2449. }
  2450. // Copy that directory to the new path
  2451. $editor->copy_content($from_dir, $to_dir);
  2452. // Finally remove the main tmp extraction directory, directly, just like we created it
  2453. recursive_unlink($mod_dir . '_tmp/');
  2454. $template->assign_vars(array(
  2455. 'S_MOD_SUCCESSBOX' => true,
  2456. 'MESSAGE' => $user->lang['MOD_UPLOAD_SUCCESS'],
  2457. 'U_RETURN' => $this->u_action,
  2458. ));
  2459. // Remove the uploaded archive file
  2460. $file->remove();
  2461. return true;
  2462. }
  2463. function delete_mod($action, $mod_path = '')
  2464. {
  2465. global $template, $user;
  2466. if (!empty($mod_path))
  2467. {
  2468. $mod_path = explode('/', str_replace('\\', '/', $mod_path));
  2469. $mod_path = (!empty($mod_path[0])) ? $mod_path[0] : $mod_path[1];
  2470. }
  2471. else
  2472. {
  2473. $mod_path = request_var('mod_delete', '');
  2474. }
  2475. if (empty($mod_path) || !is_dir($this->mods_dir . '/' . $mod_path))
  2476. {
  2477. return false; // ERROR
  2478. }
  2479. // get FTP information if we need it
  2480. $hidden_ary = get_connection_info(false);
  2481. $hidden_ary['mod_delete'] = $mod_path;
  2482. if ($action != 'delete_mod')
  2483. {
  2484. $template->assign_vars(array(
  2485. 'S_MOD_DELETE' => true,
  2486. 'U_DELETE' => $this->u_action . '&amp;action=delete_mod',
  2487. 'S_HIDDEN_FIELDS' => build_hidden_fields($hidden_ary),
  2488. ));
  2489. add_form_key('acp_mods_delete');
  2490. return;
  2491. } // end pre_delete_mod
  2492. $write_method = 'editor_' . determine_write_method(false);
  2493. // Force Direct method, in the case of manual - just like above in mod_upload()
  2494. $write_method = ($write_method == 'editor_manual') ? 'editor_direct' : $write_method;
  2495. $editor = new $write_method();
  2496. if (!check_form_key('acp_mods_delete'))
  2497. {
  2498. trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING);
  2499. }
  2500. $status = $editor->remove("{$this->mods_dir}/{$mod_path}", true);
  2501. if ($status !== true)
  2502. {
  2503. trigger_error($user->lang['DELETE_ERROR'] . " $status" . adm_back_link($this->u_action), E_USER_WARNING);
  2504. }
  2505. $template->assign_vars(array(
  2506. 'S_MOD_SUCCESSBOX' => true,
  2507. 'MESSAGE' => $user->lang['DELETE_SUCCESS'],
  2508. 'U_RETURN' => $this->u_action,
  2509. ));
  2510. }
  2511. }
  2512. ?>