diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..ada43e26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# https://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +tab_width = 4 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore index 62c5408e..8d0441ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,21 @@ -local_config.php +# These files are generated during application running or testing and are +# intentionally untracked to ignore by Git. For other development environment +# specific files, such as editor configuration, a good practice is to exclude +# them using the .git/info/exclude in the cloned repository or a global +# .gitignore file. + +# Uploaded patches +/uploads/ + +# Configuration file +/local_config.php + +# Local specific PHPUnit configuration +/phpunit.xml +.phpunit.result.cache + +# Transient and temporary generated files +/var/ + +# Generated by Composer +/vendor/ diff --git a/README.md b/README.md index 7bf2d07e..d0bef4e0 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,103 @@ -PHP Bug Tracking System -======================= -This was a collaboration between PEAR, PECL and PHP core to create a unified bug tracker. - -Requirements -============ -- PHP 5.4+ -- `ext/openssl` (for https:// fopen wrapper) -- PEAR packages: - - MDB2 - - MDB2#mysql - - MDB2#mysqli - - DB_DataObject - - Text_CAPTCHA_Numeral - - Text_Diff - - HTTP - - HTTP_Upload - -Installation -============ -1. Copy `local_config.php.sample` to `local_config.php` and modify accordingly -2. Install all required packages: -`pear install MDB2 MDB2#mysql MDB2#mysqli DB_DataObject Text_CAPTCHA_Numeral Text_Diff HTTP HTTP_Upload` -3. Import SQL schema from `sql/bugs.sql` - -TODO -==== -- AJAXify where it's useful -- Add project support (f.e. PHP-GTK, PEAR..) +# PHP Bug Tracking System + +This is a unified bug tracking system for PHP hosted online at +[bugs.php.net](https://bugs.php.net). + +## Local installation + +* Install development dependencies with Composer: + +```bash +composer install +``` + +* Configuration: + +Modify `local_config.php` according to your local development environment. + +* Database: + +Create a new MySQL/MariaDB database using `sql/database.sql`, create database +schema `sql/schema.sql` and insert fixtures using `sql/fixtures.sql`. + +## Tests + +Application unit tests can be executed in development environment after +installing dependencies by running `phpunit`: + +```bash +./vendor/bin/phpunit +``` + +## Directory structure + +Source code of this application is structured in the following directories: + +```bash +/ + ├─ .git/ # Git configuration and source directory + ├─ config/ # Application configuration parameters, services... + ├─ docs/ # Application documentation + └─ include/ # Application helper functions and configuration + ├─ prepend.php # Autoloader, DB connection, container, app initialization + └─ ... + └─ scripts/ # Command line development tools and scripts + ├─ cron/ # Various systems scripts to run periodically on the server + └─ ... + ├─ sql/ # Database schema and fixtures + └─ src/ # Application source code classes + ├─ Horde/ # https://www.horde.org/libraries/Horde_Text_Diff + └─ ... + ├─ templates/ # Application templates + ├─ tests/ # Application automated tests + ├─ uploads/ # Uploaded patch files + ├─ var/ # Transient and temporary generated files + ├─ vendor/ # Dependencies generated by Composer + └─ www/ # Publicly accessible directory for online bugs.php.net + ├─ css/ # Stylesheets + ├─ images/ # Images + ├─ js/ # JavaScript assets + └─ ... + ├─ composer.json # Composer dependencies and project meta definition + ├─ composer.lock # Dependencies versions currently installed + ├─ local_config.php # Application configuration + ├─ local_config.php.sample # Distributed configuration example + ├─ phpunit.xml.dist # PHPUnit's default XML configuration + └─ ... +``` + +## Contributing + +Issues with the application and new feature requests can be reported to +[bugs.php.net](https://bugs.php.net) and discussed by sending message to the +[webmaster mailing list](http://news.php.net/php.webmaster) to the address +php-webmaster@lists.php.net. + +Application source code is located in the +[github.com/php/web-bugs](https://github.com/php/web-bugs) repository. + +To contribute: + +```bash +git clone git@github.com:your-username/web-bugs +cd web-bugs +git checkout -b patch-1 +git add . +git commit -m "Describe changes" +git push origin patch-1 +``` + +A good practice is to also set the upstream remote in case the upstream master +branch updates. This way your master branch will track remote upstream master +branch of the root repository. + +```bash +git checkout master +git remote add upstream git://github.com/php/web-bugs +git config branch.master.remote upstream +git pull --rebase +``` + +## Documentation + +More information about this application can be found in the [documentation](/docs). diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..a9a9ae51 --- /dev/null +++ b/composer.json @@ -0,0 +1,57 @@ +{ + "name": "php/web-bugs", + "description": "The PHP Bugtracking System", + "type": "project", + "keywords": ["bugs", "php", "website"], + "homepage": "https://bugs.php.net/", + "readme": "README.md", + "license": "PHP license", + "authors": [ + { + "name": "PHP contributors" + } + ], + "support": { + "email": "php-webmaster@lists.php.net", + "issues": "https://bugs.php.net/", + "wiki": "https://wiki.php.net/", + "irc": "irc://irc.efnet.org/php.pecl", + "source": "https://github.com/php/web-bugs", + "docs": "https://php.net/manual", + "rss": "https://bugs.php.net/rss" + }, + "require": { + "php": "^8.2", + "ext-fileinfo": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-pdo": "*", + "ext-pdo_mysql": "*", + "ext-session": "*" + }, + "require-dev": { + "ext-pdo_sqlite": "*", + "phpunit/phpunit": "^8.1.5" + }, + "autoload": { + "psr-4": { + "App\\": "src/" + }, + "classmap": ["src/Horde/"] + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" + } + }, + "config": { + "sort-packages": true + }, + "scripts":{ + "post-install-cmd": [ + "App\\Utils\\ComposerScripts::installConfig", + "App\\Utils\\ComposerScripts::createDirectories" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 00000000..8ad77d94 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1451 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "9ffaca7d3269343f2ea635047d22621e", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.12.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-06-12T14:39:25+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.17", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", + "reference": "40a4ed114a4aea5afd6df8d0f0c9cd3033097f66", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": ">=7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.1.3 || ^4.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.2.2", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.2.2" + }, + "suggest": { + "ext-xdebug": "^2.7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/7.0.17" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:09:37+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "69deeb8664f611f156a924154985fbd4911eb36b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/69deeb8664f611f156a924154985fbd4911eb36b", + "reference": "69deeb8664f611f156a924154985fbd4911eb36b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/2.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:39:50+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a691211e94ff39a34811abd521c31bd5b305b0bb", + "reference": "a691211e94ff39a34811abd521c31bd5b305b0bb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/2.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:42:41+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "reference": "a853a0e183b9db7eed023d7933a858fa1c8d25a3", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2020-08-04T08:28:15+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "8.5.39", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "172ba97bcf97ae6ef86ca256adf77aece8a143fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/172ba97bcf97ae6ef86ca256adf77aece8a143fe", + "reference": "172ba97bcf97ae6ef86ca256adf77aece8a143fe", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.5.0", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=7.2", + "phpunit/php-code-coverage": "^7.0.17", + "phpunit/php-file-iterator": "^2.0.6", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1.4", + "sebastian/comparator": "^3.0.5", + "sebastian/diff": "^3.0.6", + "sebastian/environment": "^4.2.5", + "sebastian/exporter": "^3.1.6", + "sebastian/global-state": "^3.0.5", + "sebastian/object-enumerator": "^3.0.5", + "sebastian/resource-operations": "^2.0.3", + "sebastian/type": "^1.1.5", + "sebastian/version": "^2.0.1" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage", + "phpunit/php-invoker": "To allow enforcing time limits" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.39" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-07-10T11:43:00+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:45:45+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1dc7ceb4a24aede938c7af2a9ed1de09609ca770", + "reference": "1dc7ceb4a24aede938c7af2a9ed1de09609ca770", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:31:48+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/98ff311ca519c3aa73ccd3de053bdb377171d7b6", + "reference": "98ff311ca519c3aa73ccd3de053bdb377171d7b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:16:36+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "56932f6049a0482853056ffd617c91ffcc754205" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/56932f6049a0482853056ffd617c91ffcc754205", + "reference": "56932f6049a0482853056ffd617c91ffcc754205", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/4.2.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:49:59+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/1939bc8fd1d39adcfa88c5b35335910869214c56", + "reference": "1939bc8fd1d39adcfa88c5b35335910869214c56", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/3.1.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:21:38+00:00" + }, + { + "name": "sebastian/global-state", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "91c7c47047a971f02de57ed6f040087ef110c5d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/91c7c47047a971f02de57ed6f040087ef110c5d9", + "reference": "91c7c47047a971f02de57ed6f040087ef110c5d9", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:13:16+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/ac5b293dba925751b808e02923399fb44ff0d541", + "reference": "ac5b293dba925751b808e02923399fb44ff0d541", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/3.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:54:02+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/1d439c229e61f244ff1f211e5c99737f90c67def", + "reference": "1d439c229e61f244ff1f211e5c99737f90c67def", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/1.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:56:04+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/9bfd3c6f1f08c026f542032dfb42813544f7d64c", + "reference": "9bfd3c6f1f08c026f542032dfb42813544f7d64c", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T14:07:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/72a7f7674d053d548003b16ff5a106e7e0e06eee", + "reference": "72a7f7674d053d548003b16ff5a106e7e0e06eee", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T13:59:09+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/18f071c3a29892b037d35e6b20ddf3ea39b42874", + "reference": "18f071c3a29892b037d35e6b20ddf3ea39b42874", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/1.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-01T14:04:07+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.2", + "ext-fileinfo": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-pdo": "*", + "ext-pdo_mysql": "*", + "ext-session": "*" + }, + "platform-dev": { + "ext-pdo_sqlite": "*" + }, + "plugin-api-version": "2.3.0" +} diff --git a/config/container.php b/config/container.php new file mode 100644 index 00000000..11dc4ae3 --- /dev/null +++ b/config/container.php @@ -0,0 +1,92 @@ +set(\PDO::class, function ($c) { + return new \PDO( + 'mysql:host='.$c->get('db_host').';dbname='.$c->get('db_name').';charset=utf8', + $c->get('db_user'), + $c->get('db_password'), + [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + \PDO::ATTR_EMULATE_PREPARES => false, + \PDO::ATTR_STATEMENT_CLASS => [App\Database\Statement::class], + ] + ); +}); + +$container->set(App\Repository\BugRepository::class, function ($c) { + return new App\Repository\BugRepository($c->get(\PDO::class)); +}); + +$container->set(App\Repository\CommentRepository::class, function ($c) { + return new App\Repository\CommentRepository($c->get(\PDO::class)); +}); + +$container->set(App\Repository\DatabaseStatusRepository::class, function ($c) { + return new App\Repository\DatabaseStatusRepository($c->get(\PDO::class)); +}); + +$container->set(App\Repository\ObsoletePatchRepository::class, function ($c) { + return new App\Repository\ObsoletePatchRepository($c->get(\PDO::class)); +}); + +$container->set(App\Repository\PackageRepository::class, function ($c) { + return new App\Repository\PackageRepository($c->get(\PDO::class)); +}); + +$container->set(App\Repository\PatchRepository::class, function ($c) { + return new App\Repository\PatchRepository($c->get(\PDO::class), $c->get('uploads_dir')); +}); + +$container->set(App\Repository\PhpInfoRepository::class, function ($c) { + return new App\Repository\PhpInfoRepository(); +}); + +$container->set(App\Repository\PullRequestRepository::class, function ($c) { + return new App\Repository\PullRequestRepository($c->get(\PDO::class)); +}); + +$container->set(App\Repository\ReasonRepository::class, function ($c) { + return new App\Repository\ReasonRepository($c->get(\PDO::class)); +}); + +$container->set(App\Repository\VoteRepository::class, function ($c) { + return new App\Repository\VoteRepository($c->get(\PDO::class)); +}); + +$container->set(App\Template\Engine::class, function ($c) { + return new App\Template\Engine($c->get('templates_dir')); +}); + +$container->set(App\Utils\Captcha::class, function ($c) { + return new App\Utils\Captcha(); +}); + +$container->set(App\Utils\GitHub::class, function ($c) { + return new App\Utils\GitHub($c->get(\PDO::class)); +}); + +$container->set(App\Utils\PatchTracker::class, function ($c) { + return new App\Utils\PatchTracker( + $c->get(\PDO::class), + $c->get(App\Utils\Uploader::class), + $c->get('uploads_dir') + ); +}); + +$container->set(App\Utils\Uploader::class, function ($c) { + return new App\Utils\Uploader(); +}); + +return $container; diff --git a/config/parameters.php b/config/parameters.php new file mode 100644 index 00000000..9ff0d78a --- /dev/null +++ b/config/parameters.php @@ -0,0 +1,73 @@ + (defined('DEVBOX') && true === DEVBOX) ? 'dev' : 'prod', + + /** + * Site scheme - http or https. + */ + 'site_scheme' => $site_data['method'], + + /** + * Site URL. + */ + 'site_url' => $site_data['url'], + + /** + * Site base path if present. Part that comes after the domain + * https://bugs.php.net/basedir/ + */ + 'basedir' => $site_data['basedir'], + + /** + * Database username. + */ + 'db_user' => $site_data['db_user'], + + /** + * Database password. + */ + 'db_password' => $site_data['db_pass'], + + /** + * Database host name. + */ + 'db_host' => $site_data['db_host'], + + /** + * Database name. + */ + 'db_name' => $site_data['db'], + + /** + * Main email of the public mailing list. + */ + 'email'=> $site_data['email'], + + /** + * Email of the public mailing list for documentation related bugs. + */ + 'doc_email' => $site_data['doc_email'], + + /** + * Security email - emails sent to this are not visible in public. + */ + 'security_email' => $site_data['security_email'], + + /** + * Uploads directory location. + */ + 'uploads_dir' => $site_data['patch_tmp'], + + /** + * Templates directory. + */ + 'templates_dir' => __DIR__.'/../templates', +]; diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..273ab36f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,4 @@ +# Application documentation + +* [Templates](/docs/templates.md) +* [Dependency injection container](/docs/container.md) diff --git a/docs/container.md b/docs/container.md new file mode 100644 index 00000000..2403f579 --- /dev/null +++ b/docs/container.md @@ -0,0 +1,119 @@ +# Dependency injection container + +The PHP bug tracker application ships with a simplistic dependency injection +container which can create services and retrieves configuration values. + +Services are one of the more frequently used objects everywhere across the +application. For example, service for database access, utility service for +uploading files, data generators, API clients, and similar. + +## Dependency injection + +Dependencies between classes are injected using either constructor, or via a +method call such as setter. + +```php +class Repository +{ + private $pdo; + + public function __construct(\PDO $pdo) + { + $this->pdo = $pdo; + } + + public function getData(): array + { + return $this->pdo->query("SELECT * FROM table")->fetchAll(); + } +} + +$pdo = new \PDO( + 'mysql:host=localhost;dbname=bugs;charset=utf8', 'nobody', 'password', + [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + \PDO::ATTR_EMULATE_PREPARES => false, + ] +); + +$repository = new Repository($pdo); +$data = $repository->getData(); +``` + +The `$pdo` object in the example is a dependency which is injected via the +constructor. + +Dependency injection container further provides a more efficient creation of +such dependencies and services: + +```php +$container = require_once __DIR__.'/../config/container.php'; + +$data = $container->get(Repository::class)->getData(); +``` + +## Configuration + +Configuration parameters include infrastructure configuration (database +credentials...) and application level configuration (directories locations...). + +```php +// config/parameters.php + +return [ + 'parameter_key' => 'value', + + // ... +]; +``` + +Which can be retrieved by the container: + +```php +$value = $container->get('parameter_key'); +``` + +## Container definitions + +Each service class is manually defined: + +```php +// config/container.php + +// New container initialization with configuration parameters defined in a file. +$container = new Container(include __DIR__.'/parameters.php'); + +// Services are then defined using callables with a container argument $c. + +// Service with constructor arguments +$container->set(App\Foo::class, function ($c) { + return new App\Foo($c->get(App\Dependency::class)); +}); + +// Service with a setter method +$container->set(App\Foo\Bar::class, function ($c) { + $object = new App\Foo\Bar($c->get(App\Dependency::class)); + $object->setFoobar('argument'); + + return $object; +}); + +// Dependencies can be service classes or configuration parameters +$container->set(App\Foo\Bar::class, function ($c) { + return new App\Foo\Bar( + // Configuration parameter + $c->get('parameter_key')); + + // Calling method from another service + $c->get(App\Dependency::class)->methodName(); + ); +}); + +// Service with no dependencies +$container->set(App\Dependency::class, function ($c) { + return new App\Dependency(); +}); + +return $container; +``` diff --git a/docs/templates.md b/docs/templates.md new file mode 100644 index 00000000..0623d91e --- /dev/null +++ b/docs/templates.md @@ -0,0 +1,202 @@ +# Templates + +A simple template engine separates logic from the presentation and provides +methods for creating nested templates and escaping strings to protect against +too common XSS vulnerabilities. + +Template engine initialization: + +```php +$template = new App\Template\Engine(__DIR__.'/../path/to/templates'); +``` + +Site-wide configuration parameters can be assigned before rendering so they are +available in all templates: + +```php +$template->assign([ + 'siteUrl' => 'https://bugs.php.net/', + // ... +]); +``` + +Page can be rendered in the controller: + +```php +echo $template->render('pages/how_to_report.php', [ + 'mainHeading' => 'How to report a bug?', +]); +``` + +The `templates/pages/how_to_report.php`: + +```php +extends('layout.php', ['title' => 'Reporting bugs']) ?> + +start('main_content') ?> +

noHtml($mainHeading) ?>

+ +

+end('main_content') ?> + +start('scripts') ?> + +end('scripts') ?> +``` + +The `templates/layout.php`: + +```html + + + + + + PHP Bug Tracking System :: <?= $title ?? '' ?> + + + block('main_content') ?> + +
+ + + block('scripts') ?> + + +``` + +## Including templates + +To include a partial template snippet file: + +```php +include('forms/report_bug.php') ?> +``` + +which is equivalent to ``, +except that the variable scope is not inherited by the template that included +the file. To import variables into the included template snippet file: + +```php +include('forms/contact.php', ['formHeading' => 'value', 'foo' => 'bar']) ?> +``` + +## Blocks + +Blocks are main building elements that contain template snippets and can be +included into the parent file(s). + +Block is started with the `$this->start('block_name')` call and ends with +`$this->end('block_name')`: + +```php +start('block_name') ?> +

Heading

+ +

...

+end('block_name') ?> +``` + +### Appending blocks + +Block content can be appended to existing blocks by the +`$this->append('block_name')`. + +The `templates/layout.php`: + +```html + + + + block('content'); ?> + + block('scripts'); ?> + + +``` + +The `templates/pages/index.php`: + +```php +extends('layout.php'); ?> + +start('scripts'); ?> + +end('scripts'); ?> + +start('content') ?> + include('forms/form.php') ?> +end('content') ?> +``` + +The `templates/forms/form.php`: + +```php +
+ + +
+ +append('scripts'); ?> + +end('scripts'); ?> +``` + +The final rendered page: + +```html + + + +
+ + +
+ + + + + +``` + +## Helpers + +Registering additional template helpers can be useful when a custom function or +class method needs to be called in the template. + +### Registering function + +```php +$template->register('formatDate', function (int $timestamp): string { + return gmdate('Y-m-d H:i e', $timestamp - date('Z', $timestamp)); +}); +``` + +### Registering object method + +```php +$template->register('doSomething', [$object, 'methodName']); +``` + +Using helpers in templates: + +```php +

Time: formatDate(time()) ?>

+
doSomething('arguments') ?>
+``` + +## Escaping + +When protecting against XSS there are two built-in methods provided. + +To replace all characters to their applicable HTML entities in the given string: + +```php +noHtml($var) ?> +``` + +To escape given string and still preserve certain characters as HTML: + +```php +e($var) ?> +``` diff --git a/include/auth.php b/include/auth.php new file mode 100644 index 00000000..e9d10520 --- /dev/null +++ b/include/auth.php @@ -0,0 +1,31 @@ +handle; +} elseif (!empty($_SESSION['user'])) { + $isLoggedIn = true; + $username = $_SESSION['user']; +} else { + $isLoggedIn = false; + $username = ''; +} + +$template->assign([ + 'authIsLoggedIn' => $isLoggedIn, + 'authUsername' => $username, + 'authRole' => $logged_in, +]); diff --git a/include/classes/bug_diff_renderer.php b/include/classes/bug_diff_renderer.php deleted file mode 100644 index 8ecfb5b0..00000000 --- a/include/classes/bug_diff_renderer.php +++ /dev/null @@ -1,65 +0,0 @@ -diff = $d; - parent::Text_Diff_Renderer(); - } - - function _blockHeader($xbeg, $xlen, $ybeg, $ylen) - { - $removed = $xlen - $ylen; - if ($removed > 0) { - return 'Line ' . $xbeg . ' (now ' . $ybeg . '), was ' . $xlen . ' lines, now ' . $ylen . ' lines'; - } - } - - function _added($lines) - { - array_walk($lines, create_function('&$a,$b', '$a=htmlspecialchars($a);')); - return ' ' . implode("\n ", $lines) . ''; - } - - function _context($lines) - { - array_walk($lines, create_function('&$a,$b', '$a=htmlspecialchars($a);')); - return "\n" . parent::_context($lines); - } - - function _deleted($lines) - { - array_walk($lines, create_function('&$a,$b', '$a=htmlspecialchars($a);')); - return ' ' . implode("\n ", $lines) . ''; - } - - function _changed($orig, $final) - { - return $this->_deleted($orig) . "\n" . $this->_added($final); - } - - function render() - { - return parent::render($this->diff); - } -} diff --git a/include/classes/bug_ghpulltracker.php b/include/classes/bug_ghpulltracker.php deleted file mode 100644 index 6bc0ad28..00000000 --- a/include/classes/bug_ghpulltracker.php +++ /dev/null @@ -1,77 +0,0 @@ -_dbh = $GLOBALS['dbh']; - } - - private function getDataFromGithub($repo, $pull_id) - { - $ctxt = stream_context_create(array( - 'http' => array( - 'ignore_errors' => '1', - 'user_agent' => $this->userAgent, - ) - )); - $data = @json_decode(file_get_contents("https://api.github.com/repos/php/".urlencode($repo).'/pulls/'.((int)$pull_id), null, $ctxt)); - if (!is_object($data)) { - return false; - } - return $data; - } - - /** - * Attach a pull request to this bug - */ - function attach($bugid, $repo, $pull_id, $developer) - { - $data = $this->getDataFromGithub($repo, $pull_id); - if (!$data) { - return PEAR::raiseError('Failed to retrieve pull request from GitHub'); - } - PEAR::pushErrorHandling(PEAR_ERROR_RETURN); - $e = $this->_dbh->prepare('INSERT INTO bugdb_pulls - (bugdb_id, github_repo, github_pull_id, github_title, github_html_url, developer) VALUES (?, ?, ?, ?, ?, ?)')->execute( - array($bugid, $repo, $pull_id, $data->title, $data->html_url, $developer)); - PEAR::popErrorHandling(); - if (PEAR::isError($e)) { - return $e; - } - - return $data; - } - - /** - * Remove a pull request from this bug - */ - function detach($bugid, $repo, $pull_id) - { - $this->_dbh->prepare('DELETE FROM bugdb_pulls - WHERE bugdb_id = ? and github_repo = ? and github_pull_id = ?')->execute( - array($bugid, $repo, $pull_id)); - } - - /** - * Retrieve a listing of all pull requests - * - * @param int $bugid - * @return array - */ - function listPulls($bugid) - { - $query = ' - SELECT github_repo, github_pull_id, github_title, github_html_url, developer - FROM bugdb_pulls - WHERE bugdb_id = ? - ORDER BY github_repo, github_pull_id DESC - '; - - return $this->_dbh->prepare($query)->execute(array($bugid))->fetchAll(MDB2_FETCHMODE_ASSOC); - } -} diff --git a/include/classes/bug_patchtracker.php b/include/classes/bug_patchtracker.php deleted file mode 100644 index 93c6051b..00000000 --- a/include/classes/bug_patchtracker.php +++ /dev/null @@ -1,366 +0,0 @@ -_upload = false; - $this->_dbh = $GLOBALS['dbh']; - return; - } - } - $this->_upload = new HTTP_Upload('en'); - $this->_dbh = $GLOBALS['dbh']; - } - - /** - * Return the directory in which patches should be stored - * - * @param int $bugid - * @param string $name name of this patch line - * @return string - */ - function patchDir($bugid, $name) - { - return BUG_PATCHTRACKER_TMPDIR . '/p' . $bugid . '/' . $name; - } - /** - * Create the directory in which patches for this bug ID will be stored - * - * @param int $bugid - */ - function setupPatchDir($bugid, $name) - { - if (file_exists($this->patchDir($bugid, $name))) { - if (!is_dir($this->patchDir($bugid, $name))) { - return PEAR::raiseError('Cannot create patch storage for Bug #' . $bugid . - ', storage directory exists and is not a directory'); - } - return; - } - if (!file_exists(dirname($this->patchDir($bugid, $name)))) { - // setup bug directory - if (!@mkdir(dirname($this->patchDir($bugid, $name)))) { - require_once 'PEAR.php'; - return PEAR::raiseError('Cannot create patch storage for Bug #' . $bugid); - } - } elseif (!is_dir(dirname($this->patchDir($bugid, $name)))) { - return PEAR::raiseError('Cannot create patch storage for Bug #' . $bugid . - ', storage directory exists and is not a directory'); - } - // setup patch directory - if (!@mkdir($this->patchDir($bugid, $name))) { - require_once 'PEAR.php'; - return PEAR::raiseError('Cannot create patch storage for Bug #' . $bugid); - } - } - - /** - * Retrieve a unique, ordered patch filename - * - * @param int $bugid - * @param string $patch - * @return array array(revision, patch file name) - */ - function newPatchFileName($bugid, $patch, $handle) - { - $id = time(); - PEAR::pushErrorHandling(PEAR_ERROR_RETURN); - $e = $this->_dbh->prepare('INSERT INTO bugdb_patchtracker - (bugdb_id, patch, revision, developer) VALUES(?, ?, ?, ?)')->execute( - array($bugid, $patch, $id, $handle)); - if (PEAR::isError($e)) { - // try with another timestamp - $id++; - $e = $this->_dbh->prepare('INSERT INTO bugdb_patchtracker - (bugdb_id, patch, revision, developer) VALUES(?, ?, ?, ?)')->execute( - array($bugid, $patch, $id, $handle)); - } - PEAR::popErrorHandling(); - if (PEAR::isError($e)) { - return PEAR::raiseError("Could not get unique patch file name for bug #{$bugid}, patch \"{$patch}\""); - } - return array($id, $this->getPatchFileName($id)); - } - - /** - * Retrieve the name of the patch file on the system - * - * @param int $id revision ID - * @return string - */ - function getPatchFileName($id) - { - return 'p' . $id . '.patch.txt'; - } - - /** - * Retrieve the full path to a patch file - * - * @param int $bugid - * @param string $name - * @param int $revision - * @return string - */ - function getPatchFullpath($bugid, $name, $revision) - { - return $this->patchDir($bugid, $name) . - DIRECTORY_SEPARATOR . $this->getPatchFileName($revision); - } - - /** - * Attach a patch to this bug - * - * @param int $bugid - * @param string $patch uploaded patch filename form variable - * @param string $name patch name (allows several patches to be versioned) - * @param string $handle developer handle - * @param array $obsoletes obsoleted patches - * @return int patch revision - */ - function attach($bugid, $patch, $name, $handle, $obsoletes) - { - if (!$this->_upload) { - return PEAR::raiseError('Upload directory for patches could not be initialized'); - } - if (!preg_match('/^[\w\-\.]+\z/', $name) || strlen($name) > 80) { - return PEAR::raiseError("Invalid patch name \"".htmlspecialchars($name)."\""); - } - if (!is_array($obsoletes)) { - return PEAR::raiseError('Invalid obsoleted patches'); - } - - $file = $this->_upload->getFiles($patch); - if (PEAR::isError($file)) { - return $file; - } - - if ($file->isValid()) { - $newobsoletes = array(); - foreach ($obsoletes as $who) { - if (!$who) { - continue; // remove (none) - } - $who = explode('#', $who); - if (count($who) != 2) { - continue; - } - if (file_exists($this->getPatchFullpath($bugid, $who[0], $who[1]))) { - $newobsoletes[] = $who; - } - } - if (PEAR::isError($e = $this->setupPatchDir($bugid, $name))) { - return $e; - } - - $res = $this->newPatchFileName($bugid, $name, $handle); - if (PEAR::isError($res)) { - return $res; - } - list($id, $fname) = $res; - $file->setName($fname); - $allowed_mime_types = array( - 'application/x-txt', - 'text/plain', - 'text/x-diff', - 'text/x-patch', - 'text/x-c++', - 'text/x-c', - 'text/x-m4', - ); - - // return mime type ala mimetype extension - if (class_exists('finfo')) { - $finfo = new finfo(FILEINFO_MIME); - if (!$finfo) { - return PEAR::raiseError('Error: Opening fileinfo database failed'); - } - - // get mime-type for a specific file - $mime = $finfo->file($file->getProp('tmp_name')); - // get rid of the charset part - $t = explode(';', $mime); - $mime = $t[0]; - } - else // NOTE: I didn't have PHP 5.3 around with fileinfo enabled :) - { - $mime = 'text/plain'; - } - if (!in_array($mime, $allowed_mime_types)) { - $this->_dbh->prepare('DELETE FROM bugdb_patchtracker - WHERE bugdb_id = ? and patch = ? and revision = ?')->execute( - array($bugid, $name, $id)); - return PEAR::raiseError('Error: uploaded patch file must be text' - . ' file (save as e.g. "patch.txt" or "package.diff")' - . ' (detected "' . htmlspecialchars($mime) . '")' - ); - } - $tmpfile = $file->moveTo($this->patchDir($bugid, $name)); - if (PEAR::isError($tmpfile)) { - $this->_dbh->prepare('DELETE FROM bugdb_patchtracker - WHERE bugdb_id = ? and patch = ? and revision = ?')->execute( - array($bugid, $name, $id)); - return $tmpfile; - } - if (!$file->getProp('size')) { - $this->detach($bugid, $name, $id); - return PEAR::raiseError('zero-length patches not allowed'); - } - if ($file->getProp('size') > 102400) { - $this->detach($bugid, $name, $id); - return PEAR::raiseError('Patch files cannot be larger than 100k'); - } - foreach ($newobsoletes as $obsolete) { - $this->obsoletePatch($bugid, $name, $id, $obsolete[0], $obsolete[1]); - } - return $id; - } elseif ($file->isMissing()) { - return PEAR::raiseError('Uploaded file is empty or nothing was uploaded.'); - } elseif ($file->isError()) { - return PEAR::raiseError($file->errorMsg()); - } - return PEAR::raiseError('Unable to attach patch (try renaming the file with .txt extension)'); - } - - /** - * Remove a patch revision from this bug - * - * @param int $bugid - * @param string $name - * @param int $revision - */ - function detach($bugid, $name, $revision) - { - $this->_dbh->prepare('DELETE FROM bugdb_patchtracker - WHERE bugdb_id = ? and patch = ? and revision = ?')->execute( - array($bugid, $name, $revision)); - @unlink($this->patchDir($bugid, $name) . DIRECTORY_SEPARATOR . - $this->getPatchFileName($revision)); - } - - /** - * Retrieve the actual contents of the patch - * - * @param int $bugid - * @param string $name - * @param int $revision - * @return string - */ - function getPatch($bugid, $name, $revision) - { - if ($this->_dbh->prepare(' - SELECT bugdb_id - FROM bugdb_patchtracker - WHERE bugdb_id = ? AND patch = ? AND revision = ?')->execute(array($bugid, $name, $revision))->fetchOne() - ) { - $contents = @file_get_contents($this->getPatchFullpath($bugid, $name, $revision)); - if (!$contents) { - return PEAR::raiseError('Cannot retrieve patch revision "' . $revision . '" for patch "' . $name . '"'); - } - return $contents; - } - return PEAR::raiseError('No such patch revision "' . $revision . '", or no such patch "' . $name . '"'); - } - - /** - * Retrieve a listing of all patches and their revisions - * - * @param int $bugid - * @return array - */ - function listPatches($bugid) - { - $query = ' - SELECT patch, revision, developer - FROM bugdb_patchtracker - WHERE bugdb_id = ? - ORDER BY revision DESC - '; - - return $this->_dbh->prepare($query)->execute(array($bugid))->fetchAll(MDB2_FETCHMODE_ORDERED, true, false, true); - } - - /** - * Retrieve a listing of all patches and their revisions - * - * @param int $bugid - * @param string $patch - * @return array - */ - function listRevisions($bugid, $patch) - { - $query = ' - SELECT revision FROM bugdb_patchtracker - WHERE bugdb_id = ? AND patch = ? - ORDER BY revision DESC - '; - return $this->_dbh->prepare($query)->execute(array($bugid, $patch))->fetchAll(MDB2_FETCHMODE_ORDERED); - } - - /** - * Retrieve the developer who uploaded this patch - * - * @param int $bugid - * @param string $patch - * @param int $revision - * @return string|array array if no revision is selected - */ - function getDeveloper($bugid, $patch, $revision = false) - { - if ($revision) { - return $this->_dbh->prepare(' - SELECT developer - FROM bugdb_patchtracker - WHERE bugdb_id = ? AND patch = ? AND revision = ? - ')->execute(array($bugid, $patch, $revision))->fetchOne(); - } - return $this->_dbh->prepare(' - SELECT developer, revision - FROM bugdb_patchtracker - WHERE bugdb_id = ? AND patch = ? ORDER BY revision DESC - ')->execute(array($bugid, $patch))->fetchAll(MDB2_FETCHMODE_ASSOC); - } - - function getObsoletingPatches($bugid, $patch, $revision) - { - return $this->_dbh->prepare(' - SELECT bugdb_id, patch, revision - FROM bugdb_obsoletes_patches - WHERE bugdb_id = ? AND obsolete_patch = ? AND obsolete_revision = ? - ')->execute(array($bugid, $patch, $revision))->fetchAll(MDB2_FETCHMODE_ASSOC); - } - - function getObsoletePatches($bugid, $patch, $revision) - { - return $this->_dbh->prepare(' - SELECT bugdb_id, obsolete_patch, obsolete_revision - FROM bugdb_obsoletes_patches - WHERE bugdb_id = ? AND patch = ? AND revision = ? - ')->execute(array($bugid, $patch, $revision))->fetchAll(MDB2_FETCHMODE_ASSOC); - } - - /** - * link to an obsolete patch from the new one - * - * @param int $bugid - * @param string $name better patch name - * @param int $revision better patch revision - * @param string $obsoletename - * @param int $obsoleterevision - */ - function obsoletePatch($bugid, $name, $revision, $obsoletename, $obsoleterevision) - { - $this->_dbh->prepare(' - INSERT INTO bugdb_obsoletes_patches - VALUES(?, ?, ?, ?, ?) - ')->execute(array($bugid, $name, $revision, $obsoletename, $obsoleterevision)); - } -} diff --git a/include/functions.php b/include/functions.php index 7ed8ae3e..381dbc57 100644 --- a/include/functions.php +++ b/include/functions.php @@ -9,304 +9,258 @@ /* Contains functions and variables used throughout the bug system */ // used in mail_bug_updates(), below, and class for search results -$tla = array( - 'Open' => 'Opn', - 'Not a bug' => 'Nab', - 'Feedback' => 'Fbk', - 'No Feedback' => 'NoF', - 'Wont fix' => 'Wfx', - 'Duplicate' => 'Dup', - 'Critical' => 'Ctl', - 'Assigned' => 'Asn', - 'Analyzed' => 'Ana', - 'Verified' => 'Ver', - 'Suspended' => 'Sus', - 'Closed' => 'Csd', - 'Spam' => 'Spm', - 'Re-Opened' => 'ReO', -); - -$bug_types = array( - 'Bug' => 'Bug', - 'Feature/Change Request' => 'Req', - 'Documentation Problem' => 'Doc', - 'Security' => 'Sec Bug' -); - -$project_types = array( - 'PHP' => 'php', - 'PECL' => 'pecl' -); +$tla = [ + 'Open' => 'Opn', + 'Not a bug' => 'Nab', + 'Feedback' => 'Fbk', + 'No Feedback' => 'NoF', + 'Wont fix' => 'Wfx', + 'Duplicate' => 'Dup', + 'Critical' => 'Ctl', + 'Assigned' => 'Asn', + 'Analyzed' => 'Ana', + 'Verified' => 'Ver', + 'Suspended' => 'Sus', + 'Closed' => 'Csd', + 'Spam' => 'Spm', + 'Re-Opened' => 'ReO', +]; + +$bug_types = [ + 'Bug' => 'Bug', + 'Feature/Change Request' => 'Req', + 'Documentation Problem' => 'Doc', + 'Security' => 'Sec Bug' +]; // Used in show_state_options() -$state_types = array ( - 'Open' => 2, - 'Closed' => 2, - 'Re-Opened' => 1, - 'Duplicate' => 1, - 'Critical' => 1, - 'Assigned' => 2, - 'Not Assigned' => 0, - 'Analyzed' => 1, - 'Verified' => 1, - 'Suspended' => 1, - 'Wont fix' => 1, - 'No Feedback' => 1, - 'Feedback' => 1, - 'Old Feedback' => 0, - 'Stale' => 0, - 'Fresh' => 0, - 'Not a bug' => 1, - 'Spam' => 1, - 'All' => 0, -); +$state_types = [ + 'Open' => 2, + 'Closed' => 2, + 'Re-Opened' => 1, + 'Duplicate' => 1, + 'Critical' => 1, + 'Assigned' => 2, + 'Not Assigned' => 0, + 'Analyzed' => 1, + 'Verified' => 1, + 'Suspended' => 1, + 'Wont fix' => 1, + 'No Feedback' => 1, + 'Feedback' => 1, + 'Old Feedback' => 0, + 'Stale' => 0, + 'Fresh' => 0, + 'Not a bug' => 1, + 'Spam' => 1, + 'All' => 0, +]; /** * Authentication */ -function verify_password($user, $pass) +function verify_user_password($user, $pass) { - global $errors; - - $post = http_build_query( - array( - 'token' => getenv('AUTH_TOKEN'), - 'username' => $user, - 'password' => $pass, - ) - ); - - $opts = array( - 'method' => 'POST', - 'header' => 'Content-type: application/x-www-form-urlencoded', - 'content' => $post, - ); - - $ctx = stream_context_create(array('http' => $opts)); - - $s = file_get_contents('https://master.php.net/fetch/cvsauth.php', false, $ctx); - - $a = @unserialize($s); - if (!is_array($a)) { - $errors[] = "Failed to get authentication information.\nMaybe master is down?\n"; - return false; - } - if (isset($a['errno'])) { - $errors[] = "Authentication failed: {$a['errstr']}\n"; - return false; - } + global $errors; + + $post = http_build_query( + [ + 'token' => getenv('AUTH_TOKEN'), + 'username' => $user, + 'password' => $pass, + ] + ); + + $opts = [ + 'method' => 'POST', + 'header' => 'Content-type: application/x-www-form-urlencoded', + 'content' => $post, + ]; + + $ctx = stream_context_create(['http' => $opts]); + + $s = file_get_contents('https://main.php.net/fetch/cvsauth.php', false, $ctx); + + $a = @unserialize($s); + if (!is_array($a)) { + $errors[] = "Failed to get authentication information.\nMaybe master is down?\n"; + return false; + } + if (isset($a['errno'])) { + $errors[] = "Authentication failed: {$a['errstr']}\n"; + return false; + } $_SESSION["user"] = $user; - return true; + return true; } function bugs_has_access ($bug_id, $bug, $pw, $user_flags) { - global $auth_user; - - if ($bug['private'] != 'Y') { - return true; - } - - // When the bug is private, only the submitter, trusted devs, security devs and assigned dev - // should see the report info - if ($user_flags & (BUGS_SECURITY_DEV | BUGS_TRUSTED_DEV)) { - // trusted and security dev - return true; - } else if (($user_flags == BUGS_NORMAL_USER) && $pw != '' && verify_bug_passwd($bug_id, bugs_get_hash($pw))) { - // The submitter - return true; - } else if (($user_flags & BUGS_DEV_USER) && $bug['reporter_name'] != '' && - strtolower($bug['reporter_name']) == strtolower($auth_user->handle)) { - // The submitter (php developer) - return true; - } else if (($user_flags & BUGS_DEV_USER) && $bug['assign'] != '' && - strtolower($bug['assign']) == strtolower($auth_user->handle)) { - // The assigned dev - return true; - } - - return false; + global $auth_user; + + if ($bug['private'] != 'Y') { + return true; + } + + // When the bug is private, only the submitter, trusted devs, security devs and assigned dev + // should see the report info + if ($user_flags & (BUGS_SECURITY_DEV | BUGS_TRUSTED_DEV)) { + // trusted and security dev + return true; + } else if (($user_flags == BUGS_NORMAL_USER) && $pw != '' && verify_bug_passwd($bug_id, bugs_get_hash($pw))) { + // The submitter + return true; + } else if (($user_flags & BUGS_DEV_USER) && $bug['reporter_name'] != '' && + strtolower($bug['reporter_name']) == strtolower($auth_user->handle)) { + // The submitter (php developer) + return true; + } else if (($user_flags & BUGS_DEV_USER) && $bug['assign'] != '' && + strtolower($bug['assign']) == strtolower($auth_user->handle)) { + // The assigned dev + return true; + } + + return false; } function bugs_authenticate (&$user, &$pw, &$logged_in, &$user_flags) { - global $auth_user, $ROOT_DIR; - - // Default values - $user = ''; - $pw = ''; - $logged_in = false; - - $user_flags = BUGS_NORMAL_USER; - - // Set username and password - if (!empty($_POST['pw'])) { - if (empty($_POST['user'])) { - $user = ''; - } else { - $user = htmlspecialchars($_POST['user']); - } - $user = strtolower($user); - $pw = $_POST['pw']; - } elseif (isset($auth_user) && is_object($auth_user) && $auth_user->handle) { - $user = $auth_user->handle; - $pw = $auth_user->password; - } - - // Authentication and user level check - // User levels are: reader (0), commenter/patcher/etc. (edit = 3), submitter (edit = 2), developer (edit = 1) - if (!empty($_SESSION["user"])) { - $user = $_SESSION["user"]; - $user_flags = BUGS_DEV_USER; - $logged_in = 'developer'; - $auth_user = new stdClass; - $auth_user->handle = $user; - $auth_user->email = "{$user}@php.net"; - $auth_user->name = $user; - } elseif ($user != '' && $pw != '' && verify_password($user, $pw)) { - $user_flags = BUGS_DEV_USER; - $logged_in = 'developer'; - $auth_user = new stdClass; - $auth_user->handle = $user; - $auth_user->email = "{$user}@php.net"; - $auth_user->name = $user; - } else { - $auth_user = new stdClass; - $auth_user->email = isset($_POST['in']['email']) ? $_POST['in']['email'] : ''; - $auth_user->handle = ''; - $auth_user->name = ''; - } - - // Check if developer is trusted - if ($logged_in == 'developer') { - require_once "{$ROOT_DIR}/include/trusted-devs.php"; - - if (in_array(strtolower($user), $trusted_developers)) { - $user_flags |= BUGS_TRUSTED_DEV; - } - if (in_array(strtolower($user), $security_developers)) { - $user_flags |= BUGS_SECURITY_DEV; - } - } -} + global $auth_user, $ROOT_DIR; + + // Default values + $user = ''; + $pw = ''; + $logged_in = false; + + $user_flags = BUGS_NORMAL_USER; + + // Set username and password + if (!empty($_POST['pw'])) { + if (empty($_POST['user'])) { + $user = ''; + } else { + $user = htmlspecialchars($_POST['user']); + } + $user = strtolower($user); + $pw = $_POST['pw']; + } elseif (isset($auth_user) && is_object($auth_user) && $auth_user->handle) { + $user = $auth_user->handle; + $pw = $auth_user->password; + } -/** - * Fetches pseudo packages from database - * - * @param string $project define what project pseudo packages are returned - * @param bool $return_disabled whether to return read-only items, defaults to true - * - * @return array array of pseudo packages - */ -function get_pseudo_packages($project, $return_disabled = true) -{ - global $dbh, $project_types; - - $pseudo_pkgs = $nodes = $tree = array(); - $where = '1=1'; - $project = strtolower($project); - - if ($project !== false && in_array($project, $project_types)) { - $where .= " AND project IN ('', '$project')"; - } - if (!$return_disabled) { - $where.= " AND disabled = 0"; - } - - $data = $dbh->queryAll("SELECT * FROM bugdb_pseudo_packages WHERE $where ORDER BY parent, disabled, id", null, MDB2_FETCHMODE_ASSOC); - - // Convert flat array to nested strucutre - foreach ($data as &$node) - { - $node['children'] = array(); - $id = $node['id']; - $parent_id = $node['parent']; - $nodes[$id] =& $node; - - if (array_key_exists($parent_id, $nodes)) { - $nodes[$parent_id]['children'][] =& $node; - } else { - $tree[] =& $node; - } - } - - foreach ($tree as $data) - { - if (isset($data['children'])) { - $pseudo_pkgs[$data['name']] = array($data['long_name'], $data['disabled']); - $long_names = array(); - foreach ($data['children'] as $k => $v) { - $long_names[$k] = strtolower($v['long_name']); - } - array_multisort($long_names, SORT_ASC, SORT_STRING, $data['children']); - foreach ($data['children'] as $child) - { - $pseudo_pkgs[$child['name']] = array("    {$child['long_name']}", $child['disabled']); - } - - } elseif (!isset($pseudo_pkgs[$data['name']])) { - $pseudo_pkgs[$data['name']] = array($data['long_name'], $data['disabled']); - } - } - - return $pseudo_pkgs; + // Authentication and user level check + // User levels are: reader (0), commenter/patcher/etc. (edit = 3), submitter (edit = 2), developer (edit = 1) + if (!empty($_SESSION["user"])) { + $user = $_SESSION["user"]; + $user_flags = BUGS_DEV_USER; + $logged_in = 'developer'; + $auth_user = new stdClass; + $auth_user->handle = $user; + $auth_user->email = "{$user}@php.net"; + $auth_user->name = $user; + } elseif ($user != '' && $pw != '' && verify_user_password($user, $pw)) { + $user_flags = BUGS_DEV_USER; + $logged_in = 'developer'; + $auth_user = new stdClass; + $auth_user->handle = $user; + $auth_user->email = "{$user}@php.net"; + $auth_user->name = $user; + } else { + $auth_user = new stdClass; + $auth_user->email = isset($_POST['in']['email']) ? $_POST['in']['email'] : ''; + $auth_user->handle = ''; + $auth_user->name = ''; + } + + // Check if developer is trusted + if ($logged_in == 'developer') { + require_once "{$ROOT_DIR}/include/trusted-devs.php"; + + if (in_array(strtolower($user), $trusted_developers)) { + $user_flags |= BUGS_TRUSTED_DEV; + } + if (in_array(strtolower($user), $security_developers)) { + $user_flags |= BUGS_SECURITY_DEV; + } + } } /* Primitive check for SPAM. Add more later. */ function is_spam($string) { - // @php.net users are given permission to spam... we gotta eat! See also bug #48126 - if (!empty($GLOBALS['auth_user']->handle)) { - return false; - } - - if (substr_count(strtolower($string), 'http://') > 5) { - return true; - } - - $keywords = array( - 'spy', - 'bdsm', - 'massage', - 'mortage', - 'sex', - '11nong', - 'oxycontin', - 'distance-education', - 'sismatech', - 'justiceplan', - 'prednisolone', - 'baclofen', - 'diflucan', - 'unbra.se', - 'objectis', - 'angosso', - 'colchicine', - 'zovirax', - 'korsbest', - 'coachbags', - 'chaneljpoutlet', - '\/Members\/', - 'michaelkorsshop', - 'mkmichaelkors', - 'Burberrysale4u', - 'gadboisphotos', - 'oakleysunglasseslol', - 'partydressuk', - 'leslunettesdesoleil', - 'PaulRGuthrie', - '[a-z]*?fuck[a-z]*?', - ); - - if (preg_match('/\b('. implode('|', $keywords) . ')\b/i', $string)) { - return true; - } - - return false; + // @php.net users are given permission to spam... we gotta eat! See also bug #48126 + if (!empty($GLOBALS['auth_user']->handle)) { + return false; + } + + if (preg_match_all('/https?:\/\/(\S+)/', $string, $matches)) { + foreach ($matches[1] as $match) { + if (!preg_match('/^[^\/)]*(php\.net|github\.com)/', $match)) { + return "Due to large amounts of spam, only links to php.net and github.com (including subdomains like gist.github.com) are allowed."; + } + } + } + + $keywords = [ + 'spy', + 'bdsm', + 'massage', + 'mortage', + 'sex', + '11nong', + 'oxycontin', + 'distance-education', + 'sismatech', + 'justiceplan', + 'prednisolone', + 'baclofen', + 'diflucan', + 'unbra.se', + 'objectis', + 'angosso', + 'colchicine', + 'zovirax', + 'korsbest', + 'coachbags', + 'chaneljpoutlet', + '\/Members\/', + 'michaelkorsshop', + 'mkmichaelkors', + 'Burberrysale4u', + 'gadboisphotos', + 'oakleysunglasseslol', + 'partydressuk', + 'leslunettesdesoleil', + 'PaulRGuthrie', + '[a-z]*?fuck[a-z]*?', + 'jerseys', + 'wholesale', + 'fashionretailshop01', + 'amoxicillin', + 'helpdeskaustralia', + 'porn', + 'aarinkaur', + 'lildurk', + 'tvfun', + ]; + + if (preg_match('/\b('. implode('|', $keywords) . ')\b/i', $string)) { + return "Comment contains spam word, consider rewording."; + } + + return false; } +/* Primitive check for SPAMmy user. Add more later. */ +function is_spam_user($email) +{ + if (preg_match("/(rhsoft|reindl|phpbugreports|bugreprtsz|bugreports\d*@gmail|training365)/i", $email)) { + return true; + } + return false; +} /** * Obfuscates email addresses to hinder spammer's spiders @@ -314,56 +268,32 @@ function is_spam($string) * Turns "@" into character entities that get interpreted as "at" and * turns "." into character entities that get interpreted as "dot". * - * @param string $txt the email address to be obfuscated - * @param string $format how the output will be displayed ('html', 'text', 'reverse') + * @param string $txt the email address to be obfuscated + * @param string $format how the output will be displayed ('html', 'text', 'reverse') * - * @return string the altered email address + * @return string the altered email address */ function spam_protect($txt, $format = 'html') { - /* php.net addresses are not protected! */ - if (preg_match('/^(.+)@php\.net$/i', $txt)) { - return $txt; - } - if ($format == 'html') { - $translate = array( - '@' => ' at ', - '.' => ' dot ', - ); - } else { - $translate = array( - '@' => ' at ', - '.' => ' dot ', - ); - if ($format == 'reverse') { - $translate = array_flip($translate); - } - } - return strtr($txt, $translate); -} - -/** - * Escape strings so they can be used as literals in queries - * - * @param string|array $in data to be sanitized. If it's an array, each element is sanitized. - * - * @return string|array the sanitized data - * - * @see oneof(), field(), txfield() - */ -function escapeSQL($in) -{ - global $dbh; - - if (is_array($in)) { - $out = array(); - foreach ($in as $key => $value) { - $out[$key] = $dbh->escape($value); - } - return $out; - } else { - return $dbh->escape($in); - } + /* php.net addresses are not protected! */ + if (preg_match('/^(.+)@php\.net$/i', $txt)) { + return $txt; + } + if ($format == 'html') { + $translate = [ + '@' => ' at ', + '.' => ' dot ', + ]; + } else { + $translate = [ + '@' => ' at ', + '.' => ' dot ', + ]; + if ($format == 'reverse') { + $translate = array_flip($translate); + } + } + return strtr($txt, $translate); } /** @@ -372,19 +302,19 @@ function escapeSQL($in) * * Handy function for when you're dealing with user input or a default. * - * @param mixed as many variables as you wish to check + * @param mixed as many variables as you wish to check * - * @return mixed the value, if any + * @return mixed the value, if any * - * @see escapeSQL(), field(), txfield() + * @see field(), txfield() */ function oneof() { - foreach (func_get_args() as $arg) { - if ($arg) { - return $arg; - } - } + foreach (func_get_args() as $arg) { + if ($arg) { + return $arg; + } + } } /** @@ -394,60 +324,60 @@ function oneof() * If the data from a form submission exists, that is used. * But if that's not there, the info is obtained from the database. * - * @param string $n the name of the field to be looked for + * @param string $n the name of the field to be looked for * - * @return mixed the data requested + * @return mixed the data requested * - * @see escapeSQL(), oneof(), txfield() + * @see oneof(), txfield() */ function field($n) { - return oneof(isset($_POST['in']) ? - htmlspecialchars(isset($_POST['in'][$n]) ? $_POST['in'][$n] : '') : null, - htmlspecialchars($GLOBALS['bug'][$n])); + return oneof(isset($_POST['in']) ? + htmlspecialchars(isset($_POST['in'][$n]) ? $_POST['in'][$n] : '') : null, + htmlspecialchars($GLOBALS['bug'][$n] ?? '')); } /** * Escape string so it can be used as HTML * - * @param string $in the string to be sanitized + * @param string $in the string to be sanitized * - * @return string the sanitized string + * @return string the sanitized string * * @see txfield() */ function clean($in) { - return mb_encode_numericentity($in, - array( - 0x0, 0x8, 0, 0xffffff, - 0xb, 0xc, 0, 0xffffff, - 0xe, 0x1f, 0, 0xffffff, - 0x22, 0x22, 0, 0xffffff, - 0x26, 0x27, 0, 0xffffff, - 0x3c, 0x3c, 0, 0xffffff, - 0x3e, 0x3e, 0, 0xffffff, - 0x7f, 0x84, 0, 0xffffff, - 0x86, 0x9f, 0, 0xffffff, - 0xfdd0, 0xfdef, 0, 0xffffff, - 0x1fffe, 0x1ffff, 0, 0xffffff, - 0x2fffe, 0x2ffff, 0, 0xffffff, - 0x3fffe, 0x3ffff, 0, 0xffffff, - 0x4fffe, 0x4ffff, 0, 0xffffff, - 0x5fffe, 0x5ffff, 0, 0xffffff, - 0x6fffe, 0x6ffff, 0, 0xffffff, - 0x7fffe, 0x7ffff, 0, 0xffffff, - 0x8fffe, 0x8ffff, 0, 0xffffff, - 0x9fffe, 0x9ffff, 0, 0xffffff, - 0xafffe, 0xaffff, 0, 0xffffff, - 0xbfffe, 0xbffff, 0, 0xffffff, - 0xcfffe, 0xcffff, 0, 0xffffff, - 0xdfffe, 0xdffff, 0, 0xffffff, - 0xefffe, 0xeffff, 0, 0xffffff, - 0xffffe, 0xfffff, 0, 0xffffff, - 0x10fffe, 0x10ffff, 0, 0xffffff, - ), - 'UTF-8'); + return mb_encode_numericentity($in, + [ + 0x0, 0x8, 0, 0xffffff, + 0xb, 0xc, 0, 0xffffff, + 0xe, 0x1f, 0, 0xffffff, + 0x22, 0x22, 0, 0xffffff, + 0x26, 0x27, 0, 0xffffff, + 0x3c, 0x3c, 0, 0xffffff, + 0x3e, 0x3e, 0, 0xffffff, + 0x7f, 0x84, 0, 0xffffff, + 0x86, 0x9f, 0, 0xffffff, + 0xfdd0, 0xfdef, 0, 0xffffff, + 0x1fffe, 0x1ffff, 0, 0xffffff, + 0x2fffe, 0x2ffff, 0, 0xffffff, + 0x3fffe, 0x3ffff, 0, 0xffffff, + 0x4fffe, 0x4ffff, 0, 0xffffff, + 0x5fffe, 0x5ffff, 0, 0xffffff, + 0x6fffe, 0x6ffff, 0, 0xffffff, + 0x7fffe, 0x7ffff, 0, 0xffffff, + 0x8fffe, 0x8ffff, 0, 0xffffff, + 0x9fffe, 0x9ffff, 0, 0xffffff, + 0xafffe, 0xaffff, 0, 0xffffff, + 0xbfffe, 0xbffff, 0, 0xffffff, + 0xcfffe, 0xcffff, 0, 0xffffff, + 0xdfffe, 0xdffff, 0, 0xffffff, + 0xefffe, 0xeffff, 0, 0xffffff, + 0xffffe, 0xfffff, 0, 0xffffff, + 0x10fffe, 0x10ffff, 0, 0xffffff, + ], + 'UTF-8'); } /** @@ -457,104 +387,70 @@ function clean($in) * If the data from a form submission exists, that is used. * But if that's not there, the info is obtained from the database. * - * @param string $n the name of the field to be looked for + * @param string $n the name of the field to be looked for * - * @return mixed the data requested + * @return mixed the data requested * * @see clean() */ function txfield($n, $bug = null, $in = null) { - $one = (isset($in) && isset($in[$n])) ? $in[$n] : false; - if ($one) { - return $one; - } - - $two = (isset($bug) && isset($bug[$n])) ? $bug[$n] : false; - if ($two) { - return $two; - } + $one = (isset($in) && isset($in[$n])) ? $in[$n] : false; + if ($one) { + return $one; + } + + $two = (isset($bug) && isset($bug[$n])) ? $bug[$n] : false; + if ($two) { + return $two; + } } /** * Prints age \n"; - } - - echo '\n"; -} + for ($i = 10; $i < 100; $i += 10) { + echo '\n"; + } -/** - * Prints bug project \n"; - } else { - echo "\n"; - } - break; - case 'No Feedback': - echo "\n"; - break; - default: - echo "\n"; - break; - } - /* Allow state 'Closed' always when current state is not 'Not a bug' */ - if ($state != 'Not a bug') { - echo "\n"; - } - } else { - foreach($state_types as $type => $mode) { - if (($state == 'Closed' && $type == 'Open') - || ($state == 'Open' && $type == 'Re-Opened')) { - continue; - } - if ($mode >= $user_mode) { - echo '$type\n"; - } - } - } + global $state_types, $tla; + + if (!$state && !$default) { + $state = $assigned ? 'Assigned' : 'Open'; + } elseif (!$state) { + $state = $default; + } + + /* regular users can only pick states with type 2 for unclosed bugs */ + if ($state != 'All' && isset($state_types[$state]) && $state_types[$state] == 1 && $user_mode == 2) { + switch ($state) + { + /* If state was 'Feedback', set state automatically to 'Assigned' if the bug was + * assigned to someone before it to be set to 'Feedback', otherwise set it to 'Open'. + */ + case 'Feedback': + if ($assigned) { + echo ''."\n"; + } else { + echo ''."\n"; + } + break; + case 'No Feedback': + echo ''."\n"; + break; + default: + echo ''.$state.''."\n"; + break; + } + /* Allow state 'Closed' always when current state is not 'Not a bug' */ + if ($state != 'Not a bug') { + echo ''."\n"; + } + } else { + foreach($state_types as $type => $mode) { + if (($state == 'Closed' && $type == 'Open') + || ($state == 'Open' && $type == 'Re-Opened')) { + continue; + } + if ($mode >= $user_mode) { + echo '$type\n"; + } + } + } } /** * Prints bug resolution ' , "\n"; - foreach($versions as $v) { - echo '' , htmlspecialchars($v) , "\n"; - if ($current == $v) { - $use++; - } - } - if (!$use && $current) { - echo '\n"; - } - echo '', "\n"; + global $ROOT_DIR, $versions; + + $use = 0; + + echo '' , "\n"; + foreach($versions as $v) { + echo '' , htmlspecialchars($v) , "\n"; + if ($current == $v) { + $use++; + } + } + if (!$use && $current) { + echo '\n"; + } + echo '', "\n"; } /** * Prints package name