<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Snigdha Rao Parvatneni
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\library\version_control;

use seekquarry\yioop\configs as C;
use seekquarry\yioop\library\CrawlConstants;

/** For Yioop global defines */
require_once __DIR__."/../../configs/Config.php";
/**
 * Library of functions used to fetch Git internal urls
 *
 * @author Chris Pollett
 */
class FetchGitRepositoryUrls implements CrawlConstants
{
    /**
     * A list of meta words that might be extracted from a query
     * @var array
     */
    public static $repository_types = ['git' => 'git', 'svn' => 'svn',
        'cvs' => 'cvs', 'vss' => 'vss', 'mercurial' => 'mercurial',
        'monotone' => 'monotone', 'bazaar' => 'bazaar', 'darcs' => 'darcs',
        'arch' => 'arch'];
    /**
     * An array used to store all the Git internal urls
     * @var array
     */
    public static $all_git_urls;
    /**
     * An indicator to tell no actions to be taken
     */
    const INDICATOR_NONE = 'none';
    /**
     * An indicator to indicate git repository
     */
    const INDICATOR_GIT = 'git';
    /**
     * An indicator to tell more git urls need to be fetched
     */
    const GIT_URL_CONTINUE = '@@@@';
    /**
     * An indicator to tell starting position of Git url to be used
     */
    const GIT_BASE_URL_START = 0;
    /**
     * An indicator to tell ending position of Git url to be used
     */
    const GIT_BASE_URL_END = '###';
    /**
     * A fixed component to be used with Git base url to form Git first url
     */
    const GIT_URL_EXTENSION = 'info/refs?service=git-upload-pack';
    /**
     * A fixed component to be used with Git urls to get next Git urls
     */
    const GIT_URL_OBJECT = 'objects/';
    /**
     * A fixed indicator used to get last letter of git base url
     */
    const GIT_BASE_URL_END_POSITION = -1;
    /**
     * A fixed indicator used to get last letter of git base url
     */
    const GIT_BASE_END_LETTER = 1;
    /**
     * A fixed position used to indicate starting point to fetch next Git url
     * from the master file
     */
    const GIT_NEXT_URL_START = 0;
    /**
     * A fixed position used to indicate ending position to fetch next Git url
     * from the master file
     */
    const GIT_NEXT_URL_END = 40;
    /**
     * A fixed indicator used to make desired Git folder structure from SHA hash
     */
    const GIT_URL_SPLIT = '/';
    /**
     * A fixed indicator used to mark starting position of SHA hash of Git
     * master tree
     */
    const GIT_MASTER_TREE_HASH_START = 16;
    /**
     * A fixed indicator used to mark ending position of SHA hash of Git
     * master tree
     */
    const GIT_MASTER_TREE_HASH_END = 41;
    /**
     * A fixed indicator used to mark starting position of SHA hash used to
     * indicate Git object folder
     */
    const GIT_FOLDER_NAME_START = 0;
    /**
     * A fixed indicator used to mark ending position of SHA hash used to
     * indicate Git object folder
     */
    const GIT_FOLDER_NAME_END = 2;
    /**
     * A fixed indicator used to mark starting position of SHA hash used to
     * indicate Git object file
     */
    const GIT_FILE_NAME_START = 2;
    /**
     * A fixed indicator used to mark ending position of SHA hash used to
     * indicate Git object file
     */
    const GIT_FILE_NAME_END = 38;
    /**
     * A fixed indicator used to indicate Git blob object
     */
     const GIT_BLOB_OBJECT = "blob";
    /**
     * A fixed indicator used to indicate Git tree object
     */
     const GIT_TREE_OBJECT = "tree";
    /**
     * A cURL time out parameter
     */
     const CURL_TIMEOUT = 5;
    /**
     * A cURL transfer parameter
     */
     const CURL_TRANSFER = 1;
    /**
     * Git blob access code starting position
     */
     const BLOB_ACCESS_CODE_START = 0;
    /**
     * Git blob access code ending position
     */
     const BLOB_ACCESS_CODE_END = 6;
    /**
     * Git tree access code starting position
     */
     const TREE_ACCESS_CODE_START = 0;
    /**
     * Git tree access code ending position
     */
     const TREE_ACCESS_CODE_END = 5;
    /**
     * Git SHA hash binary starting position
     */
     const SHA_HASH_BINARY_START = 0;
    /**
     * Git SHA hash binary ending position
     */
     const SHA_HASH_BINARY_END = 20;
    /**
     * A indicator for starting of Git file or folder name
     */
     const GIT_NAME_START = 0;
    /**
     * A indicator to represent next position after the access code in Git
     * blob object
     */
     const GIT_BLOB_NEXT = 7;
    /**
     * A indicator to represent next position after the access code in Git
     * tree object
     */
     const GIT_TREE_NEXT = 6;
    /**
     * A indicator to represent next position after the access code in Git
     * tree object
     */
     const HEX_NULL_CHARACTER = "\x00";
    /**
     * A indicator to represent that a git file is a blob file
     */
    const GIT_BLOB_INDICATOR = '100';
    /**
     * A indicator to represent that a git file is a tree file
     */
    const GIT_TREE_INDICATOR = '400';
    /**
     * Checks repository type based on extension
     *
     * @param string $extension to check
     * @return string $repository_type repository type based on the
     * extension of urls
     */
    public static function checkForRepository($extension)
    {
        if (isset(self::$repository_types[$extension])) {
            $repository_type = self::$repository_types[$extension];
        } else {
            $repository_type = self::INDICATOR_NONE;
        }
        return $repository_type;
    }
    /**
     * Sets up the seed sites with urls from a git repository (updates
     * these sites if have already started downloading from repository)
     *
     * @param string $url_to_check url needs to be processed
     * @param int $counter to keep track of number of urls processed
     * @param array $seeds store sites which are ready to be downloaded
     * @param array $repository_indicator indicates the type of the repository
     * @param array $site_value contains original Git url crawled
     * @param int $total_git_urls number of urls in repository less those
     *      already processed
     * @param array $all_git_urls current list of urls from git repository
     * @return array $git_internal_urls containing all the internal Git urls
     * fetched from the parent Git url
     */
    public static function setGitRepositoryUrl($url_to_check, $counter, $seeds,
        $repository_indicator, $site_value, $total_git_urls, $all_git_urls)
    {
        $git_internal_urls = [];
        if (!strpos($url_to_check, self::GIT_URL_CONTINUE)) {
            $git_next_urls = self::fetchGitRepositoryUrl($url_to_check);
            $all_git_urls = $git_next_urls;
            $total_git_urls = count($all_git_urls);
            $count_all_git_urls = $total_git_urls;
            if (intval(C\NUM_MULTI_CURL_PAGES) - $counter < $total_git_urls) {
                $total_git_urls = intval(C\NUM_MULTI_CURL_PAGES) - $counter;
            }
            for ($j = 0; $j < $total_git_urls; $j++) {
                $seeds[$counter][self::URL] = $git_next_urls[$j][2];
                $seeds[$counter][self::WEIGHT] = $site_value[1];
                $seeds[$counter][self::DOC_DEPTH] = $site_value[2];
                $seeds[$counter][self::CRAWL_DELAY] = $site_value[3];
                $seeds[$counter][self::REPOSITORY_TYPE] = $repository_indicator;
                $seeds[$counter][self::FILE_NAME] = $git_next_urls[$j][0];
                $seeds[$counter][self::SHA_HASH] = $git_next_urls[$j][1];
                $counter++;
                $git_url_index = $j + 1;
                if ($git_url_index >= $count_all_git_urls) {
                    $repository_indicator = self::INDICATOR_NONE;
                } else {
                    $repository_indicator = self::INDICATOR_GIT;
                }
            }
            $counter--;
        } else {
            $position = strpos($url_to_check, self::GIT_URL_CONTINUE);
            $extension_string = substr($url_to_check, $position,
                strlen($url_to_check));
            $extension_count = explode(self::GIT_URL_CONTINUE,
                $extension_string);
            $git_index = intval(array_sum($extension_count));
            $url_to_check = substr($url_to_check, self::GIT_NEXT_URL_START,
                $position);
            $count_all_git_urls = $total_git_urls;
            if (intval(C\NUM_MULTI_CURL_PAGES) - $counter < $total_git_urls -
                $git_index) {
                $total_git_urls = intval(C\NUM_MULTI_CURL_PAGES) - $counter;
            } else {
                $total_git_urls = $total_git_urls - $git_index;
            }
            for ($j = 0; $j < $total_git_urls; $j++) {
                $seeds[$counter][self::URL] = $all_git_urls[$git_index][2];
                $seeds[$counter][self::WEIGHT] = $site_value[1];
                $seeds[$counter][self::DOC_DEPTH] = $site_value[1];
                $seeds[$counter][self::CRAWL_DELAY] = $site_value[3];
                $seeds[$counter][self::REPOSITORY_TYPE] = $repository_indicator;
                $seeds[$counter][self::FILE_NAME] = $all_git_urls[$git_index]
                    [0];
                $seeds[$counter][self::SHA_HASH] = $all_git_urls[$git_index][1];
                $counter++;
                $git_index++;
                $git_url_index = $j + 1;
                if ($git_index >= $count_all_git_urls) {
                    $repository_indicator = self::INDICATOR_NONE;
                } else {
                    $repository_indicator = self::INDICATOR_GIT;
                }
            }
            $counter--;
        }
        $git_internal_urls['position'] = $counter;
        $git_internal_urls['index'] = $git_url_index;
        $git_internal_urls['seeds'] = $seeds;
        $git_internal_urls['indicator'] = $repository_indicator;
        $git_internal_urls['count'] = $count_all_git_urls;
        $git_internal_urls['all'] = $all_git_urls;
        return $git_internal_urls;
    }
    /**
     * Get the Git internal urls from the parent Git url
     *
     * @param string $url_to_check url needs to be processed
     * @return an array $git_next_urls consists of list of Git
     *      internal urls which are called during the git clone
     */
    public static function fetchGitRepositoryUrl($url_to_check)
    {
        $compression_indicator = false;
        $position = strpos($url_to_check, self::GIT_BASE_URL_END);
        $git_base_url = substr($url_to_check, self::GIT_BASE_URL_START,
            $position);
        $base_url_last_letter = substr($git_base_url,
            self::GIT_BASE_URL_END_POSITION, self::GIT_BASE_END_LETTER);
        if ($base_url_last_letter != self::GIT_URL_SPLIT) {
            $git_base_url = $git_base_url . self::GIT_URL_SPLIT;
        }
        $git_first_url =  $git_base_url.self::GIT_URL_EXTENSION;
        $git_first_url_content = self::getNextGitUrl($git_first_url,
            $compression_indicator);
        $compression_indicator = true;
        $git_second_url = self::getGitMasterFile($git_first_url_content,
            $git_base_url);
        $git_second_url_content = self::getNextGitUrl($git_second_url,
            $compression_indicator);
        $git_third_url = self::getGitMasterTree($git_second_url_content,
            $git_base_url);
        $git_third_url_content = self::getNextGitUrl($git_third_url,
            $compression_indicator);
        $git_next_urls = self::getObjects($git_third_url_content,
            $git_base_url);
        return $git_next_urls;
    }
    /**
     * Get the Git second url which points to Git master tree structure
     *
     * @param string $git_first_url_content contents of Git first url
     * @param string $git_base_url common portion of Git urls
     * @return string $git_next_url consists of second internal Git url
     */
    public static function getGitMasterFile($git_first_url_content,
        $git_base_url)
    {
        $git_extended_url = substr($git_first_url_content,
            self::GIT_NEXT_URL_START, self::GIT_NEXT_URL_END);
        $first_split_git_extended_url = substr($git_extended_url,
            self::GIT_FOLDER_NAME_START, self::GIT_FOLDER_NAME_END);
        $second_split_git_extended_url = substr($git_extended_url,
            self::GIT_FILE_NAME_START, self::GIT_FILE_NAME_END);
        $git_url_connector = $first_split_git_extended_url .
            self::GIT_URL_SPLIT . $second_split_git_extended_url;
        $git_next_url = $git_base_url . self::GIT_URL_OBJECT .
            $git_url_connector;
        return $git_next_url;
    }
    /**
     * Get the Git third url which contains the information about the
     *    organization of entire git repository
     *
     * @param string $git_second_url_content contents of Git second url
     * @param string $git_base_url common portion of git urls
     * @return string $git_next_url consists of third internal git url
     */
    public static function getGitMasterTree($git_second_url_content,
        $git_base_url)
    {
        $git_master_tree_hash = substr($git_second_url_content,
            self::GIT_MASTER_TREE_HASH_START, self::GIT_MASTER_TREE_HASH_END);
        $git_object_folder_name = substr($git_master_tree_hash,
            self::GIT_FOLDER_NAME_START, self::GIT_FOLDER_NAME_END);
        $git_object_file_name = substr($git_master_tree_hash,
            self::GIT_FILE_NAME_START, self::GIT_FILE_NAME_END);
        $git_object_path = $git_object_folder_name . self::GIT_URL_SPLIT .
            $git_object_file_name;
        $git_next_url = $git_base_url . self::GIT_URL_OBJECT . $git_object_path;
        return $git_next_url;
    }
    /**
     * Get the Git content from url which will be used to get the
     *    next git url
     *
     * @param string $git_url git url to extract contents from it
     * @param string $compression_indicator indicator for compress and
     * uncompress contents
     * @return string $git_object_content consists contents extracted from the
     * url
     */
    public static function getNextGitUrl($git_url, $compression_indicator)
    {
        if (!$compression_indicator) {
            $git_object_compress_content = self::getGitData($git_url);
            $git_object_content = $git_object_compress_content;
        } else {
            $git_object_compress_content = self::getGitData($git_url);
            $git_object_uncompress_content = gzuncompress(
                $git_object_compress_content);
            $git_object_content = $git_object_uncompress_content;
        }
        return $git_object_content;
    }
    /**
     * Get the Git blob and tree objects
     *
     * @param string $git_object_content compressed content of git master tree
     *    file
     * @param string $git_base_url common content of git url
     * @return array $blob_url contains information and url for git blob objects
     */
    public static function getObjects($git_object_content, $git_base_url)
    {
        $blob_url = [];
        $temp_git_object_content['content'] = $git_object_content;
        for ($i = 0; $i < strlen($git_object_content); $i++) {
            $blob_position = strpos($temp_git_object_content['content'],
                self::GIT_BLOB_INDICATOR);
            $tree_position = strpos($temp_git_object_content['content'],
                self::GIT_TREE_INDICATOR);
            $git_object_positions = self::checkPosition($blob_position,
                $tree_position, $git_object_content);
            $blob_position = $git_object_positions['blob'];
            $tree_position = $git_object_positions['tree'];
            if ($blob_position < $tree_position) {
                $temp_git_object_content = self::readBlobSha(
                    $temp_git_object_content['content'], $blob_position,
                        strlen($temp_git_object_content['content']),
                            $git_base_url);
            }
            else if ($tree_position < $blob_position) {
                $temp_git_object_content = self::readTreeSha(
                    $temp_git_object_content['content'], $tree_position,
                        strlen($temp_git_object_content['content']),
                            $git_base_url);
            }
            $i = strlen($temp_git_object_content['content']);
            $i = strlen($git_object_content) - $i;
            if ($temp_git_object_content['value']['indicator'] !=
                self::GIT_TREE_OBJECT) {
                $blob_details[0] = $temp_git_object_content['value']['name'];
                $blob_details[1] = $temp_git_object_content['value']['hash'];
                $blob_details[2] = $temp_git_object_content['value']['url'];
                $blob_url[] = $blob_details;
            }
            if ($temp_git_object_content['indicator'] != self::GIT_BLOB_OBJECT){
                for ($k = 0; $k < count($temp_git_object_content['indicator']);
                    $k++) {
                    $blob_details[0] = $temp_git_object_content['indicator'][$k]
                        [0];
                    $blob_details[1] = $temp_git_object_content['indicator'][$k]
                        [1];
                    $blob_details[2] = $temp_git_object_content['indicator'][$k]
                        [2];
                    $blob_url[] = $temp_git_object_content['indicator'][$k];
                }
            }
        }
        return $blob_url;
    }
    /**
     * checks the position of access code for null values
     *
     * @param string $git_blob_position first occuence of git blob access code
     * @param string $git_tree_position first occuence of git tree access code
     * @param string $git_object_content compressed content of git master tree
     * @return array $git_object_positions length of the compressed content
     *    afterthe access code
     */
    public static function checkPosition($git_blob_position, $git_tree_position,
        $git_object_content)
    {
        $git_object_positions = [];
        if (is_bool($git_blob_position) === true) {
            $git_blob_position = strlen($git_object_content);
        }
        if (is_bool($git_tree_position) === true) {
            $git_tree_position = strlen($git_object_content);
        }
        $git_object_positions['blob'] = $git_blob_position;
        $git_object_positions['tree'] = $git_tree_position;
        return $git_object_positions;
    }
    /**
     * Get the details of the blob file i.e blob file name, sha hash and content
     *
     * @param string $git_object_content compressed content of git master tree
     * @param string $blob_position first occuence of git blob access code
     *    in $content
     * @param string $length length of the compressed content of git master tree
     * @param string $git_base_url common portion of git url
     * @return array $git_blob_content contains details of git blob object
     */
    public static function readBlobSha($git_object_content, $blob_position,
        $length, $git_base_url)
    {
        $git_blob_content = [];
        $blob_values = [];
        $temp_git_content = substr($git_object_content, $blob_position,
            $length);
        $access_code = substr($temp_git_content, self::BLOB_ACCESS_CODE_START,
            self::BLOB_ACCESS_CODE_END);
        $blob_values['code'] = $access_code;
        $temp_git_content = substr($temp_git_content, self::GIT_BLOB_NEXT,
            $length);
        $temp_position = strpos($temp_git_content, self::HEX_NULL_CHARACTER);
        $file_name = substr($temp_git_content, self::GIT_NAME_START,
            $temp_position);
        $blob_values['name'] = $file_name;
        $temp_git_content = substr($temp_git_content, $temp_position + 1,
            $length);
        $sha_binary = substr($temp_git_content, self::SHA_HASH_BINARY_START,
            self::SHA_HASH_BINARY_END);
        $sha_hash = bin2hex($sha_binary);
        $blob_values['hash'] = $sha_hash;
        $temp_git_content = substr($temp_git_content, self::SHA_HASH_BINARY_END,
            $length);
        $blob_url = self::urlMaker($sha_hash, $git_base_url);
        $blob_values['url'] = $blob_url;
        $blob_values['indicator'] = self::GIT_BLOB_OBJECT;
        $git_blob_content['value'] = $blob_values;
        $git_blob_content['content'] = $temp_git_content;
        $git_blob_content['indicator'] = self::GIT_BLOB_OBJECT;
        return $git_blob_content;
    }
    /**
     * Get the details of the tree file i.e folder name, sha hash and
     * blob url inside the tree
     *
     * @param string $git_object_content compressed content of git master tree
     * @param string $tree_position first occuence of git tree access code in
     * the $content
     * @param string $length length of the compressed content of git master tree
     * @param string $git_base_url common portion of git url
     * @return array $git_tree_content contains details of git blob object
     */
    public static function readTreeSha($git_object_content, $tree_position,
        $length, $git_base_url)
    {
        $git_tree_content = [];
        $tree_values = [];
        $temp_git_content = substr($git_object_content, $tree_position,
            $length);
        $access_code = substr($temp_git_content, self::TREE_ACCESS_CODE_START,
            self::TREE_ACCESS_CODE_END);
        $tree_values['code'] = $access_code;
        $temp_git_content = substr($temp_git_content, self::GIT_TREE_NEXT,
            $length);
        $temp_position = strpos($temp_git_content, self::HEX_NULL_CHARACTER);
        $folder_name = substr($temp_git_content, self::GIT_NAME_START,
            $temp_position);
        $tree_values['name'] = $folder_name;
        $temp_git_content = substr($temp_git_content, $temp_position + 1,
            $length);
        $sha_binary = substr($temp_git_content, self::SHA_HASH_BINARY_START,
            self::SHA_HASH_BINARY_END);
        $sha_hash = bin2hex($sha_binary);
        $tree_values['hash'] = $sha_hash;
        $tree_values['indicator'] = self::GIT_TREE_OBJECT;
        $temp_git_content = substr($temp_git_content, self::SHA_HASH_BINARY_END,
            $length);
        $blob_url = self::checkNestedStructure($sha_hash, $git_base_url);
        $git_tree_content['value'] = $tree_values;
        $git_tree_content['content'] = $temp_git_content;
        $git_tree_content['indicator'] = $blob_url;
        return $git_tree_content;
    }
    /**
     * Checks the nested structure inside git tree object
     *
     * @param string $sha_hash sha of the git tree object
     * @param string $git_base_url common portion of the parent git url
     * @return string $blob_url contains url of the blob file inside the folder
     */
    public static function checkNestedStructure($sha_hash, $git_base_url)
    {
        $url = self::urlMaker($sha_hash, $git_base_url);
        $git_compressed_content = self::getGitData($url);
        $git_uncompressed_content = gzuncompress($git_compressed_content);
        $blob_url = self::getObjects($git_uncompressed_content, $git_base_url);
        return $blob_url;
    }
    /**
     * Makes the git clone internal url for blob objects
     *
     * @param string $sha_hash of the git blob object
     * @param string $git_base_url common portion of git url
     * @return string $git_object_url contains the complete url of the blob file
     */
    public static function urlMaker($sha_hash, $git_base_url)
    {
        $git_object_folder = substr($sha_hash, self::GIT_FOLDER_NAME_START,
            self::GIT_FOLDER_NAME_END);
        $git_object_file = substr($sha_hash, self::GIT_FILE_NAME_START,
            self::GIT_FILE_NAME_END);
        $git_object_path = $git_object_folder . self::GIT_URL_SPLIT .
            $git_object_file;
        $git_object_url = $git_base_url . self::GIT_URL_OBJECT .
            $git_object_path;
        return $git_object_url;
    }
    /**
     * Makes the cURL call to get the contents
     *
     * @param string $git_url url to dowmload the contents
     * @return string $git_content actual content of the git url
     */
    public static function getGitData($git_url)
    {
        $ch = curl_init();
        $timeout = self::CURL_TIMEOUT;
        curl_setopt($ch, CURLOPT_URL, $git_url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, self::CURL_TRANSFER);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
        $git_content = curl_exec($ch);
        unset($ch);
        return $git_content;
    }
}
