#!/usr/bin/env php
<?php
// ********************************************************************************
// PHP shell script to ease the setup and execution of make.wordpress.org patches
// ********************************************************************************

$SYSTEM_INSTALLED_NAME = 'wpdo';
$SYSTEM_INSTALL_PATH = '/usr/local/bin/' . $SYSTEM_INSTALLED_NAME;
$INSTALL_FOLDER_NAME = 'wordpress-trunk';
$PATCH_FILE_NAME = '_wpdo_downloaded.patch';


// ********************************************************************************
// Runner

echo "Ⓦ  WPDO v0.3 for OSX 10.11/10.12\n\n";

$COMMANDS_INFO = array(
  's' => 'start',
  'p' => 'patch',
  'r' => 'revert',
  'u' => 'update',
  'x' => 'kill',
);

if ( $argv[1] ) {
  run( $argv[1] );
} else {
  echo "Hi! Do you have a command for me?\n";
  echo "\n";
  foreach ( $COMMANDS_INFO as $key => $value ) {
    echo "  ($key) $value\n";
  }
  echo "   ";
  $opt = listen_char();
  echo "\n\n";

  run( $commands[ $opt ] );
}

function run( $command ) {
  global $argv;

  if ( function_exists( 'command_' . $command ) ) {
    call_user_func_array( 'command_' . $command, $argv );
    echo "\n";
  }
}


// ********************************************************************************
// Commands

function command_start() {
  global $INSTALL_FOLDER_NAME;
  $install_done = true;

  echo "Checking Apache... \n";
  $install_done = $install_done & install_osx_apache();

  echo "Checking MySQL... \n";
  $install_done = $install_done & install_osx_mysql();

  echo "Checking SVN... \n";
  $install_done = $install_done & install_osx_svn();

  echo "Checking WordPress... \n";
  $install_done = $install_done & install_osx_wordpress( $INSTALL_FOLDER_NAME );

  // Launch!
  if ( $install_done == true ) {
    run_servers();
  }
}

function command_kill() {
  global $INSTALL_FOLDER_NAME;

  if ( file_exists( 'wp-blog-header.php' ) ) {
    echo "To uninstall, go in the '" . $INSTALL_FOLDER_NAME . "' parent directory first.";
  } else {
    echo "Uninstalling '$INSTALL_FOLDER_NAME'...\n";

    if ( file_exists( $INSTALL_FOLDER_NAME . '/wp-blog-header.php' ) ) {
      exec_simple(
        'rm -rf ' . $INSTALL_FOLDER_NAME,
        'Uninstall complete',
        'Uninstall failed'
      );
    } else {
      echo "      * Nothing to uninstall. All already clear.";
    }
  }
}

function command_revert() {
  global $PATCH_FILE_NAME;

  echo "Reverting to original...\n";

  // Remove patch if present
  if ( file_exists( $PATCH_FILE_NAME ) ) {
    @exec_simple(
      'rm ' . $PATCH_FILE_NAME,
      'Patch cleared',
      'Failing removing patch'
    );
  }

  // Revert SVN
  if ( exec_simple( 'svn revert -R .', 'WordPress TRUNK reverted to original', 'WordPress TRUNK revert failed, please reinstall (kill+start)' ) ) {
    @exec( 'svn info | grep Revision\:', $exec_output_array, $exec_return );
    echo "        " . $exec_output_array[0];
    echo "\n";

    // Ask for unversioned files
    $unversioned_files = get_modified_files( '?' );
    if ( ! ($unversioned_files === "") ) {
      echo "\nDetected these unversioned files:\n";
      echo $unversioned_files;
      echo "  (y) to cleanup unversioned files, press any other key to keep them\n   ";
      $opt = listen_char();
      echo "\n";
      if ( $opt == 'y' ) {
        if ( exec_simple( 'svn cleanup --remove-unversioned', 'Removed unversioned files (marked with "?")', 'Failed to remove unversioned files' ) ) {
          return true;
        }
      }
    } else {
      return true;
    }
  }
  return false;
}

function command_update() {
  echo "Updating trunk...\n";

  $ret = exec_simple(
    'svn up',
    'WordPress TRUNK updated to latest version',
    'WordPress TRUNK update failed'
  );

  if ( $ret ) {
    @exec( 'svn info | grep Revision\:', $exec_output_array, $exec_return );
    echo "        " . $exec_output_array[0];
    return true;
  } else {
    return false;
  }
}

function command_patch() {
  global $argv, $PATCH_FILE_NAME;

  if ( $argv[2] ) {
    // Path should be:
    // https://core.trac.wordpress.org/raw-attachment/ticket/00000/00000.diff
    $url = $argv[2];
    $ticket_number = '';
    $patch_file = '';
    $matches = [];

    // If parameter is just the ticket number
    // i.e. 00000 or 00000.2
    if ( preg_match( '#^([0-9]+)(\.([0-9]+))?$#', $url, $matches ) ) {
      $ticket_number = $matches[1];
      $patch_file = $ticket_number;
      if ( sizeof( $matches ) >= 4 ) $patch_file += '.' . $matches[3];
      $url = "https://core.trac.wordpress.org/raw-attachment/ticket/$ticket_number/$patch_file.diff";
    }

    // If parameter is the full URL of the ticket
    // i.e. https://core.trac.wordpress.org/ticket/00000
    if ( preg_match( '#^https://core\.trac\.wordpress\.org/ticket/([0-9]+)#', $url, $matches ) ) {
      $ticket_number = $matches[1];
      $patch_file = $ticket_number; // Defaults to the first one, as we can't know from the base URL
      $url = "https://core.trac.wordpress.org/raw-attachment/ticket/$ticket_number/$patch_file.diff";
    }

    // If parameter is Trac attachment page
    // i.e. https://core.trac.wordpress.org/attachment/ticket/00000/00000.2.diff
    if ( preg_match( '#^https://core\.trac\.wordpress\.org/attachment/ticket/([0-9]+)/(.+)\.diff$#', $url, $matches ) ) {
      $ticket_number = $matches[1];
      $patch_file = $matches[2];
      $url = "https://core.trac.wordpress.org/raw-attachment/ticket/$ticket_number/$patch_file.diff";
    }

    echo "Retrieving patch '$ticket_number/$patch_file.diff'...\n";
    echo "$url\n";

    if ( exec_simple( 'curl -o ' . $PATCH_FILE_NAME . ' ' . $url . ' 2>&1', 'Download complete', 'Download failed' ) ) {
      // Detect if patch is from develop repository (with 'src/') or normal core repository
      $path_level = '0';
      if( strpos( file_get_contents( $PATCH_FILE_NAME ), '+++ src/') !== false ) {
        $path_level = '1';
      }

      if ( exec_simple( 'patch -p' . $path_level . ' < ' . $PATCH_FILE_NAME, 'Patch succeeded', 'Patch failed' ) ) {
        // Let's show which files were changed
        echo get_modified_files();
      } else {
        echo "\n";
        echo "To see the error, try to run: patch -p$path_level <$PATCH_FILE_NAME";
      }
    }
  } else {
    echo "No patch to install. Please execute on command line: wpdo patch [URL]";
  }
}

function command_prepare() {
  echo "#TODO: prepare patch for upload.";
  // svn diff > 00000.patch
}


// ********************************************************************************
// Script Installation

function command_install() {
  global $SYSTEM_INSTALLED_NAME;
  global $SYSTEM_INSTALL_PATH;
  global $COMMANDS_INFO;

  if ( __FILE__ != $SYSTEM_INSTALL_PATH ) {
    if ( file_exists( $SYSTEM_INSTALL_PATH ) ) {
      echo "File already exists at '" . $SYSTEM_INSTALL_PATH ."'\n";
      echo "The file shouldn't be present for this script to continue.\n";
    } else {
      if ( copy( __FILE__, $SYSTEM_INSTALL_PATH ) ) {
        if ( chmod( $SYSTEM_INSTALL_PATH, 0755 ) ) {
          echo "Script installed.\n";
          echo "\n";
          echo "Now you can run wpdo from command line, for example:\n";
          foreach ( $COMMANDS_INFO as $key => $value ) {
            echo "  wpdo $value\n";
          }

          // Extra cleanup
          if ( ( basename( __FILE__ ) != $SYSTEM_INSTALLED_NAME ) && !unlink( __FILE__ ) ) {
            echo "Unable to delete after installation '" . __FILE__ . "'\n";
          }
        } else {
          echo "Script was copied to '" . $SYSTEM_INSTALL_PATH . "'\n";
          echo "However it wasn't possible to set it executable.\n";
        }
      } else {
        echo "Unable to install script to '" . $SYSTEM_INSTALL_PATH ."'\n";
        echo "Check if path exists and try again.\n";
      }
    }
  } else {
    echo "Script already running from install location.\n";
  }
}

function command_uninstall() {
  global $SYSTEM_INSTALL_PATH;

  if ( unlink( $SYSTEM_INSTALL_PATH ) ) {
    echo "Script fully uninstalled.\n";
  } else {
    echo "Unable to uninstall '" . $SYSTEM_INSTALL_PATH . "'\n";
  }
}


// ********************************************************************************
// Support functions

function install_osx_apache() {
  return exec_simple(
    'httpd -v 2>&1 2>&1',
    'Apache installed',
    'Needs to install Apache'
  );
}

function install_osx_mysql() {
  return exec_simple(
    'mysql --version 2>&1',
    'MySQL installed',
    'You need to install MySQL: http://dev.mysql.com/downloads/'
  );
}

function install_osx_svn() {
  return exec_simple(
    'svn --version 2>&1',
    'SVN installed',
    'You need to install SVN. Install XCode from the Mac App Store (big) then run `xcode-select --install`.'
  );
}

function install_osx_wordpress( $install_path ) {
  // Requires SVN
  @exec( 'svn --version 2>&1', $exec_output_array, $exec_return );
  if ( $exec_return == 0 ) {
    // Safety check from inside existing WP
    if ( file_exists( './wp-blog-header.php' ) ) {
      echo "      - You're already inside a WordPress installation. Skipping.\n";
      return true;
    }

    // Create folder if it doesn't exist
    if ( !file_exists( $install_path ) ) {
      echo "      + Folder '" . $install_path . "' created.\n";
      mkdir( $install_path, 0777, true );
    }

    // Install WP from SVN
    if ( !file_exists( $install_path . '/wp-blog-header.php' ) ) {
      echo "      > Downloading WordPress TRUNK.\n";
      return exec_simple( 'svn co https://core.svn.wordpress.org/trunk/ ' . $install_path, 'Download complete', 'Download failed' );
    } else {
      echo "      + WordPress already installed at '" . $install_path . "'.\n";
      return true;
    }
  }
}

function run_servers() {
  echo "Starting servers... \n";
  $ret = exec_simple(
    'sudo apachectl start 2>&1',
    'Apache running',
    'Apache failed'
  );

  if ( $ret ) {
    $url = 'http://localhost/';
    exec_simple(
      'open ' . $url,
      'Opened ' . $url . ' in your browser',
      'Failed to open ' . $url . ' in your browser'
    );
  }
}

function get_modified_files( $filter_flags = 'ADMRC?!~' ) {
  $out = "";
  @exec( 'svn status', $exec_output_array, $exec_return );
  foreach ( $exec_output_array as $line ) {
    if ( preg_match( '#[' . $filter_flags . ']+\s{2,7}#', $line ) && ( strpos( $line, '_wpdo_downloaded.patch' ) === false ) ) {
      $out .= "$line\n";
    }
  }

  return $out;
}


// ********************************************************************************
// Tools

function listen_char() {
  readline_callback_handler_install('', function() { });
  while (true) {
    $r = array(STDIN);
    $w = NULL;
    $e = NULL;
    $n = stream_select($r, $w, $e, null);
    if ($n && in_array(STDIN, $r)) {
      $c = stream_get_contents(STDIN, 1);
      echo $c;
      return $c;
    }
  }
}

function exec_simple( $bash_command, $msg_ok = 'Ok', $msg_no = 'Nope' ) {
  @exec( $bash_command, $exec_output_array, $exec_return );
  if ( $exec_return == 0 ) {
    echo "      + $msg_ok\n";
    return true;
  } else {
    echo "      - $msg_no\n";
    return false;
  }
}