fix:修复BUG/升级1.1.6版本

This commit is contained in:
Ying
2023-04-25 20:11:49 +08:00
parent 445e5f9662
commit 6a6866bbaf
2357 changed files with 456920 additions and 140567 deletions

16
vendor/webman/captcha/.travis.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
language: php
php:
- 5.3.3
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- hhvm
script:
- composer install
- phpunit

19
vendor/webman/captcha/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) <2012-2017> Grégoire Passault
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

142
vendor/webman/captcha/README.md vendored Normal file
View File

@@ -0,0 +1,142 @@
Captcha
=======
![Captchas examples](http://gregwar.com/captchas.png)
This is a fork of [Gregwar/Captcha](https://github.com/Gregwar/Captcha)
Installation
============
With composer :
```
composer require webman/captcha
```
Usage
=====
You can create a captcha with the `CaptchaBuilder` :
```php
<?php
use Webman\Captcha\CaptchaBuilder;
$builder = new CaptchaBuilder;
$builder->build();
```
You can then save it to a file :
```php
<?php
$builder->save('out.jpg');
```
Or output it directly :
```php
<?php
header('Content-type: image/jpeg');
$builder->output();
```
Or inline it directly in the HTML page:
```php
<img src="<?php echo $builder->inline(); ?>" />
```
You'll be able to get the code and compare it with a user input :
```php
<?php
// Example: storing the phrase in the session to test for the user
// input later
$_SESSION['phrase'] = $builder->getPhrase();
```
You can compare the phrase with user input:
```php
if($builder->testPhrase($userInput)) {
// instructions if user phrase is good
}
else {
// user phrase is wrong
}
```
API
===
You can use theses functions :
* **__construct($phrase = null)**, constructs the builder with the given phrase, if the phrase is null, a random one will be generated
* **getPhrase()**, allow you to get the phrase contents
* **setDistortion($distortion)**, enable or disable the distortion, call it before `build()`
* **isOCRReadable()**, returns `true` if the OCR can be read using the `ocrad` software, you'll need to have shell_exec enabled, imagemagick and ocrad installed
* **buildAgainstOCR($width = 150, $height = 40, $font = null)**, builds a code until it is not readable by `ocrad`
* **build($width = 150, $height = 40, $font = null)**, builds a code with the given $width, $height and $font. By default, a random font will be used from the library
* **save($filename, $quality = 80)**, saves the captcha into a jpeg in the $filename, with the given quality
* **get($quality = 80)**, returns the jpeg data
* **output($quality = 80)**, directly outputs the jpeg code to a browser
* **setBackgroundColor($r, $g, $b)**, sets the background color to force it (this will disable many effects and is not recommended)
* **setBackgroundImages(array($imagepath1, $imagePath2))**, Sets custom background images to be used as captcha background. It is recommended to disable image effects when passing custom images for background (ignore_all_effects). A random image is selected from the list passed, the full paths to the image files must be passed.
* **setInterpolation($interpolate)**, enable or disable the interpolation (enabled by default), disabling it will be quicker but the images will look uglier
* **setIgnoreAllEffects($ignoreAllEffects)**, disable all effects on the captcha image. Recommended to use when passing custom background images for the captcha.
* **testPhrase($phrase)**, returns true if the given phrase is good
* **setMaxBehindLines($lines)**, sets the maximum number of lines behind the code
* **setMaxFrontLines($lines)**, sets the maximum number of lines on the front of the code
If you want to change the number of character, you can call the phrase builder directly using
extra parameters:
```php
use Webman\Captcha\CaptchaBuilder;
use Webman\Captcha\PhraseBuilder;
// Will build phrases of 3 characters
$phraseBuilder = new PhraseBuilder(4);
// Will build phrases of 5 characters, only digits
$phraseBuilder = new PhraseBuilder(5, '0123456789');
// Pass it as first argument of CaptchaBuilder, passing it the phrase
// builder
$captcha = new CaptchaBuilder(null, $phraseBuilder);
```
You can also pass directly the wanted phrase to the builder:
```php
// Building a Captcha with the "hello" phrase
$captcha = new CaptchaBuilder('hello');
```
Complete example
================
If you want to see an example you can have a look at the ``demo/form.php``, which uses ``demo/session.php`` to
render a captcha and check it after the submission
Symfony Bundle
================
You can have a look at the following repository to enjoy the Symfony 2 bundle packaging this captcha generator :
https://github.com/Gregwar/CaptchaBundle
Yii2 Extension
===============
You can use the following extension for integrating with Yii2 Framework :
https://github.com/juliardi/yii2-captcha
License
=======
This library is under MIT license, have a look to the `LICENSE` file

35
vendor/webman/captcha/composer.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "webman/captcha",
"type": "library",
"description": "Captcha generator",
"keywords": ["captcha", "spam", "bot"],
"license": "MIT",
"authors": [
{
"name": "walkor",
"email": "walkor@workerman.net"
},
{
"name": "Grégoire Passault",
"email": "g.passault@gmail.com",
"homepage": "http://www.gregwar.com/"
},
{
"name": "Jeremy Livingston",
"email": "jeremy.j.livingston@gmail.com"
}
],
"require": {
"php": ">=5.6.0",
"ext-gd": "*",
"ext-mbstring": "*"
},
"autoload": {
"psr-4": {
"Webman\\Captcha\\": "src"
}
},
"require-dev": {
"phpunit/phpunit": "^6.4"
}
}

11
vendor/webman/captcha/demo/demo.php vendored Normal file
View File

@@ -0,0 +1,11 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Webman\Captcha\CaptchaBuilder;
$captcha = new CaptchaBuilder;
$captcha
->build()
->save('out.jpg')
;

View File

@@ -0,0 +1,12 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Webman\Captcha\CaptchaBuilder;
echo count(CaptchaBuilder::create()
->build()
->getFingerprint()
);
echo "\n";

32
vendor/webman/captcha/demo/form.php vendored Normal file
View File

@@ -0,0 +1,32 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Webman\Captcha\PhraseBuilder;
// We need the session to check the phrase after submitting
session_start();
?>
<html>
<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Checking that the posted phrase match the phrase stored in the session
if (isset($_SESSION['phrase']) && PhraseBuilder::comparePhrases($_SESSION['phrase'], $_POST['phrase'])) {
echo "<h1>Captcha is valid !</h1>";
} else {
echo "<h1>Captcha is not valid!</h1>";
}
// The phrase can't be used twice
unset($_SESSION['phrase']);
}
?>
<form method="post">
Copy the CAPTCHA:
<?php
// See session.php, where the captcha is actually rendered and the session phrase
// is set accordingly to the image displayed
?>
<img src="session.php" />
<input type="text" name="phrase" />
<input type="submit" />
</form>
</html>

15
vendor/webman/captcha/demo/index.php vendored Normal file
View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<body>
<html>
<meta charset="utf-8" />
</html>
<body>
<h1>Captchas gallery</h1>
<?php for ($x=0; $x<8; $x++) { ?>
<?php for ($y=0; $y<5; $y++) { ?>
<img src="output.php?n=<?php echo 5*$x+$y; ?>" />
<?php } ?>
<br />
<?php } ?>
</body>
</body>

22
vendor/webman/captcha/demo/inline.php vendored Normal file
View File

@@ -0,0 +1,22 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Webman\Captcha\CaptchaBuilder;
$captcha = new CaptchaBuilder();
$captcha->build();
?>
<!DOCTYPE html>
<body>
<html>
<meta charset="utf-8" />
</html>
<body>
<h1>Inline Captcha</h1>
<img src="<?php echo $captcha->inline(); ?>"/><br/>
Phrase: <?php echo $captcha->getPhrase(); ?>
</body>
</body>

39
vendor/webman/captcha/demo/ocr.php vendored Normal file
View File

@@ -0,0 +1,39 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Webman\Captcha\CaptchaBuilder;
/**
* Generates 1000 captchas and try to read their code with the
* ocrad OCR
*/
$tests = 10000;
$passed = 0;
shell_exec('rm passed*.jpg');
for ($i=0; $i<$tests; $i++) {
echo "Captcha $i/$tests... ";
$captcha = new CaptchaBuilder;
$captcha
->setDistortion(false)
->build()
;
if ($captcha->isOCRReadable()) {
$passed++;
$captcha->save("passed$passed.jpg");
echo "passed at ocr... ";
} else {
echo "failed... ";
}
echo "pass rate: ".round(100*$passed/($i+1),2)."%\n";
}
echo "\n";
echo "Over, $passed/$tests readed with OCR\n";

12
vendor/webman/captcha/demo/output.php vendored Normal file
View File

@@ -0,0 +1,12 @@
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Webman\Captcha\CaptchaBuilder;
header('Content-type: image/jpeg');
CaptchaBuilder::create()
->build()
->output()
;

22
vendor/webman/captcha/demo/session.php vendored Normal file
View File

@@ -0,0 +1,22 @@
<?php
// We need the session to store the correct phrase for later check
session_start();
// Including the autoload (you need to composer install in the main directory)
require_once __DIR__.'/../vendor/autoload.php';
use Webman\Captcha\CaptchaBuilder;
// Creating the captcha instance and setting the phrase in the session to store
// it for check when the form is submitted
$captcha = new CaptchaBuilder;
$_SESSION['phrase'] = $captcha->getPhrase();
// Setting the header to image jpeg because we here render an image
header('Content-Type: image/jpeg');
// Running the actual rendering of the captcha image
$captcha
->build()
->output()
;

15
vendor/webman/captcha/phpunit.xml.dist vendored Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="KnpMenu Test Suite">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,767 @@
<?php
namespace Webman\Captcha;
use AllowDynamicProperties;
use \Exception;
/**
* Builds a new captcha image
* Uses the fingerprint parameter, if one is passed, to generate the same image
*
* @author Gregwar <g.passault@gmail.com>
* @author Jeremy Livingston <jeremy.j.livingston@gmail.com>
*/
#[AllowDynamicProperties]
class CaptchaBuilder implements CaptchaBuilderInterface
{
/**
* @var int|bool
*/
public $background;
/**
* @var array
*/
protected $fingerprint = array();
/**
* @var bool
*/
protected $useFingerprint = false;
/**
* @var array
*/
protected $textColor = array();
/**
* @var array
*/
protected $lineColor = null;
/**
* @var array
*/
protected $backgroundColor = null;
/**
* @var array
*/
protected $backgroundImages = array();
/**
* @var resource
*/
protected $contents = null;
/**
* @var string
*/
protected $phrase = null;
/**
* @var PhraseBuilderInterface
*/
protected $builder;
/**
* @var bool
*/
protected $distortion = true;
/**
* The maximum number of lines to draw in front of
* the image. null - use default algorithm
*/
protected $maxFrontLines = null;
/**
* The maximum number of lines to draw behind
* the image. null - use default algorithm
*/
protected $maxBehindLines = null;
/**
* The maximum angle of char
*/
protected $maxAngle = 8;
/**
* The maximum offset of char
*/
protected $maxOffset = 5;
/**
* Is the interpolation enabled ?
*
* @var bool
*/
protected $interpolation = true;
/**
* Ignore all effects
*
* @var bool
*/
protected $ignoreAllEffects = false;
/**
* Allowed image types for the background images
*
* @var array
*/
protected $allowedBackgroundImageTypes = array('image/png', 'image/jpeg', 'image/gif');
/**
* The image contents
*/
public function getContents()
{
return $this->contents;
}
/**
* Enable/Disables the interpolation
*
* @param $interpolate bool True to enable, false to disable
*
* @return CaptchaBuilder
*/
public function setInterpolation($interpolate = true)
{
$this->interpolation = $interpolate;
return $this;
}
/**
* Temporary dir, for OCR check
*/
public $tempDir = 'temp/';
public function __construct($phrase = null, PhraseBuilderInterface $builder = null)
{
if ($builder === null) {
$this->builder = new PhraseBuilder;
} else {
$this->builder = $builder;
}
$this->phrase = is_string($phrase) ? $phrase : $this->builder->build($phrase);
}
/**
* Setting the phrase
*/
public function setPhrase($phrase)
{
$this->phrase = (string) $phrase;
}
/**
* Enables/disable distortion
*/
public function setDistortion($distortion)
{
$this->distortion = (bool) $distortion;
return $this;
}
public function setMaxBehindLines($maxBehindLines)
{
$this->maxBehindLines = $maxBehindLines;
return $this;
}
public function setMaxFrontLines($maxFrontLines)
{
$this->maxFrontLines = $maxFrontLines;
return $this;
}
public function setMaxAngle($maxAngle)
{
$this->maxAngle = $maxAngle;
return $this;
}
public function setMaxOffset($maxOffset)
{
$this->maxOffset = $maxOffset;
return $this;
}
/**
* Gets the captcha phrase
*/
public function getPhrase()
{
return $this->phrase;
}
/**
* Returns true if the given phrase is good
*/
public function testPhrase($phrase)
{
return ($this->builder->niceize($phrase) == $this->builder->niceize($this->getPhrase()));
}
/**
* Instantiation
*/
public static function create($phrase = null)
{
return new self($phrase);
}
/**
* Sets the text color to use
*/
public function setTextColor($r, $g, $b)
{
$this->textColor = array($r, $g, $b);
return $this;
}
/**
* Sets the background color to use
*/
public function setBackgroundColor($r, $g, $b)
{
$this->backgroundColor = array($r, $g, $b);
return $this;
}
public function setLineColor($r, $g, $b)
{
$this->lineColor = array($r, $g, $b);
return $this;
}
/**
* Sets the ignoreAllEffects value
*
* @param bool $ignoreAllEffects
* @return CaptchaBuilder
*/
public function setIgnoreAllEffects($ignoreAllEffects)
{
$this->ignoreAllEffects = $ignoreAllEffects;
return $this;
}
/**
* Sets the list of background images to use (one image is randomly selected)
*/
public function setBackgroundImages(array $backgroundImages)
{
$this->backgroundImages = $backgroundImages;
return $this;
}
/**
* Draw lines over the image
*/
protected function drawLine($image, $width, $height, $tcol = null)
{
if ($this->lineColor === null) {
$red = $this->rand(100, 255);
$green = $this->rand(100, 255);
$blue = $this->rand(100, 255);
} else {
$red = $this->lineColor[0];
$green = $this->lineColor[1];
$blue = $this->lineColor[2];
}
if ($tcol === null) {
$tcol = imagecolorallocate($image, $red, $green, $blue);
}
if ($this->rand(0, 1)) { // Horizontal
$Xa = $this->rand(0, $width/2);
$Ya = $this->rand(0, $height);
$Xb = $this->rand($width/2, $width);
$Yb = $this->rand(0, $height);
} else { // Vertical
$Xa = $this->rand(0, $width);
$Ya = $this->rand(0, $height/2);
$Xb = $this->rand(0, $width);
$Yb = $this->rand($height/2, $height);
}
imagesetthickness($image, $this->rand(1, 3));
imageline($image, $Xa, $Ya, $Xb, $Yb, $tcol);
}
/**
* Apply some post effects
*/
protected function postEffect($image)
{
if (!function_exists('imagefilter')) {
return;
}
if ($this->backgroundColor != null || $this->textColor != null) {
return;
}
// Negate ?
if ($this->rand(0, 1) == 0) {
imagefilter($image, IMG_FILTER_NEGATE);
}
// Edge ?
if ($this->rand(0, 10) == 0) {
imagefilter($image, IMG_FILTER_EDGEDETECT);
}
// Contrast
imagefilter($image, IMG_FILTER_CONTRAST, $this->rand(-50, 10));
// Colorize
if ($this->rand(0, 5) == 0) {
imagefilter($image, IMG_FILTER_COLORIZE, $this->rand(-80, 50), $this->rand(-80, 50), $this->rand(-80, 50));
}
}
/**
* Writes the phrase on the image
*/
protected function writePhrase($image, $phrase, $font, $width, $height)
{
$length = mb_strlen($phrase);
if ($length === 0) {
return \imagecolorallocate($image, 0, 0, 0);
}
// Gets the text size and start position
$size = intval($width / $length) - $this->rand(0, 3) - 1;
$box = \imagettfbbox($size, 0, $font, $phrase);
$textWidth = $box[2] - $box[0];
$textHeight = $box[1] - $box[7];
$x = intval(($width - $textWidth) / 2);
$y = intval(($height - $textHeight) / 2) + $size;
if (!$this->textColor) {
$textColor = array($this->rand(0, 150), $this->rand(0, 150), $this->rand(0, 150));
} else {
$textColor = $this->textColor;
}
$col = \imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
// Write the letters one by one, with random angle
for ($i=0; $i<$length; $i++) {
$symbol = mb_substr($phrase, $i, 1);
$box = \imagettfbbox($size, 0, $font, $symbol);
$w = $box[2] - $box[0];
$angle = $this->rand(-$this->maxAngle, $this->maxAngle);
$offset = $this->rand(-$this->maxOffset, $this->maxOffset);
\imagettftext($image, $size, $angle, $x, $y + $offset, $col, $font, $symbol);
$x += $w;
}
return $col;
}
/**
* Try to read the code against an OCR
*/
public function isOCRReadable()
{
if (!is_dir($this->tempDir)) {
@mkdir($this->tempDir, 0755, true);
}
$tempj = $this->tempDir . uniqid('captcha', true) . '.jpg';
$tempp = $this->tempDir . uniqid('captcha', true) . '.pgm';
$this->save($tempj);
shell_exec("convert $tempj $tempp");
$value = trim(strtolower(shell_exec("ocrad $tempp")));
@unlink($tempj);
@unlink($tempp);
return $this->testPhrase($value);
}
/**
* Builds while the code is readable against an OCR
*/
public function buildAgainstOCR($width = 150, $height = 40, $font = null, $fingerprint = null)
{
do {
$this->build($width, $height, $font, $fingerprint);
} while ($this->isOCRReadable());
}
/**
* Generate the image
*/
public function build($width = 150, $height = 40, $font = null, $fingerprint = null)
{
if (null !== $fingerprint) {
$this->fingerprint = $fingerprint;
$this->useFingerprint = true;
} else {
$this->fingerprint = array();
$this->useFingerprint = false;
}
if ($font === null) {
$font = $this->getFontPath(__DIR__ . '/Font/captcha'.$this->rand(0, 5).'.ttf');
}
if (empty($this->backgroundImages)) {
// if background images list is not set, use a color fill as a background
$image = imagecreatetruecolor($width, $height);
if ($this->backgroundColor == null) {
$bg = imagecolorallocate($image, $this->rand(200, 255), $this->rand(200, 255), $this->rand(200, 255));
} else {
$color = $this->backgroundColor;
$bg = imagecolorallocate($image, $color[0], $color[1], $color[2]);
}
$this->background = $bg;
imagefill($image, 0, 0, $bg);
} else {
// use a random background image
$randomBackgroundImage = $this->backgroundImages[rand(0, count($this->backgroundImages)-1)];
$imageType = $this->validateBackgroundImage($randomBackgroundImage);
$image = $this->createBackgroundImageFromType($randomBackgroundImage, $imageType);
}
// Apply effects
if (!$this->ignoreAllEffects) {
$square = $width * $height;
$effects = $this->rand($square/3000, $square/2000);
// set the maximum number of lines to draw in front of the text
if ($this->maxBehindLines != null && $this->maxBehindLines > 0) {
$effects = min($this->maxBehindLines, $effects);
}
if ($this->maxBehindLines !== 0) {
for ($e = 0; $e < $effects; $e++) {
$this->drawLine($image, $width, $height);
}
}
}
// Write CAPTCHA text
$color = $this->writePhrase($image, $this->phrase, $font, $width, $height);
// Apply effects
if (!$this->ignoreAllEffects) {
$square = $width * $height;
$effects = $this->rand($square/3000, $square/2000);
// set the maximum number of lines to draw in front of the text
if ($this->maxFrontLines != null && $this->maxFrontLines > 0) {
$effects = min($this->maxFrontLines, $effects);
}
if ($this->maxFrontLines !== 0) {
for ($e = 0; $e < $effects; $e++) {
$this->drawLine($image, $width, $height, $color);
}
}
}
// Distort the image
if ($this->distortion && !$this->ignoreAllEffects) {
$image = $this->distort($image, $width, $height, $bg);
}
// Post effects
if (!$this->ignoreAllEffects) {
$this->postEffect($image);
}
$this->contents = $image;
return $this;
}
/**
* @param $font
* @return string
*/
protected function getFontPath($font)
{
static $fontPathMap = [];
if (!\class_exists(\Phar::class, false) || !\Phar::running()) {
return $font;
}
$tmpPath = sys_get_temp_dir() ?: '/tmp';
$filePath = "$tmpPath/" . basename($font);
clearstatcache();
if (!isset($fontPathMap[$font]) || !is_file($filePath)) {
file_put_contents($filePath, file_get_contents($font));
$fontPathMap[$font] = $filePath;
}
return $fontPathMap[$font];
}
/**
* Distorts the image
*/
public function distort($image, $width, $height, $bg)
{
$contents = imagecreatetruecolor($width, $height);
$X = $this->rand(0, $width);
$Y = $this->rand(0, $height);
$phase = $this->rand(0, 10);
$scale = 1.1 + $this->rand(0, 10000) / 30000;
for ($x = 0; $x < $width; $x++) {
for ($y = 0; $y < $height; $y++) {
$Vx = $x - $X;
$Vy = $y - $Y;
$Vn = sqrt($Vx * $Vx + $Vy * $Vy);
if ($Vn != 0) {
$Vn2 = $Vn + 4 * sin($Vn / 30);
$nX = $X + ($Vx * $Vn2 / $Vn);
$nY = $Y + ($Vy * $Vn2 / $Vn);
} else {
$nX = $X;
$nY = $Y;
}
$nY = $nY + $scale * sin($phase + $nX * 0.2);
if ($this->interpolation) {
$p = $this->interpolate(
$nX - floor($nX),
$nY - floor($nY),
$this->getCol($image, floor($nX), floor($nY), $bg),
$this->getCol($image, ceil($nX), floor($nY), $bg),
$this->getCol($image, floor($nX), ceil($nY), $bg),
$this->getCol($image, ceil($nX), ceil($nY), $bg)
);
} else {
$p = $this->getCol($image, round($nX), round($nY), $bg);
}
if ($p == 0) {
$p = $bg;
}
imagesetpixel($contents, $x, $y, $p);
}
}
return $contents;
}
/**
* Saves the Captcha to a jpeg file
*/
public function save($filename, $quality = 90)
{
imagejpeg($this->contents, $filename, $quality);
}
/**
* Gets the image GD
*/
public function getGd()
{
return $this->contents;
}
/**
* Gets the image contents
*/
public function get($quality = 90)
{
ob_start();
$this->output($quality);
return ob_get_clean();
}
/**
* Gets the HTML inline base64
*/
public function inline($quality = 90)
{
return 'data:image/jpeg;base64,' . base64_encode($this->get($quality));
}
/**
* Outputs the image
*/
public function output($quality = 90)
{
imagejpeg($this->contents, null, $quality);
}
/**
* @return array
*/
public function getFingerprint()
{
return $this->fingerprint;
}
/**
* Returns a random number or the next number in the
* fingerprint
*/
protected function rand($min, $max)
{
if (!is_array($this->fingerprint)) {
$this->fingerprint = array();
}
if ($this->useFingerprint) {
$value = current($this->fingerprint);
next($this->fingerprint);
} else {
$value = mt_rand(intval($min), intval($max));
$this->fingerprint[] = $value;
}
return $value;
}
/**
* @param $x
* @param $y
* @param $nw
* @param $ne
* @param $sw
* @param $se
*
* @return int
*/
protected function interpolate($x, $y, $nw, $ne, $sw, $se)
{
list($r0, $g0, $b0) = $this->getRGB($nw);
list($r1, $g1, $b1) = $this->getRGB($ne);
list($r2, $g2, $b2) = $this->getRGB($sw);
list($r3, $g3, $b3) = $this->getRGB($se);
$cx = 1.0 - $x;
$cy = 1.0 - $y;
$m0 = $cx * $r0 + $x * $r1;
$m1 = $cx * $r2 + $x * $r3;
$r = (int) ($cy * $m0 + $y * $m1);
$m0 = $cx * $g0 + $x * $g1;
$m1 = $cx * $g2 + $x * $g3;
$g = (int) ($cy * $m0 + $y * $m1);
$m0 = $cx * $b0 + $x * $b1;
$m1 = $cx * $b2 + $x * $b3;
$b = (int) ($cy * $m0 + $y * $m1);
return ($r << 16) | ($g << 8) | $b;
}
/**
* @param $image
* @param $x
* @param $y
*
* @return int
*/
protected function getCol($image, $x, $y, $background)
{
$L = imagesx($image);
$H = imagesy($image);
if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) {
return $background;
}
return imagecolorat($image, $x, $y);
}
/**
* @param $col
*
* @return array
*/
protected function getRGB($col)
{
return array(
(int) ($col >> 16) & 0xff,
(int) ($col >> 8) & 0xff,
(int) ($col) & 0xff,
);
}
/**
* Validate the background image path. Return the image type if valid
*
* @param string $backgroundImage
* @return string
* @throws Exception
*/
protected function validateBackgroundImage($backgroundImage)
{
// check if file exists
if (!file_exists($backgroundImage)) {
$backgroundImageExploded = explode('/', $backgroundImage);
$imageFileName = count($backgroundImageExploded) > 1? $backgroundImageExploded[count($backgroundImageExploded)-1] : $backgroundImage;
throw new Exception('Invalid background image: ' . $imageFileName);
}
// check image type
$finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
$imageType = finfo_file($finfo, $backgroundImage);
finfo_close($finfo);
if (!in_array($imageType, $this->allowedBackgroundImageTypes)) {
throw new Exception('Invalid background image type! Allowed types are: ' . join(', ', $this->allowedBackgroundImageTypes));
}
return $imageType;
}
/**
* Create background image from type
*
* @param string $backgroundImage
* @param string $imageType
* @return resource
* @throws Exception
*/
protected function createBackgroundImageFromType($backgroundImage, $imageType)
{
switch ($imageType) {
case 'image/jpeg':
$image = imagecreatefromjpeg($backgroundImage);
break;
case 'image/png':
$image = imagecreatefrompng($backgroundImage);
break;
case 'image/gif':
$image = imagecreatefromgif($backgroundImage);
break;
default:
throw new Exception('Not supported file type for background image!');
break;
}
return $image;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Webman\Captcha;
/**
* A Captcha builder
*/
interface CaptchaBuilderInterface
{
/**
* Builds the code
*/
public function build($width, $height, $font, $fingerprint);
/**
* Saves the code to a file
*/
public function save($filename, $quality);
/**
* Gets the image contents
*/
public function get($quality);
/**
* Outputs the image
*/
public function output($quality);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,78 @@
<?php
namespace Webman\Captcha;
/**
* Handles actions related to captcha image files including saving and garbage collection
*
* @author Gregwar <g.passault@gmail.com>
* @author Jeremy Livingston <jeremy@quizzle.com>
*/
class ImageFileHandler
{
/**
* Name of folder for captcha images
* @var string
*/
protected $imageFolder;
/**
* Absolute path to public web folder
* @var string
*/
protected $webPath;
/**
* Frequency of garbage collection in fractions of 1
* @var int
*/
protected $gcFreq;
/**
* Maximum age of images in minutes
* @var int
*/
protected $expiration;
/**
* @param $imageFolder
* @param $webPath
* @param $gcFreq
* @param $expiration
*/
public function __construct($imageFolder, $webPath, $gcFreq, $expiration)
{
$this->imageFolder = $imageFolder;
$this->webPath = $webPath;
$this->gcFreq = $gcFreq;
$this->expiration = $expiration;
}
/**
* Saves the provided image content as a file
*
* @param string $contents
*
* @return string
*/
public function saveAsFile($contents)
{
$this->createFolderIfMissing();
$filename = md5(uniqid()) . '.jpg';
$filePath = $this->webPath . '/' . $this->imageFolder . '/' . $filename;
imagejpeg($contents, $filePath, 15);
return '/' . $this->imageFolder . '/' . $filename;
}
/**
* Creates the folder if it doesn't exist
*/
protected function createFolderIfMissing()
{
if (!file_exists($this->webPath . '/' . $this->imageFolder)) {
mkdir($this->webPath . '/' . $this->imageFolder, 0755);
}
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Webman\Captcha;
/**
* Generates random phrase
*
* @author Gregwar <g.passault@gmail.com>
*/
class PhraseBuilder implements PhraseBuilderInterface
{
/**
* @var int
*/
public $length;
/**
* @var string
*/
public $charset;
/**
* Constructs a PhraseBuilder with given parameters
*/
public function __construct($length = 5, $charset = 'abcdefghijklmnpqrstuvwxyz123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$this->length = $length;
$this->charset = $charset;
}
/**
* Generates random phrase of given length with given charset
*/
public function build($length = null, $charset = null)
{
if ($length !== null) {
$this->length = $length;
}
if ($charset !== null) {
$this->charset = $charset;
}
$phrase = '';
$chars = str_split($this->charset);
for ($i = 0; $i < $this->length; $i++) {
$phrase .= $chars[array_rand($chars)];
}
return $phrase;
}
/**
* "Niceize" a code
*/
public function niceize($str)
{
return self::doNiceize($str);
}
/**
* A static helper to niceize
*/
public static function doNiceize($str)
{
return strtr(strtolower($str), '01', 'ol');
}
/**
* A static helper to compare
*/
public static function comparePhrases($str1, $str2)
{
return self::doNiceize($str1) === self::doNiceize($str2);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Webman\Captcha;
/**
* Interface for the PhraseBuilder
*
* @author Gregwar <g.passault@gmail.com>
*/
interface PhraseBuilderInterface
{
/**
* Generates random phrase of given length with given charset
*/
public function build();
/**
* "Niceize" a code
*/
public function niceize($str);
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Test;
use Webman\Captcha\CaptchaBuilder;
use PHPUnit\Framework\TestCase;
class CaptchaBuilderTest extends TestCase
{
public function testDemo()
{
$captcha = new CaptchaBuilder();
$captcha
->build()
->save('out.jpg')
;
$this->assertTrue(file_exists(__DIR__.'/../out.jpg'));
}
public function testFingerPrint()
{
$int = count(CaptchaBuilder::create()
->build()
->getFingerprint()
);
$this->assertTrue(is_int($int));
}
}

View File

@@ -4,6 +4,7 @@ namespace Webman\Console;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command as Commands;
use support\Container;
class Command extends Application
{
@@ -24,13 +25,17 @@ class Command extends Application
if ($file->getExtension() !== 'php') {
continue;
}
// abc\def.php
$relativePath = str_replace(str_replace('/', '\\', $path . '\\'), '', str_replace('/', '\\', $file->getRealPath()));
$realNamespace = trim($namspace . '\\' . trim(dirname($relativePath), '.'), '\\');
// app\command\abc
$realNamespace = trim($namspace . '\\' . trim(dirname(str_replace('\\', DIRECTORY_SEPARATOR, $relativePath)), '.'), '\\');
$realNamespace = str_replace('/', '\\', $realNamespace);
// app\command\doc\def
$class_name = trim($realNamespace . '\\' . $file->getBasename('.php'), '\\');
if (!is_a($class_name, Commands::class, true)) {
if (!class_exists($class_name) || !is_a($class_name, Commands::class, true)) {
continue;
}
$this->add(new $class_name);
$this->add(Container::get($class_name));
}
}
}

View File

@@ -56,17 +56,17 @@ class AppPluginCreateCommand extends Command
{
$base_path = base_path();
$this->mkdir("$base_path/plugin/$name/app/controller", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/exception", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/model", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/middleware", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/view/index", 0777, true);
$this->mkdir("$base_path/plugin/$name/config", 0777, true);
$this->mkdir("$base_path/plugin/$name/public", 0777, true);
$this->mkdir("$base_path/plugin/$name/api", 0777, true);
$this->createFunctionsFile("$base_path/plugin/$name/app/functions.php");
$this->createControllerFile("$base_path/plugin/$name/app/controller/IndexController.php", $name);
$this->createViewFile("$base_path/plugin/$name/app/view/index/index.html");
$this->createExceptionFile("$base_path/plugin/$name/app/exception/Handler.php", $name);
$this->createConfigFiles("$base_path/plugin/$name/config", $name);
$this->createApiFiles("$base_path/plugin/$name/api", $name);
}
/**
@@ -139,44 +139,6 @@ EOF;
}
/**
* @param $path
* @return void
*/
protected function createExceptionFile($path, $name)
{
$content = <<<EOF
<?php
namespace plugin\\$name\\app\\exception;
use Throwable;
use Webman\\Http\\Request;
use Webman\\Http\\Response;
/**
* Class Handler
* @package Support\Exception
*/
class Handler extends \\support\\exception\\Handler
{
public function render(Request \$request, Throwable \$exception): Response
{
\$code = \$exception->getCode();
if (\$request->expectsJson()) {
\$json = ['code' => \$code ? \$code : 500, 'message' => \$this->_debug ? \$exception->getMessage() : 'Server internal error', 'type' => 'failed'];
\$this->_debug && \$json['traces'] = (string)\$exception;
return new Response(200, ['Content-Type' => 'application/json'],
\json_encode(\$json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
\$error = \$this->_debug ? \\nl2br((string)\$exception) : 'Server internal error';
return new Response(500, [], \$error);
}
}
EOF;
file_put_contents($path, $content);
}
/**
* @param $file
@@ -196,6 +158,119 @@ EOF;
file_put_contents($file, $content);
}
/**
* @param $base
* @param $name
* @return void
*/
protected function createApiFiles($base, $name)
{
$content = <<<EOF
<?php
namespace plugin\\$name\api;
use plugin\admin\api\Menu;
class Install
{
/**
* 安装
*
* @param \$version
* @return void
*/
public static function install(\$version)
{
// 导入菜单
if(\$menus = static::getMenus()) {
Menu::import(\$menus);
}
}
/**
* 卸载
*
* @param \$version
* @return void
*/
public static function uninstall(\$version)
{
// 删除菜单
foreach (static::getMenus() as \$menu) {
Menu::delete(\$menu['key']);
}
}
/**
* 更新
*
* @param \$from_version
* @param \$to_version
* @param \$context
* @return void
*/
public static function update(\$from_version, \$to_version, \$context = null)
{
// 删除不用的菜单
if (isset(\$context['previous_menus'])) {
static::removeUnnecessaryMenus(\$context['previous_menus']);
}
// 导入新菜单
if (\$menus = static::getMenus()) {
Menu::import(\$menus);
}
}
/**
* 更新前数据收集等
*
* @param \$from_version
* @param \$to_version
* @return array|array[]
*/
public static function beforeUpdate(\$from_version, \$to_version)
{
// 在更新之前获得老菜单通过context传递给 update
return ['previous_menus' => static::getMenus()];
}
/**
* 获取菜单
*
* @return array|mixed
*/
public static function getMenus()
{
clearstatcache();
if (is_file(\$menu_file = __DIR__ . '/../config/menu.php')) {
\$menus = include \$menu_file;
return \$menus ?: [];
}
return [];
}
/**
* 删除不需要的菜单
*
* @param \$previous_menus
* @return void
*/
public static function removeUnnecessaryMenus(\$previous_menus)
{
\$menus_to_remove = array_diff(Menu::column(\$previous_menus, 'name'), Menu::column(static::getMenus(), 'name'));
foreach (\$menus_to_remove as \$name) {
Menu::delete(\$name);
}
}
}
EOF;
file_put_contents("$base/Install.php", $content);
}
/**
* @param $base
* @param $name
@@ -212,12 +287,22 @@ use support\\Request;
return [
'debug' => true,
'controller_suffix' => 'Controller',
'controller_reuse' => true,
'controller_reuse' => false,
'version' => '1.0.0'
];
EOF;
file_put_contents("$base/app.php", $content);
// menu.php
$content = <<<EOF
<?php
return [];
EOF;
file_put_contents("$base/menu.php", $content);
// autoload.php
$content = <<<EOF
<?php
@@ -251,7 +336,7 @@ EOF;
<?php
return [
'' => \\plugin\\$name\\app\\exception\\Handler::class,
'' => support\\exception\\Handler::class,
];
EOF;
@@ -351,7 +436,7 @@ return [
// Fallback language
'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored
'path' => "$base/resource/translations",
'path' => base_path() . "/plugin/$name/resource/translations",
];
EOF;

View File

@@ -0,0 +1,42 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class AppPluginInstallCommand extends Command
{
protected static $defaultName = 'app-plugin:install';
protected static $defaultDescription = 'App Plugin Install';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Install App Plugin $name");
$class = "\\plugin\\$name\\api\\Install";
if (!method_exists($class, 'install')) {
throw new \RuntimeException("Method $class::install not exists");
}
call_user_func([$class, 'install'], config("plugin.$name.app.version"));
return self::SUCCESS;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class AppPluginUninstallCommand extends Command
{
protected static $defaultName = 'app-plugin:uninstall';
protected static $defaultDescription = 'App Plugin Uninstall';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Uninstall App Plugin $name");
$class = "\\plugin\\$name\\api\\Install";
if (!method_exists($class, 'uninstall')) {
throw new \RuntimeException("Method $class::uninstall not exists");
}
call_user_func([$class, 'uninstall'], config("plugin.$name.app.version"));
return self::SUCCESS;
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Webman\Console\Commands;
use Phar;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZipArchive;
class BuildBinCommand extends BuildPharCommand
{
protected static $defaultName = 'build:bin';
protected static $defaultDescription = 'build bin';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('version', InputArgument::OPTIONAL, 'PHP version');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->checkEnv();
$output->writeln('Phar packing...');
$version = $input->getArgument('version');
if (!$version) {
$version = (float)PHP_VERSION;
}
$version = $version >= 8.0 ? $version : 8.1;
$supportZip = class_exists(ZipArchive::class);
$microZipFileName = $supportZip ? "php$version.micro.sfx.zip" : "php$version.micro.sfx";
$pharFileName = config('plugin.webman.console.app.phar_filename', 'webman.phar');
$binFileName = config('plugin.webman.console.app.bin_filename', 'webman.bin');
$this->buildDir = config('plugin.webman.console.app.build_dir', base_path() . '/build');
$binFile = "$this->buildDir/$binFileName";
$pharFile = "$this->buildDir/$pharFileName";
$zipFile = "$this->buildDir/$microZipFileName";
$sfxFile = "$this->buildDir/php$version.micro.sfx";
// 打包
$command = new BuildPharCommand();
$command->execute($input, $output);
// 下载 micro.sfx.zip
if (!is_file($sfxFile) && !is_file($zipFile)) {
$domain = 'download.workerman.net';
$output->writeln("\r\nDownloading PHP$version ...");
if (extension_loaded('openssl')) {
$client = stream_socket_client("ssl://$domain:443");
} else {
$client = stream_socket_client("tcp://$domain:80");
}
fwrite($client, "GET /php/$microZipFileName HTTP/1.0\r\nAccept: text/html\r\nHost: $domain\r\nUser-Agent: webman/console\r\n\r\n");
$bodyLength = 0;
$bodyBuffer = '';
$lastPercent = 0;
while (true) {
$buffer = fread($client, 65535);
if ($buffer !== false) {
$bodyBuffer .= $buffer;
if (!$bodyLength && $pos = strpos($bodyBuffer, "\r\n\r\n")) {
if (!preg_match('/Content-Length: (\d+)\r\n/', $bodyBuffer, $match)) {
$output->writeln("Download php$version.micro.sfx.zip failed");
return self::FAILURE;
}
$firstLine = substr($bodyBuffer, 9, strpos($bodyBuffer, "\r\n") - 9);
if (!preg_match('/200 /', $bodyBuffer)) {
$output->writeln("Download php$version.micro.sfx.zip failed, $firstLine");
return self::FAILURE;
}
$bodyLength = (int)$match[1];
$bodyBuffer = substr($bodyBuffer, $pos + 4);
}
}
$receiveLength = strlen($bodyBuffer);
$percent = ceil($receiveLength * 100 / $bodyLength);
if ($percent != $lastPercent) {
echo '[' . str_pad('', $percent, '=') . '>' . str_pad('', 100 - $percent) . "$percent%]";
echo $percent < 100 ? "\r" : "\n";
}
$lastPercent = $percent;
if ($bodyLength && $receiveLength >= $bodyLength) {
file_put_contents($zipFile, $bodyBuffer);
break;
}
if ($buffer === false || !is_resource($client) || feof($client)) {
$output->writeln("Fail donwload PHP$version ...");
return self::FAILURE;
}
}
} else {
$output->writeln("\r\nUse PHP$version ...");
}
// 解压
if (!is_file($sfxFile) && $supportZip) {
$zip = new ZipArchive;
$zip->open($zipFile, ZipArchive::CHECKCONS);
$zip->extractTo($this->buildDir);
}
// 生成二进制文件
file_put_contents($binFile, file_get_contents($sfxFile));
file_put_contents($binFile, file_get_contents($pharFile), FILE_APPEND);
// 添加执行权限
chmod($binFile, 0755);
$output->writeln("\r\nSaved $binFileName to $binFile\r\nBuild Success!\r\n");
return self::SUCCESS;
}
}

View File

@@ -2,17 +2,37 @@
namespace Webman\Console\Commands;
use Phar;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Phar;
use RuntimeException;
class PharPackCommand extends Command
class BuildPharCommand extends Command
{
protected static $defaultName = 'phar:pack';
protected static $defaultName = 'build:phar';
protected static $defaultDescription = 'Can be easily packaged a project into phar files. Easy to distribute and use.';
protected $buildDir = '';
public function __construct(string $name = null)
{
parent::__construct($name);
$this->buildDir = config('plugin.webman.console.app.build_dir', base_path() . '/build');
}
/**
* @return void
* @deprecated 暂时保留 phar:pack 命令,下一个版本再取消
*/
protected function configure()
{
$this->setAliases([
'phar:pack',
]);
parent::configure();
}
/**
* @param InputInterface $input
* @param OutputInterface $output
@@ -21,28 +41,24 @@ class PharPackCommand extends Command
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->checkEnv();
$phar_file_output_dir = config('plugin.webman.console.app.phar_file_output_dir');
if (empty($phar_file_output_dir)) {
throw new RuntimeException('Please set the phar file output directory.');
}
if (!file_exists($phar_file_output_dir) && !is_dir($phar_file_output_dir)) {
if (!mkdir($phar_file_output_dir,0777,true)) {
if (!file_exists($this->buildDir) && !is_dir($this->buildDir)) {
if (!mkdir($this->buildDir,0777,true)) {
throw new RuntimeException("Failed to create phar file output directory. Please check the permission.");
}
}
$phar_filename = config('plugin.webman.console.app.phar_filename');
$phar_filename = config('plugin.webman.console.app.phar_filename', 'webman.phar');
if (empty($phar_filename)) {
throw new RuntimeException('Please set the phar filename.');
}
$phar_file = rtrim($phar_file_output_dir,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $phar_filename;
$phar_file = rtrim($this->buildDir,DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $phar_filename;
if (file_exists($phar_file)) {
unlink($phar_file);
}
$exclude_pattern = config('plugin.webman.console.app.exclude_pattern');
$exclude_pattern = config('plugin.webman.console.app.exclude_pattern','');
$phar = new Phar($phar_file,0,'webman');
$phar->startBuffering();
@@ -66,8 +82,29 @@ class PharPackCommand extends Command
$phar->buildFromDirectory(BASE_PATH,$exclude_pattern);
$exclude_files = config('plugin.webman.console.app.exclude_files');
$exclude_files = config('plugin.webman.console.app.exclude_files',[]);
// 打包生成的phar和bin文件是面向生产环境的所以以下这些命令没有任何意义执行的话甚至会出错需要排除在外。
$exclude_command_files = [
'AppPluginCreateCommand.php',
'BuildBinCommand.php',
'BuildPharCommand.php',
'MakeBootstrapCommand.php',
'MakeCommandCommand.php',
'MakeControllerCommand.php',
'MakeMiddlewareCommand.php',
'MakeModelCommand.php',
'PluginCreateCommand.php',
'PluginDisableCommand.php',
'PluginEnableCommand.php',
'PluginExportCommand.php',
'PluginInstallCommand.php',
'PluginUninstallCommand.php'
];
$exclude_command_files = array_map(function ($cmd_file) {
return 'vendor/webman/console/src/Commands/'.$cmd_file;
},$exclude_command_files);
$exclude_files = array_unique(array_merge($exclude_command_files,$exclude_files));
foreach ($exclude_files as $file) {
if($phar->offsetExists($file)){
$phar->delete($file);
@@ -94,16 +131,18 @@ __HALT_COMPILER();
/**
* @throws RuntimeException
*/
private function checkEnv(): void
public function checkEnv(): void
{
if (!class_exists(Phar::class, false)) {
throw new RuntimeException("The 'phar' extension is required for build phar package");
}
if (ini_get('phar.readonly')) {
$command = static::$defaultName;
throw new RuntimeException(
"The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0 ./webman phar:pack'"
"The 'phar.readonly' is 'On', build phar must setting it 'Off' or exec with 'php -d phar.readonly=0 ./webman $command'"
);
}
}
}

View File

@@ -2,6 +2,7 @@
namespace Webman\Console\Commands;
use Doctrine\Inflector\InflectorFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -21,6 +22,7 @@ class MakeModelCommand extends Command
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'Model name');
$this->addArgument('type', InputArgument::OPTIONAL, 'Type');
}
/**
@@ -32,6 +34,7 @@ class MakeModelCommand extends Command
{
$name = $input->getArgument('name');
$name = Util::nameToClass($name);
$type = $input->getArgument('type');
$output->writeln("Make model $name");
if (!($pos = strrpos($name, '/'))) {
$name = ucfirst($name);
@@ -58,15 +61,18 @@ class MakeModelCommand extends Command
$file = app_path() . "/$path/$name.php";
$namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path);
}
$database = config('database');
if (isset($database['default']) && strpos($database['default'], 'plugin.') === 0) {
$database = false;
if (!$type) {
$database = config('database');
if (isset($database['default']) && strpos($database['default'], 'plugin.') === 0) {
$database = false;
}
$thinkorm = config('thinkorm');
if (isset($thinkorm['default']) && strpos($thinkorm['default'], 'plugin.') === 0) {
$thinkorm = false;
}
$type = !$database && $thinkorm ? 'tp' : 'laravel';
}
$thinkorm = config('thinkorm');
if (isset($thinkorm['default']) && strpos($thinkorm['default'], 'plugin.') === 0) {
$thinkorm = false;
}
if (!$database && $thinkorm) {
if ($type == 'tp') {
$this->createTpModel($name, $namespace, $file);
} else {
$this->createModel($name, $namespace, $file);
@@ -94,8 +100,10 @@ class MakeModelCommand extends Command
try {
$prefix = config('database.connections.mysql.prefix') ?? '';
$database = config('database.connections.mysql.database');
if (\support\Db::select("show tables like '{$prefix}{$table}s'")) {
$table = "{$prefix}{$table}s";
$inflector = InflectorFactory::create()->build();
$table_plura = $inflector->pluralize($inflector->tableize($class));
if (\support\Db::select("show tables like '{$prefix}{$table_plura}'")) {
$table = "{$prefix}{$table_plura}";
} else if (\support\Db::select("show tables like '{$prefix}{$table}'")) {
$table_val = "'$table'";
$table = "{$prefix}{$table}";

View File

@@ -8,9 +8,9 @@ class Install
/**
* @var array
*/
protected static $pathRelation = array (
'config/plugin/webman/console' => 'config/plugin/webman/console',
);
protected static $pathRelation = [
'config/plugin/webman/console' => 'config/plugin/webman/console',
];
/**
* Install
@@ -18,8 +18,13 @@ class Install
*/
public static function install()
{
copy(__DIR__ . "/webman", base_path()."/webman");
chmod(base_path()."/webman", 0755);
$dest = base_path() . "/webman";
if (is_dir($dest)) {
echo "Installation failed, please remove directory $dest\n";
return;
}
copy(__DIR__ . "/webman", $dest);
chmod(base_path() . "/webman", 0755);
static::installByRelation();
}
@@ -33,7 +38,6 @@ class Install
if (is_file(base_path()."/webman")) {
unlink(base_path() . "/webman");
}
self::uninstallByRelation();
}
@@ -50,7 +54,6 @@ class Install
mkdir($parent_dir, 0777, true);
}
}
//symlink(__DIR__ . "/$source", base_path()."/$dest");
copy_dir(__DIR__ . "/$source", base_path()."/$dest");
}
}
@@ -66,9 +69,6 @@ class Install
if (!is_dir($path) && !is_file($path)) {
continue;
}
/*if (is_link($path) {
unlink($path);
}*/
remove_dir($path);
}
}

View File

@@ -16,8 +16,10 @@ class Util
public static function classToName($class)
{
$inflector = InflectorFactory::create()->build();
return $inflector->pluralize($inflector->tableize($class));
$class = lcfirst($class);
return preg_replace_callback(['/([A-Z])/'], function ($matches) {
return '_' . strtolower($matches[1]);
}, $class);
}
public static function nameToClass($class)
@@ -59,6 +61,6 @@ class Util
}
}
$realname = implode(DIRECTORY_SEPARATOR, $realname);
return $return_full_path ? realpath($base_path . DIRECTORY_SEPARATOR . $realname) : $realname;
return $return_full_path ? get_realpath($base_path . DIRECTORY_SEPARATOR . $realname) : $realname;
}
}

View File

@@ -1,18 +1,20 @@
<?php
return [
'enable' => true,
'enable' => true,
'phar_file_output_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build',
'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build',
'phar_filename' => 'webman.phar',
'phar_filename' => 'webman.phar',
'bin_filename' => 'webman.bin',
'signature_algorithm'=> Phar::SHA256, //set the signature algorithm for a phar and apply it. The signature algorithm must be one of Phar::MD5, Phar::SHA1, Phar::SHA256, Phar::SHA512, or Phar::OPENSSL.
'private_key_file' => '', // The file path for certificate or OpenSSL private key file.
//'exclude_pattern' => '#^(?!.*(config/plugin/webman/console/app.php|webman/console/src/Commands/(PharPackCommand.php|ReloadCommand.php)|LICENSE|composer.json|.github|.idea|doc|docs|.git|.setting|runtime|test|test_old|tests|Tests|vendor-bin|.md))(.*)$#',
'exclude_pattern' => '#^(?!.*(composer.json|/.github/|/.idea/|/.git/|/.setting/|/runtime/|/vendor-bin/|/build/|/vendor/webman/admin/))(.*)$#',
'exclude_files' => [
'.env', 'LICENSE', 'composer.json', 'composer.lock','start.php'
'.env', 'LICENSE', 'composer.json', 'composer.lock', 'start.php', 'webman.phar', 'webman.bin'
]
];

View File

@@ -4,6 +4,7 @@
use Webman\Config;
use Webman\Console\Command;
use Webman\Console\Util;
use support\Container;
require_once __DIR__ . '/vendor/autoload.php';
@@ -36,7 +37,7 @@ foreach (config('plugin', []) as $firm => $projects) {
continue;
}
foreach ($project['command'] ?? [] as $command) {
$cli->add(new $command);
$cli->add(Container::get($command));
}
}
}

View File

@@ -7,6 +7,7 @@ use support\Log;
class BootStrap implements \Webman\Bootstrap
{
/**
* @var array
*/
@@ -19,17 +20,30 @@ class BootStrap implements \Webman\Bootstrap
public static function start($worker)
{
static::getEvents([config()]);
}
protected static function convertCallable($callback)
{
if (\is_array($callback)) {
$callback = \array_values($callback);
if (isset($callback[1]) && \is_string($callback[0]) && \class_exists($callback[0])) {
$callback = [Container::get($callback[0]), $callback[1]];
foreach (static::$events as $name => $events) {
// 支持排序1 2 3 ... 9 a b c...z
ksort($events, SORT_NATURAL);
foreach ($events as $callbacks) {
foreach ($callbacks as $callback) {
Event::on($name, $callback);
}
}
}
return $callback;
}
/**
* @param $callbacks
* @return array|mixed
*/
protected static function convertCallable($callbacks)
{
if (\is_array($callbacks)) {
$callback = \array_values($callbacks);
if (isset($callback[1]) && \is_string($callback[0]) && \class_exists($callback[0])) {
return [Container::get($callback[0]), $callback[1]];
}
}
return $callbacks;
}
/**
@@ -43,12 +57,10 @@ class BootStrap implements \Webman\Bootstrap
continue;
}
if (isset($config['event']) && is_array($config['event']) && !isset($config['event']['app']['enable'])) {
$events = [];
foreach ($config['event'] as $event_name => $callbacks) {
$callbacks = static::convertCallable($callbacks);
if (is_callable($callbacks)) {
$events[$event_name] = [$callbacks];
Event::on($event_name, $callbacks);
static::$events[$event_name][] = [$callbacks];
continue;
}
if (!is_array($callbacks)) {
@@ -57,11 +69,11 @@ class BootStrap implements \Webman\Bootstrap
Log::error($msg);
continue;
}
foreach ($callbacks as $callback) {
ksort($callbacks, SORT_NATURAL);
foreach ($callbacks as $id => $callback) {
$callback = static::convertCallable($callback);
if (is_callable($callback)) {
$events[$event_name][] = $callback;
Event::on($event_name, $callback);
static::$events[$event_name][$id][] = $callback;
continue;
}
$msg = "Events: $event_name => " . var_export($callback, true) . " is not callable\n";
@@ -69,7 +81,6 @@ class BootStrap implements \Webman\Bootstrap
Log::error($msg);
}
}
static::$events = array_merge_recursive(static::$events, $events);
unset($config['event']);
}
static::getEvents($config);

View File

@@ -63,9 +63,8 @@ class Event
* @param bool $halt
* @return array|null|mixed
*/
public static function emit($event_name, $data, $halt = false)
public static function emit($event_name, $data, bool $halt = false)
{
$success_count = 0;
$listeners = static::getListeners($event_name);
$responses = [];
foreach ($listeners as $listener) {
@@ -91,6 +90,29 @@ class Event
}
return $halt ? null : $responses;
}
/**
* @param mixed $event_name
* @param mixed $data
* @param bool $halt
* @return array|null|mixed
*/
public static function dispatch($event_name, $data, bool $halt = false)
{
$listeners = static::getListeners($event_name);
$responses = [];
foreach ($listeners as $listener) {
$response = $listener($data, $event_name);
$responses[] = $response;
if ($halt && !is_null($response)) {
return $response;
}
if ($response === false) {
break;
}
}
return $halt ? null : $responses;
}
/**
* @return array
@@ -136,4 +158,4 @@ class Event
{
return !empty(static::getListeners($event_name));
}
}
}

View File

@@ -3,7 +3,7 @@
"type": "library",
"license": "MIT",
"require": {
"workerman/gateway-worker": "^3.0"
"workerman/gateway-worker": ">=3.0"
},
"autoload": {
"psr-4": {

View File

@@ -3,6 +3,11 @@ namespace Webman\GatewayWorker;
class Gateway extends \GatewayWorker\Gateway
{
/**
* @var string
*/
protected $_autoloadRootPath = '';
public function __construct($config)
{
foreach ($config as $key => $value)

View File

@@ -5,6 +5,8 @@ namespace Webman\ThinkCache;
use Webman\Bootstrap;
use Workerman\Timer;
use think\facade\Cache;
use think\Container;
use think\DbManager;
class ThinkCache implements Bootstrap
{
@@ -20,5 +22,10 @@ class ThinkCache implements Bootstrap
Cache::get('ping');
});
}
if (class_exists(DbManager::class)) {
$manager_instance = Container::getInstance()->make(DbManager::class);
$manager_instance->setCache(Cache::store());
}
}
}
}

View File

@@ -4,7 +4,7 @@
"license": "MIT",
"require": {
"workerman/webman-framework": "^1.2.1",
"topthink/think-orm": "^2.0.53"
"topthink/think-orm": "^2.0.53 || ^3.0.0"
},
"autoload": {
"psr-4": {

View File

@@ -29,10 +29,15 @@ class ThinkOrm implements Bootstrap
$manager_instance = $property->getValue();
}
Timer::add(55, function () use ($manager_instance) {
$reflect = new \ReflectionClass($manager_instance);
$property = $reflect->getProperty('instance');
$property->setAccessible(true);
$instances = $property->getValue($manager_instance);
$instances = [];
if (method_exists($manager_instance, 'getInstance')) {
$instances = $manager_instance->getInstance();
} else {
$reflect = new \ReflectionClass($manager_instance);
$property = $reflect->getProperty('instance');
$property->setAccessible(true);
$instances = $property->getValue($manager_instance);
}
foreach ($instances as $connection) {
/* @var \think\db\connector\Mysql $connection */
if ($connection->getConfig('type') == 'mysql') {

View File

@@ -82,7 +82,7 @@ namespace think\facade {
* @throws \think\db\exception\BindParamException
* @throws \PDOException
*/
public function query(string $sql, array $bind = []): array
public static function query(string $sql, array $bind = []): array
{
/** @var \think\Db $instance */
return $instance->query($sql,$bind);
@@ -108,7 +108,7 @@ namespace think\facade {
* @throws \think\db\exception\BindParamException
* @throws \PDOException
*/
public function execute(string $sql, array $bind = []): int
public static function execute(string $sql, array $bind = []): int
{
/** @var \think\Db $instance */
return $instance->execute($sql,$bind);