<commit_before><?php
namespace Neos\MarketPlace\Property\TypeConverter;

/*
 * This file is part of the Neos.MarketPlace package.
 *
 * (c) Contributors of the Neos Project - www.neos.io
 *
 * This package is Open Source Software. For the full copyright and license
 * information, please view the LICENSE file which was distributed with this
 * source code.
 */

use Github\Client;
use Github\Exception\ApiLimitExceedException;
use Github\Exception\RuntimeException;
use Github\HttpClient\CachedHttpClient;
use Neos\MarketPlace\Domain\Model\Slug;
use Neos\MarketPlace\Domain\Model\Storage;
use Neos\MarketPlace\Utility\VersionNumber;
use Packagist\Api\Result\Package;
use TYPO3\Eel\FlowQuery\FlowQuery;
use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Property\Exception\InvalidPropertyMappingConfigurationException;
use TYPO3\Flow\Property\Exception\TypeConverterException;
use TYPO3\Flow\Property\PropertyMappingConfigurationInterface;
use TYPO3\Flow\Property\TypeConverter\AbstractTypeConverter;
use TYPO3\Flow\Utility\Arrays;
use TYPO3\TYPO3CR\Domain\Model\NodeInterface;
use TYPO3\TYPO3CR\Domain\Model\NodeTemplate;
use TYPO3\TYPO3CR\Domain\Service\NodeTypeManager;

/**
 * Convert package from packagist to node
 *
 * @api
 */
class PackageConverter extends AbstractTypeConverter
{
    /**
     * @var string
     */
    const STORAGE = 'storage';

    /**
     * @var NodeTypeManager
     * @Flow\Inject
     */
    protected $nodeTypeManager;

    /**
     * @var array<string>
     */
    protected $sourceTypes = [Package::class];

    /**
     * @var string
     */
    protected $targetType = NodeInterface::class;

    /**
     * @Flow\InjectConfiguration(path="github")
     * @var string
     */
    protected $githubSettings;

    /**
     * Converts $source to a node
     *
     * @param string|integer|array $source the string to be converted to a \DateTime object
     * @param string $targetType must be "DateTime"
     * @param array $convertedChildProperties not used currently
     * @param PropertyMappingConfigurationInterface $configuration
     * @return NodeInterface
     * @throws TypeConverterException
     */
    public function convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
    {
        /** @var Package $package */
        $package = $source;
        $storage = $this->getStorage($configuration);
        $vendor = explode('/', $package->getName())[0];
        $identifier = Slug::create($package->getName());
        $vendorNode = $storage->createVendor($vendor);
        $packageNode = $vendorNode->getNode($identifier);
        if ($packageNode === null) {
            $node = $this->create($package, $vendorNode);
        } else {
            $node = $this->update($package, $packageNode);
        }
        $this->createOrUpdateMaintainers($package, $node);
        $this->createOrUpdateVersions($package, $node);

        $this->handleDownloads($package, $node);
        $this->handleGithubMetrics($package, $node);

        $this->handleAbandonedPackageOrVersion($package, $node);
        return $node;
    }

    /**
     * @param Package $package
     * @param NodeInterface $node
     */
    protected function handleDownloads(Package $package, NodeInterface $node)
    {
        $downloads = $package->getDownloads();
        if (!$downloads instanceof Package\Downloads) {
            return;
        }
        $this->updateNodeProperties($node, [
            'downloadTotal' => $downloads->getTotal(),
            'downloadMonthly' => $downloads->getMonthly(),
            'downloadDaily' => $downloads->getDaily(),
        ]);
    }

    /**
     * @param Package $package
     * @param NodeInterface $node
     */
    protected function handleGithubMetrics(Package $package, NodeInterface $node)
    {
        if (isset($package->abandoned) && $package->abandoned === true) {
            $this->resetGithubMetrics($node);
        } else {
            $repository = $package->getRepository();
            if (strpos($repository, 'github.com') === false) {
                return;
            }
            // todo make it a bit more clever
            $repository = str_replace('.git', '', $repository);
            preg_match("#(.*)://github.com/(.*)#", $repository, $matches);
            list($oganization, $repository) = explode('/', $matches[2]);
            $client = new Client(
                new CachedHttpClient(['cache_dir' => $this->githubSettings['cacheDirectory']])
            );
            $client->authenticate($this->githubSettings['account'], $this->githubSettings['password']);
            try {
                $repository = $client->repositories()->show($oganization, $repository);
                if (!is_array($repository)) {
                    return;
                }
                $this->updateNodeProperties($node, [
                    'githubStargazers' => (integer)Arrays::getValueByPath($repository, 'stargazers_count'),
                    'githubWatchers' => (integer)Arrays::getValueByPath($repository, 'watchers_count'),
                    'githubForks' => (integer)Arrays::getValueByPath($repository, 'forks_count'),
                    'githubIssues' => (integer)Arrays::getValueByPath($repository, 'open_issues_count'),
                    'githubAvatar' => trim(Arrays::getValueByPath($repository, 'organization.avatar_url'))
                ]);
            } catch (ApiLimitExceedException $exception) {
                // Skip the processing if we hit the API rate limit
            } catch (RuntimeException $exception) {
                if ($exception->getMessage() === 'Not Found') {
                    // todo special handling of not found repository ?
                    $this->resetGithubMetrics($node);
                }
            }
        }
    }

    /**
     * @param NodeInterface $node
     */
    protected function resetGithubMetrics(NodeInterface $node)
    {<commit_msg>TASK: Fix a small typo<commit_after><?php
namespace Neos\MarketPlace\Property\TypeConverter;

/*
 * This file is part of the Neos.MarketPlace package.
 *
 * (c) Contributors of the Neos Project - www.neos.io
 *
 * This package is Open Source Software. For the full copyright and license
 * information, please view the LICENSE file which was distributed with this
 * source code.
 */

use Github\Client;
use Github\Exception\ApiLimitExceedException;
use Github\Exception\RuntimeException;
use Github\HttpClient\CachedHttpClient;
use Neos\MarketPlace\Domain\Model\Slug;
use Neos\MarketPlace\Domain\Model\Storage;
use Neos\MarketPlace\Utility\VersionNumber;
use Packagist\Api\Result\Package;
use TYPO3\Eel\FlowQuery\FlowQuery;
use TYPO3\Flow\Annotations as Flow;
use TYPO3\Flow\Property\Exception\InvalidPropertyMappingConfigurationException;
use TYPO3\Flow\Property\Exception\TypeConverterException;
use TYPO3\Flow\Property\PropertyMappingConfigurationInterface;
use TYPO3\Flow\Property\TypeConverter\AbstractTypeConverter;
use TYPO3\Flow\Utility\Arrays;
use TYPO3\TYPO3CR\Domain\Model\NodeInterface;
use TYPO3\TYPO3CR\Domain\Model\NodeTemplate;
use TYPO3\TYPO3CR\Domain\Service\NodeTypeManager;

/**
 * Convert package from packagist to node
 *
 * @api
 */
class PackageConverter extends AbstractTypeConverter
{
    /**
     * @var string
     */
    const STORAGE = 'storage';

    /**
     * @var NodeTypeManager
     * @Flow\Inject
     */
    protected $nodeTypeManager;

    /**
     * @var array<string>
     */
    protected $sourceTypes = [Package::class];

    /**
     * @var string
     */
    protected $targetType = NodeInterface::class;

    /**
     * @Flow\InjectConfiguration(path="github")
     * @var string
     */
    protected $githubSettings;

    /**
     * Converts $source to a node
     *
     * @param string|integer|array $source the string to be converted to a \DateTime object
     * @param string $targetType must be "DateTime"
     * @param array $convertedChildProperties not used currently
     * @param PropertyMappingConfigurationInterface $configuration
     * @return NodeInterface
     * @throws TypeConverterException
     */
    public function convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
    {
        /** @var Package $package */
        $package = $source;
        $storage = $this->getStorage($configuration);
        $vendor = explode('/', $package->getName())[0];
        $identifier = Slug::create($package->getName());
        $vendorNode = $storage->createVendor($vendor);
        $packageNode = $vendorNode->getNode($identifier);
        if ($packageNode === null) {
            $node = $this->create($package, $vendorNode);
        } else {
            $node = $this->update($package, $packageNode);
        }
        $this->createOrUpdateMaintainers($package, $node);
        $this->createOrUpdateVersions($package, $node);

        $this->handleDownloads($package, $node);
        $this->handleGithubMetrics($package, $node);

        $this->handleAbandonedPackageOrVersion($package, $node);
        return $node;
    }

    /**
     * @param Package $package
     * @param NodeInterface $node
     */
    protected function handleDownloads(Package $package, NodeInterface $node)
    {
        $downloads = $package->getDownloads();
        if (!$downloads instanceof Package\Downloads) {
            return;
        }
        $this->updateNodeProperties($node, [
            'downloadTotal' => $downloads->getTotal(),
            'downloadMonthly' => $downloads->getMonthly(),
            'downloadDaily' => $downloads->getDaily(),
        ]);
    }

    /**
     * @param Package $package
     * @param NodeInterface $node
     */
    protected function handleGithubMetrics(Package $package, NodeInterface $node)
    {
        if (isset($package->abandoned) && $package->abandoned === true) {
            $this->resetGithubMetrics($node);
        } else {
            $repository = $package->getRepository();
            if (strpos($repository, 'github.com') === false) {
                return;
            }
            // todo make it a bit more clever
            $repository = str_replace('.git', '', $repository);
            preg_match("#(.*)://github.com/(.*)#", $repository, $matches);
            list($organization, $repository) = explode('/', $matches[2]);
            $client = new Client(
                new CachedHttpClient(['cache_dir' => $this->githubSettings['cacheDirectory']])
            );
            $client->authenticate($this->githubSettings['account'], $this->githubSettings['password']);
            try {
                $meta = $client->repositories()->show($organization, $repository);
                if (!is_array($meta)) {
                    return;
                }
                $this->updateNodeProperties($node, [
                    'githubStargazers' => (integer)Arrays::getValueByPath($meta, 'stargazers_count'),
                    'githubWatchers' => (integer)Arrays::getValueByPath($meta, 'watchers_count'),
                    'githubForks' => (integer)Arrays::getValueByPath($meta, 'forks_count'),
                    'githubIssues' => (integer)Arrays::getValueByPath($meta, 'open_issues_count'),
                    'githubAvatar' => trim(Arrays::getValueByPath($meta, 'organization.avatar_url'))
                ]);
            } catch (ApiLimitExceedException $exception) {
                // Skip the processing if we hit the API rate limit
            } catch (RuntimeException $exception) {
                if ($exception->getMessage() === 'Not Found') {
                    // todo special handling of not found repository ?
                    $this->resetGithubMetrics($node);
                }
            }
        }
    }

    /**
     * @param NodeInterface $node
     */
    protected function resetGithubMetrics(NodeInterface $node)
    {
{'fraction_non_alphanumeric': 0.1286564412035873, 'fraction_numerical': 0.002514458134272064, 'mean_word_length': 2.6389143031412017, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 83, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3865388', 'n_tokens_mistral': 3242, 'n_tokens_neox': 3195, 'n_words': 672}