├── tests ├── testFiles │ ├── empty.yml │ ├── test.php │ ├── test.txt.php │ ├── migrations │ │ ├── test.php │ │ ├── missing_update_schema.php │ │ ├── missing_revert_schema.php │ │ └── existing_revert_schema.php │ ├── invalid.yml │ ├── no_namespace.php │ ├── valid.yml │ ├── images │ │ ├── image.gif │ │ ├── image.jpeg │ │ ├── image.jpg │ │ ├── image.png │ │ ├── image.webp │ │ └── image.svg │ ├── no_in_phpbb.php │ ├── eval.php │ ├── addslashes.php │ ├── in_phpbb_wrong.php │ ├── enable_globals.php │ ├── enable_globals3.php │ ├── empty_import.yml │ ├── invalid_import.yml │ ├── in_phpbb_wrong2.php │ ├── variable_function.php │ ├── sql_injection.php │ ├── enable_globals2.php │ ├── language │ │ ├── en │ │ │ ├── additional.php │ │ │ └── common.php │ │ ├── en_us │ │ │ ├── additional.php │ │ │ └── common.php │ │ ├── en_complete │ │ │ ├── additional.php │ │ │ └── common.php │ │ └── en_incomplete │ │ │ └── common.php │ ├── var_test2.php │ ├── var_test.php │ ├── configs │ │ ├── autowired │ │ │ └── services.yml │ │ ├── badname3 │ │ │ └── services.yml │ │ ├── simple │ │ │ └── services.yml │ │ ├── badname1 │ │ │ └── services.yml │ │ └── badname2 │ │ │ └── services.yml │ ├── composer.json │ └── licenses │ │ ├── apache-2.0 │ │ └── license.txt │ │ └── gpl-2.0-skeleton-ext │ │ └── license.txt ├── events │ ├── invalid_name_simple.php │ ├── valid_name_simple.php │ ├── invalid_name_simple_dispatch.php │ ├── valid_name_simple_dispatch.php │ ├── invalid_name_short_single.php │ ├── valid_name_long_single.php │ ├── valid_name_short_single.php │ ├── invalid_name_long_single.php │ ├── invalid_name_short_multi.php │ ├── valid_name_short_multi.php │ ├── invalid_name_long_multi.php │ └── valid_name_long_multi.php ├── bootstrap.php ├── validate_sql_queries_test.php ├── validate_composer_test.php ├── validate_languages_test.php ├── validate_license_test.php ├── validate_revert_schema_test.php ├── validate_service_test.php ├── file_loader_test.php ├── validate_directory_structure_test.php ├── php_exporter_test.php ├── Mock │ └── Output.php └── epv_test_validate_php_functions_test.php ├── .gitignore ├── composer.phar ├── src ├── Files │ ├── Exception │ │ ├── FileException.php │ │ └── FileLoadException.php │ ├── Type │ │ ├── LangFileInterface.php │ │ ├── RoutingFileInterface.php │ │ ├── ServiceFileInterface.php │ │ ├── ComposerFileInterface.php │ │ ├── MigrationFileInterface.php │ │ ├── CssFileInterface.php │ │ ├── HTMLFileInterface.php │ │ ├── LockFileInterface.php │ │ ├── PHPFileInterface.php │ │ ├── XmlFileInterface.php │ │ ├── BinaryFileInterface.php │ │ ├── ImageFileInterface.php │ │ ├── PlainFileInterface.php │ │ ├── JavascriptFileInterface.php │ │ ├── JsonFileInterface.php │ │ ├── YmlFileInterface.php │ │ ├── CssFile.php │ │ ├── PHPFile.php │ │ ├── RoutingFile.php │ │ ├── ServiceFile.php │ │ ├── XmlFile.php │ │ ├── ComposerFile.php │ │ ├── HTMLFile.php │ │ ├── LockFile.php │ │ ├── PlainFile.php │ │ ├── BinaryFile.php │ │ ├── JavascriptFile.php │ │ ├── LangFile.php │ │ ├── MigrationFile.php │ │ ├── ImageFile.php │ │ ├── JsonFile.php │ │ └── YmlFile.php │ ├── LineInterface.php │ ├── FileInterface.php │ ├── Line.php │ ├── BaseFile.php │ └── FileLoader.php ├── Tests │ ├── Exception │ │ └── TestException.php │ ├── Type.php │ ├── ArrayKeyVisitor.php │ ├── TestInterface.php │ ├── Tests │ │ ├── epv_test_validate_event_names.php │ │ ├── epv_test_validate_linefeeds.php │ │ ├── epv_test_validate_routing.php │ │ ├── epv_test_validate_sql_queries.php │ │ ├── epv_test_validate_service.php │ │ ├── epv_test_validate_revert_schema.php │ │ ├── epv_test_validate_languages.php │ │ ├── epv_test_validate_composer.php │ │ └── epv_test_validate_directory_structure.php │ ├── BaseTest.php │ ├── TestStartup.php │ └── TestRunner.php ├── Cli.php ├── EPV.php ├── Output │ ├── OutputFormatter.php │ ├── Message.php │ ├── OutputInterface.php │ ├── HtmlOutput.php │ └── Output.php ├── Events │ └── recursive_event_filter_iterator.php ├── Command │ └── ValidateCommand.php └── Resources │ └── gpl-2.0.txt ├── phpunit.xml.dist ├── composer.json ├── .github └── workflows │ └── tests.yml └── README.md /tests/testFiles/empty.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testFiles/test.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testFiles/test.txt.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/testFiles/migrations/test.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.idea/ 3 | /bin/ -------------------------------------------------------------------------------- /tests/testFiles/invalid.yml: -------------------------------------------------------------------------------- 1 | services: 2 | some. 3 | -------------------------------------------------------------------------------- /tests/testFiles/no_namespace.php: -------------------------------------------------------------------------------- 1 | enable_super_globals(); 8 | 9 | -------------------------------------------------------------------------------- /tests/testFiles/enable_globals3.php: -------------------------------------------------------------------------------- 1 | {'enable_super_' . 'globals'}(); -------------------------------------------------------------------------------- /tests/testFiles/empty_import.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: empty.yml } 3 | 4 | services: 5 | some.service.name: 6 | class: a\b\c 7 | -------------------------------------------------------------------------------- /tests/testFiles/invalid_import.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: invalid.yml } 3 | 4 | services: 5 | some.service.name: 6 | class: a\b\c 7 | -------------------------------------------------------------------------------- /tests/testFiles/in_phpbb_wrong2.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.PostsMerging.posts_merging_end'); 9 | -------------------------------------------------------------------------------- /tests/events/valid_name_simple.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.postsmerging.posts_merging_end'); 9 | -------------------------------------------------------------------------------- /tests/testFiles/sql_injection.php: -------------------------------------------------------------------------------- 1 | db->sql_escape($topic_title) . "'"; 10 | -------------------------------------------------------------------------------- /tests/events/invalid_name_simple_dispatch.php: -------------------------------------------------------------------------------- 1 | dispatch('rxu.PostsMerging.posts_merging_end'); 9 | -------------------------------------------------------------------------------- /tests/events/valid_name_simple_dispatch.php: -------------------------------------------------------------------------------- 1 | dispatch('rxu.postsmerging.posts_merging_end'); 9 | -------------------------------------------------------------------------------- /tests/testFiles/migrations/missing_update_schema.php: -------------------------------------------------------------------------------- 1 | {$a}{$b}(); 9 | 10 | $enable_super_globals = 'enable_super_globals'; 11 | 12 | $request->$enable_super_globals(); 13 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | require ('vendor/autoload.php'); -------------------------------------------------------------------------------- /tests/testFiles/language/en/additional.php: -------------------------------------------------------------------------------- 1 | 'Third language string', 15 | )); 16 | -------------------------------------------------------------------------------- /tests/testFiles/language/en_us/additional.php: -------------------------------------------------------------------------------- 1 | 'Third language string', 15 | )); 16 | -------------------------------------------------------------------------------- /tests/testFiles/language/en_complete/additional.php: -------------------------------------------------------------------------------- 1 | 'Third language string', 15 | )); 16 | -------------------------------------------------------------------------------- /tests/testFiles/images/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/testFiles/migrations/existing_revert_schema.php: -------------------------------------------------------------------------------- 1 | 'First language string', 15 | 'C' => [ 16 | 1 => 'Singular', 17 | 2 => 'Plural', 18 | ], 19 | )); 20 | -------------------------------------------------------------------------------- /src/Files/Exception/FileException.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Exception; 11 | 12 | 13 | class FileException extends \Exception 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Tests/Exception/TestException.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Exception; 11 | 12 | 13 | class TestException extends \Exception 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tests/testFiles/language/en/common.php: -------------------------------------------------------------------------------- 1 | 'First language string', 15 | 'B' => 'Second language string', 16 | 'C' => [ 17 | 1 => 'Singular', 18 | 2 => 'Plural', 19 | ], 20 | )); 21 | -------------------------------------------------------------------------------- /tests/testFiles/var_test2.php: -------------------------------------------------------------------------------- 1 | {'validate1_' . $name}(); 12 | $result2 = $this->{"validate2_$name"}(); 13 | $result3 = $this->{$name === 'test' ? 'callBack1' : 'callBack2'}(); 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Files/Exception/FileLoadException.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Exception; 11 | 12 | 13 | class FileLoadException extends \Exception 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Files/Type/LangFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | interface LangFileInterface extends PHPFileInterface 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Files/Type/RoutingFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | interface RoutingFileInterface extends YmlFileInterface 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Files/Type/ServiceFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | interface ServiceFileInterface extends YmlFileInterface 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Files/Type/ComposerFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | interface ComposerFileInterface extends JsonFileInterface 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/Files/Type/MigrationFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | interface MigrationFileInterface extends PHPFileInterface 14 | { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tests/testFiles/var_test.php: -------------------------------------------------------------------------------- 1 | foo->{$bar}($id); 17 | 18 | $this->foo->{$this->action}($this->cat_id); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Files/Type/CssFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface CssFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/HTMLFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface HTMLFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/LockFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface LockFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/PHPFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface PHPFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/XmlFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface XmlFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/BinaryFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface BinaryFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/ImageFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface ImageFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/PlainFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface PlainFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/JavascriptFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface JavascriptFileInterface extends FileInterface 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Files/Type/JsonFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface JsonFileInterface extends FileInterface 16 | { 17 | public function getJson(); 18 | } 19 | -------------------------------------------------------------------------------- /tests/testFiles/configs/autowired/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | 5 | epv.test.controller: 6 | class: epv\test\controller\main 7 | 8 | epv.test.helper.packager: 9 | class: epv\test\helper\packager 10 | 11 | epv.test.helper.validator: 12 | class: epv\test\helper\validator 13 | 14 | epv.test.listener: 15 | class: epv\test\event\main_listener 16 | tags: ['event.listener'] 17 | -------------------------------------------------------------------------------- /tests/testFiles/language/en_us/common.php: -------------------------------------------------------------------------------- 1 | 'First language string', 15 | )); 16 | 17 | $b = array( 18 | 'B' => 'Second language string', 19 | ); 20 | 21 | $lang = array_merge($lang, $b, [ 22 | 'C' => [ 23 | // Missing plural should not generate an error 24 | 1 => 'Singular', 25 | ], 26 | ]); 27 | -------------------------------------------------------------------------------- /tests/testFiles/language/en_complete/common.php: -------------------------------------------------------------------------------- 1 | 'First language string', 15 | )); 16 | 17 | $b = array( 18 | 'B' => 'Second language string', 19 | ); 20 | 21 | $lang = array_merge($lang, $b, [ 22 | 'C' => [ 23 | // Missing plural should not generate an error 24 | 1 => 'Singular', 25 | ], 26 | ]); 27 | -------------------------------------------------------------------------------- /src/Files/LineInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files; 11 | 12 | 13 | interface LineInterface 14 | { 15 | /** 16 | * Get the file for this specific line 17 | * @return FileInterface 18 | */ 19 | public function getFile(); 20 | 21 | public function getLineNr(); 22 | 23 | public function getLine(); 24 | } 25 | -------------------------------------------------------------------------------- /src/Files/Type/YmlFileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface YmlFileInterface extends FileInterface 16 | { 17 | /** 18 | * Get an array with the data in the yaml file. 19 | * 20 | * @return array parsed yaml file 21 | */ 22 | public function getYaml(); 23 | } 24 | -------------------------------------------------------------------------------- /src/Files/Type/CssFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class CssFile extends BaseFile implements CssFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_CSS; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/PHPFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class PHPFile extends BaseFile implements PHPFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_PHP; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/RoutingFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Tests\Type; 13 | 14 | class RoutingFile extends YmlFile implements RoutingFileInterface 15 | { 16 | /** 17 | * Get the file type for the specific file. 18 | * @return int 19 | */ 20 | function getFileType() 21 | { 22 | return Type::TYPE_YML | Type::TYPE_ROUTING; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Files/Type/ServiceFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Tests\Type; 13 | 14 | class ServiceFile extends YmlFile implements ServiceFileInterface 15 | { 16 | /** 17 | * Get the file type for the specific file. 18 | * @return int 19 | */ 20 | function getFileType() 21 | { 22 | return Type::TYPE_YML | Type::TYPE_SERVICE; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Files/Type/XmlFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class XmlFile extends BaseFile implements XmlFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_XML; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Cli.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv; 11 | 12 | use Phpbb\Epv\Command\ValidateCommand; 13 | use Symfony\Component\Console\Application; 14 | 15 | class Cli extends Application 16 | { 17 | 18 | protected function getDefaultCommands(): array 19 | { 20 | $commands = parent::getDefaultCommands(); 21 | $commands[] = new ValidateCommand(); 22 | 23 | return $commands; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/ComposerFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Tests\Type; 13 | 14 | class ComposerFile extends JsonFile implements ComposerFileInterface 15 | { 16 | /** 17 | * Get the file type for the specific file. 18 | * @return int 19 | */ 20 | function getFileType() 21 | { 22 | return Type::TYPE_COMPOSER | Type::TYPE_JSON; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Files/Type/HTMLFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class HTMLFile extends BaseFile implements HTMLFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_HTML; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/LockFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class LockFile extends BaseFile implements LockFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_LOCK; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/PlainFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class PlainFile extends BaseFile implements PlainFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_PLAIN; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/BinaryFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class BinaryFile extends BaseFile implements BinaryFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_BINARY; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/JavascriptFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class JavascriptFile extends BaseFile implements JavascriptFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_JS; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/LangFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class LangFile extends BaseFile implements LangFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_LANG | Type::TYPE_PHP; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Files/Type/MigrationFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class MigrationFile extends BaseFile implements MigrationFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @return int 20 | */ 21 | function getFileType() 22 | { 23 | return Type::TYPE_PHP | Type::TYPE_MIGRATION; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Files/Type/ImageFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class ImageFile extends BaseFile implements ImageFileInterface 16 | { 17 | /** 18 | * Get the file type for the specific file. 19 | * @todo Do we need a TYPE for images? 20 | * @return int 21 | */ 22 | function getFileType() 23 | { 24 | return Type::TYPE_BINARY; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Tests/Type.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests; 11 | 12 | 13 | class Type 14 | { 15 | 16 | const TYPE_COMPOSER = 1; 17 | const TYPE_HTML = 2; 18 | const TYPE_LANG = 4; 19 | const TYPE_PHP = 8; 20 | const TYPE_PLAIN = 16; 21 | const TYPE_SERVICE = 32; 22 | const TYPE_XML = 64; 23 | const TYPE_YML = 128; 24 | const TYPE_JSON = 256; 25 | const TYPE_BINARY = 512; 26 | const TYPE_CSS = 1024; 27 | const TYPE_JS = 2048; 28 | const TYPE_LOCK = 4096; 29 | const TYPE_ROUTING = 8192; 30 | const TYPE_MIGRATION = 16384; 31 | } 32 | -------------------------------------------------------------------------------- /tests/testFiles/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paul999/mention", 3 | "type": "phpbb-extension", 4 | "description": "Mention a user by using @USERNAME", 5 | "homepage": "https://www.phpbbextensions.io", 6 | "version": "1.0.1", 7 | "time": "2017-05-07", 8 | "license": "GPL-2.0-only", 9 | "authors": [ 10 | { 11 | "name": "paul999", 12 | "email": "paul@phpbb.com", 13 | "homepage": "https://www.phpbbextensions.io", 14 | "role": "developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "~5.4", 19 | "phpbb/phpbb": ">=3.2.0RC1,<3.3.x@dev" 20 | 21 | }, 22 | "extra": { 23 | "display-name": "Simple mentions", 24 | "soft-require": { 25 | "phpbb/phpbb": ">=3.2.0RC1,<3.3.0@dev" 26 | } 27 | }, 28 | "require-dev": { 29 | "phing/phing": "2.4.*" 30 | } 31 | } -------------------------------------------------------------------------------- /src/EPV.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 8 | * @license GNU General Public License, version 2 (GPL-2.0) 9 | * 10 | */ 11 | 12 | if (file_exists(__DIR__ . '/../vendor/autoload.php')) 13 | { 14 | require __DIR__ . '/../vendor/autoload.php'; 15 | } 16 | else if (file_exists(__DIR__ . '/../../../vendor/autoload.php')) 17 | { 18 | require __DIR__ . '/../../../vendor/autoload.php'; 19 | } 20 | else if (file_exists(__DIR__ . '/../../../../vendor/autoload.php')) 21 | { 22 | require __DIR__ . '/../../../../vendor/autoload.php'; 23 | } 24 | else 25 | { 26 | exit('Composer autoloading seems to be missing. Did you run composer.phar install?'); 27 | } 28 | 29 | $app = new Phpbb\Epv\Cli(); 30 | $app->run(); 31 | -------------------------------------------------------------------------------- /src/Files/FileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files; 11 | 12 | 13 | interface FileInterface 14 | { 15 | /** 16 | * Get the file type for a specific file. 17 | * @return int 18 | */ 19 | function getFileType(); 20 | 21 | /** 22 | * Get an array of lines for a specific file. 23 | * @return array 24 | */ 25 | function getLines(); 26 | 27 | /** 28 | * Get the filename for a file. 29 | * @return string 30 | */ 31 | function getFilename(); 32 | 33 | /* 34 | * Get the filename without the full path 35 | * @return string 36 | */ 37 | function getSaveFilename(); 38 | 39 | /** 40 | * Get the full file for a specific file. 41 | * @return string 42 | */ 43 | function getFile(); 44 | } 45 | -------------------------------------------------------------------------------- /src/Files/Type/JsonFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | use Phpbb\Epv\Files\BaseFile; 13 | use Phpbb\Epv\Tests\Type; 14 | 15 | class JsonFile extends BaseFile implements JsonFileInterface 16 | { 17 | /** @var array */ 18 | private $json; 19 | 20 | public function __construct($debug, $filename, $rundir) 21 | { 22 | parent::__construct($debug, $filename, $rundir); 23 | $this->json = json_decode($this->fileData, true); 24 | } 25 | 26 | /** 27 | * Get the file type for the specific file. 28 | * @return int 29 | */ 30 | function getFileType() 31 | { 32 | return Type::TYPE_JSON; 33 | } 34 | 35 | /** 36 | * @return mixed 37 | */ 38 | public function getJson() 39 | { 40 | return $this->json; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/testFiles/configs/badname3/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | foo.bar.controller: 3 | class: epv\test\controller\main 4 | arguments: 5 | - '@config' 6 | - '@controller.helper' 7 | - '@language' 8 | - '@request' 9 | - '@epv.test.helper.packager' 10 | - '@epv.test.helper.validator' 11 | - '@template' 12 | - '@user' 13 | 14 | foo.bar.helper.packager: 15 | class: epv\test\helper\packager 16 | arguments: 17 | - '@service_container' 18 | - '%core.root_path%' 19 | 20 | EPV.test.helper.validator: 21 | class: epv\test\helper\validator 22 | arguments: 23 | - '@language' 24 | 25 | epv.TEST.listener: 26 | class: epv\test\event\main_listener 27 | arguments: 28 | - '@controller.helper' 29 | - '@template' 30 | tags: 31 | - { name: event.listener } 32 | -------------------------------------------------------------------------------- /tests/testFiles/configs/simple/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | epv.test.controller: 3 | class: epv\test\controller\main 4 | arguments: 5 | - '@config' 6 | - '@controller.helper' 7 | - '@language' 8 | - '@request' 9 | - '@epv.test.helper.packager' 10 | - '@epv.test.helper.validator' 11 | - '@template' 12 | - '@user' 13 | 14 | epv.test.helper.packager: 15 | class: epv\test\helper\packager 16 | arguments: 17 | - '@service_container' 18 | - '%core.root_path%' 19 | 20 | epv.test.helper.validator: 21 | class: epv\test\helper\validator 22 | arguments: 23 | - '@language' 24 | 25 | epv.test.listener: 26 | class: epv\test\event\main_listener 27 | arguments: 28 | - '@controller.helper' 29 | - '@template' 30 | tags: 31 | - { name: event.listener } 32 | -------------------------------------------------------------------------------- /tests/testFiles/configs/badname1/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | phpbb.test.controller: 3 | class: epv\test\controller\main 4 | arguments: 5 | - '@config' 6 | - '@controller.helper' 7 | - '@language' 8 | - '@request' 9 | - '@epv.test.helper.packager' 10 | - '@epv.test.helper.validator' 11 | - '@template' 12 | - '@user' 13 | 14 | Phpbb.test.helper.packager: 15 | class: epv\test\helper\packager 16 | arguments: 17 | - '@service_container' 18 | - '%core.root_path%' 19 | 20 | phpbb.test.helper.validator: 21 | class: epv\test\helper\validator 22 | arguments: 23 | - '@language' 24 | 25 | phpbb.test.listener: 26 | class: epv\test\event\main_listener 27 | arguments: 28 | - '@controller.helper' 29 | - '@template' 30 | tags: 31 | - { name: event.listener } 32 | -------------------------------------------------------------------------------- /tests/testFiles/configs/badname2/services.yml: -------------------------------------------------------------------------------- 1 | services: 2 | core.test.controller: 3 | class: epv\test\controller\main 4 | arguments: 5 | - '@config' 6 | - '@controller.helper' 7 | - '@language' 8 | - '@request' 9 | - '@epv.test.helper.packager' 10 | - '@epv.test.helper.validator' 11 | - '@template' 12 | - '@user' 13 | 14 | Core.test.helper.packager: 15 | class: epv\test\helper\packager 16 | arguments: 17 | - '@service_container' 18 | - '%core.root_path%' 19 | 20 | core.test.helper.validator: 21 | class: epv\test\helper\validator 22 | arguments: 23 | - '@language' 24 | 25 | core.test.listener: 26 | class: epv\test\event\main_listener 27 | arguments: 28 | - '@controller.helper' 29 | - '@template' 30 | tags: 31 | - { name: event.listener } 32 | -------------------------------------------------------------------------------- /tests/events/invalid_name_short_single.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.PostsMerging.posts_merging_end', compact($vars))); 19 | -------------------------------------------------------------------------------- /tests/events/valid_name_long_single.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.postsmerging.posts_merging_end', compact($vars))); 19 | -------------------------------------------------------------------------------- /tests/events/valid_name_short_single.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.postsmerging.posts_merging_end', compact($vars))); 19 | -------------------------------------------------------------------------------- /tests/events/invalid_name_long_single.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.PostsMerging.posts_merging_end', compact($vars))); 19 | -------------------------------------------------------------------------------- /tests/events/invalid_name_short_multi.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.PostsMerging.posts_merging_end', compact($vars))); 29 | -------------------------------------------------------------------------------- /tests/events/valid_name_short_multi.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.postsmerging.posts_merging_end', compact($vars))); 29 | -------------------------------------------------------------------------------- /tests/events/invalid_name_long_multi.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.PostsMerging.posts_merging_end', compact($vars))); 29 | -------------------------------------------------------------------------------- /tests/events/valid_name_long_multi.php: -------------------------------------------------------------------------------- 1 | trigger_event('rxu.postsmerging.posts_merging_end', compact($vars))); 29 | -------------------------------------------------------------------------------- /src/Files/Line.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files; 11 | 12 | 13 | class Line implements LineInterface 14 | { 15 | private $file; 16 | private $lineNr; 17 | private $line; 18 | 19 | /** 20 | * @param FileInterface $file 21 | * @param $lineNr 22 | * @param $line 23 | */ 24 | public function __construct(FileInterface $file, $lineNr, $line) 25 | { 26 | $this->file = $file; 27 | $this->lineNr = $lineNr; 28 | $this->line = $line; 29 | } 30 | 31 | /** 32 | * Get the file for this specific line. 33 | * @return FileInterface 34 | */ 35 | public function getFile() 36 | { 37 | return $this->file; 38 | } 39 | 40 | /** 41 | * Get the line number. 42 | * @return int 43 | */ 44 | public function getLineNr() 45 | { 46 | return $this->lineNr; 47 | } 48 | 49 | /** 50 | * Get the actual code for this line. 51 | * @return string 52 | */ 53 | public function getLine() 54 | { 55 | return $this->getLine(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phpbb/epv", 3 | "description": "A extension validator for phpBB extensions. Extensions are required to pass the validator when submitted to the extension database.", 4 | "license": "GPL-2.0-only", 5 | "authors": [ 6 | { 7 | "name": "Paul Sohier", 8 | "email": "paul@phpbb.com" 9 | } 10 | ], 11 | "minimum-stability": "stable", 12 | "require": { 13 | "php": ">=7.2", 14 | "ext-json": "*", 15 | "symfony/yaml": "^3.0 || ^4.0 || ^5.0 || ^6.0", 16 | "symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0", 17 | "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0", 18 | "symfony/process": "^3.0 || ^4.0 || ^5.0 || ^6.0", 19 | "nikic/php-parser": "^4.0", 20 | "gitonomy/gitlib": "^1.3.0", 21 | "sensiolabs/ansi-to-html": "~1.1", 22 | "composer/composer": "^1.5 || ^2.0" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 26 | }, 27 | "bin": [ 28 | "src/EPV.php" 29 | ], 30 | "config": { 31 | "bin-dir": "bin" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Phpbb\\Epv\\": "src/" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/validate_sql_queries_test.php: -------------------------------------------------------------------------------- 1 | 14 | * @license GNU General Public License, version 2 (GPL-2.0) 15 | * 16 | */ 17 | 18 | class validate_sql_queries_test extends TestCase 19 | { 20 | public static function setUpBeforeClass(): void 21 | { 22 | require_once('./tests/Mock/Output.php'); 23 | } 24 | 25 | public function test_insecure_sql_query() { 26 | $output = $this->createMock(OutputInterface::class); 27 | $output->expects(self::once()) 28 | ->method('addMessage') 29 | ->with(OutputInterface::WARNING, 'Found potential SQL injection on line 5 in tests/testFiles/sql_injection.php') 30 | ; 31 | 32 | $file_loader = new FileLoader(new Output(), false, '.', '.'); 33 | $file = $file_loader->loadFile('tests/testFiles/sql_injection.php'); 34 | 35 | $tester = new epv_test_validate_sql_queries(false, $output, '/a/b/', 'epv/test', false, '/a/'); 36 | $tester->validateFile($file); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Tests/ArrayKeyVisitor.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests; 11 | 12 | use PhpParser\Node; 13 | use PhpParser\Node\Expr\Array_; 14 | use PhpParser\Node\Expr\ArrayItem; 15 | use PhpParser\Node\Scalar\String_; 16 | use PhpParser\NodeVisitorAbstract; 17 | 18 | class ArrayKeyVisitor extends NodeVisitorAbstract 19 | { 20 | /** 21 | * @var array 22 | */ 23 | private $keys; 24 | 25 | /** 26 | * @param array $nodes 27 | * @return void|null|Node[] 28 | */ 29 | public function beforeTraverse(array $nodes) 30 | { 31 | $this->keys = []; 32 | } 33 | 34 | /** 35 | * @param Node $node 36 | * @return void|null|int|Node 37 | */ 38 | public function enterNode(Node $node) 39 | { 40 | if ($node instanceof Array_) 41 | { 42 | foreach ($node->items as $item) 43 | { 44 | /** @var ArrayItem $item */ 45 | if ($item instanceof ArrayItem && $item->key instanceof String_) 46 | { 47 | $this->keys[] = $item->key->value; 48 | } 49 | } 50 | } 51 | } 52 | 53 | /** 54 | * @return array 55 | */ 56 | public function get_array_keys() 57 | { 58 | return $this->keys; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/validate_composer_test.php: -------------------------------------------------------------------------------- 1 | 15 | * @license GNU General Public License, version 2 (GPL-2.0) 16 | * 17 | */ 18 | 19 | class validate_composer_test extends TestCase 20 | { 21 | public static function setUpBeforeClass(): void 22 | { 23 | require_once('./tests/Mock/Output.php'); 24 | } 25 | 26 | public function test_composer_test() { 27 | $output = $this->createMock(OutputInterface::class); 28 | $output->expects(self::atLeastOnce()) 29 | ->method('addMessage') 30 | ->with(OutputInterface::FATAL, 'Composer validation: require.phpbb/phpbb : invalid version constraint (Could not parse version constraint <3.3.x: Invalid version string "3.3.x")') 31 | ; 32 | 33 | $file_loader = new FileLoader(new Output(), false, '.', '.'); 34 | $file = $file_loader->loadFile('tests/testFiles/composer.json'); 35 | self::assertInstanceOf(ComposerFile::class, $file); 36 | 37 | $tester = new epv_test_validate_composer(false, $output, '/a/b/', 'epv/test', false, '/a/'); 38 | $tester->validateFile($file); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Output/OutputFormatter.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Output; 11 | 12 | use Symfony\Component\Console\Formatter\OutputFormatterStyle; 13 | 14 | class OutputFormatter extends \Symfony\Component\Console\Formatter\OutputFormatter 15 | { 16 | public function __construct($decorated = false, array $styles = array()) 17 | { 18 | parent::__construct($decorated, array_merge($styles, array( 19 | 'success' => new OutputFormatterStyle('black', 'green'), 20 | 'notice' => new OutputFormatterStyle('cyan'), 21 | 'noticebg' => new OutputFormatterStyle('black', 'cyan'), 22 | 'warning' => new OutputFormatterStyle('yellow'), 23 | 'error' => new OutputFormatterStyle('red'), 24 | 'fatal' => new OutputFormatterStyle('white', 'red'), 25 | 26 | 'successb' => new OutputFormatterStyle('black', 'green', array('bold')), 27 | 'noticeb' => new OutputFormatterStyle('cyan', null, array('bold')), 28 | 'noticebgb' => new OutputFormatterStyle('black', 'cyan', array('bold')), 29 | 'warningb' => new OutputFormatterStyle('yellow', null, array('bold')), 30 | 'errorb' => new OutputFormatterStyle('red', null, array('bold')), 31 | 'fatalb' => new OutputFormatterStyle('white', 'red', array('bold')), 32 | 33 | ))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Output/Message.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Output; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | class Message 16 | { 17 | private $type; 18 | private $message; 19 | /** 20 | * @var \Phpbb\Epv\Files\FileInterface 21 | */ 22 | private $file; 23 | 24 | /** 25 | * @param $type int Type message 26 | * @param $message string Message 27 | * @param \Phpbb\Epv\Files\FileInterface $file 28 | */ 29 | public function __construct($type, $message, ?FileInterface $file = null) 30 | { 31 | $this->type = $type; 32 | $this->message = $message; 33 | $this->file = $file; 34 | } 35 | 36 | public function __toString() 37 | { 38 | $file = ''; 39 | 40 | if ($this->file != null) 41 | { 42 | $file = ' in ' . $this->file->getSaveFilename(); 43 | } 44 | 45 | switch ($this->type) 46 | { 47 | case Output::NOTICE: 48 | return "Notice: $this->message{$file}"; 49 | case Output::WARNING: 50 | return "Warning: $this->message{$file}"; 51 | case Output::ERROR: 52 | return "Error: $this->message{$file}"; 53 | case Output::FATAL: 54 | return "Fatal error: $this->message{$file}"; 55 | case Output::DEBUG: 56 | return $this->message; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | php-tests: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | include: 17 | - php: '7.2' 18 | - php: '7.3' 19 | - php: '7.4' 20 | - php: '8.0' 21 | - php: '8.1' 22 | - php: '8.2' 23 | - php: '8.3' 24 | - php: '8.4' 25 | 26 | name: PHP ${{ matrix.php }} 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | 32 | - name: Setup PHP 33 | uses: shivammathur/setup-php@v2 34 | with: 35 | php-version: ${{ matrix.php }} 36 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, intl, iconv 37 | coverage: none 38 | 39 | - name: Cache Composer dependencies 40 | uses: actions/cache@v4 41 | with: 42 | path: /tmp/composer-cache 43 | key: ${{ runner.os }}-${{ hashFiles('**/composer.lock') }} 44 | 45 | - name: Install Composer dependencies 46 | uses: php-actions/composer@v6 47 | with: 48 | php_version: ${{ matrix.php }} 49 | args: --prefer-source 50 | 51 | - name: Run tests 52 | run: | 53 | bin/phpunit 54 | src/EPV.php run --debug --github="phpbb/phpbb-ext-acme-demo" 55 | -------------------------------------------------------------------------------- /src/Output/OutputInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Output; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | 15 | interface OutputInterface extends \Symfony\Component\Console\Output\OutputInterface 16 | { 17 | const DEBUG = 5; 18 | const FATAL = 4; 19 | const WARNING = 3; 20 | const ERROR = 2; 21 | const NOTICE = 1; 22 | 23 | /** 24 | * Write a message to the output, but only if Debug is enabled. 25 | * 26 | * @param $message string|array $messages The message as an array of lines of a single string 27 | * 28 | * @throws \InvalidArgumentException When unknown output type is given 29 | */ 30 | public function writelnIfDebug($message); 31 | 32 | /** 33 | * Add a new message to the output of the validator. 34 | * 35 | * @param $type int message type 36 | * @param $message string message 37 | * @param \Phpbb\Epv\Files\FileInterface $file File the error happened in. When provided, this is displayed to the user 38 | * 39 | * @return 40 | */ 41 | public function addMessage($type, $message, ?FileInterface $file = null); 42 | 43 | 44 | /** 45 | * Get all messages saved into the message queue. 46 | * @return array Array with messages 47 | */ 48 | public function getMessages(); 49 | 50 | /** 51 | * Get the amount of messages that were fatal. 52 | * @return int 53 | */ 54 | public function getFatalCount(); 55 | 56 | /** 57 | * Get the count for a type; 58 | * 59 | * @param $type 60 | * 61 | * @return mixed 62 | */ 63 | public function getMessageCount($type); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/Tests/TestInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Phpbb\Epv\Files\LineInterface; 15 | 16 | interface TestInterface 17 | { 18 | /** 19 | * Validate a line in a specific file. 20 | * This method is only called if doValidateLine returns true. 21 | * 22 | * @param \Phpbb\Epv\Files\LineInterface $line Line to validate 23 | * 24 | * @return 25 | */ 26 | public function validateLine(LineInterface $line); 27 | 28 | /** 29 | * Validate a full file. 30 | * This method is only called if doValidateFile returns true. 31 | * 32 | * @param \Phpbb\Epv\Files\FileInterface $file 33 | * 34 | * @return 35 | */ 36 | public function validateFile(FileInterface $file); 37 | 38 | /** 39 | * Validate the directory listing. 40 | * This method is only called if doValidateDirectory returns true. 41 | * 42 | * @param array $dirListing 43 | * 44 | * @return mixed 45 | */ 46 | public function validateDirectory(array $dirListing); 47 | 48 | /** 49 | * Check if this test should be run for the directory listing. 50 | * @return boolean 51 | */ 52 | public function doValidateDirectory(); 53 | 54 | /** 55 | * Check if this test should be run for each line. 56 | * 57 | * @param $type int Filetype 58 | * 59 | * @return boolean 60 | */ 61 | public function doValidateLine($type); 62 | 63 | /** 64 | * Check if this test should be run for the complete file 65 | * 66 | * @param $type int Filetype 67 | * 68 | * @return boolean 69 | */ 70 | public function doValidateFile($type); 71 | 72 | 73 | /** 74 | * 75 | * @return string 76 | */ 77 | public function testName(); 78 | 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Extension Pre-Validator 2 | 3 | [![Build Status](https://github.com/phpbb/epv/actions/workflows/tests.yml/badge.svg)](https://github.com/phpbb/epv/actions) 4 | 5 | This repository contains the extension pre-validator, used for pre validating extensions when submittion to the database at phpBB.com. 6 | 7 | Please note that EPV requires at least PHP 7.2 8 | 9 | ## Using EPV 10 | 11 | 1. Clone your fork of this repository. 12 | 2. Install composer dependencies: 13 | ```sh 14 | $ php composer.phar install 15 | ``` 16 | 3. Run EPV on a phpBB extension from the CLI: 17 | ```sh 18 | # Run EPV on a Git repository (at any repository hosting site) 19 | php src/EPV.php run --git="https://github.com/repo-org/repo-name.git" 20 | 21 | # Run EPV on a GitHub repository 22 | php src/EPV.php run --github="repo-org/repo-name" 23 | 24 | # Run EPV on a local directory 25 | php src/EPV.php run --dir="/path/to/extension" 26 | ``` 27 | 28 | > The `--branch` option can target a specific branch of a repository. 29 | > 30 | > The `--debug` option will output additional debugging information. 31 | 32 | You can also use EPV online at [phpBB.com](https://www.phpbb.com/extensions/epv/) 33 | 34 | phpBB's Customisation Database (Titania) will run EPV on any submissions at phpBB.com as well. 35 | 36 | ## Maintenance and contributing 37 | 38 | To contribute fork the repo, make your changes in a feature branch and send a pull request. 39 | 40 | The site is maintained by the [phpBB Extensions Team](https://www.phpbb.com/community/memberlist.php?mode=group&g=7331) 41 | 42 | Should you wish to report a bug report it at [Issue tracker](https://github.com/phpbb/epv/issues) 43 | 44 | ## License 45 | [GNU General Public License v2](https://opensource.org/licenses/GPL-2.0) 46 | 47 | By contributing you agree to assign copyright of your code to phpBB Limited. 48 | 49 | See `LICENSE` for the full license. 50 | -------------------------------------------------------------------------------- /src/Files/BaseFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files; 11 | 12 | 13 | use Phpbb\Epv\Files\Exception\FileException; 14 | 15 | abstract class BaseFile implements FileInterface 16 | { 17 | 18 | protected $fileName; 19 | protected $fileData; 20 | protected $fileArray; 21 | protected $debug; 22 | protected $basedir; 23 | 24 | /** 25 | * @param $debug boolean Debug Mode 26 | * @param $fileName string filename for this file 27 | * @param $basedir string namespace for the extension. 28 | * 29 | * @throws FileException 30 | */ 31 | public function __construct($debug, $fileName, $basedir) 32 | { 33 | if (!file_exists($fileName)) 34 | { 35 | throw new FileException(sprintf("File (%s) could not be found", $fileName)); 36 | } 37 | $this->debug = $debug; 38 | $this->fileName = $fileName; 39 | $this->fileData = @file_get_contents($this->fileName); 40 | $this->basedir = $basedir; 41 | 42 | if ($this->fileData === false) 43 | { 44 | throw new FileException("Unable to read file {$fileName}."); 45 | } 46 | $this->fileArray = explode("\n", $this->fileData); 47 | } 48 | 49 | /** 50 | * @return array 51 | */ 52 | public function getLines() 53 | { 54 | return $this->fileArray; 55 | } 56 | 57 | /** 58 | * Get the filename for this file. 59 | * @return string 60 | */ 61 | public function getFilename() 62 | { 63 | return $this->fileName; 64 | } 65 | 66 | /* 67 | * Get the filename without the full path 68 | * @return string 69 | */ 70 | public function getSaveFilename() 71 | { 72 | $filename = $this->fileName; 73 | 74 | if (0 === strpos($filename, $this->basedir)) 75 | { 76 | $filename = substr($filename, strlen($this->basedir)); 77 | } 78 | 79 | return $filename; 80 | } 81 | 82 | /** 83 | * Get the filedata 84 | * @return string 85 | */ 86 | public function getFile() 87 | { 88 | return $this->fileData; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/validate_languages_test.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GNU General Public License, version 2 (GPL-2.0) 9 | * 10 | */ 11 | 12 | use Phpbb\Epv\Output\OutputInterface; 13 | use Phpbb\Epv\Tests\Tests\epv_test_validate_languages; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | class validate_languages_test extends TestCase 17 | { 18 | public static function setUpBeforeClass(): void 19 | { 20 | require_once('./tests/Mock/Output.php'); 21 | } 22 | 23 | public function test_languages() { 24 | /** @var OutputInterface|PHPUnit\Framework\MockObject\MockObject\MockObject $output */ 25 | $output = $this->createMock(OutputInterface::class); 26 | 27 | $output 28 | ->expects(self::exactly(2)) 29 | ->method('addMessage') 30 | ->withConsecutive( 31 | [OutputInterface::NOTICE, 'Language en_incomplete is missing the language file additional.php'], 32 | [OutputInterface::WARNING, 'Language file en_incomplete/common.php is missing the language key B'] 33 | ) 34 | ; 35 | 36 | $tester = new epv_test_validate_languages(false, $output, 'tests/testFiles/', 'epv/test', false, 'tests/testFiles/'); 37 | $tester->validateDirectory([ 38 | 'tests/testFiles/language/en/common.php', 39 | 'tests/testFiles/language/en/additional.php', 40 | 'tests/testFiles/language/en_complete/common.php', 41 | 'tests/testFiles/language/en_complete/additional.php', 42 | 'tests/testFiles/language/en_incomplete/common.php', 43 | ]); 44 | } 45 | 46 | public function test_missing_en_languages() { 47 | /** @var OutputInterface|PHPUnit\Framework\MockObject\MockObject\MockObject $output */ 48 | $output = $this->createMock(OutputInterface::class); 49 | 50 | $output 51 | ->expects(self::once()) 52 | ->method('addMessage') 53 | ->with(OutputInterface::FATAL, 'English language pack is missing') 54 | ; 55 | 56 | $tester = new epv_test_validate_languages(false, $output, 'tests/testFiles/', 'epv/test', false, 'tests/testFiles/'); 57 | $tester->validateDirectory([ 58 | 'tests/testFiles/language/en_us/common.php', 59 | 'tests/testFiles/language/en_us/additional.php', 60 | ]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/validate_license_test.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GNU General Public License, version 2 (GPL-2.0) 9 | * 10 | */ 11 | 12 | use Phpbb\Epv\Output\OutputInterface; 13 | use Phpbb\Epv\Tests\Tests\epv_test_validate_directory_structure; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | class validate_license_test extends TestCase 17 | { 18 | /** 19 | * @param string $license 20 | * @param callable $configure 21 | */ 22 | private function validateLicense($license, $configure) { 23 | /** @var OutputInterface $output */ 24 | $output = $this->createMock(OutputInterface::class); 25 | $configure($output); 26 | 27 | $tester = new epv_test_validate_directory_structure(false, $output, 'tests/testFiles/', 'epv/test', false, 'tests/testFiles/'); 28 | $tester->validateDirectory([ 29 | 'tests/testFiles/licenses/' . $license . '/license.txt', 30 | ]); 31 | } 32 | 33 | public function test_license_gpl_2_0_skeleton() 34 | { 35 | $this->validateLicense('gpl-2.0-skeleton-ext', function($output) 36 | { 37 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 38 | $output 39 | ->expects($this->never()) 40 | ->method('addMessage') 41 | ; 42 | }); 43 | } 44 | 45 | public function test_license_gpl_2_0_with_appendix() 46 | { 47 | $this->validateLicense('gpl-2.0-with-appendix', function($output) 48 | { 49 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 50 | $output 51 | ->expects($this->never()) 52 | ->method('addMessage') 53 | ; 54 | }); 55 | } 56 | 57 | public function test_license_gpl_3_0() 58 | { 59 | $this->validateLicense('gpl-3.0', function($output) 60 | { 61 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 62 | $output 63 | ->expects($this->once()) 64 | ->method('addMessage') 65 | ->with(OutputInterface::WARNING) 66 | ; 67 | }); 68 | } 69 | 70 | public function test_license_apache_2_0() 71 | { 72 | $this->validateLicense('apache-2.0', function($output) 73 | { 74 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 75 | $output 76 | ->expects($this->once()) 77 | ->method('addMessage') 78 | ->with(OutputInterface::WARNING) 79 | ; 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/validate_revert_schema_test.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GNU General Public License, version 2 (GPL-2.0) 9 | * 10 | */ 11 | 12 | use Phpbb\Epv\Files\FileLoader; 13 | use Phpbb\Epv\Output\OutputInterface; 14 | use Phpbb\Epv\Tests\Mock\Output; 15 | use Phpbb\Epv\Tests\Tests\epv_test_validate_revert_schema; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | class validate_revert_schema_test extends TestCase 19 | { 20 | public static function setUpBeforeClass(): void 21 | { 22 | require_once('./tests/Mock/Output.php'); 23 | } 24 | 25 | /** 26 | * @param string $file 27 | * @param callable $configure 28 | */ 29 | private function validateFile($file, $configure) { 30 | /** @var OutputInterface $output */ 31 | $output = $this->createMock(OutputInterface::class); 32 | $configure($output); 33 | 34 | $file_loader = new FileLoader(new Output(), false, '.', '.'); 35 | $file = $file_loader->loadFile($file); 36 | 37 | $tester = new epv_test_validate_revert_schema(false, $output, '/a/b/', 'epv/test', false, '/a/'); 38 | $tester->validateFile($file); 39 | } 40 | 41 | public function test_missing_update_schema() 42 | { 43 | $this->validateFile('tests/testFiles/migrations/missing_update_schema.php', function($output) 44 | { 45 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 46 | $output->expects($this->never()) 47 | ->method('addMessage'); 48 | }); 49 | } 50 | 51 | public function test_existing_revert_schema() 52 | { 53 | $this->validateFile('tests/testFiles/migrations/existing_revert_schema.php', function($output) 54 | { 55 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 56 | $output->expects($this->never()) 57 | ->method('addMessage'); 58 | }); 59 | } 60 | 61 | public function test_missing_revert_schema() 62 | { 63 | $this->validateFile('tests/testFiles/migrations/missing_revert_schema.php', function($output) 64 | { 65 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 66 | $output->expects($this->once()) 67 | ->method('addMessage') 68 | ->with(OutputInterface::ERROR, 'Migration file tests/testFiles/migrations/missing_revert_schema.php is missing the revert_schema() method.'); 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Events/recursive_event_filter_iterator.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | 11 | namespace Phpbb\Epv\Events; 12 | 13 | /** 14 | * This filter ignores directories and files starting with a dot. 15 | * It also skips some directories that do not contain events anyway, 16 | * such as e.g. files/, store/ and vendor/ 17 | */ 18 | class recursive_event_filter_iterator extends \RecursiveFilterIterator 19 | { 20 | protected $root_path; 21 | 22 | /** 23 | * Construct 24 | * 25 | * @param \RecursiveIterator $iterator 26 | * @param string $root_path 27 | */ 28 | public function __construct(\RecursiveIterator $iterator, $root_path) 29 | { 30 | $this->root_path = str_replace(DIRECTORY_SEPARATOR, '/', $root_path); 31 | parent::__construct($iterator); 32 | } 33 | 34 | /** 35 | * Return the inner iterator's children contained in a recursive_event_filter_iterator 36 | * 37 | * @return recursive_event_filter_iterator 38 | */ 39 | public function getChildren() 40 | { 41 | return new self($this->getInnerIterator()->getChildren(), $this->root_path); 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | public function accept() 48 | { 49 | $relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $this->current()); 50 | $filename = $this->current()->getFilename(); 51 | 52 | return (substr($relative_path, -4) === '.php' || $this->current()->isDir()) 53 | && $filename[0] !== '.' 54 | && strpos($relative_path, $this->root_path . 'cache/') !== 0 55 | && strpos($relative_path, $this->root_path . 'develop/') !== 0 56 | && strpos($relative_path, $this->root_path . 'docs/') !== 0 57 | && strpos($relative_path, $this->root_path . 'ext/') !== 0 58 | && strpos($relative_path, $this->root_path . 'files/') !== 0 59 | && strpos($relative_path, $this->root_path . 'includes/utf/') !== 0 60 | && strpos($relative_path, $this->root_path . 'language/') !== 0 61 | && strpos($relative_path, $this->root_path . 'phpbb/db/migration/data/') !== 0 62 | && strpos($relative_path, $this->root_path . 'phpbb/event/') !== 0 63 | && strpos($relative_path, $this->root_path . 'store/') !== 0 64 | && strpos($relative_path, $this->root_path . 'tests/') !== 0 65 | && strpos($relative_path, $this->root_path . 'vendor/') !== 0; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_event_names.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Tests; 11 | 12 | use Phpbb\Epv\Events\php_exporter; 13 | use Phpbb\Epv\Output\Output; 14 | use Phpbb\Epv\Output\OutputInterface; 15 | use Phpbb\Epv\Tests\BaseTest; 16 | 17 | class epv_test_validate_event_names extends BaseTest 18 | { 19 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 20 | { 21 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 22 | 23 | $this->directory = true; 24 | } 25 | 26 | public function validateDirectory(array $dirList) 27 | { 28 | $exporter = new php_exporter($this->output, $this->opendir); 29 | 30 | foreach ($dirList as $file) 31 | { 32 | try 33 | { 34 | if (substr($file, -4) === '.php') 35 | { 36 | $exporter->crawl_php_file($file); 37 | } 38 | } 39 | catch 40 | (\LogicException $e) 41 | { 42 | $this->output->addMessage(Output::FATAL, $e->getMessage()); 43 | } 44 | } 45 | 46 | $events = $exporter->get_events(); 47 | // event names are required to be lowercase 48 | // event names should end with a dot to separate the vendor.name and the actual event name. 49 | $vendor = strtolower(str_replace('/', '.', $this->namespace)) . '.'; 50 | 51 | foreach ($events as $event) 52 | { 53 | $event['file'] = str_replace($this->basedir, '', $event['file']); 54 | if (0 === stripos($event['event'], 'phpbb.')) 55 | { 56 | $this->output->addMessage(Output::ERROR, sprintf('The phpbb vendorname should only be used for official extensions in event names in %s. Current event name: %s', $event['file'], $event['event'])); 57 | } 58 | else if (0 === stripos($event['event'], 'core.')) 59 | { 60 | $this->output->addMessage(Output::FATAL, sprintf('The core vendorname should not be used in event names in %s. Current event name: %s', $event['file'], $event['event'])); 61 | } 62 | 63 | $substr = substr($event['event'], 0, strlen($vendor)); 64 | if ($substr != $vendor) 65 | { 66 | $this->output->addMessage(Output::NOTICE, sprintf('The event name should start with vendor.namespace (Which is %s) but started with %s in %s', $vendor, $substr, $event['file'])); 67 | } 68 | } 69 | } 70 | 71 | public function testName() 72 | { 73 | return 'Test event names'; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_linefeeds.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Tests; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Phpbb\Epv\Files\Type\PHPFileInterface; 15 | use Phpbb\Epv\Output\Output; 16 | use Phpbb\Epv\Output\OutputInterface; 17 | use Phpbb\Epv\Tests\BaseTest; 18 | use Phpbb\Epv\Tests\Exception\TestException; 19 | use Phpbb\Epv\Tests\Type; 20 | 21 | 22 | class epv_test_validate_linefeeds extends BaseTest 23 | { 24 | /** 25 | * @param bool $debug if debug is enabled 26 | * @param OutputInterface $output 27 | * @param string $basedir 28 | * @param string $namespace 29 | * @param boolean $titania 30 | * @param string $opendir 31 | */ 32 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 33 | { 34 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 35 | 36 | $this->fileTypeFull = Type::TYPE_PHP; 37 | } 38 | 39 | /** 40 | * @param FileInterface $file 41 | * 42 | * @throws \Phpbb\Epv\Tests\Exception\TestException 43 | */ 44 | public function validateFile(FileInterface $file) 45 | { 46 | if (!$file instanceof PHPFileInterface) 47 | { 48 | throw new TestException('This test expects a php type, but found something else.'); 49 | } 50 | $this->file = $file; 51 | 52 | $eols = array_count_values(str_split(preg_replace("/[^\r\n]/", "", $file->getFile()))); 53 | $eola = array_keys($eols, max($eols)); 54 | $eol = implode("", $eola); 55 | 56 | if ($eol == "\n") { 57 | // Everything is good to go 58 | return; 59 | } 60 | if ($eol == "\r\n") { 61 | $this->addMessage(Output::FATAL, "Detected windows style newlines instead of UNIX newlines"); 62 | } 63 | if ($eol == "\r") { 64 | $this->addMessage(Output::FATAL, "Detected carriage return instead of UNIX newlines"); 65 | } 66 | } 67 | 68 | 69 | /** 70 | * Add a new Message to Messages. 71 | * The filename is automatically added. 72 | * 73 | * @param $type 74 | * @param $message 75 | */ 76 | private function addMessage($type, $message) 77 | { 78 | $this->output->addMessage($type, sprintf("%s in %s", $message, $this->file->getSaveFilename())); 79 | } 80 | 81 | /** 82 | * 83 | * @return String 84 | */ 85 | public function testName() 86 | { 87 | return 'Validate linefeeds'; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Command/ValidateCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Command; 11 | 12 | use Phpbb\Epv\Output\Output; 13 | use Phpbb\Epv\Output\OutputFormatter; 14 | use Phpbb\Epv\Tests\Exception\TestException; 15 | use Phpbb\Epv\Tests\TestStartup; 16 | use Symfony\Component\Console\Command\Command; 17 | use Symfony\Component\Console\Input\InputArgument; 18 | use Symfony\Component\Console\Input\InputInterface; 19 | use Symfony\Component\Console\Input\InputOption; 20 | use Symfony\Component\Console\Output\OutputInterface; 21 | 22 | 23 | class ValidateCommand extends Command 24 | { 25 | 26 | protected function configure() 27 | { 28 | $this 29 | ->setName('run') 30 | ->setDescription('Run the Extension Pre Validator on your extension.') 31 | //->addArgument('dir', InputArgument::OPTIONAL, 'The directory the extension is in.') 32 | //->addArgument('git', InputArgument::OPTIONAL, 'A git repository with the extension.') 33 | ->addOption('dir', null, InputOption::VALUE_OPTIONAL, 'The directory the extension is in.') 34 | ->addOption('git', null, InputOption::VALUE_OPTIONAL, 'A git repository with the extension.') 35 | ->addOption('github', null, InputOption::VALUE_OPTIONAL, 'Shortname (like phpbb/phpbb) to github with the extension.') 36 | ->addOption('branch', null, InputOption::VALUE_OPTIONAL, 'A branch for the git repo') 37 | ->addOption('debug', null, InputOption::VALUE_NONE, "Run in debug"); 38 | } 39 | 40 | protected function execute(InputInterface $input, OutputInterface $output) 41 | { 42 | $dir = $input->getOption("dir"); 43 | $git = $input->getOption('git'); 44 | $github = $input->getOption('github'); 45 | $branch = $input->getOption('branch'); 46 | 47 | if (!empty($github)) 48 | { 49 | $type = TestStartup::TYPE_GITHUB; 50 | $loc = $github; 51 | } 52 | else if (!empty($git)) 53 | { 54 | $type = TestStartup::TYPE_GIT; 55 | $loc = $git; 56 | } 57 | else if (!empty($dir)) 58 | { 59 | $type = TestStartup::TYPE_DIRECTORY; 60 | $loc = $dir; 61 | } 62 | else 63 | { 64 | throw new TestException("Or the git or the dir parameter are required"); 65 | } 66 | 67 | $debug = $input->getOption("debug"); 68 | 69 | $output = new Output($output, $debug); 70 | $output->setFormatter(new OutputFormatter(true)); 71 | 72 | new TestStartup($output, $type, $loc, $debug, $branch); 73 | 74 | if ($output->getFatalCount() > 0) 75 | { 76 | return 1; 77 | } 78 | 79 | return 0; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/validate_service_test.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | 11 | use Phpbb\Epv\Files\FileLoader; 12 | use Phpbb\Epv\Files\Type\ServiceFile; 13 | use Phpbb\Epv\Tests\Mock\Output; 14 | use Phpbb\Epv\Output\OutputInterface; 15 | use Phpbb\Epv\Tests\Tests\epv_test_validate_service; 16 | use PHPUnit\Framework\TestCase; 17 | 18 | class validate_service_test extends TestCase 19 | { 20 | public static function setUpBeforeClass(): void 21 | { 22 | require_once('./tests/Mock/Output.php'); 23 | } 24 | 25 | /** 26 | * @param string $config The config dir name 27 | * @param callable $configure Call to validation function 28 | */ 29 | private function validateConfig($config, $configure) 30 | { 31 | /** @var OutputInterface $output */ 32 | $output = $this->createMock(OutputInterface::class); 33 | $configure($output); 34 | 35 | $file_loader = new FileLoader(new Output(), false, '.', '.'); 36 | $file = $file_loader->loadFile('tests/testFiles/configs/' . $config . '/services.yml'); 37 | self::assertInstanceOf(ServiceFile::class, $file); 38 | 39 | $tester = new epv_test_validate_service(false, $output, 'tests/testFiles/', 'epv/test', false, 'tests/testFiles/'); 40 | $tester->validateFile($file); 41 | } 42 | 43 | public function good_service_names_data() 44 | { 45 | return [ 46 | ['simple'], 47 | ['autowired'], 48 | ]; 49 | } 50 | 51 | /** 52 | * @dataProvider good_service_names_data 53 | */ 54 | public function test_good_service_names($config) 55 | { 56 | $this->validateConfig($config, function($output) 57 | { 58 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 59 | $output 60 | ->expects($this->never()) 61 | ->method('addMessage') 62 | ; 63 | }); 64 | } 65 | 66 | public function bad_service_names_data() 67 | { 68 | return [ 69 | ['badname1', OutputInterface::ERROR], // service name starts with phpbb. 70 | ['badname2', OutputInterface::FATAL], // service name starts with core. 71 | ['badname3', OutputInterface::WARNING], // service name does not match vendor.package 72 | ]; 73 | } 74 | 75 | /** 76 | * @dataProvider bad_service_names_data 77 | */ 78 | public function test_bad_service_names($config, $expected) 79 | { 80 | $this->validateConfig($config, function($output) use ($expected) { 81 | /** @var PHPUnit\Framework\MockObject\MockObject $output */ 82 | $output 83 | ->expects($this->exactly(4)) 84 | ->method('addMessage') 85 | ->with($expected) 86 | ; 87 | }); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Files/Type/YmlFile.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files\Type; 11 | 12 | 13 | use Phpbb\Epv\Files\BaseFile; 14 | use Phpbb\Epv\Files\Exception\FileLoadException; 15 | use Phpbb\Epv\Tests\Type; 16 | use Symfony\Component\Yaml\Exception\ParseException; 17 | use Symfony\Component\Yaml\Yaml; 18 | 19 | class YmlFile extends BaseFile implements YmlFileInterface 20 | { 21 | /** @var array */ 22 | protected $yamlFile; 23 | 24 | public function __construct($debug, $filename, $rundir) 25 | { 26 | parent::__construct($debug, $filename, $rundir); 27 | 28 | try 29 | { 30 | $content = Yaml::parse($this->fileData); 31 | 32 | if (!is_array($content)) 33 | { 34 | throw new ParseException("Empty file"); 35 | } 36 | 37 | // Look for imports 38 | if (isset($content['imports']) && is_array($content['imports'])) 39 | { 40 | // Imports are defined relatively, get the directory based on the current file 41 | $currentPathInfo = pathinfo($filename); 42 | $dirname = $currentPathInfo['dirname']; 43 | 44 | foreach ($content['imports'] as $import) 45 | { 46 | if (isset($import['resource'])) 47 | { 48 | try 49 | { 50 | $importYmlFileName = $dirname . '/' . $import['resource']; 51 | $importYmlFile = new YmlFile($debug, $importYmlFileName, $rundir); 52 | $extraContent = $importYmlFile->getYaml(); 53 | } 54 | catch (FileLoadException $ex) 55 | { 56 | // The imported yml file will be loaded individually later. 57 | // Let's avoid duplicate error messages here and continue with the current yml. 58 | $extraContent = array(); 59 | } 60 | 61 | // Imports are at the top of the yaml file, so these should be loaded first. 62 | // The values of the current yaml file will overwrite existing array values of the imports. 63 | $content = array_replace_recursive($extraContent, $content); 64 | } 65 | } 66 | } 67 | $this->yamlFile = $content; 68 | } 69 | catch (ParseException $ex) 70 | { 71 | throw new FileLoadException("Parsing yaml file ($filename) failed: " . $ex->getMessage()); 72 | } 73 | } 74 | 75 | /** 76 | * Get an array with the data in the yaml file. 77 | * 78 | * @return array 79 | */ 80 | public function getYaml() 81 | { 82 | return $this->yamlFile; 83 | } 84 | 85 | /** 86 | * Get the file type for the specific file. 87 | * @return int 88 | */ 89 | function getFileType() 90 | { 91 | return Type::TYPE_YML; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_routing.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Tests; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Phpbb\Epv\Files\Type\RoutingFileInterface; 15 | use Phpbb\Epv\Output\Output; 16 | use Phpbb\Epv\Output\OutputInterface; 17 | use Phpbb\Epv\Tests\BaseTest; 18 | use Phpbb\Epv\Tests\Exception\TestException; 19 | use Phpbb\Epv\Tests\Type; 20 | 21 | class epv_test_validate_routing extends BaseTest 22 | { 23 | 24 | 25 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 26 | { 27 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 28 | 29 | $this->fileTypeFull = Type::TYPE_ROUTING; 30 | } 31 | 32 | public function validateFile(FileInterface $file) 33 | { 34 | if (!$file instanceof RoutingFileInterface) 35 | { 36 | throw new TestException("This test expects a routing type, but found something else."); 37 | } 38 | $this->validate($file); 39 | } 40 | 41 | /** 42 | * Do the actual validation of the routing file. 43 | * 44 | * @param RoutingFileInterface $file 45 | */ 46 | private function validate(RoutingFileInterface $file) 47 | { 48 | $yml = $file->getYaml(); 49 | 50 | if (is_array($yml)) 51 | { 52 | foreach ($yml as $key => $route) 53 | { 54 | $this->validateRoutingName($key, $file); 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Validate the route name to match the requirements for routes. 61 | * 62 | * @param string $route route name to validate 63 | * @param \Phpbb\Epv\Files\Type\RoutingFileInterface $file 64 | */ 65 | private function validateRoutingName($route, RoutingFileInterface $file) 66 | { 67 | $vendor = str_replace('/', '_', $this->namespace); 68 | 69 | if (strtolower(substr($route, 0, 6)) == 'phpbb.') 70 | { 71 | $this->output->addMessage(Output::ERROR, sprintf('The phpbb vendorname should only be used for official extensions in route names in %s. Current service name: %s', $file->getSaveFilename(), $route)); 72 | } 73 | else if (strtolower(substr($route, 0, 5)) == 'core.') 74 | { 75 | $this->output->addMessage(Output::FATAL, sprintf('The core vendorname should not be used in route names in %s. Current route name: %s', $file->getSaveFilename(), $route)); 76 | } 77 | if (substr($route, 0, strlen($vendor)) != $vendor) 78 | { 79 | $this->output->addMessage(Output::WARNING, sprintf('The route name should start with vendor_namespace (which is %s) but started with %s in %s', $vendor, $route, $file->getSaveFilename())); 80 | } 81 | } 82 | 83 | /** 84 | * 85 | * @return String 86 | */ 87 | public function testName() 88 | { 89 | return "Validate route"; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/file_loader_test.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | 11 | use Phpbb\Epv\Files\FileLoader; 12 | use Phpbb\Epv\Files\Type\ImageFile; 13 | use Phpbb\Epv\Files\Type\LangFile; 14 | use Phpbb\Epv\Files\Type\MigrationFile; 15 | use Phpbb\Epv\Files\Type\PHPFile; 16 | use Phpbb\Epv\Files\Type\PHPFileInterface; 17 | use Phpbb\Epv\Files\Type\YmlFile; 18 | use Phpbb\Epv\Tests\Mock\Output; 19 | use PHPUnit\Framework\TestCase; 20 | 21 | class file_loader_test extends TestCase { 22 | 23 | /** @var FileLoader */ 24 | private static $loader; 25 | 26 | public static function setUpBeforeClass(): void 27 | { 28 | require_once('./tests/Mock/Output.php'); 29 | 30 | static::$loader = new FileLoader(new Output(), false, 'tests/testFiles/', '.'); 31 | } 32 | 33 | public function test_file_php() { 34 | 35 | $type = static::$loader->loadFile('tests/testFiles/test.txt.php'); 36 | $typePhp = static::$loader->loadFile('tests/testFiles/test.php'); 37 | $typeMigration = static::$loader->loadFile('tests/testFiles/migrations/test.php'); 38 | 39 | self::assertInstanceOf(PHPFile::class, $type); 40 | self::assertInstanceOf(PHPFile::class, $typePhp); 41 | self::assertNotInstanceOf(MigrationFile::class, $typePhp); 42 | self::assertNotInstanceOf(LangFile::class, $typePhp); 43 | self::assertInstanceOf(PHPFileInterface::class, $typeMigration); // It extends from the interface! 44 | self::assertInstanceOf(MigrationFile::class, $typeMigration, 'type is migration file'); 45 | } 46 | 47 | public function test_file_yml() 48 | { 49 | $validYml = static::$loader->loadFile('tests/testFiles/valid.yml'); 50 | $invalidImportYml = static::$loader->loadFile('tests/testFiles/invalid_import.yml'); 51 | $emptyImportYml = static::$loader->loadFile('tests/testFiles/empty_import.yml'); 52 | 53 | self::assertInstanceOf(YmlFile::class, $validYml); 54 | self::assertInstanceOf(YmlFile::class, $invalidImportYml); 55 | self::assertInstanceOf(YmlFile::class, $emptyImportYml); 56 | } 57 | 58 | public function test_file_invalid_yml() 59 | { 60 | $this->expectException(Exception::class); 61 | $invalidYml = static::$loader->loadFile('tests/testFiles/invalid.yml'); 62 | self::assertNull($invalidYml); 63 | } 64 | 65 | public function test_file_empty_yml() 66 | { 67 | $this->expectException(Exception::class); 68 | $emptyYml = static::$loader->loadFile('tests/testFiles/empty.yml'); 69 | self::assertNull($emptyYml); 70 | } 71 | 72 | public function test_file_image() 73 | { 74 | $images = [ 75 | 'gif' => 'image.gif', 76 | 'png' => 'image.png', 77 | 'svg' => 'image.svg', 78 | 'jpg' => 'image.jpg', 79 | 'jpeg' => 'image.jpeg', 80 | 'webp' => 'image.webp' 81 | ]; 82 | 83 | foreach ($images as $image) 84 | { 85 | $img = static::$loader->loadFile('tests/testFiles/images/' . $image); 86 | self::assertInstanceOf(ImageFile::class, $img); 87 | 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_sql_queries.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Tests; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Phpbb\Epv\Files\Type\PHPFileInterface; 15 | use Phpbb\Epv\Output\Output; 16 | use Phpbb\Epv\Output\OutputInterface; 17 | use Phpbb\Epv\Tests\BaseTest; 18 | use Phpbb\Epv\Tests\Exception\TestException; 19 | use Phpbb\Epv\Tests\Type; 20 | 21 | 22 | class epv_test_validate_sql_queries extends BaseTest 23 | { 24 | /** 25 | * Allowed keywords 26 | * 27 | * If line contains one of these keywords, ignore it even when it 28 | * has been matched with regular expression. 29 | * 30 | * @var array 31 | */ 32 | protected $allowed_keywords = array( 33 | 'sql_in_set', 34 | 'sql_escape', 35 | 'sql_bit_and', 36 | 'get_visibility_sql', 37 | 'get_sql_where', 38 | 'get_forums_visibility_sql', 39 | 'ORDER BY', 40 | 'ORDER_BY', 41 | ); 42 | 43 | /** 44 | * @param bool $debug if debug is enabled 45 | * @param OutputInterface $output 46 | * @param string $basedir 47 | * @param string $namespace 48 | * @param boolean $titania 49 | * @param string $opendir 50 | */ 51 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 52 | { 53 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 54 | 55 | $this->fileTypeFull = Type::TYPE_PHP; 56 | } 57 | 58 | /** 59 | * @param FileInterface $file 60 | * 61 | * @throws \Phpbb\Epv\Tests\Exception\TestException 62 | */ 63 | public function validateFile(FileInterface $file) 64 | { 65 | if (!$file instanceof PHPFileInterface) 66 | { 67 | throw new TestException('This test expects a php type, but found something else.'); 68 | } 69 | $code = $file->getFile(); 70 | $code_exploded = explode("\n", $code); 71 | 72 | if (preg_match_all('/WHERE[^;\$]+[=<>]+[^;]+("|\') \. \$/mU', $code, $matches, PREG_OFFSET_CAPTURE)) 73 | { 74 | foreach ($matches[0] as $match) 75 | { 76 | $prelines = substr_count($code, "\n", 0, $match[1]); 77 | $inlines = substr_count($match[0], "\n"); 78 | $line = $prelines + $inlines; 79 | 80 | $test = array_reduce($this->allowed_keywords, function($acc, $keyword) use ($code_exploded, $line) { 81 | return $acc || strpos($code_exploded[$line], $keyword) !== false; 82 | }, false); 83 | if ($test) 84 | { 85 | continue; 86 | } 87 | 88 | $this->output->addMessage(Output::WARNING, sprintf('Found potential SQL injection on line %s in %s', $line + 1, $file->getSaveFilename())); 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * 95 | * @return String 96 | */ 97 | public function testName() 98 | { 99 | return 'Validate SQL queries'; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_service.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Tests; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Phpbb\Epv\Files\Type\ServiceFileInterface; 15 | use Phpbb\Epv\Output\Output; 16 | use Phpbb\Epv\Output\OutputInterface; 17 | use Phpbb\Epv\Tests\BaseTest; 18 | use Phpbb\Epv\Tests\Exception\TestException; 19 | use Phpbb\Epv\Tests\Type; 20 | 21 | class epv_test_validate_service extends BaseTest 22 | { 23 | 24 | 25 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 26 | { 27 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 28 | 29 | $this->fileTypeFull = Type::TYPE_SERVICE; 30 | } 31 | 32 | public function validateFile(FileInterface $file) 33 | { 34 | if (!$file instanceof ServiceFileInterface) 35 | { 36 | throw new TestException("This test expects a service type, but found something else."); 37 | } 38 | $this->validate($file); 39 | } 40 | 41 | /** 42 | * Do the actual validation of the service file. 43 | * 44 | * @param ServiceFileInterface $file 45 | */ 46 | private function validate(ServiceFileInterface $file) 47 | { 48 | $yml = $file->getYaml(); 49 | 50 | if (!isset ($yml['services'])) 51 | { 52 | $this->output->addMessage(Output::WARNING, "Service does not contain a 'services' key."); 53 | } 54 | 55 | if (is_array($yml['services'])) 56 | { 57 | foreach ($yml['services'] as $key => $service) 58 | { 59 | $this->validateServiceName($key, $file); 60 | } 61 | } 62 | } 63 | 64 | 65 | /** 66 | * Validate the service name to match the requirements for services. 67 | * 68 | * @param string $service service name to validate 69 | * @param \Phpbb\Epv\Files\Type\ServiceFileInterface $file 70 | */ 71 | private function validateServiceName($service, ServiceFileInterface $file) 72 | { 73 | $vendor = str_replace('/', '.', $this->namespace); 74 | 75 | if (stripos($service, 'phpbb.') === 0) 76 | { 77 | $this->output->addMessage(Output::ERROR, sprintf('The phpbb vendorname should only be used for official extensions in service names in %s. Current service name: %s', $file->getSaveFilename(), $service)); 78 | } 79 | else if (stripos($service, 'core.') === 0) 80 | { 81 | $this->output->addMessage(Output::FATAL, sprintf('The core vendorname should not be used in service names in %s. Current service name: %s', $file->getSaveFilename(), $service)); 82 | } 83 | else if ($service !== '_defaults' && strpos($service, $vendor) !== 0) 84 | { 85 | $this->output->addMessage(Output::WARNING, sprintf('The service name should start with vendor.namespace (which is %s) but started with %s in %s', $vendor, $service, $file->getSaveFilename())); 86 | } 87 | } 88 | 89 | /** 90 | * 91 | * @return String 92 | */ 93 | public function testName() 94 | { 95 | return "Validate service"; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/validate_directory_structure_test.php: -------------------------------------------------------------------------------- 1 | 12 | * @license GNU General Public License, version 2 (GPL-2.0) 13 | * 14 | */ 15 | 16 | class validate_directory_structure_test extends TestCase { 17 | public function test_missing_license() { 18 | $output = $this->getOutputMock(); 19 | $output->expects(self::once()) 20 | ->method('addMessage') 21 | ->with(OutputInterface::ERROR, 'Missing required license.txt file') 22 | ; 23 | 24 | $tester = new epv_test_validate_directory_structure(false, $output, '/a/b/epv/test', 'epv/test', false, '/a/'); 25 | $tester->validateDirectory(array(), false); 26 | } 27 | 28 | public function test_license() { 29 | $output = $this->getOutputMock(); 30 | $output->expects(self::never()) 31 | ->method('addMessage') 32 | ->with(OutputInterface::ERROR, 'Missing required license.txt file') 33 | ; 34 | $output->expects(self::never()) 35 | ->method('addMessage') 36 | ; 37 | 38 | $tester = new epv_test_validate_directory_structure(false, $output, '/a/b/epv/test', 'epv/test', false, '/a/b/'); 39 | $tester->validateDirectory(array( 40 | '/a/b/epv/test/license.txt', 41 | ), false); 42 | } 43 | 44 | public function test_composer() { 45 | $output = $this->getOutputMock(); 46 | $output->expects(self::never()) 47 | ->method('addMessage') 48 | ; 49 | 50 | $tester = new epv_test_validate_directory_structure(false, $output, '/a/b/epv/test', 'epv/test', false, '/a/b/'); 51 | $tester->validateDirectory(array( 52 | '/a/b/epv/test/composer.json', 53 | '/a/b/epv/test/license.txt', 54 | ), false); 55 | } 56 | 57 | public function test_composer_wrong2() { 58 | $output = $this->getOutputMock(); 59 | $output->expects(self::once()) 60 | ->method('addMessage') 61 | ->with(OutputInterface::ERROR, 62 | sprintf("Packaging structure doesn't meet the extension DB policies.\nExpected: %s\nGot: %s", 63 | 'epv/test', 'b/epv/test')) 64 | ; 65 | $tester = new epv_test_validate_directory_structure(false, $output, '/a/b/epv/test', 'epv/test', false, '/a/'); 66 | $tester->validateDirectory(array( 67 | '/a/b/epv/test/composer.json', 68 | '/a/b/epv/test/license.txt', 69 | ), false); 70 | } 71 | 72 | public function test_composer_wrong() { 73 | $output = $this->getOutputMock(); 74 | 75 | $output->expects(self::once()) 76 | ->method('addMessage') 77 | ->with(OutputInterface::ERROR, 78 | sprintf("Packaging structure doesn't meet the extension DB policies.\nExpected: %s\nGot: %s", 79 | 'epv/test', 'b')) 80 | ; 81 | 82 | $tester = new epv_test_validate_directory_structure(false, $output, '/a/b/', 'epv/test', false, '/a/'); 83 | $tester->validateDirectory(array( 84 | '/a/b/composer.json', 85 | '/a/b/epv/test/license.txt', 86 | ), false); 87 | } 88 | 89 | /** 90 | * @return PHPUnit\Framework\MockObject\MockObject|OutputInterface 91 | */ 92 | function getOutputMock() 93 | { 94 | return $this->createMock(OutputInterface::class); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/php_exporter_test.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | 11 | use Phpbb\Epv\Events\php_exporter; 12 | use Phpbb\Epv\Output\OutputInterface; 13 | use Phpbb\Epv\Tests\Mock\Output; 14 | use PHPUnit\Framework\TestCase; 15 | 16 | class php_exporter_test extends TestCase 17 | { 18 | 19 | public static function setUpBeforeClass(): void 20 | { 21 | require_once('./tests/Mock/Output.php'); 22 | } 23 | 24 | public function extension_data() 25 | { 26 | $expected_vars = ['mode', 'subject', 'username', 'topic_type', 'poll', 'data', 'update_message', 'update_search_index', 'url']; 27 | sort($expected_vars); 28 | 29 | $expected_errors = [ 30 | [ 31 | 'type' => OutputInterface::ERROR, 32 | 'message' => 'Event names should be all lowercase in for event %s', 33 | ], 34 | ]; 35 | 36 | return [ 37 | [27, './tests/events/invalid_name_long_multi.php', false, 'rxu.PostsMerging.posts_merging_end', $expected_vars, $expected_errors], 38 | [17, './tests/events/invalid_name_long_single.php', false, 'rxu.PostsMerging.posts_merging_end', $expected_vars, $expected_errors], 39 | [27, './tests/events/invalid_name_short_multi.php', false, 'rxu.PostsMerging.posts_merging_end', $expected_vars, $expected_errors], 40 | [17, './tests/events/invalid_name_short_single.php', false, 'rxu.PostsMerging.posts_merging_end', $expected_vars, $expected_errors], 41 | [7, './tests/events/invalid_name_simple.php', false, 'rxu.PostsMerging.posts_merging_end', [], $expected_errors], 42 | [7, './tests/events/invalid_name_simple_dispatch.php', true, 'rxu.PostsMerging.posts_merging_end', [], $expected_errors], 43 | [27, './tests/events/valid_name_long_multi.php', false, 'rxu.postsmerging.posts_merging_end', $expected_vars], 44 | [17, './tests/events/valid_name_long_single.php', false, 'rxu.postsmerging.posts_merging_end', $expected_vars], 45 | [27, './tests/events/valid_name_short_multi.php', false, 'rxu.postsmerging.posts_merging_end', $expected_vars], 46 | [17, './tests/events/valid_name_short_single.php', false, 'rxu.postsmerging.posts_merging_end', $expected_vars], 47 | [7, './tests/events/valid_name_simple.php', false, 'rxu.postsmerging.posts_merging_end', []], 48 | [7, './tests/events/valid_name_simple_dispatch.php', true, 'rxu.postsmerging.posts_merging_end', []], 49 | ]; 50 | } 51 | 52 | /** 53 | * @dataProvider extension_data 54 | */ 55 | public function test_event_name($line, $file, $is_dispatch, $expected_name, $expected_vars, $expected_errors = []) 56 | { 57 | $content = file_get_contents($file); 58 | $output = new Output(); 59 | $exporter = new php_exporter($output, ''); 60 | $exporter->set_content(explode("\n", $content)); 61 | 62 | $name = $exporter->get_event_name($line, $is_dispatch); 63 | $exporter->set_current_event($name, $line); 64 | $vars = strpos($content, 'compact(') ? $exporter->get_vars_from_array(false) : []; 65 | 66 | self::assertEquals($expected_name, $name); 67 | self::assertEquals($expected_vars, $vars); 68 | self::assertSameSize($expected_errors, $output->messages); 69 | 70 | for ($i = 0, $iMax = count($expected_errors); $i < $iMax; $i++) 71 | { 72 | self::assertEquals($output->messages[$i]['type'], $expected_errors[$i]['type']); 73 | self::assertEquals($output->messages[$i]['message'], sprintf($expected_errors[$i]['message'], $expected_name)); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_revert_schema.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Tests; 11 | 12 | use Phpbb\Epv\Files\FileInterface; 13 | use Phpbb\Epv\Files\Type\MigrationFile; 14 | use Phpbb\Epv\Output\Output; 15 | use Phpbb\Epv\Output\OutputInterface; 16 | use Phpbb\Epv\Tests\BaseTest; 17 | use Phpbb\Epv\Tests\Exception\TestException; 18 | use Phpbb\Epv\Tests\Type; 19 | use PhpParser\Error; 20 | use PhpParser\Node; 21 | use PhpParser\Node\Stmt\Class_; 22 | use PhpParser\Node\Stmt\ClassMethod; 23 | use PhpParser\Node\Stmt\Namespace_; 24 | use PhpParser\ParserFactory; 25 | 26 | class epv_test_validate_revert_schema extends BaseTest 27 | { 28 | /** 29 | * @var \PhpParser\Parser 30 | */ 31 | private $parser; 32 | 33 | /** 34 | * @param bool $debug if debug is enabled 35 | * @param OutputInterface $output 36 | * @param string $basedir 37 | * @param string $namespace 38 | * @param boolean $titania 39 | * @param string $opendir 40 | */ 41 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 42 | { 43 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 44 | 45 | $this->fileTypeFull = Type::TYPE_MIGRATION; 46 | $this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); 47 | } 48 | 49 | /** 50 | * @param FileInterface $file 51 | * 52 | * @throws TestException 53 | */ 54 | public function validateFile(FileInterface $file) 55 | { 56 | if (!$file instanceof MigrationFile) 57 | { 58 | throw new TestException(sprintf('This test expects a migration file, got %s (%s).', get_class($file), $file->getFilename())); 59 | } 60 | 61 | try 62 | { 63 | $nodes = $this->parser->parse($file->getFile()); 64 | 65 | if ($this->isMissingRevertSchema($nodes)) 66 | { 67 | $this->output->addMessage(Output::ERROR, sprintf('Migration file %s is missing the revert_schema() method.', $file->getSaveFilename())); 68 | } 69 | } 70 | catch (Error $e) 71 | { 72 | $this->output->addMessage(Output::FATAL, 'PHP parse error in file ' . $file->getSaveFilename() . '. Message: ' . $e->getMessage()); 73 | } 74 | } 75 | 76 | /** 77 | * @param Node[] $nodes 78 | * @return bool 79 | */ 80 | protected function isMissingRevertSchema($nodes) 81 | { 82 | $root = reset($nodes); 83 | 84 | if ($root instanceof Namespace_) 85 | { 86 | foreach ($root->stmts as $node) 87 | { 88 | if ($node instanceof Class_ && $this->hasMethod($node, 'update_schema') && !$this->hasMethod($node, 'revert_schema')) 89 | { 90 | return true; 91 | } 92 | } 93 | } 94 | 95 | return false; 96 | } 97 | 98 | /** 99 | * @param Class_ $node 100 | * @param string $methodName 101 | * @return bool 102 | */ 103 | protected function hasMethod($node, $methodName) 104 | { 105 | foreach ($node->stmts as $method) 106 | { 107 | if ($method instanceof ClassMethod && $method->name->name === $methodName) 108 | { 109 | return true; 110 | } 111 | } 112 | 113 | return false; 114 | } 115 | 116 | /** 117 | * @return string 118 | */ 119 | public function testName() 120 | { 121 | return 'Validate presence of revert_schema()'; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /tests/Mock/Output.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Mock; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Phpbb\Epv\Output\OutputInterface; 15 | use Symfony\Component\Console\Formatter\OutputFormatterInterface; 16 | 17 | class Output implements OutputInterface 18 | { 19 | public $progress = 0; 20 | public $messages = array(); 21 | 22 | public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) 23 | { 24 | 25 | } 26 | 27 | public function writeln($messages, $type = self::OUTPUT_NORMAL) 28 | { 29 | 30 | } 31 | 32 | public function setVerbosity($level) 33 | { 34 | } 35 | 36 | public function getVerbosity(): int 37 | { 38 | } 39 | 40 | public function setDecorated($decorated) 41 | { 42 | 43 | } 44 | 45 | public function isDecorated(): bool 46 | { 47 | 48 | } 49 | 50 | public function setFormatter(OutputFormatterInterface $formatter) 51 | { 52 | 53 | } 54 | 55 | /** 56 | * Write a message to the output, but only if Debug is enabled. 57 | * 58 | * @param $message string|array $messages The message as an array of lines of a single string 59 | * 60 | * @throws \InvalidArgumentException When unknown output type is given 61 | */ 62 | public function writelnIfDebug($message) 63 | { 64 | 65 | } 66 | 67 | /** 68 | * Add a new message to the output of the validator. 69 | * 70 | * @param $type int message type 71 | * @param $message string message 72 | * @param \Phpbb\Epv\Files\FileInterface $file File the error happened in. When provided, this is displayed to the user 73 | * 74 | * @throws \Exception 75 | * @internal param bool $skipError 76 | * 77 | */ 78 | public function addMessage($type, $message, FileInterface $file = null) 79 | { 80 | $this->messages[] = array('type' => $type, 'message' => $message); 81 | 82 | if ($type == self::FATAL) { 83 | throw new \Exception($message); 84 | } 85 | } 86 | 87 | /** 88 | * Get all messages saved into the message queue. 89 | * @return array Array with messages 90 | */ 91 | public function getMessages() 92 | { 93 | 94 | } 95 | 96 | /** 97 | * Get the amount of messages that were fatal. 98 | * @return int 99 | */ 100 | public function getFatalCount() 101 | { 102 | 103 | } 104 | 105 | /** 106 | * Get the count for a type; 107 | * 108 | * @param $type 109 | * 110 | * @return mixed 111 | */ 112 | public function getMessageCount($type) 113 | { 114 | 115 | } 116 | 117 | public function getFormatter(): OutputFormatterInterface 118 | { 119 | 120 | } 121 | 122 | /** 123 | * Print the status of this specific test. 124 | * 125 | * @param $result The result for this specific test. 126 | */ 127 | public function printErrorLevel($result = null) 128 | { 129 | 130 | } 131 | 132 | /** 133 | * Returns whether verbosity is quiet (-q). 134 | * 135 | * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise 136 | */ 137 | public function isQuiet(): bool 138 | { 139 | } 140 | 141 | /** 142 | * Returns whether verbosity is verbose (-v). 143 | * 144 | * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise 145 | */ 146 | public function isVerbose(): bool 147 | { 148 | } 149 | 150 | /** 151 | * Returns whether verbosity is very verbose (-vv). 152 | * 153 | * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise 154 | */ 155 | public function isVeryVerbose(): bool 156 | { 157 | } 158 | 159 | /** 160 | * Returns whether verbosity is debug (-vvv). 161 | * 162 | * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise 163 | */ 164 | public function isDebug(): bool 165 | { 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_languages.php: -------------------------------------------------------------------------------- 1 | 8 | * @license GNU General Public License, version 2 (GPL-2.0) 9 | * 10 | */ 11 | 12 | namespace Phpbb\Epv\Tests\Tests; 13 | 14 | use Phpbb\Epv\Output\OutputInterface; 15 | use Phpbb\Epv\Tests\ArrayKeyVisitor; 16 | use Phpbb\Epv\Tests\BaseTest; 17 | use PhpParser\Error; 18 | use PhpParser\NodeTraverser; 19 | use PhpParser\Parser; 20 | use PhpParser\ParserFactory; 21 | 22 | class epv_test_validate_languages extends BaseTest 23 | { 24 | /** 25 | * @var Parser 26 | */ 27 | private $parser; 28 | 29 | /** 30 | * @var ArrayKeyVisitor 31 | */ 32 | private $visitor; 33 | 34 | /** 35 | * @var NodeTraverser 36 | */ 37 | private $traverser; 38 | 39 | /** 40 | * @param bool $debug if debug is enabled 41 | * @param OutputInterface $output 42 | * @param string $basedir 43 | * @param string $namespace 44 | * @param boolean $titania 45 | * @param string $opendir 46 | */ 47 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 48 | { 49 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 50 | 51 | $this->directory = true; 52 | $this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); 53 | $this->visitor = new ArrayKeyVisitor; 54 | $this->traverser = new NodeTraverser; 55 | $this->traverser->addVisitor($this->visitor); 56 | } 57 | 58 | /** 59 | * @param array $files 60 | * 61 | * @return void 62 | */ 63 | public function validateDirectory(array $files) 64 | { 65 | $langs = []; 66 | $expected_keys = []; 67 | $expected_files = []; 68 | 69 | foreach ($files as $file) 70 | { 71 | if (preg_match('#^' . preg_quote($this->basedir) . 'language[\/\\\\]([a-z_]+?)[\/\\\\](.+\.php)$#', $file, $matches) === 1) 72 | { 73 | $language = $matches[1]; // language, e.g. "en" 74 | $relative_filename = $matches[2]; // file name relative to language's base dir, e.g. "info_acp_ext.php" 75 | $expected_files[$relative_filename] = $relative_filename; 76 | 77 | try 78 | { 79 | $keys = $this->load_language_keys($file); 80 | $langs[$language][$relative_filename] = $keys; 81 | 82 | $lang_keys = isset($expected_keys[$relative_filename]) ? $expected_keys[$relative_filename] : []; 83 | $expected_keys[$relative_filename] = array_unique(array_merge($lang_keys, $keys)); 84 | } 85 | catch (Error $e) 86 | { 87 | $this->output->addMessage(OutputInterface::FATAL, 'PHP parse error in file ' . str_replace($this->basedir, '', $file) . '. Message: ' . $e->getMessage()); 88 | } 89 | } 90 | } 91 | 92 | // check for missing EN language pack 93 | if (!empty($langs) && !array_key_exists('en', $langs)) 94 | { 95 | $this->output->addMessage(OutputInterface::FATAL, 'English language pack is missing'); 96 | } 97 | 98 | foreach ($langs as $lang_name => $file_contents) 99 | { 100 | // Check for missing language files 101 | foreach (array_diff($expected_files, array_keys($file_contents)) as $missing_file) 102 | { 103 | $this->output->addMessage(OutputInterface::NOTICE, sprintf("Language %s is missing the language file %s", $lang_name, $missing_file)); 104 | } 105 | 106 | // Check for missing language keys 107 | foreach ($file_contents as $relative_filename => $present_keys) 108 | { 109 | foreach (array_diff($expected_keys[$relative_filename], $present_keys) as $missing_key) 110 | { 111 | $this->output->addMessage(OutputInterface::WARNING, sprintf("Language file %s/%s is missing the language key %s", $lang_name, $relative_filename, $missing_key)); 112 | } 113 | } 114 | } 115 | } 116 | 117 | /** 118 | * This method scans through all array literals and collects all their string keys. 119 | * 120 | * @param string $filename File name to a phpBB language file 121 | * @return array 122 | * @throws Error 123 | */ 124 | protected function load_language_keys($filename) 125 | { 126 | $contents = @file_get_contents($filename); 127 | $nodes = $this->parser->parse($contents); 128 | $this->traverser->traverse($nodes); 129 | return $this->visitor->get_array_keys(); 130 | } 131 | 132 | /** 133 | * 134 | * @return String 135 | */ 136 | public function testName() 137 | { 138 | return 'Test languages'; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Tests/BaseTest.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Phpbb\Epv\Files\LineInterface; 15 | use Phpbb\Epv\Output\OutputInterface; 16 | use Phpbb\Epv\Tests\Exception\TestException; 17 | 18 | abstract class BaseTest implements TestInterface 19 | { 20 | private $debug; 21 | protected $fileTypeLine; 22 | protected $fileTypeFull; 23 | 24 | // Current file. Used in some tests. 25 | /** @var \Phpbb\Epv\Files\FileInterface * */ 26 | protected $file; 27 | 28 | /** 29 | * If this is set to true, tests are run on full directory listings. 30 | * @var bool 31 | */ 32 | protected $directory = false; 33 | /** 34 | * @var \Phpbb\Epv\Output\OutputInterface 35 | */ 36 | protected $output; 37 | /** 38 | * @var 39 | */ 40 | protected $basedir; 41 | 42 | /** 43 | * @var string 44 | */ 45 | protected $namespace; 46 | 47 | /** 48 | * @var boolean 49 | */ 50 | protected $titania; 51 | 52 | /** 53 | * @var string 54 | */ 55 | protected $opendir; 56 | 57 | /** 58 | * @param $debug 59 | * @param \Phpbb\Epv\Output\OutputInterface $output 60 | * @param $basedir string Basedirectory of the extension 61 | * @param $namespace string Namespace of the extension 62 | * @param $titania 63 | * @param string $opendir The directory the _user_ provided 64 | */ 65 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 66 | { 67 | $this->debug = $debug; 68 | $this->output = $output; 69 | $this->basedir = $basedir; 70 | $this->namespace = $namespace; 71 | $this->titania = $titania; 72 | $this->opendir = $opendir; 73 | } 74 | 75 | /** 76 | * 77 | * @param \Phpbb\Epv\Files\LineInterface $line 78 | * 79 | * @throws Exception\TestException 80 | * @internal param $ 81 | */ 82 | public function validateLine(LineInterface $line) 83 | { 84 | throw new TestException("Test declared to be a line test, but doesn't implement validateLine."); 85 | } 86 | 87 | /** 88 | * @param \Phpbb\Epv\Files\FileInterface $file 89 | * 90 | * @throws Exception\TestException 91 | * @internal param $ 92 | */ 93 | public function validateFile(FileInterface $file) 94 | { 95 | throw new TestException("Test declared to be a file test, but doesn't implement validateFile."); 96 | } 97 | 98 | /** 99 | * @param array $dirList 100 | * 101 | * @return mixed|void 102 | * @throws Exception\TestException 103 | */ 104 | public function validateDirectory(array $dirList) 105 | { 106 | throw new TestException("Test declared to be a directory listing test, but doesn't implement validateDirectory."); 107 | } 108 | 109 | /** 110 | * @param int $type 111 | * 112 | * @return bool 113 | */ 114 | public function doValidateLine($type) 115 | { 116 | return $this->fileTypeLine & $type; 117 | } 118 | 119 | /** 120 | * @param int $type 121 | * 122 | * @return bool 123 | */ 124 | public function doValidateFile($type) 125 | { 126 | return $this->fileTypeFull & $type; 127 | } 128 | 129 | /** 130 | * @return bool 131 | */ 132 | public function doValidateDirectory() 133 | { 134 | return $this->directory; 135 | } 136 | 137 | /** 138 | * Convert a boolean to Yes or No. 139 | * 140 | * @param $bool 141 | * 142 | * @return string 143 | */ 144 | private function boolToLang($bool) 145 | { 146 | return $bool ? "Yes" : "No"; 147 | } 148 | 149 | /** 150 | * @return string 151 | */ 152 | public function __toString() 153 | { 154 | $string = 'Test: ' . $this->testName() . '. '; 155 | 156 | return $string; 157 | } 158 | 159 | /** 160 | * Checks to see if the current file is for tests. 161 | * 162 | * @param FileInterface $file 163 | * 164 | * @return bool 165 | */ 166 | protected function isTest(?FileInterface $file = null) 167 | { 168 | if ($file == null) 169 | { 170 | $file = $this->file; 171 | } 172 | 173 | $dir = str_replace($this->basedir, '', $file->getFilename()); 174 | $dir = explode("/", $dir); 175 | 176 | return ($dir[0] == 'test' || $dir[0] == 'tests') && $dir[1] != 'testFiles'; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_composer.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Tests; 11 | 12 | use Composer\Composer; 13 | use Composer\Package\Loader\ArrayLoader; 14 | use Composer\Package\Loader\InvalidPackageException; 15 | use Composer\Package\Loader\ValidatingArrayLoader; 16 | use Composer\Package\Version\VersionParser; 17 | use Phpbb\Epv\Files\FileInterface; 18 | use Phpbb\Epv\Files\Type\ComposerFileInterface; 19 | use Phpbb\Epv\Output\Output; 20 | use Phpbb\Epv\Output\OutputInterface; 21 | use Phpbb\Epv\Tests\BaseTest; 22 | use Phpbb\Epv\Tests\Exception\TestException; 23 | use Phpbb\Epv\Tests\Type; 24 | 25 | class epv_test_validate_composer extends BaseTest 26 | { 27 | 28 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 29 | { 30 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 31 | 32 | $this->fileTypeFull = Type::TYPE_COMPOSER; 33 | } 34 | 35 | /** 36 | * @param FileInterface $file 37 | * 38 | * @throws \Phpbb\Epv\Tests\Exception\TestException 39 | */ 40 | public function validateFile(FileInterface $file) 41 | { 42 | if (!$file instanceof ComposerFileInterface) 43 | { 44 | throw new TestException('This test expects a php type, but found something else.'); 45 | } 46 | if (!$file->getJson() || !is_array($file->getJson())) 47 | { 48 | throw new TestException('Parsing composer file failed'); 49 | } 50 | $this->file = $file; 51 | 52 | $this->validateName($file); 53 | $this->validateLicense($file); 54 | $this->validateVersion($file); 55 | } 56 | 57 | /** 58 | * Validate if the provided license is the GPL. 59 | * 60 | * @param \Phpbb\Epv\Files\Type\ComposerFileInterface $file 61 | */ 62 | private function validateLicense(ComposerFileInterface $file) 63 | { 64 | $json = $file->getJson(); 65 | $this->addMessageIfBooleanTrue(!isset($json['license']), Output::FATAL, 'The license key is missing'); 66 | $this->addMessageIfBooleanTrue(isset($json['license']) && $json['license'] === 'GPL-2.0', Output::WARNING, '"GPL-2.0" is a deprecated SPDX license identifier, use "GPL-2.0-only" instead.'); 67 | $this->addMessageIfBooleanTrue(isset($json['license']) && ($json['license'] !== 'GPL-2.0-only' && $json['license'] !== 'GPL-2.0'), Output::ERROR, 'It is required to use "GPL-2.0-only" as the license identifier. Other licenses are not allowed as per the extension database policies.'); 68 | } 69 | 70 | private function validateName(ComposerFileInterface $file) 71 | { 72 | $json = $file->getJson(); 73 | $this->addMessageIfBooleanTrue(!isset($json['name']), Output::FATAL, 'The name key is missing'); 74 | $this->addMessageIfBooleanTrue(isset($json['name']) && strpos($json['name'], '_') !== false, Output::FATAL, 'The namespace should not contain underscores'); 75 | 76 | } 77 | 78 | /** 79 | * @param ComposerFileInterface $file 80 | */ 81 | private function validateVersion(ComposerFileInterface $file) 82 | { 83 | $json = $file->getJson(); 84 | 85 | if (isset($json['extra']) && isset($json['extra']['soft-require']) && isset($json['extra']['soft-require']['phpbb/phpbb'])) 86 | { 87 | // https://github.com/phpbb/customisation-db/blob/3.1.x/contribution/extension/type.php#L296 88 | $regex = '/(<|<=|~|\^|>|>=)([0-9]+(\.[0-9]+)?)\.[*x]/'; 89 | 90 | if (preg_match($regex, $json['extra']['soft-require']['phpbb/phpbb'])) 91 | { 92 | $replace = preg_replace($regex, '$1$2', $json['extra']['soft-require']['phpbb/phpbb']);; 93 | $this->addMessageIfBooleanTrue(true, Output::ERROR, sprintf('An invalid version constraint is used in soft-require: phpbb/phpbb. You can\'t combine a <|<=|~|\^|>|>= with a *|x. Please replace %s with %s', $json['extra']['soft-require']['phpbb/phpbb'], $replace)); 94 | } 95 | } 96 | 97 | $parser = new ValidatingArrayLoader(new ArrayLoader(), true, null, ValidatingArrayLoader::CHECK_ALL); 98 | try { 99 | $parser->load($json); 100 | } 101 | catch (InvalidPackageException $exception) 102 | { 103 | $this->handleMessages($exception->getErrors(), Output::FATAL); 104 | $this->handleMessages($exception->getWarnings(), Output::WARNING); 105 | } 106 | } 107 | 108 | /** 109 | * Add a array of errors as error into the report 110 | * 111 | * @param array $errorList 112 | * @param int $type 113 | */ 114 | private function handleMessages(array $errorList, $type = Output::ERROR) 115 | { 116 | foreach ($errorList as $error) { 117 | $this->output->addMessage($type, 'Composer validation: ' . $error); 118 | } 119 | } 120 | private function addMessageIfBooleanTrue($addMessage, $type, $message) 121 | { 122 | if ($addMessage) 123 | { 124 | $this->output->addMessage($type, $message, $this->file); 125 | } 126 | } 127 | 128 | public function testName() 129 | { 130 | return "Validate composer structure"; 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/Output/HtmlOutput.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Output; 11 | 12 | use SensioLabs\AnsiConverter\AnsiToHtmlConverter; 13 | use Symfony\Component\Console\Formatter\OutputFormatterInterface; 14 | use Symfony\Component\Console\Output\OutputInterface; 15 | 16 | class HtmlOutput implements OutputInterface 17 | { 18 | const TYPE_HTML = 1; 19 | const TYPE_BBCODE = 2; 20 | 21 | private $buffer = ""; 22 | private $type; 23 | 24 | /** 25 | * @param int $type Output type (HTML or BBCode) 26 | */ 27 | public function __construct($type = self::TYPE_HTML) 28 | { 29 | $this->type = $type; 30 | } 31 | 32 | /** 33 | * Writes a message to the output. 34 | * 35 | * @param string|array $messages The message as an array of lines or a single string 36 | * @param bool $newline Whether to add a newline 37 | * @param int $type The type of output (one of the OUTPUT constants) 38 | * 39 | * @throws \InvalidArgumentException When unknown output type is given 40 | * 41 | * @api 42 | */ 43 | public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) 44 | { 45 | if (!is_array($messages)) 46 | { 47 | $messages = array($messages); 48 | } 49 | 50 | foreach ($messages as $message) 51 | { 52 | $this->buffer .= $this->parse($message); 53 | if ($newline) 54 | { 55 | $this->buffer .= "\n"; 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * Writes a message to the output and adds a newline at the end. 62 | * 63 | * @param string|array $messages The message as an array of lines of a single string 64 | * @param int $type The type of output (one of the OUTPUT constants) 65 | * 66 | * @throws \InvalidArgumentException When unknown output type is given 67 | * 68 | * @api 69 | */ 70 | public function writeln($messages, $type = self::OUTPUT_NORMAL) 71 | { 72 | $this->write($messages, true, $type); 73 | } 74 | 75 | /** 76 | * Sets the verbosity of the output. 77 | * 78 | * @param int $level The level of verbosity (one of the VERBOSITY constants) 79 | * 80 | * @api 81 | */ 82 | public function setVerbosity($level) 83 | { 84 | 85 | } 86 | 87 | /** 88 | * Gets the current verbosity of the output. 89 | * 90 | * @return int The current level of verbosity (one of the VERBOSITY constants) 91 | * 92 | * @api 93 | */ 94 | public function getVerbosity(): int 95 | { 96 | 97 | } 98 | 99 | /** 100 | * Sets the decorated flag. 101 | * 102 | * @param bool $decorated Whether to decorate the messages 103 | * 104 | * @api 105 | */ 106 | public function setDecorated($decorated) 107 | { 108 | 109 | } 110 | 111 | /** 112 | * Gets the decorated flag. 113 | * 114 | * @return bool true if the output will decorate messages, false otherwise 115 | * 116 | * @api 117 | */ 118 | public function isDecorated(): bool 119 | { 120 | 121 | } 122 | 123 | /** 124 | * Sets output formatter. 125 | * 126 | * @param OutputFormatterInterface $formatter 127 | * 128 | * @api 129 | */ 130 | public function setFormatter(OutputFormatterInterface $formatter) 131 | { 132 | 133 | } 134 | 135 | /** 136 | * Returns current output formatter instance. 137 | * 138 | * @return OutputFormatterInterface 139 | * 140 | * @api 141 | */ 142 | public function getFormatter(): OutputFormatterInterface 143 | { 144 | 145 | } 146 | 147 | public function getBuffer() 148 | { 149 | if ($this->type == self::TYPE_HTML) 150 | { 151 | $formatter = new OutputFormatter(true); 152 | 153 | $convertor = new AnsiToHtmlConverter(); 154 | 155 | return nl2br($convertor->convert($formatter->format($this->buffer))); 156 | } 157 | 158 | return $this->buffer; 159 | } 160 | 161 | /** 162 | * Parse the code from the CLI to html. 163 | * 164 | * @param $message 165 | * 166 | * @return mixed Parsed message 167 | */ 168 | private function parse($message) 169 | { 170 | return $message; 171 | } 172 | 173 | /** 174 | * Returns whether verbosity is quiet (-q). 175 | * 176 | * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise 177 | */ 178 | public function isQuiet(): bool 179 | { 180 | } 181 | 182 | /** 183 | * Returns whether verbosity is verbose (-v). 184 | * 185 | * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise 186 | */ 187 | public function isVerbose(): bool 188 | { 189 | } 190 | 191 | /** 192 | * Returns whether verbosity is very verbose (-vv). 193 | * 194 | * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise 195 | */ 196 | public function isVeryVerbose(): bool 197 | { 198 | } 199 | 200 | /** 201 | * Returns whether verbosity is debug (-vvv). 202 | * 203 | * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise 204 | */ 205 | public function isDebug(): bool 206 | { 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/Tests/TestStartup.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests; 11 | 12 | use Gitonomy\Git\Admin; 13 | use Phpbb\Epv\Output\Output; 14 | use Phpbb\Epv\Output\OutputInterface; 15 | use Phpbb\Epv\Tests\Exception\TestException; 16 | 17 | class TestStartup 18 | { 19 | /** @var string */ 20 | private $dir = null; 21 | /** @var bool */ 22 | private $debug = false; 23 | 24 | /** @var int|null */ 25 | private $type = null; 26 | 27 | /** @var \Phpbb\Epv\Output\OutputInterface */ 28 | private $output; 29 | 30 | const TYPE_DIRECTORY = 1; 31 | const TYPE_GIT = 2; 32 | const TYPE_GITHUB = 3; 33 | 34 | /** 35 | * @param OutputInterface $output Output formatter 36 | * @param $type int Type what the location is 37 | * @param $location string Location where the extension is 38 | * @param $debug boolean if debug is enabled 39 | * @param string $branch When using GIT and GITHUB you can provide a branch name. When empty, defaults to master 40 | */ 41 | public function __construct(OutputInterface $output, $type, $location, $debug, $branch = '') 42 | { 43 | $this->output = $output; 44 | $rundir = true; 45 | 46 | if ($type == self::TYPE_GITHUB) 47 | { 48 | $location = 'https://github.com/' . $location; 49 | $type = self::TYPE_GIT; 50 | } 51 | 52 | if ($type == self::TYPE_GIT) 53 | { 54 | $location = $this->initGit($location, $branch); 55 | $rundir = false; 56 | } 57 | 58 | $this->type = $type; 59 | $this->dir = $location; 60 | $this->debug = $debug; 61 | 62 | $this->runTests($rundir); 63 | $this->cleanUp(); 64 | $this->printResults(); 65 | } 66 | 67 | /** 68 | * Init a git repository 69 | * 70 | * @param string $git Location of the git repo 71 | * @param string $branch branch to checkout 72 | * 73 | * @throws Exception\TestException 74 | * @return string local directory of the cloned repo 75 | */ 76 | private function initGit($git, $branch) 77 | { 78 | if (empty($branch)) 79 | { 80 | $branch = 'master'; 81 | } 82 | 83 | 84 | $this->output->writeln(sprintf("Checkout %s from git on branch %s.", $git, $branch)); 85 | $tmpdir = sys_get_temp_dir(); 86 | $uniq = $tmpdir . DIRECTORY_SEPARATOR . uniqid(); 87 | 88 | @mkdir($uniq); 89 | 90 | if (!file_exists($uniq)) 91 | { 92 | throw new TestException('Unable to create tmp directory'); 93 | } 94 | 95 | Admin::cloneBranchTo($uniq, $git, $branch, false); 96 | 97 | return $uniq; 98 | } 99 | 100 | /** 101 | * Run the test suite with the current directory. 102 | * 103 | * @param boolean $printDir print directory information 104 | */ 105 | private function runTests($printDir = true) 106 | { 107 | $dir = ''; 108 | if ($printDir) 109 | { 110 | $dir = "on directory $this->dir"; 111 | } 112 | 113 | $this->output->writeln("Running Extension Pre Validator {$dir}."); 114 | $runner = new TestRunner($this->output, $this->dir, $this->debug); 115 | 116 | if ($this->debug) 117 | { 118 | $this->output->writelnIfDebug("tests to run:"); 119 | 120 | foreach ($runner->tests as $t => $test) 121 | { 122 | $this->output->writelnIfDebug("$test"); 123 | } 124 | } 125 | $runner->runTests(); 126 | } 127 | 128 | /** 129 | * Print the results from the tests 130 | */ 131 | private function printResults() 132 | { 133 | // Write a empty line 134 | $this->output->writeLn(''); 135 | 136 | $found_msg = ' '; 137 | $found_msg .= 'Fatal: ' . $this->output->getMessageCount(Output::FATAL); 138 | $found_msg .= ', Error: ' . $this->output->getMessageCount(Output::ERROR); 139 | $found_msg .= ', Warning: ' . $this->output->getMessageCount(Output::WARNING); 140 | $found_msg .= ', Notice: ' . $this->output->getMessageCount(Output::NOTICE); 141 | $found_msg .= ' '; 142 | 143 | if ($this->output->getMessageCount(Output::FATAL) > 0 || $this->output->getMessageCount(Output::ERROR) > 0 || $this->output->getMessageCount(Output::WARNING) > 0) 144 | { 145 | $this->output->writeln('' . str_repeat(' ', strlen($found_msg)) . ''); 146 | $this->output->writeln(' Validation: FAILED' . str_repeat(' ', strlen($found_msg) - 19) . ''); 147 | $this->output->writeln('' . $found_msg . ''); 148 | $this->output->writeln('' . str_repeat(' ', strlen($found_msg)) . ''); 149 | $this->output->writeln(''); 150 | } 151 | else 152 | { 153 | $this->output->writeln('PASSED: ' . $found_msg . ''); 154 | } 155 | 156 | // Write debug messages. 157 | if ($this->debug) 158 | { 159 | foreach ($this->output->getDebugMessages() as $msg) 160 | { 161 | $this->output->writeln((string)$msg); 162 | } 163 | } 164 | 165 | $this->output->writeln("Test results for extension:"); 166 | 167 | foreach ($this->output->getMessages() as $msg) 168 | { 169 | $this->output->writeln((string)$msg); 170 | } 171 | 172 | if (count($this->output->getMessages()) == 0) 173 | { 174 | $this->output->writeln("No issues found "); 175 | } 176 | } 177 | 178 | /** 179 | * Cleanup the mess we made 180 | */ 181 | private function cleanUp() 182 | { 183 | if ($this->type == self::TYPE_GIT) 184 | { 185 | $this->rrmdir($this->dir); 186 | } 187 | } 188 | 189 | /** 190 | * Remove a directory including the contents 191 | * 192 | * @param $dir string Directory to remove 193 | */ 194 | private function rrmdir($dir) 195 | { 196 | if (is_dir($dir)) 197 | { 198 | $objects = scandir($dir); 199 | 200 | foreach ($objects as $object) 201 | { 202 | if ($object != "." && $object != "..") 203 | { 204 | if (filetype($dir . "/" . $object) == "dir") 205 | { 206 | $this->rrmdir($dir . "/" . $object); 207 | } 208 | else 209 | { 210 | @unlink($dir . "/" . $object); 211 | } 212 | } 213 | } 214 | reset($objects); 215 | rmdir($dir); 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Tests/Tests/epv_test_validate_directory_structure.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests\Tests; 11 | 12 | use Phpbb\Epv\Output\Output; 13 | use Phpbb\Epv\Output\OutputInterface; 14 | use Phpbb\Epv\Tests\BaseTest; 15 | use Phpbb\Epv\Tests\TestRunner; 16 | 17 | class epv_test_validate_directory_structure extends BaseTest 18 | { 19 | private $strict = false; 20 | 21 | const LICENSE_SIMILARITY_THRESHOLD = 0.99; 22 | 23 | const LICENSE_CLOSING_WORDS = 'END OF TERMS AND CONDITIONS'; 24 | 25 | public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) 26 | { 27 | parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); 28 | 29 | $this->directory = true; 30 | } 31 | 32 | public function validateDirectory(array $dirList, $validateLicenseContents = true) 33 | { 34 | $files = array( 35 | 'license' => false, 36 | 'composer' => false, 37 | ); 38 | foreach ($dirList as $dir) 39 | { 40 | switch (strtolower(basename($dir))) 41 | { 42 | case 'license.txt': 43 | $files['license'] = true; 44 | 45 | if (basename($dir) != strtolower(basename($dir))) 46 | { 47 | $this->output->addMessage(Output::WARNING, 'The name of license.txt should be completely lowercase.'); 48 | } 49 | 50 | if ($validateLicenseContents) 51 | { 52 | $licenseSimilarity = $this->licenseSimilarity(TestRunner::getResource('gpl-2.0.txt'), $dir); 53 | 54 | if ($licenseSimilarity !== false && $licenseSimilarity < self::LICENSE_SIMILARITY_THRESHOLD) 55 | { 56 | $msg = 'Similarity of the license.txt to the GPL-2.0 is too low. Expected is %s%% or above but got %s%%'; 57 | $expectedPercent = self::LICENSE_SIMILARITY_THRESHOLD * 100; 58 | // Truncate after 2nd decimal 59 | $actualPercent = floor($licenseSimilarity * 10000) / 100; 60 | 61 | $this->output->addMessage(Output::WARNING, sprintf($msg, $expectedPercent, $actualPercent)); 62 | } 63 | } 64 | 65 | // Do not check license.txt location. Will give false positives in case packaging is wrong directory wise. 66 | break; 67 | 68 | case 'composer.json': 69 | $files['composer'] = true; 70 | 71 | if (basename($dir) != strtolower(basename($dir))) 72 | { 73 | $this->output->addMessage(Output::WARNING, 'The name of composer.json should be completely lowercase.'); 74 | } 75 | $sp = str_replace('\\', '/', $dir); 76 | $sp = str_replace(str_replace('\\', '/', $this->opendir), '', $sp); 77 | $sp = str_replace('/composer.json', '', $sp); 78 | 79 | if (!empty($sp) && $sp[0] == '/') 80 | { 81 | // for some reason, there is a extra / on at least OS X 82 | $sp = substr($sp, 1, strlen($sp)); 83 | } 84 | 85 | if ($this->namespace != $sp) 86 | { 87 | $this->output->addMessage(Output::ERROR, 88 | sprintf("Packaging structure doesn't meet the extension DB policies.\nExpected: %s\nGot: %s", 89 | $this->namespace, $sp)); 90 | } 91 | break; 92 | } 93 | } 94 | 95 | if (!$files['license']) 96 | { 97 | $this->output->addMessage(Output::ERROR, 'Missing required license.txt file'); 98 | } 99 | } 100 | 101 | public function testName() 102 | { 103 | return "Validate directory structure"; 104 | } 105 | 106 | /** 107 | * @param string $expectedLicenseFile Path to the file of the expected license 108 | * @param string $extLicenseFile Path to the extension's license.txt file 109 | * @return bool|float 110 | */ 111 | public function licenseSimilarity($expectedLicenseFile, $extLicenseFile) 112 | { 113 | $expectedLicense = @file_get_contents($expectedLicenseFile); 114 | 115 | if ($expectedLicense === false) 116 | { 117 | $this->output->addMessage(Output::WARNING, 'Failed to load expected license file from ' . $expectedLicenseFile); 118 | return false; 119 | } 120 | 121 | $extLicense = @file_get_contents($extLicenseFile); 122 | 123 | if ($extLicense === false) 124 | { 125 | $this->output->addMessage(Output::WARNING, 'Failed to load extension license file from ' . $extLicense); 126 | return false; 127 | } 128 | 129 | // Remove everything after the closing words 130 | if (($closingWordsPos = strripos($extLicense, self::LICENSE_CLOSING_WORDS)) !== false) 131 | { 132 | $extLicense = substr($extLicense, 0, $closingWordsPos + strlen(self::LICENSE_CLOSING_WORDS)); 133 | } 134 | 135 | // Remove all whitespaces 136 | $extLicense = preg_replace('/\s+/', '', $extLicense); 137 | $expectedLicense = preg_replace('/\s+/', '', $expectedLicense); 138 | 139 | return $this->diceCoefficient($expectedLicense, $extLicense); 140 | } 141 | 142 | /** 143 | * Sørensen–Dice coefficient, case sensitive 144 | * 145 | * @param string $str1 146 | * @param string $str2 147 | * @return float a value between 0 and 1, 1 being exact match 148 | */ 149 | protected function diceCoefficient($str1, $str2) 150 | { 151 | $str1 = (string) $str1; 152 | $str2 = (string) $str2; 153 | 154 | if ($str1 === $str2) 155 | { 156 | return 1; 157 | } 158 | 159 | if (!strlen($str1) || !strlen($str2)) 160 | { 161 | return 0; 162 | } 163 | 164 | $bi1 = $this->bigrams($str1); 165 | $bi2 = $this->bigrams($str2); 166 | 167 | sort($bi1); 168 | sort($bi2); 169 | 170 | $i = 0; 171 | $j = 0; 172 | $matches = 0; 173 | $len1 = count($bi1); 174 | $len2 = count($bi2); 175 | 176 | while ($i < $len1 && $j < $len2) 177 | { 178 | $cmp = strcmp($bi1[$i], $bi2[$j]); 179 | 180 | if ($cmp == 0) 181 | { 182 | $matches += 2; 183 | $i++; 184 | $j++; 185 | } 186 | else if ($cmp < 0) 187 | { 188 | $i++; 189 | } 190 | else 191 | { 192 | $j++; 193 | } 194 | } 195 | 196 | return $matches / ($len1 + $len2); 197 | } 198 | 199 | /** 200 | * @param $str 201 | * @return array 202 | */ 203 | protected function bigrams($str) 204 | { 205 | $bigrams = []; 206 | $len = strlen($str); 207 | 208 | for ($i = 0; $i < $len - 1; $i++) 209 | { 210 | $bigrams[] = $str[$i] . $str[$i + 1]; 211 | } 212 | 213 | return $bigrams; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /tests/epv_test_validate_php_functions_test.php: -------------------------------------------------------------------------------- 1 | 14 | * @license GNU General Public License, version 2 (GPL-2.0) 15 | * 16 | */ 17 | 18 | class epv_test_validate_php_functions_test extends TestCase 19 | { 20 | public static function setUpBeforeClass(): void 21 | { 22 | require_once('./tests/Mock/Output.php'); 23 | } 24 | 25 | public function test_usage_of_enable_globals() { 26 | $output = $this->getOutputMock(); 27 | $output->expects(self::exactly(2)) 28 | ->method('addMessage') 29 | ->with(OutputInterface::FATAL, 'The use of enable_super_globals() is not allowed for security reasons on line 7 in tests/testFiles/enable_globals.php') 30 | ; 31 | 32 | $file = $this->getLoader()->loadFile('tests/testFiles/enable_globals.php'); 33 | 34 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 35 | $tester->validateFile($file); 36 | } 37 | 38 | public function test_usage_of_enable_globals2() { 39 | $output = $this->getOutputMock(); 40 | $output->expects(self::exactly(0)) 41 | ->method('addMessage') 42 | ; 43 | 44 | $file = $this->getLoader()->loadFile('tests/testFiles/enable_globals2.php'); 45 | 46 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 47 | $tester->validateFile($file); 48 | } 49 | 50 | public function test_usage_of_enable_globals3() { 51 | $output = $this->getOutputMock(); 52 | $output->expects(self::once()) 53 | ->method('addMessage') 54 | ->with(OutputInterface::FATAL, 'The use of enable_super_globals() is not allowed for security reasons on line 7 in tests/testFiles/enable_globals3.php') 55 | ; 56 | 57 | $file = $this->getLoader()->loadFile('tests/testFiles/enable_globals3.php'); 58 | 59 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 60 | $tester->validateFile($file); 61 | } 62 | 63 | public function test_usage_of_addslashes() { 64 | $output = $this->getOutputMock(); 65 | $output->expects(self::exactly(2)) 66 | ->method('addMessage') 67 | ->with(OutputInterface::ERROR, 'Using addslashes on line 8 in tests/testFiles/addslashes.php') 68 | ; 69 | 70 | $file = $this->getLoader()->loadFile('tests/testFiles/addslashes.php'); 71 | 72 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 73 | $tester->validateFile($file); 74 | } 75 | 76 | public function test_usage_of_evals() { 77 | $output = $this->getOutputMock(); 78 | $output->expects(self::once()) 79 | ->method('addMessage') 80 | ->with(OutputInterface::FATAL, 'The use of eval() is not allowed for security reasons on line 8 in tests/testFiles/eval.php') 81 | ; 82 | 83 | $file = $this->getLoader()->loadFile('tests/testFiles/eval.php'); 84 | 85 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 86 | $tester->validateFile($file); 87 | } 88 | 89 | public function test_usage_of_no_inphpbb() { 90 | $output = $this->getOutputMock(); 91 | $output->expects(self::once()) 92 | ->method('addMessage') 93 | ->with(OutputInterface::WARNING, 'IN_PHPBB is not defined in tests/testFiles/no_in_phpbb.php') 94 | ; 95 | 96 | $file = $this->getLoader()->loadFile('tests/testFiles/no_in_phpbb.php'); 97 | 98 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 99 | $tester->validateFile($file); 100 | } 101 | 102 | public function test_usage_of_wrong_in_phpbb() { 103 | $output = $this->getOutputMock(); 104 | $output->expects(self::exactly(2)) 105 | ->method('addMessage') 106 | ; 107 | 108 | $file = $this->getLoader()->loadFile('tests/testFiles/in_phpbb_wrong.php'); 109 | 110 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 111 | $tester->validateFile($file); 112 | } 113 | 114 | public function test_usage_of_wrong_in_phpbb2() { 115 | $output = $this->getOutputMock(); 116 | $output->expects(self::exactly(5)) 117 | ->method('addMessage') 118 | ; 119 | 120 | $file = $this->getLoader()->loadFile('tests/testFiles/in_phpbb_wrong2.php'); 121 | 122 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 123 | $tester->validateFile($file); 124 | } 125 | 126 | public function test_usage_of_namespace() { 127 | $output = $this->getOutputMock(); 128 | $output->expects(self::exactly(2)) 129 | ->method('addMessage') 130 | ; 131 | 132 | $file = $this->getLoader()->loadFile('tests/testFiles/no_namespace.php'); 133 | 134 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 135 | $tester->validateFile($file); 136 | } 137 | 138 | public function test_usage_of_var_test() { 139 | $output = $this->getOutputMock(); 140 | $output->expects(self::exactly(0)) 141 | ->method('addMessage') 142 | ; 143 | 144 | $file = $this->getLoader()->loadFile('tests/testFiles/var_test.php'); 145 | 146 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 147 | $tester->validateFile($file); 148 | } 149 | 150 | public function test_usage_of_var_test2() { 151 | $output = $this->getOutputMock(); 152 | $output->expects(self::exactly(0)) 153 | ->method('addMessage') 154 | ; 155 | 156 | $file = $this->getLoader()->loadFile('tests/testFiles/var_test2.php'); 157 | 158 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 159 | $tester->validateFile($file); 160 | } 161 | 162 | public function test_usage_variable() { 163 | $output = $this->getOutputMock(); 164 | $output->expects(self::exactly(0)) 165 | ->method('addMessage') 166 | ; 167 | 168 | $file = $this->getLoader()->loadFile('tests/testFiles/variable_function.php'); 169 | 170 | $tester = new epv_test_validate_php_functions(false, $output, '/a/b/', 'epv/test', false, '/a/'); 171 | $tester->validateFile($file); 172 | } 173 | 174 | private function getLoader() 175 | { 176 | return new FileLoader(new Output(), false, '.', '.'); 177 | } 178 | 179 | /** 180 | * @return PHPUnit\Framework\MockObject\MockObject|OutputInterface 181 | */ 182 | function getOutputMock() 183 | { 184 | return $this->createMock(OutputInterface::class); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/Files/FileLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Files; 11 | 12 | use Phpbb\Epv\Files\Exception\FileException; 13 | use Phpbb\Epv\Files\Exception\FileLoadException; 14 | use Phpbb\Epv\Files\Type\BinaryFile; 15 | use Phpbb\Epv\Files\Type\ComposerFile; 16 | use Phpbb\Epv\Files\Type\CssFile; 17 | use Phpbb\Epv\Files\Type\HTMLFile; 18 | use Phpbb\Epv\Files\Type\ImageFile; 19 | use Phpbb\Epv\Files\Type\JavascriptFile; 20 | use Phpbb\Epv\Files\Type\JsonFile; 21 | use Phpbb\Epv\Files\Type\LangFile; 22 | use Phpbb\Epv\Files\Type\LockFile; 23 | use Phpbb\Epv\Files\Type\MigrationFile; 24 | use Phpbb\Epv\Files\Type\PHPFile; 25 | use Phpbb\Epv\Files\Type\PlainFile; 26 | use Phpbb\Epv\Files\Type\RoutingFile; 27 | use Phpbb\Epv\Files\Type\ServiceFile; 28 | use Phpbb\Epv\Files\Type\XmlFile; 29 | use Phpbb\Epv\Files\Type\YmlFile; 30 | use Phpbb\Epv\Output\Output; 31 | use Phpbb\Epv\Output\OutputInterface; 32 | 33 | class FileLoader 34 | { 35 | /** 36 | * @var \Phpbb\Epv\Output\OutputInterface 37 | */ 38 | private $output; 39 | private $debug; 40 | private $basedir; 41 | private $loadError; 42 | private $rundir; 43 | 44 | /** 45 | * @param OutputInterface $output 46 | * @param $debug 47 | * @param $basedir 48 | * @param $rundir 49 | */ 50 | public function __construct(OutputInterface $output, $debug, $basedir, $rundir) 51 | { 52 | 53 | $this->output = $output; 54 | $this->debug = $debug; 55 | $this->basedir = $basedir; 56 | $this->rundir = $rundir; 57 | } 58 | 59 | public function loadFile($fileName) 60 | { 61 | $file = null; 62 | 63 | $split = explode('.', basename($fileName)); 64 | $size = count($split); 65 | 66 | try 67 | { 68 | // File has no extension 69 | if ($size == 1) 70 | { 71 | // If it is a readme file it is ok. 72 | // Otherwise add notice. 73 | if (strtolower($fileName) !== 'readme') 74 | { 75 | $this->output->addMessage(Output::NOTICE, sprintf("The file %s has no valid extension.", basename($fileName))); 76 | } 77 | $file = new PlainFile($this->debug, $fileName, $this->rundir); 78 | } 79 | // File has an extension 80 | else if ($size > 1) 81 | { 82 | // Try to load the file, the last part of $split contains the extension 83 | $file = self::tryLoadFile($fileName, $split[count($split) - 1]); 84 | } 85 | else // Blank filename? 86 | { 87 | throw new FileException("Filename was empty"); 88 | } 89 | } 90 | catch (FileLoadException $e) 91 | { 92 | $this->output->addMessage(Output::FATAL, $e->getMessage()); 93 | 94 | return null; 95 | } 96 | 97 | return $file; 98 | } 99 | 100 | /** 101 | * Attempts to load a file based on extension. 102 | * 103 | * In case of plaintext files, contents are also checked to see if it isn't a php file. 104 | * 105 | * @param $fileName 106 | * @param $extension 107 | * 108 | * @return BinaryFile|ComposerFile|CssFile|HTMLFile|JavascriptFile|JsonFile|PHPFile|PlainFile|XmlFile|YmlFile|ImageFile|null 109 | */ 110 | private function tryLoadFile($fileName, $extension) 111 | { 112 | $this->output->writelnIfDebug("Attempting to load $fileName with extension $extension"); 113 | $this->loadError = false; 114 | 115 | switch (strtolower($extension)) 116 | { 117 | case 'php': 118 | // First, check if this file is a lang file. 119 | $file = basename($fileName); 120 | $dir = str_replace($file, '', $fileName); 121 | $dir = str_replace($this->basedir, '', $dir); 122 | $dir = str_replace('\\', '/', $dir); 123 | $dir = explode('/', trim($dir, '/')); 124 | $dir = array_map('strtolower', $dir); 125 | 126 | if (in_array('language', $dir)) 127 | { 128 | return new LangFile($this->debug, $fileName, $this->rundir); 129 | } 130 | 131 | if (in_array('migrations', $dir)) 132 | { 133 | return new MigrationFile($this->debug, $fileName, $this->rundir); 134 | } 135 | 136 | return new PHPFile($this->debug, $fileName, $this->rundir); 137 | 138 | case 'html': 139 | case 'htm': 140 | return new HTMLFile($this->debug, $fileName, $this->rundir); 141 | 142 | case 'json': 143 | if (strtolower(basename($fileName)) == 'composer.json') 144 | { 145 | return new ComposerFile($this->debug, $fileName, $this->rundir); 146 | } 147 | else 148 | { 149 | return new JsonFile($this->debug, $fileName, $this->rundir); 150 | } 151 | 152 | case 'yml': 153 | if (strtolower(basename($fileName)) == 'services.yml') 154 | { 155 | return new ServiceFile($this->debug, $fileName, $this->rundir); 156 | } 157 | if (strtolower(basename($fileName)) == 'routing.yml') 158 | { 159 | return new RoutingFile($this->debug, $fileName, $this->rundir); 160 | } 161 | 162 | return new YmlFile($this->debug, $fileName, $this->rundir); 163 | 164 | case 'txt': 165 | case 'md': 166 | case 'htaccess': 167 | case 'gitattributes': 168 | case 'gitignore': 169 | case 'map': 170 | case 'sh': // Decide if we want a special file type for shell files! 171 | return new PlainFile($this->debug, $fileName, $this->rundir); 172 | 173 | case 'xml': 174 | return new XmlFile($this->debug, $fileName, $this->rundir); 175 | 176 | case 'js': 177 | return new JavascriptFile($this->debug, $fileName, $this->rundir); 178 | 179 | case 'css': 180 | return new CssFile($this->debug, $fileName, $this->rundir); 181 | 182 | case 'gif': 183 | case 'png': 184 | case 'jpg': 185 | case 'jpeg': 186 | case 'svg': 187 | case 'webp': 188 | return new ImageFile($this->debug, $fileName, $this->rundir); 189 | 190 | case 'swf': 191 | $this->output->addMessage(Output::NOTICE, sprintf("Found an SWF file (%s), please make sure to include the source files for it, as required by the GPL.", basename($fileName))); 192 | 193 | return new BinaryFile($this->debug, $fileName, $this->rundir); 194 | case 'ds_store': 195 | $this->output->addMessage(Output::ERROR, sprintf("Found an OS X specific file at %s, please make sure to remove it prior to submission.", $fileName)); 196 | 197 | return new BinaryFile($this->debug, $fileName, $this->rundir); 198 | 199 | case 'lock': 200 | return new LockFile($this->debug, $fileName, $this->rundir); 201 | 202 | default: 203 | 204 | $file = basename($fileName); 205 | $this->output->addMessage(Output::WARNING, "Can't detect the file type for $file, handling it as a binary file."); 206 | 207 | return new BinaryFile($this->debug, $fileName, $this->rundir); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/Output/Output.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Output; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Symfony\Component\Console\Formatter\OutputFormatterInterface; 15 | 16 | class Output implements \Phpbb\Epv\Output\OutputInterface 17 | { 18 | private $messages = array(); 19 | private $debugMessages = array(); 20 | private $fatal = 0; 21 | private $error = 0; 22 | private $warning = 0; 23 | private $notice = 0; 24 | 25 | 26 | private $output; 27 | private $debug; 28 | 29 | /** @var int */ 30 | protected $progress = 0; 31 | /** @var int */ 32 | protected $maxProgress = 0; 33 | 34 | public function __construct(\Symfony\Component\Console\Output\OutputInterface $output, $debug) 35 | { 36 | $this->output = $output; 37 | $this->debug = $debug; 38 | } 39 | 40 | /** 41 | * Writes a message to the output. 42 | * 43 | * @param string|array $messages The message as an array of lines or a single string 44 | * @param Boolean $newline Whether to add a newline 45 | * @param integer $type The type of output (one of the OUTPUT constants) 46 | * 47 | * @throws \InvalidArgumentException When unknown output type is given 48 | * 49 | * @api 50 | */ 51 | public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) 52 | { 53 | return $this->output->write($messages, $newline, $type); 54 | } 55 | 56 | /** 57 | * Writes a message to the output and adds a newline at the end. 58 | * 59 | * @param string|array $messages The message as an array of lines of a single string 60 | * @param integer $type The type of output (one of the OUTPUT constants) 61 | * 62 | * @throws \InvalidArgumentException When unknown output type is given 63 | * 64 | * @api 65 | */ 66 | public function writeln($messages, $type = self::OUTPUT_NORMAL) 67 | { 68 | return $this->output->writeln($messages, $type); 69 | } 70 | 71 | /** 72 | * Sets the verbosity of the output. 73 | * 74 | * @param integer $level The level of verbosity (one of the VERBOSITY constants) 75 | * 76 | * @api 77 | */ 78 | public function setVerbosity($level) 79 | { 80 | return $this->output->setVerbosity($level); 81 | } 82 | 83 | /** 84 | * Gets the current verbosity of the output. 85 | * 86 | * @return integer The current level of verbosity (one of the VERBOSITY constants) 87 | * 88 | * @api 89 | */ 90 | public function getVerbosity(): int 91 | { 92 | return $this->output->getVerbosity(); 93 | } 94 | 95 | /** 96 | * Sets the decorated flag. 97 | * 98 | * @param Boolean $decorated Whether to decorate the messages 99 | * 100 | * @api 101 | */ 102 | public function setDecorated($decorated) 103 | { 104 | return $this->output->setDecorated($decorated); 105 | } 106 | 107 | /** 108 | * Gets the decorated flag. 109 | * 110 | * @return Boolean true if the output will decorate messages, false otherwise 111 | * 112 | * @api 113 | */ 114 | public function isDecorated(): bool 115 | { 116 | return $this->output->isDecorated(); 117 | } 118 | 119 | /** 120 | * Sets output formatter. 121 | * 122 | * @param OutputFormatterInterface $formatter 123 | * 124 | * @api 125 | */ 126 | public function setFormatter(OutputFormatterInterface $formatter) 127 | { 128 | return $this->output->setFormatter($formatter); 129 | } 130 | 131 | /** 132 | * Returns current output formatter instance. 133 | * 134 | * @return OutputFormatterInterface 135 | * 136 | * @api 137 | */ 138 | public function getFormatter(): OutputFormatterInterface 139 | { 140 | return $this->output->getFormatter(); 141 | } 142 | 143 | /** 144 | * Write a message to the output, but only if Debug is enabled. 145 | * 146 | * @param $message string|array $messages The message as an array of lines of a single string 147 | * 148 | * @throws \InvalidArgumentException When unknown output type is given 149 | */ 150 | public function writelnIfDebug($message) 151 | { 152 | if ($this->debug) 153 | { 154 | $this->debugMessages[] = new Message(Output::DEBUG, $message, null); 155 | } 156 | } 157 | 158 | /** 159 | * Add a new message to the output of the validator. 160 | * 161 | * @param $type int message type 162 | * @param $message string message 163 | * @param \Phpbb\Epv\Files\FileInterface $file File the error happened in. When provided, this is displayed to the user 164 | */ 165 | public function addMessage($type, $message, ?FileInterface $file = null) 166 | { 167 | switch ($type) 168 | { 169 | case Output::FATAL: 170 | $this->fatal++; 171 | break; 172 | case Output::ERROR: 173 | $this->error++; 174 | break; 175 | case Output::WARNING: 176 | $this->warning++; 177 | break; 178 | case Output::NOTICE: 179 | $this->notice++; 180 | break; 181 | case Output::DEBUG: 182 | break; 183 | default: 184 | // TODO: Decide on this? 185 | } 186 | $this->messages[] = new Message($type, $message, $file); 187 | } 188 | 189 | 190 | /** 191 | * Get all messages saved into the message queue. 192 | * @return array Array with messages 193 | */ 194 | public function getMessages() 195 | { 196 | return $this->messages; 197 | } 198 | 199 | /** 200 | * Get all saved debug messages in the queue. 201 | * @return array Array with messages 202 | */ 203 | public function getDebugMessages() 204 | { 205 | return $this->debugMessages; 206 | } 207 | 208 | /** 209 | * Get the amount of messages that were fatal. 210 | * @return int 211 | */ 212 | public function getFatalCount() 213 | { 214 | return $this->fatal; 215 | } 216 | 217 | /** 218 | * Get the count for a type; 219 | * 220 | * @param $type 221 | * 222 | * @return mixed 223 | */ 224 | public function getMessageCount($type) 225 | { 226 | switch ($type) 227 | { 228 | case Output::FATAL: 229 | return $this->fatal; 230 | case Output::ERROR: 231 | return $this->error; 232 | case Output::WARNING: 233 | return $this->warning; 234 | case Output::NOTICE: 235 | return $this->notice; 236 | } 237 | } 238 | 239 | /** 240 | * Returns whether verbosity is quiet (-q). 241 | * 242 | * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise 243 | */ 244 | public function isQuiet(): bool 245 | { 246 | } 247 | 248 | /** 249 | * Returns whether verbosity is verbose (-v). 250 | * 251 | * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise 252 | */ 253 | public function isVerbose(): bool 254 | { 255 | } 256 | 257 | /** 258 | * Returns whether verbosity is very verbose (-vv). 259 | * 260 | * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise 261 | */ 262 | public function isVeryVerbose(): bool 263 | { 264 | } 265 | 266 | /** 267 | * Returns whether verbosity is debug (-vvv). 268 | * 269 | * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise 270 | */ 271 | public function isDebug(): bool 272 | { 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/Tests/TestRunner.php: -------------------------------------------------------------------------------- 1 | 7 | * @license GNU General Public License, version 2 (GPL-2.0) 8 | * 9 | */ 10 | namespace Phpbb\Epv\Tests; 11 | 12 | 13 | use Phpbb\Epv\Files\FileInterface; 14 | use Phpbb\Epv\Files\FileLoader; 15 | use Phpbb\Epv\Files\Line; 16 | use Phpbb\Epv\Output\Output; 17 | use Phpbb\Epv\Output\OutputInterface; 18 | use Phpbb\Epv\Tests\Exception\TestException; 19 | use Symfony\Component\Finder\Finder; 20 | use Symfony\Component\Finder\SplFileInfo; 21 | 22 | class TestRunner 23 | { 24 | /** @var array */ 25 | public $tests = array(); 26 | /** @var array */ 27 | private $files = array(); 28 | /** @var array */ 29 | private $dirList = array(); 30 | 31 | private $output; 32 | private $directory; 33 | private $debug; 34 | private $basedir; 35 | private $namespace; 36 | private $titania; 37 | 38 | /** 39 | * @param OutputInterface $output 40 | * @param string $directory The directory where the extension is located 41 | * @param boolean $debug Debug mode 42 | * @param boolean $isTitania If we run from titania or not 43 | */ 44 | public function __construct(OutputInterface $output, $directory, $debug, $isTitania = false) 45 | { 46 | $this->output = $output; 47 | $this->directory = realpath($directory); 48 | $this->debug = $debug; 49 | $this->titania = $isTitania; 50 | 51 | $this->setBasedir(); 52 | $this->loadTests(); 53 | $this->loadFiles(); 54 | } 55 | 56 | /** 57 | * @param string $resource 58 | * @return bool|string 59 | */ 60 | public static function getResource($resource) 61 | { 62 | return realpath(__DIR__. '/../Resources/' . $resource); 63 | } 64 | 65 | /** 66 | * Run the actual test suite. 67 | * 68 | * @throws Exception\TestException 69 | */ 70 | public function runTests() 71 | { 72 | if (count($this->tests) == 0) 73 | { 74 | throw new TestException("TestRunner not initialised."); 75 | } 76 | $this->output->writeln("Running tests."); 77 | 78 | // Now, we basicly do the same as above, but we do really run the tests. 79 | // All other tests are specific to files. 80 | /** @var \Phpbb\Epv\Tests\TestInterface $test */ 81 | foreach ($this->tests as $test) 82 | { 83 | if ($test->doValidateDirectory()) 84 | { 85 | $test->validateDirectory($this->dirList); 86 | } 87 | } 88 | 89 | // Time to loop over the files in memory, 90 | // And over the tests that are available. 91 | // First do the full file check. 92 | // After that loop over each line and test per line. 93 | /** @var FileInterface $file */ 94 | foreach ($this->files as $file) 95 | { 96 | $linetest = array(); 97 | 98 | /** @var \Phpbb\Epv\Tests\TestInterface $test */ 99 | foreach ($this->tests as $test) 100 | { 101 | if ($test->doValidateFile($file->getFileType())) 102 | { 103 | $test->validateFile($file); 104 | } 105 | 106 | // To prevent looping over too many tests, we check here if we need to loop 107 | // over tests for line by line tests. 108 | if ($test->doValidateLine($file->getFileType())) 109 | { 110 | $linetest[] = $test; 111 | } 112 | } 113 | if (count($linetest)) 114 | { 115 | $linenr = 1; 116 | foreach ($file->getLines() as $line) 117 | { 118 | $runline = new Line($file, $linenr, $line); 119 | foreach ($linetest as $test) 120 | { 121 | $test->validateLine($runline); 122 | } 123 | $linenr++; 124 | } 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * Set the base directory for the extension. 131 | * @throws Exception\TestException 132 | */ 133 | private function setBasedir() 134 | { 135 | $finder = new Finder(); 136 | 137 | // First find composer.json. 138 | // composer.json is required, so it should always be there. 139 | // We use it to find the base directory of all files. 140 | $iterator = $finder 141 | ->files() 142 | ->name('composer.json') 143 | ->exclude(['vendor', 'tests']) 144 | ->in($this->directory); 145 | 146 | $iteratorCount = count($iterator); 147 | 148 | if ($iteratorCount !== 1) 149 | { 150 | throw new TestException($iteratorCount === 0 ? 'Could not find the required composer.json file.' : 'Found multiple composer.json files.'); 151 | } 152 | 153 | $composer = ''; 154 | 155 | foreach ($iterator as $file) 156 | { 157 | $composer = $file; 158 | } 159 | 160 | if (empty($composer)) 161 | { 162 | throw new TestException('Iterator did result a empty filename'); 163 | } 164 | 165 | $this->basedir = str_replace('composer.json', '', $composer); 166 | 167 | $composer = @json_decode(@file_get_contents($composer)); 168 | 169 | if (!$composer) 170 | { 171 | throw new TestException('composer.json is unreadable or invalid json'); 172 | } 173 | $this->namespace = $composer->name ?? ''; 174 | } 175 | 176 | /** 177 | * Load all files from the extension. 178 | */ 179 | private function loadFiles() 180 | { 181 | $finder = new Finder(); 182 | 183 | $iterator = $finder 184 | ->ignoreDotFiles(false) 185 | ->files() 186 | ->sortByName() 187 | //->name('*') 188 | ->ignoreVCS(true) 189 | ->exclude('vendor') 190 | ->exclude('tests') 191 | ->exclude('travis') 192 | ->in($this->directory); 193 | 194 | $loader = new FileLoader($this->output, $this->debug, $this->basedir, $this->directory); 195 | foreach ($iterator as $file) 196 | { 197 | /** @var \Symfony\Component\Finder\SplFileInfo $file */ 198 | if (!$file->getRealPath()) 199 | { 200 | $this->output->write("Finder found a file, but it does not seem to be readable or does not actually exist."); 201 | continue; 202 | } 203 | $loadedFile = $loader->loadFile($file->getRealPath()); 204 | 205 | if ($loadedFile != null) 206 | { 207 | $this->files[] = $loadedFile; 208 | 209 | $this->dirList[] = $file->getRealPath(); 210 | } 211 | else 212 | { 213 | $this->output->addMessage(Output::FATAL, "Unable to load file: " . $file->getRealPath()); 214 | } 215 | } 216 | } 217 | 218 | /** 219 | * Load all available tests. 220 | */ 221 | private function loadTests() 222 | { 223 | $finder = new Finder(); 224 | 225 | $iterator = $finder 226 | ->files() 227 | ->name('epv_test_*.php') 228 | ->size(">= 0K") 229 | ->in(__DIR__ . '/Tests'); 230 | 231 | foreach ($iterator as $test) 232 | { 233 | $this->tryToLoadTest($test); 234 | } 235 | } 236 | 237 | /** 238 | * Try to load and initialise a specific test. 239 | * 240 | * @param SplFileInfo $test 241 | * 242 | * @throws Exception\TestException 243 | */ 244 | private function tryToLoadTest(SplFileInfo $test) 245 | { 246 | $this->output->writelnIfDebug("Found {$test->getRealpath()}."); 247 | $file = str_replace('.php', '', basename($test->getRealPath())); 248 | 249 | $class = '\\Phpbb\Epv\\Tests\\Tests\\' . $file; 250 | 251 | $filetest = new $class($this->debug, $this->output, $this->basedir, $this->namespace, $this->titania, $this->directory); 252 | 253 | if (!$filetest instanceof TestInterface) 254 | { 255 | throw new TestException("$class does not implement the TestInterface, but matches the test expression."); 256 | } 257 | $this->tests[] = $filetest; 258 | 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /tests/testFiles/licenses/apache-2.0/license.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /tests/testFiles/licenses/gpl-2.0-skeleton-ext/license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 675 Mass Ave, Cambridge, MA 02139, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | -------------------------------------------------------------------------------- /src/Resources/gpl-2.0.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | --------------------------------------------------------------------------------