<?php
/**
  * Ahead WebSoft Technologies Pvt Ltd.
  * @copyright  Copyright 2006-2026 Ahead WebSoft Technologies Pvt Ltd.
  * @license    https://www.socialengine.com/policies
*/
class ToolsController extends Zend_Controller_Action
{
  /**
   * @var Engine_Package_Manager
   */
  protected $_packageManager;

  /**
   * @var Zend_Session_Namespace
   */
  protected $_session;

  /**
   * @var Zend_Cache_Core
   */
  protected $_cache;

  protected $_compareCountIndex;

  public function init()
  {
    // Check if already logged in
    if( !Zend_Registry::get('Zend_Auth')->getIdentity() ) {
      return $this->_helper->redirector->gotoRoute(array(), 'default', true);
    }

    // Get manager
    $this->_packageManager = Zend_Registry::get('Engine_Package_Manager');

    // Get session
    $this->_session = new Zend_Session_Namespace('InstallToolsController');

    // Get cache
    if( !Zend_Registry::isRegistered('Cache') ) {
      throw new Engine_Exception('Cache could not be initialized. Please try setting full permissions on temporary/cache');
    }
    $this->_cache = Zend_Registry::get('Cache');
  }

  public function __call($method, $args = array())
  {
    // Proxy externals for
    if( 'externalsAction' == $method ) {
      $path = APPLICATION_PATH . '/application/libraries/Adminer';
      list($base, $static) = explode('externals', $_SERVER['REQUEST_URI']);
      $this->_outputFile($path . '/externals' . $static);
      exit();
    }
    
    parent::__call($methodName, $args);
  }

  public function indexAction()
  {
    $this->view->hasAdminer = file_exists(APPLICATION_PATH . '/application/libraries/Adminer');
  }




  public function adminerAction()
  {
    // Get config
    $path = APPLICATION_PATH . '/application/libraries/Adminer';
    $adminerPath = $path . '/adminer';
    $configFile = APPLICATION_PATH . '/install/config/adminer.php';

    $config = array();
    if( file_exists($configFile) ) {
      $config = include $configFile;
    }

    // Adminer missing
    if( !file_exists($adminerPath . '/index.php') ) {
      throw new Engine_Exception('Adminer is missing');
    }

    // Proxy static resources
    else if( '' != $this->_getParam('static') ) {
      list($base, $static) = explode('static', $_SERVER['REQUEST_URI']);
      $this->_outputFile($adminerPath . '/static' . $static);
      exit();
    }

    // Adminer main
    else {
      // Check request uri
      list($request_filename) = explode('?', $_SERVER['REQUEST_URI']);
      if( substr($request_filename, -1) != '/' && false === strpos($request_filename, 'adminer/adminer.php') ) {
        header('Location: ' . $_SERVER['REQUEST_URI'] . '/');
        exit();
      }

      // Kill output buffering?
      while( ob_get_level() > 0 ) {
        ob_end_clean();
      }

      // Change directory
      chdir($adminerPath);

      // Add autologin
      if( !empty($config['autologin']) &&
          $_SERVER['REQUEST_METHOD'] == 'GET' &&
          empty($_SESSION["usernames"]) ) {
        $db = Zend_Registry::get('Zend_Db');
        $dbConfig = $db->getConfig();
        $_POST['server'] = $dbConfig['host'];
        $_POST['username'] = $dbConfig['username'];
        $_POST['password'] = $dbConfig['password'];
      }
      
      // Globals in: adminer.inc.php
      global $VERSION, $connection;
      // Globals in: auth.inc.php
      global $connection, $adminer;
      // Globals in: connect.inc.php
      global $connection, $VERSION, $token, $error;
      // Globals in: design.inc.php
      global $LANG, $VERSION, $adminer, $connection;
      // Globals in: editing.inc.php
      global $structured_types, $unsigned, $inout, $enum_length, $connection, $types;
      // Globals in: export.inc.php
      global $connection;
      // Globals in: functions.inc.php
      global $connection, $error, $adminer, $types;
      // Globals in: lang.inc.php
      global $LANG, $translations, $langs;
      // Globals in: mysql.inc.php
      global $adminer, $connection, $on_actions;

      define('_ENGINE_ADMINER', true);
      
      include $adminerPath . '/index.php';
      
      exit();
    }
  }
  
  public function compareAction()
  {
    // Get packages
    $packages = $this->_packageManager->listInstalledPackages();

    // Get cached diffs
    if( isset($this->_session->diffs) ) {
      $diffs = $this->_session->diffs;
    } else {
      $this->_session->diffs = $diffs = new Engine_Cache_ArrayContainer(array(), $this->_cache);
    }

    // Flush diffs
    if( $this->_getParam('flush') ) {
      $diffs->clean();
      unset($diffs);
      unset($this->_session->diffs);
      return $this->_helper->redirector->gotoRoute(array('flush' => null));
    }

    // Check for skip identical
    $showAll = (bool) $this->_getParam('all', false);

    // Build diffs
    $indexedCount = array();
    if( $diffs->count() <= 0 ) {
      foreach( $packages as $key => $package ) {
        $operation = new Engine_Package_Manager_Operation_Install($this->_packageManager, $package);
        $fileOperations = $operation->getFileOperations(!$showAll);
        $fileOperations = $fileOperations['operations'];

        $currentCount = 0;
        $indexedOperations = array();
        if( !empty($fileOperations) ) {

          // Re-index file operations
          do {
            // Get key/val and remove
            $path = key($fileOperations);
            $info = $fileOperations[$path];
            $code = $info['key'];
            unset($fileOperations[$path]);

            if( !$showAll ) {
              if( $code == 'identical' ) {
                continue;
              }
            }

            // Save to index
            $indexedOperations[$code][$path] = $info;

            // Count
            $currentCount++;

            // Clear
            unset($path);
            unset($info);
            unset($code);
          } while( count($fileOperations) > 0 );
        }
        
        unset($operation);
        unset($fileOperations);

        // Save cache
        //if( !empty($indexedOperations) ) {
          $diffs->offsetSet($package->getKey(), $indexedOperations);
          $indexedCount[$package->getKey()] = $currentCount;
        //}
        unset($indexedOperations);
      }
      $this->_compareCountIndex = $indexedCount;

      // Sort
      $diffs->uksort(array($this, 'compareSort'));
    }
    
    $this->view->diffs = $diffs;
    
    // Get extracted packages
    $oldPackages = array();
    $it = new DirectoryIterator($this->_packageManager->getTemporaryPath(Engine_Package_Manager::PATH_PACKAGES));
    foreach( $it as $child ) {
      if( $it->isDot() || $it->isFile() || !$it->isDir() ) {
        continue;
      }
      $oldPackages[] = basename($child->getPathname());
    }
    
    $this->view->oldPackages = $oldPackages;
  }

  public function conflictAction()
  {
    // Get packages
    $packages = $this->_packageManager->listInstalledPackages();

    $index = array();
    $conflicts = array();

    foreach( $packages as $package ) {
      foreach( $package->getFileStructure() as $file ) {
        if( !isset($index[$file]) ) {
          $index[$file] = $package->getKey();
        } else {
          $conflicts[$file][] = $index[$file];
          $conflicts[$file][] = $package->getKey();
        }
      }
    }
    $this->view->conflicts = $conflicts;
  }

  public function diffAction()
  {
    if( $this->_getParam('hideIdentifiers') ) {
      $this->view->layout()->hideIdentifiers = true;
    }
    
    $left = $this->_getParam('left');
    $right = $this->_getParam('right');
    $file = $this->_getParam('file');
    $packageKey = $this->_getParam('package');
    $type = $this->_getParam('type', 'inline');
    $show = $this->_getParam('show', 0);

    // Left/right mode
    if( $left && $right ) {
      // Calculate base file?
      $file = '';
      for( $il = strlen($left) - 1, $ir = strlen($right) - 1; $il >= 0 && $ir >= 0; $il--, $ir-- ) {
        if( $left[$il] === $right[$ir] ) {
          $file = $left[$il] . $file;
        } else {
          break;
        }
      }
      $file = trim($file, '/\\');
      // Add base path
      if( $left[0] != '/' && $left[0] != '\\' ) {
        $left = APPLICATION_PATH . DIRECTORY_SEPARATOR . $left;
      }
      if( $right[0] != '/' && $right[0] != '\\' ) {
        $right = APPLICATION_PATH . DIRECTORY_SEPARATOR . $right;
      }
      // Make sure it's within the installation
      if( 0 !== strpos($left, APPLICATION_PATH) || 0 !== strpos($right, APPLICATION_PATH) ) {
        throw new Engine_Exception('Not within the installation');
      }
    }

    // File/Package mode
    else if( $file && $packageKey ) {
      $package = $this->_packageManager->listExtractedPackages()->offsetGet($packageKey);
      if( !$package ) {
        throw new Engine_Exception('Package does not exist.');
      }
      $left = $package->getBasePath() . DIRECTORY_SEPARATOR . ltrim($file, '/\\');
      $right = APPLICATION_PATH . DIRECTORY_SEPARATOR . ltrim($file, '/\\');
    }

    // Whoops
    else {
      return;
    }

    // Must have at least left or right
    if( !$left && !$right ) {
      return;
    } else if( !file_exists($left) && !file_exists($right) ) {
      return;
    }

    // Assign
    $this->view->file = $file;
    $this->view->left = $left;
    $this->view->right = $right;

    // Options
    $this->view->type = $type;
    $this->view->showEverything = $show;
    $arr = array();
    parse_str($_SERVER['QUERY_STRING'], $arr);
    $this->view->parts = $arr;

    // Diff
    include_once 'Text/Diff.php';
    include_once 'Text/Diff/Renderer.php';
    include_once 'Text/Diff/Renderer/context.php';
    include_once 'Text/Diff/Renderer/inline.php';
    include_once 'Text/Diff/Renderer/unified.php';
    
    $this->view->textDiff = $textDiff = new Text_Diff(
      'native',//'auto',
      array(
        file_exists($left)  ? file($left,  FILE_IGNORE_NEW_LINES) : array(),
        file_exists($right) ? file($right, FILE_IGNORE_NEW_LINES) : array(),
      )
    );
  }

  public function phpAction()
  {
    ob_start();
    phpinfo();
    $source = ob_get_clean();

    preg_match('~<style.+?>(.+?)</style>.+?(<table.+\/table>)~ims', $source, $matches);
    $css = $matches[1];
    $source = $matches[2];

    $css = preg_replace('/[\r\n](.+?{)/iu', "\n#phpinfo \$1", $css);

    //$regex = '/'.preg_quote('<a href="http://www.php.net/">', '/').'.+?'.preg_quote('</a>', '/').'/ims';
    //$source = preg_replace($regex, '', $source);

    // strip images from phpinfo()
    $regex = '/<img .+?>/ims';
    $source = preg_replace($regex, '', $source);

    $regex = '/'.preg_quote('<h2>PHP License</h2>', '/').'.+$/ims';
    $source = preg_replace($regex, '', $source);

    $source = str_replace("module_Zend Optimizer", "module_Zend_Optimizer", $source);

    $this->view->style = $css;
    $this->view->content = $source;
  }

  public function sanityAction()
  {
    // Get db
    if( Zend_Registry::isRegistered('Zend_Db') && ($db = Zend_Registry::get('Zend_Db')) instanceof Zend_Db_Adapter_Abstract ) {
      Engine_Sanity::setDefaultDbAdapter($db);
    }

    // Get packages
    $packages = $this->_packageManager->listInstalledPackages();

    // Get dependencies
    $this->view->dependencies = $dependencies = $this->_packageManager->depend();

    // Get tests
    $this->view->tests = $tests = new Engine_Sanity();

    $packageIndex = array();
    foreach( $packages as $package ) {
      $packageTests = $package->getTests();

      // No tests
      if( empty($packageTests) ) {
        continue;
      }

      $packageIndex[$package->getKey()] = $package;

      // Make battery
      $battery = new Engine_Sanity(array(
        'name' => $package->getKey(),
      ));
      foreach( $packageTests as $test ) {
        $battery->addTest($test->toArray());
      }

      $tests->addTest($battery);
    }

    $this->view->packageIndex = $packageIndex;

    $tests->run();
  }

  public function logAction()
  {
    $logPath = APPLICATION_PATH . '/temporary/log';

    // Get all existing log files
    $logFiles = array();
    foreach( scandir($logPath) as $file ) {
      if( strtolower(substr($file, -4)) == '.log' ) {
        $logFiles[] = $file;
      }
    }

    // No files
    $this->view->logFiles = $logFiles;
    if( empty($logFiles) ) {
      $this->view->error = 'There are no log files to view.';
      return;
    }

    // Make form
    $labels = array(
      'main.log' => 'Error log',
      'tasks.log' => 'Task scheduler log',
      'translate.log' => 'Language log',
      'video.log' => 'Video encoding log',
    );
    $multiOptions = array_combine($logFiles, $logFiles);
    $labels = array_intersect_key($labels, $multiOptions);
    $multiOptions = array_diff_key($multiOptions, $labels);
    $multiOptions = array_merge($labels, $multiOptions);

    $this->view->formFilter = $formFilter = new Install_Form_Tools_LogFilter();
    $formFilter->getElement('file')->addMultiOptions($multiOptions);
    //$formFilter->getElement('file')->setValue(key($logFiles));

    $values = array_merge(array(
      //'file' => current($logFiles),
      'length' => 50,
    ), $this->_getAllParams());

    if( $formFilter->isValid($values) ) {
      $values = $formFilter->getValues();
    } else {
      $values = array('length' => 50);
    }

    /*
    if( empty($values['file']) ) {
      $values = array(
        'file' => current($logFiles),
        'length' => 50,
      );
    }
     */

    // Make sure param is in existing log files
    $logFile = $values['file'];
    if( empty($logFile) ||
        !in_array($logFile, $logFiles) ||
        !file_exists($logPath . DIRECTORY_SEPARATOR . $logFile) ) {
      $logFile = null;
    }

    // Exit if no valid log file
    if( !$logFile ) {
      $this->view->error = 'Please select a file to view.';
      return;
    }

    // Clear log if requested
    if( $this->getRequest()->isPost() && $this->_getParam('clear', false) ) {
      if( ($fh = fopen($logPath . DIRECTORY_SEPARATOR . $logFile, 'w')) ){
        ftruncate($fh, 0);
        fclose($fh);
      }
      return $this->_helper->redirector->gotoRoute(array());
    }

    // Get log length
    $this->view->logFile = $logFile;
    $this->view->logSize = $logSize = filesize($logPath . DIRECTORY_SEPARATOR . $logFile);
    $this->view->logLength = $logLength = $values['length'];
    $this->view->logOffset = $logOffset = $this->_getParam('offset', $logSize);


    // Tail the file
    $endOffset = 0;
    try {
      $lines = $this->_tail($logPath . DIRECTORY_SEPARATOR . $logFile, $logLength, $logOffset, true, $endOffset);
    } catch( Exception $e ) {
      $this->view->error = $e->getMessage();
      return;
    }

    $this->view->logText = $lines;
    $this->view->logEndOffset = $endOffset;
  }

  public function logDownloadAction()
  {
    $logPath = APPLICATION_PATH . '/temporary/log';

    // Get all existing log files
    $logFiles = array();
    foreach( scandir($logPath) as $file ) {
      if( strtolower(substr($file, -4)) == '.log' ) {
        $logFiles[] = $file;
      }
    }

    $logFile = $this->_getParam('file');
    if( empty($logFile) ||
        !in_array($logFile, $logFiles) ||
        !file_exists($logPath . DIRECTORY_SEPARATOR . $logFile) ) {
      exit();
    }

    // kill output buffering
    while( ob_get_level() > 0 ) {
      ob_end_clean();
    }

    // Send headers
    header('content-disposition: attachment, filename=' . urlencode($logFile));
    header('content-length: ' . filesize($logPath . DIRECTORY_SEPARATOR . $logFile));

    // Open file
    $handle = fopen($logPath . DIRECTORY_SEPARATOR . $logFile, 'r');
    while( '' !== ($str = fread($handle, 1024)) ) {
      echo $str;
    }
    exit();
  }

  protected function _outputFile($file, $exit = true)
  {
    $ext = trim(substr($file, strrpos($file, '.')), '.');
    switch( $ext ) {
      case 'css':
        header('Content-Type: text/css');
        break;
      case 'js':
        header('Content-Type: text/javascript');
        break;
      case 'jpg': case 'jpeg':
        header('Content-Type: image/jpeg');
        break;
      case 'png':
        header('Content-Type: image/png');
        break;
      case 'gif':
        header('Content-Type: image/gif');
        break;
      case 'log':
      case 'txt':
        header('Content-Type: text/plain');
        break;
      default:
        header('Content-Type: text/html');
        break;
    }
    echo file_get_contents($file);
    if( $exit ) {
      exit();
    }
  }

  // Static utility

  public function compareSort($a, $b)
  {
    if( !isset($this->_compareCountIndex[$a]) ||
        !isset($this->_compareCountIndex[$b]) ||
        $this->_compareCountIndex[$a] == $this->_compareCountIndex[$b]) {
      return 0;
    }

    return ( $this->_compareCountIndex[$a] < $this->_compareCountIndex[$b] ? 1 : -1 );
  }

  protected function _tail($file, $length = 10, $offset = 0, $whence = true, &$endOffset = null)
  {
    // Check stuff
    if( !file_exists($file) ) {
      throw new Exception('File does not exist.');
    }
    if( 0 === ($size = filesize($file)) ) {
      throw new Exception('File is empty.');
    }
    if( !($fh = fopen($file, 'r')) ) {
      throw new Exception('Unable to open file.');
    }

    // Process args
    if( abs($offset) > $size ) {
      throw new Exception('Reached end of file.');
    }
    if( !in_array($whence, array(SEEK_SET, SEEK_END)) ) {
      throw new Exception('Unknown whence.');
    }

    // Seek to requested position
    fseek($fh, $offset, SEEK_SET);

    // Read in chunks of 512 bytes
    $position = $offset;
    $break = false;
    $lines = array();
    $chunkSize = 512;
    $buffer = '';

    do {

      // Get next position
      $position += ( $whence ? -$chunkSize : $chunkSize );
      fseek($fh, $position, SEEK_SET);

      // Whoops we ran out of stuff
      if( $position < 0 || $position > $size ) {
        $break = true;
        break;
      }

      // Read a chunk
      $chunk = fread($fh, $chunkSize);
      if( $whence ) {
        $buffer = $chunk . $buffer;
      } else {
        $buffer .= $chunk;
      }

      // Parse chunk into lines
      $bufferLines = preg_split('/\r\n?|\n/', $buffer);

      // Put the last (probably incomplete) one back
      if( $whence ) {
        $buffer = array_shift($bufferLines);
      } else {
        $buffer = array_pop($bufferLines);
      }

      // Add to existing lines
      if( $whence ) {
        $lines = array_merge($bufferLines, $lines);
      } else {
        $lines = array_merge($lines, $bufferLines);
      }

      // Are we done?
      if( count($lines) >= $length ) {
        $break = true;
      }

    } while( !$break );

    $endOffset = $position;

    // Add remaining length in buffer
    $endOffset += strlen($buffer);

    return trim(join(PHP_EOL, $lines), "\n\r");
  }
}
