Commit 40b50d14 authored by Helmut Hummel's avatar Helmut Hummel Committed by Thomas Löffler

Make it possible to confirm a composer name for an extension

A checkbox lets users confirm they uploaded the extension
to Packagist and they own the package there.

Additionally, when this box is checked the composer name
needs to be provided as double confirmation.

If this composer name is valid, registered on Packagist
and matches the composer name of the latest uploaded
extension version, the composer name is persisted.
parent cc772d0d
......@@ -14,6 +14,7 @@ namespace T3o\TerFe2\Controller;
* The TYPO3 project - inspiring people to share!
*/
use T3o\TerFe2\Validation\Validator\ComposerNameValidator;
use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface;
/**
......@@ -235,6 +236,18 @@ class ExtensionController extends \T3o\TerFe2\Controller\AbstractController
}
}
public function initializeUpdateAction()
{
if (!$this->request->hasArgument('packagistConfirm')) {
return;
}
ComposerNameValidator::$composerNameIsConfirmed = (bool)$this->request->getArgument('packagistConfirm');
if (!ComposerNameValidator::$composerNameIsConfirmed) {
$extensionProperties = $this->request->getArgument('extension');
$extensionProperties['composerName'] = '';
$this->request->setArgument('extension', $extensionProperties);
}
}
/**
* Updates an existing extension
......@@ -282,6 +295,7 @@ class ExtensionController extends \T3o\TerFe2\Controller\AbstractController
$this->addFlashMessage('Tag "' . htmlspecialchars($tag) . '" added to extension');
}
}
$extension->setComposerName($extension->getValidatedComposerName());
$this->extensionRepository->update($extension);
if (!empty($save)) {
$this->redirectWithMessage(
......
......@@ -115,9 +115,17 @@ class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
*/
protected $expire;
/**
* Composer name of extension on packagist.org
*
* @var string
* @validate \T3o\TerFe2\Validation\Validator\ComposerNameValidator
*/
protected $composerName = '';
/**
* Constructor. Initializes all ObjectStorage instances.
* Initialize all ObjectStorage instances.
*/
public function __construct()
{
......@@ -596,4 +604,31 @@ class Extension extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
return $dateOfFirstUpload;
}
/**
* @return string
*/
public function getComposerName()
{
return $this->composerName;
}
/**
* @return string
*/
public function getValidatedComposerName()
{
if (empty($this->composerName) || $this->lastVersion === null) {
return '';
}
return $this->composerName === $this->lastVersion->getComposerName() ? $this->composerName : '';
}
/**
* @param string $composerName
*/
public function setComposerName($composerName)
{
$this->composerName = $composerName;
}
}
......@@ -104,7 +104,7 @@ class TerIndexer extends \ApacheSolrForTypo3\Solr\IndexQueue\Indexer
// composer support
$document->setField('supportsComposer_boolS', false);
$document->setField('composerName_stringS', '');
if ($extension->getLastVersion()->getComposerName()) {
if ($extension->getValidatedComposerName()) {
$document->setField('supportsComposer_boolS', true);
$document->setField('composerName_stringS', $extension->getLastVersion()->getComposerName());
}
......
<?php
namespace T3o\TerFe2\Validation\Validator;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use GuzzleHttp\Exception\RequestException;
use TYPO3\CMS\Core\Http\RequestFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
/**
* Validator for composer name on packagist.org
*/
class ComposerNameValidator extends AbstractValidator
{
protected $acceptsEmptyValues = false;
public static $composerNameIsConfirmed = false;
/**
* Returns false, if given composer name is not registered on packagist.org
*
* @param mixed $value The value that should be validated
* @return boolean TRUE if the value is valid, FALSE if an error occured
*/
public function isValid($value)
{
$value = (string)$value;
if ($value === '') {
if (!self::$composerNameIsConfirmed) {
return true;
}
$this->addError('This composer name must not be empty.', 1527507206);
return false;
}
if (!$this->isValidComposerName($value)) {
$this->addError('This composer name is not valid.', 1527501477);
return false;
}
if (!$this->isRegisteredOnPackagist($value)) {
$this->addError('This composer name is not registered on packagist.org.', 1527500413);
return false;
}
return true;
}
private function isValidComposerName(string $composerName): bool
{
if (strlen($composerName) > 50) {
return false;
}
if (substr_count($composerName, '/') !== 1) {
return false;
}
$cleanedComposerName = preg_replace('/[^a-z0-9\/_-]/', '', $composerName);
return $cleanedComposerName === $composerName;
}
private function isRegisteredOnPackagist(string $composerName): bool
{
$requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
try {
$response = $requestFactory->request(
'https://packagist.org/packages/' . $composerName,
'HEAD',
[
'connect_timeout' => 2,
'allow_redirects' => false,
]
);
return $response->getStatusCode() === 200;
} catch (RequestException $e) {
return false;
}
}
}
......@@ -100,6 +100,15 @@ return array(
'eval' => 'trim',
),
),
'composer_name' => array(
'exclude' => 1,
'label' => 'LLL:EXT:ter_fe2/Resources/Private/Language/locallang_db.xlf:tx_terfe2_domain_model_extension.composer_name',
'config' => array(
'type' => 'input',
'size' => 50,
'eval' => 'trim',
),
),
'last_upload' => array(
'exclude' => 1,
'label' => 'LLL:EXT:ter_fe2/Resources/Private/Language/locallang_db.xlf:tx_terfe2_domain_model_extension.last_upload',
......
......@@ -12,6 +12,15 @@
<trans-unit id="tx_terfe2_domain_model_extension.forge_link" xml:space="preserve">
<source>Link to issue tracker</source>
</trans-unit>
<trans-unit id="tx_terfe2_domain_model_extension.composer_name" xml:space="preserve">
<source>Composer name of extension</source>
</trans-unit>
<trans-unit id="tx_terfe2_domain_model_extension.packagist_confirm" xml:space="preserve">
<source>Published on Packagist</source>
</trans-unit>
<trans-unit id="tx_terfe2_domain_model_extension.packagist_confirm_message" xml:space="preserve">
<source>I confirm the extension is published on https://packagist.org and I maintain the package</source>
</trans-unit>
<trans-unit id="tx_terfe2_domain_model_extension.last_update" xml:space="preserve">
<source>Last update</source>
</trans-unit>
......
......@@ -12,6 +12,9 @@
<trans-unit id="tx_terfe2_domain_model_extension.forge_link" xml:space="preserve">
<source>Link to issue tracker</source>
</trans-unit>
<trans-unit id="tx_terfe2_domain_model_extension.composer_name" xml:space="preserve">
<source>Composer name of extension published on https://packagist.org</source>
</trans-unit>
<trans-unit id="tx_terfe2_domain_model_extension.last_update" xml:space="preserve">
<source>Last update</source>
</trans-unit>
......
......@@ -12,6 +12,25 @@
<f:render partial="FormErrors" />
<f:if condition="{extension.lastVersion.composerName}">
<div class="form-group row">
<label for="packagistConfirm" class="col-3 col-form-label">
<f:translate key="tx_terfe2_domain_model_extension.packagist_confirm" />
</label>
<div class="col-9">
<f:form.checkbox id="packagistConfirm" name="packagistConfirm" value="1" checked="{f:if(condition: '{extension.validatedComposerName}', then: '1', else: '0')}" />
&nbsp;<f:translate key="tx_terfe2_domain_model_extension.packagist_confirm_message" />
</div>
</div>
<div class="form-group row js-composer-name-input">
<label for="composerName" class="col-3 col-form-label">
<f:translate key="tx_terfe2_domain_model_extension.composer_name" />
</label>
<div class="col-9">
<f:form.textfield class="form-control" id="composerName" property="composerName" value="{extension.validatedComposerName}" additionalAttributes="{placeholder:'{extension.lastVersion.composerName}'}" />
</div>
</div>
</f:if>
<div class="form-group row">
<label for="forgeLink" class="col-3 col-form-label">
<f:translate key="tx_terfe2_domain_model_extension.forge_link" />
......@@ -20,7 +39,6 @@
<f:form.textfield class="form-control" id="forgeLink" property="forgeLink" additionalAttributes="{placeholder:'https://external.repository.org/your-project/issues'}" />
</div>
</div>
<div class="form-group row">
<label for="repositoryUrl" class="col-3 col-form-label">
<f:translate key="tx_terfe2_domain_model_extension.repository_url" />
......
......@@ -38,5 +38,13 @@ jQuery(document).ready(function ($) {
});
}
var $packagistField = $("input[name='tx_terfe2_pi1[packagistConfirm]']");
var $composerNameInput = $(".js-composer-name-input");
$composerNameInput.toggle($packagistField.is( ":checked" ));
$packagistField.click(function() {
$composerNameInput.toggle(this.checked);
});
});
......@@ -7,6 +7,7 @@ CREATE TABLE tx_terfe2_domain_model_extension (
ext_key tinytext,
forge_link tinytext,
composer_name varchar(60) DEFAULT '' NOT NULL,
last_upload int(11) unsigned DEFAULT '0',
last_maintained int(11) unsigned DEFAULT '0',
tags int(11) unsigned DEFAULT '0' NOT NULL,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment