<?php
/*
Plugin Name: Developer Console
Description: For advanced users only! Execute MySql queries, command shell queries, and PHP code directly
Version: 1.03
Requires at least: 3.00
Author: Chris
*/

// DON'T UPDATE ANYTHING BELOW THIS LINE


//
pluginAction_addHandlerAndLink("MySQL Console", 'developerConsole_mysql_showForm');
function developerConsole_mysql_showForm() {
  
  // prepare adminUI() placeholders
  $adminUI = [];
  // page title
  $adminUI['PAGE_TITLE'] = [
    t("Plugins")       => '?menu=admin&action=plugins',
    t("MySQL Console") => '?_pluginAction=developerConsole_mysql_showForm',
  ];
  
  // buttons
  $adminUI['BUTTONS'] = [
    [ 'label' => 'MySql Console',             'onclick' => 'window.location="?_pluginAction=developerConsole_mysql_showForm";',   'type' => 'button', 'btn-type' => 'default' ],
    [ 'label' => 'Shell Console',             'onclick' => 'window.location="?_pluginAction=developerConsole_shell_showForm";',   'type' => 'button' ],
    [ 'label' => 'PHP Console',               'onclick' => 'window.location="?_pluginAction=developerConsole_php_showForm";',     'type' => 'button' ],
    [ 'label' => 'Back to Plugins &gt;&gt;',  'onclick' => 'window.location="?menu=admin&action=plugins";',                       'type' => 'button' ],
  ];
  
  // content
  $adminUI['CONTENT'] = ob_capture(function() {
    global $TABLE_PREFIX;
    ?>
    <div style="float:right; margin-bottom: 16px;">
  
      <select id="tables" onchange="developerConsole_mysql_selectTable()">
        <option value="">-- tables --</option>
        <?php foreach(getMysqlTablesWithPrefix() as $tableName): ?>
          <option><?php echo htmlencode($tableName) ?></option>
        <?php endforeach ?>
      </select>
  
      <select id="fields" onchange="developerConsole_mysql_selectField()">
        <option value="">-- fields --</option>
        <option value=''>(select a table first)</option>
      </select>
  
  
      <select id="presets" onchange="developerConsole_selectPreset()">
        <option value="">-- preset queries --</option>
  
        <optgroup label="Database Info">
          <option>SHOW DATABASES</option>
          <option>SHOW TABLES</option>
          <option>SHOW FULL PROCESSLIST</option>
        </optgroup>
  
        <optgroup label="Example Queries">
          <option>SELECT * FROM <?php echo $TABLE_PREFIX ?>exampleTable LIMIT 100</option>
          <option>INSERT INTO <?php echo $TABLE_PREFIX ?>exampleTable SET field = 'value';</option>
          <option>UPDATE <?php echo $TABLE_PREFIX ?>exampleTable SET field='value';</option>
          <option>DELETE FROM <?php echo $TABLE_PREFIX ?>exampleTable WHERE field = 'value';</option>
          <option>ALTER TABLE <?php echo $TABLE_PREFIX ?>exampleTable AUTO_INCREMENT = 100</option>
        </optgroup>
  
        <optgroup label="Advanced Queries">
          <option>SHOW VARIABLES</option>
          <option>SHOW GRANTS</option>
          <option>FLUSH PRIVILEGES</option>
        </optgroup>
  
        <optgroup label="Show CMS data">
        <?php foreach(getMysqlTablesWithPrefix() as $tableName): ?>
          <option>SELECT * FROM <?php echo $tableName ?> LIMIT 100</option>
        <?php endforeach ?>
        </optgroup>
  
      </select>
    </div>
  
    <input type="hidden" name="_pluginAction" value="developerConsole_mysql_doQuery" />
    
    <!-- search and results bar -->
    <textarea name="code" id="developerConsole_query" class="setAttr-wrap-off" style="width:100%; height:100px; border: 1px solid #999; font-family: Courier New;" rows="0" cols="0" spellcheck="false"></textarea>
    <?php echo adminUI_button(['type' => 'button', 'btn-type' => 'default', 'label' => t('Execute Query'), 'onclick' => "developerConsole_executeQuery(); return false;"]); ?>
    
    <label><input type="checkbox" id="flipTable"> Records as Columns</label>
  
    <br/>
    <div style="margin-top: 10px;" id="developerConsole_output"></div>
  
    <?php _developerConsole_showForm_javascript() ?>
    
  <?php });
  // show content
  adminUI($adminUI);
  exit;
}


//
function _developerConsole_showForm_javascript() {
?>
  <script><!--

    // extend jQuery with insertAtCaret()
    jQuery.fn.extend({
      insertAtCaret: function(myValue){
        return this.each(function(i) {
          if (document.selection) {
            this.focus();
            sel = document.selection.createRange();
            sel.text = myValue;
            this.focus();
          }
          else if (this.selectionStart || this.selectionStart == '0') {
            var startPos = this.selectionStart;
            var endPos = this.selectionEnd;
            var scrollTop = this.scrollTop;
            this.value = this.value.substring(0, startPos)+myValue+this.value.substring(endPos,this.value.length);
            this.focus();
            this.selectionStart = startPos + myValue.length;
            this.selectionEnd = startPos + myValue.length;
            this.scrollTop = scrollTop;
          } else {
            this.value += myValue;
            this.focus();
          }
        })
      }
    });

    // warn user before they lose their query by navigating away
    window.onbeforeunload = function() {
      if ($('#developerConsole_query').val().length) {
        return "If you leave this page, your query will be lost!";
      }
    }

    // make ctrl+enter on the textbox submit the form
    $('#developerConsole_query').keydown(function (e) {
      if (e.ctrlKey && e.keyCode == 13) {
        developerConsole_executeQuery();
      }
    });

    //
    function developerConsole_mysql_selectTable() {
      var table = $('#tables').val();
      $('#fields').html('<option value="">(loading...)</option>');
      $.post('admin.php', { _pluginAction: 'developerConsole_mysql_listFieldOptions', table: table }, function(r) {
        $('#fields').html(r);
      });
    }

    //
    function developerConsole_mysql_selectField() {
      if ($('#developerConsole_query').val().length) {
        $('#developerConsole_query').insertAtCaret( $('#fields').val() );
      }
      else {
        $('#developerConsole_query').val("SELECT " + $('#fields').val() + " FROM " + $('#tables').val() + " LIMIT 100")
      }
      $('#fields').val('');
    }

    //
    function developerConsole_selectPreset() {
      var preset = $('#presets').val();
      if ( !preset ) { return; }

      if ( (!$('#developerConsole_query').val()) || confirm('Replace your current query with this?') ) {
        $('#developerConsole_query').val( preset );
      }

      $('#presets').val('');
    }

    //
    function developerConsole_executeQuery() {

      var loadingHTML = '<?php echo adminUI_separator(t('Loading...')) ?>';
      
      $('#developerConsole_output').html(loadingHTML);
      $.post('admin.php', $('#developerConsole_output').closest('form').serialize(), function(r) {
        $('#developerConsole_output').html(r);
        if ($('#flipTable').is(':checked')) {
          flipResultsTable();
        }
      });
    }
    
    function flipResultsTable() {
      $("#developerConsole_output table").each(function() {
        var e=[];
        $(this).find("tr").each(function(){
          var t=[];
          $(this).find("td,th").each(function(){
            t.push($(this))
          });
          e.push(t)
        });
        $(this).empty();
        var t=0;
        while(true){
          foundOne=false;
          var n=$("<tr/>");
          for(var r=0;r<e.length;r++){
            if(e[r].length>t){
              e[r][t].appendTo(n);
              foundOne=true
            }
            else{
              $("<td/>").appendTo(n)
            }
          }
          if(!foundOne){break}
          n.appendTo($(this));
          t++
        }
      })
    }
    
    $(function() {
      $('#flipTable').on('change', flipResultsTable);
    })

  //--></script>

<?php
}


//
pluginAction_addHandler('developerConsole_mysql_doQuery');
function developerConsole_mysql_doQuery() {

  $sql = getEvalOutput(@$_REQUEST['code']);

  // get result
  $result = @mysql_query($sql);

  // show header
  echo adminUI_separator(t('MySQL Output'));

  // show errors
  if (!$result) {
    print "<p><strong>MySQL Error: " . htmlencode(mysql_error()) . "</strong></p>";
    print '<p>Query took ' . showExecuteSeconds(true) . ' seconds.</p>';
    exit;
  }

  // show query
  print "<xmp>$sql</xmp>";

  // if the query is supposed to affect rows (and not return rows), display how many rows were affected by the query
  if ($result === true) { // queries that don't return a resultset return true -
    if (preg_match('@^(DELETE|INSERT|LOAD|REPLACE|UPDATE)[[:space:]]+@i', $sql)) {
      $count     = mysql_affected_rows();
      $rowOrRows = ($count == 1) ? 'row' : 'rows';
      print "<p><strong>$count $rowOrRows affected.</strong></p>";
    }
    else {
      print '<p><strong>Operation complete.</strong></p>';
    }
    print '<p>Query took ' . showExecuteSeconds(true) . ' seconds.</p>';
    exit;
  }

  // otherwise, we're displaying rows returned by the query

  // if there are zero, report that
  if (!mysql_num_rows($result)) {
    print '<p><strong>No rows returned.</strong></p>';
    print '<p>Query took ' . showExecuteSeconds(true) . ' seconds.</p>';
    exit;
  }

  // display a table of rows
  print '<div style="overflow: auto;">';
  print '<table cellspacing="1" cellpadding="0" border="0" class="data table table-striped table-hover">';
  while ($row = mysql_fetch_assoc($result)) {

    // output column headers
    if (!@$shownHeaders++) {
      print "<thead><tr class='listHeader'>";
      foreach (array_keys($row) as $fieldName) {
        print '<th><b>' . htmlencode($fieldName) . '</td>';
      }
      print "</tr></thead>";
    }

    // output data rows
    $bgColorClass = (@$bgColorClass == "listRowOdd") ? 'listRowEven' : 'listRowOdd'; # rotate bgclass
    print '<tr class="' . $bgColorClass . '">';
    foreach ($row as $fieldValue) {
      print '<td>';
      if (preg_match("/^CREATE TABLE `/i", $fieldValue)) { print "<xmp>$fieldValue</xmp>"; } // format as plaintext for "SHOW CREATE TABLE ..."
      else                                               { print htmlencode($fieldValue); }
      print '</td>';
    }
    print '</tr>';
  }
  print '</table>';
  print '</div>';
  if (is_resource($result)) { mysql_free_result($result); }

  // finally, report how long the query took
  print '<p>Query took ' . showExecuteSeconds(true) . ' seconds.</p>';

  exit;
}

//
pluginAction_addHandler('developerConsole_mysql_listFieldOptions');
function developerConsole_mysql_listFieldOptions() {

  $table = @$_REQUEST['table'];

  echo "<option value=''>-- fields --</option>\n";
  foreach (getMySqlColsAndType(mysql_escape(getTableNameWithPrefix($table))) as $field => $type) {
    $field = htmlencode($field);
    echo "<option value='$field'>$field</option>\n";
  }
  if (!$table) {
    echo "<option value=''>(select a table first)</option>\n";
  }

  exit;
}


//
pluginAction_addHandlerAndLink("Shell Console", 'developerConsole_shell_showForm');
function developerConsole_shell_showForm() {
  
  // prepare adminUI() placeholders
  $adminUI = [];
  // page title
  $adminUI['PAGE_TITLE'] = [
    t("Plugins")       => '?menu=admin&action=plugins',
    t("Shell Console") => '?_pluginAction=developerConsole_shell_showForm',
  ];
  
  // buttons
  $adminUI['BUTTONS'] = [
    [ 'label' => 'MySql Console',             'onclick' => 'window.location="?_pluginAction=developerConsole_mysql_showForm";',   'type' => 'button' ],
    [ 'label' => 'Shell Console',             'onclick' => 'window.location="?_pluginAction=developerConsole_shell_showForm";',   'type' => 'button', 'btn-type' => 'default' ],
    [ 'label' => 'PHP Console',               'onclick' => 'window.location="?_pluginAction=developerConsole_php_showForm";',     'type' => 'button' ],
    [ 'label' => 'Back to Plugins &gt;&gt;',  'onclick' => 'window.location="?menu=admin&action=plugins";',                       'type' => 'button' ],
  ];
  
  // content
  $adminUI['CONTENT'] = ob_capture(function() {
    global $TABLE_PREFIX;
    ?>

      <div style="float:right; margin-bottom: 16px;">
        <select style="float: right;" id="presets" onchange="shellConsole_selectPreset()">
          <option value="">-- preset commands --</option>

          <optgroup label="Linux / Unix">
            <option value="pwd">pwd (show current directory)</option>
            <option value="ls -la">ls -la (list files in current dir)</option>
            <option value="set">set (show environment variables)</option>
            <option value="env">env (show environment variables)</option>
            <option value="cat filename.txt">cat filename.txt (display text file)</option>
            <option value="uname -a">uname -a (display system information)</option>
            <option value="ping -w5 google.com">ping -w5 google.com (for 5 seconds)</option>
            <option>traceroute google.com</option>

            <option value="cd ..; pwd">cd ..; pwd (use ; for multiple commands)</option>
          </optgroup>

          <optgroup label="Windows">
            <option value="cd">cd (show current directory)</option>
            <option value="dir">dir (list files in current dir)</option>
            <option value="set">set (show environment variables)</option>
            <option value="type filename.txt">type filename.txt (display text file)</option>
            <option value="ver">ver (display windows version)</option>
            <option>ping google.com</option>
            <option>tracert google.com</option>
            <option>systeminfo</option>
            <option>tasklist</option>
            <option value="cd .. &amp; cd">cd .. &amp; cd (use &amp; for multiple commands)</option>
          </optgroup>

          </optgroup>

        </select>
      </div>


      <!-- search and results bar -->
      <input type="text" id="shellConsole_query" class="setAttr-wrap-off" style="width:100%; margin-bottom: 10px" />
      <?php echo adminUI_button(['type' => 'button', 'btn-type' => 'default', 'label' => t('Execute Command'), 'onclick' => "shellConsole_executeCommand(); return false;"]); ?>
      
      <input type="checkbox" name="redirectStderr" id="redirectStderr" value="0" checked="checked" />
      Automatically add <a href="#" onclick=" $('#shellConsole_query').val( $('#shellConsole_query').val() + ' 2>&1' ); ">2>&1</a>
      to all commands to show errors (redirects stderr to stdout).

      <br/>
      <div style="margin-top: 10px" id="shellConsole_output"></div>

      <?php _developerConsole_shell_showForm_javascript() ?>

  <?php });
  // show content
  adminUI($adminUI);
  exit;
}


//
function _developerConsole_shell_showForm_javascript() {
?>
  <script type="text/javascript"><!--

    //
    function shellConsole_selectPreset() {
      var preset = $('#presets').val();
      if ( !preset ) { return; }

      //if ( (!$('#shellConsole_query').val()) || confirm('Replace your current query with this?') ) {
        $('#shellConsole_query').val( preset );
      //}

      $('#presets').val('');
    }

    //
    function shellConsole_executeCommand() {

      var loadingHTML = '<?php echo adminUI_separator(t('Loading...')) ?>';

      $('#shellConsole_output').html(loadingHTML);
      $.post('admin.php', {
        _pluginAction:  'developerConsole_shell_doQuery',
        code:           $('#shellConsole_query').val(),
        redirectStderr: $('#redirectStderr:checked').length
        }, function(r) {
        $('#shellConsole_output').html(r);
      });
    }

  //--></script>

<?php
}


//
pluginAction_addHandler('developerConsole_shell_doQuery');
function developerConsole_shell_doQuery() {

  $command = getEvalOutput(@$_REQUEST['code']);

  if ($_SERVER['REQUEST_METHOD'] != 'POST') { die("Only POST requests are accepted!"); }

  // show errors
  if (@$_REQUEST['redirectStderr']) { $command .= ' 2>&1'; }

  // get result
  $outputArray = '';
  $returnCode  = -1; //http://en.wikipedia.org/wiki/Signal_(computing)#List_of_signals
  #$output = shell_exec($command);
  exec($command, $outputArray, $returnCode);
  $output = join("\n", $outputArray);

  //
  print "<br/>\n";
  print "<strong>Command:   </strong>" . htmlencode($command)    . "<br/>\n";
  print "<strong>Exit Code: </strong>" . htmlencode($returnCode) . "<br/><br/>\n\n";

  // show header
  echo adminUI_separator(t('Shell Output'));

  // display a table of rows
  print "<div style='overflow: auto;'>\n";
  print "<xmp>$output</xmp>";
  print "</div>";

  // finally, report how long the query took
  print '<p>Command took ' . showExecuteSeconds(true) . ' seconds.</p>';

  exit;
}


//
pluginAction_addHandlerAndLink("PHP Console", 'developerConsole_php_showForm');
function developerConsole_php_showForm() {
  
  // prepare adminUI() placeholders
  $adminUI = [];
  // page title
  $adminUI['PAGE_TITLE'] = [
    t("Plugins")       => '?menu=admin&action=plugins',
    t("PHP Console") => '?_pluginAction=developerConsole_php_showForm',
  ];
  
  // buttons
  $adminUI['BUTTONS'] = [
    [ 'label' => 'MySql Console',             'onclick' => 'window.location="?_pluginAction=developerConsole_mysql_showForm";',   'type' => 'button' ],
    [ 'label' => 'Shell Console',             'onclick' => 'window.location="?_pluginAction=developerConsole_shell_showForm";',   'type' => 'button' ],
    [ 'label' => 'PHP Console',               'onclick' => 'window.location="?_pluginAction=developerConsole_php_showForm";',     'type' => 'button', 'btn-type' => 'default' ],
    [ 'label' => 'Back to Plugins &gt;&gt;',  'onclick' => 'window.location="?menu=admin&action=plugins";',                       'type' => 'button' ],
  ];
  
  // content
  $adminUI['CONTENT'] = ob_capture(function() {
    ?>
    <input type="hidden" name="_pluginAction" value="developerConsole_php_doQuery" />

    <!-- search and results bar -->
    <textarea name="code" id="developerConsole_query" class="setAttr-wrap-off" style="width:100%; height:100px; border: 1px solid #999; font-family: Courier New;" rows="0" cols="0" spellcheck="false"></textarea>
    <?php echo adminUI_button(['type' => 'button', 'btn-type' => 'default', 'label' => t('Execute PHP Code'), 'onclick' => "developerConsole_executeQuery(); return false;"]); ?>
    <br/>
    <div style="margin-top: 10px;" id="developerConsole_output"></div>

    <?php _developerConsole_showForm_javascript() ?>

  <?php });
  // show content
  adminUI($adminUI);
  exit;
}


//
pluginAction_addHandler('developerConsole_php_doQuery');
function developerConsole_php_doQuery() {
  global $php_error;

  $code = @$_REQUEST['code'];

  // get result
  unset($php_error);
  ob_start();
  eval($code . ";");
  $result = ob_get_clean();

  // show header
  echo adminUI_separator(t('PHP Output'));

  // show errors
  if (@$php_error) {
    print "<p><strong>PHP Error: " . htmlencode($php_error) . "</strong></p>";
    print '<p>Query took ' . showExecuteSeconds(true) . ' seconds.</p>';
    exit;
  }

  // show query
  //print "<xmp>$code</xmp>";

  // display query output
  print $result;

  // finally, report how long the query took
  print '<p>Query took ' . showExecuteSeconds(true) . ' seconds.</p>';

  exit;
}



// EXPERIMENTAL FUNCTIONS



// DEVELOPER FUNCTIONS


//// reset schemas from pluginDir/schemas/
//function XXX_resetPluginSchemas($tableNames) {
//  foreach ($tableNames as $basename) {
//    mysql_dropTable($basename);
//  }
//  initPluginSchemas($tableNames);
//}
//
//// save schemas to pluginDir/schemas/
//function XXX_savePluginSchemasToPluginDir($tableNames)
//  // create schemas
//  foreach ($tableNames as $basename) {
//
//    // copy default data
//    list($dataSourcePath) = getPluginPathAndUrl("schemas/$basename.defaultSqlData.php", 1);
//    $dataSourcePath = SCRIPT_DIR . "/plugins/" . $dataSourcePath;
//    $dataTargetPath = DATA_DIR .'/schema'. "/$basename.defaultSqlData.php";
//    $backupFilename = backupDatabase('tmp.sql.php', $basename);
//    @copy(DATA_DIR . '/backups/' . $backupFilename, $dataTargetPath);
//
//    // copy schema
//    list($schemaSourcePath) = getPluginPathAndUrl("schemas/$basename.ini.php", 1);
//    $schemaSourcePath = SCRIPT_DIR . "/plugins/" . $schemaSourcePath;
//    @copy(DATA_DIR . "/schema/$basename.ini.php", $schemaSourcePath);
//    createSchemaFromFile( $schemaSourcePath );
//  }
//}


?>
