PageRenderTime 110ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/deployMintCore.php

https://github.com/acobrotsen/DeployMint
PHP | 1041 lines | 367 code | 23 blank | 651 comment | 47 complexity | acf376805e5588d1ced1394635d1f2d8 MD5 | raw file
Possible License(s): GPL-3.0

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

  1. <?php
  2. /*
  3. Author: Mark Maunder <mmaunder@gmail.com>
  4. Author website: http://markmaunder.com/
  5. License: GPL 3.0
  6. */
  7. define( 'ERRORLOGFILE' , '/var/log/wp_errors' );
  8. class deploymint{
  9. private static $wpTables = array(
  10. 'commentmeta' ,
  11. 'comments' ,
  12. 'links' ,
  13. 'options' ,
  14. 'postmeta' ,
  15. 'posts' ,
  16. 'term_relationships' ,
  17. 'term_taxonomy' ,
  18. 'terms'
  19. );
  20. public static function installPlugin(){
  21. $sql_createTables = array(
  22. "CREATE TABLE IF NOT EXISTS `dep_options` (
  23. `name` VARCHAR(100) NOT NULL PRIMARY KEY ,
  24. `val` VARCHAR(255) DEFAULT ''
  25. ) DEFAULT CHARSET=utf8" ,
  26. "CREATE TABLE IF NOT EXISTS `dep_projects` (
  27. `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  28. `ctime` INT(11) UNSIGNED NOT NULL ,
  29. `name` VARCHAR(100) NOT NULL ,
  30. `dir` VARCHAR(120) NOT NULL ,
  31. `deleted` TINYINT UNSIGNED DEFAULT 0
  32. ) DEFAULT CHARSET=utf8" ,
  33. "CREATE TABLE IF NOT EXISTS `dep_members` (
  34. `blog_id` INT(11) UNSIGNED NOT NULL ,
  35. `project_id` INT(11) UNSIGNED NOT NULL ,
  36. `deleted` TINYINT UNSIGNED DEFAULT 0 ,
  37. KEY k1( `blog_id` , `project_id` )
  38. ) DEFAULT CHARSET=utf8"
  39. );
  40. if( !( $dbh = mysql_connect( DB_HOST , DB_USER , DB_PASSWORD , true ) )
  41. || !mysql_select_db( DB_NAME , $dbh ) )
  42. die( 'Database error creating table for DeployMint: ' . mysql_error( $dbh ) );
  43. foreach( $sql_createTables as $sql_statement ){
  44. mysql_query( $sql_statement , $dbh);
  45. if( mysql_error( $dbh ) )
  46. die( 'Database error creating table for DeployMint: ' . mysql_error( $dbh ) );
  47. }
  48. $options = self::getOptions();
  49. foreach (array('git', 'mysql', 'mysqldump', 'rsync') as $n) {
  50. $options[$n] = $options[$n] ? $options[$n] : trim(self::mexec("which $n"));
  51. }
  52. self::updateOptions($options);
  53. }
  54. private static function getDefaultOptions(){
  55. return array(
  56. 'git' => '' ,
  57. 'mysql' => '' ,
  58. 'mysqldump' => '' ,
  59. 'rsync' => '' ,
  60. 'numBackups' => 5 ,
  61. 'datadir' => '' ,
  62. 'preserveBlogName' => 1 ,
  63. 'backupDisabled' => 0 ,
  64. 'temporaryDatabase' => '' ,
  65. 'backupDatabase' => ''
  66. );
  67. }
  68. private static function getOptions( $createTemporaryDatabase = false , $createBackupDatabase = false ){
  69. global $wpdb;
  70. if( !( $dbh = mysql_connect( DB_HOST , DB_USER , DB_PASSWORD , true ) )
  71. || !mysql_select_db( DB_NAME , $dbh ) )
  72. die( 'Database error in DeployMint: ' . mysql_error( $dbh ) );
  73. $res = $wpdb->get_results( $wpdb->prepare( 'SELECT `name` , `val` FROM `dep_options`' ) , ARRAY_A );
  74. $options = self::getDefaultOptions();
  75. for( $i = 0 , $c = count( $res ) ; $i < $c ; $i++ ){
  76. $options[$res[$i]['name']] = $res[$i]['val'];
  77. }
  78. $options['backupDisabled'] = ( $options['backupDisabled']=='1' );
  79. $options['temporaryDatabaseCreated'] = false;
  80. if( $options['temporaryDatabase']=='' && $createTemporaryDatabase ){
  81. for( $i = 1 ; $i < 10 ; $i++ ){
  82. $options['temporaryDatabase'] = 'dep_tmpdb' . preg_replace( '/\./' , '' , microtime( true ) );
  83. $res = $wpdb->get_results( $wpdb->prepare( 'SHOW TABLES FROM ' . $options['temporaryDatabase'] ) , ARRAY_A );
  84. if( count( $res ) < 1 )
  85. break;
  86. if( $i > 4 )
  87. self::ajaxError( 'We could not create a temporary database name after 5 tries. You may not have the create DB privilege.' );
  88. }
  89. $wpdb->query( $wpdb->prepare( 'CREATE DATABASE ' . $options['temporaryDatabase'] ) );
  90. $options['temporaryDatabaseCreated'] = true;
  91. }
  92. $options['backupDatabaseCreated'] = false;
  93. if( $createBackupDatabase && !$options['backupDisabled'] && $options['numBackups']!=1 ){
  94. $dbPrefix = ( $options['backupDatabase']=='' ? 'depbak' : $options['backupDatabase'] );
  95. $options['backupDatabase'] = $dbPrefix . '__' . preg_replace( '/\./' , '' , microtime( true ) );
  96. if( !mysql_query( 'CREATE DATABASE ' . $options['backupDatabase'] , $dbh ) )
  97. self::ajaxError( 'Could not create backup database. ' . mysql_error( $dbh ) );
  98. $options['backupDatabaseCreated'] = true;
  99. }
  100. return $options;
  101. }
  102. private static function emptyDatabase( $database , $connection ){
  103. if( $result = mysql_query( 'SHOW TABLES IN ' . $database , $connection ) ){
  104. while( $row = mysql_fetch_array( $result , MYSQL_NUM ) ){
  105. mysql_query( 'DROP TABLE IF EXISTS ' . $row[0] , $connection );
  106. }
  107. }
  108. }
  109. private static function updateOptions( $o ){
  110. foreach( $o as $n => $v ){
  111. self::setOption( $n , $v );
  112. }
  113. }
  114. private static function setOption( $name , $val ){
  115. global $wpdb;
  116. $wpdb->query( $wpdb->prepare( 'INSERT INTO `dep_options` ( `name` , `val` ) VALUES ( %s , %s ) ON DUPLICATE KEY UPDATE `val` = %s' , $name , $val , $val ) );
  117. }
  118. private static function allOptionsSet(){
  119. global $wpdb;
  120. $options = self::getOptions();
  121. foreach( array( 'git' , 'mysql' , 'mysqldump' , 'rsync' , 'datadir' ) as $v ){
  122. if( !$options[$v] )
  123. return false;
  124. }
  125. return preg_match( '/^\d+$/' , $options['numBackups'] );
  126. }
  127. public static function setup(){
  128. global $wpdb;
  129. if( is_network_admin() && is_multisite() ){
  130. add_action( 'network_admin_menu' , 'deploymint::adminMenuHandler' );
  131. }elseif( is_admin() && !is_multisite() ){
  132. add_action( 'admin_menu' , 'deploymint::adminMenuHandler' );
  133. }
  134. add_action( 'init', 'deploymint::initHandler');
  135. add_action( 'wp_enqueue_scripts', 'deploymint::enqueue_scripts');
  136. add_action( 'wp_enqueue_styles', 'deploymint::enqueue_styles');
  137. add_action( 'wp_ajax_deploymint_deploy' , 'deploymint::ajax_deploy_callback' );
  138. add_action( 'wp_ajax_deploymint_createProject' , 'deploymint::ajax_createProject_callback' );
  139. add_action( 'wp_ajax_deploymint_reloadProjects' , 'deploymint::ajax_reloadProjects_callback' );
  140. add_action( 'wp_ajax_deploymint_updateCreateSnapshot' , 'deploymint::ajax_updateCreateSnapshot_callback' );
  141. add_action( 'wp_ajax_deploymint_updateDeploySnapshot' , 'deploymint::ajax_updateDeploySnapshot_callback' );
  142. add_action( 'wp_ajax_deploymint_updateSnapDesc' , 'deploymint::ajax_updateSnapDesc_callback' );
  143. add_action( 'wp_ajax_deploymint_createSnapshot' , 'deploymint::ajax_createSnapshot_callback' );
  144. add_action( 'wp_ajax_deploymint_deploySnapshot' , 'deploymint::ajax_deploySnapshot_callback' );
  145. add_action( 'wp_ajax_deploymint_undoDeploy' , 'deploymint::ajax_undoDeploy_callback' );
  146. add_action( 'wp_ajax_deploymint_addBlogToProject' , 'deploymint::ajax_addBlogToProject_callback' );
  147. add_action( 'wp_ajax_deploymint_removeBlogFromProject' , 'deploymint::ajax_removeBlogFromProject_callback' );
  148. add_action( 'wp_ajax_deploymint_deleteProject' , 'deploymint::ajax_deleteProject_callback' );
  149. add_action( 'wp_ajax_deploymint_deleteBackups' , 'deploymint::ajax_deleteBackups_callback' );
  150. add_action( 'wp_ajax_deploymint_updateOptions' , 'deploymint::ajax_updateOptions_callback' );
  151. if( !self::allOptionsSet() ){
  152. if( is_multisite() ){
  153. add_action( 'network_admin_notices' , 'deploymint::msgDataDir' );
  154. }else{
  155. add_action( 'admin_notices' , 'deploymint::msgDataDir' );
  156. }
  157. }
  158. }
  159. public static function enqueue_scripts(){
  160. /*
  161. wp_deregister_script( 'jquery' );
  162. wp_enqueue_script( 'jquery' , plugin_dir_url( __FILE__ ) . 'js/jquery-1.6.2.js' , array( ) );
  163. */
  164. }
  165. public static function enqueue_styles(){
  166. wp_register_style( 'DeployMintCSS' , plugin_dir_url( __FILE__ ) . 'css/admin.css' );
  167. wp_enqueue_style( 'DeployMintCSS' );
  168. }
  169. public static function __callStatic( $name , $args ){
  170. $matches = array();
  171. if( preg_match( '/^projectMenu(\d+)$/' , $name , $matches ) ){
  172. self::projectMenu( $matches[1] );
  173. }else{
  174. die( "Method $name doesn't exist!" );
  175. }
  176. }
  177. private static function checkPerms(){
  178. if( !is_user_logged_in() )
  179. die( '<h2>You are not logged in.</h2>' );
  180. if( ( is_multisite() && !current_user_can( 'manage_network' ) )
  181. || ( !is_multisite() && !current_user_can( 'manage_options' ) ) )
  182. die( '<h2>You do not have permission to access this page.</h2><p>You need the "manage_network" Super Admin capability to use DeployMint.</p>' );
  183. }
  184. public static function projectMenu( $projectid ){
  185. self::checkPerms();
  186. global $wpdb;
  187. if( !self::allOptionsSet() ){
  188. echo '<div class="wrap"><h2 class="depmintHead">Please visit the options page and configure all options</h2></div>';
  189. return;
  190. }
  191. $res = $wpdb->get_results($wpdb->prepare( 'SELECT * FROM `dep_projects` WHERE `id` = %d AND `deleted` = 0' , $projectid ) , ARRAY_A );
  192. $proj = $res[0];
  193. include( 'views/projectPage.php' );
  194. }
  195. public static function ajax_createProject_callback(){
  196. self::checkPerms();
  197. global $wpdb;
  198. $opt = self::getOptions();
  199. extract( $opt , EXTR_OVERWRITE );
  200. $exists = $wpdb->get_results( $wpdb->prepare( 'SELECT `name` FROM `dep_projects` WHETE `name` = %s AND `deleted` = 0' , $_POST['name'] ) , ARRAY_A );
  201. if( count( $exists ) )
  202. die( json_encode( array( 'err' => 'A project with that name already exists.' ) ) );
  203. $dir = $_POST['name'];
  204. $dir = preg_replace( '/[^a-zA-Z0-9]+/' , '_' , $dir );
  205. $fulldir = $dir . '-1';
  206. $counter = 2;
  207. while( is_dir( $datadir . $fulldir ) ){
  208. $fulldir = preg_replace( '/\-\d+$/' , '' , $fulldir );
  209. $fulldir .= '-' . $counter;
  210. $counter++;
  211. if( $counter > 1000 )
  212. die( json_encode( array( 'err' => "Too many directories already exist starting with \"$dir\"" ) ) );
  213. }
  214. $finaldir = $datadir . $fulldir;
  215. if( !@mkdir( $finaldir , 0755 ) )
  216. die( json_encode( array( 'err' => "Could not create directory $finaldir" ) ) );
  217. $git1 = self::mexec( "$git init ; $git add . " , $finaldir );
  218. $wpdb->query( $wpdb->prepare( 'INSERT INTO `dep_projects` ( `ctime` , `name` , `dir` ) VALUES ( UNIX_TIMESTAMP() , %s , %s )' , $_POST['name'] , $fulldir ) );
  219. die( json_encode( array( 'ok' => 1 ) ) );
  220. }
  221. public static function ajax_updateOptions_callback(){
  222. self::checkPerms();
  223. $defaultOptions = self::getDefaultOptions();
  224. $P = array_map( 'trim' , $_POST );
  225. $git = $P['git'];
  226. $mysql = $P['mysql'];
  227. $mysqldump = $P['mysqldump'];
  228. $rsync = $P['rsync'];
  229. $numBackups = $P['numBackups'];
  230. $temporaryDatabase = $P['temporaryDatabase'];
  231. $backupDisabled = ( $P['backupDisabled']!='' ? 1 : 0 );
  232. $backupDatabase = $P['backupDatabase'];
  233. $datadir = $P['datadir'];
  234. if( substr( $datadir , -1 )!='/' )
  235. $datadir .= '/';
  236. $errs = array();
  237. if( !( $git && $mysql && $mysqldump && $rsync && $datadir ) )
  238. $errs[] = 'You must specify a value for all options.';
  239. if( !preg_match('/^\d+$/' , $numBackups ) ){
  240. if( $backupDisabled ){
  241. $numBackups = $defaultOptions['numBackups'];
  242. } else {
  243. $errs[] = 'The number of backups you specify must be a number or 0 to keep all backups.';
  244. }
  245. }
  246. $preserveBlogName = trim($_POST['preserveBlogName']);
  247. if( $preserveBlogName!=0 && $preserveBlogName!=1 )
  248. $errs[] = "Invalid value for preserveBlogName. Expected 1 or 0. Received $preserveBlogName";
  249. if( count( $errs ) )
  250. die( json_encode( array( 'errs' => $errs ) ) );
  251. if( !file_exists( $mysql ) )
  252. $errs[] = "The file '$mysql' specified for mysql doesn't exist.";
  253. if( !file_exists( $mysqldump ) )
  254. $errs[] = "The file '$mysqldump' specified for mysqldump doesn't exist.";
  255. if( !file_exists( $rsync ) )
  256. $errs[] = "The file '$rsync' specified for rsync doesn't exist.";
  257. if( !file_exists( $git ) )
  258. $errs[] = "The file '$git' specified for git doesn't exist.";
  259. if( !is_dir( $datadir ) ){
  260. $errs[] = "The directory '$datadir' specified as the data directory doesn't exist.";
  261. }else{
  262. $fh = fopen( $datadir . '/test.tmp' , 'w' );
  263. if( !fwrite( $fh , 't' ) )
  264. $errs[] = "The directory $datadir is not writeable.";
  265. fclose( $fh );
  266. unlink( $datadir . '/test.tmp' );
  267. }
  268. if( count( $errs ) )
  269. die( json_encode( array( 'errs' => $errs ) ) );
  270. $options = array(
  271. 'git' => $git ,
  272. 'mysql' => $mysql ,
  273. 'mysqldump' => $mysqldump ,
  274. 'rsync' => $rsync ,
  275. 'datadir' => $datadir ,
  276. 'numBackups' => $numBackups ,
  277. 'temporaryDatabase' => $temporaryDatabase ,
  278. 'backupDisabled' => $backupDisabled ,
  279. 'backupDatabase' => $backupDatabase ,
  280. 'preserveBlogName' => $preserveBlogName
  281. );
  282. self::updateOptions( $options );
  283. die( json_encode( array( 'ok' => 1 ) ) );
  284. }
  285. public static function ajax_deleteBackups_callback(){
  286. self::checkPerms();
  287. if( !( $dbh = mysql_connect( DB_HOST , DB_USER , DB_PASSWORD , true ) ) )
  288. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  289. $toDel = $_POST['toDel'];
  290. for( $i = 0 , $c = count( $toDel ) ; $i < $c ; $i++ ){
  291. mysql_query( 'DROP DATABASE ' . $toDel[$i] , $dbh );
  292. if( mysql_error( $dbh ) )
  293. self::ajaxError( 'Could not drop database ' . $toDel[$i] . '. Error: ' . mysql_error( $dbh ) );
  294. }
  295. die( json_encode( array( 'ok' => 1 ) ) );
  296. }
  297. public static function ajax_deleteProject_callback(){
  298. self::checkPerms();
  299. global $wpdb;
  300. $wpdb->query( $wpdb->prepare( 'UPDATE `dep_members` SET `deleted` = 1 WHERE `project_id` = %d' , $_POST['blogID'] , $_POST['projectID'] ) );
  301. $wpdb->query( $wpdb->prepare( 'UPDATE `dep_projects` SET `deleted` = 1 WHERE `id` = %d' , $_POST['projectID'] ) );
  302. die( json_encode( array( 'ok' => 1 ) ) );
  303. }
  304. public static function ajax_removeBlogFromProject_callback(){
  305. self::checkPerms();
  306. global $wpdb;
  307. $wpdb->query( $wpdb->prepare( 'UPDATE `dep_members` SET `deleted` = 1 WHERE `blog_id` = %d AND `project_id` = %d' , $_POST['blogID'] , $_POST['projectID'] ) );
  308. die( json_encode( array( 'ok' => 1 ) ) );
  309. }
  310. public static function ajax_addBlogToProject_callback(){
  311. self::checkPerms();
  312. global $wpdb;
  313. $det = get_blog_details( $_POST['blogID'] );
  314. if( !$det )
  315. die( json_encode( array( 'err' => 'Please select a valid blog to add.' ) ) );
  316. $wpdb->query( $wpdb->prepare( 'INSERT INTO `dep_members` ( `blog_id` , `project_id` ) VALUES ( %d , %d )' , $_POST['blogID'] , $_POST['projectID'] ) );
  317. die( json_encode( array( 'ok' => 1 ) ) );
  318. }
  319. public static function ajax_createSnapshot_callback(){
  320. self::checkPerms();
  321. global $wpdb;
  322. $opt = self::getOptions();
  323. extract( $opt , EXTR_OVERWRITE );
  324. $pid = $_POST['projectid'];
  325. $blogid = $_POST['blogid'];
  326. $name = $_POST['name'];
  327. $desc = $_POST['desc'];
  328. if( !preg_match( '/\w+/' , $name ) )
  329. self::ajaxError( 'Please enter a name for this snapshot' );
  330. if( strlen( $name ) > 20 )
  331. self::ajaxError( 'Your snapshot name must be 20 characters or less.' );
  332. if( preg_match( '/[^a-zA-Z0-9\_\-\.]/' , $name ) )
  333. self::ajaxError( 'Your snapshot name can only contain characters a-z A-Z 0-9 and dashes, underscores and dots.' );
  334. if( !$desc )
  335. self::ajaxError( 'Please enter a description for this snapshot.' );
  336. $prec = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM `dep_projects` WHERE `id` = %d AND `deleted` = 0' , $pid ) , ARRAY_A );
  337. if( !count( $prec ) )
  338. self::ajaxError( 'That project does not exist.' );
  339. $proj = $prec[0];
  340. $dir = $datadir . $proj['dir'] . '/';
  341. $mexists = $wpdb->get_results( $wpdb->prepare( 'SELECT `blog_id` FROM `dep_members` WHERE `blog_id` = %d AND `project_id` = %d AND `deleted` = 0' , $blogid , $pid ) , ARRAY_A );
  342. if( !count( $mexists ) )
  343. self::ajaxError( 'That blog does not exist or is not a member of this project.' );
  344. if( !is_dir( $dir ) )
  345. self::ajaxError( "The directory $dir for this project doesn't exist for some reason. Did you delete it?" );
  346. $branchOut = self::mexec( "$git branch 2>&1" , $dir );
  347. if( preg_match( '/fatal/' , $branchOut ) )
  348. self::ajaxError( "The directory $dir is not a valid git repository. The output we received is: $branchOut" );
  349. $branches = preg_split( '/[\r\n\s\t\*]+/' , $branchOut );
  350. $bdup = array();
  351. for( $i = 0 , $c = count( $branches ) ; $i < $c ; $i++ ){
  352. $bdup[$branches[$i]] = 1;
  353. }
  354. if( array_key_exists( $name , $bdup ) )
  355. self::ajaxError( "A snapshot with the name $name already exists. Please choose another." );
  356. $cout1 = self::mexec( "$git checkout master 2>&1" , $dir );
  357. // Before we do our initial commit we will get an error trying to checkout master because it doesn't exist.
  358. if( !preg_match( "/(?:Switched to branch|Already on|error: pathspec 'master' did not match)/" , $cout1 ) )
  359. self::ajaxError( "We could not switch the git repository in $dir to 'master'. The output was: $cout1" );
  360. $prefix = '';
  361. if( $blogid==1 ){
  362. $prefix = $wpdb->base_prefix;
  363. } else {
  364. $prefix = $wpdb->base_prefix . $blogid . '_';
  365. }
  366. $prefixFile = $dir . 'deployData.txt';
  367. $fh2 = fopen( $prefixFile , 'w' );
  368. if( !fwrite( $fh2 , $prefix . ':' . microtime( true ) ) )
  369. self::ajaxError( "We could not write to deployData.txt in the directory $dir" );
  370. fclose( $fh2 );
  371. $prefixOut = self::mexec( "$git add deployData.txt 2>&1" , $dir );
  372. // Add the Media locations
  373. $files = self::mexec( "$rsync -r -d " . WP_CONTENT_DIR . "/blogs.dir/$blogid/* {$dir}blogs.dir/" );
  374. $filesOut = self::mexec( "$git add blogs.dir/ 2>&1" , $dir );
  375. $siteURLRes = $wpdb->get_results( $wpdb->prepare( 'SELECT `option_name` , `option_value` FROM `' . $prefix . 'options` WHERE `option_name` = "siteurl"' ) , ARRAY_A );
  376. $siteURL = $siteURLRes[0]['option_value'];
  377. $desc = "Snapshot of: {$siteURL}\n{$desc}";
  378. $dumpErrs = array();
  379. foreach( self::$wpTables as $t ){
  380. $tableFile = $t . '.sql';
  381. $tableName = $prefix . $t;
  382. $path = $dir . $tableFile;
  383. $dbuser = DB_USER;
  384. $dbpass = DB_PASSWORD;
  385. $dbhost = DB_HOST;
  386. $dbname = DB_NAME;
  387. $o1 = self::mexec( "{$mysqldump} --skip-comments --extended-insert --complete-insert --skip-comments -u {$dbuser} -p{$dbpass} -h {$dbhost} {$dbname} {$tableName} > {$path} 2>&1" , $dir );
  388. if( preg_match( '/\w+/' , $o1 ) ){
  389. array_push( $dumpErrs , $o1 );
  390. }else{
  391. $grepOut = self::mexec( "grep CREATE $path 2>&1" );
  392. if( !preg_match( '/CREATE/' , $grepOut ) ){
  393. array_push( $dumpErrs , "We could not create a valid table dump file for $tableName" );
  394. }else{
  395. $gitAddOut = self::mexec( "$git add $tableFile 2>&1" , $dir );
  396. if( preg_match( '/\w+/' , $gitAddOut ) )
  397. self::ajaxError("We encountered an error running '$git add $tableFile' the error was: $gitAddOut");
  398. }
  399. }
  400. }
  401. if( count( $dumpErrs ) ){
  402. $resetOut = self::mexec( "$git reset --hard HEAD 2>&1" , $dir );
  403. if (!preg_match('/HEAD is now at/', $resetOut))
  404. self::ajaxError( "Errors occured during mysqldump and we could not revert the git repository in {$dir} back to it's original state using '{$git} reset --hard HEAD'. The output we got was: {$resetOut}" );
  405. self::ajaxError( 'Errors occured during mysqldump: ' . implode( ', ' , $dumpErrs ) );
  406. }
  407. $tmpfile = $datadir . microtime( true ) . '.tmp';
  408. $fh = fopen( $tmpfile , 'w' );
  409. fwrite( $fh , $desc );
  410. fclose( $fh );
  411. global $current_user;
  412. get_currentuserinfo();
  413. $commitUser = $current_user->user_firstname . ' ' . $current_user->user_lastname . ' <' . $current_user->user_email . '>';
  414. $commitOut2 = self::mexec( "$git commit --author=\"$commitUser\" -a -F \"$tmpfile\" 2>&1" , $dir );
  415. unlink( $tmpfile );
  416. if( !preg_match( '/files changed/' , $commitOut2 ) )
  417. self::ajaxError( "git commit failed. The output we got was: $commitOut2" );
  418. $brOut2 = self::mexec( "$git branch $name 2>&1 " , $dir );
  419. if( preg_match( '/\w+/' , $brOut2 ) )
  420. self::ajaxError( "We encountered an error running '$git branch $name' the output was: $brOut2" );
  421. die( json_encode( array( 'ok' => 1 ) ) );
  422. }
  423. public static function ajax_undoDeploy_callback(){
  424. self::checkPerms();
  425. global $wpdb;
  426. $opt = self::getOptions( true );
  427. extract( $opt , EXTR_OVERWRITE );
  428. $sourceDBName = $_POST['dbname'];
  429. if( !( $dbh = mysql_connect( DB_HOST , DB_USER , DB_PASSWORD , true ) )
  430. || !mysql_select_db( $sourceDBName , $dbh ) )
  431. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  432. $res1 = mysql_query( 'SHOW TABLES' , $dbh );
  433. if( mysql_error( $dbh ) )
  434. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  435. $allTables = array();
  436. while( $row1 = mysql_fetch_array( $res1 , MYSQL_NUM ) ){
  437. if( !preg_match( '/^dep_/' , $row1[0] ) )
  438. array_push( $allTables , $row1[0] );
  439. }
  440. $renames = array();
  441. foreach( $allTables as $t ){
  442. array_push( $renames , "{$dbname}.{$t} TO {$temporaryDatabase}.{$t} , {$sourceDBName}.{$t} TO {$dbname}.{$t}" );
  443. }
  444. $stime = microtime( true );
  445. mysql_query( 'RENAME TABLE ' . implode( ', ' , $renames ) , $dbh );
  446. if( mysql_error( $dbh ) )
  447. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  448. $lockTime = sprintf( '%.4f' , microtime( true ) - $stime );
  449. if( $temporaryDatabaseCreated ){
  450. mysql_query( "DROP DATABASE {$temporaryDatabase}" , $dbh );
  451. }else{
  452. self::emptyDatabase( $temporaryDatabase , $dbh );
  453. }
  454. foreach( $allTables as $t ){
  455. mysql_query( "CREATE TABLE {$sourceDBName}.{$t} LIKE {$dbname}.{$t}" , $dbh );
  456. if( mysql_error( $dbh ) )
  457. self::ajaxError( 'A database error occured trying to recreate the backup database, but the deployment completed. Error: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  458. mysql_query( "INSERT INTO {$sourceDBName}.{$t} SELECT * FROM {$dbname}.{$t}" , $dbh );
  459. if (mysql_error($dbh))
  460. self::ajaxError( 'A database error occured trying to recreate the backup database, but the deployment completed. Error: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  461. }
  462. if( mysql_error( $dbh ) )
  463. self::ajaxError( 'A database error occured (but the revert was completed!): ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  464. die( json_encode( array( 'ok' => 1 , 'lockTime' => $lockTime ) ) );
  465. }
  466. public static function ajax_deploySnapshot_callback(){
  467. self::checkPerms();
  468. global $wpdb;
  469. $opt = self::getOptions( true , true );
  470. extract( $opt , EXTR_OVERWRITE );
  471. $pid = $_POST['projectid'];
  472. $blogid = $_POST['blogid'];
  473. $name = $_POST['name'];
  474. $leaveComments = true; //$_POST['leaveComments'];
  475. if( !preg_match( '/\w+/' , $name ) )
  476. self::ajaxError( 'Please select a snapshot to deploy.' );
  477. $prec = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM `dep_projects` WHERE `id` = %d AND `deleted` = 0' , $pid ) , ARRAY_A );
  478. if( !count( $prec ) )
  479. self::ajaxError( 'That project does not exist.');
  480. $proj = $prec[0];
  481. $dir = $datadir . $proj['dir'] . '/';
  482. $mexists = $wpdb->get_results( $wpdb->prepare( 'SELECT `blog_id` FROM `dep_members` WHERE `blog_id` = %d AND `project_id` = %d AND `deleted` = 0' , $blogid , $pid ) , ARRAY_A );
  483. if( !count( $mexists ) )
  484. self::ajaxError( 'That blog does not exist or is not a member of this project. Please select a valid blog to deploy to.' );
  485. if( !is_dir( $dir ) )
  486. self::ajaxError( "The directory {$dir} for this project does not exist for some reason. Did you delete it?" );
  487. $co1 = self::mexec( "$git checkout $name 2>&1" , $dir );
  488. if( !preg_match( '/(?:Switched|Already)/' , $co1 ) )
  489. self::ajaxError( "We could not find snapshot $name in the git repository. The error was: $co1" );
  490. $destTablePrefix = '';
  491. if( $blogid==1 ){
  492. $destTablePrefix = $wpdb->base_prefix;
  493. }else{
  494. $destTablePrefix = $wpdb->base_prefix . $blogid . '_';
  495. }
  496. $optionsToRestore = array( 'siteurl' , 'home' , 'upload_path' );
  497. if( $opt['preserveBlogName'] )
  498. $optionsToRestore[] = 'blogname';
  499. $res3 = $wpdb->get_results( $wpdb->prepare( "SELECT `option_name` , `option_value` FROM {$destTablePrefix}options WHERE option_name IN (\"" . implode( '","' , $optionsToRestore ) . '")' ) , ARRAY_A );
  500. if( !count( $res3 ) )
  501. self::ajaxError( 'We could not find the data we need for the blog you are trying to deploy to.' );
  502. $options = array();
  503. for( $i = 0 , $c = count( $res3 ) ; $i < $c ; $i++ ){
  504. $options[$res3[$i]['option_name']] = $res3[$i]['option_value'];
  505. }
  506. // Update the Media folder
  507. $files = self::mexec( "$rsync -r -d $dir" . "blogs.dir/* " . WP_CONTENT_DIR . "/blogs.dir/{$blogid}/" );
  508. $fh = fopen( $dir . 'deployData.txt' , 'r' );
  509. $deployData = fread( $fh , 100 );
  510. $depDat = explode( ':' , $deployData );
  511. $sourceTablePrefix = $depDat[0];
  512. if( !$sourceTablePrefix )
  513. self::ajaxError( "We could not read the table prefix from $dir/deployData.txt" );
  514. $dbuser = DB_USER;
  515. $dbpass = DB_PASSWORD;
  516. $dbhost = DB_HOST;
  517. $dbname = DB_NAME;
  518. $slurp1 = self::mexec( "cat *.sql | $mysql -u $dbuser -p$dbpass -h $dbhost $temporaryDatabase " , $dir );
  519. if( preg_match( '/\w+/' , $slurp1 ) )
  520. self::ajaxError( "We encountered an error importing the data files from snapshot $name into database $temporaryDatabase $dbuser:$dbpass@$dbhost. The error was: " . substr( $slurp1 , 0 , 1000 ) );
  521. if( !( $dbh = mysql_connect( $dbhost , $dbuser , $dbpass , true ) ) && mysql_error( $dbh ) )
  522. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  523. if( !mysql_select_db( $temporaryDatabase , $dbh ) )
  524. self::ajaxError( "Could not select temporary database $temporaryDatabase : " . mysql_error( $dbh ) );
  525. $curdbres = mysql_query( 'SELECT DATABASE()' , $dbh );
  526. $curdbrow = mysql_fetch_array( $curdbres , MYSQL_NUM );
  527. if( mysql_error( $dbh ) )
  528. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  529. $destSiteURL = $options['siteurl'];
  530. $res4 = mysql_query( "SELECT `option_value` FROM `{$sourceTablePrefix}options` WHERE `option_name` = 'siteurl'" , $dbh );
  531. if( mysql_error( $dbh ) ){
  532. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  533. if( !$res4 )
  534. self::ajaxError( 'We could not get the siteurl from the database we are about to deploy. That could mean that we could not create the DB or the import failed.' );
  535. $row = mysql_fetch_array( $res4 , MYSQL_ASSOC );
  536. if( mysql_error( $dbh ) )
  537. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  538. if( !$row )
  539. self::ajaxError( 'We could not get the siteurl from the database we are about to deploy. That could mean that we could not create the DB or the import failed. (2)' );
  540. $sourceSiteURL = $row['option_value'];
  541. if( !$sourceSiteURL )
  542. self::ajaxError( 'We could not get the siteurl from the database we are about to deploy. That could mean that we could not create the DB or the import failed. (3)' );
  543. $destHost = preg_replace( '/^https?:\/\/([^\/]+).*$/i' , '$1' , $destSiteURL );
  544. $sourceHost = preg_replace( '/^https?:\/\/([^\/]+).*$/i' , '$1' , $sourceSiteURL );
  545. foreach( $options as $oname => $val ){
  546. mysql_query( "UPDATE {$sourceTablePrefix}options SET option_value = '" . mysql_real_escape_string( $val ) . "' WHERE option_name='" . mysql_real_escape_string( $oname ) . "'", $dbh );
  547. if( mysql_error( $dbh ) )
  548. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  549. }
  550. $res5 = mysql_query( "SELECT id , post_content , guid FROM {$sourceTablePrefix}posts" , $dbh );
  551. if( mysql_error( $dbh ) )
  552. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  553. while( $row = mysql_fetch_array( $res5 , MYSQL_ASSOC ) ){
  554. $content = preg_replace( '/(https?:\/\/)' . $sourceHost . '/i' , '$1' . $destHost , $row['post_content'] );
  555. $guid = preg_replace( '/(https?:\/\/)' . $sourceHost . '/i' , '$1' . $destHost , $row['guid'] );
  556. mysql_query( "UPDATE {$sourceTablePrefix}posts SET post_content = '" . mysql_real_escape_string( $content ) . "' , guid = '" . mysql_real_escape_string( $guid ) . "' WHERE ID=" . $row['ID'] , $dbh );
  557. if( mysql_error( $dbh ) )
  558. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  559. }
  560. mysql_query( "UPDATE {$temporaryDatabase}.{$sourceTablePrefix}options SET option_name = '{$destTablePrefix}user_roles' WHERE option_name = '{$sourceTablePrefix}user_roles'" , $dbh );
  561. if( mysql_error( $dbh ) )
  562. self::ajaxError( 'A database error occured while updating the user_roles option in the destination database: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  563. if( $leaveComments ){
  564. // Delete comments from DB we're deploying
  565. mysql_query( "DELETE FROM {$sourceTablePrefix}comments" , $dbh );
  566. if( mysql_error( $dbh ) )
  567. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  568. mysql_query( "DELETE FROM {$sourceTablePrefix}commentmeta" , $dbh );
  569. if( mysql_error( $dbh ) )
  570. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  571. //Bring comments across from live (destination) DB
  572. mysql_query( "INSERT INTO {$temporaryDatabase}.{$sourceTablePrefix}comments SELECT * FROM {$dbname}.{$destTablePrefix}comments" , $dbh );
  573. if( mysql_error( $dbh ) )
  574. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  575. mysql_query( "INSERT INTO {$temporaryDatabase}.{$sourceTablePrefix}commentmeta SELECT * FROM {$dbname}.{$destTablePrefix}commentmeta" , $dbh );
  576. if( mysql_error( $dbh ) )
  577. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  578. // Then remap comments to posts based on the "slug" which is the post_name
  579. $res6 = mysql_query( "SELECT dp.post_name AS destPostName , dp.ID AS destID , sp.post_name AS sourcePostName , sp.ID AS sourceID FROM {$dbname}.{$destTablePrefix}posts as dp , {$temporaryDatabase}.{$sourceTablePrefix}posts AS sp WHERE dp.post_name = sp.post_name" , $dbh );
  580. if( mysql_error( $dbh ) )
  581. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  582. if( !$res6 )
  583. self::ajaxError( 'DB error creating maps betweeb post slugs: ' . mysql_error( $dbh ) );
  584. $pNameMap = array();
  585. while( $row = mysql_fetch_array( $res6 , MYSQL_ASSOC ) ){
  586. $pNameMap[$row['destID']] = $row['sourceID'];
  587. }
  588. $res10 = mysql_query( "SELECT comment_ID , comment_post_ID FROM {$sourceTablePrefix}comments" , $dbh );
  589. if( mysql_error( $dbh ) )
  590. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  591. while( $row = mysql_fetch_array( $res10 , MYSQL_ASSOC ) ){
  592. if( array_key_exists( $row['comment_post_ID'] , $pNameMap ) ){
  593. // If a post exists in the source with the same slug as the destination, then associate the destination's comments with that post.
  594. mysql_query( "UPDATE {$sourceTablePrefix}comments set comment_post_ID = " . $pNameMap[$row['comment_post_ID']] . " WHERE comment_ID = " . $row['comment_ID'] , $dbh );
  595. if( mysql_error( $dbh ) )
  596. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  597. }else{
  598. // Otherwise delete the comment because it is associated with a post on the destination which does not exist in the source we're about to deploy
  599. mysql_query( "DELETE FROM {$sourceTablePrefix}comments WHERE comment_ID = " . $row['comment_ID'] , $dbh );
  600. if( mysql_error( $dbh ) )
  601. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  602. }
  603. }
  604. $res11 = mysql_query( "SELECT id FROM {$sourceTablePrefix}posts" , $dbh );
  605. if( mysql_error( $dbh ) )
  606. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  607. while ($row = mysql_fetch_array($res11, MYSQL_ASSOC)) {
  608. $res12 = mysql_query( "SELECT COUNT(*) AS cnt FROM {$sourceTablePrefix}comments WHERE comment_post_ID = " . $row['ID'] , $dbh );
  609. if( mysql_error( $dbh ) )
  610. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  611. $row5 = mysql_fetch_array($res12, MYSQL_ASSOC);
  612. mysql_query( "UPDATE {$sourceTablePrefix}posts SET comment_count = " . $row5['cnt'] . " WHERE id = " . $row['ID'] , $dbh );
  613. if( mysql_error( $dbh ) )
  614. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  615. }
  616. }
  617. if( !$backupDisabled ){
  618. if( !mysql_select_db( $dbname , $dbh ) )
  619. self::ajaxError( "Could not select database {$dbname} : " . mysql_error( $dbh ) );
  620. $curdbres = mysql_query( 'SELECT DATABASE()' , $dbh );
  621. $curdbrow = mysql_fetch_array( $curdbres , MYSQL_NUM );
  622. if( mysql_error( $dbh ) )
  623. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  624. $res14 = mysql_query( 'SHOW TABLES' , $dbh );
  625. if( mysql_error( $dbh ) )
  626. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  627. $allTables = array();
  628. while( $row = mysql_fetch_array( $res14 , MYSQL_NUM ) ){
  629. array_push( $allTables , $row[0] );
  630. }
  631. if( mysql_error( $dbh ) )
  632. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  633. if( !mysql_select_db( $backupDatabase , $dbh ) )
  634. self::ajaxError("Could not select backup database {$backupDatabase} : " . mysql_error( $dbh ) );
  635. error_log( "BACKUPDB: {$backupDatabase}" );
  636. $curdbres = mysql_query( 'SELECT DATABASE()' , $dbh );
  637. $curdbrow = mysql_fetch_array( $curdbres , MYSQL_NUM );
  638. self::emptyDatabase( $backupDatabase , $dbh );
  639. foreach( $allTables as $t ){
  640. // We're taking across all tables including dep_ tables just so we have a backup. We won't deploy dep_ tables though
  641. mysql_query( "CREATE TABLE {$backupDatabase}.{$t} LIKE {$dbname}.{$t}" , $dbh );
  642. if( mysql_error( $dbh ) )
  643. self::ajaxError( "Could not create table {$t} in backup DB: " . mysql_error( $dbh ) );
  644. mysql_query( "INSERT INTO {$t} SELECT * FROM {$dbname}.{$t}" , $dbh );
  645. if( mysql_error( $dbh ) )
  646. self::ajaxError( "Could not copy table {$t} from {$dbname} database: " . mysql_error( $dbh ) );
  647. }
  648. mysql_query( "CREATE TABLE dep_backupdata ( name VARCHAR(20) NOT NULL , val VARCHAR(255) DEFAULT '')" , $dbh );
  649. if( mysql_error( $dbh ) )
  650. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  651. mysql_query( "INSERT INTO dep_backupdata VALUES ('blogid', '{$blogid}')" , $dbh );
  652. if( mysql_error( $dbh ) )
  653. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  654. mysql_query( "INSERT INTO dep_backupdata VALUES ('prefix', '{$destTablePrefix}')", $dbh);
  655. if( mysql_error( $dbh ) )
  656. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  657. mysql_query( "INSERT INTO dep_backupdata VALUES ('deployTime', '" . microtime(true) . "')", $dbh);
  658. if( mysql_error( $dbh ) )
  659. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  660. mysql_query( "INSERT INTO dep_backupdata VALUES ('deployFrom', '{$sourceHost}')", $dbh);
  661. if( mysql_error( $dbh ) )
  662. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  663. mysql_query( "INSERT INTO dep_backupdata VALUES ('deployTo', '{$destHost}')", $dbh);
  664. if( mysql_error( $dbh ) )
  665. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  666. mysql_query( "INSERT INTO dep_backupdata VALUES ('snapshotName', '{$name}')", $dbh);
  667. if( mysql_error( $dbh ) )
  668. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  669. mysql_query( "INSERT INTO dep_backupdata VALUES ('projectID', '{$pid}')", $dbh);
  670. if( mysql_error( $dbh ) )
  671. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  672. mysql_query( "INSERT INTO dep_backupdata VALUES ('projectName', '" . $proj['name'] . "')", $dbh);
  673. if( mysql_error( $dbh ) )
  674. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  675. }
  676. if( !mysql_select_db( $temporaryDatabase , $dbh ) )
  677. self::ajaxError( "Could not select temporary database {$temporaryDatabase} : " . mysql_error( $dbh ) );
  678. $curdbres = mysql_query( 'SELECT DATABASE()' , $dbh );
  679. $curdbrow = mysql_fetch_array( $curdbres , MYSQL_NUM );
  680. $renames = array();
  681. foreach( self::$wpTables as $t ){
  682. array_push( $renames , "{$dbname}.{$destTablePrefix}{$t} TO {$temporaryDatabase}.old_{$t} , {$temporaryDatabase}.{$sourceTablePrefix}$t TO {$dbname}.{$destTablePrefix}{$t}" );
  683. }
  684. $stime = microtime( true );
  685. mysql_query( 'RENAME TABLE ' . implode( ', ', $renames ) , $dbh );
  686. $lockTime = sprintf( '%.4f' , microtime( true ) - $stime );
  687. if( mysql_error( $dbh ) )
  688. self::ajaxError( 'A database error occured: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  689. if( $temporaryDatabaseCreated ){
  690. mysql_query( "DROP DATABASE {$temporaryDatabase}" , $dbh );
  691. }else{
  692. self::emptyDatabase( $temporaryDatabase , $dbh );
  693. }
  694. if( mysql_error( $dbh ) )
  695. self::ajaxError( 'A database error occured trying to drop an old temporary database, but the deployment completed. Error was: ' . substr( mysql_error( $dbh ) , 0 , 200 ) );
  696. if( !$backupDisabled )
  697. self::deleteOldBackupDatabases();
  698. die( json_encode( array( 'ok' => 1 , 'lockTime' => $lockTime ) ) );
  699. }
  700. }
  701. public static function ajax_updateSnapDesc_callback(){
  702. self::checkPerms();
  703. global $wpdb;
  704. $opt = self::getOptions();
  705. extract( $opt , EXTR_OVERWRITE );
  706. $pid = $_POST['projectid'];
  707. $snapname = $_POST['snapname'];
  708. $res = $wpdb->get_results( $wpdb->prepare( 'SELECT dir FROM dep_projects WHERE id = %d' , $pid ) , ARRAY_A );
  709. $dir = $res[0]['dir'];
  710. $fulldir = $datadir . $dir;
  711. $logOut = self::mexec( "$git checkout $snapname >/dev/null 2>&1 ; $git log -n 1 2>&1 ; $git checkout master >/dev/null 2>&1" , $fulldir );
  712. $logOut = preg_replace( '/^commit [0-9a-fA-F]+[\r\n]+/' , '' , $logOut );
  713. if( preg_match( '/fatal: bad default revision/' , $logOut ) )
  714. die( json_encode( array( 'desc' => '' ) ) );
  715. die( json_encode( array( 'desc' => $logOut ) ) );
  716. }
  717. public static function ajax_updateDeploySnapshot_callback(){
  718. self::checkPerms();
  719. global $wpdb;
  720. $opt = self::getOptions();
  721. extract( $opt , EXTR_OVERWRITE );
  722. $pid = $_POST['projectid'];
  723. $blogsTable = $wpdb->base_prefix . 'blogs';
  724. $blogs = $wpdb->get_results( $wpdb->prepare( "SELECT {$blogsTable}.blog_id AS blog_id , {$blogsTable}.domain AS domain , $blogsTable.path AS path FROM dep_members , $blogsTable WHERE dep_members.blog_id = {$blogsTable}.blog_id AND dep_members.project_id = %d AND dep_members.deleted = 0" , $pid ) , ARRAY_A );
  725. $res1 = $wpdb->get_results( $wpdb->prepare( "SELECT dir FROM dep_projects WHERE id = %d" , $pid ) , ARRAY_A );
  726. $dir = $datadir . $res1[0]['dir'];
  727. if( !is_dir( $dir ) )
  728. self::ajaxError( "The directory $dir for this project does not exist." );
  729. $bOut = self::mexec( "$git branch 2>&1" , $dir );
  730. $branches = preg_split( '/[\r\n\s\t\*]+/' , $bOut );
  731. $snapshots = array();
  732. for( $i = 0 , $c = count( $branches ) ; $i < $c ; $i++ ){
  733. if( preg_match( '/\w+/' , $branches[$i] ) ){
  734. $bname = $branches[$i];
  735. if( $bname=='master' )
  736. continue;
  737. $dateOut = self::mexec( "$git checkout $bname 2>&1; $git log -n 1 | grep Date 2>&1" , $dir );
  738. $m = '';
  739. if( preg_match( '/Date:\s+(.+)$/' , $dateOut , $m ) ){
  740. $ctime = strtotime($m[1]);
  741. $date = $m[1];
  742. array_push( $snapshots , array('name' => $branches[$i] , 'created' => $date , 'ctime' => $ctime ) );
  743. }
  744. }else{
  745. unset( $branches[$i] );
  746. }
  747. }
  748. if( count( $snapshots ) ){
  749. function ctimeSort( $b , $a ){
  750. if( $a['ctime']==$b['ctime'] )
  751. return 0;
  752. return ( $a['ctime'] < $b['ctime'] ? -1 : 1 );
  753. }
  754. usort( $snapshots , 'ctimeSort' );
  755. }
  756. die( json_encode( array( 'blogs' => $blogs , 'snapshots' => $snapshots ) ) );
  757. }
  758. public static function ajax_updateCreateSnapshot_callback(){
  759. self::checkPerms();
  760. global $wpdb;
  761. $pid = $_POST['projectid'];
  762. $blogsTable = $wpdb->base_prefix . 'blogs';
  763. $blogs = $wpdb->get_results( $wpdb->prepare( "SELECT {$blogsTable}.blog_id AS blog_id , {$blogsTable}.domain AS domain, {$blogsTable}.path AS path FROM dep_members , {$blogsTable} WHERE dep_members.blog_id = {$blogsTable}.blog_id AND dep_members.project_id = %d AND dep_members.deleted = 0" , $pid ) , ARRAY_A );
  764. die( json_encode( array( 'blogs' => $blogs ) ) );
  765. }
  766. public static function ajax_reloadProjects_callback(){
  767. self::checkPerms();
  768. global $wpdb;
  769. $blogsTable = $wpdb->base_prefix . 'blogs';
  770. $projects = $wpdb->get_results( $wpdb->prepare( 'SELECT id , name FROM dep_projects WHERE deleted = 0' ) , ARRAY_A );
  771. $allBlogs = $wpdb->get_results( $wpdb->prepare( "SELECT blog_id , domain , path FROM $blogsTable ORDER BY domain ASC" ) , ARRAY_A );
  772. for( $i = 0 , $c = count( $projects ) ; $i < $c ; $i++ ){
  773. $mem = $wpdb->get_results($wpdb->prepare( "SELECT $blogsTable.blog_id AS blog_id , $blogsTable.domain AS domain , $blogsTable.path AS path FROM dep_members , $blogsTable WHERE dep_members.deleted=0 AND dep_members.project_id=%d AND dep_members.blog_id = $blogsTable.blog_id" , $projects[$i]['id'] ) , ARRAY_A );
  774. $projects[$i]['memberBlogs'] = $mem;
  775. $memids = array();
  776. $notSQL = '';
  777. if( count( $mem ) ){
  778. for( $j = 0 , $d = count( $mem ) ; $j < $d ; $j++ ){
  779. array_push( $memids , $mem[$j]['blog_id'] );
  780. }
  781. $notSQL = ' WHERE blog_id NOT IN (' . implode( ',' , $memids ) . ')';
  782. }
  783. $nonmem = $wpdb->get_results( $wpdb->prepare( "SELECT blog_id , domain , path FROM {$blogsTable}{$notSQL} ORDER BY domain ASC" ) , ARRAY_A );
  784. $projects[$i]['nonmemberBlogs'] = $nonmem;
  785. $projects[$i]['numNonmembers'] = count( $nonmem );
  786. }
  787. die( json_encode( array( 'projects' => $projects ) ) );
  788. }
  789. public static function ajax_deploy_callback(){
  790. self::checkPerms();
  791. global $wpdb;
  792. $fromid = $_POST['deployFrom'];
  793. $toid = $_POST['deployTo'];
  794. $msgs = array();
  795. $fromBlog = $wpdb->get_results( $wpdb->prepare( 'SELECT blog_id , domain , path FROM wp_blogs WHERE blog_id = %d' , $fromid ) , ARRAY_A );
  796. $toBlog = $wpdb->get_results( $wpdb->prepare( 'SELECT blog_id , domain , path FROM wp_blogs WHERE blog_id = %d' , $toid ) , ARRAY_A );
  797. if( count( $fromBlog )!=1 )
  798. die( 'We could not find the blog you are deploying from.' );
  799. if( count( $toBlog )!=1 )
  800. die( 'We could not find the blog you are deploying to.');
  801. $fromPrefix = 'wp_';
  802. $toPrefix = 'wp_';
  803. if( $fromid!=1 )
  804. $fromPrefix .= $fromid . '_';
  805. if( $toid!=1 )
  806. $toPrefix .= $toid . '_';
  807. $t_fromPosts = $fromPrefix . 'posts';
  808. $t_toPosts = $toPrefix . 'posts';
  809. $fromPostTotal = $wpdb->get_results( $wpdb->prepare( "SELECT COUNT(*) AS cnt FROM $t_fromPosts WHERE post_status = 'publish'" , $fromid ) , ARRAY_A );
  810. $toPostTotal = $wpdb->get_results( $wpdb->prepare( "SELECT COUNT(*) AS cnt FROM $t_toPosts WHERE post_status = 'publish'" , $toid ) , ARRAY_A );
  811. $fromNewestPost = $wpdb->get_results( $wpdb->prepare( "SELECT post_title FROM $t_fromPosts WHERE post_status = 'publish' ORDER BY post_modified DESC LIMIT 1" , $fromid ) , ARRAY_A );
  812. $toNewestPost = $wpdb->get_results( $wpdb->prepare( "SELECT post_title FROM $t_toPosts WHERE post_status = 'publish' ORDER BY post_modified DESC LIMIT 1" , $toid ) , ARRAY_A );
  813. die( json_encode( array(
  814. 'fromid' => $fromid ,
  815. 'toid' => $toid ,
  816. 'fromDomain' => $fromBlog[0]['domain'] ,
  817. 'fromPostTotal' => $fromPostTotal[0]['cnt'] ,
  818. 'fromNewestPostTitle' => $fromNewestPost[0]['post_title'] ,
  819. 'toDomain' => $toBlog[0]['domain'] ,
  820. 'toPostTotal' => $toPostTotal[0]['cnt'] ,
  821. 'toNewestPostTitle' => $toNewestPost[0]['post_title']
  822. ) ) );
  823. }
  824. public static function initHandler(){
  825. if( is_admin() ){
  826. wp_enqueue_script( 'jquery-templates' , plugin_dir_url( __FILE__ ) . 'js/jquery.tmpl.min.js' , array( 'jquery' ) );
  827. wp_enqueue_script( 'deploymint-js' , plugin_dir_url( __FILE__ ) . 'js/deploymint.js' , array( 'jquery' ) );
  828. wp_localize_script( 'deploymint-js' , 'DeployMintVars' , array( 'ajaxURL' => admin_url( 'admin-ajax.php' ) ) );
  829. }
  830. }
  831. public static function adminMenuHandler(){
  832. global $wpdb;
  833. extract( self::getOptions() , EXTR_OVERWRITE );
  834. $capability = ( is_multisite() ? 'manage_network' : 'manage_options' );
  835. add_submenu_page( 'DeployMint' , 'Manage Projects' , 'Manage Projects' , $capability , 'DeployMint' , 'deploymint::deploymintMenu' );
  836. add_menu_page( 'DeployMint' , 'DeployMint' , $capability , 'DeployMint' , 'deploymint::deploymintMenu' , WP_PLUGIN_URL . '/DeployMint/images/deployMintIcon.png' );
  837. $projects = $wpdb->get_results($wpdb->prepare( 'SELECT id , name FROM dep_projects WHERE deleted = 0' ) , ARRAY_A );
  838. for( $i = 0 , $c = count( $projects ) ; $i < $c ; $i++ ){
  839. add_submenu_page( 'DeployMint' , 'Proj: ' . $projects[$i]['name'] , 'Proj: ' . $projects[$i]['name'] , $capability , 'DeployMintProj' . $projects[$i]['id'] , 'deploymint::projectMenu' . $projects[$i]['id'] );
  840. }
  841. if( !$backupDisabled )
  842. add_submenu_page( 'DeployMint' , 'Emergency Revert' , 'Emergency Revert' , $capability , 'DeployMintBackout' , 'deploymint::undoLog' );
  843. add_submenu_page( 'DeployMint' , 'Options' , 'Options' , $capability , 'DeployMintOptions' , 'deploymint::myOptions' );
  844. add_submenu_page( 'DeployMint' , 'Help' , 'Help' , $capability , 'DeployMintHelp' , 'deploymint::help' );
  845. }
  846. public static function deploymintMenu(){
  847. if( !self::allOptionsSet() ){
  848. echo '<div class="wrap"><h2 class="depmintHead">Please visit the options page and configure all options</h2></div>';
  849. return;
  850. }
  851. include( 'views/deploymintHome.php' );
  852. }
  853. public static function help(){
  854. include( 'views/help.php' );
  855. }
  856. public static function myOptions(){
  857. $opt = self::getOptions();
  858. include( 'views/myOptions.php' );
  859. }
  860. private static function deleteOldBackupDatabases(){
  861. self::checkPerms();
  862. $opt = self::getOptions();
  863. extract( $opt , EXTR_OVERWRITE );
  864. if( $numBackups < 1 )
  865. return;
  866. $dbh = mysql_connect( DB_HOST , DB_USER , DB_PASSWORD , true );
  867. mysql_select_db( DB_NAME , $dbh );
  868. $res1 = mysql_query( 'SHOW DATABASES' , $dbh );
  869. if( mysql_error( $dbh ) ){
  870. error_log( 'A database error occured: ' . mysql_error( $dbh ) );
  871. return;
  872. }
  873. $dbs = array();
  874. while( $row1 = mysql_fetch_array( $res1 , MYSQL_NUM ) ){
  875. $dbPrefix = ( $backupDatabase=='' ? 'depbak' : $backupDatabase );
  876. if( preg_match( '/^' . $dbPrefix . '__/' , $row1[0] ) ){
  877. $dbname = $row1[0];
  878. $res2 = mysql_query( "SELECT val FROM $dbname.dep_backupdata WHERE name = 'deployTime'" , $dbh );
  879. if( mysql_error( $dbh ) ){
  880. error_log( "Could not get deployment time for $dbname database" );
  881. return;
  882. }
  883. $row2 = mysql_fetch_array( $res2 , MYSQL_ASSOC );
  884. if( $row2 && $row2['val'] ){
  885. array_push( $dbs , array( 'dbname' => $dbname , 'deployTime' => $row2['val'] ) );
  886. }else{
  887. error_log( "Could not get deployment time for backup database $dbname" );
  888. return;
  889. }
  890. }
  891. }
  892. if( count( $dbs ) > $numBackups ){
  893. function deployTimeSort( $a , $b ){
  894. if

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