diff --git a/.php-censor.yml b/.php-censor.yml index 9d0c0e80..03ea5b68 100644 --- a/.php-censor.yml +++ b/.php-censor.yml @@ -1,4 +1,5 @@ build_settings: + clone_depth: 1 ignore: - vendor - tests @@ -13,6 +14,11 @@ test: - phpunit.xml coverage: true + php_mess_detector: + allow_failures: true + rules: + - phpmd.xml + php_code_sniffer: standard: PSR2 encoding: UTF-8 @@ -27,6 +33,9 @@ test: php_parallel_lint: allow_failures: true + php_docblock_checker: + allow_failures: true + security_checker: allow_failures: false diff --git a/composer.json b/composer.json index 85dfee1b..5909cce5 100644 --- a/composer.json +++ b/composer.json @@ -76,7 +76,7 @@ "codeception/codeception": "~2.3.0", "phpmd/phpmd": "~2.6.0", "sebastian/phpcpd": "~2.0.0", - "squizlabs/php_codesniffer": "~3.2.0", + "squizlabs/php_codesniffer": "~2.8.0", "block8/php-docblock-checker": "~1.3.0", "phploc/phploc": "~4.0.0", "jakub-onderka/php-parallel-lint": "~0.9.0", diff --git a/composer.lock b/composer.lock index ef6f9e1c..550dfd88 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_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#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8ed0d649c464752b717de050e04cd313", + "content-hash": "6c8facd9ea26fd63006a35d445fac984", "packages": [ { "name": "behat/gherkin", @@ -2959,37 +2959,64 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.2.3", + "version": "2.8.1", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "4842476c434e375f9d3182ff7b89059583aa8b27" + "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/4842476c434e375f9d3182ff7b89059583aa8b27", - "reference": "4842476c434e375f9d3182ff7b89059583aa8b27", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", + "reference": "d7cf0d894e8aa4c73712ee4a331cc1eaa37cdc7d", "shasum": "" }, "require": { "ext-simplexml": "*", "ext-tokenizer": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": ">=5.1.2" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "~4.0" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "scripts/phpcs", + "scripts/phpcbf" ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "2.x-dev" } }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" @@ -3006,7 +3033,7 @@ "phpcs", "standards" ], - "time": "2018-02-20T21:35:23+00:00" + "time": "2017-03-01T22:17:45+00:00" }, { "name": "swiftmailer/swiftmailer", diff --git a/docs/en/README.md b/docs/en/README.md index 02b8c982..963bb57f 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -78,7 +78,7 @@ Plugins * [Campfire](plugins/campfire.md) - `campfire` * [Email](plugins/email.md) - `email` -* [FlowDock](plugins/flowdock_notify.md) - `flowdock_notify` +* FlowDock - `flowdock_notify` * [HipChat](plugins/hipchat_notify.md) - `hipchat_notify` * [IRC](plugins/irc.md) - `irc` * [Slack](plugins/slack_notify.md) - `slack_notify` diff --git a/docs/en/plugins/flowdock_notify.md b/docs/en/plugins/flowdock_notify.md deleted file mode 100644 index aa18edfa..00000000 --- a/docs/en/plugins/flowdock_notify.md +++ /dev/null @@ -1,14 +0,0 @@ -Plugin FlowdockNotify -===================== - -This plugin joins a [Flowdock](https://www.flowdock.com/) room and sends a user-defined message, for example a -"Build Succeeded" message. - -Installation ------------- - -The plugin depends on `mremi/flowdock` library. To use FlowdockNotify plugin you should install dependency: - -``` -composer require "mremi/flowdock" -``` diff --git a/docs/en/plugins/hipchat_notify.md b/docs/en/plugins/hipchat_notify.md index d83ece35..d783af65 100644 --- a/docs/en/plugins/hipchat_notify.md +++ b/docs/en/plugins/hipchat_notify.md @@ -1,18 +1,9 @@ -Plugin HipchatNotify +Plugin Hipchat Notify ===================== This plugin joins a [HipChat](https://www.hipchat.com/) room and sends a user-defined message, for example a "Build Succeeded" message. -Installation ------------- - -The plugin depends on `hipchat/hipchat-php` library. To use HipchatNotify plugin you should install dependency: - -``` -composer require "hipchat/hipchat-php" -``` - Configuration ------------- diff --git a/docs/en/plugins/slack_notify.md b/docs/en/plugins/slack_notify.md index 259c5c79..07222902 100644 --- a/docs/en/plugins/slack_notify.md +++ b/docs/en/plugins/slack_notify.md @@ -1,18 +1,9 @@ -Plugin SlackNotify +Plugin Slack Notify =================== This plugin joins a [Slack](https://www.slack.com/) room and sends a user-defined message, for example a "Build Succeeded" message. -Installation ------------- - -The plugin depends on `maknz/slack` library. To use SlackNotify plugin you should install dependency: - -``` -composer require "maknz/slack" -``` - Configuration ------------- diff --git a/public/assets/js/app.js b/public/assets/js/app.js index 644d7974..c37723cd 100644 --- a/public/assets/js/app.js +++ b/public/assets/js/app.js @@ -498,3 +498,17 @@ var Lang = { return string; } }; + +jquery(document).ready(function() { + jquery('#delete-build').on('click', function (e) { + e.preventDefault(); + + var buildId = this.data('buildId'); + var projectId = this.data('projectId'); + + confirmDelete(APP_URL + 'build/delete/' + buildId) + .onCloseConfirmed = function () { + window.location = APP_URL + 'project/view/' + projectId; + }; + }); +}); diff --git a/src/Controller/BuildController.php b/src/Controller/BuildController.php index 3db8143b..7430449a 100644 --- a/src/Controller/BuildController.php +++ b/src/Controller/BuildController.php @@ -10,6 +10,7 @@ use PHPCensor\Helper\AnsiConverter; use PHPCensor\Helper\Lang; use PHPCensor\Http\Response\RedirectResponse; use PHPCensor\Model\Build; +use PHPCensor\Model\Project; use PHPCensor\Model\User; use PHPCensor\Service\BuildService; use PHPCensor\WebController; @@ -47,8 +48,6 @@ class BuildController extends WebController } /** - * View a specific build. - * * @param integer $buildId * * @throws NotFoundException @@ -143,6 +142,77 @@ class BuildController extends WebController $this->layout->actions = $actions; } + /** + * @param integer $buildId + * + * @throws NotFoundException + */ + public function viewLog($buildId) + { + $build = BuildFactory::getBuildById($buildId); + + if (!$build) { + throw new NotFoundException(Lang::get('build_x_not_found', $buildId)); + } + + /** @var Project $project */ + $project = Factory::getStore('Project')->getByPrimaryKey($build->getProjectId()); + + /** @var \PHPCensor\Store\BuildErrorStore $errorStore */ + $errorStore = Factory::getStore('BuildError'); + + $this->view->build = $build; + $this->view->totalErrors = $errorStore->getErrorTotalForBuild($build->getId()); + + $this->layout->title = Lang::get('build_n', $buildId); + $this->layout->subtitle = $build->getProjectTitle(); + + switch ($build->getStatus()) { + case 0: + $this->layout->skin = 'blue'; + break; + + case 1: + $this->layout->skin = 'yellow'; + break; + + case 2: + $this->layout->skin = 'green'; + break; + + case 3: + $this->layout->skin = 'red'; + break; + } + + $rebuildLabel = Lang::get('rebuild_now'); + $rebuildLink = APP_URL . 'build/rebuild/' . $build->getId(); + + $deleteLabel = Lang::get('delete_build'); + $deleteLink = APP_URL . 'build/delete/' . $build->getId(); + + $actions = ''; + if (!$project->getArchived()) { + $actions .= sprintf( + '%s', + $rebuildLink, + $rebuildLabel + ); + } + + if ($this->currentUserIsAdmin()) { + $actions .= sprintf( + '%s', + $build->getId(), + $build->getProjectId(), + $deleteLink, + $deleteLabel + ); + } + + $this->layout->actions = $actions; + } + /** * Returns an array of the JS plugins to include. * @return array diff --git a/src/Controller/WebhookController.php b/src/Controller/WebhookController.php index 2f987039..e810479c 100644 --- a/src/Controller/WebhookController.php +++ b/src/Controller/WebhookController.php @@ -592,11 +592,11 @@ class WebhookController extends Controller } /** + * Called by Gogs Webhooks: + * * @param string $projectId * * @return array - * - * @throws Exception */ public function gogs($projectId) { @@ -605,17 +605,15 @@ class WebhookController extends Controller Project::TYPE_GIT, ]); - $contentType = !empty($_SERVER['CONTENT_TYPE']) - ? $_SERVER['CONTENT_TYPE'] - : null; - - switch ($contentType) { + switch ($_SERVER['CONTENT_TYPE']) { + case 'application/json': + $payload = json_decode(file_get_contents('php://input'), true); + break; case 'application/x-www-form-urlencoded': $payload = json_decode($this->getParam('payload'), true); break; - case 'application/json': default: - $payload = json_decode(file_get_contents('php://input'), true); + return ['status' => 'failed', 'error' => 'Content type not supported.', 'responseCode' => 401]; } // Handle Push web hooks: diff --git a/src/Helper/Xml.php b/src/Helper/Xml.php deleted file mode 100644 index e3b91876..00000000 --- a/src/Helper/Xml.php +++ /dev/null @@ -1,85 +0,0 @@ -data = preg_replace(self::PATTERN, '', $bucket->data); - $consumed += $bucket->datalen; - - stream_bucket_append($out, $bucket); - } - - return PSFS_PASS_ON; - } -} - -class Xml -{ - /** - * @param $filePath - * - * @return null|\SimpleXMLElement - */ - public static function loadFromFile($filePath) - { - stream_filter_register('xml_utf8_clean', 'PHPCensor\Helper\XmlUtf8CleanFilter'); - - try { - $xml = simplexml_load_file('php://filter/read=xml_utf8_clean/resource=' . $filePath); - } catch (\Exception $ex) { - $xml = null; - } catch (\Throwable $ex) { // since php7 - $xml = null; - } - - if (!$xml) { - // from https://stackoverflow.com/questions/7766455/how-to-handle-invalid-unicode-with-simplexml/8092672#8092672 - $oldUse = libxml_use_internal_errors(true); - - libxml_clear_errors(); - - $dom = new \DOMDocument("1.0", "UTF-8"); - - $dom->strictErrorChecking = false; - $dom->validateOnParse = false; - $dom->recover = true; - - $dom->loadXML(strtr( - file_get_contents($filePath), - ['"' => "'"] // " in attribute names may mislead the parser - )); - - /** @var \LibXMLError $xmlError */ - $xmlError = libxml_get_last_error(); - if ($xmlError) { - $warning = sprintf('L%s C%s: %s', $xmlError->line, $xmlError->column, $xmlError->message); - print 'WARNING: ignored errors while reading phpunit result, '.$warning."\n"; - } - - if (!$dom->hasChildNodes()) { - new \SimpleXMLElement(''); - } - - $xml = simplexml_import_dom($dom); - - libxml_clear_errors(); - libxml_use_internal_errors($oldUse); - } - - return $xml; - } -} diff --git a/src/Plugin/Codeception.php b/src/Plugin/Codeception.php index 585aea4e..d5e8e1d4 100644 --- a/src/Plugin/Codeception.php +++ b/src/Plugin/Codeception.php @@ -145,7 +145,8 @@ class Codeception extends Plugin implements ZeroConfigPluginInterface } } - $parser = new Parser($this->builder, ($outputPath . 'report.xml')); + $xml = file_get_contents($outputPath . 'report.xml', false); + $parser = new Parser($this->builder, $xml); $output = $parser->parse(); $meta = [ diff --git a/src/Plugin/Util/PhpUnitResultJunit.php b/src/Plugin/Util/PhpUnitResultJunit.php index 14ec1c7f..3e149105 100644 --- a/src/Plugin/Util/PhpUnitResultJunit.php +++ b/src/Plugin/Util/PhpUnitResultJunit.php @@ -2,8 +2,6 @@ namespace PHPCensor\Plugin\Util; -use PHPCensor\Helper\Xml; - /** * Class PhpUnitResultJunit parses the results for the PhpUnitV2 plugin * @@ -26,11 +24,11 @@ class PhpUnitResultJunit extends PhpUnitResult $suites = $this->loadResultFile(); - if ($suites) { - foreach ($suites->xpath('//testcase') as $testCase) { - $this->parseTestcase($testCase); - } + foreach ($suites->xpath('//testcase') as $testCase) { + $this->parseTestcase($testCase); } + $suites['failures']; + $suites['errors']; return $this; } @@ -141,7 +139,44 @@ class PhpUnitResultJunit extends PhpUnitResult return new \SimpleXMLElement(''); // new empty element } - return Xml::loadFromFile($this->outputFile); + try { + $suites = simplexml_load_file($this->outputFile); + } catch (\Exception $ex) { + $suites = null; + } catch (\Throwable $ex) { // since php7 + $suites = null; + } + if (!$suites) { + // from https://stackoverflow.com/questions/7766455/how-to-handle-invalid-unicode-with-simplexml/8092672#8092672 + $oldUse = libxml_use_internal_errors(true); + libxml_clear_errors(); + $dom = new \DOMDocument("1.0", "UTF-8"); + $dom->strictErrorChecking = false; + $dom->validateOnParse = false; + $dom->recover = true; + $dom->loadXML(strtr( + file_get_contents($this->outputFile), + array('"' => "'") // " in attribute names may mislead the parser + )); + + /** + * @var \LibXMLError + */ + $xmlError = libxml_get_last_error(); + if ($xmlError) { + $warning = sprintf('L%s C%s: %s', $xmlError->line, $xmlError->column, $xmlError->message); + print 'WARNING: ignored errors while reading phpunit result, '.$warning."\n"; + } + if (!$dom->hasChildNodes()) { + $this->internalProblem('xml file with no content'); + } + $suites = simplexml_import_dom($dom); + + libxml_clear_errors(); + libxml_use_internal_errors($oldUse); + } + + return $suites; } /** @@ -150,5 +185,7 @@ class PhpUnitResultJunit extends PhpUnitResult private function internalProblem($description) { throw new \RuntimeException($description); + + // alternative to error throwing: append to $this->errors } } diff --git a/src/Plugin/Util/TestResultParsers/Codeception.php b/src/Plugin/Util/TestResultParsers/Codeception.php index 90fcbb2e..dfd97cae 100644 --- a/src/Plugin/Util/TestResultParsers/Codeception.php +++ b/src/Plugin/Util/TestResultParsers/Codeception.php @@ -3,7 +3,6 @@ namespace PHPCensor\Plugin\Util\TestResultParsers; use PHPCensor\Builder; -use PHPCensor\Helper\Xml; /** * Class Codeception @@ -12,49 +11,23 @@ use PHPCensor\Helper\Xml; */ class Codeception implements ParserInterface { - /** - * @var Builder - */ protected $builder; - - /** - * @var string - */ - protected $xmlPath; - - /** - * @var array - */ + protected $resultsXml; protected $results; - - /** - * @var int - */ - protected $totalTests = 0; - - /** - * @var int - */ - protected $totalTimeTaken = 0; - - /** - * @var int - */ - protected $totalFailures = 0; - - /** - * @var int - */ - protected $totalErrors = 0; + protected $totalTests; + protected $totalTimeTaken; + protected $totalFailures; + protected $totalErrors; /** * @param Builder $builder - * @param string $xmlPath + * @param $resultsXml */ - public function __construct(Builder $builder, $xmlPath) + public function __construct(Builder $builder, $resultsXml) { - $this->builder = $builder; - $this->xmlPath = $xmlPath; + $this->builder = $builder; + $this->resultsXml = $resultsXml; + $this->totalTests = 0; } /** @@ -63,43 +36,42 @@ class Codeception implements ParserInterface public function parse() { $rtn = []; - $this->results = Xml::loadFromFile($this->xmlPath); + $this->results = new \SimpleXMLElement($this->resultsXml); - if ($this->results) { - foreach ($this->results->testsuite as $testSuite) { - $this->totalTests += (int)$testSuite['tests']; - $this->totalTimeTaken += (float)$testSuite['time']; - $this->totalFailures += (int)$testSuite['failures']; - $this->totalErrors += (int)$testSuite['errors']; + // calculate total results + foreach ($this->results->testsuite as $testSuite) { + $this->totalTests += (int)$testSuite['tests']; + $this->totalTimeTaken += (float)$testSuite['time']; + $this->totalFailures += (int)$testSuite['failures']; + $this->totalErrors += (int)$testSuite['errors']; - foreach ($testSuite->testcase as $testCase) { - $testResult = [ - 'suite' => (string)$testSuite['name'], - 'file' => str_replace($this->builder->buildPath, '/', (string) $testCase['file']), - 'name' => (string)$testCase['name'], - 'feature' => (string)$testCase['feature'], - 'assertions' => (int)$testCase['assertions'], - 'time' => (float)$testCase['time'] - ]; + foreach ($testSuite->testcase as $testCase) { + $testResult = [ + 'suite' => (string)$testSuite['name'], + 'file' => str_replace($this->builder->buildPath, '/', (string) $testCase['file']), + 'name' => (string)$testCase['name'], + 'feature' => (string)$testCase['feature'], + 'assertions' => (int)$testCase['assertions'], + 'time' => (float)$testCase['time'] + ]; - if (isset($testCase['class'])) { - $testResult['class'] = (string) $testCase['class']; - } - - // PHPUnit testcases does not have feature field. Use class::method instead - if (!$testResult['feature']) { - $testResult['feature'] = sprintf('%s::%s', $testResult['class'], $testResult['name']); - } - - if (isset($testCase->failure) || isset($testCase->error)) { - $testResult['pass'] = false; - $testResult['message'] = isset($testCase->failure) ? (string)$testCase->failure : (string)$testCase->error; - } else { - $testResult['pass'] = true; - } - - $rtn[] = $testResult; + if (isset($testCase['class'])) { + $testResult['class'] = (string) $testCase['class']; } + + // PHPUnit testcases does not have feature field. Use class::method instead + if (!$testResult['feature']) { + $testResult['feature'] = sprintf('%s::%s', $testResult['class'], $testResult['name']); + } + + if (isset($testCase->failure) || isset($testCase->error)) { + $testResult['pass'] = false; + $testResult['message'] = isset($testCase->failure) ? (string)$testCase->failure : (string)$testCase->error; + } else { + $testResult['pass'] = true; + } + + $rtn[] = $testResult; } } diff --git a/src/View/Build/errors.phtml b/src/View/Build/errors.phtml index c7b87be6..979f502d 100644 --- a/src/View/Build/errors.phtml +++ b/src/View/Build/errors.phtml @@ -21,7 +21,9 @@ foreach ($errors as $error): getIsNew()): ?> - + + + @@ -29,19 +31,25 @@ foreach ($errors as $error): getSeverityString()); ?> - getPlugin()); ?> - getFile(); ?> + + getPlugin()); ?> + - getLineStart() == $error->getLineEnd() || !$error->getLineEnd()) { - echo $error->getLineStart(); - } else { - echo ($error->getLineStart() . ' - ' . $error->getLineEnd()); - } - ?> + getFile(); ?> - getMessage())); ?> + + + getLineStart() == $error->getLineEnd() || !$error->getLineEnd()): ?> + getLineStart(); ?> + + getLineStart() . ' - ' . $error->getLineEnd()); ?> + + + + + getMessage())); ?> + diff --git a/src/View/Build/viewLog.phtml b/src/View/Build/viewLog.phtml new file mode 100644 index 00000000..a09bc27d --- /dev/null +++ b/src/View/Build/viewLog.phtml @@ -0,0 +1,205 @@ + + +
+
+
+
+

+ +

+
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ + + getProject()->getTitle(); ?> + +
+ getSource()): ?> + + + getRemoteBranch(); ?> : + + + + + getBranch(); ?> + + getTag()): ?> / + + + + +
+ getEnvironment(); + echo !empty($environment) ? (' ' . $environment) : '—' ; + ?> +
+ + getBranch(); ?> + + + getExtra('branches'); + if (!empty($branches)) { + foreach($branches as $branch) { + ?> +
+
+
+
+ +
+
+
+

+ +

+
+ +
+ + + + + + + + + + + + + + + + + + + +
+ getSourceHumanize()); ?> +
+ + getCommitId(), 0, 7); ?> + +
+ getCommitterEmail(); ?> +
+ getCommitMessage()); ?> +
+
+
+
+ +
+
+
+

+ +

+
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ getCreateDate() ? $build->getCreateDate()->format('Y-m-d H:i:s') : ''); ?> +
+ getStartDate() ? $build->getStartDate()->format('Y-m-d H:i:s') : ''); ?> +
+ getFinishDate() ? $build->getFinishDate()->format('Y-m-d H:i:s') : ''); ?> +
+ getDuration(); ?> +
+
+
+
+ +
+ + diff --git a/src/View/WidgetBuildErrors/index.phtml b/src/View/WidgetBuildErrors/index.phtml index 363ae597..2d42181d 100644 --- a/src/View/WidgetBuildErrors/index.phtml +++ b/src/View/WidgetBuildErrors/index.phtml @@ -2,6 +2,10 @@ use PHPCensor\Helper\Lang; +/** + * @var string $projects + */ + ?>