PageRenderTime 82ms CodeModel.GetById 35ms RepoModel.GetById 1ms app.codeStats 1ms

/titania/includes/library/automod/acp_mods.php

https://github.com/michaelcullum/customisation-db
PHP | 2955 lines | 2151 code | 414 blank | 390 comment | 414 complexity | bbcc45ff12452a8f013b4a0f5770b5be MD5 | raw file
Possible License(s): AGPL-1.0

Large files files are truncated, but you can click here to view the full file

  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

Large files files are truncated, but you can click here to view the full file