dyjh 6 rokov pred
commit
10c579d34e
100 zmenil súbory, kde vykonal 13085 pridanie a 0 odobranie
  1. BIN
      .DS_Store
  2. 33 0
      .env.example
  3. 3 0
      .gitattributes
  4. 16 0
      .gitignore
  5. 7 0
      apidoc.json
  6. 40 0
      app/Console/Kernel.php
  7. 65 0
      app/Exceptions/Handler.php
  8. 7 0
      app/Furniture.php
  9. 125 0
      app/Helper/AttachmentHelper.php
  10. 105 0
      app/Helper/Grafika/Color.php
  11. 105 0
      app/Helper/Grafika/DrawingObject/CubicBezier.php
  12. 128 0
      app/Helper/Grafika/DrawingObject/Ellipse.php
  13. 86 0
      app/Helper/Grafika/DrawingObject/Line.php
  14. 116 0
      app/Helper/Grafika/DrawingObject/Polygon.php
  15. 89 0
      app/Helper/Grafika/DrawingObject/QuadraticBezier.php
  16. 126 0
      app/Helper/Grafika/DrawingObject/Rectangle.php
  17. 17 0
      app/Helper/Grafika/DrawingObjectInterface.php
  18. 255 0
      app/Helper/Grafika/EditorInterface.php
  19. 17 0
      app/Helper/Grafika/FilterInterface.php
  20. 412 0
      app/Helper/Grafika/Gd/DrawingObject/CubicBezier.php
  21. 42 0
      app/Helper/Grafika/Gd/DrawingObject/Ellipse.php
  22. 36 0
      app/Helper/Grafika/Gd/DrawingObject/Line.php
  23. 64 0
      app/Helper/Grafika/Gd/DrawingObject/Polygon.php
  24. 208 0
      app/Helper/Grafika/Gd/DrawingObject/QuadraticBezier.php
  25. 36 0
      app/Helper/Grafika/Gd/DrawingObject/Rectangle.php
  26. 1179 0
      app/Helper/Grafika/Gd/Editor.php
  27. 40 0
      app/Helper/Grafika/Gd/Filter/Blur.php
  28. 39 0
      app/Helper/Grafika/Gd/Filter/Brightness.php
  29. 50 0
      app/Helper/Grafika/Gd/Filter/Colorize.php
  30. 38 0
      app/Helper/Grafika/Gd/Filter/Contrast.php
  31. 190 0
      app/Helper/Grafika/Gd/Filter/Dither.php
  32. 38 0
      app/Helper/Grafika/Gd/Filter/Gamma.php
  33. 23 0
      app/Helper/Grafika/Gd/Filter/Grayscale.php
  34. 24 0
      app/Helper/Grafika/Gd/Filter/Invert.php
  35. 38 0
      app/Helper/Grafika/Gd/Filter/Pixelate.php
  36. 49 0
      app/Helper/Grafika/Gd/Filter/Sharpen.php
  37. 128 0
      app/Helper/Grafika/Gd/Filter/Sobel.php
  38. 123 0
      app/Helper/Grafika/Gd/Helper/GifByteStream.php
  39. 605 0
      app/Helper/Grafika/Gd/Helper/GifHelper.php
  40. 457 0
      app/Helper/Grafika/Gd/Image.php
  41. 71 0
      app/Helper/Grafika/Gd/ImageHash/AverageHash.php
  42. 73 0
      app/Helper/Grafika/Gd/ImageHash/DifferenceHash.php
  43. 394 0
      app/Helper/Grafika/Grafika.php
  44. 80 0
      app/Helper/Grafika/ImageInterface.php
  45. 21 0
      app/Helper/Grafika/ImageType.php
  46. 51 0
      app/Helper/Grafika/Imagick/DrawingObject/CubicBezier.php
  47. 40 0
      app/Helper/Grafika/Imagick/DrawingObject/Ellipse.php
  48. 41 0
      app/Helper/Grafika/Imagick/DrawingObject/Line.php
  49. 52 0
      app/Helper/Grafika/Imagick/DrawingObject/Polygon.php
  50. 55 0
      app/Helper/Grafika/Imagick/DrawingObject/QuadraticBezier.php
  51. 46 0
      app/Helper/Grafika/Imagick/DrawingObject/Rectangle.php
  52. 827 0
      app/Helper/Grafika/Imagick/Editor.php
  53. 37 0
      app/Helper/Grafika/Imagick/Filter/Blur.php
  54. 39 0
      app/Helper/Grafika/Imagick/Filter/Brightness.php
  55. 67 0
      app/Helper/Grafika/Imagick/Filter/Colorize.php
  56. 38 0
      app/Helper/Grafika/Imagick/Filter/Contrast.php
  57. 168 0
      app/Helper/Grafika/Imagick/Filter/Dither.php
  58. 38 0
      app/Helper/Grafika/Imagick/Filter/Gamma.php
  59. 23 0
      app/Helper/Grafika/Imagick/Filter/Grayscale.php
  60. 24 0
      app/Helper/Grafika/Imagick/Filter/Invert.php
  61. 42 0
      app/Helper/Grafika/Imagick/Filter/Pixelate.php
  62. 37 0
      app/Helper/Grafika/Imagick/Filter/Sharpen.php
  63. 142 0
      app/Helper/Grafika/Imagick/Filter/Sobel.php
  64. 269 0
      app/Helper/Grafika/Imagick/Image.php
  65. 37 0
      app/Helper/Grafika/Imagick/ImageHash/AverageHash.php
  66. 67 0
      app/Helper/Grafika/Imagick/ImageHash/DifferenceHash.php
  67. 146 0
      app/Helper/Grafika/Position.php
  68. 71 0
      app/Helper/LogHelper.php
  69. 44 0
      app/Helper/SmsHelper.php
  70. 33 0
      app/Helper/constants.inc.php
  71. 851 0
      app/Helper/functions.inc.php
  72. 20 0
      app/Helper/wxbdc/ErrorCode.php
  73. 108 0
      app/Helper/wxbdc/Prpcrypt.php
  74. 69 0
      app/Helper/wxbdc/WXBizDataCrypt.php
  75. 257 0
      app/Http/Controllers/Admin/Album/AgentController.php
  76. 146 0
      app/Http/Controllers/Admin/Album/BannerController.php
  77. 164 0
      app/Http/Controllers/Admin/Album/CatController.php
  78. 151 0
      app/Http/Controllers/Admin/Album/CommentsController.php
  79. 35 0
      app/Http/Controllers/Admin/Album/IndexController.php
  80. 141 0
      app/Http/Controllers/Admin/Album/Info/CatController.php
  81. 154 0
      app/Http/Controllers/Admin/Album/InformationController.php
  82. 181 0
      app/Http/Controllers/Admin/Album/ManufacturerController.php
  83. 186 0
      app/Http/Controllers/Admin/Album/NavController.php
  84. 147 0
      app/Http/Controllers/Admin/Album/NewsController.php
  85. 309 0
      app/Http/Controllers/Admin/Album/OrderController.php
  86. 75 0
      app/Http/Controllers/Admin/Album/PosterController.php
  87. 159 0
      app/Http/Controllers/Admin/Album/Product/AttrController.php
  88. 149 0
      app/Http/Controllers/Admin/Album/Product/PriceController.php
  89. 141 0
      app/Http/Controllers/Admin/Album/Product/StyleController.php
  90. 219 0
      app/Http/Controllers/Admin/Album/ProductController.php
  91. 141 0
      app/Http/Controllers/Admin/Album/ReviewController.php
  92. 171 0
      app/Http/Controllers/Admin/Album/UserController.php
  93. 264 0
      app/Http/Controllers/Admin/Album/Xyx/UserController.php
  94. 32 0
      app/Http/Controllers/Admin/Auth/ForgotPasswordController.php
  95. 131 0
      app/Http/Controllers/Admin/Auth/LoginController.php
  96. 71 0
      app/Http/Controllers/Admin/Auth/RegisterController.php
  97. 86 0
      app/Http/Controllers/Admin/Auth/ResetPasswordController.php
  98. 69 0
      app/Http/Controllers/Admin/Base/ActionlogController.php
  99. 168 0
      app/Http/Controllers/Admin/Base/AttachmentController.php
  100. 58 0
      app/Http/Controllers/Admin/Base/ClassController.php

BIN
.DS_Store


+ 33 - 0
.env.example

xqd
@@ -0,0 +1,33 @@
+APP_ENV=local
+APP_KEY=
+APP_DEBUG=true
+APP_LOG_LEVEL=debug
+APP_URL=http://localhost
+CDN_URL=http://localhost
+
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=homestead
+DB_USERNAME=homestead
+DB_PASSWORD=secret
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+SESSION_DRIVER=file
+QUEUE_DRIVER=sync
+
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+
+MAIL_DRIVER=smtp
+MAIL_HOST=smtp.mailtrap.io
+MAIL_PORT=2525
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+
+PUSHER_APP_ID=
+PUSHER_APP_KEY=
+PUSHER_APP_SECRET=

+ 3 - 0
.gitattributes

xqd
@@ -0,0 +1,3 @@
+* text=auto
+*.css linguist-vendored
+*.scss linguist-vendored

+ 16 - 0
.gitignore

xqd
@@ -0,0 +1,16 @@
+/node_modules
+/public/storage
+/public/hot
+/storage/*.key
+/vendor
+/.idea
+Homestead.json
+Homestead.yaml
+.env
+.phpstorm.meta.php
+_ide_helper*
+*.lock
+/storage/logs
+/public/upload
+/public/download
+/public/base/poster/download

+ 7 - 0
apidoc.json

xqd
@@ -0,0 +1,7 @@
+{
+  "name": "模板1",
+  "version": "0.1.0",
+  "description": "模板1接口",
+  "title": "MB",
+  "url" : "https://t6.9026.com"
+}

+ 40 - 0
app/Console/Kernel.php

xqd
@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Console;
+
+use Illuminate\Console\Scheduling\Schedule;
+use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
+
+class Kernel extends ConsoleKernel
+{
+    /**
+     * The Artisan commands provided by your application.
+     *
+     * @var array
+     */
+    protected $commands = [
+        //
+    ];
+
+    /**
+     * Define the application's command schedule.
+     *
+     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
+     * @return void
+     */
+    protected function schedule(Schedule $schedule)
+    {
+        // $schedule->command('inspire')
+        //          ->hourly();
+    }
+
+    /**
+     * Register the Closure based commands for the application.
+     *
+     * @return void
+     */
+    protected function commands()
+    {
+        require base_path('routes/console.php');
+    }
+}

+ 65 - 0
app/Exceptions/Handler.php

xqd
@@ -0,0 +1,65 @@
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+use Illuminate\Auth\AuthenticationException;
+use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+
+class Handler extends ExceptionHandler
+{
+    /**
+     * A list of the exception types that should not be reported.
+     *
+     * @var array
+     */
+    protected $dontReport = [
+        \Illuminate\Auth\AuthenticationException::class,
+        \Illuminate\Auth\Access\AuthorizationException::class,
+        \Symfony\Component\HttpKernel\Exception\HttpException::class,
+        \Illuminate\Database\Eloquent\ModelNotFoundException::class,
+        \Illuminate\Session\TokenMismatchException::class,
+        \Illuminate\Validation\ValidationException::class,
+    ];
+
+    /**
+     * Report or log an exception.
+     *
+     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
+     *
+     * @param  \Exception  $exception
+     * @return void
+     */
+    public function report(Exception $exception)
+    {
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Exception  $exception
+     * @return \Illuminate\Http\Response
+     */
+    public function render($request, Exception $exception)
+    {
+        return parent::render($request, $exception);
+    }
+
+    /**
+     * Convert an authentication exception into an unauthenticated response.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Illuminate\Auth\AuthenticationException  $exception
+     * @return \Illuminate\Http\Response
+     */
+    protected function unauthenticated($request, AuthenticationException $exception)
+    {
+        if ($request->expectsJson()) {
+            return response()->json(['error' => 'Unauthenticated.'], 401);
+        }
+
+        return redirect()->guest(route('login'));
+    }
+}

+ 7 - 0
app/Furniture.php

xqd
@@ -0,0 +1,7 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: 思维定制
+ * Date: 2019/3/20
+ * Time: 11:20
+ */

+ 125 - 0
app/Helper/AttachmentHelper.php

xqd
@@ -0,0 +1,125 @@
+<?php
+namespace App\Helper;
+
+use Illuminate\Http\Request;
+use Illuminate\Http\UploadedFile;
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
+use App\Services\Base\ErrorCode;
+use App\Models\BaseAttachmentModel;
+
+trait AttachmentHelper
+{
+    /**
+     * 上传附件
+     *
+     * @param Request $request  laravel's http request
+     * @param string|array $key 文件key
+     * @param string $tag       文件tag
+     * @param int $size         文件size限制,默认2M
+     * @param array $mimeType   文件mime类型限制,默认不限
+     * @return array|string|int 返回:md5字串|ErrorCode或[md5字串|ErrorCode]
+     */
+    public function uploadAttachment(Request $request, $key, $tag = 'files', $size = 10 * 1024 * 1024, array $mimeType = []) {
+        if ($request->hasFile($key)) {
+            $rel_path = '/upload/' . $tag . '/' . date('Ymd');
+            $path = public_path() . $rel_path;
+            if (!file_exists($path)) {
+                if (!@mkdir($path, 0755, true)) {
+                    return ErrorCode::ATTACHMENT_MKDIR_FAILED;
+                }
+            }
+
+            $files = $request->file($key);
+            if ($files === null) {
+                return ErrorCode::ATTACHMENT_UPLOAD_INVALID;
+            }
+            if ($files instanceof UploadedFile) {
+                $files = [$files];
+            }
+
+            $result = [];
+            foreach ($files as $idx => $file) {
+                if (!$file->isValid()) {
+                    $result[$idx] = ErrorCode::ATTACHMENT_UPLOAD_INVALID;
+                    continue;
+                }
+
+                $fileSize = $file->getSize();
+                if ($fileSize > $size) {
+                    $result[$idx] = ErrorCode::ATTACHMENT_SIZE_EXCEEDED;
+                    continue;
+                }
+
+                $fileMimeType = $file->getMimeType();
+                \Log::info("fileMimeType:".$fileMimeType);
+                if (!empty($mimeType) && !in_array($fileMimeType, $mimeType)) {
+                    $result[$idx] = ErrorCode::ATTACHMENT_MIME_NOT_ALLOWED;
+                    continue;
+                }
+
+                $clientName = $file->getClientOriginalName();
+                $md5 = md5($clientName . time());
+                $md5_filename = $md5 . '.' . $file->getClientOriginalExtension();
+
+                try {
+                    $file->move($path, $md5_filename);
+
+                    $real_path = $path . '/' . $md5_filename;
+                    $url_path = $rel_path . '/' . $md5_filename;
+
+                    $attachment = new BaseAttachmentModel();
+                    $attachment->name = $clientName;
+                    $attachment->md5 = $md5;
+                    $attachment->path = $real_path;
+                    $attachment->url = $url_path;
+                    $attachment->size = $fileSize;
+                    $attachment->file_type = $fileMimeType;
+                    if ($attachment->save()) {
+                        $result['md5'] = $md5;
+                        $result['url'] = env('APP_URL').$url_path;
+                    } else {
+                        @unlink($real_path);
+                        $result[$idx] = ErrorCode::ATTACHMENT_SAVE_FAILED;
+                    }
+                } catch (FileException $e) {
+                    $result[$idx] = ErrorCode::ATTACHMENT_MOVE_FAILED;
+                }
+            }
+            if (count($result) == 1) {
+                return array_shift($result);
+            }
+            return $result;
+        } else {
+            return ErrorCode::ATTACHMENT_UPLOAD_INVALID;
+        }
+
+    }
+
+    /**
+     * 删除附件
+     *
+     * @param $md5 string 删除文件的md5码
+     * @return int 错误码or 0(成功)
+     */
+    public function deleteAttachment($md5) {
+        $attachment = Attachment::where(['md5' => $md5])->first();
+        if (!$attachment) {
+            return ErrorCode::ATTACHMENT_NOT_EXIST;
+        }
+        if (file_exists($attachment->path)) {
+            if (@unlink($attachment->path)) {
+                if ($attachment->delete()) {
+                    return 0;
+                } else {
+                    return ErrorCode::ATTACHMENT_RECORD_DELETE_FAILED;
+                }
+            } else {
+                return ErrorCode::ATTACHMENT_DELETE_FAILED;
+            }
+        } else {
+            return ErrorCode::ATTACHMENT_NOT_EXIST;
+        }
+
+    }
+
+}

+ 105 - 0
app/Helper/Grafika/Color.php

xqd
@@ -0,0 +1,105 @@
+<?php
+namespace Grafika;
+
+/**
+ * Holds the color information.
+ * @package Grafika
+ */
+class Color {
+
+    /**
+     * @var string Hex string: #FFFFFF
+     */
+    protected $hexString;
+
+    /**
+     * @var float Transparency value 0-1
+     */
+    protected $alpha;
+
+    /**
+     * Color constructor.
+     *
+     * @param string $hexString Hex string
+     * @param float $alpha Transparency value 0-1
+     */
+    public function __construct( $hexString = '', $alpha = 1.0 ){
+        $this->hexString = $hexString; // TODO: Validate hexstring
+        $this->alpha = $alpha;
+    }
+
+    /**
+     * Get RGB array
+     *
+     * @return array Contains array($r, $g, $b)
+     */
+    public function getRgb(){
+        return $this->hexToRgb( $this->hexString );
+    }
+
+    /**
+     * Get RGBA array
+     *
+     * @return array Contains array($r, $g, $b, $a)
+     */
+    public function getRgba(){
+        $rgba = $this->hexToRgb( $this->hexString );
+        $rgba[] = $this->alpha;
+        return $rgba;
+    }
+
+    /**
+     * Convert hex string to RGB
+     * @param string $hex Hex string. Possible values: #ffffff, #fff, fff
+     * @return array Contains (RGB) values red, green and blue
+     */
+    public function hexToRgb( $hex ) {
+        $hex = ltrim($hex, '#'); // Remove #
+
+        if(strlen($hex) == 3) {
+            $r = hexdec(substr($hex,0,1).substr($hex,0,1));
+            $g = hexdec(substr($hex,1,1).substr($hex,1,1));
+            $b = hexdec(substr($hex,2,1).substr($hex,2,1));
+        } else {
+            $r = hexdec(substr($hex,0,2));
+            $g = hexdec(substr($hex,2,2));
+            $b = hexdec(substr($hex,4,2));
+        }
+        return array($r, $g, $b); // Returns an array with the rgb values
+    }
+
+    /**
+     * Get hex string.
+     *
+     * @return string
+     */
+    public function getHexString() {
+        return $this->hexString;
+    }
+
+    /**
+     * Set hex string.
+     *
+     * @param string $hexString
+     */
+    public function setHexString($hexString) {
+        $this->hexString = $hexString;
+    }
+
+    /**
+     * Alpha value.
+     * @return float
+     */
+    public function getAlpha() {
+        return $this->alpha;
+    }
+
+    /**
+     * @param float $alpha
+     */
+    public function setAlpha($alpha) {
+        $this->alpha = $alpha;
+    }
+
+
+}

+ 105 - 0
app/Helper/Grafika/DrawingObject/CubicBezier.php

xqd
@@ -0,0 +1,105 @@
+<?php
+namespace Grafika\DrawingObject;
+
+use Grafika\Color;
+
+/**
+ * Base class
+ * @package Grafika
+ */
+abstract class CubicBezier
+{
+
+    /**
+     * Starting point. Array of X Y values.
+     * @var array
+     */
+    protected $point1;
+
+    /**
+     * Control point 1. Array of X Y values.
+     * @var array
+     */
+    protected $control1;
+
+    /**
+     * Control point 2. Array of X Y values.
+     * @var array
+     */
+    protected $control2;
+
+    /**
+     * End point. Array of X Y values.
+     * @var array
+     */
+    protected $point2;
+
+    /**
+     * Color of curve.
+     *
+     * @var Color
+     */
+    protected $color;
+
+    /**
+     * Creates a cubic bezier. Cubic bezier has 2 control points.
+     * @param array $point1 Array of X and Y value for start point.
+     * @param array $control1 Array of X and Y value for control point 1.
+     * @param array $control2 Array of X and Y value for control point 2.
+     * @param array $point2 Array of X and Y value for end point.
+     * @param Color|string $color Color of the curve. Accepts hex string or a Color object. Defaults to black.
+     */
+    public function __construct($point1, $control1, $control2, $point2, $color = '#000000')
+    {
+        if (is_string($color)) {
+            $color = new Color($color);
+        }
+        $this->point1 = $point1;
+        $this->control1 = $control1;
+        $this->control2 = $control2;
+        $this->point2 = $point2;
+        $this->color = $color;
+
+    }
+
+    /**
+     * @return array
+     */
+    public function getPoint1()
+    {
+        return $this->point1;
+    }
+
+    /**
+     * @return array
+     */
+    public function getControl1()
+    {
+        return $this->control1;
+    }
+
+    /**
+     * @return array
+     */
+    public function getControl2()
+    {
+        return $this->control2;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPoint2()
+    {
+        return $this->point2;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getColor()
+    {
+        return $this->color;
+    }
+
+}

+ 128 - 0
app/Helper/Grafika/DrawingObject/Ellipse.php

xqd
@@ -0,0 +1,128 @@
+<?php
+namespace Grafika\DrawingObject;
+
+use Grafika\Color;
+
+/**
+ * Base class
+ * @package Grafika
+ */
+abstract class Ellipse
+{
+
+    /**
+     * Image width in pixels
+     * @var int
+     */
+    protected $width;
+
+    /**
+     * Image height in pixels
+     * @var int
+     */
+    protected $height;
+
+    /**
+     * X,Y pos.
+     * @var array
+     */
+    protected $pos;
+
+    /**
+     * @var int
+     */
+    protected $borderSize;
+
+    /**
+     * @var Color
+     */
+    protected $fillColor;
+
+    /**
+     * @var Color
+     */
+    protected $borderColor;
+
+
+    /**
+     * Creates an ellipse.
+     *
+     * @param int $width Width of ellipse in pixels.
+     * @param int $height Height of ellipse in pixels.
+     * @param array $pos Array containing int X and int Y position of the ellipse from top left of the canvass.
+     * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
+     * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
+     * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
+     */
+    public function __construct(
+        $width,
+        $height,
+        array $pos,
+        $borderSize = 1,
+        $borderColor = '#000000',
+        $fillColor = '#FFFFFF'
+    ) {
+        if (is_string($borderColor)) {
+            $borderColor = new Color($borderColor);
+        }
+        if (is_string($fillColor)) {
+            $fillColor = new Color($fillColor);
+        }
+        $this->width = $width;
+        $this->height = $height;
+        $this->pos = $pos;
+        $this->borderSize = $borderSize;
+        $this->borderColor = $borderColor;
+        $this->fillColor = $fillColor;
+    }
+
+    /**
+     * @return int
+     */
+    public function getWidth()
+    {
+        return $this->width;
+    }
+
+    /**
+     * @return int
+     */
+    public function getHeight()
+    {
+        return $this->height;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPos()
+    {
+        return $this->pos;
+    }
+
+    /**
+     * @return int
+     */
+    public function getBorderSize()
+    {
+        return $this->borderSize;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getFillColor()
+    {
+        return $this->fillColor;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getBorderColor()
+    {
+        return $this->borderColor;
+    }
+
+
+}

+ 86 - 0
app/Helper/Grafika/DrawingObject/Line.php

xqd
@@ -0,0 +1,86 @@
+<?php
+namespace Grafika\DrawingObject;
+
+use Grafika\Color;
+
+/**
+ * Base class
+ * @package Grafika
+ */
+abstract class Line
+{
+
+    /**
+     * X,Y pos 1.
+     * @var array
+     */
+    protected $point1;
+
+    /**
+     * X,Y pos 2.
+     * @var array
+     */
+    protected $point2;
+
+    /**
+     * @var int Thickness of line.
+     */
+    protected $thickness;
+
+    /**
+     * @var Color
+     */
+    protected $color;
+
+    /**
+     * Creates a line.
+     *
+     * @param array $point1 Array containing int X and int Y position of the starting point.
+     * @param array $point2 Array containing int X and int Y position of the starting point.
+     * @param int $thickness Thickness in pixel. Note: This is currently ignored in GD editor and falls back to 1.
+     * @param Color|string $color Color of the line. Defaults to black.
+     */
+    public function __construct(array $point1, array $point2, $thickness = 1, $color = '#000000')
+    {
+        if (is_string($color)) {
+            $color = new Color($color);
+        }
+        $this->point1 = $point1;
+        $this->point2 = $point2;
+        $this->thickness = $thickness;
+        $this->color = $color;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPoint1()
+    {
+        return $this->point1;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPoint2()
+    {
+        return $this->point2;
+    }
+
+    /**
+     * @return int
+     */
+    public function getThickness()
+    {
+        return $this->thickness;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getColor()
+    {
+        return $this->color;
+    }
+
+}

+ 116 - 0
app/Helper/Grafika/DrawingObject/Polygon.php

xqd
@@ -0,0 +1,116 @@
+<?php
+namespace Grafika\DrawingObject;
+
+use Grafika\Color;
+
+/**
+ * Base class
+ * @package Grafika
+ */
+abstract class Polygon
+{
+    /**
+     * Image width in pixels
+     * @var int
+     */
+    protected $width;
+
+    /**
+     * Image height in pixels
+     * @var int
+     */
+    protected $height;
+
+    /**
+     * Array of all X and Y positions. Must have at least three positions (x,y).
+     * @var array
+     */
+    protected $points;
+
+    /**
+     * @var int
+     */
+    protected $borderSize;
+
+    /**
+     * @var Color
+     */
+    protected $fillColor;
+
+    /**
+     * @var Color
+     */
+    protected $borderColor;
+
+    /**
+     * Creates a polygon.
+     *
+     * @param array $points Array of all X and Y positions. Must have at least three positions.
+     * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
+     * @param Color|string|bool $borderColor Border color. Defaults to black. Set to null for no color.
+     * @param Color|string|bool $fillColor Fill color. Defaults to white. Set to null for no color.
+     */
+    public function __construct($points = array(array(0,0), array(0,0), array(0,0)), $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF') {
+
+        if (is_string($borderColor)) {
+            $borderColor = new Color($borderColor);
+        }
+        if (is_string($fillColor)) {
+            $fillColor = new Color($fillColor);
+        }
+        $this->points = $points;
+        $this->borderSize = $borderSize;
+        $this->borderColor = $borderColor;
+        $this->fillColor = $fillColor;
+    }
+
+    /**
+     * @return int
+     */
+    public function getWidth()
+    {
+        return $this->width;
+    }
+
+    /**
+     * @return int
+     */
+    public function getHeight()
+    {
+        return $this->height;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPoints()
+    {
+        return $this->points;
+    }
+
+    /**
+     * @return int
+     */
+    public function getBorderSize()
+    {
+        return $this->borderSize;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getFillColor()
+    {
+        return $this->fillColor;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getBorderColor()
+    {
+        return $this->borderColor;
+    }
+
+
+}

+ 89 - 0
app/Helper/Grafika/DrawingObject/QuadraticBezier.php

xqd
@@ -0,0 +1,89 @@
+<?php
+namespace Grafika\DrawingObject;
+
+use Grafika\Color;
+
+/**
+ * Base class
+ * @package Grafika
+ */
+abstract class QuadraticBezier
+{
+
+    /**
+     * Starting point.
+     * @var array
+     */
+    protected $point1;
+
+    /**
+     * Control point.
+     * @var array
+     */
+    protected $control;
+
+    /**
+     * End point.
+     * @var array
+     */
+    protected $point2;
+
+    /**
+     * Color of curve.
+     *
+     * @var Color
+     */
+    protected $color;
+
+    /**
+     * Creates a quadratic bezier. Quadratic bezier has 1 control point.
+     *
+     * @param array $point1 Array of X and Y value for start point.
+     * @param array $control Array of X and Y value for control point.
+     * @param array $point2 Array of X and Y value for end point.
+     * @param Color|string $color Color of the curve. Accepts hex string or a Color object. Defaults to black.
+     */
+    public function __construct($point1, $control, $point2, $color = '#000000')
+    {
+        if (is_string($color)) {
+            $color = new Color($color);
+        }
+        $this->point1 = $point1;
+        $this->control = $control;
+        $this->point2 = $point2;
+        $this->color = $color;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPoint1()
+    {
+        return $this->point1;
+    }
+
+    /**
+     * @return array
+     */
+    public function getControl()
+    {
+        return $this->control;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPoint2()
+    {
+        return $this->point2;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getColor()
+    {
+        return $this->color;
+    }
+    
+}

+ 126 - 0
app/Helper/Grafika/DrawingObject/Rectangle.php

xqd
@@ -0,0 +1,126 @@
+<?php
+namespace Grafika\DrawingObject;
+
+use Grafika\Color;
+
+/**
+ * Base class
+ * @package Grafika
+ */
+abstract class Rectangle
+{
+    /**
+     * Image width in pixels
+     * @var int
+     */
+    protected $width;
+
+    /**
+     * Image height in pixels
+     * @var int
+     */
+    protected $height;
+
+    /**
+     * X and Y position in an array.
+     * @var array
+     */
+    protected $pos;
+
+    /**
+     * @var int
+     */
+    protected $borderSize;
+
+    /**
+     * @var Color
+     */
+    protected $fillColor;
+
+    /**
+     * @var Color
+     */
+    protected $borderColor;
+
+    /**
+     * Creates a rectangle.
+     *
+     * @param int $width Width of rectangle in pixels.
+     * @param int $height Height in pixels.
+     * @param array $pos Array of X and Y position. X is the distance in pixels from the left of the canvass to the left of the rectangle. Y is the distance from the top of the canvass to the top of the rectangle. Defaults to array(0,0).
+     * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
+     * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
+     * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
+     */
+    public function __construct(
+        $width,
+        $height,
+        $pos = array(0, 0),
+        $borderSize = 1,
+        $borderColor = '#000000',
+        $fillColor = '#FFFFFF'
+    ) {
+        if (is_string($borderColor)) {
+            $borderColor = new Color($borderColor);
+        }
+        if (is_string($fillColor)) {
+            $fillColor = new Color($fillColor);
+        }
+        $this->width = $width;
+        $this->height = $height;
+        $this->pos = $pos;
+        $this->borderSize = $borderSize;
+        $this->borderColor = $borderColor;
+        $this->fillColor = $fillColor;
+    }
+
+    /**
+     * @return int
+     */
+    public function getWidth()
+    {
+        return $this->width;
+    }
+
+    /**
+     * @return int
+     */
+    public function getHeight()
+    {
+        return $this->height;
+    }
+
+    /**
+     * @return array
+     */
+    public function getPos()
+    {
+        return $this->pos;
+    }
+
+    /**
+     * @return int
+     */
+    public function getBorderSize()
+    {
+        return $this->borderSize;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getFillColor()
+    {
+        return $this->fillColor;
+    }
+
+    /**
+     * @return Color
+     */
+    public function getBorderColor()
+    {
+        return $this->borderColor;
+    }
+
+
+}

+ 17 - 0
app/Helper/Grafika/DrawingObjectInterface.php

xqd
@@ -0,0 +1,17 @@
+<?php
+namespace Grafika;
+
+/**
+ * Interface DrawingObjectInterface
+ * @package Grafika
+ */
+interface DrawingObjectInterface {
+
+    /**
+     * @param ImageInterface $image
+     *
+     * @return ImageInterface
+     */
+    public function draw( $image );
+
+}

+ 255 - 0
app/Helper/Grafika/EditorInterface.php

xqd
@@ -0,0 +1,255 @@
+<?php
+namespace Grafika;
+
+/**
+ * Interface EditorInterface
+ * @package Grafika
+ */
+interface EditorInterface {
+
+    /**
+     * Apply a filter to the image. See Filters section for a list of available filters.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param FilterInterface $filter Instance implementing the FilterInterface.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function apply( &$image, $filter );
+
+    /**
+     * Blend two images together with the first image as the base and the second image on top. Supports several blend modes.
+     *
+     * @param ImageInterface $image1 The base image.
+     * @param ImageInterface $image2 The image placed on top of the base image.
+     * @param string $type The blend mode. Can be: normal, multiply, overlay or screen.
+     * @param float $opacity The opacity of $image2. Possible values 0.0 to 1.0 where 0.0 is fully transparent and 1.0 is fully opaque. Defaults to 1.0.
+     * @param string $position The position of $image2 on $image1. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to top-left.
+     * @param int $offsetX Number of pixels to add to the X position of $image2.
+     * @param int $offsetY Number of pixels to add to the Y position of $image2.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function blend(&$image1, $image2, $type='normal', $opacity = 1.0, $position = 'top-left', $offsetX = 0, $offsetY = 0 );
+
+    /**
+     * Compare two images and returns a hamming distance. A value of 0 indicates a likely similar picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is likely a different image.
+     *
+     * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image.
+     * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image.
+     *
+     * @return int Hamming distance. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
+     * @throws \Exception
+     */
+    public function compare( $image1, $image2 );
+
+    /**
+     * Crop the image to the given dimension and position.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param int $cropWidth Crop width in pixels.
+     * @param int $cropHeight Crop Height in pixels.
+     * @param string $position The crop position. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to center.
+     * @param int $offsetX Number of pixels to add to the X position of the crop.
+     * @param int $offsetY Number of pixels to add to the Y position of the crop.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function crop( &$image, $cropWidth, $cropHeight, $position = 'center', $offsetX = 0, $offsetY = 0 );
+
+    /**
+     * Draw a DrawingObject on the image. See Drawing Objects section.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param DrawingObjectInterface $drawingObject Instance of DrawingObject.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function draw( &$image, $drawingObject );
+
+    /**
+     * Compare if two images are equal. It will compare if the two images are of the same width and height. If the dimensions differ, it will return false. If the dimensions are equal, it will loop through each pixels. If one of the pixel don't match, it will return false. The pixels are compared using their RGB (Red, Green, Blue) values.
+     *
+     * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image.
+     * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image.
+     *
+     * @return bool True if equals false if not. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
+     * @throws \Exception
+     */
+    public function equal( $image1, $image2 );
+    
+    /**
+     * Fill entire image with color.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param Color $color An instance of Grafika\Color class.
+     * @param int $x X-coordinate of start point.
+     * @param int $y Y-coordinate of start point.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function fill( &$image, $color, $x = 0, $y = 0 );
+
+    /**
+     * Flatten if animated GIF. Do nothing otherwise.
+     *
+     * @param ImageInterface $image Instance of Image.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function flatten( &$image );
+
+    /**
+     * Flip an image.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param string $mode The type of flip: 'h' for horizontal flip or 'v' for vertical.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function flip( &$image, $mode);
+
+    /**
+     * Free the image clearing resources associated with it.
+     *
+     * @param ImageInterface $image Instance of Image.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function free( &$image );
+
+    /**
+     * Checks the PHP install if the editor is available.
+     *
+     * @return bool True if available false if not.
+     */
+    public function isAvailable();
+
+    /**
+     * Change the image opacity.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param float $opacity The opacity level where 1.0 is fully opaque and 0.0 is fully transparent.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function opacity( &$image, $opacity );
+
+    /**
+     * Open an image file and assign Image to first parameter. Grafika officially supports JPEG, PNG, GIF, and animated GIF. In theory, Grafika can open and edit other image formats as long as they are supported by GD and Imagick but it is currently untested.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param string $imageFile File system path to image file.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function open( &$image, $imageFile );
+    
+    /**
+     * Wrapper function for the resizeXXX family of functions. Resize an image to a given width, height and mode.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     * @param string $mode Resize mode. Possible values: "exact", "exactHeight", "exactWidth", "fill", "fit".
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function resize( &$image, $newWidth, $newHeight, $mode='fit' );
+
+    /**
+     * Resize image to exact dimensions ignoring aspect ratio. Useful if you want to force exact width and height.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function resizeExact( &$image, $newWidth, $newHeight );
+
+    /**
+     * Resize image to exact height. Width is auto calculated. Useful for creating row of images with the same height.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function resizeExactHeight( &$image, $newHeight );
+
+    /**
+     * Resize image to exact width. Height is auto calculated. Useful for creating column of images with the same width.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param int $newWidth Width in pixels.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function resizeExactWidth( &$image, $newWidth );
+
+    /**
+     * Resize image to fill all the space in the given dimension. Excess parts are cropped.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function resizeFill( &$image, $newWidth, $newHeight );
+
+    /**
+     * Resize an image to fit within the given width and height. The re-sized image will not exceed the given dimension. Useful if you want to preserve the aspect ratio.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Width in pixels.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function resizeFit( &$image, $newWidth, $newHeight );
+
+    /**
+     * Rotate an image counter-clockwise.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param int $angle The angle in degrees.
+     * @param Color|null $color The Color object containing the background color.
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function rotate( &$image, $angle, $color = null );
+
+    /**
+     * Save the image to an image format.
+     *
+     * @param ImageInterface $image Instance of Image. Saving the image to a different format will have NO effect on the Image instance.
+     * @param string $file File path where to save the image.
+     * @param null|string $type The image format to use. Can be null, "gif", "png", or "jpeg". If null, an appropriate format will be chosen based on the output file name in $file.
+     * @param null|string $quality Quality of image. Applies to JPEG only. Accepts number 0 - 100 where 0 is lowest and 100 is the highest quality. Or null for default. Default quality if null is 75.
+     * @param bool $interlace Set to true for progressive JPEG. Applies to JPEG only. Default false.
+     * @param int $permission Default permission when creating non-existing target directory. Default is 0755. Note: Its using PHP's octal notation so you must prepend numbers with zero (0).
+     *
+     * @return EditorInterface An instance of Editor.
+     */
+    public function save( $image, $file, $type = null, $quality = null, $interlace = false, $permission = 0755 );
+
+    /**
+     * Write text to image.
+     *
+     * @param ImageInterface $image Instance of Image.
+     * @param string $text The text to be written.
+     * @param int $size The font size. Defaults to 12.
+     * @param int $x The distance from the left edge of the image to the left of the text. Defaults to 0.
+     * @param int $y The distance from the top edge of the image to the baseline of the text. Defaults to 12 (equal to font size) so that the text is placed within the image.
+     * @param Color $color The Color object. Default text color is black.
+     * @param string $font Full path to font file. If blank, will default to Liberation Sans font.
+     * @param int $angle Angle of text from 0 - 359. Defaults to 0.
+     *
+     * @return EditorInterface An instance of Editor.
+     * @throws \Exception
+     */
+    public function text( &$image, $text, $size = 12, $x = 0, $y = 12, $color = null, $font = '', $angle = 0 );
+
+}

+ 17 - 0
app/Helper/Grafika/FilterInterface.php

xqd
@@ -0,0 +1,17 @@
+<?php
+namespace Grafika;
+
+/**
+ * Interface FilterInterface
+ * @package Grafika
+ */
+interface FilterInterface {
+
+    /**
+     * @param ImageInterface $image
+     *
+     * @return ImageInterface
+     */
+    public function apply( $image );
+
+}

+ 412 - 0
app/Helper/Grafika/Gd/DrawingObject/CubicBezier.php

xqd
@@ -0,0 +1,412 @@
+<?php
+namespace Grafika\Gd\DrawingObject;
+
+use Grafika\DrawingObject\CubicBezier as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Gd\Image;
+use Grafika\ImageInterface;
+
+/**
+ * Class CubicBezier
+ * @package Grafika
+ */
+class CubicBezier extends Base implements DrawingObjectInterface
+{
+
+    /**
+     * @param ImageInterface $image
+     * @return Image
+     */
+    public function draw($image)
+    {
+        // Localize vars
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+        $gd = $image->getCore();
+
+        list($x0, $y0) = $this->point1;
+        list($x1, $y1) = $this->control1;
+        list($x2, $y2) = $this->control2;
+        list($x3, $y3) = $this->point2;
+
+        $this->plot($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3);
+
+        $type = $image->getType();
+        $file = $image->getImageFile();
+        return new Image($gd, $file, $width, $height, $type); // Create new image with updated core
+    }
+
+    protected function plot($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
+    { /* plot any cubic Bezier curve */
+        $n = 0;
+        $i = 0;
+        $xc = $x0 + $x1 - $x2 - $x3;
+        $xa = $xc - 4 * ($x1 - $x2);
+        $xb = $x0 - $x1 - $x2 + $x3;
+        $xd = $xb + 4 * ($x1 + $x2);
+        $yc = $y0 + $y1 - $y2 - $y3;
+        $ya = $yc - 4 * ($y1 - $y2);
+        $yb = $y0 - $y1 - $y2 + $y3;
+        $yd = $yb + 4 * ($y1 + $y2);
+        $fx0 = $x0;
+        $fx1 = 0;
+        $fx2 = 0;
+        $fx3 = 0;
+        $fy0 = $y0;
+        $fy1 = 0;
+        $fy2 = 0;
+        $fy3 = 0;
+        $t1 = $xb * $xb - $xa * $xc;
+        $t2 = 0;
+        $t = array();
+        /* sub-divide curve at gradient sign changes */
+        if ($xa == 0) { /* horizontal */
+            if (abs($xc) < 2 * abs($xb)) {
+                $t[$n++] = $xc / (2.0 * $xb);
+            } /* one change */
+        } else {
+            if ($t1 > 0.0) { /* two changes */
+                $t2 = sqrt($t1);
+                $t1 = ($xb - $t2) / $xa;
+                if (abs($t1) < 1.0) {
+                    $t[$n++] = $t1;
+                }
+                $t1 = ($xb + $t2) / $xa;
+                if (abs($t1) < 1.0) {
+                    $t[$n++] = $t1;
+                }
+            }
+        }
+        $t1 = $yb * $yb - $ya * $yc;
+        if ($ya == 0) { /* vertical */
+            if (abs($yc) < 2 * abs($yb)) {
+                $t[$n++] = $yc / (2.0 * $yb);
+            } /* one change */
+        } else {
+            if ($t1 > 0.0) { /* two changes */
+                $t2 = sqrt($t1);
+                $t1 = ($yb - $t2) / $ya;
+                if (abs($t1) < 1.0) {
+                    $t[$n++] = $t1;
+                }
+                $t1 = ($yb + $t2) / $ya;
+                if (abs($t1) < 1.0) {
+                    $t[$n++] = $t1;
+                }
+            }
+        }
+        for ($i = 1; $i < $n; $i++) /* bubble sort of 4 points */ {
+            if (($t1 = $t[$i - 1]) > $t[$i]) {
+                $t[$i - 1] = $t[$i];
+                $t[$i] = $t1;
+                $i = 0;
+            }
+        }
+        $t1 = -1.0;
+        $t[$n] = 1.0; /* begin / end point */
+        for ($i = 0; $i <= $n; $i++) { /* plot each segment separately */
+            $t2 = $t[$i]; /* sub-divide at $t[$i-1], $t[$i] */
+            $fx1 = ($t1 * ($t1 * $xb - 2 * $xc) - $t2 * ($t1 * ($t1 * $xa - 2 * $xb) + $xc) + $xd) / 8 - $fx0;
+            $fy1 = ($t1 * ($t1 * $yb - 2 * $yc) - $t2 * ($t1 * ($t1 * $ya - 2 * $yb) + $yc) + $yd) / 8 - $fy0;
+            $fx2 = ($t2 * ($t2 * $xb - 2 * $xc) - $t1 * ($t2 * ($t2 * $xa - 2 * $xb) + $xc) + $xd) / 8 - $fx0;
+            $fy2 = ($t2 * ($t2 * $yb - 2 * $yc) - $t1 * ($t2 * ($t2 * $ya - 2 * $yb) + $yc) + $yd) / 8 - $fy0;
+            $fx0 -= $fx3 = ($t2 * ($t2 * (3 * $xb - $t2 * $xa) - 3 * $xc) + $xd) / 8;
+            $fy0 -= $fy3 = ($t2 * ($t2 * (3 * $yb - $t2 * $ya) - 3 * $yc) + $yd) / 8;
+            $x3 = floor($fx3 + 0.5);
+            $y3 = floor($fy3 + 0.5); /* scale bounds to int */
+            if ($fx0 != 0.0) {
+                $fx1 *= $fx0 = ($x0 - $x3) / $fx0;
+                $fx2 *= $fx0;
+            }
+            if ($fy0 != 0.0) {
+                $fy1 *= $fy0 = ($y0 - $y3) / $fy0;
+                $fy2 *= $fy0;
+            }
+            if ($x0 != $x3 || $y0 != $y3) /* segment $t1 - $t2 */ {
+                $this->plotCubicSegment($gd, $x0, $y0, $x0 + $fx1, $y0 + $fy1, $x0 + $fx2, $y0 + $fy2, $x3, $y3);
+            }
+            $x0 = $x3;
+            $y0 = $y3;
+            $fx0 = $fx3;
+            $fy0 = $fy3;
+            $t1 = $t2;
+        }
+    }
+
+    protected function plotCubicSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
+    { /* plot limited anti-aliased cubic Bezier segment */
+        $f = 0;
+        $fx = 0;
+        $fy = 0;
+        $leg = 1;
+        $sx = $x0 < $x3 ? 1 : -1;
+        $sy = $y0 < $y3 ? 1 : -1; /* step direction */
+
+        $xc = -abs($x0 + $x1 - $x2 - $x3);
+        $xa = $xc - 4 * $sx * ($x1 - $x2);
+        $xb = $sx * ($x0 - $x1 - $x2 + $x3);
+        $yc = -abs($y0 + $y1 - $y2 - $y3);
+        $ya = $yc - 4 * $sy * ($y1 - $y2);
+        $yb = $sy * ($y0 - $y1 - $y2 + $y3);
+
+        $ab = 0;
+        $ac = 0;
+        $bc = 0;
+        $ba = 0;
+        $xx = 0;
+        $xy = 0;
+        $yy = 0;
+        $dx = 0;
+        $dy = 0;
+        $ex = 0;
+        $px = 0;
+        $py = 0;
+        $ed = 0;
+        $ip = 0;
+        $EP = 0.01;
+        /* check for curve restrains */
+        /* slope P0-P1 == P2-P3 and (P0-P3 == P1-P2 or no slope change) */
+        assert(($x1 - $x0) * ($x2 - $x3) < $EP && (($x3 - $x0) * ($x1 - $x2) < $EP || $xb * $xb < $xa * $xc + $EP));
+        assert(($y1 - $y0) * ($y2 - $y3) < $EP && (($y3 - $y0) * ($y1 - $y2) < $EP || $yb * $yb < $ya * $yc + $EP));
+        if ($xa == 0 && $ya == 0) { /* quadratic Bezier */
+            $sx = floor((3 * $x1 - $x0 + 1) / 2);
+            $sy = floor((3 * $y1 - $y0 + 1) / 2); /* new midpoint */
+            $this->plotQuadSegment($gd, $x0, $y0, $sx, $sy, $x3, $y3);
+            return;
+        }
+        $x1 = ($x1 - $x0) * ($x1 - $x0) + ($y1 - $y0) * ($y1 - $y0) + 1; /* line lengths */
+        $x2 = ($x2 - $x3) * ($x2 - $x3) + ($y2 - $y3) * ($y2 - $y3) + 1;
+        do { /* loop over both ends */
+            $ab = $xa * $yb - $xb * $ya;
+            $ac = $xa * $yc - $xc * $ya;
+            $bc = $xb * $yc - $xc * $yb;
+            $ip = 4 * $ab * $bc - $ac * $ac; /* self intersection loop at all? */
+            $ex = $ab * ($ab + $ac - 3 * $bc) + $ac * $ac; /* P0 part of self-intersection loop? */
+            $f = $ex > 0 ? 1 : sqrt(1 + 1024 / $x1); /* calculate resolution */
+            $ab *= $f;
+            $ac *= $f;
+            $bc *= $f;
+            $ex *= $f * $f; /* increase resolution */
+            $xy = 9 * ($ab + $ac + $bc) / 8;
+            $ba = 8 * ($xa - $ya);/* init differences of 1st degree */
+            $dx = 27 * (8 * $ab * ($yb * $yb - $ya * $yc) + $ex * ($ya + 2 * $yb + $yc)) / 64 - $ya * $ya * ($xy - $ya);
+            $dy = 27 * (8 * $ab * ($xb * $xb - $xa * $xc) - $ex * ($xa + 2 * $xb + $xc)) / 64 - $xa * $xa * ($xy + $xa);
+            /* init differences of 2nd degree */
+            $xx = 3 * (3 * $ab * (3 * $yb * $yb - $ya * $ya - 2 * $ya * $yc) - $ya * (3 * $ac * ($ya + $yb) + $ya * $ba)) / 4;
+            $yy = 3 * (3 * $ab * (3 * $xb * $xb - $xa * $xa - 2 * $xa * $xc) - $xa * (3 * $ac * ($xa + $xb) + $xa * $ba)) / 4;
+            $xy = $xa * $ya * (6 * $ab + 6 * $ac - 3 * $bc + $ba);
+            $ac = $ya * $ya;
+            $ba = $xa * $xa;
+            $xy = 3 * ($xy + 9 * $f * ($ba * $yb * $yc - $xb * $xc * $ac) - 18 * $xb * $yb * $ab) / 8;
+            if ($ex < 0) { /* negate values if inside self-intersection loop */
+                $dx = -$dx;
+                $dy = -$dy;
+                $xx = -$xx;
+                $yy = -$yy;
+                $xy = -$xy;
+                $ac = -$ac;
+                $ba = -$ba;
+            } /* init differences of 3rd degree */
+            $ab = 6 * $ya * $ac;
+            $ac = -6 * $xa * $ac;
+            $bc = 6 * $ya * $ba;
+            $ba = -6 * $xa * $ba;
+            $dx += $xy;
+            $ex = $dx + $dy;
+            $dy += $xy; /* error of 1st step */
+            for ($fx = $fy = $f; $x0 != $x3 && $y0 != $y3;) {
+                $y1 = min($xy - $dx, $dy - $xy);
+                $ed = max($xy - $dx, $dy - $xy); /* approximate error distance */
+                $ed = $f * ($ed + 2 * $ed * $y1 * $y1 / (4 * $ed * $ed + $y1 * $y1));
+                $y1 = 255 * abs($ex - ($f - $fx + 1) * $dx - ($f - $fy + 1) * $dy + $f * $xy) / $ed;
+                if ($y1 < 256) {
+                    $this->setPixel($gd, $x0, $y0, $y1 / 255);
+                } /* plot curve */
+                $px = abs($ex - ($f - $fx + 1) * $dx + ($fy - 1) * $dy); /* pixel intensity x move */
+                $py = abs($ex + ($fx - 1) * $dx - ($f - $fy + 1) * $dy); /* pixel intensity y move */
+                $y2 = $y0;
+                do { /* move sub-steps of one pixel */
+                    if ($ip >= -$EP) /* intersection possible? -> check.. */ {
+                        if ($dx + $xx > $xy || $dy + $yy < $xy) {
+                            goto exits;
+                        }
+                    } /* two x or y steps */
+                    $y1 = 2 * $ex + $dx; /* save value for test of y step */
+                    if (2 * $ex + $dy > 0) { /* x sub-step */
+                        $fx--;
+                        $ex += $dx += $xx;
+                        $dy += $xy += $ac;
+                        $yy += $bc;
+                        $xx += $ab;
+                    } else {
+                        if ($y1 > 0) {
+                            goto exits;
+                        }
+                    } /* tiny nearly cusp */
+                    if ($y1 <= 0) { /* y sub-step */
+                        $fy--;
+                        $ex += $dy += $yy;
+                        $dx += $xy += $bc;
+                        $xx += $ac;
+                        $yy += $ba;
+                    }
+                } while ($fx > 0 && $fy > 0); /* pixel complete? */
+                if (2 * $fy <= $f) { /* x+ anti-aliasing pixel */
+                    if ($py < $ed) {
+                        $this->setPixel($gd, $x0 + $sx, $y0, $py / $ed);
+                    } /* plot curve */
+                    $y0 += $sy;
+                    $fy += $f; /* y step */
+                }
+                if (2 * $fx <= $f) { /* y+ anti-aliasing pixel */
+                    if ($px < $ed) {
+                        $this->setPixel($gd, $x0, $y2 + $sy, $px / $ed);
+                    } /* plot curve */
+                    $x0 += $sx;
+                    $fx += $f; /* x step */
+                }
+            }
+            break; /* finish curve by line */
+            exits:
+            if (2 * $ex < $dy && 2 * $fy <= $f + 2) { /* round x+ approximation pixel */
+                if ($py < $ed) {
+                    $this->setPixel($gd, $x0 + $sx, $y0, $py / $ed);
+                } /* plot curve */
+                $y0 += $sy;
+            }
+            if (2 * $ex > $dx && 2 * $fx <= $f + 2) { /* round y+ approximation pixel */
+                if ($px < $ed) {
+                    $this->setPixel($gd, $x0, $y2 + $sy, $px / $ed);
+                } /* plot curve */
+                $x0 += $sx;
+            }
+            $xx = $x0;
+            $x0 = $x3;
+            $x3 = $xx;
+            $sx = -$sx;
+            $xb = -$xb; /* swap legs */
+            $yy = $y0;
+            $y0 = $y3;
+            $y3 = $yy;
+            $sy = -$sy;
+            $yb = -$yb;
+            $x1 = $x2;
+        } while ($leg--); /* try other end */
+        $this->plotLine($gd, $x0, $y0, $x3, $y3); /* remaining part in case of cusp or crunode */
+    }
+
+    protected function plotQuadSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2)
+    { /* draw an limited anti-aliased quadratic Bezier segment */
+        $sx = $x2 - $x1;
+        $sy = $y2 - $y1;
+        $xx = $x0 - $x1;
+        $yy = $y0 - $y1;
+        $xy = $dx = $dy = $err = $ed = 0;
+        $cur = $xx * $sy - $yy * $sx; /* $curvature */
+        if ($sx * (int)$sx + $sy * (int)$sy > $xx * $xx + $yy * $yy) { /* begin with longer part */
+            $x2 = $x0;
+            $x0 = $sx + $x1;
+            $y2 = $y0;
+            $y0 = $sy + $y1;
+            $cur = -$cur; /* swap P0 P2 */
+        }
+        if ($cur != 0) { /* no straight line */
+            $xx += $sx;
+            $xx *= $sx = $x0 < $x2 ? 1 : -1; /* x step direction */
+            $yy += $sy;
+            $yy *= $sy = $y0 < $y2 ? 1 : -1; /* y step direction */
+            $xy = 2 * $xx * $yy;
+            $xx *= $xx;
+            $yy *= $yy; /* differences 2nd degree */
+            if ($cur * $sx * $sy < 0) { /* negat$ed $curvature? */
+                $xx = -$xx;
+                $yy = -$yy;
+                $xy = -$xy;
+                $cur = -$cur;
+            }
+            $dx = 4.0 * $sy * ($x1 - $x0) * $cur + $xx - $xy; /* differences 1st degree */
+            $dy = 4.0 * $sx * ($y0 - $y1) * $cur + $yy - $xy;
+            $xx += $xx;
+            $yy += $yy;
+            $err = $dx + $dy + $xy; /* $error 1st step */
+            do {
+                $cur = min($dx + $xy, -$xy - $dy);
+                $ed = max($dx + $xy, -$xy - $dy); /* approximate $error distance */
+                $ed += 2 * $ed * $cur * $cur / (4 * $ed * $ed + $cur * $cur);
+                $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy - $xy) / $ed); /* plot $curve */
+                if ($x0 == $x2 || $y0 == $y2) {
+                    break;
+                } /* $curve finish$ed */
+                $x1 = $x0;
+                $cur = $dx - $err;
+                $y1 = 2 * $err + $dy < 0;
+                if (2 * $err + $dx > 0) { /* x step */
+                    if ($err - $dy < $ed) {
+                        $this->setPixel($gd, $x0, $y0 + $sy, abs($err - $dy) / $ed);
+                    }
+                    $x0 += $sx;
+                    $dx -= $xy;
+                    $err += $dy += $yy;
+                }
+                if ($y1) { /* y step */
+                    if ($cur < $ed) {
+                        $this->setPixel($gd, $x1 + $sx, $y0, abs($cur) / $ed);
+                    }
+                    $y0 += $sy;
+                    $dy -= $xy;
+                    $err += $dx += $xx;
+                }
+            } while ($dy < $dx); /* gradient negates -> close curves */
+        }
+        $this->plotLine($gd, $x0, $y0, $x2, $y2); /* plot remaining needle to end */
+    }
+
+    protected function plotLine($gd, $x0, $y0, $x1, $y1)
+    { /* draw a black (0) anti-aliased line on white (255) background */
+        $dx = abs($x1 - $x0);
+        $sx = $x0 < $x1 ? 1 : -1;
+        $dy = -abs($y1 - $y0);
+        $sy = $y0 < $y1 ? 1 : -1;
+        $err = $dx + $dy;
+        $e2 = $x2 = 0; /* $error value e_xy */
+        $ed = $dx - $dy == 0 ? 1 : sqrt((float)$dx * $dx + (float)$dy * $dy);
+        for (; ;) { /* pixel loop */
+            $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy) / $ed);
+            $e2 = $err;
+            $x2 = $x0;
+            if (2 * $e2 + $dx >= 0) { /* x step */
+                if ($x0 == $x1) {
+                    break;
+                }
+                if ($e2 - $dy < $ed) {
+                    $this->setPixel($gd, $x0, $y0 + $sy, ($e2 - $dy) / $ed);
+                }
+                $err += $dy;
+                $x0 += $sx;
+            }
+            if (2 * $e2 + $dy <= 0) { /* y step */
+                if ($y0 == $y1) {
+                    break;
+                }
+                if ($dx - $e2 < $ed) {
+                    $this->setPixel($gd, $x2 + $sx, $y0, ($dx - $e2) / $ed);
+                }
+                $err += $dx;
+                $y0 += $sy;
+            }
+        }
+    }
+
+    /**
+     * @param resource $gd
+     * @param int $x
+     * @param int $y
+     * @param float $ar Alpha ratio
+     */
+    protected function setPixel($gd, $x, $y, $ar)
+    {
+        list($r, $g, $b) = $this->color->getRgb();
+        $c = imagecolorallocatealpha($gd, $r, $g, $b, 127 * $ar);
+        imagesetpixel($gd, $x, $y, $c);
+    }
+}

+ 42 - 0
app/Helper/Grafika/Gd/DrawingObject/Ellipse.php

xqd
@@ -0,0 +1,42 @@
+<?php
+namespace Grafika\Gd\DrawingObject;
+
+use Grafika\DrawingObject\Ellipse as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Gd\Editor;
+use Grafika\ImageInterface;
+
+/**
+ * Class Ellipse
+ * @package Grafika
+ */
+class Ellipse extends Base implements DrawingObjectInterface
+{
+
+    /**
+     * TODO: Anti-aliased curves
+     * @param ImageInterface $image
+     * @return ImageInterface
+     */
+    public function draw($image)
+    {
+
+        list($x, $y) = $this->pos;
+        $left = $x + $this->width / 2;
+        $top = $y + $this->height / 2;
+
+        if( null !== $this->fillColor ){
+            list($r, $g, $b, $alpha) = $this->fillColor->getRgba();
+            $fillColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha));
+            imagefilledellipse($image->getCore(), $left, $top, $this->width, $this->height, $fillColorResource);
+        }
+        // Create borders. It will be placed on top of the filled ellipse (if present)
+        if ( 0 < $this->getBorderSize() and null !== $this->borderColor) { // With border > 0 AND borderColor !== null
+            list($r, $g, $b, $alpha) = $this->borderColor->getRgba();
+            $borderColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha));
+            imageellipse($image->getCore(), $left, $top, $this->width, $this->height, $borderColorResource);
+        }
+
+        return $image;
+    }
+}

+ 36 - 0
app/Helper/Grafika/Gd/DrawingObject/Line.php

xqd
@@ -0,0 +1,36 @@
+<?php
+namespace Grafika\Gd\DrawingObject;
+
+use Grafika\DrawingObject\Line as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Class Line
+ * @package Grafika
+ */
+class Line extends Base implements DrawingObjectInterface
+{
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function draw($image)
+    {
+
+        list( $x1, $y1 ) = $this->point1;
+        list( $x2, $y2 ) = $this->point2;
+        list( $r, $g, $b ) = $this->color->getRgb();
+        $color = imagecolorallocate( $image->getCore(), $r, $g, $b );
+        if ( function_exists( 'imageantialias' ) ) { // Not available on some if PHP is not precompiled with it even if GD is enabled
+            imageantialias( $image->getCore(), true );
+        }
+        imageline( $image->getCore(), $x1, $y1, $x2, $y2, $color );
+
+        return $image;
+    }
+
+
+}

+ 64 - 0
app/Helper/Grafika/Gd/DrawingObject/Polygon.php

xqd
@@ -0,0 +1,64 @@
+<?php
+namespace Grafika\Gd\DrawingObject;
+
+use Grafika\DrawingObject\Polygon as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Gd\Editor;
+
+/**
+ * Class Rectangle
+ * @package Grafika
+ */
+class Polygon extends Base implements DrawingObjectInterface
+{
+
+    public function draw($image)
+    {
+        if(function_exists('imageantialias')){
+            imageantialias($image->getCore(), true);
+        }
+
+        $points = $this->points();
+        $count = count($this->points);
+
+
+        // Create filled polygon
+        if( null !== $this->fillColor){
+            list($r, $g, $b, $alpha) = $this->getFillColor()->getRgba();
+            $fillColorResource = imagecolorallocatealpha(
+                $image->getCore(), $r, $g, $b,
+                Editor::gdAlpha($alpha)
+            );
+            imagefilledpolygon($image->getCore(), $points,
+                $count,
+                $fillColorResource
+            );
+        }
+
+        // Create polygon borders. It will be placed on top of the filled polygon (if present)
+        if ( 0 < $this->getBorderSize() and null !== $this->borderColor) { // With border > 0 AND borderColor !== null
+            list($r, $g, $b, $alpha) = $this->getBorderColor()->getRgba();
+            $borderColorResource = imagecolorallocatealpha(
+                $image->getCore(), $r, $g, $b,
+                Editor::gdAlpha($alpha)
+            );
+            imagepolygon($image->getCore(), $points,
+                $count,
+                $borderColorResource
+            );
+        }
+        return $image;
+    }
+
+    protected function points(){
+        $points = array();
+        foreach($this->points as $point){
+            $points[] = $point[0];
+            $points[] = $point[1];
+        }
+        if( count($points) < 6 ){
+            throw new \Exception('Polygon needs at least 3 points.');
+        }
+        return $points;
+    }
+}

+ 208 - 0
app/Helper/Grafika/Gd/DrawingObject/QuadraticBezier.php

xqd
@@ -0,0 +1,208 @@
+<?php
+namespace Grafika\Gd\DrawingObject;
+
+use Grafika\DrawingObject\QuadraticBezier as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Gd\Image;
+use Grafika\ImageInterface;
+
+/**
+ * Class QuadraticBezier
+ * @package Grafika
+ */
+class QuadraticBezier extends Base implements DrawingObjectInterface
+{
+    /**
+     * @link http://members.chello.at/easyfilter/bresenham.pdf
+     * @param ImageInterface $image
+     * @return Image
+     */
+    public function draw($image)
+    {
+        // Localize vars
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+        $gd = $image->getCore();
+
+        list($x0, $y0) = $this->point1;
+        list($x1, $y1) = $this->control;
+        list($x2, $y2) = $this->point2;
+
+        $this->plot($gd, $x0, $y0, $x1, $y1, $x2, $y2);
+
+        $type = $image->getType();
+        $file = $image->getImageFile();
+        return new Image($gd, $file, $width, $height, $type); // Create new image with updated core
+    }
+
+    protected function plot($gd, $x0, $y0, $x1, $y1, $x2, $y2)
+    {
+        /* plot any quadratic Bezier curve */
+        $x = $x0 - $x1;
+        $y = $y0 - $y1;
+        $t = $x0 - 2 * $x1 + $x2; //double
+
+        if ((int)$x * ($x2 - $x1) > 0) { /* horizontal cut at P4? */
+            if ((int)$y * ($y2 - $y1) > 0) /* vertical cut at P6 too? */ {
+                if (abs(($y0 - 2 * $y1 + $y2) / $t * $x) > abs($y)) { /* which first? */
+                    $x0 = $x2;
+                    $x2 = $x + $x1;
+                    $y0 = $y2;
+                    $y2 = $y + $y1; /* swap points */
+                }
+            } /* now horizontal cut at P4 comes first */
+            $t = ($x0 - $x1) / $t;
+            $r = (1 - $t) * ((1 - $t) * $y0 + 2.0 * $t * $y1) + $t * $t * $y2; /* By(t=P4) */
+            $t = ($x0 * $x2 - $x1 * $x1) * $t / ($x0 - $x1); /* gradient dP4/dx=0 */
+            $x = floor($t + 0.5);
+            $y = floor($r + 0.5);
+            $r = ($y1 - $y0) * ($t - $x0) / ($x1 - $x0) + $y0; /* intersect P3 | P0 P1 */
+            $this->plotSegment($gd, $x0, $y0, $x, floor($r + 0.5), $x, $y);
+            $r = ($y1 - $y2) * ($t - $x2) / ($x1 - $x2) + $y2; /* intersect P4 | P1 P2 */
+            $x0 = $x1 = $x;
+            $y0 = $y;
+            $y1 = floor($r + 0.5); /* P0 = P4, P1 = P8 */
+        }
+        if ((int)($y0 - $y1) * ($y2 - $y1) > 0) { /* vertical cut at P6? */
+            $t = $y0 - 2 * $y1 + $y2;
+            $t = ($y0 - $y1) / $t;
+            $r = (1 - $t) * ((1 - $t) * $x0 + 2.0 * $t * $x1) + $t * $t * $x2; /* Bx(t=P6) */
+            $t = ($y0 * $y2 - $y1 * $y1) * $t / ($y0 - $y1); /* gradient dP6/dy=0 */
+            $x = floor($r + 0.5);
+            $y = floor($t + 0.5);
+            $r = ($x1 - $x0) * ($t - $y0) / ($y1 - $y0) + $x0; /* intersect P6 | P0 P1 */
+            $this->plotSegment($gd, $x0, $y0, floor($r + 0.5), $y, $x, $y);
+            $r = ($x1 - $x2) * ($t - $y2) / ($y1 - $y2) + $x2; /* intersect P7 | P1 P2 */
+            $x0 = $x;
+            $x1 = floor($r + 0.5);
+            $y0 = $y1 = $y; /* P0 = P6, P1 = P7 */
+        }
+        $this->plotSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2); /* remaining part */
+    }
+
+    /**
+     * Draw an limited anti-aliased quadratic Bezier segment.
+     * @param $gd
+     * @param $x0
+     * @param $y0
+     * @param $x1
+     * @param $y1
+     * @param $x2
+     * @param $y2
+     */
+    protected function plotSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2)
+    {
+        $sx = $x2 - $x1;
+        $sy = $y2 - $y1;
+        $xx = $x0 - $x1;
+        $yy = $y0 - $y1;
+
+        $cur = $xx * $sy - $yy * $sx; /* $curvature */
+        assert($xx * $sx <= 0 && $yy * $sy <= 0);
+        if ($sx * (int)$sx + $sy * (int)$sy > $xx * $xx + $yy * $yy) { /* begin with longer part */
+            $x2 = $x0;
+            $x0 = $sx + $x1;
+            $y2 = $y0;
+            $y0 = $sy + $y1;
+            $cur = -$cur; /* swap P0 P2 */
+        }
+        if ($cur != 0) { /* no straight line */
+            $xx += $sx;
+            $xx *= $sx = $x0 < $x2 ? 1 : -1; /* x step direction */
+            $yy += $sy;
+            $yy *= $sy = $y0 < $y2 ? 1 : -1; /* y step direction */
+            $xy = 2 * $xx * $yy;
+            $xx *= $xx;
+            $yy *= $yy; /* differences 2nd degree */
+            if ($cur * $sx * $sy < 0) { /* negat$ed $curvature? */
+                $xx = -$xx;
+                $yy = -$yy;
+                $xy = -$xy;
+                $cur = -$cur;
+            }
+            $dx = 4.0 * $sy * ($x1 - $x0) * $cur + $xx - $xy; /* differences 1st degree */
+            $dy = 4.0 * $sx * ($y0 - $y1) * $cur + $yy - $xy;
+            $xx += $xx;
+            $yy += $yy;
+            $err = $dx + $dy + $xy; /* $error 1st step */
+            do {
+                $cur = min($dx + $xy, -$xy - $dy);
+                $ed = max($dx + $xy, -$xy - $dy); /* approximate $error distance */
+                $ed += 2 * $ed * $cur * $cur / (4 * $ed * $ed + $cur * $cur);
+                $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy - $xy) / $ed); /* plot $curve */
+                if ($x0 == $x2 || $y0 == $y2) {
+                    break;
+                } /* $curve finish$ed */
+                $x1 = $x0;
+                $cur = $dx - $err;
+                $y1 = 2 * $err + $dy < 0;
+                if (2 * $err + $dx > 0) { /* x step */
+                    if ($err - $dy < $ed) {
+                        $this->setPixel($gd, $x0, $y0 + $sy, abs($err - $dy) / $ed);
+                    }
+                    $x0 += $sx;
+                    $dx -= $xy;
+                    $err += $dy += $yy;
+                }
+                if ($y1) { /* y step */
+                    if ($cur < $ed) {
+                        $this->setPixel($gd, $x1 + $sx, $y0, abs($cur) / $ed);
+                    }
+                    $y0 += $sy;
+                    $dy -= $xy;
+                    $err += $dx += $xx;
+                }
+            } while ($dy < $dx); /* gradient negates -> close curves */
+        }
+        $this->plotLine($gd, $x0, $y0, $x2, $y2); /* plot remaining needle to end */
+    }
+
+    protected function plotLine($gd, $x0, $y0, $x1, $y1)
+    {
+        $dx = abs($x1 - $x0);
+        $sx = $x0 < $x1 ? 1 : -1;
+        $dy = -abs($y1 - $y0);
+        $sy = $y0 < $y1 ? 1 : -1;
+        $err = $dx + $dy;
+
+        $ed = $dx - $dy == 0 ? 1 : sqrt((float)$dx * $dx + (float)$dy * $dy);
+        for (; ;) { /* pixel loop */
+            $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy) / $ed);
+            $e2 = $err;
+            $x2 = $x0;
+            if (2 * $e2 + $dx >= 0) { /* x step */
+                if ($x0 == $x1) {
+                    break;
+                }
+                if ($e2 - $dy < $ed) {
+                    $this->setPixel($gd, $x0, $y0 + $sy, ($e2 - $dy) / $ed);
+                }
+                $err += $dy;
+                $x0 += $sx;
+            }
+            if (2 * $e2 + $dy <= 0) { /* y step */
+                if ($y0 == $y1) {
+                    break;
+                }
+                if ($dx - $e2 < $ed) {
+                    $this->setPixel($gd, $x2 + $sx, $y0, ($dx - $e2) / $ed);
+                }
+                $err += $dx;
+                $y0 += $sy;
+            }
+        }
+    }
+
+    /**
+     * @param resource $gd
+     * @param int $x
+     * @param int $y
+     * @param float $ar Alpha ratio
+     */
+    protected function setPixel($gd, $x, $y, $ar)
+    {
+        list($r, $g, $b) = $this->color->getRgb();
+        $c = imagecolorallocatealpha($gd, $r, $g, $b, 127 * $ar);
+        imagesetpixel($gd, $x, $y, $c);
+    }
+}

+ 36 - 0
app/Helper/Grafika/Gd/DrawingObject/Rectangle.php

xqd
@@ -0,0 +1,36 @@
+<?php
+namespace Grafika\Gd\DrawingObject;
+
+use Grafika\DrawingObject\Rectangle as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Gd\Editor;
+
+/**
+ * Class Rectangle
+ * @package Grafika
+ */
+class Rectangle extends Base implements DrawingObjectInterface
+{
+
+    public function draw($image)
+    {
+        $x1 = $this->pos[0];
+        $x2 = $x1 + $this->getWidth();
+        $y1 = $this->pos[1];
+        $y2 = $y1 + $this->getHeight();
+
+        if( null !== $this->fillColor ){
+            list($r, $g, $b, $alpha) = $this->fillColor->getRgba();
+            $fillColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha));
+            imagefilledrectangle($image->getCore(), $x1, $y1, $x2, $y2, $fillColorResource);
+        }
+        // Create borders. It will be placed on top of the filled rectangle (if present)
+        if ( 0 < $this->getBorderSize() and null !== $this->borderColor) { // With border > 0 AND borderColor !== null
+            list($r, $g, $b, $alpha) = $this->borderColor->getRgba();
+            $borderColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha));
+            imagerectangle($image->getCore(), $x1, $y1, $x2, $y2, $borderColorResource);
+        }
+
+        return $image;
+    }
+}

+ 1179 - 0
app/Helper/Grafika/Gd/Editor.php

xqd
@@ -0,0 +1,1179 @@
+<?php
+
+namespace Grafika\Gd;
+
+use Grafika\DrawingObjectInterface;
+use Grafika\EditorInterface;
+use Grafika\FilterInterface;
+use Grafika\Gd\Helper\GifHelper;
+use Grafika\Gd\ImageHash\DifferenceHash;
+use Grafika\Grafika;
+use Grafika\ImageInterface;
+use Grafika\ImageType;
+use Grafika\Color;
+use Grafika\Position;
+
+/**
+ * GD Editor class. Uses the PHP GD library.
+ * @package Grafika\Gd
+ */
+final class Editor implements EditorInterface
+{
+
+    /**
+     * Apply a filter to the image. See Filters section for a list of available filters.
+     *
+     * @param Image $image
+     * @param FilterInterface $filter
+     *
+     * @return Editor
+     */
+    public function apply( &$image, $filter)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $image = $filter->apply($image);
+
+        return $this;
+    }
+
+    /**
+     * Blend two images together with the first image as the base and the second image on top. Supports several blend modes.
+     *
+     * @param Image $image1 The base image.
+     * @param Image $image2 The image placed on top of the base image.
+     * @param string $type The blend mode. Can be: normal, multiply, overlay or screen.
+     * @param float $opacity The opacity of $image2. Possible values 0.0 to 1.0 where 0.0 is fully transparent and 1.0 is fully opaque. Defaults to 1.0.
+     * @param string $position The position of $image2 on $image1. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to top-left.
+     * @param int $offsetX Number of pixels to add to the X position of $image2.
+     * @param int $offsetY Number of pixels to add to the Y position of $image2.
+     *
+     * @return Editor
+     * @throws \Exception When added image is outside of canvas or invalid blend type
+     */
+    public function blend(&$image1, $image2, $type='normal', $opacity = 1.0, $position = 'top-left', $offsetX = 0, $offsetY = 0 ){
+
+        // Turn into position object
+        $position = new Position($position, $offsetX, $offsetY);
+
+        // Position is for $image2. $image1 is canvas.
+        list($offsetX, $offsetY) = $position->getXY($image1->getWidth(), $image1->getHeight(), $image2->getWidth(), $image2->getHeight());
+
+        // Check if it overlaps
+        if( ($offsetX >= $image1->getWidth() ) or
+            ($offsetX + $image2->getWidth() <= 0) or
+            ($offsetY >= $image1->getHeight() ) or
+            ($offsetY + $image2->getHeight() <= 0)){
+
+            throw new \Exception('Invalid blending. Image 2 is outside the canvas.');
+        }
+
+        // Loop start X
+        $loopStartX = 0;
+        $canvasStartX = $offsetX;
+        if($canvasStartX < 0){
+            $diff = 0 - $canvasStartX;
+            $loopStartX += $diff;
+        }
+
+        // Loop end X
+        $loopEndX = $image2->getWidth();
+        $canvasEndX = $offsetX + $image2->getWidth();
+        if($canvasEndX > $image1->getWidth()){
+            $diff = $canvasEndX - $image1->getWidth();
+            $loopEndX -= $diff;
+        }
+
+        // Loop start Y
+        $loopStartY = 0;
+        $canvasStartY = $offsetY;
+        if($canvasStartY < 0){
+            $diff = 0 - $canvasStartY;
+            $loopStartY += $diff;
+        }
+
+        // Loop end Y
+        $loopEndY = $image2->getHeight();
+        $canvasEndY = $offsetY + $image2->getHeight();
+        if($canvasEndY > $image1->getHeight()){
+            $diff = $canvasEndY - ($image1->getHeight());
+            $loopEndY -= $diff;
+        }
+
+        $w   = $image1->getWidth();
+        $h   = $image1->getHeight();
+        $gd1 = $image1->getCore();
+        $gd2 = $image2->getCore();
+
+        $canvas = imagecreatetruecolor( $w, $h );
+        imagecopy( $canvas, $gd1, 0, 0, 0, 0, $w, $h );
+
+        $type = strtolower( $type );
+        if($type==='normal') {
+            if ( $opacity !== 1 ) {
+                $this->opacity($image2, $opacity);
+            }
+            imagecopy( $canvas, $gd2, $loopStartX + $offsetX, $loopStartY + $offsetY, 0, 0, $image2->getWidth(), $image2->getHeight());
+        } else if($type==='multiply'){
+            $this->_blendMultiply( $canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity );
+        } else if($type==='overlay'){
+            $this->_blendOverlay( $canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity );
+        } else if($type==='screen'){
+            $this->_blendScreen( $canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity );
+        } else {
+            throw new \Exception(sprintf('Invalid blend type "%s".', $type));
+        }
+
+        imagedestroy( $gd1 ); // Free resource
+
+        $image1 = new Image(
+            $canvas,
+            $image1->getImageFile(),
+            $w,
+            $h,
+            $image1->getType()
+        );
+
+        return $this;
+    }
+
+    /**
+     * Compare two images and returns a hamming distance. A value of 0 indicates a likely similar picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is likely a different image.
+     *
+     * @param ImageInterface|string $image1
+     * @param ImageInterface|string $image2
+     *
+     * @return int Hamming distance. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
+     * @throws \Exception
+     */
+    public function compare($image1, $image2)
+    {
+
+        if (is_string($image1)) { // If string passed, turn it into a Image object
+            $image1 = Image::createFromFile($image1);
+            $this->flatten( $image1 );
+        }
+
+        if (is_string($image2)) { // If string passed, turn it into a Image object
+            $image2 = Image::createFromFile($image2);
+            $this->flatten( $image2 );
+        }
+
+        $hash = new DifferenceHash();
+
+        $bin1     = $hash->hash($image1, $this);
+        $bin2     = $hash->hash($image2, $this);
+        $str1     = str_split($bin1);
+        $str2     = str_split($bin2);
+        $distance = 0;
+        foreach ($str1 as $i => $char) {
+            if ($char !== $str2[$i]) {
+                $distance++;
+            }
+        }
+
+        return $distance;
+
+    }
+
+    /**
+     * Crop the image to the given dimension and position.
+     *
+     * @param Image $image
+     * @param int $cropWidth Crop width in pixels.
+     * @param int $cropHeight Crop Height in pixels.
+     * @param string $position The crop position. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to center.
+     * @param int $offsetX Number of pixels to add to the X position of the crop.
+     * @param int $offsetY Number of pixels to add to the Y position of the crop.
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function crop( &$image, $cropWidth, $cropHeight, $position = 'center', $offsetX = 0, $offsetY = 0)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        if ( 'smart' === $position ) { // Smart crop
+            list( $x, $y ) = $this->_smartCrop( $image, $cropWidth, $cropHeight );
+        } else {
+            // Turn into an instance of Position
+            $position = new Position( $position, $offsetX, $offsetY );
+
+            // Crop position as x,y coordinates
+            list( $x, $y ) = $position->getXY( $image->getWidth(), $image->getHeight(), $cropWidth, $cropHeight );
+
+        }
+
+        // Create blank image
+        $newImageResource = imagecreatetruecolor($cropWidth, $cropHeight);
+
+        // Now crop
+        imagecopyresampled(
+            $newImageResource, // Target image
+            $image->getCore(), // Source image
+            0, // Target x
+            0, // Target y
+            $x, // Src x
+            $y, // Src y
+            $cropWidth, // Target width
+            $cropHeight, // Target height
+            $cropWidth, // Src width
+            $cropHeight // Src height
+        );
+
+        // Free memory of old resource
+        imagedestroy($image->getCore());
+
+        // Cropped image instance
+        $image = new Image(
+            $newImageResource,
+            $image->getImageFile(),
+            $cropWidth,
+            $cropHeight,
+            $image->getType()
+        );
+
+        return $this;
+    }
+
+    /**
+     * Draw a DrawingObject on the image. See Drawing Objects section.
+     *
+     * @param Image $image
+     * @param DrawingObjectInterface $drawingObject
+     *
+     * @return $this
+     */
+    public function draw( &$image, $drawingObject)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $image = $drawingObject->draw($image);
+
+        return $this;
+    }
+
+    /**
+     * Compare if two images are equal. It will compare if the two images are of the same width and height. If the dimensions differ, it will return false. If the dimensions are equal, it will loop through each pixels. If one of the pixel don't match, it will return false. The pixels are compared using their RGB (Red, Green, Blue) values.
+     *
+     * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image.
+     * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image.
+     *
+     * @return bool True if equals false if not. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
+     * @throws \Exception
+     */
+    public function equal($image1, $image2)
+    {
+
+        if (is_string($image1)) { // If string passed, turn it into a Image object
+            $image1 = Image::createFromFile($image1);
+            $this->flatten( $image1 );
+        }
+
+        if (is_string($image2)) { // If string passed, turn it into a Image object
+            $image2 = Image::createFromFile($image2);
+            $this->flatten( $image2 );
+        }
+
+        // Check if image dimensions are equal
+        if ($image1->getWidth() !== $image2->getWidth() or $image1->getHeight() !== $image2->getHeight()) {
+
+            return false;
+
+        } else {
+
+            // Loop using image1
+            for ($y = 0; $y < $image1->getHeight(); $y++) {
+                for ($x = 0; $x < $image1->getWidth(); $x++) {
+
+                    // Get image1 pixel
+                    $rgb1 = imagecolorat($image1->getCore(), $x, $y);
+                    $r1   = ($rgb1 >> 16) & 0xFF;
+                    $g1   = ($rgb1 >> 8) & 0xFF;
+                    $b1   = $rgb1 & 0xFF;
+
+                    // Get image2 pixel
+                    $rgb2 = imagecolorat($image2->getCore(), $x, $y);
+                    $r2   = ($rgb2 >> 16) & 0xFF;
+                    $g2   = ($rgb2 >> 8) & 0xFF;
+                    $b2   = $rgb2 & 0xFF;
+
+                    // Compare pixel value
+                    if (
+                        $r1 !== $r2 or
+                        $g1 !== $g2 or
+                        $b1 !== $b2
+                    ) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Fill entire image with color.
+     *
+     * @param Image $image
+     * @param Color $color Color object
+     * @param int $x X-coordinate of start point
+     * @param int $y Y-coordinate of start point
+     *
+     * @return Editor
+     */
+    public function fill( &$image, $color, $x = 0, $y = 0)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        list($r, $g, $b, $alpha) = $color->getRgba();
+
+        $colorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b,
+            $this->gdAlpha($alpha));
+        imagefill($image->getCore(), $x, $y, $colorResource);
+
+        return $this;
+    }
+
+    /**
+     * Flatten if animated GIF. Do nothing otherwise.
+     *
+     * @param Image $image
+     *
+     * @return Editor
+     */
+    public function flatten(&$image){
+
+        if($image->isAnimated()) {
+            $old = $image->getCore();
+            $gift = new GifHelper();
+            $hex  = $gift->encode($image->getBlocks());
+            $gd   = imagecreatefromstring(pack('H*', $hex)); // Recreate resource from blocks
+
+            imagedestroy( $old ); // Free resource
+            $image = new Image(
+                $gd,
+                $image->getImageFile(),
+                $image->getWidth(),
+                $image->getHeight(),
+                $image->getType(),
+                '', // blocks
+                false // animated
+            );
+        }
+        return $this;
+    }
+
+    /**
+     * Flip or mirrors the image.
+     *
+     * @param Image $image
+     * @param string $mode The type of flip: 'h' for horizontal flip or 'v' for vertical.
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function flip(&$image, $mode){
+
+        $image = $this->_flip($image, $mode);
+        return $this;
+    }
+
+    /**
+     * Free the image clearing resources associated with it.
+     *
+     * @param Image $image
+     *
+     * @return Editor
+     */
+    public function free( &$image )
+    {
+        imagedestroy($image->getCore());
+        return $this;
+    }
+
+    /**
+     * Convert alpha value of 0 - 1 to GD compatible alpha value of 0 - 127 where 0 is opaque and 127 is transparent
+     *
+     * @param float $alpha Alpha value of 0 - 1. Example: 0, 0.60, 0.9, 1
+     *
+     * @return int
+     */
+    public static function gdAlpha($alpha)
+    {
+
+        $scale = round(127 * $alpha);
+
+        return $invert = 127 - $scale;
+    }
+
+    /**
+     * Checks if the editor is available on the current PHP install.
+     *
+     * @return bool True if available false if not.
+     */
+    public function isAvailable()
+    {
+        if (false === extension_loaded('gd') || false === function_exists('gd_info')) {
+            return false;
+        }
+
+        // On some setups GD library does not provide imagerotate()
+        if ( ! function_exists('imagerotate')) {
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Sets the image to the specified opacity level where 1.0 is fully opaque and 0.0 is fully transparent.
+     * Warning: This function loops thru each pixel manually which can be slow. Use sparingly.
+     *
+     * @param Image $image
+     * @param float $opacity
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function opacity( &$image, $opacity )
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        // Bounds checks
+        $opacity = ($opacity > 1) ? 1 : $opacity;
+        $opacity = ($opacity < 0) ? 0 : $opacity;
+
+        for ($y = 0; $y < $image->getHeight(); $y++) {
+            for ($x = 0; $x < $image->getWidth(); $x++) {
+                $rgb   = imagecolorat($image->getCore(), $x, $y);
+                $alpha = ($rgb >> 24) & 0x7F; // 127 in hex. These are binary operations.
+                $r     = ($rgb >> 16) & 0xFF;
+                $g     = ($rgb >> 8) & 0xFF;
+                $b     = $rgb & 0xFF;
+
+                // Reverse alpha values from 127-0 (transparent to opaque) to 0-127 for easy math
+                // Previously: 0 = opaque, 127 = transparent.
+                // Now: 0 = transparent, 127 = opaque
+                $reverse = 127 - $alpha;
+                $reverse = round($reverse * $opacity);
+
+                if ($alpha < 127) { // Process non transparent pixels only
+                    imagesetpixel($image->getCore(), $x, $y,
+                        imagecolorallocatealpha($image->getCore(), $r, $g, $b, 127 - $reverse));
+                }
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Open an image file and assign Image to first parameter.
+     *
+     * @param Image $image
+     * @param string $imageFile
+     *
+     * @return Editor
+     */
+    public function open(&$image, $imageFile){
+        $image = Image::createFromFile( $imageFile );
+        return $this;
+    }
+
+    /**
+     * Wrapper function for the resizeXXX family of functions. Resize image given width, height and mode.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     * @param string $mode Resize mode. Possible values: "exact", "exactHeight", "exactWidth", "fill", "fit".
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function resize(&$image, $newWidth, $newHeight, $mode = 'fit')
+    {
+        /*
+         * Resize formula:
+         * ratio = w / h
+         * h = w / ratio
+         * w = h * ratio
+         */
+        switch ($mode) {
+            case 'exact':
+                $this->resizeExact($image, $newWidth, $newHeight);
+                break;
+            case 'fill':
+                $this->resizeFill($image, $newWidth, $newHeight);
+                break;
+            case 'exactWidth':
+                $this->resizeExactWidth($image, $newWidth);
+                break;
+            case 'exactHeight':
+                $this->resizeExactHeight($image, $newHeight);
+                break;
+            case 'fit':
+                $this->resizeFit($image, $newWidth, $newHeight);
+                break;
+            default:
+                throw new \Exception(sprintf('Invalid resize mode "%s".', $mode));
+        }
+
+        return $this;
+    }
+
+    /**
+     * Resize image to exact dimensions ignoring aspect ratio. Useful if you want to force exact width and height.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return Editor
+     */
+    public function resizeExact(&$image, $newWidth, $newHeight)
+    {
+
+        $this->_resize($image, $newWidth, $newHeight);
+
+        return $this;
+    }
+
+    /**
+     * Resize image to exact height. Width is auto calculated. Useful for creating row of images with the same height.
+     *
+     * @param Image $image
+     * @param int $newHeight Height in pixels.
+     *
+     * @return Editor
+     */
+    public function resizeExactHeight(&$image, $newHeight)
+    {
+
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $ratio  = $width / $height;
+
+        $resizeHeight = $newHeight;
+        $resizeWidth  = $newHeight * $ratio;
+
+        $this->_resize($image, $resizeWidth, $resizeHeight);
+
+        return $this;
+    }
+
+    /**
+     * Resize image to exact width. Height is auto calculated. Useful for creating column of images with the same width.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     *
+     * @return Editor
+     */
+    public function resizeExactWidth(&$image, $newWidth)
+    {
+
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $ratio  = $width / $height;
+
+        $resizeWidth  = $newWidth;
+        $resizeHeight = round($newWidth / $ratio);
+
+        $this->_resize($image, $resizeWidth, $resizeHeight);
+
+        return $this;
+    }
+
+    /**
+     * Resize image to fill all the space in the given dimension. Excess parts are cropped.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return Editor
+     */
+    public function resizeFill(&$image, $newWidth, $newHeight)
+    {
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $ratio  = $width / $height;
+
+        // Base optimum size on new width
+        $optimumWidth  = $newWidth;
+        $optimumHeight = round($newWidth / $ratio);
+
+        if (($optimumWidth < $newWidth) or ($optimumHeight < $newHeight)) { // Oops, where trying to fill and there are blank areas
+            // So base optimum size on height instead
+            $optimumWidth  = $newHeight * $ratio;
+            $optimumHeight = $newHeight;
+        }
+
+        $this->_resize($image, $optimumWidth, $optimumHeight);
+        $this->crop($image, $newWidth, $newHeight); // Trim excess parts
+
+        return $this;
+    }
+
+    /**
+     * Resize image to fit inside the given dimension. No part of the image is lost.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return Editor
+     */
+    public function resizeFit(&$image, $newWidth, $newHeight)
+    {
+
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $ratio  = $width / $height;
+
+        // Try basing it on width first
+        $resizeWidth  = $newWidth;
+        $resizeHeight = round($newWidth / $ratio);
+
+        if (($resizeWidth > $newWidth) or ($resizeHeight > $newHeight)) { // Oops, either with or height does not fit
+            // So base on height instead
+            $resizeHeight = $newHeight;
+            $resizeWidth  = $newHeight * $ratio;
+        }
+
+        $this->_resize($image, $resizeWidth, $resizeHeight);
+
+        return $this;
+    }
+
+    /**
+     * Rotate an image counter-clockwise.
+     *
+     * @param Image $image
+     * @param int $angle The angle in degrees.
+     * @param Color|null $color The Color object containing the background color.
+     *
+     * @return EditorInterface An instance of image editor.
+     * @throws \Exception
+     */
+    public function rotate(&$image, $angle, $color = null)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $color = ($color !== null) ? $color : new Color('#000000');
+        list($r, $g, $b, $alpha) = $color->getRgba();
+
+        $old = $image->getCore();
+        $new = imagerotate($old, $angle, imagecolorallocatealpha($old, $r, $g, $b, $alpha));
+
+        if(false === $new){
+            throw new \Exception('Error rotating image.');
+        }
+
+        imagedestroy( $old ); // Free resource
+        $image = new Image( $new, $image->getImageFile(), $image->getWidth(), $image->getHeight(), $image->getType() );
+
+        return $this;
+    }
+
+    /**
+     * Save the image to an image format.
+     *
+     * @param Image $image
+     * @param string $file File path where to save the image.
+     * @param null|string $type Type of image. Can be null, "GIF", "PNG", or "JPEG".
+     * @param null|string $quality Quality of image. Applies to JPEG only. Accepts number 0 - 100 where 0 is lowest and 100 is the highest quality. Or null for default.
+     * @param bool|false $interlace Set to true for progressive JPEG. Applies to JPEG only.
+     * @param int $permission Default permission when creating non-existing target directory.
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function save($image, $file, $type = null, $quality = null, $interlace = false, $permission = 0755)
+    {
+
+        if (null === $type) {
+
+            $type = $this->_getImageTypeFromFileName($file); // Null given, guess type from file extension
+            if (ImageType::UNKNOWN === $type) {
+                $type = $image->getType(); // 0 result, use original image type
+            }
+        }
+
+        $targetDir = dirname($file); // $file's directory
+        if (false === is_dir($targetDir)) { // Check if $file's directory exist
+            // Create and set default perms to 0755
+            if ( ! mkdir($targetDir, $permission, true)) {
+                throw new \Exception(sprintf('Cannot create %s', $targetDir));
+            }
+        }
+
+        switch (strtoupper($type)) {
+            case ImageType::GIF :
+                if($image->isAnimated()){
+                    $blocks = $image->getBlocks();
+                    $gift = new GifHelper();
+                    $hex = $gift->encode($blocks);
+                    file_put_contents($file, pack('H*', $hex));
+                } else {
+                    imagegif($image->getCore(), $file);
+                }
+
+                break;
+
+            case ImageType::PNG :
+                // PNG is lossless and does not need compression. Although GD allow values 0-9 (0 = no compression), we leave it alone.
+                imagepng($image->getCore(), $file);
+                break;
+
+            default: // Defaults to jpeg
+                $quality = ($quality === null) ? 75 : $quality; // Default to 75 (GDs default) if null.
+                $quality = ($quality > 100) ? 100 : $quality;
+                $quality = ($quality < 0) ? 0 : $quality;
+                imageinterlace($image->getCore(), $interlace);
+                imagejpeg($image->getCore(), $file, $quality);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Write text to image.
+     *
+     * @param Image $image
+     * @param string $text The text to be written.
+     * @param int $size The font size. Defaults to 12.
+     * @param int $x The distance from the left edge of the image to the left of the text. Defaults to 0.
+     * @param int $y The distance from the top edge of the image to the top of the text. Defaults to 12 (equal to font size) so that the text is placed within the image.
+     * @param Color $color The Color object. Default text color is black.
+     * @param string $font Full path to font file. If blank, will default to Liberation Sans font.
+     * @param int $angle Angle of text from 0 - 359. Defaults to 0.
+     *
+     * @return EditorInterface
+     * @throws \Exception
+     */
+    public function text(&$image, $text, $size = 12, $x = 0, $y = 0, $color = null, $font = '', $angle = 0)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $y += $size;
+
+        $color = ($color !== null) ? $color : new Color('#000000');
+        $font  = ($font !== '') ? $font : Grafika::fontsDir() . DIRECTORY_SEPARATOR . 'LiberationSans-Regular.ttf';
+
+        list($r, $g, $b, $alpha) = $color->getRgba();
+
+        $colorResource = imagecolorallocatealpha(
+            $image->getCore(),
+            $r, $g, $b,
+            $this->gdAlpha($alpha)
+        );
+
+        imagettftext(
+            $image->getCore(),
+            $size,
+            $angle,
+            $x,
+            $y,
+            $colorResource,
+            $font,
+            $text
+        );
+
+        return $this;
+    }
+
+    /**
+     * @param $canvas
+     * @param $gd1
+     * @param $gd2
+     * @param $loopStartY
+     * @param $loopEndY
+     * @param $loopStartX
+     * @param $loopEndX
+     * @param $offsetX
+     * @param $offsetY
+     *
+     * @param $opacity
+     *
+     * @return $this
+     */
+    private function _blendMultiply($canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity){
+
+        for ( $y = $loopStartY; $y < $loopEndY; $y++ ) {
+            for ( $x = $loopStartX; $x < $loopEndX; $x++ ) {
+                $canvasX    = $x + $offsetX;
+                $canvasY    = $y + $offsetY;
+                $argb1 = imagecolorat( $gd1, $canvasX, $canvasY );
+                $r1    = ( $argb1 >> 16 ) & 0xFF;
+                $g1    = ( $argb1 >> 8 ) & 0xFF;
+                $b1    = $argb1 & 0xFF;
+
+                $argb2 = imagecolorat( $gd2, $x, $y );
+                $a2 = ($argb2 >> 24) & 0x7F; // 127 in hex. These are binary operations.
+                $r2    = ( $argb2 >> 16 ) & 0xFF;
+                $g2    = ( $argb2 >> 8 ) & 0xFF;
+                $b2    = $argb2 & 0xFF;
+
+                $r3 = round($r1 * $r2 / 255);
+                $g3 = round($g1 * $g2 / 255);
+                $b3 = round($b1 * $b2 / 255);
+
+                $reverse = 127 - $a2;
+                $reverse = round($reverse * $opacity);
+
+                $argb3 = imagecolorallocatealpha( $canvas, $r3, $g3, $b3, 127 - $reverse );
+                imagesetpixel( $canvas, $canvasX, $canvasY, $argb3 );
+            }
+        }
+        return $canvas;
+    }
+
+    /**
+     * @param $canvas
+     * @param $gd1
+     * @param $gd2
+     * @param $loopStartY
+     * @param $loopEndY
+     * @param $loopStartX
+     * @param $loopEndX
+     * @param $offsetX
+     * @param $offsetY
+     *
+     * @param $opacity
+     *
+     * @return $this
+     */
+    private function _blendOverlay($canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity){
+
+        for ( $y = $loopStartY; $y < $loopEndY; $y++ ) {
+            for ( $x = $loopStartX; $x < $loopEndX; $x++ ) {
+                $canvasX    = $x + $offsetX;
+                $canvasY    = $y + $offsetY;
+                $argb1 = imagecolorat( $gd1, $canvasX, $canvasY );
+                $r1    = ( $argb1 >> 16 ) & 0xFF;
+                $g1    = ( $argb1 >> 8 ) & 0xFF;
+                $b1    = $argb1 & 0xFF;
+
+                $argb2 = imagecolorat( $gd2, $x, $y );
+                $a2 = ($argb2 >> 24) & 0x7F; // 127 in hex. These are binary operations.
+                $r2    = ( $argb2 >> 16 ) & 0xFF;
+                $g2    = ( $argb2 >> 8 ) & 0xFF;
+                $b2    = $argb2 & 0xFF;
+
+                $r1 /= 255;
+                $r2 /= 255;
+                if ($r1 < 0.5) {
+                    $r3 = 2 * ($r1 * $r2);
+                } else {
+                    $r3 = (1 - (2 *(1-$r1)) * (1-$r2));
+                }
+
+                $g1 /= 255;
+                $g2 /= 255;
+                if ($g1 < 0.5) {
+                    $g3 = 2 * ($g1 * $g2);
+                } else {
+                    $g3 = (1 - (2 *(1-$g1)) * (1-$g2));
+                }
+
+                $b1 /= 255;
+                $b2 /= 255;
+                if ($b1 < 0.5) {
+                    $b3 = 2 * ($b1 * $b2);
+                } else {
+                    $b3 = (1 - (2 *(1-$b1)) * (1-$b2));
+                }
+
+                $reverse = 127 - $a2;
+                $reverse = round($reverse * $opacity);
+
+                $argb3 = imagecolorallocatealpha( $canvas, $r3*255, $g3*255, $b3*255, 127 - $reverse );
+                imagesetpixel( $canvas, $canvasX, $canvasY, $argb3 );
+            }
+        }
+        return $canvas;
+    }
+
+    /**
+     * Calculate entropy based on histogram.
+     *
+     * @param $hist
+     *
+     * @return float|int
+     */
+    private function _entropy($hist){
+        $entropy = 0;
+        $hist_size = array_sum($hist['r']) + array_sum($hist['g']) + array_sum($hist['b']);
+        foreach($hist['r'] as $p){
+            $p = $p / $hist_size;
+            $entropy += $p * log($p, 2);
+        }
+        foreach($hist['g'] as $p){
+            $p = $p / $hist_size;
+            $entropy += $p * log($p, 2);
+        }
+        foreach($hist['b'] as $p){
+            $p = $p / $hist_size;
+            $entropy += $p * log($p, 2);
+        }
+        return $entropy * -1;
+    }
+
+    /**
+     * @param $canvas
+     * @param $gd1
+     * @param $gd2
+     * @param $loopStartY
+     * @param $loopEndY
+     * @param $loopStartX
+     * @param $loopEndX
+     * @param $offsetX
+     * @param $offsetY
+     *
+     * @param $opacity
+     *
+     * @return $this
+     */
+    private function _blendScreen($canvas, $gd1, $gd2, $loopStartY, $loopEndY, $loopStartX, $loopEndX, $offsetX, $offsetY, $opacity){
+
+        for ( $y = $loopStartY; $y < $loopEndY; $y++ ) {
+            for ( $x = $loopStartX; $x < $loopEndX; $x++ ) {
+                $canvasX    = $x + $offsetX;
+                $canvasY    = $y + $offsetY;
+                $argb1 = imagecolorat( $gd1, $canvasX, $canvasY );
+                $r1    = ( $argb1 >> 16 ) & 0xFF;
+                $g1    = ( $argb1 >> 8 ) & 0xFF;
+                $b1    = $argb1 & 0xFF;
+
+                $argb2 = imagecolorat( $gd2, $x, $y );
+                $a2 = ($argb2 >> 24) & 0x7F; // 127 in hex. These are binary operations.
+                $r2    = ( $argb2 >> 16 ) & 0xFF;
+                $g2    = ( $argb2 >> 8 ) & 0xFF;
+                $b2    = $argb2 & 0xFF;
+
+                $r3 = 255 - ( ( 255 - $r1 ) * ( 255 - $r2 ) ) / 255;
+                $g3 = 255 - ( ( 255 - $g1 ) * ( 255 - $g2 ) ) / 255;
+                $b3 = 255 - ( ( 255 - $b1 ) * ( 255 - $b2 ) ) / 255;
+
+                $reverse = 127 - $a2;
+                $reverse = round($reverse * $opacity);
+
+                $argb3 = imagecolorallocatealpha( $canvas, $r3, $g3, $b3, 127 - $reverse );
+                imagesetpixel( $canvas, $canvasX, $canvasY, $argb3 );
+            }
+        }
+        return $canvas;
+    }
+
+    /**
+     * Flips image.
+     * @param Image $image
+     * @param $mode
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    private function _flip($image, $mode)
+    {
+        $old = $image->getCore();
+        $w   = $image->getWidth();
+        $h   = $image->getHeight();
+        if ($mode === 'h') {
+            $new = imagecreatetruecolor($w, $h);
+            for ($x = 0; $x < $w; $x++) {
+                imagecopy($new, $old, $w - $x - 1, 0, $x, 0, 1, $h);
+            }
+            imagedestroy($old); // Free resource
+            return new Image(
+                $new,
+                $image->getImageFile(),
+                $w,
+                $h,
+                $image->getType(),
+                $image->getBlocks(),
+                $image->isAnimated()
+            );
+        } else if ($mode === 'v') {
+            $new = imagecreatetruecolor($w, $h);
+            for ($y = 0; $y < $h; $y++) {
+                imagecopy($new, $old, 0, $h - $y - 1, 0, $y, $w, 1);
+            }
+            imagedestroy($old); // Free resource
+            return new Image(
+                $new,
+                $image->getImageFile(),
+                $w,
+                $h,
+                $image->getType(),
+                $image->getBlocks(),
+                $image->isAnimated()
+            );
+        } else {
+            throw new \Exception(sprintf('Unsupported mode "%s"', $mode));
+        }
+    }
+
+    /**
+     * Get image type base on file extension.
+     *
+     * @param int $imageFile File path to image.
+     *
+     * @return ImageType string Type of image.
+     */
+    private function _getImageTypeFromFileName($imageFile)
+    {
+        $ext = strtolower((string)pathinfo($imageFile, PATHINFO_EXTENSION));
+
+        if ('jpg' === $ext or 'jpeg' === $ext) {
+            return ImageType::JPEG;
+        } else if ('gif' === $ext) {
+            return ImageType::GIF;
+        } else if ('png' === $ext) {
+            return ImageType::PNG;
+        } else if ('wbm' === $ext or 'wbmp' === $ext) {
+            return ImageType::WBMP;
+        } else {
+            return ImageType::UNKNOWN;
+        }
+    }
+
+    /**
+     * Resize helper function.
+     *
+     * @param Image $image
+     * @param int $newWidth
+     * @param int $newHeight
+     * @param int $targetX
+     * @param int $targetY
+     * @param int $srcX
+     * @param int $srcY
+     *
+     */
+    private function _resize(&$image, $newWidth, $newHeight, $targetX = 0, $targetY = 0, $srcX = 0, $srcY = 0)
+    {
+
+//        $this->_imageCheck();
+
+        if ($image->isAnimated()) { // Animated GIF
+            $gift = new GifHelper();
+            $blocks = $gift->resize($image->getBlocks(), $newWidth, $newHeight);
+            // Resize image instance
+            $image = new Image(
+                $image->getCore(),
+                $image->getImageFile(),
+                $newWidth,
+                $newHeight,
+                $image->getType(),
+                $blocks,
+                true
+            );
+        } else {
+
+            // Create blank image
+            $newImage = Image::createBlank($newWidth, $newHeight);
+
+            if (ImageType::PNG === $image->getType()) {
+                // Preserve PNG transparency
+                $newImage->fullAlphaMode(true);
+            }
+
+            imagecopyresampled(
+                $newImage->getCore(),
+                $image->getCore(),
+                $targetX,
+                $targetY,
+                $srcX,
+                $srcY,
+                $newWidth,
+                $newHeight,
+                $image->getWidth(),
+                $image->getHeight()
+            );
+
+            // Free memory of old resource
+            imagedestroy($image->getCore());
+
+            // Resize image instance
+            $image = new Image(
+                $newImage->getCore(),
+                $image->getImageFile(),
+                $newWidth,
+                $newHeight,
+                $image->getType()
+            );
+
+        }
+    }
+
+    /**
+     * Crop based on entropy.
+     *
+     * @param Image $oldImage
+     * @param $cropW
+     * @param $cropH
+     *
+     * @return array
+     */
+    private function _smartCrop($oldImage, $cropW, $cropH){
+        $image = clone $oldImage;
+
+        $this->resizeFit($image, 30, 30);
+
+        $origW = $oldImage->getWidth();
+        $origH = $oldImage->getHeight();
+        $resizeW = $image->getWidth();
+        $resizeH = $image->getHeight();
+
+        $smallCropW = round(($resizeW / $origW) * $cropW);
+        $smallCropH = round(($resizeH / $origH) * $cropH);
+
+        $step = 1;
+
+        for($y = 0; $y < $resizeH-$smallCropH; $y+=$step){
+            for($x = 0; $x < $resizeW-$smallCropW; $x+=$step){
+                $hist[$x.'-'.$y] = $this->_entropy($image->histogram(array(array($x, $y), array($smallCropW, $smallCropH))));
+            }
+            if($resizeW-$smallCropW <= 0){
+                $hist['0-'.$y] = $this->_entropy($image->histogram(array(array(0, 0), array($smallCropW, $smallCropH))));
+            }
+        }
+        if($resizeH-$smallCropH <= 0){
+            $hist['0-0'] = $this->_entropy($image->histogram(array(array(0, 0), array($smallCropW, $smallCropH))));
+        }
+
+        asort($hist);
+        end($hist);
+        $pos = key($hist); // last key
+        list($x, $y) = explode('-', $pos);
+        $x = round($x*($origW / $resizeW));
+        $y = round($y*($origH / $resizeH));
+
+        return array($x,$y);
+    }
+}

+ 40 - 0
app/Helper/Grafika/Gd/Filter/Blur.php

xqd
@@ -0,0 +1,40 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Blurs the image.
+ */
+class Blur implements FilterInterface
+{
+
+    /**
+     * @var int
+     */
+    protected $amount;
+
+    /**
+     * Blur constructor.
+     * @param int $amount The amount of blur to apply. Possible values 1-100.
+     */
+    public function __construct($amount = 1)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply($image)
+    {
+        for ($i=0; $i < $this->amount; $i++) {
+            imagefilter($image->getCore(), IMG_FILTER_GAUSSIAN_BLUR);
+        }
+        return $image;
+    }
+}

+ 39 - 0
app/Helper/Grafika/Gd/Filter/Brightness.php

xqd
@@ -0,0 +1,39 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Change the image brightness.
+ *
+ * TODO: param checks
+ */
+class Brightness implements FilterInterface{
+
+    /**
+     * @var int
+     */
+    protected $amount; // -100 >= 0 >= 100
+
+    /**
+     * Brightness constructor.
+     * @param int $amount The amount of brightness to apply. >= -100 and <= -1 to darken. 0 for no change. >= 1 and <= 100 to brighten.
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+        imagefilter($image->getCore(), IMG_FILTER_BRIGHTNESS, ($this->amount * 2.55));
+        return $image;
+    }
+
+}

+ 50 - 0
app/Helper/Grafika/Gd/Filter/Colorize.php

xqd
@@ -0,0 +1,50 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Change the values for red, green and blue in an image.
+ */
+class Colorize implements FilterInterface{
+
+    /**
+     * @var int
+     */
+    protected $red; // -100 >= 0 >= 100
+    /**
+     * @var int
+     */
+    protected $green; // -100 >= 0 >= 100
+    /**
+     * @var int
+     */
+    protected $blue; // -100 >= 0 >= 100
+
+    /**
+     * Colorize constructor.
+     * @param int $red The amount of red colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add.
+     * @param int $green The amount of green colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add.
+     * @param int $blue The amount of blue colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add.
+     */
+    public function __construct($red, $green, $blue)
+    {
+        $this->red = round($red * 2.55);
+        $this->green = round($green * 2.55);
+        $this->blue = round($blue * 2.55);
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        imagefilter($image->getCore(), IMG_FILTER_COLORIZE, $this->red, $this->green, $this->blue);
+        return $image;
+    }
+
+}

+ 38 - 0
app/Helper/Grafika/Gd/Filter/Contrast.php

xqd
@@ -0,0 +1,38 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Change the contrast of an image. Contrast is the difference in luminance or colour that makes an object distinguishable.
+ */
+class Contrast implements FilterInterface{
+
+    /**
+     * @var int
+     */
+    protected $amount; // -100 >= 0 >= 100
+
+    /**
+     * Contrast constructor.
+     * @param int $amount The amount of contrast to apply. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to increase.
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        imagefilter($image->getCore(), IMG_FILTER_CONTRAST, ($this->amount * -1));
+        return $image;
+    }
+
+}

+ 190 - 0
app/Helper/Grafika/Gd/Filter/Dither.php

xqd
@@ -0,0 +1,190 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Dither image. Dithering will reduce the color to black and white and add noise.
+ */
+class Dither implements FilterInterface{
+
+	/**
+     * @var string Dithering algorithm to use.
+     */
+    private $type;
+
+    /**
+     * Dither an image.
+     *
+     * @param string $type Dithering algorithm to use. Options: diffusion, ordered. Defaults to diffusion.
+     */
+    public function __construct( $type = 'diffusion' )
+    {
+        $this->type = $type;
+    }
+
+
+    /**
+     * Apply filter.
+     *
+     * @param Image $image
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    public function apply( $image ) {
+        if ( $this->type === 'ordered' ) {
+            return $this->ordered( $image );
+        } else if ( $this->type === 'diffusion' ) {
+            return $this->diffusion( $image );
+        }
+        throw new \Exception( sprintf( 'Invalid dither type "%s".', $this->type ) );
+    }
+
+    /**
+     * Dither using error diffusion.
+     *
+     * @param Image $image
+     *
+     * @return Image
+     */
+    private function diffusion( $image ){
+        $pixel = array();
+
+        // Localize vars
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+        $old = $image->getCore();
+
+        $new = imagecreatetruecolor($width, $height);
+
+        for ( $y = 0; $y < $height; $y+=1 ) {
+            for ( $x = 0; $x < $width; $x+=1 ) {
+
+                $color = imagecolorat( $old, $x, $y );
+                $r = ($color >> 16) & 0xFF;
+                $g = ($color >> 8) & 0xFF;
+                $b = $color & 0xFF;
+
+                $gray = round($r * 0.3 + $g * 0.59 + $b * 0.11);
+
+                if(isset($pixel[$x][$y])){ // Add errors to color if there are
+                    $gray += $pixel[$x][$y];
+                }
+
+                if ( $gray <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess val due to adding the error
+                    $blackOrWhite = 0;
+                } else {
+                    $blackOrWhite = 255;
+                }
+
+                $oldPixel = $gray;
+                $newPixel = $blackOrWhite;
+
+                // Current pixel
+                imagesetpixel( $new, $x, $y,
+                    imagecolorallocate( $new,
+                        $newPixel,
+                        $newPixel,
+                        $newPixel
+                    )
+                );
+
+                $qError = $oldPixel - $newPixel; // Quantization error
+
+                // Propagate error on neighbor pixels
+                if ( $x + 1 < $width ) {
+                    $pixel[$x+1][$y] = (isset($pixel[$x+1][$y]) ? $pixel[$x+1][$y] : 0) + ($qError * (7 / 16));
+                }
+
+                if ( $x - 1 > 0 and $y + 1 < $height ) {
+                    $pixel[$x-1][$y+1] = (isset($pixel[$x-1][$y+1]) ? $pixel[$x-1][$y+1] : 0) + ($qError * (3 / 16));
+                }
+
+                if ( $y + 1 < $height ) {
+                    $pixel[$x][$y+1] = (isset($pixel[$x][$y+1]) ? $pixel[$x][$y+1] : 0) + ($qError * (5 / 16));
+                }
+
+                if ( $x + 1 < $width and $y + 1 < $height ) {
+                    $pixel[$x+1][$y+1] = (isset($pixel[$x+1][$y+1]) ? $pixel[$x+1][$y+1] : 0) + ($qError * (1 / 16));
+                }
+
+            }
+        }
+
+        imagedestroy($old); // Free resource
+        // Create new image with updated core
+        return new Image(
+            $new,
+            $image->getImageFile(),
+            $width,
+            $height,
+            $image->getType()
+        );
+    }
+
+    /**
+     * Dither by applying a threshold map.
+     *
+     * @param Image $image
+     *
+     * @return Image
+     */
+    private function ordered( $image ) {
+        // Localize vars
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $old    = $image->getCore();
+
+        $new = imagecreatetruecolor( $width, $height );
+
+        $thresholdMap = array(
+            array( 15, 135, 45, 165 ),
+            array( 195, 75, 225, 105 ),
+            array( 60, 180, 30, 150 ),
+            array( 240, 120, 210, 90 )
+        );
+
+        for ( $y = 0; $y < $height; $y += 1 ) {
+            for ( $x = 0; $x < $width; $x += 1 ) {
+
+                $color = imagecolorat( $old, $x, $y );
+                $r     = ( $color >> 16 ) & 0xFF;
+                $g     = ( $color >> 8 ) & 0xFF;
+                $b     = $color & 0xFF;
+
+                $gray = round( $r * 0.3 + $g * 0.59 + $b * 0.11 );
+
+                $threshold = $thresholdMap[ $x % 4 ][ $y % 4 ];
+                $oldPixel  = ( $gray + $threshold ) / 2;
+                if ( $oldPixel <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess value
+                    $newPixel = 0;
+                } else {
+                    $newPixel = 255;
+                }
+
+                // Current pixel
+                imagesetpixel( $new, $x, $y,
+                    imagecolorallocate( $new,
+                        $newPixel,
+                        $newPixel,
+                        $newPixel
+                    )
+                );
+
+            }
+        }
+
+        imagedestroy( $old ); // Free resource
+        // Create new image with updated core
+        return new Image(
+            $new,
+            $image->getImageFile(),
+            $width,
+            $height,
+            $image->getType()
+        );
+    }
+}

+ 38 - 0
app/Helper/Grafika/Gd/Filter/Gamma.php

xqd
@@ -0,0 +1,38 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Performs a gamma correction on an image.
+ */
+class Gamma implements FilterInterface{
+
+    /**
+     * @var float
+     */
+    protected $amount; // >= 1.0
+
+    /**
+     * Gamma constructor.
+     * @param float $amount The amount of gamma correction to apply. >= 1.0
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (float) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        imagegammacorrect($image->getCore(), 1, $this->amount);
+        return $image;
+    }
+
+}

+ 23 - 0
app/Helper/Grafika/Gd/Filter/Grayscale.php

xqd
@@ -0,0 +1,23 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Turn image into grayscale.
+ */
+class Grayscale implements FilterInterface{
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+        imagefilter($image->getCore(), IMG_FILTER_GRAYSCALE);
+        return $image;
+    }
+
+}

+ 24 - 0
app/Helper/Grafika/Gd/Filter/Invert.php

xqd
@@ -0,0 +1,24 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Invert the image colors.
+ */
+class Invert implements FilterInterface{
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        imagefilter($image->getCore(), IMG_FILTER_NEGATE);
+        return $image;
+    }
+
+}

+ 38 - 0
app/Helper/Grafika/Gd/Filter/Pixelate.php

xqd
@@ -0,0 +1,38 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Pixelate an image.
+ */
+class Pixelate implements FilterInterface{
+
+    /**
+     * @var int $amount Pixelate size from >= 1
+     */
+    protected $amount;
+
+    /**
+     * Pixelate constructor.
+     * @param int $amount The size of pixelation. >= 1
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        imagefilter($image->getCore(), IMG_FILTER_PIXELATE, $this->amount, true);
+        return $image;
+    }
+
+}

+ 49 - 0
app/Helper/Grafika/Gd/Filter/Sharpen.php

xqd
@@ -0,0 +1,49 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Sharpen an image.
+ */
+class Sharpen implements FilterInterface{
+
+    /**
+     * @var int $amount 
+     */
+    protected $amount;
+
+    /**
+     * Sharpen constructor.
+     * @param int $amount Amount of sharpening from >= 1 to <= 100
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+        $amount = $this->amount;
+        // build matrix
+        $min = $amount >= 10 ? $amount * -0.01 : 0;
+        $max = $amount * -0.025;
+        $abs = ((4 * $min + 4 * $max) * -1) + 1;
+        $div = 1;
+        $matrix = array(
+            array($min, $max, $min),
+            array($max, $abs, $max),
+            array($min, $max, $min)
+        );
+        // apply the matrix
+        imageconvolution($image->getCore(), $matrix, $div, 0);
+        return $image;
+    }
+
+}

+ 128 - 0
app/Helper/Grafika/Gd/Filter/Sobel.php

xqd
@@ -0,0 +1,128 @@
+<?php
+
+namespace Grafika\Gd\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Gd\Image;
+
+/**
+ * Sobel filter is an edge detection filter.
+ * @link https://en.wikipedia.org/wiki/Sobel_operator
+ */
+class Sobel implements FilterInterface
+{
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply($image)
+    {
+
+        // Localize vars
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $old     = $image->getCore();
+
+        $pixels = array();
+        $new    = imagecreatetruecolor($width, $height);
+        for ($y = 0; $y < $height; $y++) {
+            for ($x = 0; $x < $width; $x++) {
+                // row 0
+                if ($x > 0 and $y > 0) {
+                    $matrix[0][0] = $this->getColor($old, $pixels,$x - 1, $y - 1);
+                } else {
+                    $matrix[0][0] = $this->getColor($old, $pixels, $x, $y);
+                }
+
+                if ($y > 0) {
+                    $matrix[1][0] = $this->getColor($old, $pixels, $x, $y - 1);
+                } else {
+                    $matrix[1][0] = $this->getColor($old, $pixels, $x, $y);
+                }
+
+                if ($x + 1 < $width and $y > 0) {
+                    $matrix[2][0] = $this->getColor($old, $pixels, $x + 1, $y - 1);
+                } else {
+                    $matrix[2][0] = $this->getColor($old, $pixels, $x, $y);
+                }
+
+                // row 1
+                if ($x > 0) {
+                    $matrix[0][1] = $this->getColor($old, $pixels, $x - 1, $y);
+                } else {
+                    $matrix[0][1] = $this->getColor($old, $pixels, $x, $y);
+                }
+
+                if ($x + 1 < $width) {
+                    $matrix[2][1] = $this->getColor($old, $pixels, $x + 1, $y);
+                } else {
+                    $matrix[2][1] = $this->getColor($old, $pixels, $x, $y);
+                }
+
+                // row 1
+                if ($x > 0 and $y + 1 < $height) {
+                    $matrix[0][2] = $this->getColor($old, $pixels, $x - 1, $y + 1);
+                } else {
+                    $matrix[0][2] = $this->getColor($old, $pixels, $x, $y);
+                }
+
+                if ($y + 1 < $height) {
+                    $matrix[1][2] = $this->getColor($old, $pixels, $x, $y + 1);
+                } else {
+                    $matrix[1][2] = $this->getColor($old, $pixels, $x, $y);
+                }
+
+                if ($x + 1 < $width and $y + 1 < $height) {
+                    $matrix[2][2] = $this->getColor($old, $pixels, $x + 1, $y + 1);
+                } else {
+                    $matrix[2][2] = $this->getColor($old, $pixels, $x, $y);
+                }
+
+                $edge = $this->convolve($matrix);
+                $edge = intval($edge / 2);
+                if ($edge > 255) {
+                    $edge = 255;
+                }
+                $color = imagecolorallocate($new, $edge, $edge, $edge);
+                imagesetpixel($new, $x, $y, $color);
+
+            }
+        }
+        imagedestroy($old); // Free resource
+        // Create and return new image with updated core
+        return new Image(
+            $new,
+            $image->getImageFile(),
+            $width,
+            $height,
+            $image->getType()
+        );
+    }
+
+    private function convolve($matrix)
+    {
+        $gx = $matrix[0][0] + ($matrix[2][0] * -1) +
+              ($matrix[0][1] * 2) + ($matrix[2][1] * -2) +
+              $matrix[0][2] + ($matrix[2][2] * -1);
+
+        $gy = $matrix[0][0] + ($matrix[1][0] * 2) + $matrix[2][0] +
+              ($matrix[0][2] * -1) + ($matrix[1][2] * -2) + ($matrix[2][2] * -1);
+
+        return sqrt(($gx * $gx) + ($gy * $gy));
+    }
+
+    private function getColor($gd, &$pixels, $x, $y)
+    {
+        if (isset($pixels[$x][$y])) {
+            return $pixels[$x][$y];
+        }
+        $color = imagecolorat($gd, $x, $y);
+        $r     = ($color >> 16) & 0xFF;
+        $g     = ($color >> 8) & 0xFF;
+        $b     = $color & 0xFF;
+
+        return $pixels[$x][$y] = round($r * 0.3 + $g * 0.59 + $b * 0.11); // gray
+    }
+}

+ 123 - 0
app/Helper/Grafika/Gd/Helper/GifByteStream.php

xqd
@@ -0,0 +1,123 @@
+<?php
+
+namespace Grafika\Gd\Helper;
+
+/**
+ * Class GifByteStream
+ * Normalize string operations.
+ * Treat string as byte stream where 2 string characters are treated as 1 hex string (byte).
+ * Eg. String ffff with length 4 is 0xff 0xff in bytes with length of 2.
+ */
+final class GifByteStream
+{
+    /**
+     * @var int
+     */
+    private $position;
+    /**
+     * @var string
+     */
+    private $bytes;
+
+    /**
+     * GifByteStream constructor.
+     *
+     * @param string $bytes Accepts only the string created by unpack('H*')
+     */
+    public function __construct($bytes)
+    {
+        $this->position = 0;
+        $this->bytes    = $bytes;
+    }
+
+    /**
+     * Take a bite from the byte stream.
+     *
+     * @param int $size Byte size in integer.
+     *
+     * @return string
+     */
+    public function bite($size)
+    {
+        $str = substr($this->bytes, $this->position * 2, $size * 2);
+        $this->position += $size;
+
+        return $str;
+    }
+
+    /**
+     * @param $byteString
+     * @param $offset
+     *
+     * @return bool|float
+     */
+    public function find($byteString, $offset)
+    {
+        $pos = strpos($this->bytes, $byteString, $offset * 2);
+        if ($pos !== false) {
+            return $pos / 2;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param int $step
+     */
+    public function back($step = 1)
+    {
+        $this->position -= $step;
+    }
+
+    /**
+     * @param int $step
+     */
+    public function next($step = 1)
+    {
+        $this->position += $step;
+    }
+
+    /**
+     * @return float
+     */
+    public function length()
+    {
+        return strlen($this->bytes) / 2;
+    }
+
+    /**
+     * @param $position
+     */
+    public function setPosition($position)
+    {
+        $this->position = $position;
+    }
+
+    /**
+     * @return int
+     */
+    public function getPosition()
+    {
+        return $this->position;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getBytes()
+    {
+        return $this->bytes;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isEnd()
+    {
+        if ($this->position > $this->length() - 1) {
+            return true;
+        }
+
+        return false;
+    }
+}

+ 605 - 0
app/Helper/Grafika/Gd/Helper/GifHelper.php

xqd
@@ -0,0 +1,605 @@
+<?php
+namespace Grafika\Gd\Helper;
+
+final class GifHelper {
+
+    /**
+     * @param $imageFile
+     *
+     * @return GifByteStream
+     * @throws \Exception
+     */
+    public function open($imageFile){
+        $fp = fopen( $imageFile, 'rb'); // Binary read
+
+        if($fp === false ) {
+            throw new \Exception(sprintf('Error loading file: "%s".', $imageFile));
+        }
+
+        $size = filesize( $imageFile );
+        $bytes = fread($fp, $size);
+        $bytes = unpack('H*', $bytes); // Unpack as hex
+        $bytes = $bytes[1];
+        fclose($fp);
+
+        return new GifByteStream($bytes);
+    }
+
+    /**
+     * @param string $bin Raw binary data from imagegif or file_get_contents
+     *
+     * @return GifByteStream
+     */
+    public function load($bin){
+        $bytes = unpack('H*', $bin); // Unpack as hex
+        $bytes = $bytes[1];
+
+        return new GifByteStream($bytes);
+    }
+
+    /**
+     * @param GifByteStream $bytes
+     *
+     * @return bool
+     */
+    public function isAnimated($bytes){
+
+        $bytes->setPosition(13);
+        $lastPos = $bytes->getPosition();
+        $gceCount = 0;
+        while (($lastPos = $bytes->find('21f904', $lastPos))!== false) {
+            $gceCount++;
+            if($gceCount>1){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Encode data into GIF hex string.
+     *
+     * @param array $data The array returned by decode.
+     *
+     * @return string Hex string of GIF
+     */
+    public function encode($data){
+        $hex = '';
+        // header block
+        $hex .= $this->_fixSize($this->_asciiToHex($data['signature']),3);
+        $hex .= $this->_fixSize($this->_asciiToHex($data['version']),3);
+
+        // logical screen descriptor block
+        $hex .= $this->_switchEndian($this->_fixSize(dechex($data['canvasWidth']), 4));
+        $hex .= $this->_switchEndian($this->_fixSize(dechex($data['canvasHeight']), 4));
+        $packedField = decbin($data['globalColorTableFlag']);
+        $packedField .= $this->_fixSize(decbin($data['colorResolution']), 3);
+        $packedField .= decbin($data['sortFlag']);
+        $packedField .= $this->_fixSize(decbin($data['sizeOfGlobalColorTable']), 3);
+        $hex .= $this->_fixSize(dechex(bindec($packedField)), 2);
+        $hex .= $this->_fixSize(dechex($data['backgroundColorIndex']), 2);
+        $hex .= $this->_fixSize(dechex($data['pixelAspectRatio']), 2);
+
+        // global color table optional
+        if($data['globalColorTableFlag']>0) {
+            $hex .= $data['globalColorTable'];
+        }
+        // app ext optional
+        if(isset($data['applicationExtension'])){
+            foreach($data['applicationExtension'] as $app){
+                $hex .= '21ff0b';
+                $hex .= $this->_fixSize($this->_asciiToHex($app['appId']),8);
+                $hex .= $this->_fixSize($this->_asciiToHex($app['appCode']),3);
+                foreach($app['subBlocks'] as $subBlock){
+                    $len = $this->_fixSize(dechex(strlen($subBlock)/2),2);
+                    $hex .= $len.$subBlock;
+                }
+                $hex .= '00';
+            }
+        }
+
+        foreach($data['frames'] as $i=>$frame){
+
+            // graphics control optional
+            if(isset($frame['delayTime'])) {
+                $hex .= '21f904';
+                $packedField = '000'; // reserved
+                $packedField .= $this->_fixSize(decbin($frame['disposalMethod']), 3);
+                $packedField .= decbin($frame['userInputFlag']);
+                $packedField .= decbin($frame['transparentColorFlag']);
+                $hex .= $this->_fixSize(dechex(bindec($packedField)), 2);
+                $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['delayTime']), 4));
+                $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['transparentColorIndex']), 2));
+                $hex .= '00';
+            }
+
+            //image desc
+            $hex .= '2c';
+            $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['imageLeft']), 4));
+            $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['imageTop']), 4));
+            $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['imageWidth']), 4));
+            $hex .= $this->_switchEndian($this->_fixSize(dechex($frame['imageHeight']), 4));
+            $packedField = decbin($frame['localColorTableFlag']);
+            $packedField .= decbin($frame['interlaceFlag']);
+            $packedField .= decbin($frame['sortFlag']);
+            $packedField .= '00'; // reserved
+            $packedField .= $this->_fixSize(decbin($frame['sizeOfLocalColorTable']), 3);
+            $hex .= $this->_fixSize(dechex(bindec($packedField)), 2);
+
+            // local color table optional
+            if($frame['localColorTableFlag']>0){
+                $hex .= $frame['localColorTable'];
+            }
+
+            $hex .= $frame['imageData'];
+        }
+        $hex .= $data['trailer'];
+        return $hex;
+    }
+
+    /**
+     * Decode GIF into array of data for easy use in PHP userland.
+     *
+     * @param GifByteStream $bytes Decode byte stream into array of GIF blocks.
+     *
+     * @return array Array containing GIF data
+     * @throws \Exception
+     *
+     */
+    public function decode($bytes){
+        $bytes->setPosition(0);
+        $blocks = $this->decodeToBlocks($bytes);
+
+        return $this->expandBlocks($blocks);
+    }
+
+    /**
+     * Decompose GIF into its block components. The GIF blocks are in the order that they appear in the byte stream.
+     *
+     * @param GifByteStream $bytes
+     *
+     * @return array
+     * @throws \Exception
+     */
+    public function decodeToBlocks($bytes){
+        $bytes->setPosition(0);
+        $blocks = array();
+
+        // Header block
+        $blocks['header'] = $bytes->bite(6);
+
+        // Logical screen descriptor block
+        $part = $bytes->bite(2); // canvass w
+        $hex = $part;
+        $part = $bytes->bite(2); // canvass h
+        $hex .= $part;
+        $part = $bytes->bite(1); // packed field
+        $hex .= $part;
+        $bin = $this->_fixSize($this->_hexToBin($part),8);
+        $globalColorTableFlag = bindec(substr($bin, 0 ,1));
+        $sizeOfGlobalColorTable = bindec(substr($bin, 5 ,3));
+
+        $part = $bytes->bite(1); // backgroundColorIndex
+        $hex .= $part;
+        $part = $bytes->bite(1); // pixelAspectRatio
+        $hex .= $part;
+        $blocks['logicalScreenDescriptor'] = $hex;
+
+        // Global color table is optional so check its existence
+        if($globalColorTableFlag > 0){
+            // Formula: 3 * (2^(N+1))
+            $colorTableLength = 3*(pow(2,($sizeOfGlobalColorTable+1)));
+            $part = $bytes->bite($colorTableLength);
+            $blocks['globalColorTable'] = $part;
+        }
+
+
+        $commentC = $plainTextC = $appCount = $gce = $dc = 0; // index count
+        while(!$bytes->isEnd()){
+            $part = $bytes->bite(1);
+
+            if('21'===$part){ // block tests
+                $hex = $part;
+                $part = $bytes->bite(1);
+                if('ff'===$part) { // App extension block
+                    $hex .= $part;
+                    $part = $bytes->bite(1); // app name length should be 0x0b or int 11 but we check anyways
+                    $size = hexdec($part); // turn it to int
+                    $hex .= $part;
+                    $part = $bytes->bite($size); // app name
+                    $hex .= $part;
+                    while (!$bytes->isEnd()) { // loop thru all app sub blocks
+                        $nextSize = $bytes->bite(1);
+                        if($nextSize !== '00'){
+                            $hex .= $nextSize;
+                            $size = hexdec($nextSize);
+                            $part = $bytes->bite($size);
+                            $hex .= $part;
+                        } else {
+                            $hex .= $nextSize;
+                            $blocks['applicationExtension-'.$appCount] = $hex;
+                            break;
+                        }
+
+                    }
+
+                    $appCount++;
+                } else if('f9'===$part){ // graphic
+                    $hex .= $part;
+                    $part = $bytes->bite(1); // size
+                    $hex .= $part;
+                    $part = $bytes->bite(1); // packed field
+                    $hex .= $part;
+                    $part = $bytes->bite(2); // delay time
+                    $hex .= $part;
+                    $part = $bytes->bite(1); // trans color index
+                    $hex .= $part;
+                    $part = $bytes->bite(1); // terminator
+                    $hex .= $part;
+                    $blocks['graphicControlExtension-'.$gce] = $hex;
+                    $gce++;
+                } else if('01' === $part){ // plain text ext
+                    $hex .= $part;
+
+                    while (!$bytes->isEnd()) { // loop thru all app sub blocks
+                        $nextSize = $bytes->bite(1);
+                        if($nextSize !== '00'){
+                            $hex .= $nextSize;
+                            $size = hexdec($nextSize);
+                            $part = $bytes->bite($size);
+                            $hex .= $part;
+                        } else {
+                            $hex .= $nextSize;
+                            $blocks['plainTextExtension-'.$plainTextC] = $hex;
+                            break;
+                        }
+
+                    }
+                    $plainTextC++;
+                } else if('fe' === $part){ // comment ext
+                    $hex .= $part;
+
+                    while (!$bytes->isEnd()) { // loop thru all app sub blocks
+                        $nextSize = $bytes->bite(1);
+                        if($nextSize !== '00'){
+                            $hex .= $nextSize;
+                            $size = hexdec($nextSize);
+                            $part = $bytes->bite($size);
+                            $hex .= $part;
+                        } else {
+                            $hex .= $nextSize;
+                            $blocks['commentExtension-'.$commentC] = $hex;
+                            break;
+                        }
+
+                    }
+                    $commentC++;
+                }
+            } else if ('2c'===$part){ // image descriptors
+                $hex = $part;
+                $part = $bytes->bite(2); // imageLeft
+                $hex .= $part;
+                $part = $bytes->bite(2); // imageTop
+                $hex .= $part;
+                $part = $bytes->bite(2); // imageWidth
+                $hex .= $part;
+                $part = $bytes->bite(2); // imageHeight
+                $hex .= $part;
+                $part = $bytes->bite(1); // packed field
+                $hex .= $part;
+                $blocks['imageDescriptor-'.$dc] = $hex;
+                $bin = $this->_fixSize($this->_hexToBin($part), 8);
+                $localColorTableFlag = bindec(substr($bin, 0, 1));
+                $sizeOfLocalColorTable = bindec(substr($bin, 5, 3));
+
+                //LC
+                if($localColorTableFlag){
+                    // Formula: 3 * (2^(N+1))
+                    $localColorTableLen = 3 * (pow(2, ($sizeOfLocalColorTable + 1)));
+                    $part = $bytes->bite($localColorTableLen);
+                    $blocks['localColorTable-'.$dc] = $part;
+                }
+
+                // Image data
+                $part = $bytes->bite(1); // LZW code
+                $hex = $part;
+                while ($bytes->isEnd()===false) {
+                    $nextSize = $bytes->bite(1);
+                    $hex .= $nextSize;
+                    if($nextSize !== '00') {
+                        $subBlockLen = hexdec($nextSize);
+                        $subBlock    = $bytes->bite($subBlockLen);
+                        $hex .= $subBlock;
+                    } else {
+                        $blocks['imageData-'.$dc] = $hex;
+                        break;
+                    }
+
+                }
+
+                $dc++;
+
+            } else {
+                $blocks['trailer'] = $part;
+                break;
+            }
+        }
+        if($blocks['trailer']!=='3b'){
+            throw new \Exception('Error decoding GIF. Stopped at '.$bytes->getPosition().'. Length is '.$bytes->length().'.');
+        }
+
+        return $blocks;
+    }
+
+    /**
+     * Expand GIF blocks into useful info.
+     *
+     * @param array $blocks Accepts the array returned by decodeToBlocks
+     *
+     * @return array
+     */
+    public function expandBlocks($blocks){
+
+        $decoded = array();
+        foreach($blocks as $blockName=>$block){
+            $bytes = new GifByteStream($block);
+            if(false !== strpos($blockName, 'header')){
+                $part = $bytes->bite(3);
+                $decoded['signature'] = $this->_hexToAscii($part);
+                $part = $bytes->bite(3);
+                $decoded['version'] = $this->_hexToAscii($part);
+            } else if(false !== strpos($blockName, 'logicalScreenDescriptor')){
+                $part = $bytes->bite(2);
+                $decoded['canvasWidth'] = hexdec($this->_switchEndian($part));
+                $part = $bytes->bite(2);
+                $decoded['canvasHeight'] = hexdec($this->_switchEndian($part));
+                $part = $bytes->bite(1);
+                $bin = $this->_fixSize($this->_hexToBin($part), 8); // Make sure len is correct
+                $decoded['globalColorTableFlag'] = bindec(substr($bin, 0 ,1));
+                $decoded['colorResolution'] = bindec(substr($bin, 1 ,3));
+                $decoded['sortFlag'] = bindec(substr($bin, 4 ,1));
+                $decoded['sizeOfGlobalColorTable'] = bindec(substr($bin, 5 ,3));
+                $part = $bytes->bite(1);
+                $decoded['backgroundColorIndex'] = hexdec($part);
+                $part = $bytes->bite(1);
+                $decoded['pixelAspectRatio'] = hexdec($part);
+
+            } else if(false !== strpos($blockName, 'globalColorTable')){
+                $decoded['globalColorTable'] = $block;
+            } else if(false !== strpos($blockName, 'applicationExtension')){
+                $index = explode('-', $blockName, 2);
+                $index = $index[1];
+
+                $bytes->next(2); // Skip ext intro and label: 21 ff
+                $appNameSize = $bytes->bite(1); // 0x0b or 11 according to spec but we check anyways
+                $appNameSize = hexdec($appNameSize);
+                $appName = $this->_hexToAscii($bytes->bite($appNameSize));
+                $subBlocks = array();
+                while (!$bytes->isEnd()) { // loop thru all app sub blocks
+                    $nextSize = $bytes->bite(1);
+                    if($nextSize !== '00'){
+                        $size = hexdec($nextSize);
+                        $subBlocks[] = $bytes->bite($size);
+
+                    }
+                }
+                if($appName==='NETSCAPE2.0'){
+                    $decoded['applicationExtension'][$index]['appId'] = 'NETSCAPE';
+                    $decoded['applicationExtension'][$index]['appCode'] = '2.0';
+                    $decoded['applicationExtension'][$index]['subBlocks'] = $subBlocks;
+                    $decoded['loopCount'] = hexdec($this->_switchEndian(substr($subBlocks[0], 2, 4)));
+                } else {
+                    $decoded['applicationExtension'][$index]['appId'] = substr($appName, 0, 8);
+                    $decoded['applicationExtension'][$index]['appCode'] = substr($appName, 8, 3);
+                    $decoded['applicationExtension'][$index]['subBlocks'] = $subBlocks;
+                }
+            } else if(false !== strpos($blockName, 'graphicControlExtension')) {
+                $index = explode('-', $blockName, 2);
+                $index = $index[1];
+
+                $bytes->next(3); // Skip ext intro, label, and block size which is always 4: 21 f9 04
+                $part = $bytes->bite(1); // packed field
+                $bin = $this->_fixSize($this->_hexToBin($part), 8); // Make sure len is correct
+                $decoded['frames'][$index]['disposalMethod'] = bindec(substr($bin, 3 ,3));
+                $decoded['frames'][$index]['userInputFlag'] = bindec(substr($bin, 6 ,1));
+                $decoded['frames'][$index]['transparentColorFlag'] = bindec(substr($bin, 7 ,1));
+                $part = $bytes->bite(2);
+                $decoded['frames'][$index]['delayTime'] = hexdec($this->_switchEndian($part));
+                $part = $bytes->bite(1);
+                $decoded['frames'][$index]['transparentColorIndex'] = hexdec($part);
+            } else if(false !== strpos($blockName, 'imageDescriptor')) {
+                $index = explode('-', $blockName, 2);
+                $index = $index[1];
+
+                $bytes->next(1); // skip separator: 2c
+                $part                                               = $bytes->bite(2);
+                $decoded['frames'][$index]['imageLeft']             = hexdec($this->_switchEndian($part));
+                $part                                               = $bytes->bite(2);
+                $decoded['frames'][$index]['imageTop']              = hexdec($this->_switchEndian($part));
+                $part                                               = $bytes->bite(2);
+                $decoded['frames'][$index]['imageWidth']            = hexdec($this->_switchEndian($part));
+                $part                                               = $bytes->bite(2);
+                $decoded['frames'][$index]['imageHeight']           = hexdec($this->_switchEndian($part));
+                $part                                               = $bytes->bite(1); // packed field
+                $bin                                                = $this->_fixSize($this->_hexToBin($part),
+                    8);
+                $decoded['frames'][$index]['localColorTableFlag']   = bindec(substr($bin, 0, 1));
+                $decoded['frames'][$index]['interlaceFlag']         = bindec(substr($bin, 1, 1));
+                $decoded['frames'][$index]['sortFlag']              = bindec(substr($bin, 2, 1));
+                $decoded['frames'][$index]['sizeOfLocalColorTable'] = bindec(substr($bin, 5, 3));
+            } else if(false !== strpos($blockName, 'localColorTable')){
+                $index = explode('-', $blockName, 2);
+                $index = $index[1];
+                $decoded['frames'][$index]['localColorTable'] = $block;
+            } else if(false !== strpos($blockName, 'imageData')) {
+                $index = explode('-', $blockName, 2);
+                $index = $index[1];
+
+                $decoded['frames'][$index]['imageData'] = $block;
+            } else if($blockName === 'trailer') {
+                $decoded['trailer'] = $block;
+            }
+            unset($bytes);
+        }
+
+        return $decoded;
+    }
+
+    /**
+     * @param array $blocks The array returned by decode.
+     *
+     * @return array Array of images each containing 1 of each frames of the original image.
+     */
+    public function splitFrames($blocks){
+        $images = array();
+        if (isset($blocks['frames'])){
+            foreach($blocks['frames'] as $a=>$unused){
+                $images[$a] = $blocks;
+                unset($images[$a]['frames']); // remove all frames.
+                foreach($blocks['frames'] as $b=>$frame){
+                    if($a===$b){
+                        $images[$a]['frames'][0] = $frame; // Re-add frames but use only 1 frame and discard others
+                        break;
+                    }
+                }
+            }
+        }
+        return $images;
+    }
+
+    /**
+     * @param $blocks
+     * @param $newW
+     * @param $newH
+     *
+     * @return array $blocks
+     */
+    public function resize($blocks, $newW, $newH){
+        $images = $this->splitFrames($blocks);
+
+        // Loop on individual images and resize them using Gd
+        $firstFrameGd = null;
+        foreach($images as $imageIndex=>$image){
+            $hex = $this->encode($image);
+            $binaryRaw = pack('H*', $hex);
+
+            // Utilize gd for resizing
+            $old = imagecreatefromstring($binaryRaw);
+            $width  = imagesx($old);
+            $height = imagesy($old);
+            $new = imagecreatetruecolor($newW, $newH); // Create a blank image
+            if($firstFrameGd){
+                $new = $firstFrameGd;
+            }
+            // Account for frame imageLeft and imageTop
+            $cX = $newW / $blocks['canvasWidth']; // change x
+            $dX = $image['frames'][0]['imageLeft'];
+            $cY = $newH / $blocks['canvasHeight'];
+            $dY = $image['frames'][0]['imageTop'];
+
+            imagecopyresampled(
+                $new,
+                $old,
+                $dX * $cX,// dx
+                $dY * $cY, // dy
+                0,
+                0,
+                $image['frames'][0]['imageWidth'] * $cX,
+                $image['frames'][0]['imageHeight'] * $cY,
+                $width,
+                $height
+            );
+            ob_start();
+            imagegif($new);
+            $binaryRaw = ob_get_contents();
+            ob_end_clean();
+
+            if($firstFrameGd===null){
+                $firstFrameGd = $new;
+            }
+
+            // Hex of resized
+            $bytes = $this->load($binaryRaw);
+            $hexNew = $this->decode($bytes);
+
+
+
+            // Update original frames with hex from resized frames
+            $blocks['frames'][$imageIndex]['imageWidth'] = $hexNew['frames'][0]['imageWidth'];
+            $blocks['frames'][$imageIndex]['imageHeight'] = $hexNew['frames'][0]['imageHeight'];
+            $blocks['frames'][$imageIndex]['imageLeft'] = $hexNew['frames'][0]['imageLeft'];
+            $blocks['frames'][$imageIndex]['imageTop'] = $hexNew['frames'][0]['imageTop'];
+            $blocks['frames'][$imageIndex]['imageData'] = $hexNew['frames'][0]['imageData'];
+
+            // We use local color tables on each frame. This will result in faster processing since we dont have to process the global color table at the cost of a larger file size.
+            $blocks['frames'][$imageIndex]['localColorTableFlag'] = $hexNew['globalColorTableFlag'];
+            $blocks['frames'][$imageIndex]['localColorTable'] = $hexNew['globalColorTable'];
+            $blocks['frames'][$imageIndex]['sizeOfLocalColorTable'] = $hexNew['sizeOfGlobalColorTable'];
+            $blocks['frames'][$imageIndex]['transparentColorFlag'] = 0;
+        }
+        // Update dimensions or else imagecreatefromgif will choke.
+        $blocks['canvasWidth'] = $newW;
+        $blocks['canvasHeight'] = $newH;
+        // Disable flickering bug. Also we are using localColorTable anyways.
+        $blocks['globalColorTableFlag'] = 0;
+        $blocks['globalColorTable'] = '';
+        return $blocks;
+    }
+
+    /**
+     * @param $asciiString
+     *
+     * @return string
+     */
+    private function _asciiToHex($asciiString){
+        $chars = str_split($asciiString, 1);
+        $string = '';
+        foreach($chars as $char){
+            $string .= dechex(ord($char));
+        }
+        return $string;
+    }
+
+    /**
+     * @param $hexString
+     *
+     * @return string
+     */
+    private function _hexToAscii($hexString){
+        $bytes = str_split($hexString, 2);
+        $string = '';
+        foreach($bytes as $byte){
+            $string .= chr(hexdec($byte)); // convert hex to dec to ascii character. See http://www.ascii.cl/
+        }
+        return $string;
+    }
+
+    /**
+     * @param $hexString
+     *
+     * @return string
+     */
+    private function _hexToBin($hexString){
+        return base_convert($hexString, 16, 2);
+    }
+
+    /**
+     * @param $string
+     * @param $size
+     * @param string $char
+     *
+     * @return string
+     */
+    private function _fixSize($string, $size, $char='0'){
+        return str_pad($string, $size, $char, STR_PAD_LEFT);
+    }
+
+    /**
+     * @param $hexString
+     *
+     * @return string
+     */
+    private function _switchEndian($hexString) {
+        return implode('', array_reverse(str_split($hexString, 2)));
+    }
+}

+ 457 - 0
app/Helper/Grafika/Gd/Image.php

xqd
@@ -0,0 +1,457 @@
+<?php
+namespace Grafika\Gd;
+
+use Grafika\Gd\Helper\GifHelper;
+use Grafika\ImageType;
+use Grafika\ImageInterface;
+
+/**
+ * Image class for GD.
+ * @package Grafika\Gd
+ */
+final class Image implements ImageInterface {
+
+    /**
+     * @var resource GD resource ID.
+     */
+    private $gd;
+
+    /**
+     * @var string File path to image.
+     */
+    private $imageFile;
+
+    /**
+     * @var int Image width in pixels.
+     */
+    private $width;
+
+    /**
+     * @var int Image height in pixels.
+     */
+    private $height;
+
+    /**
+     * @var string Image type. See \Grafika\ImageType
+     */
+    private $type;
+
+    /**
+     * @var string Contains array of animated GIF data.
+     */
+    private $blocks;
+
+    /**
+     * @var bool True if animated GIF.
+     */
+    private $animated;
+
+    /**
+     * Image constructor.
+     *
+     * @param resource $gd Must use GD's imagecreate* family of functions to create a GD resource.
+     * @param string $imageFile
+     * @param int $width
+     * @param int $height
+     * @param string $type
+     * @param string $blocks
+     * @param bool $animated
+     */
+    public function __construct( $gd, $imageFile, $width, $height, $type, $blocks = '', $animated = false ) {
+        $this->gd         = $gd;
+        $this->imageFile  = $imageFile;
+        $this->width      = $width;
+        $this->height     = $height;
+        $this->type       = $type;
+        $this->blocks        = $blocks;
+        $this->animated = $animated;
+    }
+
+    /**
+     * Method called when 'clone' keyword is used.
+     */
+    public function __clone()
+    {
+        $original = $this->gd;
+        $copy = imagecreatetruecolor($this->width, $this->height);
+
+        imagecopy($copy, $original, 0, 0, 0, 0, $this->width, $this->height);
+
+        $this->gd = $copy;
+    }
+
+    /**
+     * Output a binary raw dump of an image in a specified format.
+     *
+     * @param string|ImageType $type Image format of the dump.
+     *
+     * @throws \Exception When unsupported type.
+     */
+    public function blob( $type = 'PNG' ) {
+
+        $type = strtoupper($type);
+        if ( ImageType::GIF == $type ) {
+
+            imagegif( $this->gd );
+
+        } else if ( ImageType::JPEG == $type ) {
+
+            imagejpeg( $this->gd );
+
+        } else if ( ImageType::PNG == $type ) {
+
+            imagepng( $this->gd );
+
+        } else if ( ImageType::WBMP == $type ) {
+
+            imagewbmp( $this->gd );
+
+        } else {
+            throw new \Exception( sprintf( 'File type "%s" not supported.', $type ) );
+        }
+    }
+
+    /**
+     * Create Image from image file.
+     *
+     * @param string $imageFile Path to image.
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    public static function createFromFile( $imageFile ) {
+        if ( ! file_exists( $imageFile ) ) {
+            throw new \Exception( sprintf( 'Could not open "%s". File does not exist.', $imageFile ) );
+        }
+
+        $type = self::_guessType( $imageFile );
+        if ( ImageType::GIF == $type ) {
+
+            return self::_createGif( $imageFile );
+
+        } else if ( ImageType::JPEG == $type ) {
+
+            return self::_createJpeg( $imageFile );
+
+        } else if ( ImageType::PNG == $type ) {
+
+            return self::_createPng( $imageFile );
+
+        } else if ( ImageType::WBMP == $type ) {
+
+            return self::_createWbmp( $imageFile );
+
+        } else {
+            throw new \Exception( sprintf( 'Could not open "%s". File type not supported.', $imageFile ) );
+        }
+    }
+
+    /**
+     * Create an Image from a GD resource. The file type defaults to unknown.
+     *
+     * @param resource $gd GD resource.
+     *
+     * @return Image
+     */
+    public static function createFromCore( $gd ) {
+        return new self( $gd, '', imagesx( $gd ), imagesy( $gd ), ImageType::UNKNOWN );
+    }
+
+    /**
+     * Create a blank image.
+     *
+     * @param int $width Width in pixels.
+     * @param int $height Height in pixels.
+     *
+     * @return Image
+     */
+    public static function createBlank($width = 1, $height = 1){
+
+        return new self(imagecreatetruecolor($width, $height), '', $width, $height, ImageType::UNKNOWN);
+
+    }
+
+    /**
+     * Set the blending mode for an image. Allows transparent overlays on top of an image.
+     *
+     * @param bool $flag True to enable blending mode.
+     * @return self
+     */
+    public function alphaBlendingMode( $flag ){
+        imagealphablending( $this->gd, $flag );
+
+        return $this;
+    }
+
+    /**
+     * Enable/Disable transparency
+     *
+     * @param bool $flag True to enable alpha mode.
+     * @return self
+     */
+    public function fullAlphaMode( $flag ){
+        if( true === $flag ){
+            $this->alphaBlendingMode( false ); // Must be false for full alpha mode to work
+        }
+        imagesavealpha( $this->gd, $flag );
+
+        return $this;
+    }
+
+    /**
+     * Returns animated flag.
+     *
+     * @return bool True if animated GIF.
+     */
+    public function isAnimated() {
+        return $this->animated;
+    }
+
+    /**
+     * Get GD resource ID.
+     *
+     * @return resource
+     */
+    public function getCore() {
+        return $this->gd;
+    }
+
+    /**
+     * Get image file path.
+     *
+     * @return string File path to image.
+     */
+    public function getImageFile() {
+        return $this->imageFile;
+    }
+
+    /**
+     * Get image width in pixels.
+     *
+     * @return int
+     */
+    public function getWidth() {
+        return $this->width;
+    }
+
+    /**
+     * Get image height in pixels.
+     *
+     * @return int
+     */
+    public function getHeight() {
+        return $this->height;
+    }
+
+    /**
+     * Get image type.
+     *
+     * @return string
+     */
+    public function getType() {
+        return $this->type;
+    }
+
+    /**
+     * Get blocks.
+     *
+     * @return string.
+     */
+    public function getBlocks() {
+        return $this->blocks;
+    }
+
+    /**
+     * Get histogram from an entire image or its sub-region.
+     *
+     * @param array|null $slice Array of slice information. array( array( 0,0), array(100,50)) means x,y is 0,0 and width,height is 100,50
+     *
+     * @return array Returns array containing RGBA bins array('r'=>array(), 'g'=>array(), 'b'=>array(), 'a'=>array())
+     */
+    public function histogram($slice = null)
+    {
+        $gd = $this->getCore();
+
+        if(null === $slice){
+            $sliceX = 0;
+            $sliceY = 0;
+            $sliceW = $this->getWidth();
+            $sliceH = $this->getHeight();
+        } else {
+            $sliceX = $slice[0][0];
+            $sliceY = $slice[0][1];
+            $sliceW = $slice[1][0];
+            $sliceH = $slice[1][1];
+        }
+
+        $rBin = array();
+        $gBin = array();
+        $bBin = array();
+        $aBin = array();
+        for ($y = $sliceY; $y < $sliceY+$sliceH; $y++) {
+            for ($x = $sliceX; $x < $sliceX+$sliceW; $x++) {
+                $rgb = imagecolorat($gd, $x, $y);
+                $a   = ($rgb >> 24) & 0x7F; // 127 in hex. These are binary operations.
+                $r   = ($rgb >> 16) & 0xFF;
+                $g   = ($rgb >> 8) & 0xFF;
+                $b   = $rgb & 0xFF;
+
+                if ( ! isset($rBin[$r])) {
+                    $rBin[$r] = 1;
+                } else {
+                    $rBin[$r]++;
+                }
+
+                if ( ! isset($gBin[$g])) {
+                    $gBin[$g] = 1;
+                } else {
+                    $gBin[$g]++;
+                }
+
+                if ( ! isset($bBin[$b])) {
+                    $bBin[$b] = 1;
+                } else {
+                    $bBin[$b]++;
+                }
+
+                if ( ! isset($aBin[$a])) {
+                    $aBin[$a] = 1;
+                } else {
+                    $aBin[$a]++;
+                }
+            }
+        }
+        return array(
+            'r' => $rBin,
+            'g' => $gBin,
+            'b' => $bBin,
+            'a' => $aBin
+        );
+    }
+
+    /**
+     * Load a GIF image.
+     *
+     * @param string $imageFile
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    private static function _createGif( $imageFile ){
+        $gift = new GifHelper();
+        $bytes = $gift->open($imageFile);
+        $animated = $gift->isAnimated($bytes);
+        $blocks = '';
+        if($animated){
+            $blocks = $gift->decode($bytes);
+        }
+        $gd = @imagecreatefromgif( $imageFile );
+
+        if(!$gd){
+            throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::GIF) );
+        }
+
+        return new self(
+            $gd,
+            $imageFile,
+            imagesx( $gd ),
+            imagesy( $gd ),
+            ImageType::GIF,
+            $blocks,
+            $animated
+        );
+    }
+
+    /**
+     * Load a JPEG image.
+     *
+     * @param string $imageFile File path to image.
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    private static function _createJpeg( $imageFile ){
+        $gd = @imagecreatefromjpeg( $imageFile );
+
+        if(!$gd){
+            throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::JPEG ) );
+        }
+
+        return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::JPEG );
+    }
+
+    /**
+     * Load a PNG image.
+     *
+     * @param string $imageFile File path to image.
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    private static function _createPng( $imageFile ){
+        $gd = @imagecreatefrompng( $imageFile );
+
+        if(!$gd){
+            throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::PNG) );
+        }
+
+        $image = new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::PNG );
+        $image->fullAlphaMode( true );
+        return $image;
+    }
+
+    /**
+     * Load a WBMP image.
+     *
+     * @param string $imageFile
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    private static function _createWbmp( $imageFile ){
+        $gd = @imagecreatefromwbmp( $imageFile );
+
+        if(!$gd){
+            throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::WBMP) );
+        }
+
+        return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::WBMP );
+    }
+
+    /**
+     * @param $imageFile
+     *
+     * @return string
+     */
+    private static function _guessType( $imageFile ){
+        // Values from http://php.net/manual/en/image.constants.php starting with IMAGETYPE_GIF.
+        // 0 - unknown,
+        // 1 - GIF,
+        // 2 - JPEG,
+        // 3 - PNG
+        // 15 - WBMP
+        list($width, $height, $type) = getimagesize( $imageFile );
+
+        unset($width, $height);
+
+        if ( 1 == $type) {
+
+            return ImageType::GIF;
+
+        } else if ( 2 == $type) {
+
+            return ImageType::JPEG;
+
+        } else if ( 3 == $type) {
+
+            return ImageType::PNG;
+
+        } else if ( 15 == $type) {
+
+            return ImageType::WBMP;
+
+        }
+
+        return ImageType::UNKNOWN;
+    }
+}

+ 71 - 0
app/Helper/Grafika/Gd/ImageHash/AverageHash.php

xqd
@@ -0,0 +1,71 @@
+<?php
+
+
+namespace Grafika\Gd\ImageHash;
+
+use Grafika\Gd\Editor;
+use Grafika\Gd\Image;
+
+/**
+ * AverageHash
+ *
+ * Algorithm:
+ * Reduce size. Remove high frequencies and detail by shrinking to 8x8 so that there are 64 total pixels.
+ * Reduce color. The tiny 8x8 picture is converted to a grayscale.
+ * Average the colors. Compute the mean value of the 64 colors.
+ * Compute the bits. Each bit is simply set based on whether the color value is above or below the mean.
+ * Construct the hash. Set the 64 bits into a 64-bit integer. The order does not matter, just as long as you are consistent.
+ *
+ * http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
+ *
+ * @package Grafika\Gd\ImageHash
+ */
+class AverageHash
+{
+
+    /**
+     * Generate and get the average hash of the image.
+     *
+     * @param Image $image
+     *
+     * @param Editor $editor
+     *
+     * @return string
+     */
+    public function hash($image, $editor)
+    {
+        // Resize the image.
+        $width = 8;
+        $height = 8;
+
+        $image = clone $image; // Make sure we are working on the clone if Image is passed
+        $editor->resizeExact($image, $width, $height); // Resize to exactly 8x8
+        $gd = $image->getCore();
+
+        // Create an array of greyscale pixel values.
+        $pixels = array();
+        for ($y = 0; $y < $height; $y++) {
+            for ($x = 0; $x < $width; $x++) {
+                $rgba = imagecolorat($gd, $x, $y);
+                $r = ($rgba >> 16) & 0xFF;
+                $g = ($rgba >> 8) & 0xFF;
+                $b = $rgba & 0xFF;
+
+                $pixels[] = floor(($r + $g + $b) / 3); // Gray
+            }
+        }
+
+        // Get the average pixel value.
+        $average = floor(array_sum($pixels) / count($pixels));
+        // Each hash bit is set based on whether the current pixels value is above or below the average.
+        $hash = '';
+        foreach ($pixels as $pixel) {
+            if ($pixel > $average) {
+                $hash .= '1';
+            } else {
+                $hash .= '0';
+            }
+        }
+        return $hash;
+    }
+}

+ 73 - 0
app/Helper/Grafika/Gd/ImageHash/DifferenceHash.php

xqd
@@ -0,0 +1,73 @@
+<?php
+
+
+namespace Grafika\Gd\ImageHash;
+
+use Grafika\Gd\Editor;
+use Grafika\Gd\Image;
+
+/**
+ * DifferenceHash
+ *
+ * Algorithm:
+ * Reduce size. The fastest way to remove high frequencies and detail is to shrink the image. In this case, shrink it to 9x8 so that there are 72 total pixels.
+ * Reduce color. Convert the image to a grayscale picture. This changes the hash from 72 pixels to a total of 72 colors.
+ * Compute the difference. The algorithm works on the difference between adjacent pixels. This identifies the relative gradient direction. In this case, the 9 pixels per row yields 8 differences between adjacent pixels. Eight rows of eight differences becomes 64 bits.
+ * Assign bits. Each bit is simply set based on whether the left pixel is brighter than the right pixel.
+ *
+ * http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
+ *
+ * @package Grafika\Gd\ImageHash
+ */
+class DifferenceHash
+{
+
+    /**
+     * Generate and get the difference hash of image.
+     *
+     * @param Image $image
+     *
+     * @param Editor $editor
+     *
+     * @return string
+     */
+    public function hash($image, $editor)
+    {
+        $width  = 9;
+        $height = 8;
+
+        $image = clone $image; // Make sure we are working on the clone if Image is passed
+        $editor->resizeExact($image, $width, $height); // Resize to exactly 9x8
+        $gd = $image->getCore();
+
+        // Build hash
+        $hash = '';
+        for ($y = 0; $y < $height; $y++) {
+            // Get the pixel value for the leftmost pixel.
+            $rgba = imagecolorat($gd, 0, $y);
+            $r    = ($rgba >> 16) & 0xFF;
+            $g    = ($rgba >> 8) & 0xFF;
+            $b    = $rgba & 0xFF;
+
+            $left = floor(($r + $g + $b) / 3);
+            for ($x = 1; $x < $width; $x++) {
+                // Get the pixel value for each pixel starting from position 1.
+                $rgba  = imagecolorat($gd, $x, $y);
+                $r     = ($rgba >> 16) & 0xFF;
+                $g     = ($rgba >> 8) & 0xFF;
+                $b     = $rgba & 0xFF;
+                $right = floor(($r + $g + $b) / 3);
+                // Each hash bit is set based on whether the left pixel is brighter than the right pixel.
+                if ($left > $right) {
+                    $hash .= '1';
+                } else {
+                    $hash .= '0';
+                }
+                // Prepare the next loop.
+                $left = $right;
+            }
+        }
+        $editor->free( $image );
+        return $hash;
+    }
+}

+ 394 - 0
app/Helper/Grafika/Grafika.php

xqd
@@ -0,0 +1,394 @@
+<?php
+
+namespace Grafika;
+
+use Grafika\Gd\DrawingObject\CubicBezier as GdCubicBezier;
+use Grafika\Gd\DrawingObject\Ellipse as GdEllipse;
+use Grafika\Gd\DrawingObject\Line as GdLine;
+use Grafika\Gd\DrawingObject\Polygon as GdPolygon;
+use Grafika\Gd\DrawingObject\QuadraticBezier as GdQuadraticBezier;
+use Grafika\Gd\DrawingObject\Rectangle as GdRectangle;
+use Grafika\Gd\Editor as GdEditor;
+use Grafika\Gd\Filter\Dither as GdDither;
+use Grafika\Gd\Filter\Blur as GdBlur;
+use Grafika\Gd\Filter\Brightness as GdBrightness;
+use Grafika\Gd\Filter\Colorize as GdColorize;
+use Grafika\Gd\Filter\Contrast as GdContrast;
+use Grafika\Gd\Filter\Gamma as GdGamma;
+use Grafika\Gd\Filter\Grayscale as GdGrayscale;
+use Grafika\Gd\Filter\Invert as GdInvert;
+use Grafika\Gd\Filter\Pixelate as GdPixelate;
+use Grafika\Gd\Filter\Sharpen as GdSharpen;
+use Grafika\Gd\Filter\Sobel as GdSobel;
+use Grafika\Gd\Image as GdImage;
+use Grafika\Imagick\DrawingObject\CubicBezier as ImagickCubicBezier;
+use Grafika\Imagick\DrawingObject\Ellipse as ImagickEllipse;
+use Grafika\Imagick\DrawingObject\Line as ImagickLine;
+use Grafika\Imagick\DrawingObject\Polygon as ImagickPolygon;
+use Grafika\Imagick\DrawingObject\QuadraticBezier as ImagickQuadraticBezier;
+use Grafika\Imagick\DrawingObject\Rectangle as ImagickRectangle;
+use Grafika\Imagick\Editor as ImagickEditor;
+use Grafika\Imagick\Filter\Blur as ImagickBlur;
+use Grafika\Imagick\Filter\Brightness as ImagickBrightness;
+use Grafika\Imagick\Filter\Colorize as ImagickColorize;
+use Grafika\Imagick\Filter\Contrast as ImagickContrast;
+use Grafika\Imagick\Filter\Gamma as ImagickGamma;
+use Grafika\Imagick\Filter\Dither as ImagickDither;
+use Grafika\Imagick\Filter\Grayscale as ImagickGrayscale;
+use Grafika\Imagick\Filter\Invert as ImagickInvert;
+use Grafika\Imagick\Filter\Pixelate as ImagickPixelate;
+use Grafika\Imagick\Filter\Sharpen as ImagickSharpen;
+use Grafika\Imagick\Filter\Sobel as ImagickSobel;
+use Grafika\Imagick\Image as ImagickImage;
+
+/**
+ * Contains factory methods for detecting editors, creating editors and images.
+ * @package Grafika
+ */
+class Grafika
+{
+
+    /**
+     * Grafika root directory
+     */
+    const DIR = __DIR__;
+
+    /**
+     * @var array $editorList List of editors to evaluate.
+     */
+    private static $editorList = array('Imagick', 'Gd');
+
+    /**
+     * Return path to directory containing fonts used in text operations.
+     *
+     * @return string
+     */
+    public static function fontsDir()
+    {
+        $ds = DIRECTORY_SEPARATOR;
+        return realpath(self::DIR . $ds . '..' . $ds . '..') . $ds . 'fonts';
+    }
+
+
+    /**
+     * Change the editor list order of evaluation globally.
+     *
+     * @param array $editorList
+     *
+     * @throws \Exception
+     */
+    public static function setEditorList($editorList){
+        if(!is_array($editorList)){
+            throw new \Exception('$editorList must be an array.');
+        }
+        self::$editorList = $editorList;
+    }
+
+    /**
+     * Detects and return the name of the first supported editor which can either be "Imagick" or "Gd".
+     *
+     * @param array $editorList Array of editor list names. Use this to change the order of evaluation for editors for this function call only. Default order of evaluation is Imagick then GD.
+     *
+     * @return string Name of available editor.
+     * @throws \Exception Throws exception if there are no supported editors.
+     */
+    public static function detectAvailableEditor($editorList = null)
+    {
+
+        if(null === $editorList){
+            $editorList = self::$editorList;
+        }
+
+        /* Get first supported editor instance. Order of editorList matter. */
+        foreach ($editorList as $editorName) {
+            if ('Imagick' === $editorName) {
+                $editorInstance = new ImagickEditor();
+            } else {
+                $editorInstance = new GdEditor();
+            }
+            /** @var EditorInterface $editorInstance */
+            if (true === $editorInstance->isAvailable()) {
+                return $editorName;
+            }
+        }
+
+        throw new \Exception('No supported editor.');
+    }
+
+    /**
+     * Creates the first available editor.
+     *
+     * @param array $editorList Array of editor list names. Use this to change the order of evaluation for editors. Default order of evaluation is Imagick then GD.
+     *
+     * @return EditorInterface
+     * @throws \Exception
+     */
+    public static function createEditor($editorList = array('Imagick', 'Gd'))
+    {
+        $editorName = self::detectAvailableEditor($editorList);
+        if ('Imagick' === $editorName) {
+            return new ImagickEditor();
+        } else {
+            return new GdEditor();
+        }
+    }
+
+    /**
+     * Create an image.
+     * @param string $imageFile Path to image file.
+     *
+     * @return ImageInterface
+     * @throws \Exception
+     */
+    public static function createImage($imageFile)
+    {
+        $editorName = self::detectAvailableEditor();
+        if ('Imagick' === $editorName) {
+            return ImagickImage::createFromFile($imageFile);
+        } else {
+            return GdImage::createFromFile($imageFile);
+        }
+    }
+
+
+    /**
+     * Create a blank image.
+     *
+     * @param int $width Width of image in pixels.
+     * @param int $height Height of image in pixels.
+     *
+     * @return ImageInterface
+     * @throws \Exception
+     */
+    public static function createBlankImage($width = 1, $height = 1)
+    {
+        $editorName = self::detectAvailableEditor();
+        if ('Imagick' === $editorName) {
+            return ImagickImage::createBlank($width, $height);
+        } else {
+            return GdImage::createBlank($width, $height);
+        }
+    }
+
+
+    /**
+     * Create a filter. Detects available editor to use.
+     *
+     * @param string $filterName The name of the filter.
+     *
+     * @return FilterInterface
+     * @throws \Exception
+     */
+    public static function createFilter($filterName)
+    {
+        $editorName = self::detectAvailableEditor();
+        $p = func_get_args();
+        if ('Imagick' === $editorName) {
+            switch ($filterName){
+                case 'Blur':
+                    return new ImagickBlur(
+                        (array_key_exists(1,$p) ? $p[1] : 1)
+                    );
+                case 'Brightness':
+                    return new ImagickBrightness(
+                        $p[1]
+                    );
+                case 'Colorize':
+                    return new ImagickColorize(
+                        $p[1], $p[2], $p[3]
+                    );
+                case 'Contrast':
+                    return new ImagickContrast(
+                        $p[1]
+                    );
+                case 'Dither':
+                    return new ImagickDither(
+                        $p[1]
+                    );
+                case 'Gamma':
+                    return new ImagickGamma(
+                        $p[1]
+                    );
+                case 'Grayscale':
+                    return new ImagickGrayscale();
+                case 'Invert':
+                    return new ImagickInvert();
+                case 'Pixelate':
+                    return new ImagickPixelate(
+                        $p[1]
+                    );
+                case 'Sharpen':
+                    return new ImagickSharpen(
+                        $p[1]
+                    );
+                case 'Sobel':
+                    return new ImagickSobel();
+            }
+            throw new \Exception('Invalid filter name.');
+        } else {
+            switch ($filterName){
+                case 'Blur':
+                    return new GdBlur(
+                        (array_key_exists(1,$p) ? $p[1] : 1)
+                    );
+                case 'Brightness':
+                    return new GdBrightness(
+                        $p[1]
+                    );
+                case 'Colorize':
+                    return new GdColorize(
+                        $p[1], $p[2], $p[3]
+                    );
+                case 'Contrast':
+                    return new GdContrast(
+                        $p[1]
+                    );
+                case 'Dither':
+                    return new GdDither(
+                        $p[1]
+                    );
+                case 'Gamma':
+                    return new GdGamma(
+                        $p[1]
+                    );
+                case 'Grayscale':
+                    return new GdGrayscale();
+                case 'Invert':
+                    return new GdInvert();
+                case 'Pixelate':
+                    return new GdPixelate(
+                        $p[1]
+                    );
+                case 'Sharpen':
+                    return new GdSharpen(
+                        $p[1]
+                    );
+                case 'Sobel':
+                    return new GdSobel();
+            }
+            throw new \Exception('Invalid filter name.');
+        }
+    }
+
+    /**
+     * Draws an object. Detects available editor to use.
+     *
+     * @param string $drawingObjectName The name of the DrawingObject.
+     *
+     * @return DrawingObjectInterface
+     * @throws \Exception
+     *
+     * We use array_key_exist() instead of isset() to be able to detect a parameter with a NULL value.
+     */
+    public static function createDrawingObject($drawingObjectName)
+    {
+        $editorName = self::detectAvailableEditor();
+        $p = func_get_args();
+        if ('Imagick' === $editorName) {
+            switch ($drawingObjectName){
+                case 'CubicBezier':
+                    return new ImagickCubicBezier(
+                        $p[1],
+                        $p[2],
+                        $p[3],
+                        $p[4],
+                        (array_key_exists(5,$p) ? $p[5] : '#000000')
+                    );
+                case 'Ellipse':
+                    return new ImagickEllipse(
+                        $p[1],
+                        $p[2],
+                        (array_key_exists(3,$p) ? $p[3] : array(0,0)),
+                        (array_key_exists(4,$p) ? $p[4] : 1),
+                        (array_key_exists(5,$p) ? $p[5] : '#000000'),
+                        (array_key_exists(6,$p) ? $p[6] : '#FFFFFF')
+                    );
+                case 'Line':
+                    return new ImagickLine(
+                        $p[1],
+                        $p[2],
+                        (array_key_exists(3,$p) ? $p[3] : 1),
+                        (array_key_exists(4,$p) ? $p[4] : '#000000')
+                    );
+                case 'Polygon':
+                    return new ImagickPolygon(
+                        $p[1],
+                        (array_key_exists(2,$p) ? $p[2] : 1),
+                        (array_key_exists(3,$p) ? $p[3] : '#000000'),
+                        (array_key_exists(4,$p) ? $p[4] : '#FFFFFF')
+                    );
+                case 'Rectangle':
+                    return new ImagickRectangle(
+                        $p[1],
+                        $p[2],
+                        (array_key_exists(3,$p) ? $p[3] : array(0,0)),
+                        (array_key_exists(4,$p) ? $p[4] : 1),
+                        (array_key_exists(5,$p) ? $p[5] : '#000000'),
+                        (array_key_exists(6,$p) ? $p[6] : '#FFFFFF')
+                    );
+                case 'QuadraticBezier':
+                    return new ImagickQuadraticBezier(
+                        $p[1],
+                        $p[2],
+                        $p[3],
+                        (array_key_exists(4,$p) ? $p[4] : '#000000')
+                    );
+
+            }
+            throw new \Exception('Invalid drawing object name.');
+        } else {
+            switch ($drawingObjectName) {
+                case 'CubicBezier':
+                    return new GdCubicBezier(
+                        $p[1],
+                        $p[2],
+                        $p[3],
+                        $p[4],
+                        (array_key_exists(5,$p) ? $p[5] : '#000000')
+                    );
+                case 'Ellipse':
+                    return new GdEllipse(
+                        $p[1],
+                        $p[2],
+                        (array_key_exists(3,$p) ? $p[3] : array(0,0)),
+                        (array_key_exists(4,$p) ? $p[4] : 1),
+                        (array_key_exists(5,$p) ? $p[5] : '#000000'),
+                        (array_key_exists(6,$p) ? $p[6] : '#FFFFFF')
+                    );
+                case 'Line':
+                    return new GdLine(
+                        $p[1],
+                        $p[2],
+                        (array_key_exists(3,$p) ? $p[3] : 1),
+                        (array_key_exists(4,$p) ? $p[4] : '#000000')
+                    );
+                case 'Polygon':
+                    return new GdPolygon(
+                        $p[1],
+                        (array_key_exists(2,$p) ? $p[2] : 1),
+                        (array_key_exists(3,$p) ? $p[3] : '#000000'),
+                        (array_key_exists(4,$p) ? $p[4] : '#FFFFFF')
+                    );
+                case 'Rectangle':
+                    return new GdRectangle(
+                        $p[1],
+                        $p[2],
+                        (array_key_exists(3,$p) ? $p[3] : array(0,0)),
+                        (array_key_exists(4,$p) ? $p[4] : 1),
+                        (array_key_exists(5,$p) ? $p[5] : '#000000'),
+                        (array_key_exists(6,$p) ? $p[6] : '#FFFFFF')
+                    );
+                case 'QuadraticBezier':
+                    return new GdQuadraticBezier(
+                        $p[1],
+                        $p[2],
+                        $p[3],
+                        (array_key_exists(4,$p) ? $p[4] : '#000000')
+                    );
+            }
+            throw new \Exception('Invalid drawing object name.');
+        }
+    }
+
+
+}

+ 80 - 0
app/Helper/Grafika/ImageInterface.php

xqd
@@ -0,0 +1,80 @@
+<?php
+namespace Grafika;
+
+/**
+ * Interface ImageInterface
+ * @package Grafika
+ */
+interface ImageInterface
+{
+
+    /**
+     * Output a binary raw dump of an image in a specified format.
+     *
+     * @param string|ImageType $type Image format of the dump. See Grafika\ImageType for supported formats.
+     */
+    public function blob($type);
+
+    /**
+     * Create a blank image.
+     *
+     * @param int $width Width of image in pixels.
+     * @param int $height Height of image in pixels.
+     *
+     * @return ImageInterface Instance of image.
+     */
+    public static function createBlank($width = 1, $height = 1);
+
+    /**
+     * Create Image from core.
+     *
+     * @param resource|\Imagick $core GD resource for GD editor or Imagick instance for Imagick editor
+     *
+     * @return ImageInterface Instance of image.
+     */
+    public static function createFromCore($core);
+
+    /**
+     * Create Image from image file.
+     *
+     * @param string $imageFile Path to image file.
+     *
+     * @return ImageInterface Instance of image.
+     */
+    public static function createFromFile($imageFile);
+
+    /**
+     * Get Image core.
+     *
+     * @return resource|\Imagick GD resource or Imagick instance
+     */
+    public function getCore();
+
+    /**
+     * @return int Height in pixels.
+     */
+    public function getHeight();
+
+    /**
+     * @return string File path to image if Image was created from an image file.
+     */
+    public function getImageFile();
+
+    /**
+     * @return string Type of image. See ImageType.
+     */
+    public function getType();
+
+    /**
+     * @return int Width in pixels.
+     */
+    public function getWidth();
+
+    /**
+     * Returns animated flag.
+     *
+     * @return bool True if animated GIF or false otherwise.
+     */
+    public function isAnimated();
+
+}

+ 21 - 0
app/Helper/Grafika/ImageType.php

xqd
@@ -0,0 +1,21 @@
+<?php
+
+namespace Grafika;
+
+/**
+ * Class ImageType. Represent the different image types for GD and Imagick consistently.
+ *
+ * @package Grafika
+ */
+class ImageType {
+
+    const UNKNOWN = '';
+
+    const GIF = 'GIF';
+
+    const JPEG = 'JPEG';
+
+    const PNG = 'PNG';
+
+    const WBMP = 'WBMP';
+}

+ 51 - 0
app/Helper/Grafika/Imagick/DrawingObject/CubicBezier.php

xqd
@@ -0,0 +1,51 @@
+<?php
+namespace Grafika\Imagick\DrawingObject;
+
+use Grafika\DrawingObject\CubicBezier as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Imagick\Image;
+use Grafika\ImageInterface;
+
+/**
+ * Class CubicBezier
+ * @package Grafika
+ */
+class CubicBezier extends Base implements DrawingObjectInterface
+{
+
+    /**
+     * @param ImageInterface $image
+     * @return Image
+     */
+    public function draw($image)
+    {
+        // Localize vars
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+        $imagick = $image->getCore();
+
+        $draw = new \ImagickDraw();
+
+        $strokeColor = new \ImagickPixel($this->getColor()->getHexString());
+        $fillColor = new \ImagickPixel('rgba(0,0,0,0)');
+
+        $draw->setStrokeOpacity(1);
+        $draw->setStrokeColor($strokeColor);
+        $draw->setFillColor($fillColor);
+
+        $points = array(
+            array('x'=> $this->point1[0], 'y'=> $this->point1[1]),
+            array('x'=> $this->control1[0], 'y'=> $this->control1[1]),
+            array('x'=> $this->control2[0], 'y'=> $this->control2[1]),
+            array('x'=> $this->point2[0], 'y'=> $this->point2[1]),
+        );
+        $draw->bezier($points);
+
+        // Render the draw commands in the ImagickDraw object
+        $imagick->drawImage($draw);
+
+        $type = $image->getType();
+        $file = $image->getImageFile();
+        return new Image($imagick, $file, $width, $height, $type); // Create new image with updated core
+    }
+}

+ 40 - 0
app/Helper/Grafika/Imagick/DrawingObject/Ellipse.php

xqd
@@ -0,0 +1,40 @@
+<?php
+namespace Grafika\Imagick\DrawingObject;
+
+use Grafika\DrawingObject\Ellipse as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\ImageInterface;
+
+/**
+ * Class Ellipse
+ * @package Grafika
+ */
+class Ellipse extends Base implements DrawingObjectInterface
+{
+
+    /**
+     * @param ImageInterface $image
+     * @return ImageInterface
+     */
+    public function draw($image)
+    {
+
+        $strokeColor = new \ImagickPixel($this->getBorderColor()->getHexString());
+        $fillColor = new \ImagickPixel($this->getFillColor()->getHexString());
+
+        $draw = new \ImagickDraw();
+        $draw->setStrokeColor($strokeColor);
+        $draw->setFillColor($fillColor);
+
+        $draw->setStrokeWidth($this->borderSize);
+
+        list($x, $y) = $this->pos;
+        $left = $x + $this->width / 2;
+        $top = $y + $this->height / 2;
+        $draw->ellipse($left, $top, $this->width/2, $this->height/2, 0, 360);
+
+        $image->getCore()->drawImage($draw);
+
+        return $image;
+    }
+}

+ 41 - 0
app/Helper/Grafika/Imagick/DrawingObject/Line.php

xqd
@@ -0,0 +1,41 @@
+<?php
+namespace Grafika\Imagick\DrawingObject;
+
+use Grafika\DrawingObject\Line as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Class Line
+ * @package Grafika
+ */
+class Line extends Base implements DrawingObjectInterface
+{
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function draw($image)
+    {
+
+        $strokeColor = new \ImagickPixel($this->getColor()->getHexString());
+
+        $draw = new \ImagickDraw();
+
+        $draw->setStrokeColor($strokeColor);
+
+        $draw->setStrokeWidth($this->thickness);
+
+        list($x1, $y1) = $this->point1;
+        list($x2, $y2) = $this->point2;
+        $draw->line($x1, $y1, $x2, $y2);
+
+        $image->getCore()->drawImage($draw);
+
+        return $image;
+    }
+
+
+}

+ 52 - 0
app/Helper/Grafika/Imagick/DrawingObject/Polygon.php

xqd
@@ -0,0 +1,52 @@
+<?php
+namespace Grafika\Imagick\DrawingObject;
+
+use Grafika\DrawingObject\Polygon as Base;
+use Grafika\DrawingObjectInterface;
+
+/**
+ * Class Polygon
+ * @package Grafika
+ */
+class Polygon extends Base implements DrawingObjectInterface{
+
+    public function draw( $image ) {
+        $draw = new \ImagickDraw();
+        $draw->setStrokeWidth($this->borderSize);
+
+        if(null !== $this->fillColor) {
+            $fillColor = new \ImagickPixel( $this->fillColor->getHexString() );
+            $draw->setFillColor($fillColor);
+        } else {
+            $draw->setFillOpacity(0);
+        }
+
+        if(null !== $this->borderColor) {
+            $borderColor = new \ImagickPixel( $this->borderColor->getHexString() );
+            $draw->setStrokeColor($borderColor);
+        } else {
+            $draw->setStrokeOpacity(0);
+        }
+
+        $draw->polygon($this->points());
+
+        $image->getCore()->drawImage($draw);
+
+        return $image;
+    }
+
+    protected function points(){
+        $points = array();
+        foreach($this->points as $i=>$pos){
+            $points[$i] = array(
+                'x' => $pos[0],
+                'y' => $pos[1]
+            );
+        }
+        if( count($points) < 3 ){
+            throw new \Exception('Polygon needs at least 3 points.');
+        }
+        return $points;
+    }
+
+}

+ 55 - 0
app/Helper/Grafika/Imagick/DrawingObject/QuadraticBezier.php

xqd
@@ -0,0 +1,55 @@
+<?php
+namespace Grafika\Imagick\DrawingObject;
+
+use Grafika\DrawingObject\QuadraticBezier as Base;
+use Grafika\DrawingObjectInterface;
+use Grafika\Imagick\Image;
+use Grafika\ImageInterface;
+
+/**
+ * Class QuadraticBezier
+ * @package Grafika
+ */
+class QuadraticBezier extends Base implements DrawingObjectInterface
+{
+    /**
+     * @param ImageInterface $image
+     * @return Image
+     */
+    public function draw($image)
+    {
+        // Localize vars
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+        $imagick = $image->getCore();
+
+        $draw = new \ImagickDraw();
+
+        $strokeColor = new \ImagickPixel($this->getColor()->getHexString());
+        $fillColor = new \ImagickPixel('rgba(0,0,0,0)');
+
+        $draw->setStrokeOpacity(1);
+        $draw->setStrokeColor($strokeColor);
+        $draw->setFillColor($fillColor);
+
+
+
+        list($x1, $y1) = $this->point1;
+        list($x2, $y2) = $this->control;
+        list($x3, $y3) = $this->point2;
+        $draw->pathStart();
+        $draw->pathMoveToAbsolute($x1, $y1);
+        $draw->pathCurveToQuadraticBezierAbsolute(
+            $x2, $y2,
+            $x3, $y3
+        );
+        $draw->pathFinish();
+
+        // Render the draw commands in the ImagickDraw object
+        $imagick->drawImage($draw);
+
+        $type = $image->getType();
+        $file = $image->getImageFile();
+        return new Image($imagick, $file, $width, $height, $type); // Create new image with updated core
+    }
+}

+ 46 - 0
app/Helper/Grafika/Imagick/DrawingObject/Rectangle.php

xqd
@@ -0,0 +1,46 @@
+<?php
+namespace Grafika\Imagick\DrawingObject;
+
+use Grafika\DrawingObject\Rectangle as Base;
+use Grafika\DrawingObjectInterface;
+
+/**
+ * Class Rectangle
+ * @package Grafika
+ */
+class Rectangle extends Base implements DrawingObjectInterface{
+
+    public function draw( $image ) {
+
+        $draw = new \ImagickDraw();
+        $draw->setStrokeWidth($this->borderSize);
+
+        if(null !== $this->fillColor) {
+            $fillColor = new \ImagickPixel( $this->fillColor->getHexString() );
+            $draw->setFillColor($fillColor);
+        } else {
+            $draw->setFillOpacity(0);
+        }
+
+        if(null !== $this->borderColor) {
+            $borderColor = new \ImagickPixel( $this->borderColor->getHexString() );
+            $draw->setStrokeColor($borderColor);
+        } else {
+            $draw->setStrokeOpacity(0);
+        }
+
+
+
+        $x1 = $this->pos[0];
+        $x2 = $x1 + $this->getWidth();
+        $y1 = $this->pos[1];
+        $y2 = $y1 + $this->getHeight();
+
+        $draw->rectangle( $x1, $y1, $x2, $y2 );
+
+        $image->getCore()->drawImage($draw);
+
+        return $image;
+    }
+
+}

+ 827 - 0
app/Helper/Grafika/Imagick/Editor.php

xqd
@@ -0,0 +1,827 @@
+<?php
+
+namespace Grafika\Imagick;
+
+use Grafika\DrawingObjectInterface;
+use Grafika\EditorInterface;
+use Grafika\FilterInterface;
+use Grafika\Grafika;
+use Grafika\ImageInterface;
+use Grafika\ImageType;
+use Grafika\Color;
+use Grafika\Imagick\ImageHash\DifferenceHash;
+use Grafika\Position;
+
+/**
+ * Imagick Editor class. Uses the PHP Imagick library.
+ * @package Grafika\Imagick
+ */
+final class Editor implements EditorInterface
+{
+
+    /**
+     * Apply a filter to the image. See Filters section for a list of available filters.
+     *
+     * @param Image $image
+     * @param FilterInterface $filter
+     *
+     * @return Editor
+     */
+    public function apply(&$image, $filter)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $image = $filter->apply($image);
+
+        return $this;
+    }
+
+    /**
+     * Blend two images together with the first image as the base and the second image on top. Supports several blend modes.
+     *
+     * @param Image $image1 The base image.
+     * @param Image $image2 The image placed on top of the base image.
+     * @param string $type The blend mode. Can be: normal, multiply, overlay or screen.
+     * @param float $opacity The opacity of $image2. Possible values 0.0 to 1.0 where 0.0 is fully transparent and 1.0 is fully opaque. Defaults to 1.0.
+     * @param string $position The position of $image2 on $image1. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to top-left.
+     * @param int $offsetX Number of pixels to add to the X position of $image2.
+     * @param int $offsetY Number of pixels to add to the Y position of $image2.
+     *
+     * @return Editor
+     * @throws \Exception When added image is outside of canvas or invalid blend type
+     */
+    public function blend(&$image1, $image2, $type='normal', $opacity = 1.0, $position = 'top-left', $offsetX = 0, $offsetY = 0 ){
+
+        // Turn into position object
+        $position = new Position($position, $offsetX, $offsetY);
+
+        // Position is for $image2. $image1 is canvas.
+        list($offsetX, $offsetY) = $position->getXY($image1->getWidth(), $image1->getHeight(), $image2->getWidth(), $image2->getHeight());
+
+        // Check if it overlaps
+        if( ($offsetX >= $image1->getWidth() ) or
+            ($offsetX + $image2->getWidth() <= 0) or
+            ($offsetY >= $image1->getHeight() ) or
+            ($offsetY + $image2->getHeight() <= 0)){
+
+            throw new \Exception('Invalid blending. Image 2 is outside the canvas.');
+        }
+
+        // Loop start X
+        $loopStartX = 0;
+        $canvasStartX = $offsetX;
+        if($canvasStartX < 0){
+            $diff = 0 - $canvasStartX;
+            $loopStartX += $diff;
+        }
+
+        // Loop start Y
+        $loopStartY = 0;
+        $canvasStartY = $offsetY;
+        if($canvasStartY < 0){
+            $diff = 0 - $canvasStartY;
+            $loopStartY += $diff;
+        }
+
+        if ( $opacity !== 1 ) {
+            $this->opacity($image2, $opacity);
+        }
+
+        $type = strtolower( $type );
+        if($type==='normal') {
+            $image1->getCore()->compositeImage($image2->getCore(), \Imagick::COMPOSITE_OVER, $loopStartX + $offsetX, $loopStartY + $offsetY);
+        } else if($type==='multiply'){
+            $image1->getCore()->compositeImage($image2->getCore(), \Imagick::COMPOSITE_MULTIPLY, $loopStartX + $offsetX, $loopStartY + $offsetY);
+        } else if($type==='overlay'){
+            $image1->getCore()->compositeImage($image2->getCore(), \Imagick::COMPOSITE_OVERLAY, $loopStartX + $offsetX, $loopStartY + $offsetY);
+        } else if($type==='screen'){
+            $image1->getCore()->compositeImage($image2->getCore(), \Imagick::COMPOSITE_SCREEN, $loopStartX + $offsetX, $loopStartY + $offsetY);
+        } else {
+            throw new \Exception(sprintf('Invalid blend type "%s".', $type));
+        }
+
+        return $this;
+    }
+
+    /**
+     * Compare two images and returns a hamming distance. A value of 0 indicates a likely similar picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is likely a different image.
+     *
+     * @param ImageInterface|string $image1
+     * @param ImageInterface|string $image2
+     *
+     * @return int Hamming distance. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
+     * @throws \Exception
+     */
+    public function compare($image1, $image2)
+    {
+
+        if (is_string($image1)) { // If string passed, turn it into a Image object
+            $image1 = Image::createFromFile($image1);
+            $this->flatten( $image1 );
+        }
+
+        if (is_string($image2)) { // If string passed, turn it into a Image object
+            $image2 = Image::createFromFile($image2);
+            $this->flatten( $image2 );
+        }
+
+        $hash = new DifferenceHash();
+
+        $bin1     = $hash->hash($image1, $this);
+        $bin2     = $hash->hash($image2, $this);
+        $str1     = str_split($bin1);
+        $str2     = str_split($bin2);
+        $distance = 0;
+        foreach ($str1 as $i => $char) {
+            if ($char !== $str2[$i]) {
+                $distance++;
+            }
+        }
+
+        return $distance;
+
+    }
+
+    /**
+     * Crop the image to the given dimension and position.
+     *
+     * @param Image $image
+     * @param int $cropWidth Crop width in pixels.
+     * @param int $cropHeight Crop Height in pixels.
+     * @param string $position The crop position. Possible values top-left, top-center, top-right, center-left, center, center-right, bottom-left, bottom-center, bottom-right and smart. Defaults to center.
+     * @param int $offsetX Number of pixels to add to the X position of the crop.
+     * @param int $offsetY Number of pixels to add to the Y position of the crop.
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function crop(&$image, $cropWidth, $cropHeight, $position = 'center', $offsetX = 0, $offsetY = 0)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        if ( 'smart' === $position ) { // Smart crop
+            list( $x, $y ) = $this->_smartCrop( $image, $cropWidth, $cropHeight );
+        } else {
+            // Turn into an instance of Position
+            $position = new Position( $position, $offsetX, $offsetY );
+
+            // Crop position as x,y coordinates
+            list( $x, $y ) = $position->getXY( $image->getWidth(), $image->getHeight(), $cropWidth, $cropHeight );
+
+        }
+
+        $image->getCore()->cropImage($cropWidth, $cropHeight, $x, $y);
+
+        return $this;
+    }
+
+    /**
+     * Draw a DrawingObject on the image. See Drawing Objects section.
+     *
+     * @param Image $image
+     * @param DrawingObjectInterface $drawingObject
+     *
+     * @return $this
+     */
+    public function draw(&$image, $drawingObject)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $image = $drawingObject->draw($image);
+
+        return $this;
+    }
+
+    /**
+     * Compare if two images are equal. It will compare if the two images are of the same width and height. If the dimensions differ, it will return false. If the dimensions are equal, it will loop through each pixels. If one of the pixel don't match, it will return false. The pixels are compared using their RGB (Red, Green, Blue) values.
+     *
+     * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image.
+     * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image.
+     *
+     * @return bool True if equals false if not. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
+     * @throws \Exception
+     */
+    public function equal($image1, $image2)
+    {
+
+        if (is_string($image1)) { // If string passed, turn it into a Image object
+            $image1 = Image::createFromFile($image1);
+            $this->flatten( $image1 );
+        }
+
+        if (is_string($image2)) { // If string passed, turn it into a Image object
+            $image2 = Image::createFromFile($image2);
+            $this->flatten( $image2 );
+        }
+
+        // Check if image dimensions are equal
+        if ($image1->getWidth() !== $image2->getWidth() or $image1->getHeight() !== $image2->getHeight()) {
+
+            return false;
+
+        } else {
+
+            // Loop using image1
+            $pixelIterator = $image1->getCore()->getPixelIterator();
+            foreach ($pixelIterator as $row => $pixels) { /* Loop through pixel rows */
+                foreach ($pixels as $column => $pixel) { /* Loop through the pixels in the row (columns) */
+                    /**
+                     * Get image1 pixel
+                     * @var $pixel \ImagickPixel
+                     */
+                    $rgba1 = $pixel->getColor();
+
+                    // Get image2 pixel
+                    $rgba2 = $image2->getCore()->getImagePixelColor($column, $row)->getColor();
+
+                    // Compare pixel value
+                    if (
+                        $rgba1['r'] !== $rgba2['r'] or
+                        $rgba1['g'] !== $rgba2['g'] or
+                        $rgba1['b'] !== $rgba2['b']
+                    ) {
+                        return false;
+                    }
+                }
+                $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Fill entire image with color.
+     *
+     * @param Image $image
+     * @param Color $color Color object
+     * @param int $x X-coordinate of start point
+     * @param int $y Y-coordinate of start point
+     *
+     * @return self
+     */
+    public function fill(&$image, $color, $x = 0, $y = 0)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $target = $image->getCore()->getImagePixelColor($x, $y);
+        $image->getCore()->floodfillPaintImage($color->getHexString(), 1, $target, $x, $y, false);
+
+        return $this;
+    }
+
+    /**
+     * Flatten if animated GIF. Do nothing otherwise.
+     *
+     * @param Image $image
+     *
+     * @return self
+     */
+    public function flatten(&$image){
+        if($image->isAnimated()){
+            $imagick = $image->getCore()->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
+            $image = new Image(
+                $imagick,
+                $image->getImageFile(),
+                $image->getWidth(),
+                $image->getHeight(),
+                $image->getType(),
+                '', // blocks
+                false // animated
+            );
+        }
+        return $this;
+    }
+
+    /**
+     * Flip or mirrors the image.
+     *
+     * @param Image $image
+     * @param string $mode The type of flip: 'h' for horizontal flip or 'v' for vertical.
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function flip(&$image, $mode){
+        if ($mode === 'h') {
+            $image->getCore()->flopImage();
+        } else if ($mode === 'v') {
+            $image->getCore()->flipImage();
+        } else {
+            throw new \Exception(sprintf('Unsupported mode "%s"', $mode));
+        }
+        return $this;
+    }
+
+    /**
+     * Free the image clearing resources associated with it.
+     *
+     * @param Image $image
+     *
+     * @return Editor
+     */
+    public function free( &$image )
+    {
+        $image->getCore()->clear();
+        return $this;
+    }
+
+    /**
+     * Checks if the editor is available on the current PHP install.
+     *
+     * @return bool True if available false if not.
+     */
+    public function isAvailable()
+    {
+        // First, test Imagick's extension and classes.
+        if (false === extension_loaded('imagick') ||
+            false === class_exists('Imagick') ||
+            false === class_exists('ImagickDraw') ||
+            false === class_exists('ImagickPixel') ||
+            false === class_exists('ImagickPixelIterator')
+        ) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Sets the image to the specified opacity level where 1.0 is fully opaque and 0.0 is fully transparent.
+     *
+     * @param Image $image
+     * @param float $opacity
+     *
+     * @return self
+     * @throws \Exception
+     */
+    public function opacity(&$image, $opacity)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        // Bounds checks
+        $opacity = ($opacity > 1) ? 1 : $opacity;
+        $opacity = ($opacity < 0) ? 0 : $opacity;
+
+        $image->getCore()->setImageOpacity($opacity);
+
+        return $this;
+    }
+
+    /**
+     * Open an image file and assign Image to first parameter.
+     *
+     * @param Image $image
+     * @param string $imageFile
+     *
+     * @return Editor
+     */
+    public function open(&$image, $imageFile){
+        $image = Image::createFromFile( $imageFile );
+        return $this;
+    }
+
+    /**
+     * Wrapper function for the resizeXXX family of functions. Resize image given width, height and mode.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     * @param string $mode Resize mode. Possible values: "exact", "exactHeight", "exactWidth", "fill", "fit".
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function resize(&$image, $newWidth, $newHeight, $mode = 'fit')
+    {
+        /*
+         * Resize formula:
+         * ratio = w / h
+         * h = w / ratio
+         * w = h * ratio
+         */
+        switch ($mode) {
+            case 'exact':
+                $this->resizeExact($image, $newWidth, $newHeight);
+                break;
+            case 'fill':
+                $this->resizeFill($image, $newWidth, $newHeight);
+                break;
+            case 'exactWidth':
+                $this->resizeExactWidth($image, $newWidth);
+                break;
+            case 'exactHeight':
+                $this->resizeExactHeight($image, $newHeight);
+                break;
+            case 'fit':
+                $this->resizeFit($image, $newWidth, $newHeight);
+                break;
+            default:
+                throw new \Exception(sprintf('Invalid resize mode "%s".', $mode));
+        }
+
+        return $this;
+    }
+
+    /**
+     * Resize image to exact dimensions ignoring aspect ratio. Useful if you want to force exact width and height.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return self
+     */
+    public function resizeExact(&$image, $newWidth, $newHeight)
+    {
+
+        $this->_resize($image, $newWidth, $newHeight);
+
+        return $this;
+    }
+
+    /**
+     * Resize image to exact height. Width is auto calculated. Useful for creating row of images with the same height.
+     *
+     * @param Image $image
+     * @param int $newHeight Height in pixels.
+     *
+     * @return self
+     */
+    public function resizeExactHeight(&$image, $newHeight)
+    {
+
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $ratio  = $width / $height;
+
+        $resizeHeight = $newHeight;
+        $resizeWidth  = $newHeight * $ratio;
+
+        $this->_resize($image, $resizeWidth, $resizeHeight);
+
+        return $this;
+    }
+
+    /**
+     * Resize image to exact width. Height is auto calculated. Useful for creating column of images with the same width.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     *
+     * @return self
+     */
+    public function resizeExactWidth(&$image, $newWidth)
+    {
+
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $ratio  = $width / $height;
+
+        $resizeWidth  = $newWidth;
+        $resizeHeight = round($newWidth / $ratio);
+
+        $this->_resize($image, $resizeWidth, $resizeHeight);
+
+        return $this;
+    }
+
+    /**
+     * Resize image to fill all the space in the given dimension. Excess parts are cropped.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return self
+     */
+    public function resizeFill(&$image, $newWidth, $newHeight)
+    {
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $ratio  = $width / $height;
+
+        // Base optimum size on new width
+        $optimumWidth  = $newWidth;
+        $optimumHeight = round($newWidth / $ratio);
+
+        if (($optimumWidth < $newWidth) or ($optimumHeight < $newHeight)) { // Oops, where trying to fill and there are blank areas
+            // So base optimum size on height instead
+            $optimumWidth  = $newHeight * $ratio;
+            $optimumHeight = $newHeight;
+        }
+
+        $this->_resize($image, $optimumWidth, $optimumHeight);
+        $this->crop($image, $newWidth, $newHeight); // Trim excess parts
+
+        return $this;
+    }
+
+    /**
+     * Resize image to fit inside the given dimension. No part of the image is lost.
+     *
+     * @param Image $image
+     * @param int $newWidth Width in pixels.
+     * @param int $newHeight Height in pixels.
+     *
+     * @return self
+     */
+    public function resizeFit(&$image, $newWidth, $newHeight)
+    {
+
+        $width  = $image->getWidth();
+        $height = $image->getHeight();
+        $ratio  = $width / $height;
+
+        // Try basing it on width first
+        $resizeWidth  = $newWidth;
+        $resizeHeight = round($newWidth / $ratio);
+
+        if (($resizeWidth > $newWidth) or ($resizeHeight > $newHeight)) { // Oops, either with or height does not fit
+            // So base on height instead
+            $resizeHeight = $newHeight;
+            $resizeWidth  = $newHeight * $ratio;
+        }
+
+        $this->_resize($image, $resizeWidth, $resizeHeight);
+
+        return $this;
+    }
+
+    /**
+     * Rotate an image counter-clockwise.
+     *
+     * @param Image $image
+     * @param int $angle The angle in degrees.
+     * @param Color|null $color The Color object containing the background color.
+     *
+     * @return EditorInterface An instance of image editor.
+     */
+    public function rotate(&$image, $angle, $color = null)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $color = ($color !== null) ? $color : new Color('#000000');
+        list($r, $g, $b, $alpha) = $color->getRgba();
+
+        $image->getCore()->rotateImage(new \ImagickPixel("rgba($r, $g, $b, $alpha)"), $angle * -1);
+
+        return $this;
+    }
+
+    /**
+     * Save the image to an image format.
+     *
+     * @param Image $image
+     * @param string $file File path where to save the image.
+     * @param null|string $type Type of image. Can be null, "GIF", "PNG", or "JPEG".
+     * @param null|string $quality Quality of image. Applies to JPEG only. Accepts number 0 - 100 where 0 is lowest and 100 is the highest quality. Or null for default.
+     * @param bool|false $interlace Set to true for progressive JPEG. Applies to JPEG only.
+     * @param int $permission Default permission when creating non-existing target directory.
+     *
+     * @return Editor
+     * @throws \Exception
+     */
+    public function save( $image, $file, $type = null, $quality = null, $interlace = false, $permission = 0755)
+    {
+
+        if (null === $type) {
+
+            $type = $this->_getImageTypeFromFileName($file); // Null given, guess type from file extension
+            if (ImageType::UNKNOWN === $type) {
+                $type = $image->getType(); // 0 result, use original image type
+            }
+        }
+
+        $targetDir = dirname($file); // $file's directory
+        if (false === is_dir($targetDir)) { // Check if $file's directory exist
+            // Create and set default perms to 0755
+            if ( ! mkdir($targetDir, $permission, true)) {
+                throw new \Exception(sprintf('Cannot create %s', $targetDir));
+            }
+        }
+
+        switch (strtoupper($type)) {
+            case ImageType::GIF :
+                $image->getCore()->writeImages($file, true); // Support animated image. Eg. GIF
+                break;
+
+            case ImageType::PNG :
+                // PNG is lossless and does not need compression. Although GD allow values 0-9 (0 = no compression), we leave it alone.
+                $image->getCore()->setImageFormat($type);
+                $image->getCore()->writeImage($file);
+                break;
+
+            default: // Defaults to jpeg
+                $quality = ($quality === null) ? 75 : $quality; // Default to 75 (GDs default) if null.
+                $quality = ($quality > 100) ? 100 : $quality;
+                $quality = ($quality <= 0) ? 1 : $quality; // Note: If 0 change it to 1. The lowest quality in Imagick is 1 whereas in GD its 0.
+
+                if ($interlace) {
+                    $image->getCore()->setImageInterlaceScheme(\Imagick::INTERLACE_JPEG);
+                }
+                $image->getCore()->setImageFormat($type);
+                $image->getCore()->setImageCompression(\Imagick::COMPRESSION_JPEG);
+                $image->getCore()->setImageCompressionQuality($quality);
+                $image->getCore()->writeImage($file); // Single frame image. Eg. JPEG
+        }
+
+        return $this;
+    }
+
+    /**
+     * Write text to image.
+     *
+     * @param Image $image
+     * @param string $text The text to be written.
+     * @param int $size The font size. Defaults to 12.
+     * @param int $x The distance from the left edge of the image to the left of the text. Defaults to 0.
+     * @param int $y The distance from the top edge of the image to the top of the text. Defaults to 12 (equal to font size) so that the text is placed within the image.
+     * @param Color $color The Color object. Default text color is black.
+     * @param string $font Full path to font file. If blank, will default to Liberation Sans font.
+     * @param int $angle Angle of text from 0 - 359. Defaults to 0.
+     *
+     * @return EditorInterface
+     * @throws \Exception
+     */
+    public function text(&$image, $text, $size = 12, $x = 0, $y = 0, $color = null, $font = '', $angle = 0)
+    {
+
+        if ($image->isAnimated()) { // Ignore animated GIF for now
+            return $this;
+        }
+
+        $y += $size;
+
+        $color = ($color !== null) ? $color : new Color('#000000');
+        $font  = ($font !== '') ? $font : Grafika::fontsDir() . DIRECTORY_SEPARATOR . 'LiberationSans-Regular.ttf';
+
+        list($r, $g, $b, $alpha) = $color->getRgba();
+
+        // Set up draw properties
+        $draw = new \ImagickDraw();
+        // Text color
+        $draw->setFillColor(new \ImagickPixel("rgba($r, $g, $b, $alpha)"));
+        // Font properties
+        $draw->setFont($font);
+        $draw->setFontSize($size);
+
+        // Write text
+        $image->getCore()->annotateImage(
+            $draw,
+            $x,
+            $y,
+            $angle,
+            $text
+        );
+
+        return $this;
+    }
+
+    /**
+     * Calculate entropy based on histogram.
+     *
+     * @param array $hist Histogram returned by Image->histogram
+     *
+     * @return float|int
+     */
+    private function _entropy($hist){
+        $entropy = 0;
+        $hist_size = array_sum($hist['r']) + array_sum($hist['g']) + array_sum($hist['b']);
+        foreach($hist['r'] as $p){
+            $p = $p / $hist_size;
+            $entropy += $p * log($p, 2);
+        }
+        foreach($hist['g'] as $p){
+            $p = $p / $hist_size;
+            $entropy += $p * log($p, 2);
+        }
+        foreach($hist['b'] as $p){
+            $p = $p / $hist_size;
+            $entropy += $p * log($p, 2);
+        }
+        return $entropy * -1;
+    }
+
+    /**
+     * Crop based on entropy.
+     *
+     * @param Image $oldImage
+     * @param $cropW
+     * @param $cropH
+     *
+     * @return array
+     */
+    private function _smartCrop($oldImage, $cropW, $cropH){
+        $image = clone $oldImage;
+
+        $this->resizeFit($image, 30, 30);
+
+        $origW = $oldImage->getWidth();
+        $origH = $oldImage->getHeight();
+        $resizeW = $image->getWidth();
+        $resizeH = $image->getHeight();
+
+        $smallCropW = round(($resizeW / $origW) * $cropW);
+        $smallCropH = round(($resizeH / $origH) * $cropH);
+
+        $step = 1;
+
+        for($y = 0; $y < $resizeH-$smallCropH; $y+=$step){
+            for($x = 0; $x < $resizeW-$smallCropW; $x+=$step){
+                $hist[$x.'-'.$y] = $this->_entropy($image->histogram(array(array($x, $y), array($smallCropW, $smallCropH))));
+            }
+            if($resizeW-$smallCropW <= 0){
+                $hist['0-'.$y] = $this->_entropy($image->histogram(array(array(0, 0), array($smallCropW, $smallCropH))));
+            }
+        }
+        if($resizeH-$smallCropH <= 0){
+            $hist['0-0'] = $this->_entropy($image->histogram(array(array(0, 0), array($smallCropW, $smallCropH))));
+        }
+
+        asort($hist);
+        end($hist);
+        $pos = key($hist); // last key
+        list($x, $y) = explode('-', $pos);
+        $x = round($x*($origW / $resizeW));
+        $y = round($y*($origH / $resizeH));
+
+        return array($x,$y);
+    }
+
+    /**
+     * Resize helper function.
+     *
+     * @param Image $image
+     * @param int $newWidth
+     * @param int $newHeight
+     *
+     * @return self
+     * @throws \Exception
+     */
+    private function _resize(&$image, $newWidth, $newHeight)
+    {
+
+        if ('GIF' == $image->getType()) { // Animated image. Eg. GIF
+
+            $imagick = $image->getCore()->coalesceImages();
+
+            foreach ($imagick as $frame) {
+                $frame->resizeImage($newWidth, $newHeight, \Imagick::FILTER_BOX, 1, false);
+                $frame->setImagePage($newWidth, $newHeight, 0, 0);
+            }
+
+            // Assign new image with frames
+            $image = new Image($imagick->deconstructImages(), $image->getImageFile(), $newWidth, $newHeight,
+                $image->getType());
+        } else { // Single frame image. Eg. JPEG, PNG
+
+            $image->getCore()->resizeImage($newWidth, $newHeight, \Imagick::FILTER_LANCZOS, 1, false);
+            // Assign new image
+            $image = new Image($image->getCore(), $image->getImageFile(), $newWidth, $newHeight,
+                $image->getType());
+        }
+
+    }
+
+    /**
+     * Get image type base on file extension.
+     *
+     * @param int $imageFile File path to image.
+     *
+     * @return ImageType string Type of image.
+     */
+    private function _getImageTypeFromFileName($imageFile)
+    {
+        $ext = strtolower((string)pathinfo($imageFile, PATHINFO_EXTENSION));
+
+        if ('jpg' == $ext or 'jpeg' == $ext) {
+            return ImageType::JPEG;
+        } else if ('gif' == $ext) {
+            return ImageType::GIF;
+        } else if ('png' == $ext) {
+            return ImageType::PNG;
+        } else {
+            return ImageType::UNKNOWN;
+        }
+    }
+
+}

+ 37 - 0
app/Helper/Grafika/Imagick/Filter/Blur.php

xqd
@@ -0,0 +1,37 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Blurs the image.
+ */
+class Blur implements FilterInterface{
+
+    /**
+     * @var int
+     */
+    protected $amount;
+
+    /**
+     * Blur constructor.
+     * @param int $amount The amount of blur to apply. Possible values 1-100.
+     */
+    public function __construct($amount = 1)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+        $image->getCore()->blurImage(1 * $this->amount, 0.5 * $this->amount);
+        return $image;
+    }
+
+}

+ 39 - 0
app/Helper/Grafika/Imagick/Filter/Brightness.php

xqd
@@ -0,0 +1,39 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Change the image brightness.
+ *
+ * TODO: param checks
+ */
+class Brightness implements FilterInterface{
+
+    /**
+     * @var int
+     */
+    protected $amount; // -100 >= 0 >= 100
+
+    /**
+     * Brightness constructor.
+     * @param int $amount The amount of brightness to apply. >= -100 and <= -1 to darken. 0 for no change. >= 1 and <= 100 to brighten.
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+        $image->getCore()->modulateImage(100 + $this->amount, 100, 100);
+        return $image;
+    }
+
+}

+ 67 - 0
app/Helper/Grafika/Imagick/Filter/Colorize.php

xqd
@@ -0,0 +1,67 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Change the values for red, green and blue in an image.
+ */
+class Colorize implements FilterInterface{
+
+    /**
+     * @var int
+     */
+    protected $red; // -100 >= 0 >= 100
+    /**
+     * @var int
+     */
+    protected $green; // -100 >= 0 >= 100
+    /**
+     * @var int
+     */
+    protected $blue; // -100 >= 0 >= 100
+
+    /**
+     * Colorize constructor.
+     * @param int $red The amount of red colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add.
+     * @param int $green The amount of green colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add.
+     * @param int $blue The amount of blue colors. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to add.
+     */
+    public function __construct($red, $green, $blue)
+    {
+        $this->red = intval($red);
+        $this->green = intval($green);
+        $this->blue = intval($blue);
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        // normalize colorize levels
+        $red = $this->normalizeLevel($this->red);
+        $green = $this->normalizeLevel($this->green);
+        $blue = $this->normalizeLevel($this->blue);
+        $qrange = $image->getCore()->getQuantumRange();
+
+        $image->getCore()->levelImage(0, $red, $qrange['quantumRangeLong'], \Imagick::CHANNEL_RED);
+        $image->getCore()->levelImage(0, $green, $qrange['quantumRangeLong'], \Imagick::CHANNEL_GREEN);
+        $image->getCore()->levelImage(0, $blue, $qrange['quantumRangeLong'], \Imagick::CHANNEL_BLUE);
+
+        return $image;
+    }
+
+    private function normalizeLevel($level)
+    {
+        if ($level > 0) {
+            return $level/5;
+        } else {
+            return ($level+100)/100;
+        }
+    }
+}

+ 38 - 0
app/Helper/Grafika/Imagick/Filter/Contrast.php

xqd
@@ -0,0 +1,38 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Change the contrast of an image. Contrast is the difference in luminance or colour that makes an object distinguishable.
+ */
+class Contrast implements FilterInterface{
+
+    /**
+     * @var int
+     */
+    protected $amount; // -100 >= 0 >= 100
+
+    /**
+     * Contrast constructor.
+     * @param int $amount The amount of contrast to apply. >= -100 and <= -1 to reduce. 0 for no change. >= 1 and <= 100 to increase.
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        $image->getCore()->sigmoidalContrastImage($this->amount > 0, $this->amount / 4, 0);
+        return $image;
+    }
+
+}

+ 168 - 0
app/Helper/Grafika/Imagick/Filter/Dither.php

xqd
@@ -0,0 +1,168 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Dither image. Dithering will turn the image black and white and add noise.
+ */
+class Dither implements FilterInterface{
+
+    /**
+     * @var string Dithering algorithm to use.
+     */
+    private $type;
+
+    /**
+     * Dither an image.
+     *
+     * @param string $type Dithering algorithm to use. Options: diffusion, ordered. Defaults to diffusion.
+     */
+    public function __construct( $type = 'diffusion' )
+    {
+        $this->type = $type;
+    }
+
+    /**
+     * Apply filter.
+     *
+     * @param Image $image
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    public function apply( $image ) {
+        if ( $this->type === 'ordered' ) {
+            return $this->ordered( $image );
+        } else if ( $this->type === 'diffusion' ) {
+            return $this->diffusion( $image );
+        }
+        throw new \Exception( sprintf( 'Invalid dither type "%s".', $this->type ) );
+    }
+
+    /**
+     * Dither using error diffusion.
+     *
+     * @param Image $image
+     *
+     * @return Image
+     */
+    private function diffusion( $image ){
+        $pixels = array();
+
+        // Localize vars
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+
+        // Loop using image1
+        $pixelIterator = $image->getCore()->getPixelIterator();
+        foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */
+            foreach ( $rows as $x => $px ) { /* Loop through the pixels in the row (columns) */
+                /**
+                 * @var $px \ImagickPixel */
+                $rgba = $px->getColor();
+
+                $gray = round($rgba['r'] * 0.3 + $rgba['g'] * 0.59 + $rgba['b'] * 0.11);
+
+                if(isset($pixels[$x][$y])){ // Add errors to color if there are
+                    $gray += $pixels[$x][$y];
+                }
+
+                if ( $gray <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess val due to adding the error
+                    $blackOrWhite = 0;
+                } else {
+                    $blackOrWhite = 255;
+                }
+
+                $oldPixel = $gray;
+                $newPixel = $blackOrWhite;
+
+                // Current pixel
+                $px->setColor("rgb($newPixel,$newPixel,$newPixel)");
+
+                $qError = $oldPixel - $newPixel; // Quantization error
+
+                // Propagate error on neighbor pixels
+                if ( $x + 1 < $width ) {
+                    $pixels[$x+1][$y] = (isset($pixels[$x+1][$y]) ? $pixels[$x+1][$y] : 0) + ($qError * (7 / 16));
+                }
+
+                if ( $x - 1 > 0 and $y + 1 < $height ) {
+                    $pixels[$x-1][$y+1] = (isset($pixels[$x-1][$y+1]) ? $pixels[$x-1][$y+1] : 0) + ($qError * (3 / 16));
+                }
+
+                if ( $y + 1 < $height ) {
+                    $pixels[$x][$y+1] = (isset($pixels[$x][$y+1]) ? $pixels[$x][$y+1] : 0) + ($qError * (5 / 16));
+                }
+
+                if ( $x + 1 < $width and $y + 1 < $height ) {
+                    $pixels[$x+1][$y+1] = (isset($pixels[$x+1][$y+1]) ? $pixels[$x+1][$y+1] : 0) + ($qError * (1 / 16));
+                }
+
+            }
+            $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */
+        }
+
+        $type = $image->getType();
+        $file = $image->getImageFile();
+        $image = $image->getCore();
+
+        return new Image( $image, $file, $width, $height, $type ); // Create new image with updated core
+
+    }
+
+    /**
+     * Dither by applying a threshold map.
+     *
+     * @param Image $image
+     *
+     * @return Image
+     */
+    private function ordered( $image ) {
+
+        // Localize vars
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+
+        $thresholdMap = array(
+            array( 15, 135, 45, 165 ),
+            array( 195, 75, 225, 105 ),
+            array( 60, 180, 30, 150 ),
+            array( 240, 120, 210, 90 )
+        );
+
+        // Loop using image1
+        $pixelIterator = $image->getCore()->getPixelIterator();
+        foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */
+            foreach ( $rows as $x => $px ) { /* Loop through the pixels in the row (columns) */
+                /**
+                 * @var $px \ImagickPixel */
+                $rgba = $px->getColor();
+
+                $gray = round($rgba['r'] * 0.3 + $rgba['g'] * 0.59 + $rgba['b'] * 0.11);
+
+                $threshold = $thresholdMap[ $x % 4 ][ $y % 4 ];
+                $oldPixel  = ( $gray + $threshold ) / 2;
+                if ( $oldPixel <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess value
+                    $newPixel = 0;
+                } else {
+                    $newPixel = 255;
+                }
+
+                // Current pixel
+                $px->setColor("rgb($newPixel,$newPixel,$newPixel)");
+
+            }
+            $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */
+        }
+
+        $type = $image->getType();
+        $file = $image->getImageFile();
+        $image = $image->getCore();
+
+        return new Image( $image, $file, $width, $height, $type ); // Create new image with updated core
+
+    }
+}

+ 38 - 0
app/Helper/Grafika/Imagick/Filter/Gamma.php

xqd
@@ -0,0 +1,38 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Performs a gamma correction on an image.
+ */
+class Gamma implements FilterInterface{
+
+    /**
+     * @var float
+     */
+    protected $amount; // >= 1.0
+
+    /**
+     * Gamma constructor.
+     * @param float $amount The amount of gamma correction to apply. >= 1.0
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (float) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        $image->getCore()->gammaImage($this->amount);
+        return $image;
+    }
+
+}

+ 23 - 0
app/Helper/Grafika/Imagick/Filter/Grayscale.php

xqd
@@ -0,0 +1,23 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Turn image into grayscale.
+ */
+class Grayscale implements FilterInterface{
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+        $image->getCore()->modulateImage(100, 0, 100);
+        return $image;
+    }
+
+}

+ 24 - 0
app/Helper/Grafika/Imagick/Filter/Invert.php

xqd
@@ -0,0 +1,24 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Invert the image colors.
+ */
+class Invert implements FilterInterface{
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        $image->getCore()->negateImage(false);
+        return $image;
+    }
+
+}

+ 42 - 0
app/Helper/Grafika/Imagick/Filter/Pixelate.php

xqd
@@ -0,0 +1,42 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Pixelate an image.
+ */
+class Pixelate implements FilterInterface{
+
+    /**
+     * @var int $amount Pixelate size from >= 1
+     */
+    protected $amount;
+
+    /**
+     * Pixelate constructor.
+     * @param int $amount The size of pixelation. >= 1
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        $size = $this->amount;
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+        $image->getCore()->scaleImage(max(1, ($width / $size)), max(1, ($height / $size)));
+        $image->getCore()->scaleImage($width, $height);
+        return $image;
+    }
+
+}

+ 37 - 0
app/Helper/Grafika/Imagick/Filter/Sharpen.php

xqd
@@ -0,0 +1,37 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Sharpen an image.
+ */
+class Sharpen implements FilterInterface{
+
+    /**
+     * @var int $amount
+     */
+    protected $amount;
+
+    /**
+     * Sharpen constructor.
+     * @param int $amount Amount of sharpening from >= 1 to <= 100
+     */
+    public function __construct($amount)
+    {
+        $this->amount = (int) $amount;
+    }
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+        $image->getCore()->unsharpMaskImage(1, 1, $this->amount / 6.25, 0);
+        return $image;
+    }
+
+}

+ 142 - 0
app/Helper/Grafika/Imagick/Filter/Sobel.php

xqd
@@ -0,0 +1,142 @@
+<?php
+
+namespace Grafika\Imagick\Filter;
+
+use Grafika\FilterInterface;
+use Grafika\Imagick\Image;
+
+/**
+ * Sobel filter is an edge detection filter.
+ * @link https://en.wikipedia.org/wiki/Sobel_operator
+ */
+class Sobel implements FilterInterface{
+
+
+    /**
+     * @param Image $image
+     *
+     * @return Image
+     */
+    public function apply( $image ) {
+
+        $pixels = array();
+        $finalPx = array();
+        // Localize vars
+        $width = $image->getWidth();
+        $height = $image->getHeight();
+
+        // Loop
+        $pixelIterator = $image->getCore()->getPixelIterator();
+        foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */
+            foreach ( $rows as $x => $px ) { /* Loop through the pixels in the row (columns) */
+
+                // row 0
+                if ($x > 0 and $y > 0) {
+                    $matrix[0][0] = $this->getColor($px, $pixels, $x - 1, $y - 1);
+                } else {
+                    $matrix[0][0] = $this->getColor($px, $pixels, $x, $y);
+                }
+
+                if ($y > 0) {
+                    $matrix[1][0] = $this->getColor($px, $pixels, $x, $y - 1);
+                } else {
+                    $matrix[1][0] = $this->getColor($px, $pixels, $x, $y);
+                }
+
+                if ($x + 1 < $width and $y > 0) {
+                    $matrix[2][0] = $this->getColor($px, $pixels, $x + 1, $y - 1);
+                } else {
+                    $matrix[2][0] = $this->getColor($px, $pixels, $x, $y);
+                }
+
+                // row 1
+                if ($x > 0) {
+                    $matrix[0][1] = $this->getColor($px, $pixels, $x - 1, $y);
+                } else {
+                    $matrix[0][1] = $this->getColor($px, $pixels, $x, $y);
+                }
+
+                if ($x + 1 < $width) {
+                    $matrix[2][1] = $this->getColor($px, $pixels, $x + 1, $y);
+                } else {
+                    $matrix[2][1] = $this->getColor($px, $pixels, $x, $y);
+                }
+
+                // row 1
+                if ($x > 0 and $y + 1 < $height) {
+                    $matrix[0][2] = $this->getColor($px, $pixels, $x - 1, $y + 1);
+                } else {
+                    $matrix[0][2] = $this->getColor($px, $pixels, $x, $y);
+                }
+
+                if ($y + 1 < $height) {
+                    $matrix[1][2] = $this->getColor($px, $pixels, $x, $y + 1);
+                } else {
+                    $matrix[1][2] = $this->getColor($px, $pixels, $x, $y);
+                }
+
+                if ($x + 1 < $width and $y + 1 < $height) {
+                    $matrix[2][2] = $this->getColor($px, $pixels, $x + 1, $y + 1);
+                } else {
+                    $matrix[2][2] = $this->getColor($px, $pixels, $x, $y);
+                }
+
+                $edge = $this->convolve($matrix);
+                $edge = intval($edge / 2);
+                if ($edge > 255) {
+                    $edge = 255;
+                }
+
+                /**
+                 * @var \ImagickPixel $px Current pixel.
+                 */
+                $finalPx[] = $edge; // R
+                $finalPx[] = $edge; // G
+                $finalPx[] = $edge; // B
+
+            }
+            $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */
+        }
+
+        $new = new \Imagick();
+        $new->newImage($width, $height, new \ImagickPixel('black'));
+        /* Import the pixels into image.
+        width * height * strlen("RGB") must match count($pixels) */
+        $new->importImagePixels(0, 0, $width, $height, "RGB", \Imagick::PIXEL_CHAR, $finalPx);
+
+        $type = $image->getType();
+        $file = $image->getImageFile();
+
+        return new Image( $new, $file, $width, $height, $type ); // Create new image with updated core
+
+    }
+
+    private function convolve($matrix)
+    {
+        $gx = $matrix[0][0] + ($matrix[2][0] * -1) +
+              ($matrix[0][1] * 2) + ($matrix[2][1] * -2) +
+              $matrix[0][2] + ($matrix[2][2] * -1);
+
+        $gy = $matrix[0][0] + ($matrix[1][0] * 2) + $matrix[2][0] +
+              ($matrix[0][2] * -1) + ($matrix[1][2] * -2) + ($matrix[2][2] * -1);
+
+        return sqrt(($gx * $gx) + ($gy * $gy));
+    }
+
+    /**
+     * @param \ImagickPixel $px
+     * @param array $pixels
+     * @param int $x
+     * @param int $y
+     *
+     * @return float
+     */
+    private function getColor($px, &$pixels, $x, $y)
+    {
+        if (isset($pixels[$x][$y])) {
+            return $pixels[$x][$y];
+        }
+        $rgba = $px->getColor();
+        return $pixels[$x][$y] = round($rgba['r'] * 0.3 + $rgba['g'] * 0.59 + $rgba['b'] * 0.11); // gray
+    }
+}

+ 269 - 0
app/Helper/Grafika/Imagick/Image.php

xqd
@@ -0,0 +1,269 @@
+<?php
+namespace Grafika\Imagick;
+
+use Grafika\ImageInterface;
+use Grafika\ImageType;
+
+/**
+ * Image class for Imagick.
+ * @package Grafika\Gd
+ */
+final class Image implements ImageInterface {
+
+    /**
+     * @var \Imagick Imagick instance
+     */
+    private $imagick;
+
+    /**
+     * @var string File path to image
+     */
+    private $imageFile;
+
+    /**
+     * @var int Image width in pixels
+     */
+    private $width;
+
+    /**
+     * @var int Image height in pixels
+     */
+    private $height;
+
+    /**
+     * @var string Image type. Return value of Imagick::queryFormats(). See http://phpimagick.com/Imagick/queryFormats
+     * Sample values: JPEG, PNG, GIF, WBMP
+     */
+    private $type;
+
+    /**
+     * @var bool True if image is an animated GIF.
+     */
+    private $animated;
+
+    /**
+     * Image constructor.
+     *
+     * @param \Imagick $imagick
+     * @param string $imageFile
+     * @param int $width
+     * @param int $height
+     * @param string $type
+     * @param bool $animated
+     */
+    public function __construct( \Imagick $imagick, $imageFile, $width, $height, $type, $animated = false ) {
+        $this->imagick   = $imagick;
+        $this->imageFile = $imageFile;
+        $this->width     = $width;
+        $this->height    = $height;
+        $this->type      = $type;
+        $this->animated  = $animated;
+    }
+
+    public function __clone()
+    {
+        $copy = clone $this->imagick;
+
+        $this->imagick = $copy;
+    }
+
+    /**
+     * Output a binary raw dump of an image in a specified format.
+     *
+     * @param string|ImageType $type Image format of the dump.
+     *
+     * @throws \Exception When unsupported type.
+     */
+    public function blob( $type = 'PNG' ) {
+        $this->imagick->setImageFormat($type);
+        echo $this->imagick->getImageBlob();
+    }
+
+    /**
+     * @param $imageFile
+     *
+     * @return Image
+     * @throws \Exception
+     */
+    public static function createFromFile( $imageFile ){
+        $imageFile = realpath( $imageFile );
+
+        if ( ! file_exists( $imageFile ) ) {
+            throw new \Exception( sprintf('Could not open image file "%s"', $imageFile) );
+        }
+
+        $imagick = new \Imagick( realpath($imageFile) );
+        $animated = false;
+        if ($imagick->getImageIterations() > 0) {
+            $animated = true;
+        }
+
+        return new self(
+            $imagick,
+            $imageFile,
+            $imagick->getImageWidth(),
+            $imagick->getImageHeight(),
+            $imagick->getImageFormat(),
+            $animated
+        );
+    }
+
+    /**
+     * Create an Image from an instance of Imagick.
+     *
+     * @param \Imagick $imagick Instance of Imagick.
+     *
+     * @return Image
+     */
+    public static function createFromCore( $imagick ) {
+        return new self( $imagick, '', $imagick->getImageWidth(), $imagick->getImageHeight(), $imagick->getImageFormat() );
+    }
+
+    /**
+     * Create a blank image.
+     *
+     * @param int $width Width in pixels.
+     * @param int $height Height in pixels.
+     *
+     * @return self
+     */
+    public static function createBlank($width = 1, $height = 1){
+        $imagick = new \Imagick();
+        $imagick->newImage($width, $height, new \ImagickPixel('black'));
+        $imagick->setImageFormat('png'); // Default to PNG.
+
+        return new self( $imagick, '', $imagick->getImageWidth(), $imagick->getImageHeight(), $imagick->getImageFormat());
+
+    }
+
+    /**
+     * Get Imagick instance
+     *
+     * @return \Imagick
+     */
+    public function getCore() {
+        return $this->imagick;
+    }
+
+    /**
+     * Get image file path.
+     *
+     * @return string File path to image.
+     */
+    public function getImageFile() {
+        return $this->imageFile;
+    }
+
+    /**
+     * Get image width in pixels.
+     *
+     * @return int
+     */
+    public function getWidth() {
+        return $this->width;
+    }
+
+    /**
+     * Get image height in pixels.
+     *
+     * @return int
+     */
+    public function getHeight() {
+        return $this->height;
+    }
+
+    /**
+     * Get image type.
+     *
+     * @return string
+     */
+    public function getType() {
+        return $this->type;
+    }
+
+    /**
+     * Get histogram from an entire image or its sub-region.
+     *
+     * @param array|null $slice Array of slice information. array( array( 0,0), array(100,50)) means x,y is 0,0 and width,height is 100,50
+     *
+     * @return array Returns array containing RGBA bins array('r'=>array(), 'g'=>array(), 'b'=>array(), 'a'=>array())
+     */
+    public function histogram($slice = null)
+    {
+
+        if(null === $slice){
+            $sliceX = 0;
+            $sliceY = 0;
+            $sliceW = $this->getWidth();
+            $sliceH = $this->getHeight();
+        } else {
+            $sliceX = $slice[0][0];
+            $sliceY = $slice[0][1];
+            $sliceW = $slice[1][0];
+            $sliceH = $slice[1][1];
+        }
+
+        $rBin = array();
+        $gBin = array();
+        $bBin = array();
+        $aBin = array();
+
+        // Loop using image
+        $pixelIterator = $this->getCore()->getPixelIterator();
+        foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */
+            if($y >= $sliceY and $y < $sliceY+$sliceH) {
+                foreach ($rows as $x => $px) { /* Loop through the pixels in the row (columns) */
+                    if($x >= $sliceX and $x < $sliceX+$sliceW) {
+                        /**
+                         * @var $px \ImagickPixel */
+                        $pixel = $px->getColor();
+                        $r = $pixel['r'];
+                        $g = $pixel['g'];
+                        $b = $pixel['b'];
+                        $a = $pixel['a'];
+
+                        if ( ! isset($rBin[$r])) {
+                            $rBin[$r] = 1;
+                        } else {
+                            $rBin[$r]++;
+                        }
+
+                        if ( ! isset($gBin[$g])) {
+                            $gBin[$g] = 1;
+                        } else {
+                            $gBin[$g]++;
+                        }
+
+                        if ( ! isset($bBin[$b])) {
+                            $bBin[$b] = 1;
+                        } else {
+                            $bBin[$b]++;
+                        }
+
+                        if ( ! isset($aBin[$a])) {
+                            $aBin[$a] = 1;
+                        } else {
+                            $aBin[$a]++;
+                        }
+                    }
+                }
+            }
+        }
+        return array(
+            'r' => $rBin,
+            'g' => $gBin,
+            'b' => $bBin,
+            'a' => $aBin
+        );
+    }
+
+    /**
+     * Returns animated flag.
+     *
+     * @return bool True if animated GIF.
+     */
+    public function isAnimated() {
+        return $this->animated;
+    }
+
+}

+ 37 - 0
app/Helper/Grafika/Imagick/ImageHash/AverageHash.php

xqd
@@ -0,0 +1,37 @@
+<?php
+
+
+namespace Grafika\Imagick\ImageHash;
+
+use Grafika\Imagick\Editor;
+use Grafika\Imagick\Image;
+
+/**
+ * AverageHash
+ *
+ * Algorithm:
+ * Reduce size. Remove high frequencies and detail by shrinking to 8x8 so that there are 64 total pixels.
+ * Reduce color. The tiny 8x8 picture is converted to a grayscale.
+ * Average the colors. Compute the mean value of the 64 colors.
+ * Compute the bits. Each bit is simply set based on whether the color value is above or below the mean.
+ * Construct the hash. Set the 64 bits into a 64-bit integer. The order does not matter, just as long as you are consistent.
+ *
+ * http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
+ *
+ * @package Grafika\Imagick\ImageHash
+ */
+class AverageHash
+{
+
+    /**
+     * Generate and get the average hash of the image.
+     *
+     * @param Image $image
+     *
+     * @return string
+     */
+    public function hash(Image $image)
+    {
+        return ''; // TODO: Implementation
+    }
+}

+ 67 - 0
app/Helper/Grafika/Imagick/ImageHash/DifferenceHash.php

xqd
@@ -0,0 +1,67 @@
+<?php
+
+
+namespace Grafika\Imagick\ImageHash;
+
+use Grafika\Imagick\Editor;
+use Grafika\Imagick\Image;
+
+/**
+ * DifferenceHash
+ *
+ * Algorithm:
+ * Reduce size. The fastest way to remove high frequencies and detail is to shrink the image. In this case, shrink it to 9x8 so that there are 72 total pixels.
+ * Reduce color. Convert the image to a grayscale picture. This changes the hash from 72 pixels to a total of 72 colors.
+ * Compute the difference. The algorithm works on the difference between adjacent pixels. This identifies the relative gradient direction. In this case, the 9 pixels per row yields 8 differences between adjacent pixels. Eight rows of eight differences becomes 64 bits.
+ * Assign bits. Each bit is simply set based on whether the left pixel is brighter than the right pixel.
+ *
+ * http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
+ *
+ * @package Grafika\Imagick\ImageHash
+ */
+class DifferenceHash
+{
+
+    /**
+     * Generate and get the difference hash of image.
+     *
+     * @param Image $image
+     *
+     * @param Editor $editor
+     *
+     * @return string
+     */
+    public function hash($image, $editor)
+    {
+        $width  = 9;
+        $height = 8;
+
+        $image = clone $image; // Make sure we are working on the clone if Image is passed
+        $editor->resizeExact($image, $width, $height); // Resize to exactly 9x8
+        $imagick = $image->getCore();
+
+        // Build hash
+        $hash = '';
+        for ($y = 0; $y < $height; $y++) {
+            // Get the pixel value for the leftmost pixel.
+            $rgba = $imagick->getImagePixelColor(0, $y)->getColor();
+
+            $left = floor(($rgba['r'] + $rgba['g'] + $rgba['b']) / 3);
+            for ($x = 1; $x < $width; $x++) {
+                // Get the pixel value for each pixel starting from position 1.
+                $rgba  = $imagick->getImagePixelColor($x, $y)->getColor();
+                $right = floor(($rgba['r'] + $rgba['g'] + $rgba['b']) / 3);
+                // Each hash bit is set based on whether the left pixel is brighter than the right pixel.
+                if ($left > $right) {
+                    $hash .= '1';
+                } else {
+                    $hash .= '0';
+                }
+                // Prepare the next loop.
+                $left = $right;
+            }
+        }
+        $editor->free( $image );
+        return $hash;
+    }
+}

+ 146 - 0
app/Helper/Grafika/Position.php

xqd
@@ -0,0 +1,146 @@
+<?php
+
+namespace Grafika;
+
+/**
+ * Hold and computes position of objects added to canvas.
+ *
+ * @package Grafika
+ */
+class Position {
+
+	/**
+     * Top left of the canvas.
+     */
+    const TOP_LEFT = 'top-left';
+	/**
+     * Top center of the canvas.
+     */
+    const TOP_CENTER = 'top-center';
+	/**
+     * Top right of the canvas.
+     */
+    const TOP_RIGHT = 'top-right';
+	/**
+     * Center left of the canvas.
+     */
+    const CENTER_LEFT = 'center-left';
+	/**
+     * Center of the canvas.
+     */
+    const CENTER = 'center';
+	/**
+     * Center right of the canvas.
+     */
+    const CENTER_RIGHT = 'center-right';
+	/**
+     * Center left of the canvas.
+     */
+    const BOTTOM_LEFT = 'bottom-left';
+	/**
+     * Bottom center of the canvas.
+     */
+    const BOTTOM_CENTER = 'bottom-center';
+	/**
+     * Bottom right of the canvas.
+     */
+    const BOTTOM_RIGHT = 'bottom-right';
+
+	/**
+     * @var string Holds position in human-readable text.
+     */
+    private $position;
+	/**
+     * @var int Number of pixels to the left of the origin
+     */
+    private $offsetX;
+	/**
+     * @var int Number of pixels to the bottom of the origin.
+     */
+    private $offsetY;
+
+	/**
+     * Position constructor.
+     *
+     * @param string $position Defaults to center.
+     * @param int $offsetX Defaults to 0.
+     * @param int $offsetY Defaults to 0.
+     */
+    public function __construct($position='center', $offsetX=0, $offsetY=0) {
+        $this->position = $position;
+        $this->offsetX = $offsetX;
+        $this->offsetY = $offsetY;
+    }
+
+	/**
+     * Translate the textual position + offsets into x,y values.
+     *
+     * @param int $canvasWidth Width of canvas.
+     * @param int $canvasHeight Height of canvas.
+     * @param int $imageWidth Width of image/object added.
+     * @param int $imageHeight Height of image/object added.
+     *
+     * @return array Array of X and Y coordinates: array($x, $y).
+     * @throws \Exception When invalid position.
+     */
+    public function getXY($canvasWidth, $canvasHeight, $imageWidth, $imageHeight){
+        if ( self::TOP_LEFT === $this->position) {
+            $x = 0;
+            $y = 0;
+        } else if ( self::TOP_CENTER === $this->position) {
+            $x = (int)round(($canvasWidth / 2) - ($imageWidth / 2));
+            $y = 0;
+        } else if ( self::TOP_RIGHT === $this->position) {
+            $x = $canvasWidth - $imageWidth;
+            $y = 0;
+        } else if ( self::CENTER_LEFT === $this->position) {
+            $x = 0;
+            $y = (int)round(($canvasHeight / 2) - ($imageHeight / 2));
+        } else if ( self::CENTER_RIGHT === $this->position) {
+            $x = $canvasWidth - $imageWidth;
+            $y = (int)round(($canvasHeight / 2) - ($imageHeight / 2));
+        } else if ( self::BOTTOM_LEFT === $this->position) {
+            $x = 0;
+            $y = $canvasHeight - $imageHeight;
+        } else if ( self::BOTTOM_CENTER === $this->position) {
+            $x = (int)round(($canvasWidth / 2) - ($imageWidth / 2));
+            $y = $canvasHeight - $imageHeight;
+        } else if ( self::BOTTOM_RIGHT === $this->position) {
+            $x = $canvasWidth - $imageWidth;
+            $y = $canvasHeight - $imageHeight;
+        } else if ( self::CENTER === $this->position) {
+            $x = (int)round(($canvasWidth / 2) - ($imageWidth / 2));
+            $y = (int)round(($canvasHeight / 2) - ($imageHeight / 2));
+        } else {
+            throw new \Exception( sprintf( 'Invalid position "%s".', $this->position ) );
+        }
+
+        return array(
+            $x + $this->offsetX,
+            $y + $this->offsetY
+        );
+    }
+
+    /**
+     * @return string
+     */
+    public function getText() {
+        return $this->position;
+    }
+
+    /**
+     * @return int
+     */
+    public function getOffsetY() {
+        return $this->offsetY;
+    }
+
+    /**
+     * @return int
+     */
+    public function getOffsetX() {
+        return $this->offsetX;
+    }
+
+
+}

+ 71 - 0
app/Helper/LogHelper.php

xqd
@@ -0,0 +1,71 @@
+<?php
+namespace App\Http\HelperTraits;
+
+use App\Models\AccountLog;
+
+trait LogHelper
+{
+    /**
+     * account log记录
+     * @param $data
+     * @return AccountLog
+     * @throws \Exception
+     */
+//    public function writeAccountLog($data) {
+//        if (!isset($data['obj_type']) || empty($data['obj_type'])) {
+//            throw new \Exception('obj_type must be supplied and cannot be empty');
+//        }
+//        if (!isset($data['obj_id']) || empty($data['obj_id'])) {
+//            throw new \Exception('obj_id must be supplied and cannot be empty');
+//        }
+//        if (!isset($data['direction'])) {
+//            $data['direction'] = 1;
+//        }
+//        if ($data['direction'] != 1 && $data['direction'] != 2) {
+//            throw new \Exception('direction must be 1 or 2');
+//        }
+//        if (isset($data['vm_type']) && !array_key_exists($data['vm_type'], AccountLog::getAllType())) {
+//            throw new \Exception('vm_type invalid');
+//        }
+//        if (isset($data['channel']) && !array_key_exists($data['channel'], AccountLog::getAllChannels())) {
+//            throw new \Exception('channel invalid');
+//        }
+//        if (isset($data['op']) && !array_key_exists($data['op'], AccountLog::getAllop())) {
+//            throw new \Exception('op invalid');
+//        }
+//
+//        return AccountLog::create($data);
+//    }
+//
+//    public function logAccount($objType, $objId, $objName, $op, $vmType, $amount, $direction, $balance, $channel, $note = null) {
+//        return $this->writeAccountLog([
+//            'obj_type'      => $objType,
+//            'obj_id'        => $objId,
+//            'obj_name'      => $objName,
+//            'vm_type'       => $vmType,
+//            'amount'        => $amount,
+//            'balance'       => $balance,
+//            'direction'     => $direction,
+//            'op'            => $op,
+//            'channel'       => $channel,
+//            'note'          => $note,
+//        ]);
+//    }
+
+    private function logAccount($fromType, $fromId, $fromName, $fromAmount,  $op, $toType, $toId, $toName, $toAmount) {
+        return AccountLog::create([
+            'from_type'     => $fromType,
+            'from_id'       => $fromId,
+            'from_name'     => $fromName,
+            'from_amount'   => $fromAmount,
+            'op'            => $op,
+            'to_type'       => $toType,
+            'to_id'         => $toId,
+            'to_name'       => $toName,
+            'to_amount'     => $toAmount,
+        ]);
+    }
+
+
+
+}

+ 44 - 0
app/Helper/SmsHelper.php

xqd
@@ -0,0 +1,44 @@
+<?php
+namespace App\Helper;
+
+use GuzzleHttp\Client as GuzzleHttpClient;
+use GuzzleHttp\Exception\RequestException;
+
+trait SmsHelper
+{
+    public function sendSms($msg, $mobile) {
+        $url = "http://api.106txt.com/smsGBK.aspx?";
+        $account = "swufecredit2017";
+        $password = "mask751002";
+        $password = strtoupper(md5($password));
+        $gwid = 53;
+        $message = iconv("UTF-8", "GB2312", $msg);
+
+        try {
+            $client = new GuzzleHttpClient();
+            $data = [
+                'action'      => 'Send',
+                'username'        => $account,
+                'password'       => $password,
+                'gwid'      => $gwid,
+                'mobile'    => $mobile,
+                'message'       => $message,
+            ];
+            $apiRequest = $client->post($url, [
+                'form_params'      => $data,
+            ]);
+            $resp = $apiRequest->getBody()->getContents();
+            $res = iconv("GB2312", "UTF-8", $resp);
+            $re = json_decode($res, true);
+            \Log::info("发送短信 到手机:$mobile 内容:$msg RESULT:".$re['RESULT']);
+            if (trim($re['CODE']) == '1') {
+                return true;
+            }
+        } catch (RequestException $re) {
+            \Log::info("发送短信错误:".$re->getMessage());
+            return false;
+        }
+        return false;
+    }
+
+}

+ 33 - 0
app/Helper/constants.inc.php

xqd
@@ -0,0 +1,33 @@
+<?php
+/**
+ *  全局的常量配置
+ */
+
+// ===================== 返回状态 ================== //
+define("SUCESS_CODE", 200);
+define("FAILURE_CODE", 400);
+define("SERVER_ERROR",500);
+define("NO_PERMISSION",401);
+define("APP_PATH", str_replace('\\', '/', substr(__DIR__, 0, -6)));
+define("SYSTEM_TIME", time());
+defined("PAGE_NUMS") || define("PAGE_NUMS", 10);
+define("PAGE_MAX_NUMS", 50);
+
+//网站登录SESSION
+define("LOGIN_OPENID_SESSION_KEY", 'LOGIN_OPENID_SESSION_KEY');
+define("AUTH_BACK_REDIRECT", 'AUTH_BACK_REDIRECT');
+
+//网站管理角色
+define("WEB_ADMIN_ROLE", 'ROLE_WEB_ADMIN');
+
+defined("LOGIN_MARK_SESSION_KEY") || define("LOGIN_MARK_SESSION_KEY", 'tewelwekrkjk34293423k4jnn');
+defined("USER_ROOT_ID") || define("USER_ROOT_ID", '0');
+defined("USER_ROOT_EMAIL") || define("USER_ROOT_EMAIL", 'root');
+
+/*
+ * 当前现有系统
+ */
+
+
+
+

+ 851 - 0
app/Helper/functions.inc.php

xqd
@@ -0,0 +1,851 @@
+<?php
+
+/**
+ * 二维数组去重
+ * @return array
+ */
+if (! function_exists('array_unique_two_dimensional')) {
+    function array_unique_multidimensional(array $input)
+    {
+        $unique = [];
+        foreach ($input as $item) {
+            if (! is_array($item)) {
+                return $input;
+            }
+
+            $str = implode(',', $item); //利用implode,将二维数组中的下层数组变成字符串
+            $strArr[] = $str;
+
+            $unique = array_unique($strArr); //去掉重复的字符串,也就是生成一个干净的一维数组
+        }
+
+        $output = [];
+        foreach ($unique as $item) {
+            $output[] = explode(',', $item);
+        }
+
+        return $output;
+    }
+}
+
+if (! function_exists('getUrlImage')) {
+    /**
+     * Get the image in the url.
+     *
+     * @param  string  $url
+     * @param  string  $filename
+     * @return string|boolean
+     */
+    function getUrlImage($url, $filename)
+    {
+        if (file_exists($filename)) {
+            unlink($filename);
+        }
+        if (empty($url) || empty($filename)) {
+            return false;
+        }
+        ob_start();//打开输出
+        readfile($url);//输出图片文件
+        $img = ob_get_contents();//得到浏览器输出
+        ob_end_clean();//清除输出并关闭
+      //  $size = strlen($img);//得到图片大小
+        $fp2 = @fopen($filename, "a");
+        fwrite($fp2, $img);//向当前目录写入图片文件,并重新命名
+        fclose($fp2);
+        return $filename;
+    }
+}
+
+
+
+
+if (! function_exists('imageToCircle')) {
+    /**
+     * Change the image to the circle.
+     *
+     * @param  string  $url
+     * @return string
+     */
+    function imageToCircle($url)
+    {
+        $ext = pathinfo($url);
+       // dd($ext);
+        $src_img = null;
+        switch ($ext['extension']) {
+            case 'jpg':
+                $src_img = imagecreatefromjpeg($url);
+                break;
+            case 'png':
+                $src_img = imagecreatefrompng($url);
+                break;
+        }
+        $wh = getimagesize($url);
+        $w = $wh[0];
+        $h = $wh[1];
+        $w = min($w, $h);
+        $h = $w;
+        $img = imagecreatetruecolor($w, $h);
+        //这一句一定要有
+        imagesavealpha($img, true);
+        //拾取一个完全透明的颜色,最后一个参数127为全透明
+        $bg = imagecolorallocatealpha($img, 255, 255, 255, 127);
+        imagefill($img, 0, 0, $bg);
+        $r = $w / 2; //圆半径
+        $y_x = $r; //圆心X坐标
+        $y_y = $r; //圆心Y坐标
+        for ($x = 0; $x < $w; $x++) {
+            for ($y = 0; $y < $h; $y++) {
+                $rgbColor = imagecolorat($src_img, $x, $y);
+                if (((($x - $r) * ($x - $r) + ($y - $r) * ($y - $r)) < ($r * $r))) {
+                    imagesetpixel($img, $x, $y, $rgbColor);
+                }
+            }
+        }
+        //存储png
+        imagepng($img, $url);
+        //存储jpeg
+        //imagejpeg($img, $url);
+        return $url;
+    }
+}
+
+if( ! function_exists('array_to_sort')) {
+    /**
+     * 对二维数组排序
+     * @param string $arr old数组
+     * @param string $keys 要排序的键
+     * @param string $type 排序类型[asc,desc]
+     * @param string $reset 重新排列数组key
+     * @return string 返回排序之后的数组
+     */
+    function array_to_sort($arr, $keys, $type = 'asc', $reset = false)
+    {
+        $keysvalue = $new_array = array();
+        foreach ($arr as $k => $v) {
+            $keysvalue[$k] = $v[$keys];
+        }
+        if ($type == 'asc') {
+            asort($keysvalue);
+        } else {
+            arsort($keysvalue);
+        }
+        reset($keysvalue);
+        foreach ($keysvalue as $k => $v) {
+            if ($reset) {
+                $new_array[] = $arr[$k];
+            } else {
+                $new_array[$k] = $arr[$k];
+            }
+        }
+        return $new_array;
+    }
+}
+
+
+/**
+ * 写作的时间人性化
+ *
+ * @param int $time 写作的时间
+ * @return string
+ */
+if( ! function_exists('showWriteTime'))
+{
+    function showWriteTime($time)
+    {
+        $interval = time() - $time;
+        $format = array(
+            '31536000'  => '年',
+            '2592000'   => '个月',
+            '604800'    => '星期',
+            '86400'     => '天',
+            '3600'      => '小时',
+            '60'        => '分钟',
+            '1'         => '秒'
+        );
+        foreach($format as $key => $value)
+        {
+            $match = floor($interval / (int) $key );
+            if(0 != $match)
+            {
+                return $match . $value . '前';
+            }
+        }
+        return date('Y-m-d', $time);
+    }
+}
+if( ! function_exists('pairList'))
+{
+    function pairList($list, $keyField, $valueField)
+    {
+        $pairList = array();
+        foreach ($list as $one) {
+            $pairList[$one[$keyField]] = $one[$valueField];
+        }
+        return $pairList;
+    }
+}
+
+
+/**
+ * 重新组装url ,如果没有host回自动添加当前host
+ * @param string $url
+ * @param array $query  key=>val
+ * @return string
+ */
+function U ($url, $query = [])
+{
+    $url = ltrim($url, '/');
+    $urlInfo = parse_url($url);
+    $aQuery = [];
+
+    if (isset($urlInfo['query']) && $urlInfo['query'] !== '') {
+        parse_str($urlInfo['query'],$aQuery);
+    }
+    $queryString = http_build_query(array_merge($aQuery, $query));
+    if(isset($urlInfo['host'])) {
+        $url = $urlInfo['scheme'] . '://' . $urlInfo['host'].'/admin/';
+    }else{
+        $url = request()->root() . '/admin/';
+    }
+    $url .= isset($urlInfo['path']) ? $urlInfo['path'] : '';
+    $url .= $queryString === '' ? '' : ('?'.$queryString);
+    return $url;
+}
+/**
+ * 验证角色菜单权限
+ *
+ * @param string $route 路由
+ * @param string $params 附带参数
+ * @return bool
+ */
+
+if( ! function_exists('role'))
+{
+    function role($route, $params = [])
+    {
+        $user = \Auth::guard('admin')->user();
+        $role = session()->get(LOGIN_MARK_SESSION_KEY);
+        if($user['is_root'] ||$user['level'] >=0) {
+            return true;
+        }
+        $route = trim($route);
+        $roles = $role['role'];
+        if(isset($roles[$route])){
+            return true;
+        }else{
+            return false;
+        }
+    }
+}
+if( ! function_exists('dict'))
+{
+    function dict()
+    {
+        return new App\Services\Base\Dictionary;
+    }
+}
+
+
+/**
+ * 隐藏部分手机号码
+ * @param $mobile
+ * @param $hide_length
+ * @return string
+ */
+if( ! function_exists('hidePartMobile'))
+{
+    function hidePartMobile($mobile, $hide_length = 5){
+        $hide_length = intval($hide_length);
+        $hide = '';
+        for($i = 0; $i < $hide_length; $i++){
+            $hide .= '*';
+        }
+        $pattern = "/(1\d{1,2})([0-9]{". $hide_length .",". $hide_length ."})(\d+)/";
+        $replacement = "\$1{$hide}\$3";
+        return preg_replace($pattern, $replacement, $mobile);
+    }
+}
+
+/**
+ * Function echo_log
+ * 输出调试日志
+ * @param $content 输出内容
+ */
+if( ! function_exists('echoLog'))
+{
+    function echoLog($content, $filename = '')
+    {
+        if(is_object($content) || is_array($content)) {
+            $content = var_export($content, true);
+        }
+        $log_path = storage_path() . DIRECTORY_SEPARATOR . "debug_log" . DIRECTORY_SEPARATOR;
+
+        if($filename){
+            $file_path = $log_path . $filename;
+        }else{
+            $file_path = $log_path . "debug_log_" . date("Ymd") . ".txt";
+        }
+
+        if(!file_exists($log_path)){
+            mkdir($log_path,0777);
+        }
+
+        $fp = fopen($file_path, "a");
+        flock($fp, LOCK_EX) ;
+        fwrite($fp,"执行日期:".date("Y-m-d H:i:s",time())."\n".$content."\n\n");
+        flock($fp, LOCK_UN);
+        fclose($fp);
+    }
+}
+if( ! function_exists('curlAjax'))
+{
+    function curlAjax($url) {
+        $cookieStr = '';
+        if($_COOKIE) {
+            foreach ($_COOKIE as $key=>$val) {
+                $cookieStr .= $key . '=' . $val . ';';
+            }
+            $cookieStr = substr($cookieStr, 0,-1);
+        }
+        $headers = array(
+            'Content-Type' => 'text/json;charset=utf-8', // 设置为Ajax方式
+            'X-Requested-With' => 'XMLHttpRequest', // 设置为Ajax方式
+            'User-Agent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36', // 设置为Ajax方式
+            'Referer' => 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'],
+            'Cookie' => $cookieStr
+        );
+        $headerArr = array();
+        foreach( $headers as $n => $v ) {
+            $headerArr[] = $n .':' . $v;
+        }
+        $ch = curl_init($url);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArr);
+        curl_setopt($ch, CURLOPT_HEADER, 0);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        $return = curl_exec($ch);
+        curl_close ( $ch );
+        return $return;
+    }
+}
+
+if( ! function_exists('img'))
+{
+    function img($system, $system_primary = NULL, $system_key = NULL, $first = false, $pagesize = PAGE_MAX_NUMS)
+    {
+        $paramArgs = func_get_args();
+        $key = "system_img" . md5(serialize($paramArgs));
+        if(!Cache::has($key) ||  request('no_cache') === 'true') {
+            $data =  \App\Services\Base\Images::getSrc($system, $system_primary, $system_key, $first, $pagesize);
+            \Cache::forever($key,$data);
+        }
+        $data = \Cache::get($key);
+        return $data;
+    }
+}
+
+/**
+ * 字符截取 支持UTF8/GBK
+ * @param $string
+ * @param $length
+ * @param $dot
+ */
+if( ! function_exists('str_cut'))
+{
+    function str_cut($string, $length, $dot = '...') {
+        $strlen = strlen($string);
+        if($strlen <= $length) return $string;
+        $string = str_replace(array(' ','&nbsp;', '&amp;', '&quot;', '&#039;', '&ldquo;', '&rdquo;', '&mdash;', '&lt;', '&gt;', '&middot;', '&hellip;'), array('∵',' ', '&', '"', "'", '“', '”', '—', '<', '>', '·', '…'), $string);
+        $strcut = '';
+        if(strtolower('utf-8') == 'utf-8') {
+            $length = intval($length-strlen($dot)-$length/3);
+            $n = $tn = $noc = 0;
+            while($n < strlen($string)) {
+                $t = ord($string[$n]);
+                if($t == 9 || $t == 10 || (32 <= $t && $t <= 126)) {
+                    $tn = 1; $n++; $noc++;
+                } elseif(194 <= $t && $t <= 223) {
+                    $tn = 2; $n += 2; $noc += 2;
+                } elseif(224 <= $t && $t <= 239) {
+                    $tn = 3; $n += 3; $noc += 2;
+                } elseif(240 <= $t && $t <= 247) {
+                    $tn = 4; $n += 4; $noc += 2;
+                } elseif(248 <= $t && $t <= 251) {
+                    $tn = 5; $n += 5; $noc += 2;
+                } elseif($t == 252 || $t == 253) {
+                    $tn = 6; $n += 6; $noc += 2;
+                } else {
+                    $n++;
+                }
+                if($noc >= $length) {
+                    break;
+                }
+            }
+            if($noc > $length) {
+                $n -= $tn;
+            }
+            $strcut = substr($string, 0, $n);
+            $strcut = str_replace(array('∵', '&', '"', "'", '“', '”', '—', '<', '>', '·', '…'), array(' ', '&amp;', '&quot;', '&#039;', '&ldquo;', '&rdquo;', '&mdash;', '&lt;', '&gt;', '&middot;', '&hellip;'), $strcut);
+        } else {
+            $dotlen = strlen($dot);
+            $maxi = $length - $dotlen - 1;
+            $current_str = '';
+            $search_arr = array('&',' ', '"', "'", '“', '”', '—', '<', '>', '·', '…','∵');
+            $replace_arr = array('&amp;','&nbsp;', '&quot;', '&#039;', '&ldquo;', '&rdquo;', '&mdash;', '&lt;', '&gt;', '&middot;', '&hellip;',' ');
+            $search_flip = array_flip($search_arr);
+            for ($i = 0; $i < $maxi; $i++) {
+                $current_str = ord($string[$i]) > 127 ? $string[$i].$string[++$i] : $string[$i];
+                if (in_array($current_str, $search_arr)) {
+                    $key = $search_flip[$current_str];
+                    $current_str = str_replace($search_arr[$key], $replace_arr[$key], $current_str);
+                }
+                $strcut .= $current_str;
+            }
+        }
+        return $strcut.$dot;
+    }
+}
+
+/**
+ * 取得文件扩展
+ *
+ * @param $filename 文件名
+ * @return 扩展名
+ */
+if( ! function_exists('fileExt'))
+{
+    function fileExt($filename)
+    {
+        return strtolower(trim(substr(strrchr($filename, '.'), 1, 10)));
+    }
+}
+
+/**
+ * 过滤参数
+ *
+ * @param $param    参数数组
+ * @param $allowKey 被允许的KEY集合数组
+ * @return 扩展名
+ */
+if( ! function_exists('filterParam'))
+{
+    function filterParam(array $param, array $allowKey)
+    {
+        $data = array();
+        foreach ($param AS $key => $val) {
+            if(in_array($key, $allowKey)) $data[$key] = $val;
+        }
+        return $data;
+    }
+}
+if(!function_exists('list_to_tree')) {
+    function list_to_tree($list, $pk='id',$pid = 'pid',$child = '_child',$root=0)
+    {
+        // 创建Tree
+        $tree = array();
+        if(is_array($list)) {
+            // 创建基于主键的数组引用
+            $refer = array();
+            foreach ($list as $key => $data) {
+                $refer[$data[$pk]] =& $list[$key];
+            }
+            foreach ($list as $key => $data) {
+                // 判断是否存在parent
+                $parentId = $data[$pid];
+                if ($root == $parentId) {
+                    $tree[] =& $list[$key];
+                }else{
+                    if (isset($refer[$parentId])) {
+                        $parent =& $refer[$parentId];
+                        $parent[$child][] =& $list[$key];
+                    }
+                }
+            }
+        }
+        return $tree;
+    }
+}
+
+
+/**
+ * 判断远程文件是否存在
+ * @param unknown $url
+ * @return boolean
+ */
+function check_remote_file_exists($url)
+{
+    $curl = curl_init($url);
+    curl_setopt($curl, CURLOPT_NOBODY, true);
+    curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
+    $result = curl_exec($curl);
+    $found = false;
+    if ($result !== false)
+    {
+        $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
+        if ($statusCode == 200)
+        {
+            $found = true;
+        }
+    }
+    curl_close($curl);
+    return $found;
+}
+
+if( ! function_exists('getCacheKey')) {
+    /**
+     * 更具参数获取一个唯一的缓存KEY
+     * @param string $name 名称
+     * @param obj|array|string $data 参数
+     */
+    function getCacheKey($name,$data) {
+        return $name . md5(serialize($data));
+
+    }
+}
+
+function isMobile()
+{
+    // 如果有HTTP_X_WAP_PROFILE则一定是移动设备
+    if (isset ($_SERVER['HTTP_X_WAP_PROFILE']))
+    {
+        return true;
+    }
+    // 如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息
+    if (isset ($_SERVER['HTTP_VIA']))
+    {
+        // 找不到为false,否则为true
+        return stristr($_SERVER['HTTP_VIA'], "wap") ? true : false;
+    }
+    // 脑残法,判断手机发送的客户端标志,兼容性有待提高
+    if (isset ($_SERVER['HTTP_USER_AGENT']))
+    {
+        $clientkeywords = array ('nokia',
+            'sony',
+            'ericsson',
+            'mot',
+            'samsung',
+            'htc',
+            'sgh',
+            'lg',
+            'sharp',
+            'sie-',
+            'philips',
+            'panasonic',
+            'alcatel',
+            'lenovo',
+            'iphone',
+            'ipod',
+            'blackberry',
+            'meizu',
+            'android',
+            'netfront',
+            'symbian',
+            'ucweb',
+            'windowsce',
+            'palm',
+            'operamini',
+            'operamobi',
+            'openwave',
+            'nexusone',
+            'cldc',
+            'midp',
+            'wap',
+            'mobile'
+        );
+        // 从HTTP_USER_AGENT中查找手机浏览器的关键字
+        if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER['HTTP_USER_AGENT'])))
+        {
+            return true;
+        }
+    }
+    // 协议法,因为有可能不准确,放到最后判断
+    if (isset ($_SERVER['HTTP_ACCEPT']))
+    {
+        // 如果只支持wml并且不支持html那一定是移动设备
+        // 如果支持wml和html但是wml在html之前则是移动设备
+        if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html'))))
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+
+/**
+ * post 提交
+ * @param strint $url
+ * @param array $post_data
+ * @return mixed
+ */
+function formPost($url, $post_data=array(), $timeout=60, $userpwd = null)
+{
+    $ch = curl_init();
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_POST, 1);
+    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+    curl_setopt($ch, CURLOPT_HEADER, 0);
+    curl_setopt($ch, CURLOPT_URL, $url);
+    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // 设置超时限制防止死循环
+    curl_setopt($ch, CURLOPT_POST, 1);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
+    // 		curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
+    if ($userpwd) {
+        curl_setopt($ch, CURLOPT_HTTPAUTH , CURLAUTH_BASIC);
+        curl_setopt($ch, CURLOPT_USERPWD  , $userpwd);
+    }
+    $result = curl_exec($ch);
+    if (curl_errno($ch)) {
+        // echo curl_error($ch);exit;
+    }
+    curl_close($ch); // 关键CURL会话
+    //	CBase::write_log('formPost' . date("Ymd") . ".log", $result);
+    return $result;
+}
+
+
+
+/**
+ *
+ * @param string $textareaid      编辑器ID
+ * @param unknown $getParam       上传参数
+ * @param unknown $options        编辑器自带参数,直接以php数组的形式传入即可,但不支持对象类型(eg.Function)
+ * @return string
+ */
+function editor($textareaid = 'content', $getParam = array(), $options = array()) {
+    $getParam['_token']     = csrf_token();
+    $getParam['elementid']  = isset($getParam['elementid'])?$getParam['elementid']:'elementid';
+    $getParam['KindEditor'] = isset($getParam['KindEditor'])?$getParam['KindEditor']:true;
+    $getParam['field']      = isset($getParam['field'])?$getParam['field']:'imgFile';
+    $uploadUrl = U('Base/Attachment/upload', $getParam);
+
+    if(isset($options['allowFileManager']) && $options['allowFileManager'] == true){
+        echo "暂时不支持该功能!【allowFileManager】";exit;
+    }
+
+    //是否开启过滤模式
+    //$options['filterMode']  = false;
+    $options['urlType']  = 'domain';
+    $options['uploadJson']  = isset($options['uploadJson'])?$options['uploadJson']:$uploadUrl;
+    $options = json_encode($options);
+    //SESSION_ID
+    $session_id = session()->getId();
+    $_csrf_token = csrf_token();
+
+    $editer = <<<HTML
+        <link rel="stylesheet" href="/base/kindeditor-4.1.10/themes/default/default.css" />
+        <script charset="utf-8" src="/base/kindeditor-4.1.10/kindeditor-min.js"></script>
+        <script charset="utf-8" src="/base/kindeditor-4.1.10/lang/zh_CN.js"></script>
+        <script>
+            var editor_{$textareaid};
+            var options = '{$options}';
+                options = eval('('+ options +')');
+                options.extraFileUploadParams = {
+                        PHPSESSID : "$session_id",
+                        _token : "$_csrf_token"
+                    };
+            //是否开启过滤模式
+            KindEditor.options.filterMode = false;
+            KindEditor.ready(function(K) {
+                editor_{$textareaid} = K.create('#{$textareaid}', options);
+            });
+        </script>
+HTML;
+    return $editer;
+}
+
+function ueditor() {
+
+    $editer = <<<HTML
+    <!-- 配置文件 -->
+    <script type="text/javascript" src="/base/neditor-1.5.3/neditor.config.js"></script>
+    <!-- 编辑器源码文件 -->
+    <script type="text/javascript" src="/base/neditor-1.5.3/neditor.all.js"></script>
+    <!-- 实例化编辑器 -->
+    <script type="text/javascript">
+        var ue = UE.getEditor('container',{
+             toolbars: [
+          ["fullscreen","source","autotypeset","bold", "italic","underline","forecolor", "paragraph","fontfamily","fontsize","indent","justifyleft", "justifycenter","justifyright","justifyjustify","link","unlink","insertimage","insertcode"
+          ]],
+           autoHeightEnabled: true,
+           autoFloatEnabled: true,
+           initialFrameHeight:320
+        });
+   
+        ue.ready(function(){
+        ue.execCommand('serverparam', '_token', '{{ csrf_token() }}');
+    })
+    </script>
+HTML;
+
+    return $editer;
+}
+
+
+if( ! function_exists('widget'))
+{
+    function widget($widgetName)
+    {
+        $widgetNameEx = explode('.', $widgetName);
+        if( ! isset($widgetNameEx[1])) return false;
+        $widgetClass = 'App\\Widget\\'.$widgetNameEx[0].'\\'.$widgetNameEx[1];
+        if(app()->bound($widgetName)) return app()->make($widgetName);
+        app()->singleton($widgetName, function() use ($widgetClass)
+        {
+            return new $widgetClass();
+        });
+        return app()->make($widgetName);
+    }
+}
+
+
+//
+//if( ! function_exists('sendSms')) {
+//    // return string 'success' 为成功
+//    function sendSms($msg, $mobile){
+//        $post_data = array();
+//        $post_data['un'] ="N9304000";
+//        $post_data['pw'] = "Mask751002@";
+//        $post_data['msg']="【小洲蔬菜】$msg";
+//        $post_data['phone'] =$mobile;
+//        $post_data['rd']=1;
+//
+//        $post_data['needstatus']='true';
+//        $url='https://sms.253.com/msg/send';
+//        $data=http_build_query($post_data);
+//
+//        if(function_exists('curl_init')){
+//            $curl = curl_init();
+//            curl_setopt($curl, CURLOPT_URL, $url);
+//
+//            if (!empty($data)){
+//                curl_setopt($curl, CURLOPT_POST, 1);
+//                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
+//            }
+//            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+//            $output = curl_exec($curl);
+//            curl_close($curl);
+//            $result=preg_split("/[,\r\n]/",$output);
+//            if($result[1]==0){
+//                $res = "success";
+//            }else{
+//                $res = "curl error:".$result[1];
+//            }
+//        }elseif(function_exists('file_get_contents')){
+//            $output=file_get_contents($url.$data);
+//            $result=preg_split("/[,\r\n]/",$output);
+//            if($result[1]==0){
+//                $res = "success";
+//            }else{
+//                $res = "error:".$result[1];
+//            }
+//        }else{
+//            $res="error";
+//        }
+//        return $res;
+//    }
+//}
+//
+//if( ! function_exists('sendMultiSms')) {
+//    //  群发短信 $mobiles电话号码之间用英文逗号分割
+//    // return string 'success' 为成功
+//    function sendMultiSms($msg, $mobiles){
+//        $post_data = array();
+//        $post_data['un'] ="M3512322";
+//        $post_data['pw'] = "N8ecbXn0A7dc18";
+//        $post_data['msg']="【小洲蔬菜】$msg 回复TD退订";
+//        $post_data['phone'] =$mobiles;
+//        $post_data['rd']=1;
+//
+//        $post_data['needstatus']='true';
+//        $url='https://sms.253.com/msg/send';
+//        $data=http_build_query($post_data);
+//
+//        if(function_exists('curl_init')){
+//            $curl = curl_init();
+//            curl_setopt($curl, CURLOPT_URL, $url);
+//
+//            if (!empty($data)){
+//                curl_setopt($curl, CURLOPT_POST, 1);
+//                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
+//            }
+//            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+//            $output = curl_exec($curl);
+//            curl_close($curl);
+//            $result=preg_split("/[,\r\n]/",$output);
+//            if($result[1]==0){
+//                $res = "success";
+//            }else{
+//                $res = "curl error:".$result[1];
+//            }
+//        }elseif(function_exists('file_get_contents')){
+//            $output=file_get_contents($url.$data);
+//            $result=preg_split("/[,\r\n]/",$output);
+//            if($result[1]==0){
+//                $res = "success";
+//            }else{
+//                $res = "error:".$result[1];
+//            }
+//        }else{
+//            $res="error";
+//        }
+//        return $res;
+//    }
+//}
+//
+//
+//if( ! function_exists('sendCaixin')) {
+//    //return  true or false
+//    function sendCaixin($mobile){
+//
+//        $post_data =$data =$msgdata = array();
+//
+//        $url ="http://www.baidu.com";//回调URL
+//        $key ="6MG5jNK4Q15907";//密码
+////文字body
+//        $data['frame']="1"; //文字标识
+//        $data['part']  ="1";//文字标识
+//        $data['type']="1" ;
+//        $data['content']=base64_encode('亲爱的朋友,“芜湖小洲蔬菜”,芜湖人的掌上菜篮子,已是我每天买菜必备工具。好东西首先想到了您,赶快关注使用吧!');
+//
+////图片body
+//        $msgdata['frame']="1";
+//        $msgdata['part']  ="2";//图片标识
+//        $msgdata['type']="2" ;//图片标识
+//        $msgdata['content']=base64_encode(file_get_contents(public_path('app/wap/images/')."qrcode.jpg"));//图片
+//
+//        $post_data['account'] ="C8636841";//账号
+//        $post_data['ext_id']="72175534217316595927";//自传参数
+//        $post_data['msg']=json_encode(array($data,$msgdata)) ;//json
+//        $post_data['phones']  = $mobile;//手机
+//        $post_data['timestamp'] = time();//时间戳
+//
+//        $post_data['title']= '芜湖小洲蔬菜'; //标题
+//
+//        $sign ='account=' . $post_data['account'].'ext_id=' . $post_data['ext_id'].'msg=' . $post_data['msg'].'phones=' . $post_data['phones'].'timestamp=' . $post_data['timestamp'].'title=' . $post_data['title'].'url=' . $url.'key=' . $key;
+//        $sign=md5($sign); //加密
+//        $post_data['sign']=$sign;
+//        $post_data['url']=$url;
+//
+//        $url='http://caixin.253.com/api/send?';
+//        $data = http_build_query($post_data);
+//
+//        if(function_exists('curl_init')){
+//            $curl = curl_init();
+//            curl_setopt($curl, CURLOPT_URL, $url);
+//
+//            if (!empty($data)){
+//                curl_setopt($curl, CURLOPT_POST, 1);
+//                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
+//            }
+//            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
+//            $output = curl_exec($curl);
+//            curl_close($curl);
+//            $res = json_decode($output);
+//            if(is_array($res)&&$res['code']==1){
+//                return true;
+//            }
+//
+//        }
+//        return false;
+//    }
+//}

+ 20 - 0
app/Helper/wxbdc/ErrorCode.php

xqd
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * error code 说明.
+ * <ul>
+ *    <li>-41001: encodingAesKey 非法</li>
+ *    <li>-41003: aes 解密失败</li>
+ *    <li>-41004: 解密后得到的buffer非法</li>
+ *    <li>-41005: base64加密失败</li>
+ *    <li>-41016: base64解密失败</li>
+ * </ul>
+ */
+class ErrorCode
+{
+    public static $OK = 0;
+    public static $IllegalAesKey = -41001;
+    public static $IllegalIv = -41002;
+    public static $IllegalBuffer = -41003;
+    public static $DecodeBase64Error = -41004;
+}

+ 108 - 0
app/Helper/wxbdc/Prpcrypt.php

xqd
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Created by IntelliJ IDEA.
+ * User: luwei
+ * Date: 2017/7/1
+ * Time: 23:18
+ */
+include_once "ErrorCode.php";
+
+/**
+ * PKCS7Encoder class
+ *
+ * 提供基于PKCS7算法的加解密接口.
+ */
+class PKCS7Encoder
+{
+    public static $block_size = 16;
+
+    /**
+     * 对需要加密的明文进行填充补位
+     * @param $text 需要进行填充补位操作的明文
+     * @return 补齐明文字符串
+     */
+    function encode($text)
+    {
+        $block_size = PKCS7Encoder::$block_size;
+        $text_length = strlen($text);
+        //计算需要填充的位数
+        $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size);
+        if ($amount_to_pad == 0) {
+            $amount_to_pad = PKCS7Encoder::block_size;
+        }
+        //获得补位所用的字符
+        $pad_chr = chr($amount_to_pad);
+        $tmp = "";
+        for ($index = 0; $index < $amount_to_pad; $index++) {
+            $tmp .= $pad_chr;
+        }
+        return $text . $tmp;
+    }
+
+    /**
+     * 对解密后的明文进行补位删除
+     * @param decrypted 解密后的明文
+     * @return 删除填充补位后的明文
+     */
+    function decode($text)
+    {
+
+        $pad = ord(substr($text, -1));
+        if ($pad < 1 || $pad > 32) {
+            $pad = 0;
+        }
+        return substr($text, 0, (strlen($text) - $pad));
+    }
+
+}
+
+/**
+ * Prpcrypt class
+ *
+ *
+ */
+class Prpcrypt
+{
+    public $key;
+
+    function __construct($k)
+    {
+        $this->key = $k;
+    }
+
+    /**
+     * 对密文进行解密
+     * @param string $aesCipher 需要解密的密文
+     * @param string $aesIV 解密的初始向量
+     * @return string 解密得到的明文
+     */
+    public function decrypt($aesCipher, $aesIV)
+    {
+
+        try {
+
+            $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, '');
+
+            mcrypt_generic_init($module, $this->key, $aesIV);
+
+            //解密
+            $decrypted = mdecrypt_generic($module, $aesCipher);
+            mcrypt_generic_deinit($module);
+            mcrypt_module_close($module);
+        } catch (Exception $e) {
+            return array(ErrorCode::$IllegalBuffer, null);
+        }
+
+
+        try {
+            //去除补位字符
+            $pkc_encoder = new PKCS7Encoder;
+            $result = $pkc_encoder->decode($decrypted);
+
+        } catch (Exception $e) {
+            //print $e;
+            return array(ErrorCode::$IllegalBuffer, null);
+        }
+        return array(0, $result);
+    }
+}

+ 69 - 0
app/Helper/wxbdc/WXBizDataCrypt.php

xqd
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: luwei
+ * Date: 2017/7/1
+ * Time: 23:18
+ */
+include_once "ErrorCode.php";
+include_once "Prpcrypt.php";
+
+class WXBizDataCrypt
+{
+
+    private $appid;
+    private $sessionKey;
+
+    /**
+     * 构造函数
+     * @param $sessionKey string 用户在小程序登录后获取的会话密钥
+     * @param $appid string 小程序的appid
+     */
+    public function __construct($appid, $sessionKey)
+    {
+        $this->sessionKey = $sessionKey;
+        $this->appid = $appid;
+    }
+
+
+    /**
+     * 检验数据的真实性,并且获取解密后的明文.
+     * @param $encryptedData string 加密的用户数据
+     * @param $iv string 与用户数据一同返回的初始向量
+     * @param $data string 解密后的原文
+     *
+     * @return int 成功0,失败返回对应的错误码
+     */
+    public function decryptData( $encryptedData, $iv, &$data )
+    {
+        if (strlen($this->sessionKey) != 24) {
+            return ErrorCode::$IllegalAesKey;
+        }
+        $aesKey=base64_decode($this->sessionKey);
+
+
+        if (strlen($iv) != 24) {
+            return ErrorCode::$IllegalIv;
+        }
+        $aesIV=base64_decode($iv);
+
+        $aesCipher=base64_decode($encryptedData);
+
+        $result=openssl_decrypt( $aesCipher, "AES-128-CBC", $aesKey, 1, $aesIV);
+
+        $dataObj=json_decode( $result );
+        if( $dataObj  == NULL )
+        {
+            return ErrorCode::$IllegalBuffer;
+        }
+        if( $dataObj->watermark->appid != $this->appid )
+        {
+            return ErrorCode::$IllegalBuffer;
+        }
+        $data = $result;
+        return ErrorCode::$OK;
+    }
+
+}
+

+ 257 - 0
app/Http/Controllers/Admin/Album/AgentController.php

xqd
@@ -0,0 +1,257 @@
+<?php
+/**
+ *  经销商管理
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-14 13:26:07
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AgentBannerModel;
+use App\Models\AlbumAgentModel;
+use App\Models\AlbumProductPriceModel;
+use App\Models\AlbumUserModel;
+use App\Repositories\Album\Criteria\AgentWhere;
+use App\Services\OSS;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\AgentRepository;
+
+class AgentController extends Controller
+{
+    private $repository;
+
+    public function __construct(AgentRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new AgentWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        foreach ($list as $item){
+            $product = AlbumProductPriceModel::where([['agent_id',$item->id],['store_id',$this->getStoreId()]])->first();
+            if(!empty($product)){
+                $item->product_name = $product['mobile'];
+            }
+
+            $item->product_pic = $product['cover_pic'];
+        }
+        foreach ($list as $item){
+            if($item->status == 0){
+                $item->status = '待审核';
+            }else{
+                $item->status = '已审核';
+            }
+        }
+        return view('admin.album.agent.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.agent.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.agent.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $agent_banner = $data['banner_agent']['url'];
+        unset($data['banner_agent']);
+        if($agent_banner){
+            foreach($agent_banner as $key=>$val){
+                $add['url'] = $this->formatImgUrl($val);
+                $add['agent_id'] = request('id');
+                $add['type'] = 0;
+
+                AgentBannerModel::create($add);
+            }
+        }
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Agent/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Agent/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Agent/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        $banner = AgentBannerModel::where([['agent_id',$data['id']],['type',0]])->get();
+        $imgs = array();
+        foreach($banner as $key=>$val){
+            $imgs[] = $val['url'];
+        }
+        $data->agent_banner = $imgs;
+        $video = AgentBannerModel::where([['agent_id',$request->get('id')],['type',1]])->first();
+        return view('admin.album.agent.edit',compact('data','video'));
+    }
+
+    public function videoDel(Request $request)
+    {
+        $check = AgentBannerModel::find($request->get('id'));
+        if($check) $osskey = $check->oss_key;
+        if(!$check) return  $this->showWarning("操作失败");
+        $bool = AgentBannerModel::where('id',$request->get('id'))->delete();
+        if($bool) {
+            OSS::publicDeleteObject(config('alioss.BucketName'),$osskey);
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');//dd($data);
+        $id = request('id');
+        $banner = AgentBannerModel::where([['agent_id',$id],['type',0]])->get();
+        if($banner){
+            foreach ($banner as $key=>$val){
+                $url = str_replace(env('APP_URL'),public_path(),$val['url']);
+                if(file_exists($url)) unlink($url);
+
+            }
+        }
+        AgentBannerModel::where([['agent_id',$id],['type',0]])->delete();
+        if(!empty($data['agent_banner']['url'])){
+            foreach($data['agent_banner']['url'] as $key=>$val){
+                $add['url'] = $this->formatImgUrl($val);
+                $add['agent_id'] = $id;
+                $add['type'] = 0;
+                AgentBannerModel::create($add);
+            }
+
+        }
+        unset($data['agent_banner']);
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $urls[] = array('url'=>U( 'Album/Agent/index'),'title'=>'返回列表');
+            $this->showMessage('添加成功',$urls);
+        }else{
+            $urls[] = array('url'=>U( 'Album/Agent/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$urls);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.agent.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if(request('status')==1){
+            $agent = AlbumAgentModel::find(request('id'));
+            $user = AlbumUserModel::find($agent->user_id);
+            $user->is_dealer = 1;
+            $user->save();
+        }
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $agent = AlbumAgentModel::find($request->get('id'));
+        $id= $agent->user_id;
+        $ok = $agent->delete();
+        $save['is_dealer'] = 0;
+        AlbumUserModel::where('id',$id)->update($save);
+        if($ok) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+
+
+    public function uploadVideo(Request $request)
+    {
+        $video = $request->file('video');
+// 判断图片有效性
+        if (!$video) {
+            return back()->withErrors('上传视频无效..');
+        }
+        $agentid = $request->input('agent_id');
+        $check = AgentBannerModel::where([['agent_id',$agentid],['type',1]])->first();
+        if($check) OSS::publicDeleteObject(config('alioss.BucketName'),$check->oss_key);
+// 获取图片在临时文件中的地址
+        $videoPath = $video->getRealPath();
+
+// 制作文件名
+        $ex = $video->getClientOriginalExtension();
+        $key = time() . rand(10000, 99999999) . '.' . $ex;
+
+//阿里 OSS 文件上传
+        $result = OSS::publicUpload(config('alioss.BucketName'),$key, $videoPath);
+        if ($result) {
+            $data['name'] = $video->getClientOriginalName();
+            $data['type'] = 1;
+            $data['agent_id'] = $agentid;
+            $data['oss_key'] =  $key;
+            $data['url'] = config('alioss.FileUrl').$key;
+            $video = AgentBannerModel::create($data);
+            if(!$video){
+                return back()->withErrors('上传视频失败..');
+            }
+            return  $this->showMessage('上传成功');
+
+        } else {
+            return back()->withErrors('上传视频失败..');
+        }
+    }
+}

+ 146 - 0
app/Http/Controllers/Admin/Album/BannerController.php

xqd
@@ -0,0 +1,146 @@
+<?php
+/**
+ *  轮播图管理
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-14 10:41:53
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Repositories\Album\Criteria\BannerWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Banner\Criteria\MultiWhere;
+use App\Repositories\Album\BannerRepository;
+
+class BannerController extends Controller
+{
+    private $repository;
+
+    public function __construct(BannerRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new BannerWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        return view('admin.album.banner.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.banner.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.banner.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        if(!empty($data['pic_url']))
+        $data['pic_url'] = $this->formatImgUrl($data['pic_url']);
+
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Banner/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Banner/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Banner/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        return view('admin.album.banner.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        if(!empty($data['pic_url']))
+        $data['pic_url'] = $this->formatImgUrl($data['pic_url']);
+
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Banner/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Banner/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.banner.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $bool = $this->repository->destroy($request->get('id'));
+        if($bool) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 164 - 0
app/Http/Controllers/Admin/Album/CatController.php

xqd
@@ -0,0 +1,164 @@
+<?php
+/**
+ *  产品分类
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-14 13:27:48
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumCatModel;
+use App\Repositories\Album\Criteria\CatWhere;
+use Dingo\Blueprint\Annotation\Method\Post;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\CatRepository;
+
+class CatController extends Controller
+{
+    private $repository;
+
+    public function __construct(CatRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        //AlbumCatModel::where()->get();
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new CatWhere($search,0,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        foreach ($list as $key => $item) {
+            //dump($item);
+            $item->sonlist = AlbumCatModel::where('parent_id',$item->id)->get();
+        }
+//        dump($list);
+        return view('admin.album.cat.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.cat.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        $cat = AlbumCatModel::where('parent_id',0)->get();
+        $data['parent_id'] = null;
+        return view('admin.album.cat.edit',compact('data','cat'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        if(!empty($data['pic_url']))
+        $data['pic_url'] = $this->formatImgUrl($data['pic_url']);
+
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Cat/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Cat/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Cat/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        $cat = AlbumCatModel::where('parent_id',0)->get();
+
+        return view('admin.album.cat.edit',compact('data','cat'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        if(!empty($data['pic_url']))
+        $data['pic_url'] = $this->formatImgUrl($data['pic_url']);
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Cat/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Cat/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.cat.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        //$bool = $this->repository->destroy($request->get('id'));
+        $cat = AlbumCatModel::find($request->get('id'));
+        $son_cat = AlbumCatModel::where('parent_id',$cat->id)->delete();
+        if($son_cat){
+            //$son_cat->delete();
+        }
+        $ok = $cat->delete();
+        if($ok) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 151 - 0
app/Http/Controllers/Admin/Album/CommentsController.php

xqd
@@ -0,0 +1,151 @@
+<?php
+/**
+ *  评论管理
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-14 10:49:40
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumCommentsModel;
+use App\Models\AlbumInformationModel;
+use App\Models\AlbumNewsModel;
+use App\Repositories\Album\Criteria\CommentWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\CommentsRepository;
+
+class CommentsController extends Controller
+{
+    private $repository;
+
+    public function __construct(CommentsRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $store_id = $this->getStoreId();
+
+        $query = $this->repository->pushCriteria(new CommentWhere($search,$store_id));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate(10);
+        foreach ($list as $key=>$item) {
+            $info = AlbumNewsModel::where('id',$item->news_id)->first();
+            if($info){
+                $item->title = $info->title;
+            } else {
+                unset($list[$key]);
+            }
+        }
+        return view('admin.album.comments.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.comments.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.comments.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $id = $this->repository->create($data);
+        if($id) {
+            $this->showMessage('添加成功');
+        }else{
+            return $this->showWarning('添加失败');
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        return view('admin.album.comments.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        //print_r($data);die;
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Comments/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Comments/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.comments.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $comments = AlbumCommentsModel::find($request->get('id'));
+        $ok = $comments->delete();
+        if($ok) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 35 - 0
app/Http/Controllers/Admin/Album/IndexController.php

xqd
@@ -0,0 +1,35 @@
+<?php
+/**
+ *  用户管理
+ * @author  system
+ * @version    1.0
+ * @date 2018-05-14 13:25:12
+ *
+ */
+
+namespace App\Http\Controllers\Admin\Album;
+
+use App\Http\Controllers\Admin\Base;
+use App\Models\AlbumAgentModel;
+use App\Models\AlbumUserModel;
+use App\Repositories\Album\Criteria\UserWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\UserRepository;
+
+class IndexController extends Base\IndexController
+{
+    function index()
+    {
+        $_user = $this->_user;
+        if(request('isalbum') ==1 ){
+            $menus = $this->albumMenus($_user,request('store_id'));
+        }else{
+            $menus = $this->furntureMenus($_user,request('store_id'));
+        }
+
+        return view('admin.base.index.index', compact('menus',  '_user'));
+    }
+
+}

+ 141 - 0
app/Http/Controllers/Admin/Album/Info/CatController.php

xqd
@@ -0,0 +1,141 @@
+<?php
+/**
+ *  咨询分类
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-21 16:17:09
+ *
+ */
+namespace App\Http\Controllers\Admin\Album\Info;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumInfoCatModel;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Info\Criteria\MultiWhere;
+use App\Repositories\Album\Info\CatRepository;
+
+class CatController extends Controller
+{
+    private $repository;
+
+    public function __construct(CatRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new MultiWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        return view('admin.album.info.cat.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.info.cat.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.info.cat.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Info/Cat/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Info/Cat/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Info/Cat/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        return view('admin.album.info.cat.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Info/Cat/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Info/Cat/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.info.cat.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $cat = AlbumInfoCatModel::find($request->get('id'));
+        $ok = $cat->delete();
+        if($ok) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 154 - 0
app/Http/Controllers/Admin/Album/InformationController.php

xqd
@@ -0,0 +1,154 @@
+<?php
+/**
+ *  咨询管理
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-14 13:23:56
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumInfoCatModel;
+use App\Models\AlbumInformationModel;
+use App\Repositories\Album\Criteria\InfoWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\InformationRepository;
+
+class InformationController extends Controller
+{
+    private $repository;
+    private $cat;
+
+    public function __construct(InformationRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+        $this->cat = AlbumInfoCatModel::where('store_id',$this->getStoreId())->get();
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new InfoWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        foreach ($list as $item)
+        {
+            $cat = AlbumInfoCatModel::where('id',$item->cat_id)->first();
+            $item->cat_name = $cat['name'];
+        }
+        return view('admin.album.information.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.information.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        $cat = $this->cat;
+        return view('admin.album.information.edit',compact('cat'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        $data['thumb'] = $this->formatImgUrl($data['thumb']);
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Information/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Information/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Information/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        $cat = $this->cat;
+        return view('admin.album.information.edit',compact('data','cat'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $data['thumb'] = $this->formatImgUrl($data['thumb']);
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Information/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Information/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.information.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $info = AlbumInformationModel::find($request->get('id'));
+        $ok = $info->delete();
+        if($ok) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 181 - 0
app/Http/Controllers/Admin/Album/ManufacturerController.php

xqd
@@ -0,0 +1,181 @@
+<?php
+/**
+ *  系统设置
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-14 13:30:36
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumManufacturerModel;
+use App\Models\AlbumUserModel;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\ManufacturerRepository;
+
+class ManufacturerController extends Controller
+{
+    private $repository;
+
+    public function __construct(ManufacturerRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function list(Request $request){
+        $search['keyword'] = $request->input('keyword');
+        $isalbum = request('isalbum')?request('isalbum'):0;
+        $query = $this->repository->pushCriteria(new MultiWhere($search));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }
+        $list = $query->paginate(10);
+        return view('admin.album.manufacturer.index',compact('list','isalbum'));
+    }
+
+    function index(Request $request) {
+        if($request->method() == 'POST') {
+            $iscreate = AlbumManufacturerModel::where('store_id',$this->getStoreId())->first();
+            if($iscreate){
+                return $this->_updateSave();
+            }else{
+                return $this->_createSave();
+            }
+
+        }
+        $data = AlbumManufacturerModel::where('store_id',$this->getStoreId())->first();
+        if ($data) {
+            $data['banner'] = json_decode($data['banner'], true);
+        }
+        return view('admin.album.manufacturer.edit',compact('data'));
+    }
+
+    function create(Request $request)
+    {
+        if ($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.manufacturer.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] =$this->getStoreId();
+        if (!empty($data['banner']['url'])) {
+            foreach ($data['banner']['url'] as $key => $val) {
+                $data['banner']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['banner'] = json_encode($data['banner']['url']);
+        }
+        if(!empty($data['avatar']))
+            $data['avatar'] = $this->formatImgUrl($data['avatar']);
+        if (!empty($data['logo'])) {
+            $data['logo'] = $this->formatImgUrl($data['logo']);
+        }
+
+        if(!empty($data['background_pic']))
+            $data['background_pic'] = $this->formatImgUrl($data['background_pic']);
+        if(!empty($data['advertising_pic']))
+            $data['advertising_pic'] = $this->formatImgUrl($data['advertising_pic']);
+        if(!empty($data['notice_icon']))
+            $data['notice_icon'] = $this->formatImgUrl($data['notice_icon']);
+        if(!empty($data['furniture_ads_pic']))
+            $data['furniture_ads_pic'] = $this->formatImgUrl($data['furniture_ads_pic']);
+
+        $id = $this->repository->create($data);
+        if($id) {
+            $this->showMessage('添加成功');
+        }else{
+            return $this->showWarning('添加失败');
+        }
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $saveData = AlbumManufacturerModel::where('store_id', $this->getStoreId())->first();
+        if (!empty($data['avatar'])) {
+            $data['avatar'] = $this->formatImgUrl($data['avatar']);
+            //$this->deleteUrl($saveData->avatar);
+        }
+        if (!empty($data['logo'])) {
+            $data['logo'] = $this->formatImgUrl($data['logo']);
+        }
+        if (!empty($data['background_pic'])) {
+            $data['background_pic'] = $this->formatImgUrl($data['background_pic']);
+            //$this->deleteUrl($saveData->background_pic);
+        }
+        if (!empty($data['advertising_pic'])) {
+            $data['advertising_pic'] = $this->formatImgUrl($data['advertising_pic']);
+           // $this->deleteUrl($saveData->advertising_pic);
+        }
+        if (!empty($data['notice_icon'])) {
+            $data['notice_icon'] = $this->formatImgUrl($data['notice_icon']);
+           // $this->deleteUrl($saveData->notice_icon);
+        }
+        if (!empty($data['furniture_ads_pic'])) {
+            $data['furniture_ads_pic'] = $this->formatImgUrl($data['furniture_ads_pic']);
+           // $this->deleteUrl($saveData->furniture_ads_pic);
+        }
+
+        if (!empty($data['banner']['url'])) {
+            foreach ($data['banner']['url'] as $key => $val) {
+                $data['banner']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['banner'] = json_encode($data['banner']['url']);
+            /*$banner = json_decode($data['banner'], true);
+            foreach ($banner as $val) {
+                $this->deleteUrl($val);
+            }*/
+        }
+
+
+        $id = $saveData->id;
+        $ok = $this->repository->update($id,$data);
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.manufacturer.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request)
+    {
+        $bool = $this->repository->destroy($request->get('id'));
+        if ($bool) {
+            return  $this->showMessage('操作成功');
+        } else {
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 186 - 0
app/Http/Controllers/Admin/Album/NavController.php

xqd
@@ -0,0 +1,186 @@
+<?php
+/**
+ *  导航栏设置
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-16 17:42:12
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumInfoCatModel;
+use App\Models\AlbumNavModel;
+use App\Models\AlbumNewsModel;
+use App\Repositories\Album\Criteria\NavWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\NavRepository;
+
+class NavController extends Controller
+{
+    private $repository;
+    private $url;
+
+    public function __construct(NavRepository $repository) {
+
+        if(!$this->repository) $this->repository = $repository;
+
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new NavWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        return view('admin.album.nav.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.nav.check',compact('list'));
+    }
+
+    private function getPageData(){
+        $url = [
+            ['name'=>'主页','value'=>'/page/homePage/homePage'],
+            ['name'=>'个人中心','value'=>'/page/user/user'],
+            ['name'=>'产品列表','value'=>'/pages/goods/list'],
+            ['name'=>'新品发布','value'=>'/page/newpro/newpro']
+        ];
+
+        $this->url = $url;
+    }
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        $this->getPageData();
+        $page = $this->url;
+
+        return view('admin.album.nav.edit',compact('page'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        $zhi = request('zhi');
+        //dd($zhi);die;
+        if($zhi!=null)
+            $data['url'].=$zhi;
+        $data['pic_url'] = isset($data['pic_url'])?$this->formatImgUrl($data['pic_url']):null;
+        $data['pic_url_active'] = isset($data['pic_url_active'])?$this->formatImgUrl($data['pic_url_active']):null;
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Nav/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Nav/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Nav/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            \Log::info($request->all());
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        if($data['open_type'] == 4){
+            $url = $data['url'];
+            if(strpos($url,'=')){
+                $urls = explode('=',$url);
+                $data['zhi'] = $urls[1];
+                $data['url'] = $urls[0].'=';
+            }else{
+                $data['zhi'] = '';
+            }
+        }
+        //dd($data);
+        $this->getPageData();
+        $page = $this->url;
+        return view('admin.album.nav.edit',compact('data','page'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $zhi = request('zhi');
+        //dump($zhi);die;
+        $data['url'].=$zhi;
+        $data['pic_url'] = isset($data['pic_url'])?$this->formatImgUrl($data['pic_url']):null;
+        $data['pic_url_active'] = isset($data['pic_url_active'])?$this->formatImgUrl($data['pic_url_active']):null;
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Nav/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Nav/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.nav.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $nav = AlbumNavModel::find($request->get('id'));
+        $ok = $nav->delete();
+
+        if($ok) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 147 - 0
app/Http/Controllers/Admin/Album/NewsController.php

xqd
@@ -0,0 +1,147 @@
+<?php
+/**
+ *  咨询列表
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-24 11:11:11
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Repositories\Album\Criteria\InfoWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\NewsRepository;
+
+class NewsController extends Controller
+{
+    private $repository;
+
+    public function __construct(NewsRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new InfoWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate(10);
+        return view('admin.album.news.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.news.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.news.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        if(!empty($data['thumb']))
+            $data['thumb'] = $this->formatImgUrl($data['thumb']);
+
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/News/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/News/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/News/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        return view('admin.album.news.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        if(!empty($data['thumb']))
+            $data['thumb'] = $this->formatImgUrl($data['thumb']);
+
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/News/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/News/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.news.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $bool = $this->repository->destroy($request->get('id'));
+        if($bool) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 309 - 0
app/Http/Controllers/Admin/Album/OrderController.php

xqd
@@ -0,0 +1,309 @@
+<?php
+/**
+ *  订单管理
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-07-03 16:51:03
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumManufacturerModel;
+use App\Models\AlbumOrderModel;
+use App\Models\AlbumPartsModel;
+use App\Models\AlbumProgressModel;
+use App\Models\AlbumUserModel;
+use App\Models\FurnitureFormidModel;
+use App\Services\Base\Attachment;
+use EasyWeChat\Factory;
+use EasyWeChat\Payment\Order;
+use Image;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\OrderRepository;
+use Illuminate\Support\Facades\DB;
+
+class OrderController extends Controller
+{
+    private $repository;
+
+    public function __construct(OrderRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+        parent::__construct();
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $search['status'] = $request->input('status');
+        $search['expected'] = $request->input('expected');
+        $search['storeid'] = $this->getStoreId();
+
+        $order = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $order[$request['sort_field']] = $request['sort_field_by'];
+        }else{
+            $order['id']='DESC';
+        }
+
+        $list = $this->repository->searchOrder($search,$order);
+
+        return view('admin.album.order.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $data = $this->repository->find(request('id'));
+        $img = explode(',',$data['picture']);
+        $parts = AlbumPartsModel::where('store_id', $this->getStoreId())->where('order_id', request('id'))
+            ->get(['name', 'count']);
+
+        return view('admin.album.order.check',compact('data','img','parts'));
+    }
+
+
+    /**
+     * 添加
+     *
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.order.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+
+        $data['store_id'] = $this->getStoreId();
+
+        $array_pic = request('picture');
+
+        if ($array_pic && isset($array_pic['url'])) {
+            $data['picture'] = '';
+            foreach ($array_pic['url'] as $item) {
+                $data['picture'] .= $item . ',';
+            }
+            if ($data['picture']) $data['picture'] = rtrim($data['picture'], ',');
+        } else {
+            $data['picture'] = '';
+        }
+        $data['status'] = 0;
+        $data['sno'] = date('YmdHmis');
+
+        $res = $this->repository->create($data);
+        if($res) {
+            $this->getQrcode($this->getStoreId(),$res->id);
+            $url[] = array('url'=>U( 'Album/Order/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Order/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Order/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+
+    /**
+     *
+     * 修改
+     *
+     *
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        $data['picture'] = explode(',',$data['picture']);
+
+        return view('admin.album.order.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+
+    private function _updateSave() {
+        $data = (array) request('data');
+        $array_pic = request('picture');
+
+        if ($array_pic && isset($array_pic['url'])) {
+            $data['picture'] = '';
+            foreach ($array_pic['url'] as $item) {
+                $data['picture'] .= $item . ',';
+            }
+            if ($data['picture']) $data['picture'] = rtrim($data['picture'], ',');
+        } else {
+            $data['picture'] = '';
+        }
+        $old = $this->repository->find(request('id'))->picture;
+
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            //操作成功,删除原来的图片
+            if ($old) {
+                $pics = explode(',', $old);
+                foreach ($pics as $pic) {
+                    if (!in_array($pic, $array_pic['url'])) {
+                        $md5 = $this->getarea($pic);
+                        $attache = new Attachment();
+                        $attache->deleteAttachment($md5);
+                    }
+                }
+            }
+
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Order/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        $img = explode(',',$data['picture']);
+        $this->getQrcode($this->getStoreId(),request('id'));
+        return view('admin.album.order.view',compact('data','img'));
+    }
+
+    public function picture(Request $request) {
+        $data = $this->repository->find(request('id'));
+        $img = explode(',',$data['picture']);
+        return view('admin.album.order.picture',compact('img'));
+    }
+
+    public function addecomment(Request $request){
+        $data = $this->repository->find(request('id'));
+        $data->expected_comment = $request->get('expected_comment');
+        $ok = $data->save();
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $data = [
+            'status'=>request('status'),
+            'price' =>request('price'),
+            'verifier_id' =>$this->_user->id,
+            'expected_time' =>request('expected_time'),
+        ];
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            foreach (request('name') as $k =>$v){
+                $parts[$k]['name'] = $v;
+                $parts[$k]['count'] = request('count')[$k];
+                $parts[$k]['order_id'] = request('id');
+                $parts[$k]['store_id'] = $this->getStoreId();
+                if($parts[$k]['name']){
+                    AlbumPartsModel::create($parts[$k]);
+                }
+            }
+            $data = [
+                'status' =>request('status'),
+                'order_id' =>request('id'),
+                'store_id' =>$this->getStoreId(),
+                'description' => '订单已审核,修理费为'.request('price').'订单下发到生产部,等待制作'
+            ];
+            AlbumProgressModel::create($data);
+            $storeid =$this->getStoreId();
+            $setting = AlbumManufacturerModel::where('store_id', $storeid)->first();
+            $config = [
+                'app_id' => $setting->service_app_id,
+                'secret' => $setting->service_app_secret,
+                // 下面为可选项
+                // 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
+                'response_type' => 'array',
+            ];
+            $app = Factory::miniProgram($config);
+
+            $users = AlbumUserModel::where('store_id',1)->where('role',1)->get();
+//            foreach ($users as $user){
+//                $formid = FurnitureFormidModel::where('store_id',$storeid)->where(DB::raw("DATEDIFF(NOW(),created_at)"), '<', 7)->where('openid',$user->wechat_open_id)->where('status',0)->orderBy('created_at','asc')->first();
+//                $app->template_message->send([
+//                    'touser' => $user->wechat_open_id,
+//                    'template_id' => '',
+//                    'page' => 'index',
+//                    'form_id' => $formid->formid,
+//                    'data' => [
+//                        'keyword1' => 'VALUE',
+//                        'keyword2' => 'VALUE2',
+//                        // ...
+//                    ],
+//                ]);
+//            }
+
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $bool = $this->repository->destroy($request->get('id'));
+        if($bool) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+
+    public function getPic(Request $request){
+        $order = AlbumOrderModel::find(request('id'));
+        $img = $order->picture;
+        $img = explode(',',$img);
+        return json_encode($img);
+    }
+
+    public function getQrcode($store_id, $id)
+    {
+        $this->wechat_app = AlbumManufacturerModel::where('store_id', $store_id)->first();
+        $config = [
+            'app_id' => $this->wechat_app->service_app_id,
+            'secret' => $this->wechat_app->service_app_secret,
+
+            // 下面为可选项
+            // 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
+            'response_type' => 'array',
+        ];
+        $app = Factory::miniProgram($config);
+        $response = $app->app_code->getQrCode('pages/search/search?order_id=' . $id,300);
+        $path = public_path() . '/download';
+        $qrcode = $response->save($path, $id);
+
+        if ($qrcode) {
+            $qrcode = env('APP_URL') . '/download/' . $id . '.jpg';
+            $order = AlbumOrderModel::find($id);
+            $order->qrcode = $qrcode;
+            $order->save();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public function getarea($str)
+    {
+        $start = strripos($str, '/');
+        $first = substr($str, $start + 1);
+        $end = strripos($first, '.');
+        $last = substr($first, 0, $end);
+        return $last;
+    }
+}

+ 75 - 0
app/Http/Controllers/Admin/Album/PosterController.php

xqd
@@ -0,0 +1,75 @@
+<?php
+/**
+ *  画册海报
+ *  @author  system
+ *  @version    1.0
+ *  @date 2019-03-04 13:38:57
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\PosterRepository;
+
+class PosterController extends Controller
+{
+    private $repository;
+
+    public function __construct(PosterRepository $repository)
+    {
+        if (!$this->repository) {
+            $this->repository = $repository;
+        }
+    }
+
+    /**
+     * 修改
+     */
+    public function update(Request $request)
+    {
+        if ($request->method() == 'POST') {
+            return $this->updateSave();
+        }
+        $data = $this->repository->findWhere(['store_id' => $this->getStoreId()])->toArray();
+        if (!empty($data)) {
+            $data = $data[0];
+            $data['posters'] = json_decode($data['posters'], true);
+            $data['words'] = json_decode($data['words'], true);
+        }
+        return view('admin.album.poster.edit', compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function updateSave()
+    {
+        $data = (array) request('data');
+        if (!empty($data['posters']['url'])) {
+            foreach ($data['posters']['url'] as $key => $val) {
+                $data['posters']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['posters'] = json_encode($data['posters']['url']);
+        }
+        $data['share'] = $this->formatImgUrl($data['share']);
+        $data['qrcode'] = $this->formatImgUrl($data['qrcode']);
+        if (!empty($data['words'])) {
+            $data['words'] = json_encode($data['words']);
+        }
+        $id = request('id');
+        if (empty($id)) {
+            $data['store_id'] = $this->getStoreId();
+            $ok = $this->repository->create($data);
+        } else {
+            $ok = $this->repository->update($id, $data);
+        }
+        if ($ok) {
+            return $this->showMessage('操作成功');
+        } else {
+            return $this->showMessage('操作失败');
+        }
+    }
+
+}

+ 159 - 0
app/Http/Controllers/Admin/Album/Product/AttrController.php

xqd
@@ -0,0 +1,159 @@
+<?php
+/**
+ *  产品属性
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-07-03 16:51:48
+ *
+ */
+namespace App\Http\Controllers\Admin\Album\Product;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumProductAttrModel;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Product\Criteria\MultiWhere;
+use App\Repositories\Album\Product\AttrRepository;
+
+class AttrController extends Controller
+{
+    private $repository;
+
+    public function __construct(AttrRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $search['storeid'] = $this->getStoreId();
+
+        $order = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $order[$request['sort_field']] = $request['sort_field_by'];
+        }else{
+            $order['updated_at']='DESC';
+        }
+
+        $list = $this->repository->searchAttr($search,$order);
+
+        return view('admin.album.product.attr.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.product.attr.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        $list = AlbumProductAttrModel::genAreaTree($this->getStoreId(),2);
+
+        return view('admin.album.product.attr.edit',compact('list'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        $data['attr_type'] = $this->getattrtype(request('pid'));
+
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Product/Attr/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Product/Attr/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Product/Attr/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        $list = AlbumProductAttrModel::genAreaTree($this->getStoreId(),2);
+
+        return view('admin.album.product.attr.edit',compact('data','list'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Product/Attr/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Product/Attr/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.product.attr.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $bool = $this->repository->destroy($request->get('id'));
+        if($bool) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+
+    private function getattrtype($pid)
+    {
+        if($pid){
+            $attr_type = AlbumProductAttrModel::find($pid)->attr_type;
+            return $attr_type + 1;
+        }else{
+            return 0;
+        }
+    }
+}

+ 149 - 0
app/Http/Controllers/Admin/Album/Product/PriceController.php

xqd
@@ -0,0 +1,149 @@
+<?php
+/**
+ *  价格列表
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-19 16:10:08
+ *
+ */
+namespace App\Http\Controllers\Admin\Album\Product;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumProductModel;
+use App\Models\AlbumProductPriceModel;
+use App\Repositories\Album\Criteria\PriceWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Product\Criteria\MultiWhere;
+use App\Repositories\Album\Product\PriceRepository;
+
+class PriceController extends Controller
+{
+    private $repository;
+
+    public function __construct(PriceRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $agent_id = $request->input('id');
+        //dd($agent_id);die;
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new PriceWhere($search,$this->getStoreId(),$agent_id));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        foreach ($list as $item){
+            $product = AlbumProductModel::where([['id',$item->product_id],['store_id',$this->getStoreId()]])->first();
+            $item->product_name = $product['name'];
+            $item->product_pic = $product['cover_pic'];
+        }
+        return view('admin.album.product.price.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.product.price.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.product.price.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Product/Price/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Product/Price/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Product/Price/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        return view('admin.album.product.price.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Product/Price/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Product/Price/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.product.price.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $cat = AlbumProductPriceModel::find($request->get('id'));
+        $ok = $cat->delete();
+        if($ok) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 141 - 0
app/Http/Controllers/Admin/Album/Product/StyleController.php

xqd
@@ -0,0 +1,141 @@
+<?php
+/**
+ *  产品样式管理
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-06-05 17:11:53
+ *
+ */
+namespace App\Http\Controllers\Admin\Album\Product;
+use App\Http\Controllers\Admin\Controller;
+use App\Repositories\Album\Criteria\StyleWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Product\Criteria\MultiWhere;
+use App\Repositories\Album\Product\StyleRepository;
+
+class StyleController extends Controller
+{
+    private $repository;
+
+    public function __construct(StyleRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new StyleWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        return view('admin.album.product.style.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.product.style.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.product.style.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        //dd($data);die;
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Product/Style/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Product/Style/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Product/Style/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        return view('admin.album.product.style.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Product/Style/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Product/Style/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.product.style.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $bool = $this->repository->destroy($request->get('id'));
+        if($bool) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 219 - 0
app/Http/Controllers/Admin/Album/ProductController.php

xqd
@@ -0,0 +1,219 @@
+<?php
+/**
+ *  产品列表
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-14 13:29:14
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumCatModel;
+use App\Models\AlbumProductModel;
+use App\Models\AlbumProductStyleModel;
+use App\Repositories\Album\Criteria\ProductWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\ProductRepository;
+
+class ProductController extends Controller
+{
+    private $repository;
+
+    public function __construct(ProductRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new ProductWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }else{
+            $query = $query->pushCriteria(new OrderBy('id','DESC'));
+        }
+        $list = $query->paginate();
+        foreach ($list as $key => $item) {
+            $cat = AlbumCatModel::where('id',$item->cat_id)->first();
+            $style = AlbumProductStyleModel::where('id',$item->style)->first();
+            $item->cat_name = $cat['name'];
+            $item->style_name = $style['name'];
+            $upload_img = json_decode($item->upload_img);
+            $attr = json_decode($item->attr);
+            $detail = json_decode($item->detail);
+            $list[$key]['install_img'] = $upload_img[0];
+            $list[$key]['specifications_img'] = $attr[0];
+            $list[$key]['detail'] = $detail[0];
+        }
+        //dd($list);
+        return view('admin.album.product.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.product.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        $cat = AlbumCatModel::where([['store_id',$this->getStoreId()],['parent_id',0]])->get();
+        $style = AlbumProductStyleModel::Where('store_id',$this->getStoreId())->get();
+        $data['cat_id']=null;
+        return view('admin.album.product.edit',compact('data','cat','style'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $data['store_id'] = $this->getStoreId();
+        if(!empty($data['specifications_img']['url'])) {
+            foreach ($data['specifications_img']['url'] as $key=>$val) {
+                $data['specifications_img']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['specifications_img'] = json_encode($data['specifications_img']['url']);
+        }
+        if(!empty($data['install_img']['url'])){
+            foreach ($data['install_img']['url'] as $key=>$val) {
+                $data['install_img']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['install_img'] = json_encode($data['install_img']['url']);
+        }
+        if(!empty($data['cover_pic']))
+            $data['cover_pic'] = $this->formatImgUrl($data['cover_pic']);
+        if(!empty($data['thumb']))
+            $data['thumb'] = $this->formatImgUrl($data['thumb']);
+        if(!empty($data['detail']['url'])) {
+            foreach ($data['detail']['url'] as $key=>$val) {
+                $data['detail']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['detail'] = json_encode($data['detail']['url']);
+        }
+        if(!empty($data['detail_pic']))
+            $data['detail_pic'] = $this->formatImgUrl($data['detail_pic']);
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Product/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Product/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Product/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $cat = AlbumCatModel::where([['store_id',$this->getStoreId()],['parent_id',0]])->get();
+        $style = AlbumProductStyleModel::Where('store_id',$this->getStoreId())->get();
+        $data = $this->repository->find($request->get('id'));
+
+
+            $data['install_img'] = json_decode($data['install_img']);
+            $data['specifications_img'] = json_decode($data['specifications_img']);
+            $data['detail'] = json_decode($data['detail']);
+
+
+        return view('admin.album.product.edit',compact('data','cat','style'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        if(!empty($data['specifications_img']['url'])) {
+            foreach ($data['specifications_img']['url'] as $key=>$val) {
+                $data['specifications_img']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['specifications_img'] = json_encode($data['specifications_img']['url']);
+        }
+        if(!empty($data['install_img']['url'])){
+            foreach ($data['install_img']['url'] as $key=>$val) {
+                $data['install_img']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['install_img'] = json_encode($data['install_img']['url']);
+        }
+        if(!empty($data['cover_pic']))
+            $data['cover_pic'] = $this->formatImgUrl($data['cover_pic']);
+        if(!empty($data['thumb']))
+            $data['thumb'] = $this->formatImgUrl($data['thumb']);
+        if(!empty($data['detail']['url'])) {
+            foreach ($data['detail']['url'] as $key=>$val) {
+                $data['detail']['url'][$key] = $this->formatImgUrl($val);
+            }
+            $data['detail'] = json_encode($data['detail']['url']);
+        }
+        if(!empty($data['detail_pic']))
+            $data['detail_pic'] = $this->formatImgUrl($data['detail_pic']);
+       // dd($data);
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Product/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Product/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.product.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        //$bool = $this->repository->destroy($request->get('id'));
+        $cat = AlbumProductModel::find($request->get('id'));
+        $ok = $cat->delete();
+        if($ok) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 141 - 0
app/Http/Controllers/Admin/Album/ReviewController.php

xqd
@@ -0,0 +1,141 @@
+<?php
+/**
+ *  评论管理
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-07-03 16:52:38
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumReviewModel;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\ReviewRepository;
+
+class ReviewController extends Controller
+{
+    private $repository;
+
+    public function __construct(ReviewRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['store_id'] = $this->getStoreId();
+        $list = AlbumReviewModel::where('store_id',$this->getStoreId())
+                ->orderBy('id','desc')
+                ->paginate(10);
+
+        return view('admin.album.review.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.review.check',compact('list'));
+    }
+
+    public function picture(Request $request) {
+        $data = $this->repository->find(request('id'));
+        $img = explode(',',$data['picture']);
+        return view('admin.album.review.picture',compact('img'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.review.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Review/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Review/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Review/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        return view('admin.album.review.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Review/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Review/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.review.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $bool = $this->repository->destroy($request->get('id'));
+        if($bool) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 171 - 0
app/Http/Controllers/Admin/Album/UserController.php

xqd
@@ -0,0 +1,171 @@
+<?php
+/**
+ *  用户管理
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-14 13:25:12
+ *
+ */
+namespace App\Http\Controllers\Admin\Album;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumAgentModel;
+use App\Models\AlbumUserModel;
+use App\Repositories\Album\Criteria\UserWhere;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Criteria\MultiWhere;
+use App\Repositories\Album\UserRepository;
+
+class UserController extends Controller
+{
+    private $repository;
+
+    public function __construct(UserRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $search['storeid'] = $this->getStoreId();
+
+        $order = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $order[$request['sort_field']] = $request['sort_field_by'];
+        }else{
+            $order['id']='DESC';
+        }
+
+        $list = $this->repository->searchUser($search,$order);
+        //dd($list);
+//        dump($list);die;
+        return view('admin.album.user.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.user.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.user.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/User/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/User/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/User/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        $data = $this->repository->find($request->get('id'));
+        if($data->is_dealer == 1){
+            return $this->showWarning('该用户已成为经销商');
+        }
+        $save['is_dealer'] = 1;
+        $ok = $this->repository->update(request('id'),$save);
+
+        if($ok){
+            $add['store_id'] = $this->getStoreId();
+            $add['user_id'] = $data->id;
+            $add['name'] = $data->username;
+            $add['status'] = 1;
+            $res = AlbumAgentModel::create($add);
+            if($res) {
+                return $this->showMessage('操作成功');
+            }else{
+                return $this->showWarning('操作失败');
+            }
+        }
+        return view('admin.album.user.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/User/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/User/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.user.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $bool = $this->repository->destroy($request->get('id'));
+        if($bool) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+
+    public function role(Request $request){
+        $user = AlbumUserModel::find(request('id'));
+        $user->role = request('role');
+        $ok = $user->save();
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+}

+ 264 - 0
app/Http/Controllers/Admin/Album/Xyx/UserController.php

xqd
@@ -0,0 +1,264 @@
+<?php
+/**
+ *  小游戏用户
+ *  @author  system
+ *  @version    1.0
+ *  @date 2018-05-25 10:59:46
+ *
+ */
+namespace App\Http\Controllers\Admin\Album\Xyx;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\AlbumAgentModel;
+use Illuminate\Http\Request;
+use App\Repositories\Base\Criteria\OrderBy;
+use App\Repositories\Album\Xyx\Criteria\MultiWhere;
+use App\Repositories\Album\Xyx\UserRepository;
+use Illuminate\Support\Facades\DB;
+
+class UserController extends Controller
+{
+    private $repository;
+
+    public function __construct(UserRepository $repository) {
+        if(!$this->repository) $this->repository = $repository;
+    }
+
+    function index(Request $request) {
+        $search['keyword'] = $request->input('keyword');
+        $query = $this->repository->pushCriteria(new MultiWhere($search,$this->getStoreId()));
+
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+        $query = $query->pushCriteria(new OrderBy($request['sort_field'],$request['sort_field_by']));
+        }
+        $list = $query->paginate();
+        /*$obj1s = DB::table('album_cat')->where([['store_id',1],['parent_id',0]])->get();
+        $cat_arr = array();
+        if(!empty($obj1s)){
+            foreach ($obj1s as $obj1){
+                $obj1 = get_object_vars($obj1);
+                $obj1['store_id'] = 2;
+                $old_cat_id = $obj1['id'];
+                unset($obj1['id']);
+                $new_cat_id = DB::table('album_cat')->insertGetId($obj1);
+                $cat_arr[$old_cat_id] = $new_cat_id;
+            }
+        }
+        $obj2s = DB::table('album_product_style')->where([['store_id',1]])->get();
+        $style_arr = array();
+        if(!empty($obj2s)){
+            foreach ($obj2s as $obj2){
+                $obj2 = get_object_vars($obj2);
+                $obj2['store_id'] = 2;
+                $old_style_id = $obj2['id'];
+                unset($obj2['id']);
+                $new_style_id = DB::table('album_product_style')->insertGetId($obj2);
+                $style_arr[$old_style_id] = $new_style_id;
+            }
+        }
+        $obj3s = DB::table('album_product')->where([['store_id',1]])->get();
+        if(!empty($obj3s)){
+            foreach ($obj3s as $obj3){
+                $obj3 = get_object_vars($obj3);
+                if(!isset($style_arr[$obj3['style']])||!isset($cat_arr[$obj3['cat_id']])){
+                    continue;
+                }
+                $obj3['store_id'] = 2;
+                $obj3['style'] = $style_arr[$obj3['style']];
+                $obj3['cat_id'] = $cat_arr[$obj3['cat_id']];
+                unset($obj3['id']);
+                DB::table('album_product')->insert($obj3);
+            }
+        }
+        $obj4s = DB::table('album_banner')->where([['store_id',1]])->get();
+        if(!empty($obj4s)){
+            foreach ($obj4s as $obj4){
+                $obj4 = get_object_vars($obj4);
+                $obj4['store_id'] = 2;
+                unset($obj4['id']);
+                DB::table('album_banner')->insert($obj4);
+            }
+        }
+        $obj5s = DB::table('album_manufacturer')->where([['store_id',1]])->get();
+        if(!empty($obj5s)){
+            foreach ($obj5s as $obj5){
+                $obj5 = get_object_vars($obj5);
+                $obj5['store_id'] = 2;
+                unset($obj5['id']);
+                DB::table('album_manufacturer')->insert($obj5);
+            }
+        }
+        $obj6s = DB::table('album_information')->where([['store_id',1]])->get();
+        if(!empty($obj6s)){
+            foreach ($obj6s as $obj6){
+                $obj6 = get_object_vars($obj6);
+                $obj6['store_id'] = 2;
+                unset($obj6['id']);
+                DB::table('album_information')->insert($obj6);
+            }
+        }
+        $obj7s = DB::table('album_news')->where([['store_id',1]])->get();
+        if(!empty($obj7s)){
+            foreach ($obj7s as $obj7){
+                $obj7 = get_object_vars($obj7);
+                $obj7['store_id'] = 2;
+                unset($obj7['id']);
+                DB::table('album_news')->insert($obj7);
+            }
+        }
+        $obj8s = DB::table('album_nav')->where([['store_id',1]])->get();
+        if(!empty($obj8s)){
+            foreach ($obj8s as $obj8){
+                $obj8 = get_object_vars($obj8);
+                $obj8['store_id'] = 2;
+                unset($obj8['id']);
+                DB::table('album_nav')->insert($obj8);
+            }
+        }
+        $obj8s = DB::table('album_info_cat')->where([['store_id',1]])->get();
+        $info_cat_arr = array();
+        if(!empty($obj8s)){
+            foreach ($obj8s as $obj8){
+                $obj8 = get_object_vars($obj8);
+                $obj8['store_id'] = 2;
+                $old_info_cat_id = $obj8['id'];
+                unset($obj8['id']);
+                $new_info_cat_id = DB::table('album_info_cat')->insertGetId($obj8);
+                $info_cat_arr[$old_info_cat_id] = $new_info_cat_id;
+            }
+        }
+        $obj6s = DB::table('album_information')->where([['store_id',1]])->get();
+        if(!empty($obj6s)){
+            foreach ($obj6s as $obj6){
+                $obj6 = get_object_vars($obj6);
+                if(!isset($info_cat_arr[$obj6['cat_id']])){
+                    continue;
+                }
+                $obj6['store_id'] = 2;
+                $obj6['cat_id'] = $info_cat_arr[$obj6['cat_id']];
+                unset($obj6['id']);
+                DB::table('album_information')->insert($obj6);
+            }
+        }*/
+        return view('admin.album.xyx.user.index',compact('list'));
+    }
+
+
+    function check(Request $request) {
+        $request = $request->all();
+        $search['keyword'] = $request->input('keyword');
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $list = $this->repository->search($search,$orderby);
+        return view('admin.album.xyx.user.check',compact('list'));
+    }
+
+
+    /**
+     * 添加
+     * 
+     */
+    public function create(Request $request)
+    {
+        if($request->method() == 'POST') {
+            return $this->_createSave();
+        }
+        return view('admin.album.xyx.user.edit');
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _createSave(){
+        $data = (array) request('data');
+        $id = $this->repository->create($data);
+        if($id) {
+            $url[] = array('url'=>U( 'Album/Xyx/User/index'),'title'=>'返回列表');
+            $url[] = array('url'=>U( 'Album/Xyx/User/create'),'title'=>'继续添加');
+            $this->showMessage('添加成功',$url);
+        }else{
+            $url[] = array('url'=>U( 'Album/Xyx/User/index'),'title'=>'返回列表');
+            return $this->showWarning('添加失败',$url);
+        }
+    }
+    
+    /**
+     * 
+     * 修改
+     * 
+     * 
+     */
+    public function update(Request $request) {
+        /*if($request->method() == 'POST') {
+            return $this->_updateSave();
+        }
+        $data = $this->repository->find($request->get('id'));
+        return view('admin.album.xyx.user.edit',compact('data'));*/
+        $data = $this->repository->find($request->get('id'));
+        if($data->is_dealer == 1){
+            return $this->showWarning('该用户已成为经销商');
+        }
+        $save['is_dealer'] = 1;
+        $ok = $this->repository->update(request('id'),$save);
+
+        if($ok){
+            $add['store_id'] = $this->getStoreId();
+            $add['user_id'] = $data->id;
+            $res = AlbumAgentModel::create($add);
+            if($res) {
+                return $this->showMessage('操作成功');
+            }else{
+                return $this->showWarning('操作失败');
+            }
+        }
+        return view('admin.album.user.edit',compact('data'));
+    }
+
+    /**
+     * 保存修改
+     */
+    private function _updateSave() {
+        $data = (array) request('data');
+        $ok = $this->repository->update(request('id'),$data);
+        if($ok) {
+            $url[] = array('url'=>U( 'Album/Xyx/User/index'),'title'=>'返回列表');
+            return $this->showMessage('操作成功',urldecode(request('_referer')));
+        }else{
+            $url[] = array('url'=>U( 'Album/Xyx/User/index'),'title'=>'返回列表');
+            return $this->showWarning('操作失败',$url);
+        }
+    }
+
+    public function view(Request $request) {
+        $data = $this->repository->find(request('id'));
+        return view('admin.album.xyx.user.view',compact('data'));
+    }
+
+
+    /**
+     *
+     * 状态改变
+     *
+     */
+    public function status(Request $request) {
+        $ok = $this->repository->updateStatus(request('id'),request('status'));
+        if($ok) {
+            return $this->showMessage('操作成功');
+        }else{
+            return $this->showWarning('操作失败');
+        }
+    }
+    
+    /**
+     * 删除
+     */
+    public function destroy(Request $request) {
+        $bool = $this->repository->destroy($request->get('id'));
+        if($bool) {
+            return  $this->showMessage('操作成功');
+        }else{
+            return  $this->showWarning("操作失败");
+        }
+    }
+}

+ 32 - 0
app/Http/Controllers/Admin/Auth/ForgotPasswordController.php

xqd
@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Auth;
+
+use App\Http\Controllers\Admin\Controller;
+use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
+
+class ForgotPasswordController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller is responsible for handling password reset emails and
+    | includes a trait which assists in sending these notifications from
+    | your application to your users. Feel free to explore this trait.
+    |
+    */
+
+    use SendsPasswordResetEmails;
+
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->middleware('guest');
+    }
+}

+ 131 - 0
app/Http/Controllers/Admin/Auth/LoginController.php

xqd
@@ -0,0 +1,131 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Auth;
+
+use App\Models\AdminUserModel;
+use App\Services\Admin\AdminUser;
+use App\Http\Controllers\Admin\Controller;
+use Illuminate\Foundation\Auth\AuthenticatesUsers;
+use Validator, Auth;
+
+class LoginController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Login Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller handles authenticating users for the application and
+    | redirecting them to your home screen. The controller uses a trait
+    | to conveniently provide its functionality to your applications.
+    |
+    */
+
+    use AuthenticatesUsers;
+
+    /**
+     * Where to redirect users after login.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/admin';
+
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->_service = new AdminUser();
+        $this->middleware('guest', ['except' => 'logout']);
+    }
+    /**
+     * 重写登录视图页面
+     * @author 晚黎
+     * @date   2016-09-05T23:06:16+0800
+     * @return [type]                   [description]
+     */
+    public function showLoginForm()
+    {
+        return view('admin.auth.login');
+    }
+    /**
+     * 自定义认证驱动
+     * @author 晚黎
+     * @date   2016-09-05T23:53:07+0800
+     * @return [type]                   [description]
+     */
+    protected function guard()
+    {
+        return auth()->guard('admin');
+    }
+
+    public function username()
+    {
+        return 'name';
+    }
+
+    public function wxLogin()
+    {
+        $store_id = session()->get('store_id');
+        // dd($store_id);die;
+        $wUser = session()->get('wUser');
+        $wAccount = session()->get('wAccount');
+        $res = AdminUserModel::where([['name', $wUser['name']],['store_id', $store_id]])->first();
+        if (!$res) {
+            $createUser['name'] = $wUser['name'];
+            $createUser['real_name'] = $wUser['username'];
+            $createUser['password'] = bcrypt('qweqwe');
+            $createUser['type'] = 0;
+            $createUser['status'] = 1;
+            $createUser['is_root'] = null;
+            $createUser['store_id'] = $store_id;
+            $createUser['admin_role_id'] = 4;
+            $res = AdminUserModel::create($createUser);
+        }
+        //dd(Auth::loginUsingId($res->id));
+        if (Auth::guard('admin')->loginUsingId($res->id)) {
+
+            $userAuth = Auth::guard('admin')->user();
+            session()->put('is_we7','true');
+            return redirect('?route=Admin/Base/Index/index');
+        }
+        //dd('success');
+    }
+
+    public function login(\Illuminate\Http\Request $request)
+    {
+
+        $validator = Validator::make($data = $request->all(),
+            [
+                'name' => 'required', 'password' => 'required',
+            ],
+            [
+                'name.required'=>'请输入用户名',
+                'password.required'=>'请输入密码'
+            ]
+        );
+
+        if ($validator->fails()) {
+            $msg = $validator->messages()->first();
+            return $this->showWarning($msg);
+        }
+        $res = $this->_service->login($request->name, $request->password);
+        if($res) {
+            session()->put('is_we7','true');
+            redirect('/admin/login');
+        }else{
+            $msg = $this->_service->getMsg();
+            $this->showWarning($msg);
+        }
+
+        return redirect('/admin');
+    }
+    public function logout()
+    {
+        Auth::guard('admin')->logout();
+        return redirect('/admin/login');
+
+    }
+}

+ 71 - 0
app/Http/Controllers/Admin/Auth/RegisterController.php

xqd
@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Auth;
+
+use App\User;
+use App\Http\Controllers\Admin\Controller;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Foundation\Auth\RegistersUsers;
+
+class RegisterController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Register Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller handles the registration of new users as well as their
+    | validation and creation. By default this controller uses a trait to
+    | provide this functionality without requiring any additional code.
+    |
+    */
+
+    use RegistersUsers;
+
+    /**
+     * Where to redirect users after registration.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/home';
+
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->middleware('guest');
+    }
+
+    /**
+     * Get a validator for an incoming registration request.
+     *
+     * @param  array  $data
+     * @return \Illuminate\Contracts\Validation\Validator
+     */
+    protected function validator(array $data)
+    {
+        return Validator::make($data, [
+            'name' => 'required|max:255',
+            'email' => 'required|email|max:255|unique:users',
+            'password' => 'required|min:6|confirmed',
+        ]);
+    }
+
+    /**
+     * Create a new user instance after a valid registration.
+     *
+     * @param  array  $data
+     * @return User
+     */
+    protected function create(array $data)
+    {
+        return User::create([
+            'name' => $data['name'],
+            'email' => $data['email'],
+            'password' => bcrypt($data['password']),
+        ]);
+    }
+}

+ 86 - 0
app/Http/Controllers/Admin/Auth/ResetPasswordController.php

xqd
@@ -0,0 +1,86 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Auth;
+
+use App\Http\Controllers\Admin\Controller;
+use Illuminate\Foundation\Auth\ResetsPasswords;
+use Validator, Auth,Hash;
+
+class ResetPasswordController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller is responsible for handling password reset requests
+    | and uses a simple trait to include this behavior. You're free to
+    | explore this trait and override any methods you wish to tweak.
+    |
+    */
+
+    use ResetsPasswords;
+
+    /**
+     * Where to redirect users after resetting their password.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/home';
+
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->middleware('guest');
+    }
+
+    public function showChangeForm()
+    {
+        return view('admin.auth.change');
+    }
+
+    public function changePassword(\Illuminate\Http\Request $request)
+    {
+
+        $validator = Validator::make($data = $request->all(),
+            [
+                'old_passwprd' => 'required', 'password' => 'required','password_confirmation'=> 'required'
+            ],
+            [
+                'old_passwprd.required'=>'请输入原密码',
+                'password.required'=>'请输入密码',
+                'password_confirmation.required'=>'请输入确认密码',
+            ]
+        );
+        if ($validator->fails()) {
+            $msg = $validator->messages()->first();
+            return $this->showWarning($msg);
+        }
+
+        $user = Auth::guard('admin')->user();
+        $oldpassword = $request->input('old_passwprd');
+        $newpassword = $request->input('password');
+        $password_confirmation= $request->input('password_confirmation');
+
+        if($newpassword!=$password_confirmation){
+            return $this->showWarning("两次密码一直");
+        }
+
+        if(!Hash::check($oldpassword, $user->password)){
+            return $this->showWarning("原密码不正确");
+        }
+
+        $user->password = bcrypt($newpassword);
+        $result = $user->save();
+
+        if($result){
+            return $this->showMessage("修改成功");
+        }else{
+            return $this->showWarning("修改密码失败");
+        }
+    }
+}

+ 69 - 0
app/Http/Controllers/Admin/Base/ActionlogController.php

xqd
@@ -0,0 +1,69 @@
+<?php
+/**
+ * 操作日志
+ *  @author  Mike <m@9026.com>
+ *  @version    1.0
+ *  @date 2015年10月15日
+ *
+ */
+namespace App\Http\Controllers\Admin\Base;
+
+use App\Http\Controllers\Admin\Controller;
+use Request;
+use App\Services\User\Payinfo;
+use App\Utils\AliLog;
+use Carbon\Carbon;
+
+class ActionlogController extends Controller {
+    private $_service;
+    private $_viewdata;
+    private $_aTopic = [ 'action-view-log'=>'访问日志','action-operate-log'=>'操作日志'];
+    /**
+     * 初始化Service
+     */
+    public function __construct()
+    {
+        parent::__construct();
+    
+        if(!$this->_service) $this->_service = new Payinfo();
+        
+        $this->_viewdata = array();
+    }
+    
+    /**
+     * 列表
+     */
+    function index()
+    {
+        $obj = new AliLog();
+        $this->_viewdata['aTopIc'] = $this->_aTopic;
+        $this->_viewdata['topic'] = Request::input('topic','action-view-log');
+        $this->_viewdata['aLine'] = [50=>'50行',100=>'100行',200=>'200行'];
+        $this->_viewdata['line']  = Request::input('line',50);
+        $this->_viewdata['start_time']  = Request::input('start_time',Carbon::yesterday());
+        $this->_viewdata['end_time']  = Request::input('end_time',Carbon::tomorrow());
+        $query = '';
+        $data = $obj->listLogs('admin-operate-log',
+                                strtotime($this->_viewdata['start_time']),
+                                strtotime($this->_viewdata['end_time']), 
+                                $this->_viewdata['topic'],
+                                $query,
+                                $this->_viewdata['line']
+                );
+        $request = Request::all();
+        $search['keyword'] = Request::input('keyword');
+        
+        $orderby = array();
+        if(isset($request['sort_field']) && $request['sort_field'] && isset($request['sort_field_by'])) {
+            $orderby[$request['sort_field']] = $request['sort_field_by'];
+        }
+        $this->_viewdata['data'] = $data;
+    
+        return view('admin.base.actionlog.index', $this->_viewdata);
+    }
+    
+    
+    
+    
+    
+}

+ 168 - 0
app/Http/Controllers/Admin/Base/AttachmentController.php

xqd
@@ -0,0 +1,168 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Base;
+
+use App\Http\Controllers\Admin\Controller;
+use App\Services\Base\Attachment;
+use App\Models\BaseAttachmentModel;
+use Request, Response;
+
+class AttachmentController extends Controller
+{
+    private $_serviceAttachment;
+    private $_serviceAttachmentBbs;
+    private $_serviceForumAttachment;
+    private $_serviceForumAttachmentUnused;
+
+    public function __construct()
+    {
+        if( !$this->_serviceAttachment ) $this->_serviceAttachment = new Attachment();
+
+    }
+
+
+    /**
+     * SWFUpload文件上传
+     */
+    public function upload()
+    {
+        $request = Request::all();
+
+        //通过上传控件ID,区分文件上传类型
+        if(isset($request['elementid']) && substr($request['elementid'], 0, 15) == 'upload_template'){
+
+            $this->_uploadTemplate($request);
+
+            //上传到本地
+        }elseif(isset($request['position']) && $request['position'] == 'local'){
+
+            $this->_uploadToServer($request);
+
+            //上传专题文件
+        }elseif(isset($request['position']) && $request['position'] == 'special'){
+
+            $this->_uploadSpecial($request);
+
+            //上传到阿里云
+        } else{
+
+            $this->_uploadToAlioss($request);
+
+        }
+    }
+
+
+    /**
+     * 上传到本地
+     */
+    private function _uploadToServer($request)
+    {
+        $return = $this->_serviceAttachment->localUpload('imgFile', $request);
+        if($return['code']=='200'){
+            echo json_encode(["error"=> 0,"url" => $return['fileurl']]);exit;
+        }else{
+
+            echo json_encode(["error" => 1,"message" =>  $return['message']]);exit;
+        }
+    }
+
+    /**
+     * 上传专题文件
+     */
+    private function _uploadSpecial($request)
+    {
+        $return = $this->_serviceAttachment->specialUpload('Filedata', $request);
+        echo json_encode($return);exit;
+    }
+
+    /**
+     * 上传到阿里云
+     */
+    private function _uploadToAlioss($request)
+    {
+        $return = [];
+        if(isset($request['KindEditor'])){
+            $data = $this->_serviceAttachment->aliUpload($request['field'], $request);
+            if($data['code'] === 200){
+                $return['error'] = 0;
+                $return['url']   = $data['fileurl'];
+            }else{
+                $return['error']    = 1;
+                $return['message']  = $data['message'];
+            }
+        }else{
+            $return = $this->_serviceAttachment->aliUpload('Filedata', $request);
+        }
+        echo json_encode($return);exit;
+    }
+
+    /**
+     * 百度上传
+     */
+    public function webupload()
+    {
+        $request = request()->all();
+        $data = $this->_serviceAttachment->localUpload('file', $request, 'files');
+//        return response()->json(array('data' => $data), 200);
+        echo json_encode($data);exit;
+    }
+
+
+    /**
+     * @api {get} /api/attachment/download/{md5} 下载文件(图片)
+     * @apiDescription 下载文件(图片)(get code)
+     * @apiGroup Attachment
+     * @apiPermission none
+     * @apiVersion 0.1.0
+     * @apiParam {string} md5   图片md5码
+     * @apiSuccessExample {json} Success-Response:
+     *     HTTP/1.1 200 OK
+     *     {
+     *       文件二进制码
+     *     }
+     * @apiErrorExample {json} Error-Response:
+     *     HTTP/1.1 404 Not found
+     */
+    public function download()
+    {
+        $request = Request::all();
+        $attachment = BaseAttachmentModel::where(['md5' => $request['md5']])->first();
+        if (!$attachment) {
+            return view('errors.404');
+        }
+
+        return Response::download($attachment->path, $attachment->name, [
+            'Content-type'  => $attachment->file_type,
+            'Accept-Ranges' => 'bytes',
+            'Accept-Length' => $attachment->size,
+        ]);
+    }
+
+
+
+
+    private function _createAttachmentRecord($data)
+    {
+        $uid = Request::input('uid');
+        $aid = $this->_serviceForumAttachment->create(['tid' => 0, 'pid' => 0, 'uid' => $uid, 'tableid' => 127]);
+        if($aid){
+            $info = [
+                'aid'           => $aid,
+                'uid'           => $uid,
+                'dateline'      => SYSTEM_TIME,
+                'filename'      => $data['filename'],
+                'filesize'      => $data['filesize'],
+                'attachment'    => $data['attachment'],
+                'isimage'       => (isset($data['width']) && $data['width']) ? 1 : 0,
+                'remote'        => 1,
+                'width'         => isset($data['width']) ? $data['width'] : 0,
+                'thumb'         => 0,
+            ];
+            if(!$this->_serviceForumAttachmentUnused->create($info)){
+                return 0;
+            }
+        }
+        return $aid;
+    }
+
+}

+ 58 - 0
app/Http/Controllers/Admin/Base/ClassController.php

xqd
@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Base;
+
+use Illuminate\Http\Request as HttpRequest;
+use App\Http\Controllers\Admin\Controller;
+use App\Models\ClassModel;
+use App\Models\BaseAttachmentModel;
+use Request, Response, Validator, File;
+
+class ClassController extends Controller
+{
+    public function add(HttpRequest $request)
+    {
+    	if(! $request->has('class') || Request::method() != 'POST') {
+    		return back();
+    	}
+
+    	$validator = Validator::make($request->all(), [
+            'class' => 'required|string|max:100|unique:classes,class',
+        ]);
+    	if($validator->fails()) {
+    		$validator->errors()->add('my-error', '分类已存在!');
+    		return back()->withErrors($validator)->withInput();
+    	}
+    	
+    	$class = new ClassModel;
+    	$class->class = $request->input('class');
+    	if(! $class->save()) {
+    		$validator->errors()->add('my-error', '添加分类失败!');
+    		return back()->withErrors($validator)->withInput();
+    	}
+
+    	return back();
+    }
+
+    public function delete(HttpRequest $request)
+    {
+        if(Request::method() != 'POST') {
+            return back();
+        }
+
+        $class = null;
+        if($request->has('class') && ($class = ClassModel::find($request->input('class'))) != null) {
+            if($class->class == '未分类') {
+                return back();
+            }
+
+            $photos = BaseAttachmentModel::where('class', $class->class)->get();
+            foreach($photos as $photo) {
+                File::delete($photo->path);
+                $photo->delete();
+            }
+            $class->delete();
+        }
+        return redirect('admin/Base/Photos/index');
+    }
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov