PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/commands/sql/sql.drush.inc

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