/**
 * This function will use the resource cache to lookup all controllers and their methods.  Then it
 * will create a Triumph_TemplateFileTag instance for each method.
 *
 * @param  string $sourceDir            the root directory of the project in question
 * @param  string $detectorDbFileName   the location of the resource cache SQLite file; as created by Triumph
 * @param  boolean $doSkip              out parameter; if TRUE then this detector does not know how
 *                                      to detect templates for the given source directory; this situation
 *                                      is different than zero templates being detected.
 * @return Triumph_TemplateFileTag[]  array of Triumph_TemplateFileTag instances the detected template files and their variables
 */
function detectTemplates($sourceDir, $detectorDbFileName, &$doSkip)
{
    $allTemplates = array();
    if (!is_file($detectorDbFileName)) {
        return $allTemplates;
    }
    // need to check that this detector is able to recognize the directory structure of sourceDir
    // if not, then we need to skip detection by returning immediately and setting $doSkip to TRUE.
    // by skipping detection, we prevent any detected templates from the previous detection script
    // from being deleted.
    $doSkip = TRUE;
    // for laravel, we look for the artisan script. if we don't have the artisan script assume
    // that this source is not a laravel project.
    $sourceDir = \opstring\ensure_ends_with($sourceDir, DIRECTORY_SEPARATOR);
    if (!is_file($sourceDir . 'artisan')) {
        return $allTemplates;
    }
    $doSkip = FALSE;
    // add your logic here; usually it will consist of querying the SQLite database in $detectorDbFileName
    // recursing though the call stack and picking the method calls for templates.
    $pdo = Zend_Db::factory('Pdo_Sqlite', array("dbname" => $detectorDbFileName));
    $callStackTable = new Triumph_CallStackTable($pdo);
    $callStacks = $callStackTable->load();
    // figure out the variables
    $scopes = $callStackTable->splitScopes($callStacks);
    // now go through each scope, looking for calls to $this->load->view
    foreach ($scopes as $scope) {
        // list of all method calls used to find calls to view method
        $methodCalls = $callStackTable->getMethodCalls($scope);
        // list of all property accesses, make sure that calls to view method
        // are used on the loader member variables
        $propertyCalls = $callStackTable->getPropertyCalls($scope);
        $variableCalls = $callStackTable->getVariables($scope);
        foreach ($methodCalls as $destinationVariable => $call) {
            if (\opstring\compare_case($call->methodName, 'make') == 0 && \opstring\compare_case($call->objectName, 'View') == 0) {
                // get the function arguments to View::make
                // the first argument to make is the template file
                // the second argument is an array of data (the template vars)
                $template = $variableCalls[$call->functionArguments[0]];
                $data = $variableCalls[$call->functionArguments[1]];
                if ($template) {
                    $currentTemplate = new Triumph_TemplateFileTag();
                    $currentTemplate->variables = array();
                    $currentTemplate->fullPath = '';
                    $currentTemplate->fullPath = $template->scalarValue;
                    // view file may have an extension; if it has an extension use that; otherwise use the default (.php)
                    if (stripos($currentTemplate->fullPath, '.') === FALSE) {
                        $currentTemplate->fullPath .= '.php';
                    }
                    // not using realpath() so that Triumph can know that a template file has not yet been created
                    // or the programmer has a bug in the project.
                    $currentTemplate->fullPath = \opstring\replace($currentTemplate->fullPath, '/', DIRECTORY_SEPARATOR);
                    $currentTemplate->fullPath = \opstring\replace($currentTemplate->fullPath, '\\', DIRECTORY_SEPARATOR);
                    $currentTemplate->fullPath = $sourceDir . 'app' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $currentTemplate->fullPath;
                    $allTemplates[] = $currentTemplate;
                }
                // argument 2 is an array of template variables
                if (isset($variableCalls[$call->functionArguments[1]])) {
                    $variableCall = $variableCalls[$call->functionArguments[1]];
                    $arrayKeys = $callStackTable->getArrayKeys($scope, $variableCall->destinationVariable);
                    foreach ($arrayKeys as $key) {
                        // add the siguil here; editor expects us to return variables
                        $currentTemplate->variables[] = '$' . $key;
                    }
                    $allTemplates[count($allTemplates) - 1] = $currentTemplate;
                }
            }
        }
    }
    return $allTemplates;
}
 function testCompareCase()
 {
     $this->assertEquals(-1, \opstring\compare_case('that', 'this'));
     $this->assertEquals(-1, \opstring\compare_case('that', 'thIS'));
     $this->assertEquals(0, \opstring\compare_case('this', 'this'));
     $this->assertEquals(0, \opstring\compare_case('this', 'thIS'));
     $this->assertEquals(1, \opstring\compare_case('this', 'that'));
     $this->assertEquals(1, \opstring\compare_case('this', 'thAT'));
 }
/**
 * This function will lookup all database connections for the given source.  Then it
 * will create a Triumph_DatabaseTag connection.
 *
 * @param  string $sourceDir            the root directory of the project in question
 * @param  boolean $doSkip              out parameter; if TRUE then this detector does not know how
 *                                      to detect databases for the given source directory; this situation
 *                                      is different than zero databases being detected.
 * @return Triumph_DatabaseTag[]      array of Triumph_DatabaseTag instances the detected databases
 */
function detectDatabases($sourceDir, &$doSkip)
{
    $doSkip = TRUE;
    $allDatabases = array();
    $sourceDir = \opstring\ensure_ends_with($sourceDir, DIRECTORY_SEPARATOR);
    if (!is_file($sourceDir . 'application/config/development/database.php') && !is_file($sourceDir . 'application/config/database.php')) {
        return $allDatabases;
    }
    // need to check that this detector is able to recognize the directory structure of sourceDir
    // if not, then we need to skip detection by returning immediately and setting $doSkip to TRUE.
    // by skipping detection, we prevent any detected databases from the previous detection script
    // from being deleted.
    $doSkip = FALSE;
    // need this define so that we can include code igniter files directly
    define('BASEPATH', '');
    // database config file can be in the environment directory
    // for now just get the development environment info
    if (is_file($sourceDir . 'application/config/development/database.php')) {
        include $sourceDir . 'application/config/development/database.php';
        if ($db) {
            foreach ($db as $groupName => $groupConnection) {
                if (strcasecmp('mysql', $groupConnection['dbdriver']) == 0) {
                    $tag = tagFromDbArray($groupName, $groupConnection);
                    $allDatabases[] = $tag;
                }
            }
        }
    } else {
        if (is_file($sourceDir . 'application/config/database.php')) {
            $db = array();
            include $sourceDir . 'application/config/database.php';
            if ($db) {
                foreach ($db as $groupName => $groupConnection) {
                    if (\opstring\compare_case('mysql', $groupConnection['dbdriver']) == 0 || \opstring\compare_case('mysqli', $groupConnection['dbdriver']) == 0) {
                        $tag = tagFromDbArray($groupName, $groupConnection);
                        $allDatabases[] = $tag;
                    }
                }
            }
        }
    }
    return $allDatabases;
}
/**
 * This function will use the resource cache to lookup all controllers and their methods.  Then it
 * will create a Triumph_TemplateFile instance for each method.
 *
 * @param  string $sourceDir            the root directory of the project in question
 * @param  string $detectorDbFileName   the location of the resource cache SQLite file; as created by Triumph
 * @param  boolean $doSkip              out parameter; if TRUE then this detector does not know how
 *                                      to detect templates for the given source directory; this situation
 *                                      is different than zero templates being detected.
 * @return Triumph_TemplateFile[]     array of Triumph_TemplateFile instances the detected template files and their variables
 */
function detectTemplates($sourceDir, $detectorDbFileName, &$doSkip)
{
    $doSkip = TRUE;
    $allTemplates = array();
    if (!is_file($detectorDbFileName)) {
        return $allTemplates;
    }
    // need to check that this detector is able to recognize the directory structure of sourceDir
    // if not, then we need to skip detection by returning immediately and setting $doSkip to TRUE.
    // by skipping detection, we prevent any detected templates from the previous detection script
    // from being deleted.
    $sourceDir = \opstring\ensure_ends_with($sourceDir, DIRECTORY_SEPARATOR);
    if (!is_file($sourceDir . 'application/config/routes.php') || !is_file($sourceDir . 'application/config/config.php')) {
        // this source directory does not contain a code igniter directory.
        return $allTemplates;
    }
    $doSkip = FALSE;
    // add your logic here; usually it will consist of querying the SQLite database in $detectorDbFileName
    // recursing though the call stack and picking the method calls for templates.
    $pdo = Zend_Db::factory('Pdo_Sqlite', array("dbname" => $detectorDbFileName));
    $callStackTable = new Triumph_CallStackTable($pdo);
    $callStacks = $callStackTable->load();
    // figure out the variables
    $scopes = $callStackTable->splitScopes($callStacks);
    // now go through each scope, looking for calls to $this->load->view
    foreach ($scopes as $scope) {
        // list of all method calls used to find calls to view method
        $methodCalls = $callStackTable->getMethodCalls($scope);
        // list of all property accesses, make sure that calls to view method
        // are used on the loader member variables
        $propertyCalls = $callStackTable->getPropertyCalls($scope);
        $variableCalls = $callStackTable->getVariables($scope);
        foreach ($methodCalls as $destinationVariable => $call) {
            if (\opstring\compare_case($call->methodName, 'view') == 0 && isset($propertyCalls[$call->objectName])) {
                // is this view call of a loader object ?
                $propertyCall = $propertyCalls[$call->objectName];
                if (\opstring\compare('$this', $propertyCall->objectName) == 0 && (\opstring\compare('load', $propertyCall->propertyName) == 0 || \opstring\compare('loader', $propertyCall->propertyName) == 0)) {
                    $currentTemplate = new Triumph_TemplateFileTag();
                    $currentTemplate->variables = array();
                    $currentTemplate->fullPath = '';
                    if (count($call->functionArguments) >= 1) {
                        // argument 1 of the view method call is the template file
                        // most of the time views are given as relative relatives; starting from the application/views/ directory
                        // for now ignore variable arguments
                        if (isset($variableCalls[$call->functionArguments[0]])) {
                            $variableCall = $variableCalls[$call->functionArguments[0]];
                            if ($variableCall->type == Triumph_CallStack::SCALAR) {
                                $currentTemplate->fullPath = $variableCall->scalarValue;
                                // view file may have an extension; if it has an extension use that; otherwise use the default (.php)
                                if (stripos($currentTemplate->fullPath, '.') === FALSE) {
                                    $currentTemplate->fullPath .= '.php';
                                }
                                // not using realpath() so that Triumph can know that a template file has not yet been created
                                // or the programmer has a bug in the project.
                                $currentTemplate->fullPath = \opstring\replace($currentTemplate->fullPath, '/', DIRECTORY_SEPARATOR);
                                $currentTemplate->fullPath = \opstring\replace($currentTemplate->fullPath, '\\', DIRECTORY_SEPARATOR);
                                $currentTemplate->fullPath = $sourceDir . 'application' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $currentTemplate->fullPath;
                                // push it now just in case the template does not get any variables
                                $allTemplates[] = $currentTemplate;
                            }
                        }
                    }
                    if (count($call->functionArguments) >= 2 && !empty($currentTemplate->fullPath)) {
                        // argument 2 is an array of template variables
                        if (isset($variableCalls[$call->functionArguments[1]])) {
                            $variableCall = $variableCalls[$call->functionArguments[1]];
                            $arrayKeys = $callStackTable->getArrayKeys($scope, $variableCall->destinationVariable);
                            foreach ($arrayKeys as $key) {
                                // add the siguil here; editor expects us to return variables
                                $currentTemplate->variables[] = '$' . $key;
                            }
                            $allTemplates[count($allTemplates) - 1] = $currentTemplate;
                        }
                    }
                }
            }
        }
    }
    return $allTemplates;
}
/**
 * This function will use the resource cache to lookup all controllers and their methods.  Then it
 * will create a Triumph_Url instance for each method; note that the routes file is 
 * also consulted and we will generate URLs for the default controller.
 *
 * @param  string  $sourceDir            the root directory of the project in question
 * @param  string  $resourceDbFileName   the location of the resource cache SQLite file; as created by Triumph
 * @param  string  $host                 the hostname of the application; this will be used a the prefix on all URLs
 * @param  boolean $doSkip               out parameter; if TRUE then this detector does not know how
 *                                       to detect URLs for the given source directory; this situation
 *                                       is different than zero URLs being detected.
 * @return Triumph_Url[]               array of Triumph_Url instances the detected URLs
 */
function detectUrls($sourceDir, $resourceDbFileName, $host, &$doSkip)
{
    $doSkip = TRUE;
    if (!is_file($resourceDbFileName)) {
        return array();
    }
    $sourceDir = \opstring\ensure_ends_with($sourceDir, DIRECTORY_SEPARATOR);
    $host = \opstring\ensure_ends_with($host, '/');
    // TODO: handle multiple apps
    // need this define so that we can include code igniter files directly
    if (!defined('BASEPATH')) {
        define('BASEPATH', '');
    }
    if (!is_file($sourceDir . 'application/config/routes.php') || !is_file($sourceDir . 'application/config/config.php')) {
        // this source directory does not contain a code igniter directory.
        return array();
    }
    $doSkip = FALSE;
    // get the code igniter configuration so that we can use it to build the
    // correct routes
    $config = array();
    $route = array();
    include $sourceDir . 'application/config/routes.php';
    include $sourceDir . 'application/config/config.php';
    $allUrls = array();
    // lookup all controller files from the resource cache, only controllers are accessible via URLs
    // since file names in the cache are OS dependant, need to use the correct directory separators
    $pdo = Zend_Db::factory('Pdo_Sqlite', array("dbname" => $resourceDbFileName));
    $fileItemTable = new Triumph_FileItemTable($pdo);
    $controllerDir = $sourceDir . 'application' . DIRECTORY_SEPARATOR . 'controllers';
    $matchingFiles = $fileItemTable->MatchingFiles($controllerDir);
    // lookup all of the methods for all controller files.
    $resourceTable = new Triumph_ResourceTable($pdo);
    $methods = $resourceTable->PublicMethodsFromFiles($matchingFiles);
    foreach ($methods as $resource) {
        // need to handle any sub-directories underneath the controllers; as the subdirectory
        // is propagated in the URL
        $controllerFile = \opstring\after($resource->fullPath, $controllerDir);
        $subDirectory = dirname($controllerFile);
        if ('\\' == $subDirectory || '/' == $subDirectory) {
            // hack to work around special case when there is no subdirectory
            $subDirectory = '';
        }
        // constructors are not web-accessible
        // code igniter makes methods that start with underscore
        if (\opstring\compare_case('__construct', $resource->identifier) && !\opstring\begins_with($resource->identifier, '_')) {
            // TODO: any controller arguments ... should get these from the user somehow
            $extra = '';
            $appUrl = makeUrl($route, $config, $subDirectory, $resource->fullPath, $resource->className, $resource->identifier, $extra);
            $appUrl->url = $host . $appUrl->url;
            $allUrls[] = $appUrl;
        }
    }
    return $allUrls;
}