Compare commits

...

182 commits

Author SHA1 Message Date
Arnout Boks 64b0f60368 Removed unused 'standard' option for PhpCpd plugin (#1249) 2016-08-17 12:35:21 +01:00
Dan Cryer 42ca1c6527 Fix for handling old unserialize mode. 2016-06-29 10:13:27 +01:00
Dan Cryer 4d0911f2a9 Bug fixes
Signed-off-by: Dan Cryer <dan@block8.co.uk>
2016-06-23 11:30:41 +01:00
Dan Cryer 3cdaef8fa9 Update README.md 2016-06-03 17:30:45 +01:00
David Rimbault db93f55427 [FIX] Running Complete stage even on Exception catch. (#1186) 2016-04-27 16:59:29 +01:00
Dan Cryer 49db1a26ba
Make sure we always show the correct error count on the build errors tab. 2016-04-27 16:40:55 +01:00
Dan Cryer 77e9710d09
Testing PHPMD error supression 2016-04-27 15:45:24 +01:00
Dan Cryer 7027055711
Merge branch 'feature/bitbucket-webhooks' 2016-04-27 15:35:10 +01:00
Dan Cryer 21ba39e6ff
Fixes 2016-04-27 15:27:27 +01:00
Dan Cryer 6418fde928 Defaulting to using neither --prefer-source or --prefer-dist for composer. Allow user to specify either. 2016-04-27 15:20:20 +01:00
Dan Cryer 21d5f4954f
Adding a debug mode to the worker so you can see what commands are being run. 2016-04-27 14:56:52 +01:00
Dan Cryer 1f035463e4 Merge branch 'patch-4' of https://github.com/wodka/PHPCI into feature/bitbucket-webhooks 2016-04-27 14:26:44 +01:00
Dan Cryer 5ead42a7c2
README tweaks. 2016-04-27 14:18:40 +01:00
Dan Cryer 1e13538c28
Updating README 2016-04-27 14:13:42 +01:00
Dan Cryer 424f1c5c4b Removing Test from README 2016-04-27 14:08:01 +01:00
Dan Cryer dc9aa195d1 Merge branch 'master' of github.com:Block8/PHPCI 2016-04-27 12:35:17 +01:00
Dan Cryer 1e2cbcaf3d Fixing duplicate error listing in builds.
Closes #1130
2016-04-27 12:33:07 +01:00
David Valdez e567088a00 now take in account the errors in the codeception plugin (#1206) 2016-04-27 12:10:46 +01:00
Dan Cryer ffa593f1a3 Fixing PHPCS errors 2016-04-27 12:02:13 +01:00
Dan Cryer cc6d0f2964 Updating dependencies 2016-04-27 11:43:57 +01:00
Dan Cryer e6b02b6019 Merge remote-tracking branch 'origin/master' into v/1.7 2016-04-27 11:32:50 +01:00
Dan Cryer 81eadcf3f5 Contributing guidelines, issue template and PR template. 2016-04-27 11:29:03 +01:00
Dan Cryer f200bd0411 Fixing MySQL strict-mode install error.
Fixes #977
Fixes #818
2016-04-27 11:06:32 +01:00
Mike Bronner fe9289eda5 Remove whitespace from empty lines, reformatted inline comments 2016-02-08 12:59:15 +01:00
Michael Schramm 66ffea12f0 remove whitespace 2016-02-08 12:59:15 +01:00
Michael Schramm 3c8ed45c46 implement bitbucket webhooks
this fixes issue https://github.com/Block8/PHPCI/issues/1015 and adds support for the new bitbucket webhooks
2016-02-08 12:59:15 +01:00
Dan Cryer 2ddda7711e Update README.md 2016-02-08 11:44:35 +00:00
Dan Cryer 42bea5f876 Merge pull request #1154 from staudenmeir/update-german-translation
Update German translation.
2016-01-08 11:04:54 +00:00
Dan Cryer d3af89a0c5 Merge pull request #1157 from Lechus/patch-2
Update Polish translation
2016-01-08 11:03:35 +00:00
Leszek 9af9e0ec35 Update Polish translation
Contribution Type: improvement
Primary Area: front-end

Description of change: Updated Polish translation.
2016-01-06 22:01:38 +00:00
Jonas Staudenmeir 7f43f94cc0 Updated German translation. 2016-01-06 00:24:42 +01:00
Jonas Staudenmeir 949bfc5376 Added default value in settings language select (current language). 2016-01-05 16:21:21 +01:00
Dan Cryer 992cf767a6 Merge pull request #1128 from Block8/Deployer_branch_update
Added branch and update_only parameters
2015-11-24 11:37:16 +00:00
Stephen Ball 6eeddc9dc6 Added branch and update_only parameters
Fixes #1127
2015-11-14 23:08:22 +00:00
Dan Cryer 6eb180ed5f Merge pull request #1124 from Block8/REBELinBLUE-patch-1
Fixes a issue with a double slash in the path meaning ignore doesn't …
2015-11-12 17:47:16 +00:00
Stephen Ball 148e4414fb Fixes a issue with a double slash in the path meaning ignore doesn't work 2015-11-12 17:42:00 +00:00
Dan Cryer b24b01ec65 Fixing tests on install command 2015-11-07 12:47:39 +00:00
Dan Cryer 04c67dc1dd Update installer to support new worker functionality. Closes #1094 2015-11-07 12:25:01 +00:00
Dan Cryer 4b8d25c0f7 Adding rebuild queue option 2015-11-03 20:42:47 +00:00
Dan Cryer 9ea6f299c1 Fix for warnings chart, courtesy of @Henk8 closes #1098 2015-11-03 11:58:22 +00:00
Dan Cryer 7b345cbc49 Making the lines of code chart hide if no data available. Closes #1103 2015-11-03 11:51:45 +00:00
Dan Cryer c95e226496 Allow more errors. Fixes #1104 2015-11-03 11:44:08 +00:00
Dan Cryer 7bfba0ee30 Fixing error duplication bug. Closes #1112 2015-11-03 11:42:28 +00:00
Stephen Ball 551bae312f Merge pull request #1111 from SQweb-team/img_optim
ImageOptim'ed assets/
2015-10-27 10:46:13 +00:00
Pierre Lavaux d8be38ace0 ImageOptim'ed assets/ 2015-10-27 02:17:26 +01:00
Andreus Timm 1d9c610a25 Adding slash in path
Fix / for DIRECTORY_SEPARATOR

Closed #1109
2015-10-26 16:26:47 +01:00
Dan Cryer 943c8124ac Merge pull request #1091 from andreustimm/master
Improvement with rtrim
2015-10-15 14:49:54 +01:00
Dan Cryer f33f0a4512 Fixes 2015-10-15 14:23:55 +01:00
Dan Cryer dd1d3b3d6a Switching charts to use Chart.js instead of Google Charts 2015-10-15 12:34:58 +01:00
Dan Cryer 66cf713ebc Merge branch 'master' of github.com:Block8/PHPCI 2015-10-15 10:41:42 +01:00
Dan Cryer 8fed994921 Fixing login pages 2015-10-15 10:41:35 +01:00
Stephen Ball cb346f6808 Merge pull request #1093 from REBELinBLUE/missing_composer_dependencies
Added missing dependencies to require
2015-10-15 10:30:33 +01:00
Stephen Ball 78690f32c2 Merge branch 'master' into missing_composer_dependencies 2015-10-15 10:26:35 +01:00
Dan Cryer f904cab599 Fixing PHPMD error 2015-10-15 10:26:13 +01:00
Stephen Ball 19a7ab995c Merge branch 'master' into missing_composer_dependencies 2015-10-15 10:21:00 +01:00
Dan Cryer 38b024833f Fixing PHPCI errors 2015-10-15 10:17:22 +01:00
Stephen Ball 7c03c77870 Added missing dependencies to require 2015-10-15 10:11:21 +01:00
Dan Cryer 7f823b37cf Big update: New way of storing build errors, updated UI, AdminLTE 2, fixes, etc. 2015-10-15 10:07:54 +01:00
Andreus Timm d49cecf40a Improvement with rtrim 2015-10-14 14:24:18 -03:00
Dan Cryer 9c7a1c7907 Merge pull request #1085 from corpsee/feature-ru-update-2
Updated russian translation for Summary plugin section
2015-10-14 09:24:55 +01:00
Stephen Ball 82ffbfea5c Merge pull request #1086 from Block8/REBELinBLUE-patch-1
Adding missing closing bracket
2015-10-13 22:23:32 +01:00
Stephen Ball 0f84cc6334 Adding missing closing bracket 2015-10-13 22:02:05 +01:00
corpsee 914067c15a Updated russian translation for Summary plugin section 2015-10-13 22:33:07 +06:00
Dan Cryer 8f3cdd0fb7 Merge pull request #1083 from REBELinBLUE/build_broken_failed
Added 2 new build stages
2015-10-13 16:39:26 +01:00
Dan Cryer 9c2cdbe3d1 Merge pull request #1032 from EmmanuelVella/php-cs-fixer
Update PhpCsFixer plugin
2015-10-13 16:36:21 +01:00
Dan Cryer a821072d27 Merge pull request #1082 from REBELinBLUE/hide_slack_attachment
Add the ability to hide the slack attachment
2015-10-13 16:36:12 +01:00
Stephen Ball 8f8009c30c Fixed parse error..... Apparently I shouldn't be allowed near a computer today 2015-10-13 16:35:27 +01:00
Dan Cryer 2f34c47656 Merge pull request #1076 from corpsee/feature-ru-update
Updated russian translation
2015-10-13 16:33:42 +01:00
Stephen Ball 3f71695013 Clean up PHPCS issue 2015-10-13 16:31:17 +01:00
corpsee 94b79860c5 Merge branch 'master' into feature-ru-update
Conflicts:
	PHPCI/Languages/lang.ru.php
2015-10-13 21:30:54 +06:00
Stephen Ball 4a5b1afb47 Clean up PHPCS issue 2015-10-13 16:28:37 +01:00
Stephen Ball 8e0122f6a0 Added a fixed and broken stage 2015-10-13 16:22:39 +01:00
Stephen Ball 2d667d477d Fix PHPCS error 2015-10-13 16:08:44 +01:00
Stephen Ball d2ab7b300d Add the ability to hide the slack attachment 2015-10-13 16:04:16 +01:00
Emmanuel Vella 6e5ede8073 Update PhpCsFixer plugin 2015-10-13 16:41:41 +02:00
Dan Cryer 6aea2bdb88 Merge pull request #1077 from GeneaLabs/feature/fix-arrangement-of-components
Fix Arrangement of Build Result Components
2015-10-13 15:33:33 +01:00
Dan Cryer 3aeef0650d Merge pull request #1078 from GeneaLabs/feature/fix-bootstrap-datepicker-locale
Fix Datepicker Locale File Include
2015-10-13 15:31:23 +01:00
Dan Cryer 5216270416 Merge pull request #1070 from cooperaj/master
Incorrect docblock
2015-10-13 15:26:57 +01:00
Dan Cryer ad27ca9037 Merge pull request #1025 from delormejonathan/patch-1
Fixed a css issue with Block8 logo
2015-10-13 15:14:59 +01:00
Dan Cryer 1385379f1a Merge pull request #1021 from mrobinsonuk/cleanup/spelling-corrections
Corrected a number of minor spelling errors.
2015-10-13 15:14:44 +01:00
Dan Cryer 10720656f9 Merge pull request #1019 from detain/mysql_plugin_host_support
Contribution Type: improvement / new feature
2015-10-13 15:13:55 +01:00
Dan Cryer 0fbae60f52 Merge pull request #998 from teemule11/bugfix-pdepend-1
Pdepend Module: make check for Pdepend output dir more robust.
2015-10-13 15:04:42 +01:00
Dan Cryer 3d21ad24e9 Merge pull request #993 from corpsee/feature-phpcpd-fix
Fixed GithubBuild::getDiffLineNumber method for correct phpcpd work
2015-10-13 15:04:13 +01:00
Dan Cryer d8cbe68705 Merge pull request #992 from corpsee/feature-phpunit-fixes
Fixed TapParser parse fail
2015-10-13 15:03:09 +01:00
Dan Cryer 6591ada85b Merge pull request #972 from memnuniyetsizim/master
Fixes default value for PHP Cs Fixer ( #833 )
2015-10-13 14:45:20 +01:00
Dan Cryer bee77a25c8 Merge pull request #942 from corpsee/feature-archived-fix
Separated strings for 'archived'/'archived_menu'
2015-10-13 14:33:07 +01:00
Dan Cryer 351b24c91d Merge pull request #939 from drydenmaker/master
adding windows batch file for console
2015-10-13 14:32:12 +01:00
Dan Cryer c49821adf1 Merge pull request #1080 from GeneaLabs/feature/add-logo-icon-images
Add Logo Icons
2015-10-13 14:18:19 +01:00
Dan Cryer ed35dee322 Merge branch 'master' of github.com:Block8/PHPCI 2015-10-13 10:00:17 +01:00
Dan Cryer 156c635af5 Updating deps 2015-10-13 09:59:48 +01:00
Mike Bronner a504789bd4 Merge branch 'master' into feature/add-logo-icon-images 2015-10-12 12:26:42 -07:00
Mike Bronner dc6e4ed6b4 Add logo icons 2015-10-12 12:23:55 -07:00
Dan Cryer c9f6946189 Merge pull request #1079 from GeneaLabs/feature/fix-missing-sprintf-map-file
Add Missing sprintf.min.map File
2015-10-12 16:06:34 +01:00
Mike Bronner 21b59ab4d4 Add missing sprintf.min.map file 2015-10-10 10:07:31 -07:00
Mike Bronner c474a5c73a Check if datepicker locale file exists 2015-10-10 09:54:01 -07:00
Mike Bronner 8845fe63d9 Move id attribute to outer div to hide the result component completely
This will allow the other non-hidden components to properly reflow on the page.
2015-10-10 09:22:53 -07:00
corpsee c9541d630b Updated russian translation 2015-10-10 18:16:07 +06:00
Dan Cryer 97216b0f41 Fixing login stuff 2015-10-09 11:10:14 +01:00
Dan Cryer 725ce70ae9 Fixing ordering on projects for @REBELinBLUE 2015-10-09 10:58:18 +01:00
Dan Cryer edcff1030d Merge pull request #1075 from Block8/dc/phpci-fixes
Fixing PHPCI Errors
2015-10-09 10:40:12 +01:00
Dan Cryer fbc3da59dd Fix 2015-10-09 10:37:50 +01:00
Dan Cryer f747371c6d Fixing tests #2 2015-10-09 10:29:21 +01:00
Dan Cryer 07b92fecf3 Fixing tests 2015-10-09 10:14:54 +01:00
Dan Cryer e61ce203a3 Docblocks 2015-10-09 09:38:55 +01:00
Dan Cryer a008358056 Cleaning up BuildWorker #1 2015-10-09 09:27:45 +01:00
Dan Cryer 68249d2f5d Cleanup of new executor changes. 2015-10-09 09:16:05 +01:00
Dan Cryer 4375c524a9 PHPMD Fix 2015-10-08 20:27:40 +01:00
Dan Cryer af90b94a3c PHPMD Fixes 2015-10-08 20:24:32 +01:00
Dan Cryer 2f859be369 PHPCS Fix 2015-10-08 20:22:43 +01:00
Dan Cryer 370eebd227 Fixing PHPCS error in HomeController 2015-10-08 20:20:42 +01:00
Dan Cryer ada9e33204 Restoring to login by email. 2015-10-08 20:15:20 +01:00
Dan Cryer 4e0325e887 Missing build bugfix. 2015-10-08 17:30:34 +01:00
Dan Cryer e871df80c2 Adding the ability to run branch-specific stages 2015-10-08 16:54:01 +01:00
Dan Cryer a7473eb250 Updating deps. 2015-10-08 16:53:39 +01:00
Dan Cryer 7ad1d317cd Updating builds to try database config first, and fall back to file config. 2015-10-08 16:52:44 +01:00
Dan Cryer 31165a6bfa General model / store cleanup 2015-10-08 16:52:19 +01:00
Dan Cryer 74d313862c Adding project groups 2015-10-08 16:33:01 +01:00
Dan Cryer e186874eba Removing the ability to install 'plugins' via the web interface. 2015-10-08 16:29:29 +01:00
Dan Cryer cf415a7c78 Merge branch 'master' of github.com:Block8/PHPCI 2015-10-08 09:49:20 +01:00
Dan Cryer 2380d2e472 Build worker should delete jobs in its queue that are not recognised. 2015-10-08 09:49:16 +01:00
Dan Cryer 69cd4789be Merge pull request #1073 from REBELinBLUE/add_commit_message_to_deploy_reason
Added the commit message to the BuildInterpolator
2015-10-07 15:08:52 +01:00
Stephen Ball 6f0945fb97 Added the commit message to the BuildInterpolator so it can be used as the default reason passed to deployer 2015-10-07 15:06:07 +01:00
Dan Cryer 857990b776 Update README.md 2015-10-06 19:10:11 +01:00
Dan Cryer 3f8bb1acc4 Merge pull request #1072 from REBELinBLUE/master
Update README.md
2015-10-06 19:09:00 +01:00
Stephen Ball 801a4f7309 Update README.md 2015-10-06 17:28:44 +01:00
Dan Cryer 06e3235960 Add "source" and "url" parameters to Deployer plugin. 2015-10-06 14:16:48 +01:00
Dan Cryer de6eb62367 Deployer plugin 2015-10-06 12:00:58 +01:00
Dan Cryer 7e2fee9504 Verify that jobs actually come from PHPCI 2015-10-06 11:38:09 +01:00
Adam Cooper 668ee67617 Incorrect docblock
The "optionally" is not true, the config file is now required.
2015-10-06 10:53:23 +01:00
Dan Cryer de1c058f83 Merge pull request #1067 from Block8/dc/workers
Adding support for a beanstalkd-based queue
2015-10-05 15:27:18 +01:00
Dan Cryer 90b4de453e Moving to new .phpci.yml filename 2015-10-05 15:25:45 +01:00
Dan Cryer 5062a58ba1 Merge pull request #1066 from REBELinBLUE/hide_yml_file
Adding support to check for .phpci.yml so the file can be 'hidden'
2015-10-05 15:23:20 +01:00
Dan Cryer 9dd6edc24a Fix 2015-10-05 15:07:25 +01:00
Dan Cryer 11445304d6 Making PHPMD happy. 2015-10-05 14:48:17 +01:00
Dan Cryer 771bee0aa3 Fixings 2015-10-05 14:41:13 +01:00
Dan Cryer 2858ce506a Merge branch 'master' into dc/workers 2015-10-05 14:17:23 +01:00
Dan Cryer ea1157066e Fixing new PHPCS errors. 2015-10-05 14:17:13 +01:00
Dan Cryer b2ed9f102b Docblock fixes 2015-10-05 14:11:43 +01:00
Dan Cryer 183d7d9e50 Making duplicate builds work again. 2015-10-05 14:07:48 +01:00
Dan Cryer 06204ef171 Adding support for beanstalkd-based workers. 2015-10-05 12:13:22 +01:00
Stephen Ball 647c0eb09c Update Build.php
Changed the order the files are loaded in
2015-10-05 10:17:04 +01:00
Dan Cryer 6c33417839 More composer.json cleanup 2015-10-05 09:57:50 +01:00
Dan Cryer c0b2ed0df0 Merge branch 'master' of github.com:Block8/PHPCI 2015-10-05 09:56:42 +01:00
Dan Cryer fa979f6a02 Fixing composer.json 2015-10-05 09:56:36 +01:00
Stephen Ball 9ee27bad37 Adding support to check for .phpci.yml so the file can be 'hidden' as with other CI systems such as Travis, StyleCI and Codeclimate 2015-10-04 17:38:03 +01:00
Dan Cryer b0482b191d Merge pull request #872 from REBELinBLUE/additional_interpolate
Additional interpolate
2015-10-02 22:14:14 +01:00
Dan Cryer 58a97e5a97 Merge pull request #869 from REBELinBLUE/technical_debt_cleanup
Cleanup technical debt
2015-10-02 22:12:50 +01:00
Dan Cryer b171a10759 Fixing Codeception plugin PHPCS errors. 2015-10-02 21:53:39 +01:00
Dan Cryer aaf93ddd7d Updating PHPCI to send more detailed commit statuses, for @REBELinBLUE. 2015-10-02 21:35:53 +01:00
Dariusz Ruminski dc6e63a7e7 PHP CS Fixer - fix name
Closed #1054
2015-10-02 13:54:25 +02:00
Mehmet Ali Ergut f55b336900 Fixes default value for PHP Cs Fixer ( #833 ) 2015-08-24 21:24:37 +03:00
Steve Robbins 92ee90998a Fixes notice in github builds
Closed #996
2015-08-13 14:28:08 +02:00
DELORME Jonathan 8a3d7605d5 Fixed a css issue with Block8 logo
In Chrome (Android L), when you focus an input, the Block8 logo overrides the view.
See : http://s10.postimg.org/d5ffp7dwp/2015_06_30_07_57_03.png
2015-06-30 11:43:25 +02:00
Mark Robinson d2c512d94b Corrected a number of minor spelling errors.
Originally observed that "successful" was misspelled "succesfull" on the build view page. While correcting that, a number of other spelling errors were also corrected.
2015-06-25 15:21:23 +01:00
Joe Huss 9efd8e06dc Contribution Type: improvement / new feature
Primary Area: plugins

Description of change: Implemented using the same changes mentioned here: https://www.phptesting.org/forums/topic/5-mysql-plugin-setup/
Allows you to specify a hostname for your mysql connection.  Not my original code just patching it into the current source and made a branchs/push for it.

(i.e. Created a new plugin named ToolX, used library X to implement, see link)
2015-06-25 07:48:26 -04:00
Adirelle d698b11673 Fixes "Undefined index: login_token".
Fixes https://github.com/Block8/PHPCI/issues/994.
2015-05-30 21:16:31 +02:00
mulleto 2672908225 Make check for Pdepend output dir more robust. Before this fix, the pdepend module would check for "is_writable", which returns false even when the directory does not exist. I suggest to a) attempt to crete the dir if it does not exist, and b) make the error message more explanatory in case the dir cannot be created or is not accessible. 2015-05-29 08:26:47 +02:00
Aleix Canal 3a6008db53 Bugfix: Default codeception xml path "tests/_output/" but now 100% configurable.
Closed #991
2015-05-28 12:18:24 +02:00
tankist 9ace15a55f Fix for #988
Added class::method fallback for feature entry to prevent empty report for Codeception plugin

Closed #989
2015-05-28 12:10:14 +02:00
mulleto 3784cc8ea9 Adding an option for Plugin::Composer to pass the no_dev option. If set to true, it will execute composer with the --no-dev option, which usually suffices for testing in most projects. Default is set to false.
Closed #987
2015-05-28 12:08:30 +02:00
mulleto d34818f029 Added missing strings in german translation
Closed #982
2015-05-28 12:05:58 +02:00
Stephen Ball 1eb5248319 Removed allowed_warnings as it is not used
Calling setOptions

Closed #975
2015-05-28 12:03:23 +02:00
Adirelle 3734d3bc9a Don't use an if and two return statements when one return is sufficient.
Don't use two if when one else does the same job.
Don't use convoluted calculations when one return statement is sufficient.
Don't call a method that doesn't exist.
Don't write a try-catch block that does nothing.
Do send exceptions when a abnormal situation occurs.

Closed #962
2015-05-28 12:00:43 +02:00
Adirelle b7d9af1e72 Update sensiolabs/ansi-to-html to fix the log background.
Closed #961
2015-05-28 11:59:17 +02:00
corpsee 5c6dd81a9a Extended shields.io status badge support
Closed #958
2015-05-28 11:50:57 +02:00
Petr Cervenka 7ff080971e Adding Flowdock integration
Closed #954
2015-05-28 11:47:57 +02:00
Adirelle ad29ba4cfd Track and display the build progression, for each stages and plugins.
Translations for the build summary.

Closed #944
2015-05-28 11:39:35 +02:00
Adirelle 8549ba30cf PHPUnit display: escape HTML code in test messages.
PHPUnit display: display data produced before the TapParser refactoring.

Closed #937
2015-05-28 11:14:20 +02:00
Adirelle 4edefee761 Reworked the DaemonCommand.
* Accepts options for PID and log file.
* Uses posix_kill whenever available.
* Checks that the daemon actually started or stopped.
* Try to terminate then kill the daemon.
* Uses the logger or output instead of "echo".

Added a ProcessControl interface and implementations.

Closed #908
2015-05-28 10:55:59 +02:00
Vaidas Zilionis 15b6917f68 Allow see project build status in cctray xml format
Refactoring is done for BuildStatus information.
- Fixed all phpcs, phpmd errors
- Added test for my code (hurray 100 tests already :D)

Closed #705
2015-05-28 10:50:29 +02:00
corpsee 853107027b Fixed GithubBuild::getDiffLineNumber method for correct phpcpd work 2015-05-24 14:20:51 +06:00
corpsee 3f02e63c92 Fixed TapParser::parseLine Cyclomatic Complexity 2015-05-24 12:43:43 +06:00
corpsee c20ee0533b Fixed TapParser::parseLine Cyclomatic Complexity 2015-05-24 12:39:27 +06:00
corpsee 1c864cebed Fixed TapParser parse fail with valid data + added tests 2015-05-23 15:40:49 +06:00
Victor 0887bd4bc4 Fix for strict standards issue in RebuildCommand.php
[ErrorException]
Runtime Notice: Only variables should be passed by reference in /var/www/phpci/PHPCI/Command/RebuildCommand.php line 78
2015-05-15 16:37:45 +02:00
Adirelle 8bf1d09afd Black-on-white color theme for the build log. 2015-04-28 14:14:25 +02:00
Adirelle 5cd29ca7fc Allow to specify a mail template ('short' or 'long') in phpci.yml.
Email plugin: checks if the custom template exists before trying to use it.

Closed #933
2015-04-28 14:09:23 +02:00
corpsee 21bf104587 Separated strings for 'archived'/'archived_menu' 2015-04-25 09:54:08 +06:00
alton.crossley 5133d85b99 adding windows batch file for console 2015-04-23 14:35:15 -06:00
Stephen Ball fdaaa1ede4 Parsing variables in the code coverage output directory for PHPUnit 2015-03-16 11:10:03 +00:00
Stephen Ball 52b2f87df2 Parsing variables in the Wipe plugin 2015-03-16 11:09:45 +00:00
Stephen Ball 70f0d2516f Removed log output so that it matches the other plugins which don't pollute the build log, and to prevent issues with the log output not being escaped 2015-03-13 13:31:38 +00:00
1008 changed files with 179320 additions and 30764 deletions

41
.github/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,41 @@
Contributions to PHPCI are very much encouraged, and we do our best to make it as welcoming and simple as possible.
### Before You Start
Before you start, please make sure that you are aware of, and agree to, the following conditions of contribution:
* By making a contribution to PHPCI, you accept that you are granting copyright ownership for that contribution to Block 8 Limited - the company responsible for PHPCI. In countries where copyright reassignment is not permitted, you grant Block 8 Limited a perpetual, non-exclusive licence to use your contribution in any way and for any purpose.
* By making a contribution to PHPCI, you accept that your code will be released under the open-source [BSD 2-Clause Licence](https://github.com/Block8/PHPCI/blob/master/LICENSE.md).
Block 8 are committed to PHPCI being a truly free and open source project, providing easy to use continuous integration and testing to as many developers as possible. We may, at our sole discretion, provide paid services based upon PHPCI - but PHPCI will always remain free (as in cost, and freedom) and open source.
### The Contribution Process
1. If you are thinking of making a large change or feature addition, [open an issue](/Block8/PHPCI/issues) titled "Intent to implement: <Your Feature>". Describe your idea in detail and discuss it with the community. It might be that someone already has a plan, could help you out, or your idea may simply not be suitable for the project at this time.
2. Fork the PHPCI project on Github
3. Add a feature or fix a bug - We recommend that you do this on a branch within your repository.
4. Create a pull request containing just the one change you want to contribute back to PHPCI. If you have more than one feature or bug fix, please create separate branches within your repository, and then submit a separate pull request for each one. Your pull request should use the template detailed below.
5. We'll then review your pull request and give any necessary feedback, this could be:
* Suggestions to improve your implementation
* Questions
* Issues/bugs related to the change
* Coding standards pointers
6. Once everyone is happy with the submission, we'll merge it back into PHPCI. Your change will then be included in the next project release.
### Not sure what to start with?
We maintain two labels within our issue tracker that may be of interest to new contributors:
* [The "Easy Fix" List](https://github.com/Block8/PHPCI/labels/flag:easy-fix)
* [The "Priority" List](https://github.com/Block8/PHPCI/labels/flag:priority)
### Coding Standards
We require that all contributions meet at least the following guidelines:
* PSR-1 & PSR-2 compliance for all code
* Doc-blocks for all classes and methods
* All files must contain the standard file-level docblock, including the copyright, license and link tags.
All pull requests will be checked against these standards. If you're modifying a file as part of your change which does not comply with the above, please make the necessary changes to bring it into compliance prior to submitting the pull request.
### Other Requirements
When you're adding new features or functionality, or you're updating existing functionality, please ensure that the relevant documentation is also either created or updated on the Wiki.

28
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,28 @@
Before submitting your issue, please make sure that you've checked all of the checkboxes below.
- [ ] You're running the [latest release](https://github.com/Block8/PHPCI/releases/latest) version of PHPCI.
- [ ] Ensure that you're running at least PHP 5.3.6, you can check this by running `php -v`
- [ ] You've run `composer install --no-dev -o` from the root of your installation.
- [ ] You have set up either the PHPCI [Worker](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-a-Worker), [Daemon](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-a-Daemon) or [Cron Job](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-Cron) to run builds.
To help us better understand your issue, please answer the following.
### Expected behaviour
*Please describe what you're expecting to see happen.*
### Actual behaviour
*Please describe what you're actually seeing happen.*
### Steps to reproduce
*If your issue requires any specific steps to reproduce, please outline them here.*
### Environment info
Operating System:
PHP Version:
MySQL Version:
### Logs or other output that would be helpful
(If logs are large, please upload as attachment).

23
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,23 @@
Contribution Type: bug fix | new plugin | new feature | refactor | cosmetic
Link to Intent to Implement:
Link to Bug:
This pull request affects the following areas:
* [ ] Front-End
* [ ] Builder
* [ ] Build Plugins
**In raising this pull request, I confirm the following (please check boxes):**
- [ ] I have read and understood the [contributing guidelines](/.github/CONTRIBUTING.md)?
- [ ] I have checked that another pull request for this purpose does not exist.
- [ ] I have considered, and confirmed that this submission will be valuable to others.
- [ ] I have created or updated the relevant documentation for this change on the PHPCI Wiki.
- [ ] Do the PHPCI tests pass?
Detailed description of change:

38
.phpci.yml Normal file
View file

@ -0,0 +1,38 @@
build_settings:
verbose: false
ignore:
- "vendor"
- "Tests"
- "PHPCI/Command" # PHPMD complains about un-used parameters, but they are required.
- "public/install.php" # PHPCS really doesn't like PHP mixed with HTML (and so it shouldn't)
- "PHPCI/Migrations" # Ignore the migrations directory, as both PHPMD and PHPCS can't cope with them.
- "PHPCI/Model/Base" # These files are auto-generated, and sometimes hit PHPMD complexity thresholds.
- "PHPCI/Languages" # PHPCS fails on character counts for non-Latin languages
- "public/assets" # If there are any PHP files in here, we didn't write them.
setup:
composer:
action: "install"
test:
php_parallel_lint:
ignore:
# Only ignore vendor
- vendor/
php_mess_detector:
allowed_warnings: 0
rules:
- phpmd.xml
php_code_sniffer:
standard: phpcs.xml
allowed_warnings: 0
allowed_errors: 0
php_loc:
php_unit:
php_docblock_checker:
allowed_warnings: 0
broken:
email:
committer: true
cc: ["php-ci@googlegroups.com"]

View file

@ -135,15 +135,18 @@ class Application extends b8\Application
*/
protected function setLayoutVariables(View &$layout)
{
/** @var \PHPCI\Store\ProjectStore $projectStore */
$projectStore = b8\Store\Factory::getStore('Project');
$layout->projects = $projectStore->getWhere(
array('archived' => (int)isset($_GET['archived'])),
50,
0,
array(),
array('title' => 'ASC')
);
$groups = array();
$groupStore = b8\Store\Factory::getStore('ProjectGroup');
$groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
foreach ($groupList['items'] as $group) {
$thisGroup = array('title' => $group->getTitle());
$projects = b8\Store\Factory::getStore('Project')->getByGroupId($group->getId());
$thisGroup['projects'] = $projects['items'];
$groups[] = $thisGroup;
}
$layout->groups = $groups;
}
/**

View file

@ -36,38 +36,44 @@ class BuildFactory
/**
* Takes a generic build and returns a type-specific build model.
* @param Build $base The build from which to get a more specific build type.
* @param Build $build The build from which to get a more specific build type.
* @return Build
*/
public static function getBuild(Build $base)
public static function getBuild(Build $build)
{
switch($base->getProject()->getType())
{
case 'remote':
$type = 'RemoteGitBuild';
break;
case 'local':
$type = 'LocalBuild';
break;
case 'github':
$type = 'GithubBuild';
break;
case 'bitbucket':
$type = 'BitbucketBuild';
break;
case 'gitlab':
$type = 'GitlabBuild';
break;
case 'hg':
$type = 'MercurialBuild';
break;
case 'svn':
$type = 'SubversionBuild';
break;
$project = $build->getProject();
if (!empty($project)) {
switch ($project->getType()) {
case 'remote':
$type = 'RemoteGitBuild';
break;
case 'local':
$type = 'LocalBuild';
break;
case 'github':
$type = 'GithubBuild';
break;
case 'bitbucket':
$type = 'BitbucketBuild';
break;
case 'gitlab':
$type = 'GitlabBuild';
break;
case 'hg':
$type = 'MercurialBuild';
break;
case 'svn':
$type = 'SubversionBuild';
break;
default:
return $build;
}
$class = '\\PHPCI\\Model\\Build\\' . $type;
$build = new $class($build->getDataArray());
}
$type = '\\PHPCI\\Model\\Build\\' . $type;
return new $type($base->getDataArray());
return $build;
}
}

View file

@ -188,6 +188,14 @@ class Builder implements LoggerAwareInterface
$this->build->sendStatusPostback();
$success = true;
$previous_build = $this->build->getProject()->getPreviousBuild($this->build->getBranch());
$previous_state = Build::STATUS_NEW;
if ($previous_build) {
$previous_state = $previous_build->getStatus();
}
try {
// Set up the build:
$this->setupBuild();
@ -205,19 +213,30 @@ class Builder implements LoggerAwareInterface
$this->build->setStatus(Build::STATUS_FAILED);
}
// Complete stage plugins are always run
$this->pluginExecutor->executePlugins($this->config, 'complete');
if ($success) {
$this->pluginExecutor->executePlugins($this->config, 'success');
if ($previous_state == Build::STATUS_FAILED) {
$this->pluginExecutor->executePlugins($this->config, 'fixed');
}
$this->buildLogger->logSuccess(Lang::get('build_success'));
} else {
$this->pluginExecutor->executePlugins($this->config, 'failure');
if ($previous_state == Build::STATUS_SUCCESS || $previous_state == Build::STATUS_NEW) {
$this->pluginExecutor->executePlugins($this->config, 'broken');
}
$this->buildLogger->logFailure(Lang::get('build_failed'));
}
} catch (\Exception $ex) {
$this->build->setStatus(Build::STATUS_FAILED);
$this->buildLogger->logFailure(Lang::get('exception') . $ex->getMessage());
}finally{
// Complete stage plugins are always run
$this->pluginExecutor->executePlugins($this->config, 'complete');
}
@ -285,8 +304,7 @@ class Builder implements LoggerAwareInterface
*/
protected function setupBuild()
{
$this->buildPath = $this->build->getBuildPath() . '/';
$this->build->currentBuildPath = $this->buildPath;
$this->buildPath = $this->build->getBuildPath();
$this->interpolator->setupInterpolationVars(
$this->build,

View file

@ -1,4 +1,5 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
@ -10,28 +11,48 @@
namespace PHPCI\Command;
use Monolog\Logger;
use PHPCI\ProcessControl\Factory;
use PHPCI\ProcessControl\ProcessControlInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Daemon that loops and call the run-command.
* @author Gabriel Baker <gabriel.baker@autonomicpilot.co.uk>
* @package PHPCI
* @subpackage Console
*/
* Daemon that loops and call the run-command.
* @author Gabriel Baker <gabriel.baker@autonomicpilot.co.uk>
* @package PHPCI
* @subpackage Console
*/
class DaemonCommand extends Command
{
/**
* @var \Monolog\Logger
* @var Logger
*/
protected $logger;
public function __construct(Logger $logger, $name = null)
/**
* @var string
*/
protected $pidFilePath;
/**
* @var string
*/
protected $logFilePath;
/**
* @var ProcessControlInterface
*/
protected $processControl;
public function __construct(Logger $logger, ProcessControlInterface $processControl = null, $name = null)
{
parent::__construct($name);
$this->logger = $logger;
$this->processControl = $processControl ?: Factory::getInstance();
}
protected function configure()
@ -40,17 +61,30 @@ class DaemonCommand extends Command
->setName('phpci:daemon')
->setDescription('Initiates the daemon to run commands.')
->addArgument(
'state',
InputArgument::REQUIRED,
'start|stop|status'
);
'state', InputArgument::REQUIRED, 'start|stop|status'
)
->addOption(
'pid-file', 'p', InputOption::VALUE_REQUIRED,
'Path of the PID file',
implode(DIRECTORY_SEPARATOR,
array(PHPCI_DIR, 'daemon', 'daemon.pid'))
)
->addOption(
'log-file', 'l', InputOption::VALUE_REQUIRED,
'Path of the log file',
implode(DIRECTORY_SEPARATOR,
array(PHPCI_DIR, 'daemon', 'daemon.log'))
);
}
/**
* Loops through running.
*/
* Loops through running.
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->pidFilePath = $input->getOption('pid-file');
$this->logFilePath = $input->getOption('log-file');
$state = $input->getArgument('state');
switch ($state) {
@ -61,64 +95,108 @@ class DaemonCommand extends Command
$this->stopDaemon();
break;
case 'status':
$this->statusDaemon();
$this->statusDaemon($output);
break;
default:
echo "Not a valid choice, please use start stop or status";
$this->output->writeln("<error>Not a valid choice, please use start, stop or status</error>");
break;
}
}
protected function startDaemon()
{
if (file_exists(PHPCI_DIR.'/daemon/daemon.pid')) {
echo "Already started\n";
$this->logger->warning("Daemon already started");
$pid = $this->getRunningPid();
if ($pid) {
$this->logger->notice("Daemon already started", array('pid' => $pid));
return "alreadystarted";
}
$logfile = PHPCI_DIR."/daemon/daemon.log";
$this->logger->info("Trying to start the daemon");
$cmd = "nohup %s/daemonise phpci:daemonise > %s 2>&1 &";
$command = sprintf($cmd, PHPCI_DIR, $logfile);
$this->logger->info("Daemon started");
exec($command);
$command = sprintf($cmd, PHPCI_DIR, $this->logFilePath);
$output = $exitCode = null;
exec($command, $output, $exitCode);
if ($exitCode !== 0) {
$this->logger->error(sprintf("daemonise exited with status %d", $exitCode));
return "notstarted";
}
for ($i = 0; !($pid = $this->getRunningPid()) && $i < 5; $i++) {
sleep(1);
}
if (!$pid) {
$this->logger->error("Could not start the daemon");
return "notstarted";
}
$this->logger->notice("Daemon started", array('pid' => $pid));
return "started";
}
protected function stopDaemon()
{
if (!file_exists(PHPCI_DIR.'/daemon/daemon.pid')) {
echo "Not started\n";
$this->logger->warning("Can't stop daemon as not started");
$pid = $this->getRunningPid();
if (!$pid) {
$this->logger->notice("Cannot stop the daemon as it is not started");
return "notstarted";
}
$cmd = "kill $(cat %s/daemon/daemon.pid)";
$command = sprintf($cmd, PHPCI_DIR);
exec($command);
$this->logger->info("Daemon stopped");
unlink(PHPCI_DIR.'/daemon/daemon.pid');
}
$this->logger->info("Trying to terminate the daemon", array('pid' => $pid));
$this->processControl->kill($pid);
protected function statusDaemon()
{
if (!file_exists(PHPCI_DIR.'/daemon/daemon.pid')) {
echo "Not running\n";
return "notrunning";
for ($i = 0; ($pid = $this->getRunningPid()) && $i < 5; $i++) {
sleep(1);
}
$pid = trim(file_get_contents(PHPCI_DIR.'/daemon/daemon.pid'));
$pidcheck = sprintf("/proc/%s", $pid);
if (is_dir($pidcheck)) {
echo "Running\n";
if ($pid) {
$this->logger->warning("The daemon is resiting, trying to kill it", array('pid' => $pid));
$this->processControl->kill($pid, true);
for ($i = 0; ($pid = $this->getRunningPid()) && $i < 5; $i++) {
sleep(1);
}
}
if (!$pid) {
$this->logger->notice("Daemon stopped");
return "stopped";
}
$this->logger->error("Could not stop the daemon");
}
protected function statusDaemon(OutputInterface $output)
{
$pid = $this->getRunningPid();
if ($pid) {
$output->writeln(sprintf('The daemon is running, PID: %d', $pid));
return "running";
}
unlink(PHPCI_DIR.'/daemon/daemon.pid');
echo "Not running\n";
$output->writeln('The daemon is not running');
return "notrunning";
}
/** Check if there is a running daemon
*
* @return int|null
*/
protected function getRunningPid()
{
if (!file_exists($this->pidFilePath)) {
return;
}
$pid = intval(trim(file_get_contents($this->pidFilePath)));
if($this->processControl->isRunning($pid, true)) {
return $pid;
}
// Not found, remove the stale PID file
unlink($this->pidFilePath);
}
}

View file

@ -16,10 +16,12 @@ use b8\Config;
use b8\Store\Factory;
use PHPCI\Helper\Lang;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use PHPCI\Service\UserService;
use Symfony\Component\Console\Question\ConfirmationQuestion;
/**
* Install console command - Installs PHPCI.
@ -46,6 +48,9 @@ class InstallCommand extends Command
->addOption('admin-pass', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_pass'))
->addOption('admin-mail', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_email'))
->addOption('config-path', null, InputOption::VALUE_OPTIONAL, Lang::get('config_path'), $defaultPath)
->addOption('queue-disabled', null, InputOption::VALUE_NONE, 'Don\'t ask for queue details')
->addOption('queue-server', null, InputOption::VALUE_OPTIONAL, 'Beanstalkd queue server hostname')
->addOption('queue-name', null, InputOption::VALUE_OPTIONAL, 'Beanstalkd queue name')
->setDescription(Lang::get('install_phpci'));
}
@ -229,10 +234,45 @@ class InstallCommand extends Command
}
$phpci['url'] = $url;
$phpci['worker'] = $this->getQueueInformation($input, $output, $dialog);
return $phpci;
}
/**
* If the user wants to use a queue, get the necessary details.
* @param InputInterface $input
* @param OutputInterface $output
* @param DialogHelper $dialog
* @return array
*/
protected function getQueueInformation(InputInterface $input, OutputInterface $output, DialogHelper $dialog)
{
if ($input->getOption('queue-disabled')) {
return null;
}
$rtn = [];
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Use beanstalkd to manage build queue? ', true);
if (!$helper->ask($input, $output, $question)) {
$output->writeln('<error>Skipping beanstalkd configuration.</error>');
return null;
}
if (!$rtn['host'] = $input->getOption('queue-server')) {
$rtn['host'] = $dialog->ask($output, 'Enter your beanstalkd hostname [localhost]: ', 'localhost');
}
if (!$rtn['queue'] = $input->getOption('queue-name')) {
$rtn['queue'] = $dialog->ask($output, 'Enter the queue (tube) name to use [phpci]: ', 'phpci');
}
return $rtn;
}
/**
* Load configuration for DB form CLI options or ask info to user.
*

View file

@ -75,7 +75,8 @@ class RebuildCommand extends Command
$store = Factory::getStore('Build');
$service = new BuildService($store);
$lastBuild = array_shift($store->getLatestBuilds(null, 1));
$builds = $store->getLatestBuilds(null, 1);
$lastBuild = array_shift($builds);
$service->createDuplicateBuild($lastBuild);
$runner->run(new ArgvInput(array()), $output);

View file

@ -0,0 +1,85 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Command;
use b8\Config;
use b8\Store\Factory;
use Monolog\Logger;
use PHPCI\BuildFactory;
use PHPCI\Helper\Lang;
use PHPCI\Logging\OutputLogHandler;
use PHPCI\Service\BuildService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Console
*/
class RebuildQueueCommand extends Command
{
/**
* @var OutputInterface
*/
protected $output;
/**
* @var Logger
*/
protected $logger;
/**
* @param \Monolog\Logger $logger
* @param string $name
*/
public function __construct(Logger $logger, $name = null)
{
parent::__construct($name);
$this->logger = $logger;
}
protected function configure()
{
$this
->setName('phpci:rebuild-queue')
->setDescription('Rebuilds the PHPCI worker queue.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
// For verbose mode we want to output all informational and above
// messages to the symphony output interface.
if ($input->hasOption('verbose') && $input->getOption('verbose')) {
$this->logger->pushHandler(
new OutputLogHandler($this->output, Logger::INFO)
);
}
$store = Factory::getStore('Build');
$result = $store->getByStatus(0);
$this->logger->addInfo(Lang::get('found_n_builds', count($result['items'])));
$buildService = new BuildService($store);
while (count($result['items'])) {
$build = array_shift($result['items']);
$build = BuildFactory::getBuild($build);
$this->logger->addInfo('Added build #' . $build->getId() . ' to queue.');
$buildService->addBuildToQueue($build);
}
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Command;
use b8\Config;
use Monolog\Logger;
use PHPCI\Logging\OutputLogHandler;
use PHPCI\Worker\BuildWorker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Worker Command - Starts the BuildWorker, which pulls jobs from beanstalkd
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Console
*/
class WorkerCommand extends Command
{
/**
* @var OutputInterface
*/
protected $output;
/**
* @var Logger
*/
protected $logger;
/**
* @param \Monolog\Logger $logger
* @param string $name
*/
public function __construct(Logger $logger, $name = null)
{
parent::__construct($name);
$this->logger = $logger;
}
protected function configure()
{
$this
->setName('phpci:worker')
->setDescription('Runs the PHPCI build worker.')
->addOption('debug', null, null, 'Run PHPCI in Debug Mode');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
// For verbose mode we want to output all informational and above
// messages to the symphony output interface.
if ($input->hasOption('verbose') && $input->getOption('verbose')) {
$this->logger->pushHandler(
new OutputLogHandler($this->output, Logger::INFO)
);
}
// Allow PHPCI to run in "debug mode"
if ($input->hasOption('debug') && $input->getOption('debug')) {
$output->writeln('<comment>Debug mode enabled.</comment>');
define('PHPCI_DEBUG_MODE', true);
}
$config = Config::getInstance()->get('phpci.worker', []);
if (empty($config['host']) || empty($config['queue'])) {
$error = 'The worker is not configured. You must set a host and queue in your config.yml file.';
throw new \Exception($error);
}
$worker = new BuildWorker($config['host'], $config['queue']);
$worker->setLogger($this->logger);
$worker->setMaxJobs(Config::getInstance()->get('phpci.worker.max_jobs', -1));
$worker->startWorker();
}
}

View file

@ -63,24 +63,42 @@ class BuildController extends \PHPCI\Controller
$this->view->plugins = $this->getUiPlugins();
$this->view->build = $build;
$this->view->data = json_encode($this->getBuildData($build));
$this->view->data = $this->getBuildData($build);
$this->layout->title = Lang::get('build_n', $buildId);
$this->layout->subtitle = $build->getProjectTitle();
$nav = array(
'title' => Lang::get('build_n', $buildId),
'icon' => 'cog',
'links' => array(
'build/rebuild/' . $build->getId() => Lang::get('rebuild_now'),
),
);
switch ($build->getStatus()) {
case 0:
$this->layout->skin = 'blue';
break;
if ($this->currentUserIsAdmin()) {
$nav['links']['build/delete/' . $build->getId()] = Lang::get('delete_build');
case 1:
$this->layout->skin = 'yellow';
break;
case 2:
$this->layout->skin = 'green';
break;
case 3:
$this->layout->skin = 'red';
break;
}
$this->layout->nav = $nav;
$rebuild = Lang::get('rebuild_now');
$rebuildLink = PHPCI_URL . 'build/rebuild/' . $build->getId();
$delete = Lang::get('delete_build');
$deleteLink = PHPCI_URL . 'build/delete/' . $build->getId();
$actions = "<a class=\"btn btn-default\" href=\"{$rebuildLink}\">{$rebuild}</a> ";
if ($this->currentUserIsAdmin()) {
$actions .= " <a class=\"btn btn-danger\" href=\"{$deleteLink}\">{$delete}</a>";
}
$this->layout->actions = $actions;
}
/**
@ -144,7 +162,7 @@ class BuildController extends \PHPCI\Controller
/**
* Get build data from database and json encode it:
*/
protected function getBuildData($build)
protected function getBuildData(Build $build)
{
$data = array();
$data['status'] = (int)$build->getStatus();
@ -152,6 +170,19 @@ class BuildController extends \PHPCI\Controller
$data['created'] = !is_null($build->getCreated()) ? $build->getCreated()->format('Y-m-d H:i:s') : null;
$data['started'] = !is_null($build->getStarted()) ? $build->getStarted()->format('Y-m-d H:i:s') : null;
$data['finished'] = !is_null($build->getFinished()) ? $build->getFinished()->format('Y-m-d H:i:s') : null;
$data['duration'] = $build->getDuration();
/** @var \PHPCI\Store\BuildErrorStore $errorStore */
$errorStore = b8\Store\Factory::getStore('BuildError');
$errors = $errorStore->getErrorsForBuild($build->getId(), $this->getParam('since', null));
$errorView = new b8\View('Build/errors');
$errorView->build = $build;
$errorView->errors = $errors;
$data['errors'] = $errorStore->getErrorTotalForBuild($build->getId());
$data['error_html'] = $errorView->render();
$data['since'] = (new \DateTime())->format('Y-m-d H:i:s');
return $data;
}
@ -169,6 +200,10 @@ class BuildController extends \PHPCI\Controller
$build = $this->buildService->createDuplicateBuild($copy);
if ($this->buildService->queueError) {
$_SESSION['global_error'] = Lang::get('add_to_queue_failed');
}
$response = new b8\Http\Response\RedirectResponse();
$response->setHeader('Location', PHPCI_URL.'build/view/' . $build->getId());
return $response;

View file

@ -15,6 +15,7 @@ use b8\Store;
use PHPCI\BuildFactory;
use PHPCI\Model\Project;
use PHPCI\Model\Build;
use PHPCI\Service\BuildStatusService;
/**
* Build Status Controller - Allows external access to build status information / images.
@ -24,10 +25,9 @@ use PHPCI\Model\Build;
*/
class BuildStatusController extends \PHPCI\Controller
{
/**
* @var \PHPCI\Store\ProjectStore
*/
/* @var \PHPCI\Store\ProjectStore */
protected $projectStore;
/* @var \PHPCI\Store\BuildStore */
protected $buildStore;
/**
@ -70,11 +70,69 @@ class BuildStatusController extends \PHPCI\Controller
return $status;
}
/**
* Displays projects information in ccmenu format
*
* @param $projectId
* @return bool
* @throws \Exception
* @throws b8\Exception\HttpException
*/
public function ccxml($projectId)
{
/* @var Project $project */
$project = $this->projectStore->getById($projectId);
$xml = new \SimpleXMLElement('<Projects/>');
if (!$project instanceof Project || !$project->getAllowPublicStatus()) {
return $this->renderXml($xml);
}
try {
$branchList = $this->buildStore->getBuildBranches($projectId);
if (!$branchList) {
$branchList = array($project->getBranch());
}
foreach ($branchList as $branch) {
$buildStatusService = new BuildStatusService($branch, $project, $project->getLatestBuild($branch));
if ($attributes = $buildStatusService->toArray()) {
$projectXml = $xml->addChild('Project');
foreach ($attributes as $attributeKey => $attributeValue) {
$projectXml->addAttribute($attributeKey, $attributeValue);
}
}
}
} catch (\Exception $e) {
$xml = new \SimpleXMLElement('<projects/>');
}
return $this->renderXml($xml);
}
/**
* @param \SimpleXMLElement $xml
* @return bool
*/
protected function renderXml(\SimpleXMLElement $xml = null)
{
$this->response->setHeader('Content-Type', 'text/xml');
$this->response->setContent($xml->asXML());
$this->response->flush();
echo $xml->asXML();
return true;
}
/**
* Returns the appropriate build status image in SVG format for a given project.
*/
public function image($projectId)
{
$style = $this->getParam('style', 'plastic');
$label = $this->getParam('label', 'build');
$status = $this->getStatus($projectId);
if (is_null($status)) {
@ -84,7 +142,13 @@ class BuildStatusController extends \PHPCI\Controller
}
$color = ($status == 'passing') ? 'green' : 'red';
$image = file_get_contents('http://img.shields.io/badge/build-' . $status . '-' . $color . '.svg');
$image = file_get_contents(sprintf(
'http://img.shields.io/badge/%s-%s-%s.svg?style=%s',
$label,
$status,
$color,
$style
));
$this->response->disableLayout();
$this->response->setHeader('Content-Type', 'image/svg+xml');

View file

@ -0,0 +1,120 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use b8\Form;
use b8\Store;
use PHPCI\Controller;
use PHPCI\Model\ProjectGroup;
/**
* Project Controller - Allows users to create, edit and view projects.
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Web
*/
class GroupController extends Controller
{
/**
* @var \PHPCI\Store\ProjectGroupStore
*/
protected $groupStore;
/**
* Set up this controller.
*/
public function init()
{
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
}
/**
* List project groups.
*/
public function index()
{
$this->requireAdmin();
$groups = array();
$groupList = $this->groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
foreach ($groupList['items'] as $group) {
$thisGroup = array(
'title' => $group->getTitle(),
'id' => $group->getId(),
);
$projects = b8\Store\Factory::getStore('Project')->getByGroupId($group->getId());
$thisGroup['projects'] = $projects['items'];
$groups[] = $thisGroup;
}
$this->view->groups = $groups;
}
/**
* Add or edit a project group.
* @param null $groupId
* @return void|b8\Http\Response\RedirectResponse
*/
public function edit($groupId = null)
{
$this->requireAdmin();
if (!is_null($groupId)) {
$group = $this->groupStore->getById($groupId);
} else {
$group = new ProjectGroup();
}
if ($this->request->getMethod() == 'POST') {
$group->setTitle($this->getParam('title'));
$this->groupStore->save($group);
$response = new b8\Http\Response\RedirectResponse();
$response->setHeader('Location', PHPCI_URL.'group');
return $response;
}
$form = new Form();
$form->setMethod('POST');
$form->setAction(PHPCI_URL . 'group/edit' . (!is_null($groupId) ? '/' . $groupId : ''));
$title = new Form\Element\Text('title');
$title->setContainerClass('form-group');
$title->setClass('form-control');
$title->setLabel('Group Title');
$title->setValue($group->getTitle());
$submit = new Form\Element\Submit();
$submit->setValue('Save Group');
$form->addField($title);
$form->addField($submit);
$this->view->form = $form;
}
/**
* Delete a project group.
* @param $groupId
* @return b8\Http\Response\RedirectResponse
*/
public function delete($groupId)
{
$this->requireAdmin();
$group = $this->groupStore->getById($groupId);
$this->groupStore->delete($group);
$response = new b8\Http\Response\RedirectResponse();
$response->setHeader('Location', PHPCI_URL.'group');
return $response;
}
}

View file

@ -23,15 +23,20 @@ use PHPCI\Model\Build;
class HomeController extends \PHPCI\Controller
{
/**
* @var \b8\Store\BuildStore
* @var \PHPCI\Store\BuildStore
*/
protected $buildStore;
/**
* @var \b8\Store\ProjectStore
* @var \PHPCI\Store\ProjectStore
*/
protected $projectStore;
/**
* @var \PHPCI\Store\ProjectGroupStore
*/
protected $groupStore;
/**
* Initialise the controller, set up stores and services.
*/
@ -39,6 +44,7 @@ class HomeController extends \PHPCI\Controller
{
$this->buildStore = b8\Store\Factory::getStore('Build');
$this->projectStore = b8\Store\Factory::getStore('Project');
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
}
/**
@ -47,15 +53,6 @@ class HomeController extends \PHPCI\Controller
public function index()
{
$this->layout->title = Lang::get('dashboard');
$projects = $this->projectStore->getWhere(
array('archived' => (int)isset($_GET['archived'])),
50,
0,
array(),
array('title' => 'ASC')
);
$builds = $this->buildStore->getLatestBuilds(null, 10);
foreach ($builds as &$build) {
@ -63,8 +60,7 @@ class HomeController extends \PHPCI\Controller
}
$this->view->builds = $builds;
$this->view->projects = $projects['items'];
$this->view->summary = $this->getSummaryHtml($projects);
$this->view->groups = $this->getGroupInfo();
return $this->view->render();
}
@ -102,7 +98,7 @@ class HomeController extends \PHPCI\Controller
$failures = array();
$counts = array();
foreach ($projects['items'] as $project) {
foreach ($projects as $project) {
$summaryBuilds[$project->getId()] = $this->buildStore->getLatestBuilds($project->getId());
$count = $this->buildStore->getWhere(
@ -122,7 +118,7 @@ class HomeController extends \PHPCI\Controller
}
$summaryView = new b8\View('SummaryTable');
$summaryView->projects = $projects['items'];
$summaryView->projects = $projects;
$summaryView->builds = $summaryBuilds;
$summaryView->successful = $successes;
$summaryView->failed = $failures;
@ -147,4 +143,24 @@ class HomeController extends \PHPCI\Controller
return $view->render();
}
/**
* Get a summary of the project groups we have, and what projects they have in them.
* @return array
*/
protected function getGroupInfo()
{
$rtn = array();
$groups = $this->groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
foreach ($groups['items'] as $group) {
$thisGroup = array('title' => $group->getTitle());
$projects = $this->projectStore->getByGroupId($group->getId());
$thisGroup['projects'] = $projects['items'];
$thisGroup['summary'] = $this->getSummaryHtml($thisGroup['projects']);
$rtn[] = $thisGroup;
}
return $rtn;
}
}

View file

@ -23,24 +23,6 @@ use PHPCI\Plugin\Util\PluginInformationCollection;
*/
class PluginController extends \PHPCI\Controller
{
protected $required = array(
'php',
'ext-pdo',
'ext-pdo_mysql',
'block8/b8framework',
'ircmaxell/password-compat',
'swiftmailer/swiftmailer',
'symfony/yaml',
'symfony/console',
'psr/log',
'monolog/monolog',
'pimple/pimple',
'robmorgan/phinx',
);
protected $canInstall;
protected $composerPath;
/**
* List all enabled plugins, installed and recommend packages.
* @return string
@ -49,12 +31,8 @@ class PluginController extends \PHPCI\Controller
{
$this->requireAdmin();
$this->view->canWrite = is_writable(APPLICATION_PATH . 'composer.json');
$this->view->required = $this->required;
$json = $this->getComposerJson();
$this->view->installedPackages = $json['require'];
$this->view->suggestedPackages = $json['suggest'];
$pluginInfo = new PluginInformationCollection();
$pluginInfo->add(FilesPluginInformation::newFromDir(
@ -71,49 +49,6 @@ class PluginController extends \PHPCI\Controller
return $this->view->render();
}
/**
* Remove a given package.
*/
public function remove()
{
$this->requireAdmin();
$package = $this->getParam('package', null);
$json = $this->getComposerJson();
$response = new b8\Http\Response\RedirectResponse();
if (!in_array($package, $this->required)) {
unset($json['require'][$package]);
$this->setComposerJson($json);
$response->setHeader('Location', PHPCI_URL . 'plugin?r=' . $package);
return $response;
}
$response->setHeader('Location', PHPCI_URL);
return $response;
}
/**
* Install a given package.
*/
public function install()
{
$this->requireAdmin();
$package = $this->getParam('package', null);
$version = $this->getParam('version', '*');
$json = $this->getComposerJson();
$json['require'][$package] = $version;
$this->setComposerJson($json);
$response = new b8\Http\Response\RedirectResponse();
$response->setHeader('Location', PHPCI_URL . 'plugin?w=' . $package);
return $response;
}
/**
* Get the json-decoded contents of the composer.json file.
* @return mixed
@ -123,83 +58,4 @@ class PluginController extends \PHPCI\Controller
$json = file_get_contents(APPLICATION_PATH . 'composer.json');
return json_decode($json, true);
}
/**
* Convert array to json and save composer.json
*
* @param $array
*/
protected function setComposerJson($array)
{
if (defined('JSON_PRETTY_PRINT')) {
$json = json_encode($array, JSON_PRETTY_PRINT);
} else {
$json = json_encode($array);
}
file_put_contents(APPLICATION_PATH . 'composer.json', $json);
}
/**
* Find a system binary.
* @param $binary
* @return null|string
*/
protected function findBinary($binary)
{
if (is_string($binary)) {
$binary = array($binary);
}
foreach ($binary as $bin) {
// Check project root directory:
if (is_file(APPLICATION_PATH . $bin)) {
return APPLICATION_PATH . $bin;
}
// Check Composer bin dir:
if (is_file(APPLICATION_PATH . 'vendor/bin/' . $bin)) {
return APPLICATION_PATH . 'vendor/bin/' . $bin;
}
// Use "which"
$which = trim(shell_exec('which ' . $bin));
if (!empty($which)) {
return $which;
}
}
return null;
}
/**
* Perform a search on packagist.org.
*/
public function packagistSearch()
{
$searchQuery = $this->getParam('q', '');
$http = new \b8\HttpClient();
$http->setHeaders(array('User-Agent: PHPCI/1.0 (+https://www.phptesting.org)'));
$res = $http->get('https://packagist.org/search.json', array('q' => $searchQuery));
$response = new b8\Http\Response\JsonResponse();
$response->setContent($res['body']);
return $response;
}
/**
* Look up available versions of a given package on packagist.org
*/
public function packagistVersions()
{
$name = $this->getParam('p', '');
$http = new \b8\HttpClient();
$http->setHeaders(array('User-Agent: PHPCI/1.0 (+https://www.phptesting.org)'));
$res = $http->get('https://packagist.org/packages/'.$name.'.json');
$response = new b8\Http\Response\JsonResponse();
$response->setContent($res['body']);
return $response;
}
}

View file

@ -116,6 +116,10 @@ class ProjectController extends PHPCI\Controller
$email = $_SESSION['phpci_user']->getEmail();
$build = $this->buildService->createBuild($project, null, urldecode($branch), $email);
if ($this->buildService->queueError) {
$_SESSION['global_error'] = Lang::get('add_to_queue_failed');
}
$response = new b8\Http\Response\RedirectResponse();
$response->setHeader('Location', PHPCI_URL.'build/view/' . $build->getId());
return $response;
@ -220,6 +224,7 @@ class ProjectController extends PHPCI\Controller
'build_config' => $this->getParam('build_config', null),
'allow_public_status' => $this->getParam('allow_public_status', 0),
'branch' => $this->getParam('branch', null),
'group' => $this->getParam('group_id', null),
);
$project = $this->projectService->createProject($title, $type, $reference, $options);
@ -284,6 +289,7 @@ class ProjectController extends PHPCI\Controller
'allow_public_status' => $this->getParam('allow_public_status', 0),
'archived' => $this->getParam('archived', 0),
'branch' => $this->getParam('branch', null),
'group' => $this->getParam('group_id', null),
);
$project = $this->projectService->updateProject($project, $title, $type, $reference, $options);
@ -352,6 +358,20 @@ class ProjectController extends PHPCI\Controller
$field->setClass('form-control')->setContainerClass('form-group')->setValue('master');
$form->addField($field);
$field = Form\Element\Select::create('group_id', 'Project Group', true);
$field->setClass('form-control')->setContainerClass('form-group')->setValue(1);
$groups = array();
$groupStore = b8\Store\Factory::getStore('ProjectGroup');
$groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
foreach ($groupList['items'] as $group) {
$groups[$group->getId()] = $group->getTitle();
}
$field->setOptions($groups);
$form->addField($field);
$field = Form\Element\Checkbox::create('allow_public_status', Lang::get('allow_public_status'), false);
$field->setContainerClass('form-group');
$field->setCheckedValue(1);

View file

@ -44,12 +44,12 @@ class SessionController extends \PHPCI\Controller
if ($this->request->getMethod() == 'POST') {
$token = $this->getParam('token');
if ($token === null || $token !== $_SESSION['login_token']) {
if (!isset($token, $_SESSION['login_token']) || $token !== $_SESSION['login_token']) {
$isLoginFailure = true;
} else {
unset($_SESSION['login_token']);
$user = $this->userStore->getByLoginOrEmail($this->getParam('email'));
$user = $this->userStore->getByEmail($this->getParam('email'));
if ($user && password_verify($this->getParam('password', ''), $user->getHash())) {
session_regenerate_id(true);
@ -68,7 +68,7 @@ class SessionController extends \PHPCI\Controller
$form->setAction(PHPCI_URL.'session/login');
$email = new b8\Form\Element\Email('email');
$email->setLabel(Lang::get('login'));
$email->setLabel(Lang::get('email_address'));
$email->setRequired(true);
$email->setContainerClass('form-group');
$email->setClass('form-control');

View file

@ -444,7 +444,7 @@ class SettingsController extends Controller
$field->setClass('form-control');
$field->setContainerClass('form-group');
$field->setOptions(Lang::getLanguageOptions());
$field->setValue('en');
$field->setValue(Lang::getLanguage());
$form->addField($field);

View file

@ -27,6 +27,8 @@ use PHPCI\Store\ProjectStore;
* @author Guillaume Perréal <adirelle@gmail.com>
* @package PHPCI
* @subpackage Web
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
class WebhookController extends \b8\Controller
{
@ -80,11 +82,64 @@ class WebhookController extends \b8\Controller
}
/**
* Called by Bitbucket POST service.
* Called by Bitbucket.
*/
public function bitbucket($projectId)
{
$project = $this->fetchProject($projectId, 'bitbucket');
// Support both old services and new webhooks
if ($payload = $this->getParam('payload')) {
return $this->bitbucketService(json_decode($payload, true), $project);
}
$payload = json_decode(file_get_contents("php://input"), true);
if (empty($payload['push']['changes'])) {
// Invalid event from bitbucket
return [
'status' => 'failed',
'commits' => []
];
}
return $this->bitbucketWebhook($payload, $project);
}
/**
* Bitbucket webhooks.
*/
protected function bitbucketWebhook($payload, $project)
{
$results = array();
$status = 'failed';
foreach ($payload['push']['changes'] as $commit) {
try {
$email = $commit['new']['target']['author']['raw'];
$email = substr($email, 0, strpos($email, '>'));
$email = substr($email, strpos($email, '<') + 1);
$results[$commit['new']['target']['hash']] = $this->createBuild(
$project,
$commit['new']['target']['hash'],
$commit['new']['name'],
$email,
$commit['new']['target']['message']
);
$status = 'ok';
} catch (Exception $ex) {
$results[$commit['new']['target']['hash']] = array('status' => 'failed', 'error' => $ex->getMessage());
}
}
return array('status' => $status, 'commits' => $results);
}
/**
* Bitbucket POST service.
*/
protected function bitbucketService($payload, $project)
{
$payload = json_decode($this->getParam('payload'), true);
$results = array();
@ -170,7 +225,7 @@ class WebhookController extends \b8\Controller
protected function githubCommitRequest(Project $project, array $payload)
{
// Github sends a payload when you close a pull request with a
// non-existant commit. We don't want this.
// non-existent commit. We don't want this.
if (array_key_exists('after', $payload) && $payload['after'] === '0000000000000000000000000000000000000000') {
return array('status' => 'ignored');
}
@ -364,10 +419,6 @@ class WebhookController extends \b8\Controller
// If not, create a new build job for it:
$build = $this->buildService->createBuild($project, $commitId, $branch, $committer, $commitMessage, $extra);
$build = BuildFactory::getBuild($build);
// Send a status postback if the build type provides one:
$build->sendStatusPostback();
return array('status' => 'ok', 'buildID' => $build->getID());
}

View file

@ -21,7 +21,7 @@ final class AnsiConverter
static private $converter = null;
/**
* Initialize the singletion.
* Initialize the singleton.
*
* @return AnsiToHtmlConverter
*/
@ -35,7 +35,7 @@ final class AnsiConverter
}
/**
* Convert a text containing ANSI colr sequences into HTML code.
* Convert a text containing ANSI color sequences into HTML code.
*
* @param string $text The text to convert
*
@ -47,7 +47,7 @@ final class AnsiConverter
}
/**
* Do not instanciate this class.
* Do not instantiate this class.
*/
private function __construct()
{

View file

@ -76,6 +76,7 @@ abstract class BaseCommandExecutor implements CommandExecutor
$this->lastOutput = array();
$command = call_user_func_array('sprintf', $args);
$this->logger->logDebug($command);
if ($this->quiet) {
$this->logger->log('Executing: ' . $command);
@ -89,7 +90,6 @@ abstract class BaseCommandExecutor implements CommandExecutor
);
$pipes = array();
$process = proc_open($command, $descriptorSpec, $pipes, $this->buildPath, null);
if (is_resource($process)) {

View file

@ -38,6 +38,7 @@ class BuildInterpolator
$this->interpolation_vars['%COMMIT%'] = $build->getCommitId();
$this->interpolation_vars['%SHORT_COMMIT%'] = substr($build->getCommitId(), 0, 7);
$this->interpolation_vars['%COMMIT_EMAIL%'] = $build->getCommitterEmail();
$this->interpolation_vars['%COMMIT_MESSAGE%'] = $build->getCommitMessage();
$this->interpolation_vars['%COMMIT_URI%'] = $build->getCommitLink();
$this->interpolation_vars['%BRANCH%'] = $build->getBranch();
$this->interpolation_vars['%BRANCH_URI%'] = $build->getBranchLink();
@ -49,6 +50,7 @@ class BuildInterpolator
$this->interpolation_vars['%BUILD_URI%'] = $phpCiUrl . "build/view/" . $build->getId();
$this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%'];
$this->interpolation_vars['%PHPCI_SHORT_COMMIT%'] = $this->interpolation_vars['%SHORT_COMMIT%'];
$this->interpolation_vars['%PHPCI_COMMIT_MESSAGE%'] = $this->interpolation_vars['%COMMIT_MESSAGE%'];
$this->interpolation_vars['%PHPCI_COMMIT_EMAIL%'] = $this->interpolation_vars['%COMMIT_EMAIL%'];
$this->interpolation_vars['%PHPCI_COMMIT_URI%'] = $this->interpolation_vars['%COMMIT_URI%'];
$this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%'];
@ -61,6 +63,7 @@ class BuildInterpolator
putenv('PHPCI=1');
putenv('PHPCI_COMMIT=' . $this->interpolation_vars['%COMMIT%']);
putenv('PHPCI_SHORT_COMMIT=' . $this->interpolation_vars['%SHORT_COMMIT%']);
putenv('PHPCI_COMMIT_MESSAGE=' . $this->interpolation_vars['%COMMIT_MESSAGE%']);
putenv('PHPCI_COMMIT_EMAIL=' . $this->interpolation_vars['%COMMIT_EMAIL%']);
putenv('PHPCI_COMMIT_URI=' . $this->interpolation_vars['%COMMIT_URI%']);
putenv('PHPCI_PROJECT=' . $this->interpolation_vars['%PROJECT%']);

View file

@ -12,7 +12,7 @@ namespace PHPCI\Helper;
use b8\Config;
/**
* Login Is Disabled Helper - Checks if login is disalbed in the view
* Login Is Disabled Helper - Checks if login is disabled in the view
* @author Stephen Ball <phpci@stephen.rebelinblue.com>
* @package PHPCI
* @subpackage Web

View file

@ -71,7 +71,7 @@ class MailerFactory
} else {
// Check defaults
switch($configName) {
switch ($configName) {
case 'smtp_address':
return "localhost";
case 'default_mailto_address':

View file

@ -114,6 +114,7 @@ i din foretrukne hosting-platform.',
'default_branch' => 'Default branch navn',
'allow_public_status' => 'Tillad offentlig status-side og -billede for dette projekt?',
'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Gem Projekt',
'error_mercurial' => 'Mercurial repository-URL skal starte med http:// eller https://',
@ -203,7 +204,7 @@ Services</a> sektionen under dit Bitbucket-repository.',
'build_finished' => 'Build Afsluttet',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',

View file

@ -39,7 +39,7 @@ PHPCI',
'reset_email_title' => 'PHPCI Passwort zurücksetzen für %s',
'reset_invalid' => 'Fehlerhafte Anfrage für das Zurücksetzen eines Passwortes',
'email_address' => 'Emailadresse',
'login' => 'Login / Email Address',
'login' => 'Login / Emailadresse',
'password' => 'Passwort',
'log_in' => 'Einloggen',
@ -102,6 +102,7 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'remote' => 'Externe URL',
'local' => 'Lokaler Pfad',
'hg' => 'Mercurial',
'svn' => 'Subversion',
'where_hosted' => 'Wo wird Ihr Projekt gehostet?',
'choose_github' => 'Wählen Sie ein GitHub Repository:',
@ -114,7 +115,8 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
(falls Sie Ihrem Projektrepository kein phpci.yml hinzufügen können)',
'default_branch' => 'Name des Standardbranches',
'allow_public_status' => 'Öffentliche Statusseite und -bild für dieses Projekt einschalten?',
'archived' => 'Archived',
'archived' => 'Archiviert',
'archived_menu' => 'Archiviert',
'save_project' => 'Projekt speichern',
'error_mercurial' => 'Mercurial Repository-URL muss mit http://, oder https:// beginnen',
@ -128,7 +130,7 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'all_branches' => 'Alle Branches',
'builds' => 'Builds',
'id' => 'ID',
'date' => 'Date',
'date' => 'Datum',
'project' => 'Projekt',
'commit' => 'Commit',
'branch' => 'Branch',
@ -149,6 +151,9 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'webhooks_help_bitbucket' => 'Um für dieses Projekt automatisch einen Build zu starten, wenn neue Commits gepushed werden, fügen Sie die untenstehende URL als "POST" Service in der <a href="https://bitbucket.org/%s/admin/services">Services</a>-Sektion Ihres Bitbucket Repositories hinzu.',
// View Build
'errors' => 'Fehler',
'information' => 'Information',
'build_x_not_found' => 'Build mit ID %d existiert nicht.',
'build_n' => 'Build %d',
'rebuild_now' => 'Build neu starten',
@ -183,6 +188,14 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'phpmd' => 'PHP Mess Detector',
'phpspec' => 'PHP Spec',
'phpunit' => 'PHP Unit',
'technical_debt' => 'Technische Schulden',
'behat' => 'Behat',
'codeception_feature' => 'Feature',
'codeception_suite' => 'Suite',
'codeception_time' => 'Zeit',
'codeception_synopsis' => '<strong>%1$d</strong> Tests in <strong>%2$f</strong> Sekunden ausgeführt.
<strong>%3$d</strong> Fehler.',
'file' => 'Datei',
'line' => 'Zeile',
@ -199,14 +212,14 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'build_created' => 'Build erstellt',
'build_started' => 'Build gestartet',
'build_finished' => 'Build abgeschlossen',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_message' => 'Nachricht',
'test_no_message' => 'Keine Nachricht',
'test_success' => 'Erfolgreich: %d',
'test_fail' => 'Fehlschläge: %d',
'test_skipped' => 'Übersprungen: %d',
'test_error' => 'Fehler: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
'test_total' => '%d Test(s)',
// Users
'name' => 'Name',
@ -282,6 +295,19 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'search_packagist_for_more' => 'Packagist nach mehr Packages durchsuchen',
'search' => 'Suchen &raquo;',
// Summary plugin
'build-summary' => 'Zusammenfassung',
'stage' => 'Abschnitt',
'duration' => 'Dauer',
'plugin' => 'Plugin',
'stage_setup' => 'Vorbereitung',
'stage_test' => 'Test',
'stage_complete' => 'Vollständig',
'stage_success' => 'Erfolg',
'stage_failure' => 'Fehlschlag',
'stage_broken' => 'Defekt',
'stage_fixed' => 'Behoben',
// Installer
'installation_url' => 'PHPCI Installations-URL',
'db_host' => 'Datenbankserver',
@ -390,5 +416,18 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'build_file_missing' => 'Angegebene Builddatei existiert nicht.',
'property_file_missing' => 'Angegebene Eigenschaftsdatei existiert nicht.',
'could_not_process_report' => 'Konnte den von diesem Tool erstellten Bericht nicht verarbeiten.',
'shell_not_enabled' => 'Das Shell-Plugin ist nicht aktiviert. Bitte aktivieren Sie es via config.yml.'
'shell_not_enabled' => 'Das Shell-Plugin ist nicht aktiviert. Bitte aktivieren Sie es via config.yml.',
// Error Levels:
'critical' => 'Kritisch',
'high' => 'Hoch',
'normal' => 'Normal',
'low' => 'Niedrig',
// Plugins that generate errors:
'php_mess_detector' => 'PHP Mess Detector',
'php_code_sniffer' => 'PHP Code Sniffer',
'php_unit' => 'PHP Unit',
'php_cpd' => 'PHP Copy/Paste Detector',
'php_docblock_checker' => 'PHP Docblock Checker',
);

View file

@ -115,6 +115,7 @@ PHPCI',
'default_branch' => 'Προκαθορισμένο όνομα διακλάδωσης',
'allow_public_status' => 'Ενεργοποίηση της σελίδας δημόσιας κατάστασης και την εικόνα για το έργο αυτό;',
'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Αποθήκευση έργου',
'error_mercurial' => 'Ο σύνδεσμος URL του ευμετάβλητου αποθετηρίου πρέπει να ξεκινάει με http:// ή https://',
@ -204,7 +205,7 @@ Services</a> του Bitbucket αποθετηρίου σας.',
'build_finished' => 'Η κατασκευή ολοκληρώθηκε',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',

View file

@ -116,6 +116,7 @@ PHPCI',
'default_branch' => 'Default branch name',
'allow_public_status' => 'Enable public status page and image for this project?',
'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Save Project',
'error_mercurial' => 'Mercurial repository URL must be start with http:// or https://',
@ -153,6 +154,9 @@ PHPCI',
Services</a> section of your Bitbucket repository.',
// View Build
'errors' => 'Errors',
'information' => 'Information',
'build_x_not_found' => 'Build with ID %d does not exist.',
'build_n' => 'Build %d',
'rebuild_now' => 'Rebuild Now',
@ -213,7 +217,7 @@ PHPCI',
'build_finished' => 'Build Finished',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
@ -295,6 +299,19 @@ PHPCI',
'search_packagist_for_more' => 'Search Packagist for more packages',
'search' => 'Search &raquo;',
// Summary plugin
'build-summary' => 'Summary',
'stage' => 'Stage',
'duration' => 'Duration',
'plugin' => 'Plugin',
'stage_setup' => 'Setup',
'stage_test' => 'Test',
'stage_complete' => 'Complete',
'stage_success' => 'Success',
'stage_failure' => 'Failure',
'stage_broken' => 'Broken',
'stage_fixed' => 'Fixed',
// Installer
'installation_url' => 'PHPCI Installation URL',
'db_host' => 'Database Host',
@ -355,6 +372,9 @@ PHPCI',
'project_id_argument' => 'A project ID',
'commit_id_option' => 'Commit ID to build',
'branch_name_option' => 'Branch to build',
'add_to_queue_failed' => 'Build created successfully, but failed to add to build queue. This usually happens
when PHPCI is set to use a beanstalkd server that does not exist,
or your beanstalkd server has stopped.',
// Run Command
'run_all_pending' => 'Run all pending PHPCI builds.',
@ -403,5 +423,22 @@ PHPCI',
'build_file_missing' => 'Specified build file does not exist.',
'property_file_missing' => 'Specified property file does not exist.',
'could_not_process_report' => 'Could not process the report generated by this tool.',
'shell_not_enabled' => 'The shell plugin is not enabled. Please enable it via config.yml.'
'shell_not_enabled' => 'The shell plugin is not enabled. Please enable it via config.yml.',
// Error Levels:
'critical' => 'Critical',
'high' => 'High',
'normal' => 'Normal',
'low' => 'Low',
// Plugins that generate errors:
'php_mess_detector' => 'PHP Mess Detector',
'php_code_sniffer' => 'PHP Code Sniffer',
'php_unit' => 'PHP Unit',
'php_cpd' => 'PHP Copy/Paste Detector',
'php_docblock_checker' => 'PHP Docblock Checker',
'behat' => 'Behat',
'technical_debt' => 'Technical Debt',
);

View file

@ -115,6 +115,7 @@ PHPCI',
'default_branch' => 'Nombre de la rama por defecto',
'allow_public_status' => '¿Activar página pública con el estado del proyecto?',
'archived' => 'Archivado',
'archived_menu' => 'Archivado',
'save_project' => 'Guardar Proyecto',
'error_mercurial' => 'La URL del repositorio de Mercurial debe comenzar con http:// or https://',

View file

@ -115,6 +115,7 @@ PHPCI',
'default_branch' => 'Nom de la branche par défaut',
'allow_public_status' => 'Activer la page de statut publique et l\'image pour ce projet&nbsp;?',
'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Enregistrer le projet',
'error_mercurial' => 'Les URLs de dépôt Mercurial doivent commencer par http:// ou https://',
@ -286,6 +287,17 @@ PHPCI',
'search_packagist_for_more' => 'Rechercher sur Packagist pour trouver plus de paquets',
'search' => 'Rechercher &raquo;',
// Summary plugin
'build-summary' => 'Résumé',
'stage' => 'Étape',
'duration' => 'Durée',
'plugin' => 'Plugin',
'stage_setup' => 'Préparation',
'stage_test' => 'Test',
'stage_complete' => 'Terminé',
'stage_success' => 'Succes',
'stage_failure' => 'Échec',
// Installer
'installation_url' => 'URL d\'installation de PHPCI',
'db_host' => 'Hôte de la BDD',

View file

@ -113,8 +113,9 @@ PHPCI',
(se non puoi aggiungere il file phpci.yml nel repository di questo progetto)',
'default_branch' => 'Nome del branch di default',
'allow_public_status' => 'Vuoi rendere pubblica la pagina dello stato e l\'immagine per questo progetto?',
'save_project' => 'Salva il Progetto',
'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Salva il Progetto',
'error_mercurial' => 'L\'URL del repository Mercurial URL deve iniziare con http:// o https://',
'error_remote' => 'L\'URL del repository deve iniziare con git://, http:// o https://',
@ -206,7 +207,7 @@ PHPCI',
'build_finished' => 'Build Terminata',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',

View file

@ -115,6 +115,7 @@ van je gekozen source code hosting platform',
'default_branch' => 'Standaard branch naam',
'allow_public_status' => 'Publieke statuspagina en afbeelding beschikbaar maken voor dit project?',
'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Project opslaan',
'error_mercurial' => 'Mercurial repository URL dient te starten met http:// of https://',
@ -204,7 +205,7 @@ Services</a> sectie van je Bitbucket repository toegevoegd worden.',
'build_finished' => 'Build beëindigd',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',

View file

@ -116,6 +116,7 @@ od wybranego kodu źródłowego platformy hostingowej.',
'default_branch' => 'Domyślna nazwa gałęzi',
'allow_public_status' => 'Włączyć status publiczny dla tego projektu?',
'archived' => 'W archiwum',
'archived_menu' => 'W archiwum',
'save_project' => 'Zachowaj Projekt',
'error_mercurial' => 'URL repozytorium Mercurialnego powinno zaczynać się od http:// and https://',
@ -129,7 +130,7 @@ od wybranego kodu źródłowego platformy hostingowej.',
'all_branches' => 'Wszystkie Gałęzie',
'builds' => 'Budowania',
'id' => 'ID',
'date' => 'Date',
'date' => 'Data',
'project' => 'Projekt',
'commit' => 'Commit',
'branch' => 'Gałąź',
@ -205,14 +206,14 @@ Services</a> repozytoria Bitbucket.',
'build_created' => 'Budowanie Stworzone',
'build_started' => 'Budowanie Rozpoczęte',
'build_finished' => 'Budowanie Zakończone',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
'test_message' => 'Wiadomość',
'test_no_message' => 'Brak wiadomości',
'test_success' => 'Powodzenie: %d',
'test_fail' => 'Niepowodzenia: %d',
'test_skipped' => 'Pominęte: %d',
'test_error' => 'Błędy: %d',
'test_todo' => 'Do zrobienia: %d',
'test_total' => '%d test(ów)',
// Users
'name' => 'Nazwa',
@ -343,10 +344,10 @@ Przejrzyj powyższą listę błędów przed kontynuowaniem.',
'incorrect_format' => 'Niepoprawny format',
// Create Build Command
'create_build_project' => 'Create a build for a project',
'project_id_argument' => 'A project ID',
'commit_id_option' => 'Commit ID to build',
'branch_name_option' => 'Branch to build',
'create_build_project' => 'Utwórz budowanie dla projektu',
'project_id_argument' => 'ID projektu',
'commit_id_option' => 'ID Commita do budowania',
'branch_name_option' => 'Gałąź do budowania',
// Run Command
'run_all_pending' => 'Uruchom wszystkie oczekujące budowy w PHPCI',

View file

@ -42,7 +42,6 @@ PHPCI',
'password' => 'Пароль',
'log_in' => 'Войти',
// Top Nav
'toggle_navigation' => 'Скрыть/показать панель навигации',
'n_builds_pending' => '%d сборок ожидает',
@ -108,12 +107,13 @@ PHPCI',
'repo_name' => 'Репозиторий / Внешний URL / Локальный путь',
'project_title' => 'Название проекта',
'project_private_key' => 'Приватный ключ для доступа к репозиторию
(оставьте поле пустым для локального использования и/или анонимного доступа)',
(оставьте поле пустым для локального использования и/или анонимного доступа)',
'build_config' => 'Конфигурация сборки проекта для PHPCI
(если вы не добавили файл phpci.yml в репозиторий вашего проекта)',
(если вы не добавили файл phpci.yml в репозиторий вашего проекта)',
'default_branch' => 'Ветка по умолчанию',
'allow_public_status' => 'Разрешить публичный статус и изображение (статуса) для проекта',
'archived' => 'Запакован',
'archived' => 'Архивный',
'archived_menu' => 'Архив',
'save_project' => 'Сохранить проект',
'error_mercurial' => 'URL репозитория Mercurial должен начинаться с http:// или https://',
@ -139,13 +139,13 @@ PHPCI',
'webhooks' => 'Webhooks',
'webhooks_help_github' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже в качестве нового хука в разделе настроек <a href="https://github.com/%s/settings/hooks">Webhooks
and Services</a> вашего GitHub репозитория.',
and Services</a> вашего GitHub репозитория.',
'webhooks_help_gitlab' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже в качестве "WebHook URL"
в разделе "Web Hooks" вашего GitLab репозитория.',
'webhooks_help_bitbucket' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже как "POST" сервис в разделе <a href="https://bitbucket.org/%s/admin/services">
Services</a> вашего Bitbucket репозитория.',
Services</a> вашего Bitbucket репозитория.',
// View Build
'build_x_not_found' => 'Сборки с ID %d не существует.',
@ -185,6 +185,11 @@ PHPCI',
'technical_debt' => 'Технические долги',
'behat' => 'Behat',
'codeception_feature' => 'Свойство',
'codeception_suite' => 'Набор',
'codeception_time' => 'Время',
'codeception_synopsis' => 'Тестов выполнено: <strong>%1$d</strong> (за <strong>%2$f</strong> сек.). Провалов: <strong>%3$d</strong>.',
'file' => 'Файл',
'line' => 'Строка',
'class' => 'Класс',
@ -202,12 +207,12 @@ PHPCI',
'build_finished' => 'Сборка окончена',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',
'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)',
'test_success' => 'Успешно: %d',
'test_fail' => 'Провалено: %d',
'test_skipped' => 'Пропущено: %d',
'test_error' => 'Ошибок: %d',
'test_todo' => 'Todo: %d',
'test_total' => 'Тестов: %d',
// Users
'name' => 'Имя',
@ -282,6 +287,19 @@ PHPCI',
'search_packagist_for_more' => 'Искать на Packagist',
'search' => 'Искать &raquo;',
// Summary plugin
'build-summary' => 'Сводка',
'stage' => 'Этап',
'duration' => 'Продолжительность',
'plugin' => 'Плагин',
'stage_setup' => 'Установка',
'stage_test' => 'тестирование',
'stage_complete' => 'Завершение',
'stage_success' => 'Успешное завершение',
'stage_failure' => 'Провал',
'stage_broken' => 'Поломка',
'stage_fixed' => 'Исправление',
// Installer
'installation_url' => 'URL-адрес PHPCI для установки',
'db_host' => 'Хост базы данных',

View file

@ -113,7 +113,8 @@ PHPCI',
(якщо ви не додали файл phpci.yml до репозиторію вашого проекту)',
'default_branch' => 'Назва гілки за замовчуванням',
'allow_public_status' => 'Увімкнути публічну сторінку статусу та зображення для цього проекта?',
'archived' => 'Archived',
'archived' => 'Архівний',
'archived_menu' => 'Архів',
'save_project' => 'Зберегти проект',
'error_mercurial' => 'URL репозиторію Mercurial повинен починатись із http:// або https://',
@ -204,7 +205,7 @@ PHPCI',
'build_finished' => 'Збірка завершена',
'test_message' => 'Message',
'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d',
'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d',

View file

@ -67,7 +67,7 @@ class BuildLogger implements LoggerAwareInterface
}
}
/**
/**
* Add a success-coloured message to the log.
* @param string
*/
@ -98,6 +98,17 @@ class BuildLogger implements LoggerAwareInterface
);
}
/**
* Add a debug message to the log.
* @param string
*/
public function logDebug($message)
{
if (defined('PHPCI_DEBUG_MODE') && PHPCI_DEBUG_MODE) {
$this->log("\033[0;33m" . $message . "\033[0m");
}
}
/**
* Sets a logger instance on the object
*

View file

@ -20,7 +20,7 @@ class FixDatabaseColumns extends AbstractMigration
$build->changeColumn('project_id', 'integer', array('null' => false));
$build->changeColumn('commit_id', 'string', array('limit' => 50, 'null' => false));
$build->changeColumn('status', 'integer', array('null' => false));
$build->changeColumn('log', 'text', array('null' => true, 'default' => ''));
$build->changeColumn('log', 'text', array('null' => true));
$build->changeColumn('branch', 'string', array('limit' => 50, 'null' => false, 'default' => 'master'));
$build->changeColumn('created', 'datetime', array('null' => true));
$build->changeColumn('started', 'datetime', array('null' => true));

View file

@ -14,7 +14,6 @@ class FixColumnTypes extends AbstractMigration
$build = $this->table('build');
$build->changeColumn('log', 'text', array(
'null' => true,
'default' => '',
'limit' => MysqlAdapter::TEXT_MEDIUM,
));

View file

@ -0,0 +1,29 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddProjectGroups extends AbstractMigration
{
public function change()
{
$table = $this->table('project_group');
$table->addColumn('title', 'string', array('limit' => 100, 'null' => false));
$table->save();
$group = new \PHPCI\Model\ProjectGroup();
$group->setTitle('Projects');
/** @var \PHPCI\Model\ProjectGroup $group */
$group = \b8\Store\Factory::getStore('ProjectGroup')->save($group);
$table = $this->table('project');
$table->addColumn('group_id', 'integer', array(
'signed' => true,
'null' => false,
'default' => $group->getId(),
));
$table->addForeignKey('group_id', 'project_group', 'id', array('delete'=> 'RESTRICT', 'update' => 'CASCADE'));
$table->save();
}
}

View file

@ -0,0 +1,40 @@
<?php
use Phinx\Migration\AbstractMigration;
class RemoveUniqueNameIndex extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$user = $this->table('user');
if ($user->hasIndex('name', array('unique' => true))) {
$user->removeIndex('name', array('unique' => true));
$user->save();
}
$user->addIndex('name', array('unique' => false));
$user->save();
}
}

View file

@ -0,0 +1,24 @@
<?php
use Phinx\Migration\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;
class ErrorsTable extends AbstractMigration
{
public function change()
{
$table = $this->table('build_error');
$table->addColumn('build_id', 'integer', array('signed' => true));
$table->addColumn('plugin', 'string', array('limit' => 100));
$table->addColumn('file', 'string', array('limit' => 250, 'null' => true));
$table->addColumn('line_start', 'integer', array('signed' => false, 'null' => true));
$table->addColumn('line_end', 'integer', array('signed' => false, 'null' => true));
$table->addColumn('severity', 'integer', array('signed' => false, 'limit' => MysqlAdapter::INT_TINY));
$table->addColumn('message', 'string', array('limit' => 250));
$table->addColumn('created_date', 'datetime');
$table->addIndex(array('build_id', 'created_date'), array('unique' => false));
$table->addForeignKey('build_id', 'build', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE'));
$table->save();
}
}

View file

@ -0,0 +1,183 @@
<?php
use Phinx\Migration\AbstractMigration;
use PHPCI\Model\BuildMeta;
use PHPCI\Model\BuildError;
class ConvertErrors extends AbstractMigration
{
/**
* @var \PHPCI\Store\BuildMetaStore
*/
protected $metaStore;
/**
* @var \PHPCI\Store\BuildErrorStore
*/
protected $errorStore;
public function change()
{
$count = 100;
$this->metaStore = \b8\Store\Factory::getStore('BuildMeta');
$this->errorStore = \b8\Store\Factory::getStore('BuildError');
while ($count == 100) {
$data = $this->metaStore->getErrorsForUpgrade(100);
$count = count($data);
/** @var \PHPCI\Model\BuildMeta $meta */
foreach ($data as $meta) {
try {
switch ($meta->getMetaKey()) {
case 'phpmd-data':
$this->processPhpMdMeta($meta);
break;
case 'phpcs-data':
$this->processPhpCsMeta($meta);
break;
case 'phpdoccheck-data':
$this->processPhpDocCheckMeta($meta);
break;
case 'phpcpd-data':
$this->processPhpCpdMeta($meta);
break;
case 'technicaldebt-data':
$this->processTechnicalDebtMeta($meta);
break;
}
} catch (\Exception $ex) {}
$this->metaStore->delete($meta);
}
}
}
protected function processPhpMdMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('php_mess_detector');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line_start']);
$buildError->setLineEnd($error['line_end']);
$buildError->setSeverity(BuildError::SEVERITY_HIGH);
$buildError->setMessage($error['message']);
$this->errorStore->save($buildError);
}
}
}
protected function processPhpCsMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('php_code_sniffer');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line']);
$buildError->setLineEnd($error['line']);
$buildError->setMessage($error['message']);
switch ($error['type']) {
case 'ERROR':
$buildError->setSeverity(BuildError::SEVERITY_HIGH);
break;
case 'WARNING':
$buildError->setSeverity(BuildError::SEVERITY_LOW);
break;
}
$this->errorStore->save($buildError);
}
}
}
protected function processPhpDocCheckMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('php_docblock_checker');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line']);
$buildError->setLineEnd($error['line']);
switch ($error['type']) {
case 'method':
$buildError->setMessage($error['class'] . '::' . $error['method'] . ' is missing a docblock.');
$buildError->setSeverity(BuildError::SEVERITY_NORMAL);
break;
case 'class':
$buildError->setMessage('Class ' . $error['class'] . ' is missing a docblock.');
$buildError->setSeverity(BuildError::SEVERITY_LOW);
break;
}
$this->errorStore->save($buildError);
}
}
}
protected function processPhpCpdMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('php_cpd');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line_start']);
$buildError->setLineEnd($error['line_end']);
$buildError->setSeverity(BuildError::SEVERITY_NORMAL);
$buildError->setMessage('Copy and paste detected.');
$this->errorStore->save($buildError);
}
}
}
protected function processTechnicalDebtMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('technical_debt');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line']);
$buildError->setSeverity(BuildError::SEVERITY_NORMAL);
$buildError->setMessage($error['message']);
$this->errorStore->save($buildError);
}
}
}
}

View file

@ -0,0 +1,18 @@
<?php
use Phinx\Migration\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;
class ProjectTableDefaults extends AbstractMigration
{
public function change()
{
$this->table('project')
->changeColumn('build_config', MysqlAdapter::PHINX_TYPE_TEXT, array('null' => true))
->changeColumn('archived', MysqlAdapter::PHINX_TYPE_INTEGER, array(
'length' => MysqlAdapter::INT_TINY,
'default' => 0,
))
->save();
}
}

View file

@ -118,7 +118,7 @@ class BuildBase extends Model
'default' => null,
),
'log' => array(
'type' => 'text',
'type' => 'mediumtext',
'nullable' => true,
'default' => null,
),
@ -621,6 +621,18 @@ class BuildBase extends Model
return $this->setProjectId($value->getId());
}
/**
* Get BuildError models by BuildId for this Build.
*
* @uses \PHPCI\Store\BuildErrorStore::getByBuildId()
* @uses \PHPCI\Model\BuildError
* @return \PHPCI\Model\BuildError[]
*/
public function getBuildBuildErrors()
{
return Factory::getStore('BuildError', 'PHPCI')->getByBuildId($this->getId());
}
/**
* Get BuildMeta models by BuildId for this Build.
*

View file

@ -0,0 +1,503 @@
<?php
/**
* BuildError base model for table: build_error
*/
namespace PHPCI\Model\Base;
use PHPCI\Model;
use b8\Store\Factory;
/**
* BuildError Base Model
*/
class BuildErrorBase extends Model
{
/**
* @var array
*/
public static $sleepable = array();
/**
* @var string
*/
protected $tableName = 'build_error';
/**
* @var string
*/
protected $modelName = 'BuildError';
/**
* @var array
*/
protected $data = array(
'id' => null,
'build_id' => null,
'plugin' => null,
'file' => null,
'line_start' => null,
'line_end' => null,
'severity' => null,
'message' => null,
'created_date' => null,
);
/**
* @var array
*/
protected $getters = array(
// Direct property getters:
'id' => 'getId',
'build_id' => 'getBuildId',
'plugin' => 'getPlugin',
'file' => 'getFile',
'line_start' => 'getLineStart',
'line_end' => 'getLineEnd',
'severity' => 'getSeverity',
'message' => 'getMessage',
'created_date' => 'getCreatedDate',
// Foreign key getters:
'Build' => 'getBuild',
);
/**
* @var array
*/
protected $setters = array(
// Direct property setters:
'id' => 'setId',
'build_id' => 'setBuildId',
'plugin' => 'setPlugin',
'file' => 'setFile',
'line_start' => 'setLineStart',
'line_end' => 'setLineEnd',
'severity' => 'setSeverity',
'message' => 'setMessage',
'created_date' => 'setCreatedDate',
// Foreign key setters:
'Build' => 'setBuild',
);
/**
* @var array
*/
public $columns = array(
'id' => array(
'type' => 'int',
'length' => 11,
'primary_key' => true,
'auto_increment' => true,
'default' => null,
),
'build_id' => array(
'type' => 'int',
'length' => 11,
'default' => null,
),
'plugin' => array(
'type' => 'varchar',
'length' => 100,
'default' => null,
),
'file' => array(
'type' => 'varchar',
'length' => 250,
'nullable' => true,
'default' => null,
),
'line_start' => array(
'type' => 'int',
'length' => 11,
'nullable' => true,
'default' => null,
),
'line_end' => array(
'type' => 'int',
'length' => 11,
'nullable' => true,
'default' => null,
),
'severity' => array(
'type' => 'tinyint',
'length' => 3,
'default' => null,
),
'message' => array(
'type' => 'varchar',
'length' => 250,
'default' => null,
),
'created_date' => array(
'type' => 'datetime',
'default' => null,
),
);
/**
* @var array
*/
public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'),
'build_id' => array('columns' => 'build_id, created_date'),
);
/**
* @var array
*/
public $foreignKeys = array(
'build_error_ibfk_1' => array(
'local_col' => 'build_id',
'update' => 'CASCADE',
'delete' => 'CASCADE',
'table' => 'build',
'col' => 'id'
),
);
/**
* Get the value of Id / id.
*
* @return int
*/
public function getId()
{
$rtn = $this->data['id'];
return $rtn;
}
/**
* Get the value of BuildId / build_id.
*
* @return int
*/
public function getBuildId()
{
$rtn = $this->data['build_id'];
return $rtn;
}
/**
* Get the value of Plugin / plugin.
*
* @return string
*/
public function getPlugin()
{
$rtn = $this->data['plugin'];
return $rtn;
}
/**
* Get the value of File / file.
*
* @return string
*/
public function getFile()
{
$rtn = $this->data['file'];
return $rtn;
}
/**
* Get the value of LineStart / line_start.
*
* @return int
*/
public function getLineStart()
{
$rtn = $this->data['line_start'];
return $rtn;
}
/**
* Get the value of LineEnd / line_end.
*
* @return int
*/
public function getLineEnd()
{
$rtn = $this->data['line_end'];
return $rtn;
}
/**
* Get the value of Severity / severity.
*
* @return int
*/
public function getSeverity()
{
$rtn = $this->data['severity'];
return $rtn;
}
/**
* Get the value of Message / message.
*
* @return string
*/
public function getMessage()
{
$rtn = $this->data['message'];
return $rtn;
}
/**
* Get the value of CreatedDate / created_date.
*
* @return \DateTime
*/
public function getCreatedDate()
{
$rtn = $this->data['created_date'];
if (!empty($rtn)) {
$rtn = new \DateTime($rtn);
}
return $rtn;
}
/**
* Set the value of Id / id.
*
* Must not be null.
* @param $value int
*/
public function setId($value)
{
$this->_validateNotNull('Id', $value);
$this->_validateInt('Id', $value);
if ($this->data['id'] === $value) {
return;
}
$this->data['id'] = $value;
$this->_setModified('id');
}
/**
* Set the value of BuildId / build_id.
*
* Must not be null.
* @param $value int
*/
public function setBuildId($value)
{
$this->_validateNotNull('BuildId', $value);
$this->_validateInt('BuildId', $value);
if ($this->data['build_id'] === $value) {
return;
}
$this->data['build_id'] = $value;
$this->_setModified('build_id');
}
/**
* Set the value of Plugin / plugin.
*
* Must not be null.
* @param $value string
*/
public function setPlugin($value)
{
$this->_validateNotNull('Plugin', $value);
$this->_validateString('Plugin', $value);
if ($this->data['plugin'] === $value) {
return;
}
$this->data['plugin'] = $value;
$this->_setModified('plugin');
}
/**
* Set the value of File / file.
*
* @param $value string
*/
public function setFile($value)
{
$this->_validateString('File', $value);
if ($this->data['file'] === $value) {
return;
}
$this->data['file'] = $value;
$this->_setModified('file');
}
/**
* Set the value of LineStart / line_start.
*
* @param $value int
*/
public function setLineStart($value)
{
$this->_validateInt('LineStart', $value);
if ($this->data['line_start'] === $value) {
return;
}
$this->data['line_start'] = $value;
$this->_setModified('line_start');
}
/**
* Set the value of LineEnd / line_end.
*
* @param $value int
*/
public function setLineEnd($value)
{
$this->_validateInt('LineEnd', $value);
if ($this->data['line_end'] === $value) {
return;
}
$this->data['line_end'] = $value;
$this->_setModified('line_end');
}
/**
* Set the value of Severity / severity.
*
* Must not be null.
* @param $value int
*/
public function setSeverity($value)
{
$this->_validateNotNull('Severity', $value);
$this->_validateInt('Severity', $value);
if ($this->data['severity'] === $value) {
return;
}
$this->data['severity'] = $value;
$this->_setModified('severity');
}
/**
* Set the value of Message / message.
*
* Must not be null.
* @param $value string
*/
public function setMessage($value)
{
$this->_validateNotNull('Message', $value);
$this->_validateString('Message', $value);
if ($this->data['message'] === $value) {
return;
}
$this->data['message'] = $value;
$this->_setModified('message');
}
/**
* Set the value of CreatedDate / created_date.
*
* Must not be null.
* @param $value \DateTime
*/
public function setCreatedDate($value)
{
$this->_validateNotNull('CreatedDate', $value);
$this->_validateDate('CreatedDate', $value);
if ($this->data['created_date'] === $value) {
return;
}
$this->data['created_date'] = $value;
$this->_setModified('created_date');
}
/**
* Get the Build model for this BuildError by Id.
*
* @uses \PHPCI\Store\BuildStore::getById()
* @uses \PHPCI\Model\Build
* @return \PHPCI\Model\Build
*/
public function getBuild()
{
$key = $this->getBuildId();
if (empty($key)) {
return null;
}
$cacheKey = 'Cache.Build.' . $key;
$rtn = $this->cache->get($cacheKey, null);
if (empty($rtn)) {
$rtn = Factory::getStore('Build', 'PHPCI')->getById($key);
$this->cache->set($cacheKey, $rtn);
}
return $rtn;
}
/**
* Set Build - Accepts an ID, an array representing a Build or a Build model.
*
* @param $value mixed
*/
public function setBuild($value)
{
// Is this an instance of Build?
if ($value instanceof \PHPCI\Model\Build) {
return $this->setBuildObject($value);
}
// Is this an array representing a Build item?
if (is_array($value) && !empty($value['id'])) {
return $this->setBuildId($value['id']);
}
// Is this a scalar value representing the ID of this foreign key?
return $this->setBuildId($value);
}
/**
* Set Build - Accepts a Build model.
*
* @param $value \PHPCI\Model\Build
*/
public function setBuildObject(\PHPCI\Model\Build $value)
{
return $this->setBuildId($value->getId());
}
}

View file

@ -99,7 +99,7 @@ class BuildMetaBase extends Model
'default' => null,
),
'meta_value' => array(
'type' => 'text',
'type' => 'mediumtext',
'default' => null,
),
);

View file

@ -45,6 +45,7 @@ class ProjectBase extends Model
'ssh_public_key' => null,
'allow_public_status' => null,
'archived' => null,
'group_id' => null,
);
/**
@ -64,8 +65,10 @@ class ProjectBase extends Model
'ssh_public_key' => 'getSshPublicKey',
'allow_public_status' => 'getAllowPublicStatus',
'archived' => 'getArchived',
'group_id' => 'getGroupId',
// Foreign key getters:
'Group' => 'getGroup',
);
/**
@ -85,8 +88,10 @@ class ProjectBase extends Model
'ssh_public_key' => 'setSshPublicKey',
'allow_public_status' => 'setAllowPublicStatus',
'archived' => 'setArchived',
'group_id' => 'setGroupId',
// Foreign key setters:
'Group' => 'setGroup',
);
/**
@ -153,10 +158,14 @@ class ProjectBase extends Model
),
'archived' => array(
'type' => 'tinyint',
'length' => 4,
'nullable' => true,
'length' => 1,
'default' => null,
),
'group_id' => array(
'type' => 'int',
'length' => 11,
'default' => 1,
),
);
/**
@ -165,12 +174,20 @@ class ProjectBase extends Model
public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'),
'idx_project_title' => array('columns' => 'title'),
'group_id' => array('columns' => 'group_id'),
);
/**
* @var array
*/
public $foreignKeys = array(
'project_ibfk_1' => array(
'local_col' => 'group_id',
'update' => 'CASCADE',
'delete' => '',
'table' => 'project_group',
'col' => 'id'
),
);
/**
@ -317,6 +334,18 @@ class ProjectBase extends Model
return $rtn;
}
/**
* Get the value of GroupId / group_id.
*
* @return int
*/
public function getGroupId()
{
$rtn = $this->data['group_id'];
return $rtn;
}
/**
* Set the value of Id / id.
*
@ -530,10 +559,12 @@ class ProjectBase extends Model
/**
* Set the value of Archived / archived.
*
* Must not be null.
* @param $value int
*/
public function setArchived($value)
{
$this->_validateNotNull('Archived', $value);
$this->_validateInt('Archived', $value);
if ($this->data['archived'] === $value) {
@ -545,6 +576,83 @@ class ProjectBase extends Model
$this->_setModified('archived');
}
/**
* Set the value of GroupId / group_id.
*
* Must not be null.
* @param $value int
*/
public function setGroupId($value)
{
$this->_validateNotNull('GroupId', $value);
$this->_validateInt('GroupId', $value);
if ($this->data['group_id'] === $value) {
return;
}
$this->data['group_id'] = $value;
$this->_setModified('group_id');
}
/**
* Get the ProjectGroup model for this Project by Id.
*
* @uses \PHPCI\Store\ProjectGroupStore::getById()
* @uses \PHPCI\Model\ProjectGroup
* @return \PHPCI\Model\ProjectGroup
*/
public function getGroup()
{
$key = $this->getGroupId();
if (empty($key)) {
return null;
}
$cacheKey = 'Cache.ProjectGroup.' . $key;
$rtn = $this->cache->get($cacheKey, null);
if (empty($rtn)) {
$rtn = Factory::getStore('ProjectGroup', 'PHPCI')->getById($key);
$this->cache->set($cacheKey, $rtn);
}
return $rtn;
}
/**
* Set Group - Accepts an ID, an array representing a ProjectGroup or a ProjectGroup model.
*
* @param $value mixed
*/
public function setGroup($value)
{
// Is this an instance of ProjectGroup?
if ($value instanceof \PHPCI\Model\ProjectGroup) {
return $this->setGroupObject($value);
}
// Is this an array representing a ProjectGroup item?
if (is_array($value) && !empty($value['id'])) {
return $this->setGroupId($value['id']);
}
// Is this a scalar value representing the ID of this foreign key?
return $this->setGroupId($value);
}
/**
* Set Group - Accepts a ProjectGroup model.
*
* @param $value \PHPCI\Model\ProjectGroup
*/
public function setGroupObject(\PHPCI\Model\ProjectGroup $value)
{
return $this->setGroupId($value->getId());
}
/**
* Get Build models by ProjectId for this Project.
*

View file

@ -0,0 +1,168 @@
<?php
/**
* ProjectGroup base model for table: project_group
*/
namespace PHPCI\Model\Base;
use PHPCI\Model;
use b8\Store\Factory;
/**
* ProjectGroup Base Model
*/
class ProjectGroupBase extends Model
{
/**
* @var array
*/
public static $sleepable = array();
/**
* @var string
*/
protected $tableName = 'project_group';
/**
* @var string
*/
protected $modelName = 'ProjectGroup';
/**
* @var array
*/
protected $data = array(
'id' => null,
'title' => null,
);
/**
* @var array
*/
protected $getters = array(
// Direct property getters:
'id' => 'getId',
'title' => 'getTitle',
// Foreign key getters:
);
/**
* @var array
*/
protected $setters = array(
// Direct property setters:
'id' => 'setId',
'title' => 'setTitle',
// Foreign key setters:
);
/**
* @var array
*/
public $columns = array(
'id' => array(
'type' => 'int',
'length' => 11,
'primary_key' => true,
'auto_increment' => true,
'default' => null,
),
'title' => array(
'type' => 'varchar',
'length' => 100,
'default' => null,
),
);
/**
* @var array
*/
public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'),
);
/**
* @var array
*/
public $foreignKeys = array(
);
/**
* Get the value of Id / id.
*
* @return int
*/
public function getId()
{
$rtn = $this->data['id'];
return $rtn;
}
/**
* Get the value of Title / title.
*
* @return string
*/
public function getTitle()
{
$rtn = $this->data['title'];
return $rtn;
}
/**
* Set the value of Id / id.
*
* Must not be null.
* @param $value int
*/
public function setId($value)
{
$this->_validateNotNull('Id', $value);
$this->_validateInt('Id', $value);
if ($this->data['id'] === $value) {
return;
}
$this->data['id'] = $value;
$this->_setModified('id');
}
/**
* Set the value of Title / title.
*
* Must not be null.
* @param $value string
*/
public function setTitle($value)
{
$this->_validateNotNull('Title', $value);
$this->_validateString('Title', $value);
if ($this->data['title'] === $value) {
return;
}
$this->data['title'] = $value;
$this->_setModified('title');
}
/**
* Get Project models by GroupId for this ProjectGroup.
*
* @uses \PHPCI\Store\ProjectStore::getByGroupId()
* @uses \PHPCI\Model\Project
* @return \PHPCI\Model\Project[]
*/
public function getGroupProjects()
{
return Factory::getStore('Project', 'PHPCI')->getByGroupId($this->getId());
}
}

View file

@ -106,6 +106,8 @@ class UserBase extends Model
public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'),
'idx_email' => array('unique' => true, 'columns' => 'email'),
'email' => array('unique' => true, 'columns' => 'email'),
'name' => array('columns' => 'name'),
);
/**

View file

@ -28,7 +28,7 @@ class Build extends BuildBase
const STATUS_SUCCESS = 2;
const STATUS_FAILED = 3;
public $currentBuildPath = null;
public $currentBuildPath;
/**
* Get link to commit from another source (i.e. Github)
@ -99,16 +99,21 @@ class Build extends BuildBase
{
$build_config = null;
// Try phpci.yml first:
if (is_file($buildPath . '/phpci.yml')) {
$build_config = file_get_contents($buildPath . '/phpci.yml');
}
// Try getting the project build config from the database:
if (empty($build_config)) {
$build_config = $this->getProject()->getBuildConfig();
}
// Try .phpci.yml
if (is_file($buildPath . '/.phpci.yml')) {
$build_config = file_get_contents($buildPath . '/.phpci.yml');
}
// Try phpci.yml first:
if (empty($build_config) && is_file($buildPath . '/phpci.yml')) {
$build_config = file_get_contents($buildPath . '/phpci.yml');
}
// Fall back to zero config plugins:
if (empty($build_config)) {
$build_config = $this->getZeroConfigPlugins($builder);
@ -208,14 +213,36 @@ class Build extends BuildBase
/**
* Allows specific build types (e.g. Github) to report violations back to their respective services.
* @param Builder $builder
* @param $file
* @param $line
* @param $plugin
* @param $message
* @return mixed
* @param int $severity
* @param null $file
* @param null $lineStart
* @param null $lineEnd
* @return BuildError
*/
public function reportError(Builder $builder, $file, $line, $message)
{
return array($builder, $file, $line, $message);
public function reportError(
Builder $builder,
$plugin,
$message,
$severity = BuildError::SEVERITY_NORMAL,
$file = null,
$lineStart = null,
$lineEnd = null
) {
unset($builder);
$error = new BuildError();
$error->setBuild($this);
$error->setCreatedDate(new \DateTime());
$error->setPlugin($plugin);
$error->setMessage($message);
$error->setSeverity($severity);
$error->setFile($file);
$error->setLineStart($lineStart);
$error->setLineEnd($lineEnd);
return Factory::getStore('BuildError')->save($error);
}
/**
@ -228,7 +255,13 @@ class Build extends BuildBase
if (!$this->getId()) {
return null;
}
return PHPCI_BUILD_ROOT_DIR . $this->getId();
if (empty($this->currentBuildPath)) {
$buildDirectory = $this->getId() . '_' . substr(md5(microtime(true)), 0, 5);
$this->currentBuildPath = PHPCI_BUILD_ROOT_DIR . $buildDirectory . DIRECTORY_SEPARATOR;
}
return $this->currentBuildPath;
}
/**
@ -244,4 +277,25 @@ class Build extends BuildBase
exec(sprintf(IS_WIN ? 'rmdir /S /Q "%s"' : 'rm -Rf "%s"', $buildPath));
}
/**
* Get the number of seconds a build has been running for.
* @return int
*/
public function getDuration()
{
$start = $this->getStarted();
if (empty($start)) {
return 0;
}
$end = $this->getFinished();
if (empty($end)) {
$end = new \DateTime();
}
return $end->getTimestamp() - $start->getTimestamp();
}
}

View file

@ -45,39 +45,52 @@ class GithubBuild extends RemoteGitBuild
{
$token = \b8\Config::getInstance()->get('phpci.github.token');
if (empty($token)) {
if (empty($token) || empty($this->data['id'])) {
return;
}
$project = $this->getProject();
if (empty($project)) {
return;
}
$url = 'https://api.github.com/repos/'.$project->getReference().'/statuses/'.$this->getCommitId();
$http = new \b8\HttpClient();
switch($this->getStatus())
{
switch ($this->getStatus()) {
case 0:
case 1:
$status = 'pending';
$description = 'PHPCI build running.';
break;
case 2:
$status = 'success';
$description = 'PHPCI build passed.';
break;
case 3:
$status = 'failure';
$description = 'PHPCI build failed.';
break;
default:
$status = 'error';
$description = 'PHPCI build failed to complete.';
break;
}
$phpciUrl = \b8\Config::getInstance()->get('phpci.url');
$params = array( 'state' => $status,
'target_url' => $phpciUrl . '/build/view/' . $this->getId());
$params = array(
'state' => $status,
'target_url' => $phpciUrl . '/build/view/' . $this->getId(),
'description' => $description,
'context' => 'PHPCI',
);
$headers = array(
'Authorization: token ' . $token,
'Content-Type: application/x-www-form-urlencoded'
);
);
$http->setHeaders($headers);
$http->request('POST', $url, json_encode($params));
@ -105,10 +118,14 @@ class GithubBuild extends RemoteGitBuild
{
$rtn = parent::getCommitMessage($this->data['commit_message']);
$reference = $this->getProject()->getReference();
$commitLink = '<a target="_blank" href="https://github.com/' . $reference . '/issues/$1">#$1</a>';
$rtn = preg_replace('/\#([0-9]+)/', $commitLink, $rtn);
$rtn = preg_replace('/\@([a-zA-Z0-9_]+)/', '<a target="_blank" href="https://github.com/$1">@$1</a>', $rtn);
$project = $this->getProject();
if (!is_null($project)) {
$reference = $project->getReference();
$commitLink = '<a target="_blank" href="https://github.com/' . $reference . '/issues/$1">#$1</a>';
$rtn = preg_replace('/\#([0-9]+)/', $commitLink, $rtn);
$rtn = preg_replace('/\@([a-zA-Z0-9_]+)/', '<a target="_blank" href="https://github.com/$1">@$1</a>', $rtn);
}
return $rtn;
}
@ -134,7 +151,7 @@ class GithubBuild extends RemoteGitBuild
$link = 'https://github.com/' . $reference . '/';
$link .= 'blob/' . $branch . '/';
$link .= '{FILE}';
$link .= '#L{LINE}';
$link .= '#L{LINE}-L{LINE_END}';
return $link;
}
@ -173,9 +190,16 @@ class GithubBuild extends RemoteGitBuild
/**
* @inheritDoc
*/
public function reportError(Builder $builder, $file, $line, $message)
{
$diffLineNumber = $this->getDiffLineNumber($builder, $file, $line);
public function reportError(
Builder $builder,
$plugin,
$message,
$severity = BuildError::SEVERITY_NORMAL,
$file = null,
$lineStart = null,
$lineEnd = null
) {
$diffLineNumber = $this->getDiffLineNumber($builder, $file, $lineStart);
if (!is_null($diffLineNumber)) {
$helper = new Github();
@ -190,6 +214,8 @@ class GithubBuild extends RemoteGitBuild
$helper->createCommitComment($repo, $commit, $file, $diffLineNumber, $message);
}
}
return parent::reportError($builder, $plugin, $message, $severity, $file, $lineStart, $lineEnd);
}
/**
@ -201,6 +227,8 @@ class GithubBuild extends RemoteGitBuild
*/
protected function getDiffLineNumber(Builder $builder, $file, $line)
{
$line = (integer)$line;
$builder->logExecOutput(false);
$prNumber = $this->getExtra('pull_request_number');
@ -221,6 +249,6 @@ class GithubBuild extends RemoteGitBuild
$helper = new Diff();
$lines = $helper->getLinePositions($diff);
return $lines[$line];
return isset($lines[$line]) ? $lines[$line] : null;
}
}

View file

@ -0,0 +1,63 @@
<?php
/**
* BuildError model for table: build_error
*/
namespace PHPCI\Model;
use PHPCI\Model\Base\BuildErrorBase;
/**
* BuildError Model
* @uses PHPCI\Model\Base\BuildErrorBase
*/
class BuildError extends BuildErrorBase
{
const SEVERITY_CRITICAL = 0;
const SEVERITY_HIGH = 1;
const SEVERITY_NORMAL = 2;
const SEVERITY_LOW = 3;
/**
* Get the language string key for this error's severity level.
* @return string
*/
public function getSeverityString()
{
switch ($this->getSeverity()) {
case self::SEVERITY_CRITICAL:
return 'critical';
case self::SEVERITY_HIGH:
return 'high';
case self::SEVERITY_NORMAL:
return 'normal';
case self::SEVERITY_LOW:
return 'low';
}
}
/**
* Get the class to apply to HTML elements representing this error.
* @return string
*/
public function getSeverityClass()
{
switch ($this->getSeverity()) {
case self::SEVERITY_CRITICAL:
return 'danger';
case self::SEVERITY_HIGH:
return 'warning';
case self::SEVERITY_NORMAL:
return 'info';
case self::SEVERITY_LOW:
return 'default';
}
}
}

View file

@ -50,6 +50,29 @@ class Project extends ProjectBase
return null;
}
/**
* Return the previous build from a specific branch, for this project.
* @param string $branch
* @return mixed|null
*/
public function getPreviousBuild($branch = 'master')
{
$criteria = array('branch' => $branch, 'project_id' => $this->getId());
$order = array('id' => 'DESC');
$builds = Store\Factory::getStore('Build')->getWhere($criteria, 1, 1, array(), $order);
if (is_array($builds['items']) && count($builds['items'])) {
$previous = array_shift($builds['items']);
if (isset($previous) && $previous instanceof Build) {
return $previous;
}
}
return null;
}
/**
* Store this project's access_information data
* @param string|array $value
@ -73,7 +96,7 @@ class Project extends ProjectBase
$info = $this->data['access_information'];
// Handle old-format (serialized) access information first:
if (!empty($info) && substr($info, 0, 1) != '{') {
if (!empty($info) && !in_array(substr($info, 0, 1), array('{', '['))) {
$data = unserialize($info);
} else {
$data = json_decode($info, true);

View file

@ -0,0 +1,18 @@
<?php
/**
* ProjectGroup model for table: project_group
*/
namespace PHPCI\Model;
use PHPCI\Model\Base\ProjectGroupBase;
/**
* ProjectGroup Model
* @uses PHPCI\Model\Base\ProjectGroupBase
*/
class ProjectGroup extends ProjectGroupBase
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
}

View file

@ -12,6 +12,7 @@ namespace PHPCI\Plugin;
use PHPCI\Builder;
use PHPCI\Helper\Lang;
use PHPCI\Model\Build;
use PHPCI\Model\BuildError;
/**
* Behat BDD Plugin
@ -119,7 +120,14 @@ class Behat implements \PHPCI\Plugin
'line' => $lineParts[1]
);
$this->build->reportError($this->phpci, $lineParts[0], $lineParts[1], 'Behat scenario failed.');
$this->build->reportError(
$this->phpci,
'behat',
'Behat scenario failed.',
BuildError::SEVERITY_HIGH,
$lineParts[0],
$lineParts[1]
);
}
}

View file

@ -38,19 +38,19 @@ class Campfire implements \PHPCI\Plugin
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->build = $build;
$this->message = $options['message'];
$this->phpci = $phpci;
$this->build = $build;
$this->message = $options['message'];
$this->userAgent = "PHPCI/1.0 (+http://www.phptesting.org/)";
$this->cookie = "phpcicookie";
$this->cookie = "phpcicookie";
$buildSettings = $phpci->getConfig('build_settings');
if (isset($buildSettings['campfire'])) {
$campfire = $buildSettings['campfire'];
$this->url = $campfire['url'];
$campfire = $buildSettings['campfire'];
$this->url = $campfire['url'];
$this->authToken = $campfire['authToken'];
$this->roomId = $campfire['roomId'];
$this->roomId = $campfire['roomId'];
} else {
throw new \Exception(Lang::get('no_campfire_settings'));
}
@ -63,7 +63,7 @@ class Campfire implements \PHPCI\Plugin
*/
public function execute()
{
$url = PHPCI_URL."build/view/".$this->build->getId();
$url = PHPCI_URL . "build/view/" . $this->build->getId();
$message = str_replace("%buildurl%", $url, $this->message);
$this->joinRoom($this->roomId);
$status = $this->speak($message, $this->roomId);
@ -101,6 +101,7 @@ class Campfire implements \PHPCI\Plugin
public function speak($message, $roomId, $isPaste = false)
{
$page = '/room/'.$roomId.'/speak.json';
if ($isPaste) {
$type = 'PasteMessage';
} else {
@ -143,10 +144,12 @@ class Campfire implements \PHPCI\Plugin
// We tend to get one space with an otherwise blank response
$output = trim($output);
if (strlen($output)) {
/* Responses are JSON. Decode it to a data structure */
return json_decode($output);
}
// Simple 200 OK response (such as for joining a room)
return true;
}

View file

@ -39,9 +39,9 @@ class CleanBuild implements \PHPCI\Plugin
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->build = $build;
$this->remove = isset($options['remove']) && is_array($options['remove']) ? $options['remove'] : array();
$this->phpci = $phpci;
$this->build = $build;
$this->remove = isset($options['remove']) && is_array($options['remove']) ? $options['remove'] : array();
}
/**
@ -57,7 +57,7 @@ class CleanBuild implements \PHPCI\Plugin
$this->phpci->executeCommand($cmd, $this->phpci->buildPath . 'composer.lock');
$success = true;
foreach ($this->remove as $file) {
$ok = $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $file);

View file

@ -52,11 +52,7 @@ class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
*/
public static function canExecute($stage, Builder $builder, Build $build)
{
if ($stage == 'test' && !is_null(self::findConfigFile($builder->buildPath))) {
return true;
}
return false;
return $stage == 'test' && !is_null(self::findConfigFile($builder->buildPath));
}
/**
@ -87,12 +83,11 @@ class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
{
$this->phpci = $phpci;
$this->build = $build;
$this->path = 'tests/';
$this->path = 'tests' . DIRECTORY_SEPARATOR . '_output' . DIRECTORY_SEPARATOR;
if (empty($options['config'])) {
$this->ymlConfigFile = self::findConfigFile($this->phpci->buildPath);
}
if (isset($options['config'])) {
} else {
$this->ymlConfigFile = $options['config'];
}
if (isset($options['args'])) {
@ -104,21 +99,16 @@ class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
}
/**
* Runs Codeception tests, optionally using specified config file(s).
* Runs Codeception tests
*/
public function execute()
{
if (empty($this->ymlConfigFile)) {
$this->phpci->logFailure('No configuration file found');
return false;
throw new \Exception("No configuration file found");
}
$success = true;
// Run any config files first. This can be either a single value or an array.
$success &= $this->runConfigFile($this->ymlConfigFile);
return $success;
return $this->runConfigFile($this->ymlConfigFile);
}
/**
@ -129,54 +119,45 @@ class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
*/
protected function runConfigFile($configPath)
{
if (is_array($configPath)) {
return $this->recurseArg($configPath, array($this, 'runConfigFile'));
} else {
$this->phpci->logExecOutput(false);
$this->phpci->logExecOutput(false);
$codecept = $this->phpci->findBinary('codecept');
$codecept = $this->phpci->findBinary('codecept');
if (!$codecept) {
$this->phpci->logFailure(Lang::get('could_not_find', 'codecept'));
if (!$codecept) {
$this->phpci->logFailure(Lang::get('could_not_find', 'codecept'));
return false;
}
$cmd = 'cd "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args;
if (IS_WIN) {
$cmd = 'cd /d "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args;
}
$configPath = $this->phpci->buildPath . $configPath;
$success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath, $configPath);
$this->phpci->log(
'Codeception XML path: '. $this->phpci->buildPath . $this->path . '_output/report.xml',
Loglevel::DEBUG
);
$xml = file_get_contents($this->phpci->buildPath . $this->path . '_output/report.xml', false);
try {
$parser = new Parser($this->phpci, $xml);
$output = $parser->parse();
} catch (\Exception $ex) {
throw $ex;
}
$meta = array(
'tests' => $parser->getTotalTests(),
'timetaken' => $parser->getTotalTimeTaken(),
'failures' => $parser->getTotalFailures()
);
$this->build->storeMeta('codeception-meta', $meta);
$this->build->storeMeta('codeception-data', $output);
$this->build->storeMeta('codeception-errors', $parser->getTotalFailures());
$this->phpci->logExecOutput(true);
return $success;
return false;
}
$cmd = 'cd "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args;
if (IS_WIN) {
$cmd = 'cd /d "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args;
}
$configPath = $this->phpci->buildPath . $configPath;
$success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath, $configPath);
$this->phpci->log(
'Codeception XML path: '. $this->phpci->buildPath . $this->path . 'report.xml',
Loglevel::DEBUG
);
$xml = file_get_contents($this->phpci->buildPath . $this->path . 'report.xml', false);
$parser = new Parser($this->phpci, $xml);
$output = $parser->parse();
$meta = array(
'tests' => $parser->getTotalTests(),
'timetaken' => $parser->getTotalTimeTaken(),
'failures' => $parser->getTotalFailures()
);
$this->build->storeMeta('codeception-meta', $meta);
$this->build->storeMeta('codeception-data', $output);
$this->build->storeMeta('codeception-errors', $parser->getTotalFailures());
$this->phpci->logExecOutput(true);
return $success;
}
}

View file

@ -27,6 +27,7 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
protected $preferDist;
protected $phpci;
protected $build;
protected $nodev;
/**
* Check if this plugin can be executed.
@ -37,7 +38,7 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
*/
public static function canExecute($stage, Builder $builder, Build $build)
{
$path = $builder->buildPath . '/composer.json';
$path = $builder->buildPath . DIRECTORY_SEPARATOR . 'composer.json';
if (file_exists($path) && $stage == 'setup') {
return true;
@ -54,15 +55,17 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$path = $phpci->buildPath;
$this->phpci = $phpci;
$this->build = $build;
$this->directory = $path;
$this->action = 'install';
$path = $phpci->buildPath;
$this->phpci = $phpci;
$this->build = $build;
$this->directory = $path;
$this->action = 'install';
$this->preferDist = false;
$this->preferSource = false;
$this->nodev = false;
if (array_key_exists('directory', $options)) {
$this->directory = $path . '/' . $options['directory'];
$this->directory = $path . DIRECTORY_SEPARATOR . $options['directory'];
}
if (array_key_exists('action', $options)) {
@ -72,6 +75,15 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
if (array_key_exists('prefer_dist', $options)) {
$this->preferDist = (bool)$options['prefer_dist'];
}
if (array_key_exists('prefer_source', $options)) {
$this->preferDist = false;
$this->preferSource = (bool)$options['prefer_source'];
}
if (array_key_exists('no_dev', $options)) {
$this->nodev = (bool)$options['no_dev'];
}
}
/**
@ -91,10 +103,17 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
if ($this->preferDist) {
$this->phpci->log('Using --prefer-dist flag');
$cmd .= '--prefer-dist';
} else {
$cmd .= ' --prefer-dist';
}
if ($this->preferSource) {
$this->phpci->log('Using --prefer-source flag');
$cmd .= '--prefer-source';
$cmd .= ' --prefer-source';
}
if ($this->nodev) {
$this->phpci->log('Using --no-dev flag');
$cmd .= ' --no-dev';
}
$cmd .= ' --working-dir="%s" %s';

View file

@ -35,12 +35,12 @@ class CopyBuild implements \PHPCI\Plugin
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$path = $phpci->buildPath;
$this->phpci = $phpci;
$this->build = $build;
$this->directory = isset($options['directory']) ? $options['directory'] : $path;
$this->wipe = isset($options['wipe']) ? (bool)$options['wipe'] : false;
$this->ignore = isset($options['respect_ignore']) ? (bool)$options['respect_ignore'] : false;
$path = $phpci->buildPath;
$this->phpci = $phpci;
$this->build = $build;
$this->directory = isset($options['directory']) ? $options['directory'] : $path;
$this->wipe = isset($options['wipe']) ? (bool)$options['wipe'] : false;
$this->ignore = isset($options['respect_ignore']) ? (bool)$options['respect_ignore'] : false;
}
/**

73
PHPCI/Plugin/Deployer.php Normal file
View file

@ -0,0 +1,73 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin;
use b8\HttpClient;
use PHPCI\Builder;
use PHPCI\Model\Build;
/**
* Integrates PHPCI with Deployer: https://github.com/rebelinblue/deployer
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Plugins
*/
class Deployer implements \PHPCI\Plugin
{
protected $webhookUrl;
protected $reason;
protected $updateOnly;
/**
* Set up the plugin, configure options, etc.
* @param Builder $phpci
* @param Build $build
* @param array $options
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->build = $build;
$this->reason = 'PHPCI Build #%BUILD% - %COMMIT_MESSAGE%';
if (isset($options['webhook_url'])) {
$this->webhookUrl = $options['webhook_url'];
}
if (isset($options['reason'])) {
$this->reason = $options['reason'];
}
$this->updateOnly = isset($options['update_only']) ? (bool) $options['update_only'] : true;
}
/**
* Copies files from the root of the build directory into the target folder
*/
public function execute()
{
if (empty($this->webhookUrl)) {
$this->phpci->logFailure('You must specify a webhook URL.');
return false;
}
$http = new HttpClient();
$response = $http->post($this->webhookUrl, array(
'reason' => $this->phpci->interpolate($this->reason),
'source' => 'PHPCI',
'url' => $this->phpci->interpolate('%BUILD_URI%'),
'branch' => $this->phpci->interpolate('%BRANCH%'),
'update_only' => $this->updateOnly
));
return $response['success'];
}
}

View file

@ -9,11 +9,13 @@
namespace PHPCI\Plugin;
use Exception;
use b8\View;
use PHPCI\Builder;
use PHPCI\Helper\Lang;
use PHPCI\Model\Build;
use PHPCI\Helper\Email as EmailHelper;
use Psr\Log\LogLevel;
/**
* Email Plugin - Provides simple email capability to PHPCI.
@ -50,9 +52,9 @@ class Email implements \PHPCI\Plugin
Build $build,
array $options = array()
) {
$this->phpci = $phpci;
$this->build = $build;
$this->options = $options;
$this->phpci = $phpci;
$this->build = $build;
$this->options = $options;
}
/**
@ -70,12 +72,25 @@ class Email implements \PHPCI\Plugin
$buildStatus = $this->build->isSuccessful() ? "Passing Build" : "Failing Build";
$projectName = $this->build->getProject()->getTitle();
$mailTemplate = $this->build->isSuccessful() ? 'Email/success' : 'Email/failed';
$view = new View($mailTemplate);
try {
$view = $this->getMailTemplate();
} catch (Exception $e) {
$this->phpci->log(
sprintf('Unknown mail template "%s", falling back to default.', $this->options['template']),
LogLevel::WARNING
);
$view = $this->getDefaultMailTemplate();
}
$view->build = $this->build;
$view->project = $this->build->getProject();
$body = $view->render();
$layout = new View('Email/layout');
$layout->build = $this->build;
$layout->project = $this->build->getProject();
$layout->content = $view->render();
$body = $layout->render();
$sendFailures = $this->sendSeparateEmails(
$addresses,
@ -97,7 +112,7 @@ class Email implements \PHPCI\Plugin
* @param string $body Email body
* @return array Array of failed addresses
*/
public function sendEmail($toAddress, $ccList, $subject, $body)
protected function sendEmail($toAddress, $ccList, $subject, $body)
{
$email = new EmailHelper();
@ -119,7 +134,7 @@ class Email implements \PHPCI\Plugin
* Send an email to a list of specified subjects.
*
* @param array $toAddresses
* List of destinatary of message.
* List of destination addresses for message.
* @param string $subject
* Mail subject
* @param string $body
@ -168,6 +183,7 @@ class Email implements \PHPCI\Plugin
/**
* Get the list of email addresses to CC.
*
* @return array
*/
protected function getCcAddresses()
@ -182,4 +198,30 @@ class Email implements \PHPCI\Plugin
return $ccAddresses;
}
/**
* Get the mail template used to sent the mail.
*
* @return View
*/
protected function getMailTemplate()
{
if (isset($this->options['template'])) {
return new View('Email/' . $this->options['template']);
}
return $this->getDefaultMailTemplate();
}
/**
* Get the default mail template.
*
* @return View
*/
protected function getDefaultMailTemplate()
{
$template = $this->build->isSuccessful() ? 'short' : 'long';
return new View('Email/' . $template);
}
}

View file

@ -52,7 +52,7 @@ class Env implements \PHPCI\Plugin
// This allows the standard syntax: "FOO: bar"
$env_var = "$key=$value";
}
if (!putenv($this->phpci->interpolate($env_var))) {
$success = false;
$this->phpci->logFailure(Lang::get('unable_to_set_env'));

View file

@ -0,0 +1,73 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
* @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin;
use PHPCI\Builder;
use PHPCI\Model\Build;
use Mremi\Flowdock\Api\Push\Push;
use Mremi\Flowdock\Api\Push\TeamInboxMessage;
/**
* Flowdock Plugin
* @author Petr Cervenka <petr@nanosolutions.io>
* @package PHPCI
* @subpackage Plugins
*/
class FlowdockNotify implements \PHPCI\Plugin
{
private $api_key;
private $email;
const MESSAGE_DEFAULT = 'Build %BUILD% has finished for commit <a href="%COMMIT_URI%">%SHORT_COMMIT%</a>
(%COMMIT_EMAIL%)> on branch <a href="%BRANCH_URI%">%BRANCH%</a>';
/**
* Set up the plugin, configure options, etc.
* @param Builder $phpci
* @param Build $build
* @param array $options
* @throws \Exception
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->build = $build;
if (!is_array($options) || !isset($options['api_key'])) {
throw new \Exception('Please define the api_key for Flowdock Notify plugin!');
}
$this->api_key = trim($options['api_key']);
$this->message = isset($options['message']) ? $options['message'] : self::MESSAGE_DEFAULT;
$this->email = isset($options['email']) ? $options['email'] : 'PHPCI';
}
/**
* Run the Flowdock plugin.
* @return bool
* @throws \Exception
*/
public function execute()
{
$message = $this->phpci->interpolate($this->message);
$successfulBuild = $this->build->isSuccessful() ? 'Success' : 'Failed';
$push = new Push($this->api_key);
$flowMessage = TeamInboxMessage::create()
->setSource("PHPCI")
->setFromAddress($this->email)
->setFromName($this->build->getProject()->getTitle())
->setSubject($successfulBuild)
->setTags(['#ci'])
->setLink($this->build->getBranchLink())
->setContent($message);
if (!$push->sendTeamInboxMessage($flowMessage, array('connect_timeout' => 5000, 'timeout' => 5000))) {
throw new \Exception(sprintf('Flowdock Failed: %s', $flowMessage->getResponseErrors()));
}
return true;
}
}

View file

@ -52,7 +52,7 @@ class Grunt implements \PHPCI\Plugin
// Handle options:
if (isset($options['directory'])) {
$this->directory = $path . '/' . $options['directory'];
$this->directory = $path . DIRECTORY_SEPARATOR . $options['directory'];
}
if (isset($options['task'])) {

View file

@ -52,7 +52,7 @@ class Gulp implements \PHPCI\Plugin
// Handle options:
if (isset($options['directory'])) {
$this->directory = $path . '/' . $options['directory'];
$this->directory = $path . DIRECTORY_SEPARATOR . $options['directory'];
}
if (isset($options['task'])) {

View file

@ -41,9 +41,9 @@ class Lint implements PHPCI\Plugin
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->phpci = $phpci;
$this->build = $build;
$this->directories = array('');
$this->directories = array('');
$this->ignore = $phpci->ignore;
if (!empty($options['directory'])) {
@ -93,7 +93,7 @@ class Lint implements PHPCI\Plugin
if ($item->isFile() && $item->getExtension() == 'php' && !$this->lintFile($php, $itemPath)) {
$success = false;
} elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($php, $itemPath . '/')) {
} elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($php, $itemPath . DIRECTORY_SEPARATOR)) {
$success = false;
}

View file

@ -166,10 +166,11 @@ class Mysql implements \PHPCI\Plugin
$args = array(
':import_file' => escapeshellarg($import_file),
':decomp_cmd' => $decomp_cmd,
':host' => escapeshellarg($this->host),
':user' => escapeshellarg($this->user),
':pass' => escapeshellarg($this->pass),
':database' => ($database === null)? '': escapeshellarg($database),
);
return strtr('cat :import_file :decomp_cmd | mysql -u:user -p:pass :database', $args);
return strtr('cat :import_file :decomp_cmd | mysql -h:host -u:user -p:pass :database', $args);
}
}

View file

@ -33,12 +33,12 @@ class PackageBuild implements \PHPCI\Plugin
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$path = $phpci->buildPath;
$this->build = $build;
$this->phpci = $phpci;
$this->directory = isset($options['directory']) ? $options['directory'] : $path;
$this->filename = isset($options['filename']) ? $options['filename'] : 'build';
$this->format = isset($options['format']) ? $options['format'] : 'zip';
$path = $phpci->buildPath;
$this->build = $build;
$this->phpci = $phpci;
$this->directory = isset($options['directory']) ? $options['directory'] : $path;
$this->filename = isset($options['filename']) ? $options['filename'] : 'build';
$this->format = isset($options['format']) ? $options['format'] : 'zip';
}
/**
@ -46,7 +46,7 @@ class PackageBuild implements \PHPCI\Plugin
*/
public function execute()
{
$path = $this->phpci->buildPath;
$path = $this->phpci->buildPath;
$build = $this->build;
if ($this->directory == $path) {
@ -69,8 +69,7 @@ class PackageBuild implements \PHPCI\Plugin
}
foreach ($this->format as $format) {
switch($format)
{
switch ($format) {
case 'tar':
$cmd = 'tar cfz "%s/%s.tar.gz" ./*';
break;

View file

@ -73,8 +73,11 @@ class Pdepend implements \PHPCI\Plugin
*/
public function execute()
{
if (!file_exists($this->location)) {
mkdir($this->location);
}
if (!is_writable($this->location)) {
throw new \Exception(sprintf('The location %s is not writable.', $this->location));
throw new \Exception(sprintf('The location %s is not writable or does not exist.', $this->location));
}
$pdepend = $this->phpci->findBinary('pdepend');

View file

@ -73,9 +73,9 @@ class Pgsql implements \PHPCI\Plugin
}
/**
* Connects to PgSQL and runs a specified set of queries.
* @return boolean
*/
* Connects to PgSQL and runs a specified set of queries.
* @return boolean
*/
public function execute()
{
try {

View file

@ -213,7 +213,7 @@ class Phar implements \PHPCI\Plugin
$content = '';
$filename = $this->getStub();
if ($filename) {
$content = file_get_contents($this->getPHPCI()->buildPath . '/' . $this->getStub());
$content = file_get_contents($this->getPHPCI()->buildPath . DIRECTORY_SEPARATOR . $this->getStub());
}
return $content;
}
@ -227,7 +227,8 @@ class Phar implements \PHPCI\Plugin
$success = false;
try {
$phar = new PHPPhar($this->getDirectory() . '/' . $this->getFilename(), 0, $this->getFilename());
$file = $this->getDirectory() . DIRECTORY_SEPARATOR . $this->getFilename();
$phar = new PHPPhar($file, 0, $this->getFilename());
$phar->buildFromDirectory($this->getPHPCI()->buildPath, $this->getRegExp());
$stub = $this->getStubContent();
@ -236,7 +237,6 @@ class Phar implements \PHPCI\Plugin
}
$success = true;
} catch (Exception $e) {
$this->getPHPCI()->log(Lang::get('phar_internal_error'));
$this->getPHPCI()->log($e->getMessage());

View file

@ -47,7 +47,7 @@ class Phing implements \PHPCI\Plugin
* Set working directory
*/
if (isset($options['directory'])) {
$directory = $phpci->buildPath . '/' . $options['directory'];
$directory = $phpci->buildPath . DIRECTORY_SEPARATOR . $options['directory'];
} else {
$directory = $phpci->buildPath;
}
@ -255,7 +255,7 @@ class Phing implements \PHPCI\Plugin
*/
public function setPropertyFile($propertyFile)
{
if (!file_exists($this->getDirectory() . '/' . $propertyFile)) {
if (!file_exists($this->getDirectory() . DIRECTORY_SEPARATOR . $propertyFile)) {
throw new \Exception(Lang::get('property_file_missing'));
}

View file

@ -12,6 +12,7 @@ namespace PHPCI\Plugin;
use PHPCI;
use PHPCI\Builder;
use PHPCI\Model\Build;
use PHPCI\Model\BuildError;
/**
* PHP Code Sniffer Plugin - Allows PHP Code Sniffer testing.
@ -163,14 +164,13 @@ class PhpCodeSniffer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
);
$output = $this->phpci->getLastOutput();
list($errors, $warnings, $data) = $this->processReport($output);
list($errors, $warnings) = $this->processReport($output);
$this->phpci->logExecOutput(true);
$success = true;
$this->build->storeMeta('phpcs-warnings', $warnings);
$this->build->storeMeta('phpcs-errors', $errors);
$this->build->storeMeta('phpcs-data', $data);
if ($this->allowed_warnings != -1 && $warnings > $this->allowed_warnings) {
$success = false;
@ -226,23 +226,21 @@ class PhpCodeSniffer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$errors = $data['totals']['errors'];
$warnings = $data['totals']['warnings'];
$rtn = array();
foreach ($data['files'] as $fileName => $file) {
$fileName = str_replace($this->phpci->buildPath, '', $fileName);
foreach ($file['messages'] as $message) {
$this->build->reportError($this->phpci, $fileName, $message['line'], 'PHPCS: ' . $message['message']);
$rtn[] = array(
'file' => $fileName,
'line' => $message['line'],
'type' => $message['type'],
'message' => $message['message'],
$this->build->reportError(
$this->phpci,
'php_code_sniffer',
'PHPCS: ' . $message['message'],
$message['type'] == 'ERROR' ? BuildError::SEVERITY_HIGH : BuildError::SEVERITY_LOW,
$fileName,
$message['line']
);
}
}
return array($errors, $warnings, $rtn);
return array($errors, $warnings);
}
}

37
PHPCI/Plugin/PhpCpd.php Normal file → Executable file
View file

@ -12,6 +12,7 @@ namespace PHPCI\Plugin;
use PHPCI\Builder;
use PHPCI\Helper\Lang;
use PHPCI\Model\Build;
use PHPCI\Model\BuildError;
/**
* PHP Copy / Paste Detector - Allows PHP Copy / Paste Detector testing.
@ -28,7 +29,7 @@ class PhpCpd implements \PHPCI\Plugin
/**
* @var string, based on the assumption the root may not hold the code to be
* tested, exteds the base path
* tested, extends the base path
*/
protected $path;
@ -49,17 +50,12 @@ class PhpCpd implements \PHPCI\Plugin
$this->build = $build;
$this->path = $phpci->buildPath;
$this->standard = 'PSR1';
$this->ignore = $phpci->ignore;
if (!empty($options['path'])) {
$this->path = $phpci->buildPath . $options['path'];
}
if (!empty($options['standard'])) {
$this->standard = $options['standard'];
}
if (!empty($options['ignore'])) {
$this->ignore = $options['ignore'];
}
@ -74,9 +70,9 @@ class PhpCpd implements \PHPCI\Plugin
if (count($this->ignore)) {
$map = function ($item) {
// remove the trailing slash
$item = (substr($item, -1) == '/' ? substr($item, 0, -1) : $item);
$item = rtrim($item, DIRECTORY_SEPARATOR);
if (is_file($this->path . '/' . $item)) {
if (is_file(rtrim($this->path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $item)) {
return ' --names-exclude ' . $item;
} else {
return ' --exclude ' . $item;
@ -97,9 +93,8 @@ class PhpCpd implements \PHPCI\Plugin
print $this->phpci->getLastOutput();
list($errorCount, $data) = $this->processReport(file_get_contents($tmpfilename));
$errorCount = $this->processReport(file_get_contents($tmpfilename));
$this->build->storeMeta('phpcpd-warnings', $errorCount);
$this->build->storeMeta('phpcpd-data', $data);
unlink($tmpfilename);
@ -122,20 +117,11 @@ class PhpCpd implements \PHPCI\Plugin
}
$warnings = 0;
$data = array();
foreach ($xml->duplication as $duplication) {
foreach ($duplication->file as $file) {
$fileName = (string)$file['path'];
$fileName = str_replace($this->phpci->buildPath, '', $fileName);
$data[] = array(
'file' => $fileName,
'line_start' => (int) $file['line'],
'line_end' => (int) $file['line'] + (int) $duplication['lines'],
'code' => (string) $duplication->codefragment
);
$message = <<<CPD
Copy and paste detected:
@ -144,13 +130,20 @@ Copy and paste detected:
```
CPD;
$this->build->reportError($this->phpci, $fileName, $file['line'], $message);
$this->build->reportError(
$this->phpci,
'php_cpd',
$message,
BuildError::SEVERITY_NORMAL,
$fileName,
$file['line'],
(int) $file['line'] + (int) $duplication['lines']
);
}
$warnings++;
}
return array($warnings, $data);
return $warnings;
}
}

View file

@ -14,7 +14,7 @@ use PHPCI\Helper\Lang;
use PHPCI\Model\Build;
/**
* PHP CS Fixer - Works with the PHP CS Fixer for testing coding standards.
* PHP CS Fixer - Works with the PHP Coding Standards Fixer for testing coding standards.
* @author Gabriel Baker <gabriel@autonomicpilot.co.uk>
* @package PHPCI
* @subpackage Plugins
@ -32,10 +32,10 @@ class PhpCsFixer implements \PHPCI\Plugin
protected $build;
protected $workingDir = '';
protected $level = ' --level=all';
protected $verbose = '';
protected $diff = '';
protected $levels = array('psr0', 'psr1', 'psr2', 'all');
protected $level = ' --level=psr2';
protected $verbose = '';
protected $diff = '';
protected $levels = array('psr0', 'psr1', 'psr2', 'symfony');
/**
* Standard Constructor

View file

@ -143,7 +143,6 @@ class PhpDocblockChecker implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$success = true;
$this->build->storeMeta('phpdoccheck-warnings', $errors);
$this->build->storeMeta('phpdoccheck-data', $output);
$this->reportErrors($output);
if ($this->allowed_warnings != -1 && $errors > $this->allowed_warnings) {
@ -160,13 +159,22 @@ class PhpDocblockChecker implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
protected function reportErrors($output)
{
foreach ($output as $error) {
$message = 'Class ' . $error['class'] . ' does not have a Docblock comment.';
$message = 'Class ' . $error['class'] . ' is missing a docblock.';
$severity = PHPCI\Model\BuildError::SEVERITY_LOW;
if ($error['type'] == 'method') {
$message = 'Method ' . $error['class'] . '::' . $error['method'] . ' does not have a Docblock comment.';
$message = $error['class'] . '::' . $error['method'] . ' is missing a docblock.';
$severity = PHPCI\Model\BuildError::SEVERITY_NORMAL;
}
$this->build->reportError($this->phpci, $error['file'], $error['line'], $message);
$this->build->reportError(
$this->phpci,
'php_docblock_checker',
$message,
$severity,
$error['file'],
$error['line']
);
}
}
}

View file

@ -69,19 +69,20 @@ class PhpLoc implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
public function execute()
{
$ignore = '';
if (count($this->phpci->ignore)) {
$map = function ($item) {
return ' --exclude ' . (substr($item, -1) == '/' ? substr($item, 0, -1) : $item);
};
$ignore = array_map($map, $this->phpci->ignore);
if (count($this->phpci->ignore)) {
$map = function ($item) {
return ' --exclude ' . rtrim($item, DIRECTORY_SEPARATOR);
};
$ignore = array_map($map, $this->phpci->ignore);
$ignore = implode('', $ignore);
}
$phploc = $this->phpci->findBinary('phploc');
$success = $this->phpci->executeCommand($phploc . ' %s "%s"', $ignore, $this->directory);
$output = $this->phpci->getLastOutput();
$output = $this->phpci->getLastOutput();
if (preg_match_all('/\((LOC|CLOC|NCLOC|LLOC)\)\s+([0-9]+)/', $output, $matches)) {
$data = array();

View file

@ -38,7 +38,7 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
/**
* @var string, based on the assumption the root may not hold the code to be
* tested, exteds the base path only if the provided path is relative. Absolute
* tested, extends the base path only if the provided path is relative. Absolute
* paths are used verbatim
*/
protected $path;
@ -50,7 +50,7 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
/**
* Array of PHPMD rules. Can be one of the builtins (codesize, unusedcode, naming, design, controversial)
* or a filenname (detected by checking for a / in it), either absolute or relative to the project root.
* or a filename (detected by checking for a / in it), either absolute or relative to the project root.
* @var array
*/
protected $rules;
@ -123,9 +123,8 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$this->executePhpMd($phpmdBinaryPath);
list($errorCount, $data) = $this->processReport(trim($this->phpci->getLastOutput()));
$errorCount = $this->processReport(trim($this->phpci->getLastOutput()));
$this->build->storeMeta('phpmd-warnings', $errorCount);
$this->build->storeMeta('phpmd-data', $data);
return $this->wasLastExecSuccessful($errorCount);
}
@ -158,7 +157,6 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
}
$warnings = 0;
$data = array();
foreach ($xml->file as $file) {
$fileName = (string)$file['name'];
@ -166,22 +164,20 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
foreach ($file->violation as $violation) {
$warnings++;
$warning = array(
'file' => $fileName,
'line_start' => (int)$violation['beginline'],
'line_end' => (int)$violation['endline'],
'rule' => (string)$violation['rule'],
'ruleset' => (string)$violation['ruleset'],
'priority' => (int)$violation['priority'],
'message' => (string)$violation,
);
$this->build->reportError($this->phpci, $fileName, (int)$violation['beginline'], (string)$violation);
$data[] = $warning;
$this->build->reportError(
$this->phpci,
'php_mess_detector',
(string)$violation,
PHPCI\Model\BuildError::SEVERITY_HIGH,
$fileName,
(int)$violation['beginline'],
(int)$violation['endline']
);
}
}
return array($warnings, $data);
return $warnings;
}
/**

View file

@ -55,10 +55,10 @@ class PhpParallelLint implements \PHPCI\Plugin
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->build = $build;
$this->directory = $phpci->buildPath;
$this->ignore = $this->phpci->ignore;
$this->phpci = $phpci;
$this->build = $build;
$this->directory = $phpci->buildPath;
$this->ignore = $this->phpci->ignore;
if (isset($options['directory'])) {
$this->directory = $phpci->buildPath.$options['directory'];

View file

@ -155,7 +155,7 @@ class PhpTalLint implements PHPCI\Plugin
if (!$this->lintFile($itemPath)) {
$success = false;
}
} elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($itemPath . '/')) {
} elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($itemPath . DIRECTORY_SEPARATOR)) {
$success = false;
}
@ -202,7 +202,9 @@ class PhpTalLint implements PHPCI\Plugin
list($suffixes, $tales) = $this->getFlags();
$lint = dirname(__FILE__) . '/../../vendor/phptal/phptal/tools/phptal_lint.php';
$lint = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
$lint .= 'vendor' . DIRECTORY_SEPARATOR . 'phptal' . DIRECTORY_SEPARATOR . 'phptal' . DIRECTORY_SEPARATOR;
$lint .= 'tools' . DIRECTORY_SEPARATOR . 'phptal_lint.php';
$cmd = '/usr/bin/env php ' . $lint . ' %s %s "%s"';
$this->phpci->executeCommand($cmd, $suffixes, $tales, $this->phpci->buildPath . $path);
@ -222,7 +224,7 @@ class PhpTalLint implements PHPCI\Plugin
$row = str_replace('(use -i to include your custom modifier functions)', '', $row);
$message = str_replace($name . ': ', '', $row);
$parts = explode(' (line ', $message);
$message = trim($parts[0]);

View file

@ -76,8 +76,8 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
return 'phpunit.xml';
}
if (file_exists($buildPath . 'tests/phpunit.xml')) {
return 'tests/phpunit.xml';
if (file_exists($buildPath . 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml')) {
return 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml';
}
if (file_exists($buildPath . 'phpunit.xml.dist')) {
@ -85,7 +85,7 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
}
if (file_exists($buildPath . 'tests/phpunit.xml.dist')) {
return 'tests/phpunit.xml.dist';
return 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml.dist';
}
return null;
@ -133,7 +133,7 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
}
if (isset($options['coverage'])) {
$this->coverage = " --coverage-html {$options['coverage']} ";
$this->coverage = ' --coverage-html ' . $this->phpci->interpolate($options['coverage']) . ' ';
}
}
@ -194,7 +194,7 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
} else {
if ($this->runFrom) {
$curdir = getcwd();
chdir($this->phpci->buildPath.'/'.$this->runFrom);
chdir($this->phpci->buildPath . DIRECTORY_SEPARATOR . $this->runFrom);
}
$phpunit = $this->phpci->findBinary('phpunit');

View file

@ -24,6 +24,7 @@ class SlackNotify implements \PHPCI\Plugin
private $username;
private $message;
private $icon;
private $show_status;
/**
* Set up the plugin, configure options, etc.
@ -60,6 +61,12 @@ class SlackNotify implements \PHPCI\Plugin
$this->username = 'PHPCI';
}
if (isset($options['show_status'])) {
$this->show_status = (bool) $options['show_status'];
} else {
$this->show_status = true;
}
if (isset($options['icon'])) {
$this->icon = $options['icon'];
}
@ -74,31 +81,7 @@ class SlackNotify implements \PHPCI\Plugin
*/
public function execute()
{
$message = $this->phpci->interpolate($this->message);
$successfulBuild = $this->build->isSuccessful();
if ($successfulBuild) {
$status = 'Success';
$color = 'good';
} else {
$status = 'Failed';
$color = 'danger';
}
// Build up the attachment data
$attachment = new \Maknz\Slack\Attachment(array(
'fallback' => $message,
'pretext' => $message,
'color' => $color,
'fields' => array(
new \Maknz\Slack\AttachmentField(array(
'title' => 'Status',
'value' => $status,
'short' => false
))
)
));
$body = $this->phpci->interpolate($this->message);
$client = new \Maknz\Slack\Client($this->webHook);
@ -116,12 +99,39 @@ class SlackNotify implements \PHPCI\Plugin
$message->setIcon($this->icon);
}
$message->attach($attachment);
// Include an attachment which shows the status and hide the message
if ($this->show_status) {
$successfulBuild = $this->build->isSuccessful();
$success = true;
if ($successfulBuild) {
$status = 'Success';
$color = 'good';
} else {
$status = 'Failed';
$color = 'danger';
}
$message->send('');
// Build up the attachment data
$attachment = new \Maknz\Slack\Attachment(array(
'fallback' => $body,
'pretext' => $body,
'color' => $color,
'fields' => array(
new \Maknz\Slack\AttachmentField(array(
'title' => 'Status',
'value' => $status,
'short' => false
))
)
));
return $success;
$message->attach($attachment);
$body = '';
}
$message->send($body);
return true;
}
}

View file

@ -42,11 +42,6 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
*/
protected $allowed_errors;
/**
* @var int
*/
protected $allowed_warnings;
/**
* @var string, based on the assumption the root may not hold the code to be
* tested, extends the base path
@ -94,7 +89,6 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$this->directory = $phpci->buildPath;
$this->path = '';
$this->ignore = $this->phpci->ignore;
$this->allowed_warnings = 0;
$this->allowed_errors = 0;
$this->searches = array('TODO', 'FIXME', 'TO DO', 'FIX ME');
@ -103,9 +97,10 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
}
if (isset($options['zero_config']) && $options['zero_config']) {
$this->allowed_warnings = -1;
$this->allowed_errors = -1;
}
$this->setOptions($options);
}
/**
@ -114,7 +109,7 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
*/
protected function setOptions($options)
{
foreach (array('directory', 'path', 'ignore', 'allowed_warnings', 'allowed_errors') as $key) {
foreach (array('directory', 'path', 'ignore', 'allowed_errors') as $key) {
if (array_key_exists($key, $options)) {
$this->{$key} = $options[$key];
}
@ -129,12 +124,11 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$success = true;
$this->phpci->logExecOutput(false);
list($errorCount, $data) = $this->getErrorList();
$errorCount = $this->getErrorList();
$this->phpci->log("Found $errorCount instances of " . implode(', ', $this->searches));
$this->build->storeMeta('technical_debt-warnings', $errorCount);
$this->build->storeMeta('technical_debt-data', $data);
if ($this->allowed_errors != -1 && $errorCount > $this->allowed_errors) {
$success = false;
@ -156,6 +150,7 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$ignores = $this->ignore;
$ignores[] = 'phpci.yml';
$ignores[] = '.phpci.yml';
foreach ($iterator as $file) {
$filePath = $file->getRealPath();
@ -168,7 +163,7 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
}
// Ignore hidden files, else .git, .sass_cache, etc. all get looped over
if (stripos($filePath, '/.') !== false) {
if (stripos($filePath, DIRECTORY_SEPARATOR . '.') !== false) {
$skipFile = true;
}
@ -179,7 +174,6 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$files = array_filter(array_unique($files));
$errorCount = 0;
$data = array();
foreach ($files as $file) {
foreach ($this->searches as $search) {
@ -193,21 +187,21 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$content = trim($allLines[$lineNumber - 1]);
$errorCount++;
$this->phpci->log("Found $search on line $lineNumber of $file:\n$content");
$fileName = str_replace($this->directory, '', $file);
$data[] = array(
'file' => $fileName,
'line' => $lineNumber,
'message' => $content
$this->build->reportError(
$this->phpci,
'technical_debt',
$content,
PHPCI\Model\BuildError::SEVERITY_LOW,
$fileName,
$lineNumber
);
$this->build->reportError($this->phpci, $fileName, $lineNumber, $content);
}
}
}
return array( $errorCount, $data );
return $errorCount;
}
}

View file

@ -2,8 +2,12 @@
namespace PHPCI\Plugin\Util;
use b8\Store\Factory as StoreFactory;
use Exception;
use PHPCI\Helper\Lang;
use \PHPCI\Logging\BuildLogger;
use PHPCI\Logging\BuildLogger;
use PHPCI\Model\Build;
use PHPCI\Store\BuildStore;
/**
* Plugin Executor - Runs the configured plugins for a given build stage.
@ -21,14 +25,20 @@ class Executor
*/
protected $pluginFactory;
/**
* @var BuildStore
*/
protected $store;
/**
* @param Factory $pluginFactory
* @param BuildLogger $logger
*/
public function __construct(Factory $pluginFactory, BuildLogger $logger)
public function __construct(Factory $pluginFactory, BuildLogger $logger, BuildStore $store = null)
{
$this->pluginFactory = $pluginFactory;
$this->logger = $logger;
$this->store = $store ?: StoreFactory::getStore('Build');
}
/**
@ -40,30 +50,114 @@ class Executor
public function executePlugins(&$config, $stage)
{
$success = true;
// Ignore any stages for which we don't have plugins set:
if (!array_key_exists($stage, $config) || !is_array($config[$stage])) {
return $success;
$pluginsToExecute = array();
// If we have global plugins to execute for this stage, add them to the list to be executed:
if (array_key_exists($stage, $config) && is_array($config[$stage])) {
$pluginsToExecute[] = $config[$stage];
}
foreach ($config[$stage] as $plugin => $options) {
$pluginsToExecute = $this->getBranchSpecificPlugins($config, $stage, $pluginsToExecute);
foreach ($pluginsToExecute as $pluginSet) {
if (!$this->doExecutePlugins($pluginSet, $stage)) {
$success = false;
}
}
return $success;
}
/**
* Check the config for any plugins specific to the branch we're currently building.
* @param $config
* @param $stage
* @param $pluginsToExecute
* @return array
*/
protected function getBranchSpecificPlugins(&$config, $stage, $pluginsToExecute)
{
/** @var \PHPCI\Model\Build $build */
$build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build');
$branch = $build->getBranch();
// If we don't have any branch-specific plugins:
if (!isset($config['branch-' . $branch][$stage]) || !is_array($config['branch-' . $branch][$stage])) {
return $pluginsToExecute;
}
// If we have branch-specific plugins to execute, add them to the list to be executed:
$branchConfig = $config['branch-' . $branch];
$plugins = $branchConfig[$stage];
$runOption = 'after';
if (!empty($branchConfig['run-option'])) {
$runOption = $branchConfig['run-option'];
}
switch ($runOption) {
// Replace standard plugin set for this stage with just the branch-specific ones:
case 'replace':
$pluginsToExecute = array();
$pluginsToExecute[] = $plugins;
break;
// Run branch-specific plugins before standard plugins:
case 'before':
array_unshift($pluginsToExecute, $plugins);
break;
// Run branch-specific plugins after standard plugins:
case 'after':
array_push($pluginsToExecute, $plugins);
break;
default:
array_push($pluginsToExecute, $plugins);
break;
}
return $pluginsToExecute;
}
/**
* Execute the list of plugins found for a given testing stage.
* @param $plugins
* @param $stage
* @return bool
* @throws \Exception
*/
protected function doExecutePlugins(&$plugins, $stage)
{
$success = true;
foreach ($plugins as $plugin => $options) {
$this->logger->log(Lang::get('running_plugin', $plugin));
// Try and execute it:
if ($this->executePlugin($plugin, $options)) {
// Execution was successful:
$this->logger->logSuccess(Lang::get('plugin_success'));
} elseif ($stage == 'setup') {
// If we're in the "setup" stage, execution should not continue after
// a plugin has failed:
throw new \Exception('Plugin failed: ' . $plugin);
} else {
// If we're in the "test" stage and the plugin is not allowed to fail,
// then mark the build as failed:
if ($stage == 'test' && (!isset($options['allow_failures']) || !$options['allow_failures'])) {
$success = false;
}
$this->setPluginStatus($stage, $plugin, Build::STATUS_RUNNING);
// Try and execute it
if ($this->executePlugin($plugin, $options)) {
// Execution was successful
$this->logger->logSuccess(Lang::get('plugin_success'));
$this->setPluginStatus($stage, $plugin, Build::STATUS_SUCCESS);
} else {
// Execution failed
$this->logger->logFailure(Lang::get('plugin_failed'));
$this->setPluginStatus($stage, $plugin, Build::STATUS_FAILED);
if ($stage === 'setup') {
// If we're in the "setup" stage, execution should not continue after
// a plugin has failed:
throw new Exception('Plugin failed: ' . $plugin);
} elseif ($stage === 'test') {
// If we're in the "test" stage and the plugin is not allowed to fail,
// then mark the build as failed:
if (empty($options['allow_failures'])) {
$success = false;
}
}
}
}
@ -91,20 +185,62 @@ class Executor
return false;
}
$rtn = true;
// Try running it:
try {
// Build and run it
$obj = $this->pluginFactory->buildPlugin($class, $options);
if (!$obj->execute()) {
$rtn = false;
}
return $obj->execute();
} catch (\Exception $ex) {
$this->logger->logFailure(Lang::get('exception') . $ex->getMessage(), $ex);
$rtn = false;
return false;
}
}
/**
* Change the status of a plugin for a given stage.
*
* @param string $stage The builder stage.
* @param string $plugin The plugin name.
* @param int $status The new status.
*/
protected function setPluginStatus($stage, $plugin, $status)
{
$summary = $this->getBuildSummary();
if (!isset($summary[$stage][$plugin])) {
$summary[$stage][$plugin] = array();
}
return $rtn;
$summary[$stage][$plugin]['status'] = $status;
if ($status === Build::STATUS_RUNNING) {
$summary[$stage][$plugin]['started'] = time();
} elseif ($status >= Build::STATUS_SUCCESS) {
$summary[$stage][$plugin]['ended'] = time();
}
$this->setBuildSummary($summary);
}
/**
* Fetch the summary data of the current build.
*
* @return array
*/
private function getBuildSummary()
{
$build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build');
$metas = $this->store->getMeta('plugin-summary', $build->getProjectId(), $build->getId());
return isset($metas[0]['meta_value']) ? $metas[0]['meta_value'] : array();
}
/**
* Sets the summary data of the current build.
*
* @param array summary
*/
private function setBuildSummary($summary)
{
$build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build');
$this->store->setMeta($build->getProjectId(), $build->getId(), 'plugin-summary', json_encode($summary));
}
}

View file

@ -8,8 +8,8 @@ namespace PHPCI\Plugin\Util;
*/
class Factory
{
const TYPE_ARRAY = "array";
const TYPE_CALLABLE = "callable";
const TYPE_ARRAY = "array";
const TYPE_CALLABLE = "callable";
const INTERFACE_PHPCI_PLUGIN = '\PHPCI\Plugin';
private $currentPluginOptions;
@ -150,11 +150,11 @@ class Factory
}
/**
* @param null $type
* @param null $name
* @return null
* @param string $type
* @param string $name
* @return mixed
*/
private function getResourceFor($type = null, $name = null)
public function getResourceFor($type = null, $name = null)
{
$fullId = $this->getInternalID($type, $name);
if (isset($this->container[$fullId])) {

View file

@ -56,6 +56,7 @@ class FilesPluginInformation implements InstalledPluginInformation
if ($this->pluginInfo === null) {
$this->loadPluginInfo();
}
return $this->pluginInfo;
}
@ -83,7 +84,7 @@ class FilesPluginInformation implements InstalledPluginInformation
$this->pluginInfo = array();
foreach ($this->files as $fileInfo) {
if ($fileInfo instanceof \SplFileInfo) {
if ($fileInfo->isFile() && $fileInfo->getExtension()=='php') {
if ($fileInfo->isFile() && $fileInfo->getExtension() == 'php') {
$this->addPluginFromFile($fileInfo);
}
}
@ -99,11 +100,11 @@ class FilesPluginInformation implements InstalledPluginInformation
$class = $this->getFullClassFromFile($fileInfo);
if (!is_null($class)) {
$newPlugin = new \stdClass();
$newPlugin->class = $class;
$newPlugin = new \stdClass();
$newPlugin->class = $class;
$newPlugin->source = "core";
$parts = explode('\\', $newPlugin->class);
$newPlugin->name = end($parts);
$parts = explode('\\', $newPlugin->class);
$newPlugin->name = end($parts);
$this->pluginInfo[] = $newPlugin;
}
@ -123,11 +124,11 @@ class FilesPluginInformation implements InstalledPluginInformation
if (isset($matches[1])) {
$className = $matches[1];
$matches = array();
preg_match('#namespace +([A-Za-z\\\\]+);#i', $contents, $matches);
$namespace = $matches[1];
return $namespace . '\\' . $className;
} else {
return null;

View file

@ -32,9 +32,11 @@ class PluginInformationCollection implements InstalledPluginInformation
public function getInstalledPlugins()
{
$arr = array();
foreach ($this->pluginInformations as $single) {
$arr = array_merge($arr, $single->getInstalledPlugins());
}
return $arr;
}
@ -47,9 +49,11 @@ class PluginInformationCollection implements InstalledPluginInformation
public function getPluginClasses()
{
$arr = array();
foreach ($this->pluginInformations as $single) {
$arr = array_merge($arr, $single->getPluginClasses());
}
return $arr;
}
}

View file

@ -13,9 +13,10 @@ use Symfony\Component\Yaml\Yaml;
class TapParser
{
const TEST_COUNTS_PATTERN = '/^\d+\.\.(\d+)/';
const TEST_LINE_PATTERN = '/^(ok|not ok)(?:\s+\d+)?(?:\s+\-)?\s*(.*?)(?:\s*#\s*(skip|todo)\s*(.*))?\s*$/i';
const TEST_YAML_START = '/^(\s*)---/';
const TEST_DIAGNOSTIC = '/^#/';
const TEST_LINE_PATTERN = '/^(ok|not ok)(?:\s+\d+)?(?:\s+\-)?\s*(.*?)(?:\s*#\s*(skip|todo)\s*(.*))?\s*$/i';
const TEST_YAML_START = '/^(\s*)---/';
const TEST_DIAGNOSTIC = '/^#/';
const TEST_COVERAGE = '/^Generating/';
/**
* @var string
@ -73,7 +74,7 @@ class TapParser
$line = $this->nextLine();
if ($line === $header) {
throw new Exception("Duplicated TAP log, please check the configration.");
throw new Exception("Duplicated TAP log, please check the configuration.");
}
while ($line !== false && ($this->testCount === false || count($this->results) < $this->testCount)) {
@ -81,7 +82,7 @@ class TapParser
$line = $this->nextLine();
}
if (count($this->results) !== $this->testCount) {
if (false !== $this->testCount && count($this->results) !== $this->testCount) {
throw new Exception(Lang::get('tap_error'));
}
@ -96,7 +97,7 @@ class TapParser
*/
protected function findTapLog()
{
// Look for the beggning of the TAP output
// Look for the beginning of the TAP output
do {
$header = $this->nextLine();
} while ($header !== false && substr($header, 0, 12) !== 'TAP version ');
@ -123,19 +124,14 @@ class TapParser
return false;
}
/** Parse a single line.
*
/**
* @param string $line
*
* @return boolean
*/
protected function parseLine($line)
protected function testLine($line)
{
if (preg_match(self::TEST_COUNTS_PATTERN, $line, $matches)) {
$this->testCount = intval($matches[1]);
} elseif (preg_match(self::TEST_DIAGNOSTIC, $line)) {
return;
} elseif (preg_match(self::TEST_LINE_PATTERN, $line, $matches)) {
if (preg_match(self::TEST_LINE_PATTERN, $line, $matches)) {
$this->results[] = $this->processTestLine(
$matches[1],
isset($matches[2]) ? $matches[2] : '',
@ -143,18 +139,61 @@ class TapParser
isset($matches[4]) ? $matches[4] : null
);
} elseif (preg_match(self::TEST_YAML_START, $line, $matches)) {
return true;
}
return false;
}
/**
* @param string $line
*
* @return boolean
*/
protected function yamlLine($line)
{
if (preg_match(self::TEST_YAML_START, $line, $matches)) {
$diagnostic = $this->processYamlBlock($matches[1]);
$test = array_pop($this->results);
$test = array_pop($this->results);
if (isset($test['message'], $diagnostic['message'])) {
$test['message'] .= PHP_EOL . $diagnostic['message'];
unset($diagnostic['message']);
}
$this->results[] = array_replace($test, $diagnostic);
} else {
throw new Exception(sprintf('Incorrect TAP data, line %d: %s', $this->lineNumber, $line));
return true;
}
return false;
}
/** Parse a single line.
*
* @param string $line
*
* @throws Exception
*/
protected function parseLine($line)
{
if (preg_match(self::TEST_DIAGNOSTIC, $line) || preg_match(self::TEST_COVERAGE, $line) || !$line) {
return;
}
if (preg_match(self::TEST_COUNTS_PATTERN, $line, $matches)) {
$this->testCount = intval($matches[1]);
return;
}
if ($this->testLine($line)) {
return;
}
if ($this->yamlLine($line)) {
return;
}
throw new Exception(sprintf('Incorrect TAP data, line %d: %s', $this->lineNumber, $line));
}
/**
@ -196,18 +235,20 @@ class TapParser
*/
protected function processYamlBlock($indent)
{
$startLine = $this->lineNumber+1;
$endLine = $indent.'...';
$startLine = $this->lineNumber + 1;
$endLine = $indent . '...';
$yamlLines = array();
do {
$line = $this->nextLine();
if ($line === false) {
throw new Exception(Lang::get('tap_error_endless_yaml', $startLine));
} elseif ($line === $endLine) {
break;
}
$yamlLines[] = substr($line, strlen($indent));
$yamlLines[] = substr($line, strlen($indent));
} while (true);
return Yaml::parse(join("\n", $yamlLines));

View file

@ -20,6 +20,7 @@ class Codeception implements ParserInterface
protected $totalTests;
protected $totalTimeTaken;
protected $totalFailures;
protected $totalErrors;
/**
* @param Builder $phpci
@ -29,7 +30,6 @@ class Codeception implements ParserInterface
{
$this->phpci = $phpci;
$this->resultsXml = $resultsXml;
$this->totalTests = 0;
}
@ -47,6 +47,7 @@ class Codeception implements ParserInterface
$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 = array(
@ -62,9 +63,14 @@ class Codeception implements ParserInterface
$testresult['class'] = (string) $testcase['class'];
}
if (isset($testcase->failure)) {
// 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'] = (string) $testcase->failure;
$testresult['message'] = (string)$testcase->failure . (string)$testcase->error;
} else {
$testresult['pass'] = true;
}
@ -103,6 +109,6 @@ class Codeception implements ParserInterface
*/
public function getTotalFailures()
{
return $this->totalFailures;
return $this->totalFailures + $this->totalErrors;
}
}

View file

@ -43,7 +43,7 @@ class Wipe implements \PHPCI\Plugin
$path = $phpci->buildPath;
$this->phpci = $phpci;
$this->build = $build;
$this->directory = isset($options['directory']) ? $options['directory'] : $path;
$this->directory = isset($options['directory']) ? $this->phpci->interpolate($options['directory']) : $path;
}
/**

View file

@ -132,8 +132,9 @@ class XMPP implements \PHPCI\Plugin
*/
public function findConfigFile()
{
if (file_exists($this->phpci->buildPath . '/.sendxmpprc')) {
if (md5(file_get_contents($this->phpci->buildPath . '/.sendxmpprc')) !== md5($this->getConfigFormat())) {
if (file_exists($this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc')) {
if (md5(file_get_contents($this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc'))
!== md5($this->getConfigFormat())) {
return null;
}
@ -160,7 +161,7 @@ class XMPP implements \PHPCI\Plugin
/*
* Try to build conf file
*/
$config_file = $this->phpci->buildPath . '/.sendxmpprc';
$config_file = $this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc';
if (is_null($this->findConfigFile())) {
file_put_contents($config_file, $this->getConfigFormat());
chmod($config_file, 0600);
@ -174,7 +175,7 @@ class XMPP implements \PHPCI\Plugin
$tls = ' -t';
}
$message_file = $this->phpci->buildPath . '/' . uniqid('xmppmessage');
$message_file = $this->phpci->buildPath . DIRECTORY_SEPARATOR . uniqid('xmppmessage');
if ($this->buildMessage($message_file) === false) {
return false;
}

View file

@ -0,0 +1,63 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\ProcessControl;
/**
* Construct an appropriate ProcessControl instance.
*
* @author Adirelle <adirelle@gmail.com>
*/
class Factory
{
/**
* ProcessControl singleton.
*
* @var ProcessControlInterface
*/
protected static $instance = null;
/**
* Returns the ProcessControl singleton.
*
* @return ProcessControlInterface
*/
public static function getInstance()
{
if (static::$instance === null) {
static::$instance = static::createProcessControl();
}
return static::$instance;
}
/**
* Create a ProcessControl depending on available extensions and the underlying OS.
*
* Check PosixProcessControl, WindowsProcessControl and UnixProcessControl, in that order.
*
* @return ProcessControlInterface
*
* @internal
*/
public static function createProcessControl()
{
switch (true) {
case PosixProcessControl::isAvailable():
return new PosixProcessControl();
case WindowsProcessControl::isAvailable():
return new WindowsProcessControl();
case UnixProcessControl::isAvailable():
return new UnixProcessControl();
}
throw new \Exception("No ProcessControl implementation available.");
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\ProcessControl;
/**
* Control process using the POSIX extension.
*
* @author Adirelle <adirelle@gmail.com>
*/
class PosixProcessControl implements ProcessControlInterface
{
/**
*
* @param int $pid
* @return bool
*/
public function isRunning($pid)
{
// Signal "0" is not sent to the process, but posix_kill checks the process anyway;
return posix_kill($pid, 0);
}
/**
* Sends a TERMINATE or KILL signal to the process using posix_kill.
*
* @param int $pid
* @param bool $forcefully Whether to send TERMINATE (false) or KILL (true).
*/
public function kill($pid, $forcefully = false)
{
posix_kill($pid, $forcefully ? 9 : 15);
}
/**
* Check whether this posix_kill is available.
*
* @return bool
*
* @internal
*/
public static function isAvailable()
{
return function_exists('posix_kill');
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\ProcessControl;
/**
* A stateless service to check and kill system processes.
*
* @author Adirelle <adirelle@gmail.com>
*/
interface ProcessControlInterface
{
/** Checks if a process exists.
*
* @param int $pid The process identifier.
*
* @return boolean true is the process is running, else false.
*/
public function isRunning($pid);
/** Terminate a running process.
*
* @param int $pid The process identifier.
* @param bool $forcefully Whether to gently (false) or forcefully (true) terminate the process.
*/
public function kill($pid, $forcefully = false);
}

View file

@ -0,0 +1,54 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\ProcessControl;
/**
* Control processes using the "ps" and "kill" commands.
*
* @author Adirelle <adirelle@gmail.com>
*/
class UnixProcessControl implements ProcessControlInterface
{
/**
* Check process using the "ps" command.
*
* @param int $pid
* @return boolean
*/
public function isRunning($pid)
{
$output = $exitCode = null;
exec(sprintf("ps %d", $pid), $output, $exitCode);
return $exitCode === 0;
}
/**
* Sends a signal using the "kill" command.
*
* @param int $pid
* @param bool $forcefully
*/
public function kill($pid, $forcefully = false)
{
exec(sprintf("kill -%d %d", $forcefully ? 9 : 15, $pid));
}
/**
* Check whether the commands "ps" and "kill" are available.
*
* @return bool
*
* @internal
*/
public static function isAvailable()
{
return DIRECTORY_SEPARATOR === '/' && exec("which ps") && exec("which kill");
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\ProcessControl;
/**
* Control processes using the "tasklist" and "taskkill" commands.
*
* @author Adirelle <adirelle@gmail.com>
*/
class WindowsProcessControl implements ProcessControlInterface
{
/**
* Check if the process is running using the "tasklist" command.
*
* @param type $pid
* @return bool
*/
public function isRunning($pid)
{
$lastLine = exec(sprintf('tasklist /fi "PID eq %d" /nh /fo csv 2>nul:', $pid));
$record = str_getcsv($lastLine);
return isset($record[1]) && intval($record[1]) === $pid;
}
/**
* Terminate the process using the "taskkill" command.
*
* @param type $pid
* @param bool $forcefully
*/
public function kill($pid, $forcefully = false)
{
exec(sprintf("taskkill /t /pid %d %s 2>nul:", $pid, $forcefully ? '/f' : ''));
}
/**
* Check whether the commands "tasklist" and "taskkill" are available.
*
* @return bool
*
* @internal
*/
public static function isAvailable()
{
return DIRECTORY_SEPARATOR === '\\' && exec("where tasklist") && exec("where taskkill");
}
}

View file

@ -9,6 +9,10 @@
namespace PHPCI\Service;
use b8\Config;
use Pheanstalk\Pheanstalk;
use Pheanstalk\PheanstalkInterface;
use PHPCI\BuildFactory;
use PHPCI\Helper\Lang;
use PHPCI\Model\Build;
use PHPCI\Model\Project;
@ -26,6 +30,11 @@ class BuildService
*/
protected $buildStore;
/**
* @var bool
*/
public $queueError = false;
/**
* @param BuildStore $buildStore
*/
@ -81,7 +90,17 @@ class BuildService
$build->setExtra(json_encode($extra));
}
return $this->buildStore->save($build);
$build = $this->buildStore->save($build);
$buildId = $build->getId();
if (!empty($buildId)) {
$build = BuildFactory::getBuild($build);
$build->sendStatusPostback();
$this->addBuildToQueue($build);
}
return $build;
}
/**
@ -104,7 +123,17 @@ class BuildService
$build->setCreated(new \DateTime());
$build->setStatus(0);
return $this->buildStore->save($build);
$build = $this->buildStore->save($build);
$buildId = $build->getId();
if (!empty($buildId)) {
$build = BuildFactory::getBuild($build);
$build->sendStatusPostback();
$this->addBuildToQueue($build);
}
return $build;
}
/**
@ -117,4 +146,44 @@ class BuildService
$build->removeBuildDirectory();
return $this->buildStore->delete($build);
}
/**
* Takes a build and puts it into the queue to be run (if using a queue)
* @param Build $build
*/
public function addBuildToQueue(Build $build)
{
$buildId = $build->getId();
if (empty($buildId)) {
return;
}
$config = Config::getInstance();
$settings = $config->get('phpci.worker', []);
if (!empty($settings['host']) && !empty($settings['queue'])) {
try {
$jobData = array(
'type' => 'phpci.build',
'build_id' => $build->getId(),
);
if ($config->get('using_custom_file')) {
$jobData['config'] = $config->getArray();
}
$pheanstalk = new Pheanstalk($settings['host']);
$pheanstalk->useTube($settings['queue']);
$pheanstalk->put(
json_encode($jobData),
PheanstalkInterface::DEFAULT_PRIORITY,
PheanstalkInterface::DEFAULT_DELAY,
$config->get('phpci.worker.job_timeout', 600)
);
} catch (\Exception $ex) {
$this->queueError = true;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more