PageRenderTime 52ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/commands/sql/sql.drush.inc

https://github.com/exeqt/drush
Pascal | 734 lines | 487 code | 23 blank | 224 comment | 25 complexity | e3a6bcec8f1bcd4783da6e28da7f947f MD5 | raw file
  1. <?php
  2. /**
  3. * @file Drush sql commands
  4. */
  5. /**
  6. * Implementation of hook_drush_help().
  7. */
  8. function sql_drush_help($section) {
  9. switch ($section) {
  10. case 'meta:sql:title':
  11. return dt('SQL commands');
  12. case 'meta:sql:summary':
  13. return dt('Examine and modify your Drupal database.');
  14. case 'drush:sql-sanitize':
  15. return dt('Run sanitization operations on the current database. You can add more sanitization to this command by implementing hook_drush_sql_sync_sanitize().');
  16. }
  17. }
  18. /**
  19. * Implementation of hook_drush_command().
  20. */
  21. function sql_drush_command() {
  22. $options['database'] = array(
  23. 'description' => 'The DB connection key if using multiple connections in settings.php.',
  24. 'example-value' => 'key',
  25. );
  26. $db_url['db-url'] = array(
  27. 'description' => 'A Drupal 6 style database URL.',
  28. 'example-value' => 'mysql://root:pass@127.0.0.1/db',
  29. );
  30. $options['target'] = array(
  31. 'description' => 'The name of a target within the specified database connection. Defaults to \'default\'.',
  32. 'example-value' => 'key',
  33. // Gets unhidden in help_alter(). We only want to show this to D7 users but have to
  34. // declare it here since some commands do not bootstrap fully.
  35. 'hidden' => TRUE,
  36. );
  37. $items['sql-drop'] = array(
  38. 'description' => 'Drop all tables in a given database.',
  39. 'arguments' => array(
  40. ),
  41. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  42. 'options' => array(
  43. 'yes' => 'Skip confirmation and proceed.',
  44. 'result-file' => array(
  45. 'description' => 'Save to a file. The file should be relative to Drupal root. Recommended.',
  46. 'example-value' => '/path/to/file',
  47. ),
  48. ) + $options + $db_url,
  49. 'topics' => array('docs-policy'),
  50. );
  51. $items['sql-conf'] = array(
  52. 'description' => 'Print database connection details using print_r().',
  53. 'hidden' => TRUE,
  54. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  55. 'options' => array(
  56. 'all' => 'Show all database connections, instead of just one.',
  57. 'show-passwords' => 'Show database password.',
  58. ) + $options,
  59. 'outputformat' => array(
  60. 'default' => 'print-r',
  61. 'pipe-format' => 'var_export',
  62. 'private-fields' => 'password',
  63. ),
  64. );
  65. $items['sql-connect'] = array(
  66. 'description' => 'A string for connecting to the DB.',
  67. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  68. 'options' => $options + $db_url + array(
  69. 'extra' => array(
  70. 'description' => 'Add custom options to the mysql command.',
  71. 'example-value' => '--skip-column-names',
  72. ),
  73. ),
  74. 'examples' => array(
  75. '`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.',
  76. ),
  77. );
  78. $items['sql-create'] = array(
  79. 'description' => 'Create a database.',
  80. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  81. 'examples' => array(
  82. 'drush sql-create' => 'Create the database for the current site.',
  83. 'drush @site.test sql-create' => 'Create the database as specified for @site.test.',
  84. 'drush sql-create --db-su=root --db-su-pw=rootpassword --db-url="mysql://drupal_db_user:drupal_db_password@127.0.0.1/drupal_db"' =>
  85. 'Create the database as specified in the db-url option.'
  86. ),
  87. 'options' => array(
  88. 'db-su' => 'Account to use when creating a new database. Optional.',
  89. 'db-su-pw' => 'Password for the "db-su" account. Optional.',
  90. ) + $options + $db_url,
  91. );
  92. $items['sql-dump'] = array(
  93. 'description' => 'Exports the Drupal DB as SQL using mysqldump or equivalent.',
  94. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  95. 'examples' => array(
  96. 'drush sql-dump --result-file=../18.sql' => 'Save SQL dump to the directory above Drupal root.',
  97. 'drush sql-dump --skip-tables-key=common' => 'Skip standard tables. @see example.drushrc.php',
  98. ),
  99. 'options' => array(
  100. 'result-file' => array(
  101. 'description' => 'Save to a file. The file should be relative to Drupal root. If --result-file is provided with no value, then date based filename will be created under ~/drush-backups directory.',
  102. 'example-value' => '/path/to/file',
  103. 'value' => 'optional',
  104. ),
  105. 'skip-tables-key' => 'A key in the $skip_tables array. @see example.drushrc.php. Optional.',
  106. 'structure-tables-key' => 'A key in the $structure_tables array. @see example.drushrc.php. Optional.',
  107. 'tables-key' => 'A key in the $tables array. Optional.',
  108. 'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.',
  109. 'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. Optional.',
  110. 'tables-list' => 'A comma-separated list of tables to transfer. Optional.',
  111. 'ordered-dump' => 'Use this option to output ordered INSERT statements in the sql-dump.Useful when backups are managed in a Version Control System. Optional.',
  112. 'create-db' => array('hidden' => TRUE, 'description' => 'Omit DROP TABLE statements. Postgres and Oracle only. Used by sql-sync, since including the DROP TABLE statements interfere with the import when the database is created.'),
  113. 'data-only' => 'Dump data without statements to create any of the schema.',
  114. 'ordered-dump' => 'Order by primary key and add line breaks for efficient diff in revision control. Also, faster rsync. Slows down the dump. Mysql only.',
  115. 'gzip' => 'Compress the dump using the gzip program which must be in your $PATH.',
  116. ) + $options + $db_url,
  117. );
  118. $items['sql-query'] = array(
  119. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  120. 'description' => 'Execute a query against a database.',
  121. 'examples' => array(
  122. 'drush sql-query "SELECT * FROM users WHERE uid=1"' => 'Browse user record. Table prefixes, if used, must be added to table names by hand.',
  123. 'drush sql-query --db-prefix "SELECT * FROM {users} WHERE uid=1"' => 'Browse user record. Table prefixes are honored. Caution: curly-braces will be stripped from all portions of the query.',
  124. '`drush sql-connect` < example.sql' => 'Import sql statements from a file into the current database.',
  125. 'drush sql-query --file=example.sql' => 'Alternate way to import sql statements from a file.',
  126. ),
  127. 'arguments' => array(
  128. 'query' => 'An SQL query. Ignored if \'file\' is provided.',
  129. ),
  130. 'options' => array(
  131. 'result-file' => array(
  132. 'description' => 'Save to a file. The file should be relative to Drupal root. Optional.',
  133. 'example-value' => '/path/to/file',
  134. ),
  135. 'file' => 'Path to a file containing the SQL to be run. Gzip files are accepted.',
  136. 'extra' => array(
  137. 'description' => 'Add custom options to the mysql command.',
  138. 'example-value' => '--skip-column-names',
  139. ),
  140. 'db-prefix' => 'Enable replacement of braces in your query.',
  141. 'db-spec' => array(
  142. 'description' => 'A database specification',
  143. 'hidden' => TRUE, // Hide since this is only used with --backend calls.
  144. )
  145. ) + $options + $db_url,
  146. 'aliases' => array('sqlq'),
  147. );
  148. $items['sql-sync'] = array(
  149. 'description' => 'Copies the database contents from a source site to a target site. Transfers the database dump via rsync.',
  150. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  151. 'drush dependencies' => array('core'), // core-rsync.
  152. 'examples' => array(
  153. 'drush sql-sync @source @target' => 'Copy the database from the site with the alias "source" to the site with the alias "target".',
  154. 'drush sql-sync prod dev' => 'Copy the database from the site in /sites/prod to the site in /sites/dev (multisite installation).',
  155. ),
  156. 'arguments' => array(
  157. 'source' => 'A site-alias or the name of a subdirectory within /sites whose database you want to copy from.',
  158. 'target' => 'A site-alias or the name of a subdirectory within /sites whose database you want to replace.',
  159. ),
  160. 'required-arguments' => TRUE,
  161. 'options' => array(
  162. 'skip-tables-key' => 'A key in the $skip_tables array. See example.drushrc.php. Optional.',
  163. 'skip-tables-list' => 'A comma-separated list of tables to exclude completely. Optional.',
  164. 'structure-tables-key' => 'A key in the $structure_tables array. See example.drushrc.php. Optional.',
  165. 'structure-tables-list' => 'A comma-separated list of tables to include for structure, but not data. Optional.',
  166. 'tables-key' => 'A key in the $tables array. Optional.',
  167. 'tables-list' => 'A comma-separated list of tables to transfer. Optional.',
  168. // 'cache' => 'Skip dump if result file exists and is less than "cache" hours old. Optional; default is 24 hours.',
  169. // 'no-cache' => 'Do not cache the sql-dump file.',
  170. 'no-dump' => 'Do not dump the sql database; always use an existing dump file.',
  171. 'source-db-url' => 'Database specification for source system to dump from.',
  172. 'source-remote-port' => 'Override sql database port number in source-db-url. Optional.',
  173. 'source-remote-host' => 'Remote machine to run sql-dump file on. Optional; default is local machine.',
  174. 'source-dump' => 'The destination for the dump file, or the path to the dump file when --no-dump is specified.',
  175. 'source-database' => 'A key in the $db_url (D6) or $databases (D7+) array which provides the data.',
  176. 'source-target' => array(
  177. 'description' => 'A key within the SOURCE database identifying a particular server in the database group.',
  178. 'example-value' => 'key',
  179. // Gets unhidden in help_alter(). We only want to show to D7+ users but have to
  180. // declare it here since this command does not bootstrap fully.
  181. 'hidden' => TRUE,
  182. ),
  183. 'target-db-url' => '',
  184. 'target-remote-port' => '',
  185. 'target-remote-host' => '',
  186. 'target-dump' => 'A path for saving the dump file on target. Mandatory when using --no-sync.',
  187. 'target-database' => 'A key in the $db_url (D6) or $databases (D7+) array which shall receive the data.',
  188. 'target-target' => array(
  189. 'description' => 'Oy. A key within the TARGET database identifying a particular server in the database group.',
  190. 'example-value' => 'key',
  191. // Gets unhidden in help_alter(). We only want to show to D7+ users but have to
  192. // declare it here since this command does not bootstrap fully.
  193. 'hidden' => TRUE,
  194. ),
  195. // 'temp' => 'Use a temporary file to hold dump files. Implies --no-cache.',
  196. // 'dump-dir' => 'Directory to store sql dump files in when --source-dump or --target-dump are not used.',
  197. 'create-db' => 'Create a new database before importing the database dump on the target machine.',
  198. 'db-su' => array(
  199. 'description' => 'Account to use when creating a new database. Optional.',
  200. 'example-value' => 'root',
  201. ),
  202. 'db-su-pw' => array(
  203. 'description' => 'Password for the "db-su" account. Optional.',
  204. 'example-value' => 'pass',
  205. ),
  206. // 'no-ordered-dump' => 'Do not pass --ordered-dump to sql-dump. sql-sync orders the dumpfile by default in order to increase the efficiency of rsync.',
  207. 'sanitize' => 'Obscure email addresses and reset passwords in the user table post-sync.',
  208. ),
  209. 'sub-options' => array(
  210. 'sanitize' => array(
  211. 'sanitize-password' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged. Default is "password".',
  212. 'sanitize-email' => 'The pattern for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %name. Default is "user+%uid@localhost".',
  213. 'confirm-sanitizations' => 'Prompt yes/no after importing the database, but before running the sanitizations',
  214. ),
  215. ),
  216. 'topics' => array('docs-aliases', 'docs-policy', 'docs-example-sync-via-http', 'docs-example-sync-extension'),
  217. );
  218. $items['sql-cli'] = array(
  219. 'description' => "Open a SQL command-line interface using Drupal's credentials.",
  220. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  221. // 'options' => $options + $db_url,
  222. 'allow-additional-options' => array('sql-connect'),
  223. 'aliases' => array('sqlc'),
  224. 'examples' => array(
  225. 'drush sql-cli' => "Open a SQL command-line interface using Drupal's credentials.",
  226. 'drush sql-cli --extra=-A' => "Open a SQL CLI and skip reading table information.",
  227. ),
  228. 'remote-tty' => TRUE,
  229. );
  230. $items['sql-sanitize'] = array(
  231. 'description' => "Run sanitization operations on the current database.",
  232. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  233. 'options' => array(
  234. 'sanitize-password' => 'The password to assign to all accounts in the sanitization operation, or "no" to keep passwords unchanged. Default is "password".',
  235. 'sanitize-email' => 'The pattern for test email addresses in the sanitization operation, or "no" to keep email addresses unchanged. May contain replacement patterns %uid, %mail or %name. Default is "user+%uid@localhost".',
  236. ) + $db_url,
  237. 'aliases' => array('sqlsan'),
  238. );
  239. return $items;
  240. }
  241. /**
  242. * Implements hook_drush_help_alter().
  243. */
  244. function sql_drush_help_alter(&$command) {
  245. // Drupal 7+ only options.
  246. if (drush_drupal_major_version() >= 7) {
  247. if ($command['command'] == 'sql-sync') {
  248. unset($command['options']['source-target']['hidden'], $command['options']['target-target']['hidden']);
  249. }
  250. elseif ($command['commandfile'] == 'sql') {
  251. unset($command['options']['target']['hidden']);
  252. }
  253. }
  254. }
  255. /**
  256. * Command argument complete callback.
  257. *
  258. * @return
  259. * Array of available site aliases.
  260. */
  261. function sql_sql_sync_complete() {
  262. return array('values' => array_keys(_drush_sitealias_all_list()));
  263. }
  264. /**
  265. * Check whether further bootstrap is needed. If so, do it.
  266. */
  267. function drush_sql_bootstrap_further() {
  268. if (!drush_get_option(array('db-url', 'db-spec'))) {
  269. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
  270. }
  271. }
  272. /**
  273. * Command callback. Displays the Drupal site's database connection string.
  274. */
  275. function drush_sql_conf() {
  276. // Under Drupal 7, if the database is configured but empty, then
  277. // DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION will throw an exception.
  278. // If this happens, we'll just catch it and continue.
  279. // TODO: Fix this in the bootstrap, per http://drupal.org/node/1996004
  280. try {
  281. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_CONFIGURATION);
  282. }
  283. catch (Exception $e) {
  284. }
  285. if (drush_get_option('all')) {
  286. $sqlVersion = drush_sql_get_version();
  287. return $sqlVersion->getAll();
  288. }
  289. else {
  290. $sql = drush_sql_get_class();
  291. return $sql->db_spec();
  292. }
  293. }
  294. /**
  295. * Command callback. Emits a connect string.
  296. */
  297. function drush_sql_connect() {
  298. drush_sql_bootstrap_further();
  299. $sql = drush_sql_get_class();
  300. return $sql->connect(FALSE);
  301. }
  302. /**
  303. * Command callback. Create a database.
  304. */
  305. function drush_sql_create() {
  306. $sql = drush_sql_get_class();
  307. $db_spec = $sql->db_spec();
  308. // Prompt for confirmation.
  309. if (!drush_get_context('DRUSH_SIMULATE')) {
  310. // @todo odd - maybe for sql-sync.
  311. $txt_destination = (isset($db_spec['remote-host']) ? $db_spec['remote-host'] . '/' : '') . $db_spec['database'];
  312. drush_print(dt("Creating database !target. Any possible existing database will be dropped!", array('!target' => $txt_destination)));
  313. if (!drush_confirm(dt('Do you really want to continue?'))) {
  314. return drush_user_abort();
  315. }
  316. }
  317. return $sql->createdb();
  318. }
  319. /**
  320. * Command callback. Outputs the entire Drupal database in SQL format using mysqldump or equivalent.
  321. */
  322. function drush_sql_dump() {
  323. drush_sql_bootstrap_further();
  324. $sql = drush_sql_get_class();
  325. return $sql->dump(drush_get_option('result-file', FALSE));
  326. }
  327. /**
  328. * Construct an array that places table names in appropriate
  329. * buckets based on whether the table is to be skipped, included
  330. * for structure only, or have structure and data dumped.
  331. * The keys of the array are:
  332. * - skip: tables to be skipped completed in the dump
  333. * - structure: tables to only have their structure i.e. DDL dumped
  334. * - tables: tables to have structure and data dumped
  335. *
  336. * @return array
  337. * An array of table names with each table name in the appropriate
  338. * element of the array.
  339. */
  340. function drush_sql_get_table_selection() {
  341. // Skip large core tables if instructed. Used by 'sql-drop/sql-dump/sql-sync' commands.
  342. $skip_tables = _drush_sql_get_raw_table_list('skip-tables');
  343. // Skip any structure-tables as well.
  344. $structure_tables = _drush_sql_get_raw_table_list('structure-tables');
  345. // Dump only the specified tables. Takes precedence over skip-tables and structure-tables.
  346. $tables = _drush_sql_get_raw_table_list('tables');
  347. return array('skip' => $skip_tables, 'structure' => $structure_tables, 'tables' => $tables);
  348. }
  349. /**
  350. * Expand wildcard tables.
  351. *
  352. * @param array $tables
  353. * An array of table names, some of which may contain wildcards (`*`).
  354. * @param array $db_tables
  355. * An array with all the existing table names in the current database.
  356. * @return
  357. * $tables array with wildcards resolved to real table names.
  358. */
  359. function drush_sql_expand_wildcard_tables($tables, $db_tables) {
  360. // Table name expansion based on `*` wildcard.
  361. $expanded_db_tables = array();
  362. foreach ($tables as $k => $table) {
  363. // Only deal with table names containing a wildcard.
  364. if (strpos($table, '*') !== FALSE) {
  365. $pattern = '/^' . str_replace('*', '.*', $table) . '$/i';
  366. // Merge those existing tables which match the pattern with the rest of
  367. // the expanded table names.
  368. $expanded_db_tables += preg_grep($pattern, $db_tables);
  369. }
  370. }
  371. return $expanded_db_tables;
  372. }
  373. /**
  374. * Filters tables.
  375. *
  376. * @param array $tables
  377. * An array of table names to filter.
  378. * @param array $db_tables
  379. * An array with all the existing table names in the current database.
  380. * @return
  381. * An array with only valid table names (i.e. all of which actually exist in
  382. * the database).
  383. */
  384. function drush_sql_filter_tables($tables, $db_tables) {
  385. // Ensure all the tables actually exist in the database.
  386. foreach ($tables as $k => $table) {
  387. if (!in_array($table, $db_tables)) {
  388. unset($tables[$k]);
  389. }
  390. }
  391. return $tables;
  392. }
  393. /**
  394. * Given the table names in the input array that may contain wildcards (`*`),
  395. * expand the table names so that the array returned only contains table names
  396. * that exist in the database.
  397. *
  398. * @param array $tables
  399. * An array of table names where the table names may contain the
  400. * `*` wildcard character.
  401. * @param array $db_tables
  402. * The list of tables present in a database.
  403. * @return array
  404. * An array of tables with non-existant tables removed.
  405. */
  406. function _drush_sql_expand_and_filter_tables($tables, $db_tables) {
  407. $expanded_tables = drush_sql_expand_wildcard_tables($tables, $db_tables);
  408. $tables = drush_sql_filter_tables(array_merge($tables, $expanded_tables), $db_tables);
  409. $tables = array_unique($tables);
  410. sort($tables);
  411. return $tables;
  412. }
  413. /**
  414. * Consult the specified options and return the list of tables
  415. * specified.
  416. *
  417. * @param option_name
  418. * The option name to check: skip-tables, structure-tables
  419. * or tables. This function will check both *-key and *-list,
  420. * and, in the case of sql-sync, will also check target-*
  421. * and source-*, to see if an alias set one of these options.
  422. * @returns array
  423. * Returns an array of tables based on the first option
  424. * found, or an empty array if there were no matches.
  425. */
  426. function _drush_sql_get_raw_table_list($option_name) {
  427. foreach(array('' => 'cli', 'target-,,source-' => NULL) as $prefix_list => $context) {
  428. foreach(explode(',',$prefix_list) as $prefix) {
  429. $key_list = drush_get_option($prefix . $option_name . '-key', NULL, $context);
  430. foreach(explode(',', $key_list) as $key) {
  431. $all_tables = drush_get_option($option_name, array());
  432. if (array_key_exists($key, $all_tables)) {
  433. return $all_tables[$key];
  434. }
  435. if ($option_name != 'tables') {
  436. $all_tables = drush_get_option('tables', array());
  437. if (array_key_exists($key, $all_tables)) {
  438. return $all_tables[$key];
  439. }
  440. }
  441. }
  442. $table_list = drush_get_option($prefix . $option_name . '-list', NULL, $context);
  443. if (isset($table_list)) {
  444. return empty($table_list) ? array() : explode(',', $table_list);
  445. }
  446. }
  447. }
  448. return array();
  449. }
  450. /**
  451. * Command callback. Executes the given SQL query on the Drupal database.
  452. */
  453. function drush_sql_query($query = NULL) {
  454. drush_sql_bootstrap_further();
  455. $filename = drush_get_option('file', NULL);
  456. // Enable prefix processing when db-prefix option is used.
  457. if (drush_get_option('db-prefix')) {
  458. drush_bootstrap_max(DRUSH_BOOTSTRAP_DRUPAL_DATABASE);
  459. }
  460. $sql = drush_sql_get_class(drush_get_option('db-spec'));
  461. $result = $sql->query($query, $filename, FALSE, drush_get_option('result-file'));
  462. if (!$result) {
  463. return drush_set_error('DRUSH_SQL_NO_QUERY', dt('Query failed.'));
  464. }
  465. drush_print(implode("\n", drush_shell_exec_output()));
  466. return TRUE;
  467. }
  468. /**
  469. * Drops all tables in the database.
  470. */
  471. function drush_sql_drop() {
  472. drush_sql_bootstrap_further();
  473. $sql = drush_sql_get_class();
  474. $db_spec = $sql->db_spec();
  475. if (!drush_confirm(dt('Do you really want to drop all tables in the database !db?', array('!db' => $db_spec['database'])))) {
  476. return drush_user_abort();
  477. }
  478. $tables = $sql->listTables();
  479. $sql->drop($tables);
  480. }
  481. function drush_sql_cli() {
  482. drush_sql_bootstrap_further();
  483. $sql = drush_sql_get_class();
  484. return !(bool)drush_shell_proc_open($sql->connect());
  485. }
  486. /**
  487. * Command callback. Run's the sanitization operations on the current database.
  488. *
  489. * @see hook_drush_sql_sync_sanitize() for adding custom sanitize routines.
  490. */
  491. function drush_sql_sanitize() {
  492. drush_sql_bootstrap_further();
  493. drush_include(DRUSH_BASE_PATH . '/commands/sql', 'sync.sql');
  494. drush_command_invoke_all('drush_sql_sync_sanitize', 'default');
  495. $options = drush_get_context('post-sync-ops');
  496. if (!empty($options)) {
  497. if (!drush_get_context('DRUSH_SIMULATE')) {
  498. $messages = _drush_sql_get_post_sync_messages();
  499. if ($messages) {
  500. drush_print();
  501. drush_print($messages);
  502. }
  503. }
  504. }
  505. if (!drush_confirm(dt('Do you really want to sanitize the current database?'))) {
  506. return drush_user_abort();
  507. }
  508. $sanitize_query = '';
  509. foreach($options as $id => $data) {
  510. $sanitize_query .= $data['query'] . " ";
  511. }
  512. if ($sanitize_query) {
  513. $sql = drush_sql_get_class();
  514. $result = $sql->query($sanitize_query);
  515. }
  516. }
  517. /**
  518. * Call from a pre-sql-sync hook to register an sql
  519. * query to be executed in the post-sql-sync hook.
  520. * @see drush_sql_pre_sql_sync() and @see drush_sql_post_sql_sync().
  521. *
  522. * @param $id
  523. * String containing an identifier representing this
  524. * operation. This id is not actually used at the
  525. * moment, it is just used to fufill the contract
  526. * of drush contexts.
  527. * @param $message
  528. * String with the confirmation message that describes
  529. * to the user what the post-sync operation is going
  530. * to do. This confirmation message is printed out
  531. * just before the user is asked whether or not the
  532. * sql-sync operation should be continued.
  533. * @param $query
  534. * String containing the sql query to execute. If no
  535. * query is provided, then the confirmation message will
  536. * be displayed to the user, but no action will be taken
  537. * in the post-sync hook. This is useful for drush modules
  538. * that wish to provide their own post-sync hooks to fix
  539. * up the target database in other ways (e.g. through
  540. * Drupal APIs).
  541. */
  542. function drush_sql_register_post_sync_op($id, $message, $query = NULL) {
  543. $options = drush_get_context('post-sync-ops');
  544. $options[$id] = array('message' => $message, 'query' => $query);
  545. drush_set_context('post-sync-ops', $options);
  546. }
  547. /**
  548. * Builds a confirmation message for all post-sync operations.
  549. *
  550. * @return string
  551. * All post-sync operation messages concatenated together.
  552. */
  553. function _drush_sql_get_post_sync_messages() {
  554. $messages = FALSE;
  555. $options = drush_get_context('post-sync-ops');
  556. if (!empty($options)) {
  557. $messages = dt('The following post-sync operations will be done on the destination:') . "\n";
  558. foreach($options as $id => $data) {
  559. $messages .= " * " . $data['message'] . "\n";
  560. }
  561. }
  562. return $messages;
  563. }
  564. /**
  565. * Wrapper for drush_get_class; instantiates an driver-specific instance
  566. * of SqlBase class.
  567. *
  568. * @param array $db_spec
  569. * If known, specify a $db_spec that the class can operate with.
  570. *
  571. * @throws \Drush\Sql\SqlException
  572. *
  573. * @return Drush\Sql\SqlBase
  574. */
  575. function drush_sql_get_class($db_spec = NULL) {
  576. $database = drush_get_option('database', 'default');
  577. $target = drush_get_option('target', 'default');
  578. // Try a few times to quickly get $db_spec.
  579. if ($db_spec) {
  580. return drush_get_class('Drush\Sql\Sql', array($db_spec), array($db_spec['driver']));
  581. }
  582. elseif ($url = drush_get_option('db-url')) {
  583. $url = is_array($url) ? $url[$database] : $url;
  584. $db_spec = drush_convert_db_from_db_url($url);
  585. $db_spec['db_prefix'] = drush_get_option('db-prefix');
  586. return drush_sql_get_class($db_spec);
  587. }
  588. elseif (($databases = drush_get_option('databases')) && (array_key_exists($database, $databases)) && (array_key_exists($target, $databases[$database]))) {
  589. $db_spec = $databases[$database][$target];
  590. return drush_sql_get_class($db_spec);
  591. }
  592. else {
  593. // No parameter or options provided. Determine $db_spec ourselves.
  594. if ($sqlVersion = drush_sql_get_version()) {
  595. if ($db_spec = $sqlVersion->get_db_spec()) {
  596. return drush_sql_get_class($db_spec);
  597. }
  598. }
  599. }
  600. throw new \Drush\Sql\SqlException('Unable to find a matching SQL Class. Drush cannot find your database connection details.');
  601. }
  602. function drush_sql_get_version() {
  603. return drush_get_class('Drush\Sql\Sql', array(), array(drush_drupal_major_version())) ?: NULL;
  604. }
  605. /**
  606. * Implements hook_sql_drush_sql_sync_sanitize.
  607. *
  608. * Sanitize usernames, passwords, and sessions when the --sanitize option is used.
  609. * It is also an example of how to write a database sanitizer for sql sync.
  610. *
  611. * To write your own sync hook function, define mymodule_drush_sql_sync_sanitize()
  612. * and follow the form of this function to add your own database
  613. * sanitization operations via the register post-sync op function;
  614. * @see drush_sql_register_post_sync_op(). This is the only thing that the
  615. * sync hook function needs to do; sql-sync takes care of the rest.
  616. *
  617. * The function below has a lot of logic to process user preferences and
  618. * generate the correct SQL regardless of whether Postgres, Mysql,
  619. * Drupal 6 or Drupal 7 is in use. A simpler sanitize function that
  620. * always used default values and only worked with Drupal 6 + mysql
  621. * appears in the drush.api.php. @see hook_drush_sql_sync_sanitize().
  622. */
  623. function sql_drush_sql_sync_sanitize($site) {
  624. $site_settings = drush_sitealias_get_record($site);
  625. $databases = sitealias_get_databases_from_record($site_settings);
  626. $prefix = $databases['default']['default']['prefix'];
  627. $prefix = isset($databases['default']['default']['prefix']) ? $databases['default']['default']['prefix'] : '';
  628. $user_table_updates = array();
  629. $message_list = array();
  630. // Sanitize passwords.
  631. $newpassword = drush_get_option(array('sanitize-password', 'destination-sanitize-password'), 'password');
  632. if ($newpassword != 'no' && $newpassword !== 0) {
  633. $major_version = drush_drupal_major_version();
  634. $pw_op = "";
  635. // In Drupal 6, passwords are hashed via the MD5 algorithm.
  636. if ($major_version == 6) {
  637. $pw_op = "MD5('$newpassword')";
  638. }
  639. // In Drupal 7, passwords are hashed via a more complex algorithm,
  640. // available via the user_hash_password function.
  641. elseif ($major_version == 7) {
  642. $core = DRUSH_DRUPAL_CORE;
  643. include_once $core . '/includes/password.inc';
  644. include_once $core . '/includes/bootstrap.inc';
  645. $hash = user_hash_password($newpassword);
  646. $pw_op = "'$hash'";
  647. }
  648. else {
  649. // Mimic Drupal's /scripts/password-hash.sh
  650. drush_bootstrap(DRUPAL_BOOTSTRAP_CODE);
  651. $password_hasher = \Drupal::service('password');
  652. $hash = $password_hasher->hash($newpassword);
  653. $pw_op = "'$hash'";
  654. }
  655. if (!empty($pw_op)) {
  656. $user_table_updates[] = "pass = $pw_op";
  657. $message_list[] = "passwords";
  658. }
  659. }
  660. // Sanitize email addresses.
  661. $newemail = drush_get_option(array('sanitize-email', 'destination-sanitize-email'), 'user+%uid@localhost.localdomain');
  662. if ($newemail != 'no' && $newemail !== 0) {
  663. if (strpos($newemail, '%') !== FALSE) {
  664. // We need a different sanitization query for Postgres and Mysql.
  665. $db_driver = $databases['default']['default']['driver'];
  666. if ($db_driver == 'pgsql') {
  667. $email_map = array('%uid' => "' || uid || '", '%mail' => "' || replace(mail, '@', '_') || '", '%name' => "' || replace(name, ' ', '_') || '");
  668. $newmail = "'" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "'";
  669. }
  670. else {
  671. $email_map = array('%uid' => "', uid, '", '%mail' => "', replace(mail, '@', '_'), '", '%name' => "', replace(name, ' ', '_'), '");
  672. $newmail = "concat('" . str_replace(array_keys($email_map), array_values($email_map), $newemail) . "')";
  673. }
  674. $user_table_updates[] = "mail = $newmail, init = $newmail";
  675. }
  676. else {
  677. $user_table_updates[] = "mail = '$newemail', init = '$newemail'";
  678. }
  679. $message_list[] = 'email addresses';
  680. }
  681. if (!empty($user_table_updates)) {
  682. $sanitize_query = "UPDATE {$prefix}users SET " . implode(', ', $user_table_updates) . " WHERE uid > 0;";
  683. drush_sql_register_post_sync_op('user-email', dt('Reset !message in user table', array('!message' => implode(' and ', $message_list))), $sanitize_query);
  684. }
  685. // Seems quite portable (SQLite?) - http://en.wikipedia.org/wiki/Truncate_(SQL)
  686. $sql_sessions = "TRUNCATE TABLE {$prefix}sessions;";
  687. drush_sql_register_post_sync_op('sessions', dt('Truncate Drupal\'s sessions table'), $sql_sessions);
  688. }