Completed
Push — master ( 0598e6...0379c1 )
by
unknown
13s queued 11s
created

MailEclipse::templateComponentReplace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 53
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 43
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 53
rs 9.232

1 Method

Rating   Name   Duplication   Size   Complexity  
A MailEclipse::previewMarkdownHtml() 0 3 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Qoraiche\MailEclipse;
4
5
use ErrorException;
6
use Illuminate\Database\Eloquent\Factory as EloquentFactory;
7
use Illuminate\Http\JsonResponse;
8
use Illuminate\Mail\Markdown;
9
use Illuminate\Support\Collection;
10
use Illuminate\Support\Facades\Artisan;
11
use Illuminate\Support\Facades\DB;
12
use Illuminate\Support\Facades\File;
13
use Illuminate\Support\Facades\Mail;
14
use Illuminate\Support\Facades\View;
15
use Illuminate\Support\Str;
16
use Qoraiche\MailEclipse\Utils\Replacer;
17
use RecursiveDirectoryIterator;
18
use RecursiveIteratorIterator;
19
use ReeceM\Mocker\Mocked;
20
use ReflectionClass;
21
use ReflectionProperty;
22
use RegexIterator;
23
24
/**
25
 * Class MailEclipse.
26
 */
27
class MailEclipse
28
{
29
    public const VIEW_NAMESPACE = 'maileclipse';
30
31
    public const VERSION = '3.3.0';
32
33
    /**
34
     * Default type examples for being passed to reflected classes.
35
     *
36
     * @var array TYPES
37
     */
38
    public const TYPES = [
39
        'int' => 31,
40
        // 'string' => 'test_string', // not needed as it can be cast __toString()
41
        'bool' => false,
42
        'float' => 3.14159,
43
    ];
44
45
    /**
46
     * @return array
47
     * @throws \ReflectionException
48
     */
49
    public static function getMailables()
50
    {
51
        return self::mailablesList();
52
    }
53
54
    /**
55
     * @param $key
56
     * @param $name
57
     * @return Collection
58
     * @throws \ReflectionException
59
     */
60
    public static function getMailable($key, $name): Collection
61
    {
62
        return collect(self::getMailables())->where($key, $name);
63
    }
64
65
    /**
66
     * @param $templateSlug
67
     * @return bool
68
     */
69
    public static function deleteTemplate($templateSlug): bool
70
    {
71
        $template = self::getTemplates()
72
            ->where('template_slug', $templateSlug)->first();
73
74
        if ($template !== null) {
75
            self::saveTemplates(self::getTemplates()->reject(function ($value) use ($template) {
76
                return $value->template_slug === $template->template_slug;
77
            }));
78
79
            $template_view = self::VIEW_NAMESPACE.'::templates.'.$templateSlug;
80
            $template_plaintext_view = $template_view.'_plain_text';
81
82
            if (View::exists($template_view)) {
83
                unlink(View($template_view)->getPath());
84
85
                // remove plain text template version when exists
86
                if (View::exists($template_plaintext_view)) {
87
                    unlink(View($template_plaintext_view)->getPath());
88
                }
89
90
                return true;
91
            }
92
        }
93
94
        return false;
95
    }
96
97
    /**
98
     * @return string
99
     */
100
    public static function getTemplatesFile()
101
    {
102
        $file = config('maileclipse.mailables_dir').'templates.json';
103
        if (! file_exists($file)) {
104
            if (! file_exists(config('maileclipse.mailables_dir'))) {
105
                if (! mkdir($concurrentDirectory = config('maileclipse.mailables_dir')) && ! is_dir($concurrentDirectory)) {
106
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
107
                }
108
            }
109
            file_put_contents($file, '[]');
110
        }
111
112
        return $file;
113
    }
114
115
    /**
116
     * Save templates to templates.json file.
117
     *
118
     * @param Collection $templates
119
     */
120
    public static function saveTemplates(Collection $templates): void
121
    {
122
        file_put_contents(self::getTemplatesFile(), $templates->toJson());
123
    }
124
125
    /**
126
     * @param $request
127
     * @return JsonResponse|null
128
     */
129
    public static function updateTemplate($request): ?JsonResponse
130
    {
131
        $template = self::getTemplates()
132
            ->where('template_slug', $request->templateslug)->first();
133
134
        if ($template !== null) {
135
            if (! preg_match("/^[a-zA-Z0-9-_\s]+$/", $request->title)) {
136
                return response()->json([
137
                    'status' => 'failed',
138
                    'message' => 'Template name not valid',
139
                ]);
140
            }
141
142
            $templateName = Str::camel(preg_replace('/\s+/', '_', $request->title));
143
144
            if (self::getTemplates()->contains('template_slug', '=', $templateName)) {
145
                return response()->json([
146
147
                    'status' => 'failed',
148
                    'message' => 'Template name already exists',
149
150
                ]);
151
            }
152
153
            // Update
154
            $oldForm = self::getTemplates()->reject(function ($value) use ($template) {
155
                return $value->template_slug === $template->template_slug;
156
            });
157
            $newForm = array_merge($oldForm->toArray(), [array_merge((array) $template, [
158
                'template_slug' => $templateName,
159
                'template_name' => $request->title,
160
                'template_description' => $request->description,
161
            ])]);
162
163
            self::saveTemplates(collect($newForm));
164
165
            $template_view = self::VIEW_NAMESPACE.'::templates.'.$request->templateslug;
166
            $template_plaintext_view = $template_view.'_plain_text';
167
168
            if (View::exists($template_view)) {
169
                $viewPath = View($template_view)->getPath();
170
171
                rename($viewPath, dirname($viewPath)."/{$templateName}.blade.php");
172
173
                if (View::exists($template_plaintext_view)) {
174
                    $textViewPath = View($template_plaintext_view)->getPath();
175
176
                    rename($textViewPath, dirname($viewPath)."/{$templateName}_plain_text.blade.php");
177
                }
178
            }
179
180
            return response()->json([
181
                'status' => 'ok',
182
                'message' => 'Updated Successfully',
183
                'template_url' => route('viewTemplate', ['templatename' => $templateName]),
184
            ]);
185
        }
186
    }
187
188
    /**
189
     * @param $templateSlug
190
     * @return Collection|null
191
     */
192
    public static function getTemplate($templateSlug): ?Collection
193
    {
194
        $template = self::getTemplates()->where('template_slug', $templateSlug)->first();
195
196
        if ($template !== null) {
197
            $template_view = self::VIEW_NAMESPACE.'::templates.'.$template->template_slug;
198
            $template_plaintext_view = $template_view.'_plain_text';
199
200
            if (View::exists($template_view)) {
201
                $viewPath = View($template_view)->getPath();
202
                $textViewPath = View($template_plaintext_view)->getPath();
203
204
                /** @var Collection $templateData */
205
                $templateData = collect([
206
                    'template' => Replacer::toEditor(file_get_contents($viewPath)),
207
                    'plain_text' => View::exists($template_plaintext_view) ? file_get_contents($textViewPath) : '',
208
                    'slug' => $template->template_slug,
209
                    'name' => $template->template_name,
210
                    'description' => $template->template_description,
211
                    'template_type' => $template->template_type,
212
                    'template_view_name' => $template->template_view_name,
213
                    'template_skeleton' => $template->template_skeleton,
214
                ]);
215
216
                return $templateData;
217
            }
218
        }
219
    }
220
221
    /**
222
     * Get templates collection.
223
     *
224
     * @return Collection
225
     */
226
    public static function getTemplates(): Collection
227
    {
228
        return collect(json_decode(file_get_contents(self::getTemplatesFile())));
229
    }
230
231
    /**
232
     * @param $request
233
     * @return JsonResponse
234
     */
235
    public static function createTemplate($request): JsonResponse
236
    {
237
        if (! preg_match("/^[a-zA-Z0-9-_\s]+$/", $request->template_name)) {
238
            return response()->json([
239
                'status' => 'error',
240
                'message' => 'Template name not valid',
241
242
            ]);
243
        }
244
245
        $view = self::VIEW_NAMESPACE.'::templates.'.$request->template_name;
246
        $templateName = Str::camel(preg_replace('/\s+/', '_', $request->template_name));
247
248
        if (! view()->exists($view) && ! self::getTemplates()->contains('template_slug', '=', $templateName)) {
249
            self::saveTemplates(self::getTemplates()
250
                ->push([
251
                    'template_name' => $request->template_name,
252
                    'template_slug' => $templateName,
253
                    'template_description' => $request->template_description,
254
                    'template_type' => $request->template_type,
255
                    'template_view_name' => $request->template_view_name,
256
                    'template_skeleton' => $request->template_skeleton,
257
                ]));
258
259
            $dir = resource_path('views/vendor/'.self::VIEW_NAMESPACE.'/templates');
260
261
            if (! File::isDirectory($dir)) {
262
                File::makeDirectory($dir, 0755, true);
263
            }
264
265
            file_put_contents($dir."/{$templateName}.blade.php", Replacer::toBlade($request->content));
266
267
            file_put_contents($dir."/{$templateName}_plain_text.blade.php", $request->plain_text);
268
269
            return response()->json([
270
                'status' => 'ok',
271
                'message' => 'Template created<br> <small>Reloading...<small>',
272
                'template_url' => route('viewTemplate', ['templatename' => $templateName]),
273
            ]);
274
        }
275
276
        return response()->json([
277
            'status' => 'error',
278
            'message' => 'Template not created',
279
280
        ]);
281
    }
282
283
    protected static function markdownedTemplate($viewPath)
284
    {
285
        $viewContent = file_get_contents($viewPath);
286
287
        return Replacer::toEditor($viewContent);
288
    }
289
290
    /**
291
     * Markdowned template view.
292
     */
293
    public static function markdownedTemplateToView($save = true, $content = '', $viewPath = '', $template = false)
294
    {
295
        if ($template && View::exists(self::VIEW_NAMESPACE.'::templates.'.$viewPath)) {
296
            $viewPath = View(self::VIEW_NAMESPACE.'::templates.'.$viewPath)->getPath();
297
        }
298
299
        $replaced = Replacer::toBlade($content);
300
301
        if (! $save) {
302
            return $replaced;
303
        }
304
305
        return file_put_contents($viewPath, $replaced) !== false;
306
    }
307
308
    /**
309
     * @param $simpleview
310
     * @param $content
311
     * @param $viewName
312
     * @param bool $template
313
     * @param null $namespace
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $namespace is correct as it would always require null to be passed?
Loading history...
314
     * @return bool|string|void
315
     * @throws \ReflectionException
316
     */
317
    public static function previewMarkdownViewContent($simpleview, $content, $viewName, $template = false, $namespace = null)
318
    {
319
        $previewtoset = self::markdownedTemplateToView(false, $content);
320
        $dir = dirname(__FILE__, 2).'/resources/views/draft';
321
        $viewName = $template ? $viewName.'_template' : $viewName;
322
323
        if (file_exists($dir)) {
324
            file_put_contents($dir."/{$viewName}.blade.php", $previewtoset);
325
326
            if ($template) {
327
                $instance = null;
328
            } elseif (self::handleMailableViewDataArgs($namespace) !== null) {
329
                $instance = self::handleMailableViewDataArgs($namespace);
330
            } else {
331
                $instance = new $namespace;
332
            }
333
334
            return self::renderPreview($simpleview, self::VIEW_NAMESPACE.'::draft.'.$viewName, $template, $instance);
0 ignored issues
show
Bug introduced by
It seems like $instance can also be of type object; however, parameter $instance of Qoraiche\MailEclipse\MailEclipse::renderPreview() does only seem to accept null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

334
            return self::renderPreview($simpleview, self::VIEW_NAMESPACE.'::draft.'.$viewName, $template, /** @scrutinizer ignore-type */ $instance);
Loading history...
335
        }
336
337
        return false;
338
    }
339
340
    /**
341
     * @param $instance
342
     * @param $view
343
     * @return string|void
344
     */
345
    public static function previewMarkdownHtml($instance, $view)
346
    {
347
        return self::renderPreview($instance, $view);
348
    }
349
350
    /**
351
     * @param $mailableName
352
     * @return array|bool
353
     */
354
    public static function getMailableTemplateData($mailableName)
355
    {
356
        $mailable = self::getMailable('name', $mailableName);
357
358
        if ($mailable->isEmpty()) {
359
            return false;
360
        }
361
362
        $templateData = collect($mailable->first())->only(['markdown', 'view_path', 'text_view_path', 'text_view', 'view_data', 'data', 'namespace'])->all();
363
364
        $templateExists = $templateData['view_path'] !== null;
365
        $textTemplateExists = $templateData['text_view_path'] !== null;
366
367
        if ($templateExists) {
368
            $viewPathParams = collect($templateData)->union([
369
370
                'text_template' => $textTemplateExists ? file_get_contents($templateData['text_view_path']) : null,
371
                'template' => file_get_contents($templateData['view_path']),
372
                'markdowned_template' => self::markdownedTemplate($templateData['view_path']),
373
                'template_name' => $templateData['markdown'] ?? $templateData['data']->view,
374
                'is_markdown' => $templateData['markdown'] !== null,
375
            ])->all();
376
377
            return $viewPathParams;
378
        }
379
380
        return $templateData;
381
    }
382
383
    /**
384
     * @param null $request
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $request is correct as it would always require null to be passed?
Loading history...
385
     * @return JsonResponse
386
     */
387
    public static function generateMailable($request = null): JsonResponse
388
    {
389
        $name = self::generateClassName($request->input('name'));
0 ignored issues
show
Bug introduced by
The method input() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

389
        $name = self::generateClassName($request->/** @scrutinizer ignore-call */ input('name'));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
390
391
        if ($name === false) {
392
            return response()->json([
393
                'status' => 'error',
394
                'message' => 'Wrong name format.',
395
            ]);
396
        }
397
398
        if (! $request->has('force') && ! self::getMailable('name', $name)->isEmpty()) {
399
            return response()->json([
400
                'status' => 'error',
401
                'message' => 'This mailable name already exists. names should be unique! to override it, enable "force" option.',
402
            ]);
403
        }
404
405
        if (strtolower($name) === 'mailable') {
406
            return response()->json([
407
                'status' => 'error',
408
                'message' => 'You cannot use "mailable" as a mailable name',
409
            ]);
410
        }
411
412
        $params = collect([
413
            'name' => $name,
414
        ]);
415
416
        if ($request->input('markdown')) {
417
            $params->put('--markdown', $request->markdown);
418
        }
419
420
        if ($request->has('force')) {
421
            $params->put('--force', true);
422
        }
423
424
        $exitCode = Artisan::call('make:mail', $params->all());
425
426
        if ($exitCode > -1) {
427
            return response()->json([
428
                'status' => 'ok',
429
                'message' => 'Mailable Created<br> <small>Reloading...<small>',
430
            ]);
431
        }
432
433
        return response()->json([
434
435
            'status' => 'error',
436
            'message' => 'mailable not created successfully',
437
438
        ]);
439
    }
440
441
    /**
442
     * Get Mailables list.
443
     *
444
     * @return array
445
     * @throws \ReflectionException
446
     */
447
    protected static function mailablesList()
448
    {
449
        $fqcns = [];
450
451
        if (! file_exists(config('maileclipse.mailables_dir'))) {
452
            return;
453
        } else {
454
            $allFiles = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(config('maileclipse.mailables_dir')));
455
            $phpFiles = new RegexIterator($allFiles, '/\.php$/');
456
            $i = 0;
457
458
            foreach ($phpFiles as $phpFile) {
459
                $i++;
460
                $content = file_get_contents($phpFile->getRealPath());
461
                $tokens = token_get_all($content);
462
                $namespace = '';
463
                for ($index = 0; isset($tokens[$index]); $index++) {
464
                    if (! isset($tokens[$index][0])) {
465
                        continue;
466
                    }
467
                    if (T_NAMESPACE === $tokens[$index][0]) {
468
                        $index += 2; // Skip namespace keyword and whitespace
469
                        while (isset($tokens[$index]) && is_array($tokens[$index])) {
470
                            $namespace .= $tokens[$index++][1];
471
                        }
472
                    }
473
                    if (T_CLASS === $tokens[$index][0] && T_WHITESPACE === $tokens[$index + 1][0] && T_STRING === $tokens[$index + 2][0]) {
474
                        $index += 2; // Skip class keyword and whitespace
475
476
                        [$name, $extension] = explode('.', $phpFile->getFilename());
477
478
                        $mailableClass = $namespace.'\\'.$tokens[$index][1];
479
480
                        if (! self::mailable_exists($mailableClass)) {
481
                            continue;
482
                        }
483
484
                        $reflector = new ReflectionClass($mailableClass);
485
486
                        if ($reflector->isAbstract()) {
487
                            continue;
488
                        }
489
490
                        $mailable_data = self::buildMailable($mailableClass);
491
492
                        if (! is_null(self::handleMailableViewDataArgs($mailableClass))) {
493
                            $mailable_view_data = self::getMailableViewData(self::handleMailableViewDataArgs($mailableClass), $mailable_data);
494
                        } else {
495
                            $mailable_view_data = self::getMailableViewData(new $mailableClass, $mailable_data);
496
                        }
497
498
                        $fqcns[$i]['data'] = $mailable_data;
499
                        $fqcns[$i]['markdown'] = self::getMarkdownViewName($mailable_data);
500
                        $fqcns[$i]['name'] = $name;
501
                        $fqcns[$i]['namespace'] = $mailableClass;
502
                        $fqcns[$i]['filename'] = $phpFile->getFilename();
503
                        $fqcns[$i]['modified'] = $phpFile->getCTime();
504
                        $fqcns[$i]['viewed'] = $phpFile->getATime();
505
                        $fqcns[$i]['view_data'] = $mailable_view_data;
506
                        // $fqcns[$i]['view_data'] = [];
507
                        $fqcns[$i]['path_name'] = $phpFile->getPathname();
508
                        $fqcns[$i]['readable'] = $phpFile->isReadable();
509
                        $fqcns[$i]['writable'] = $phpFile->isWritable();
510
                        $fqcns[$i]['view_path'] = null;
511
                        $fqcns[$i]['text_view_path'] = null;
512
513
                        if (! is_null($fqcns[$i]['markdown']) && View::exists($fqcns[$i]['markdown'])) {
514
                            $fqcns[$i]['view_path'] = View($fqcns[$i]['markdown'])->getPath();
515
                        }
516
517
                        if (! is_null($fqcns[$i]['data'])) {
518
                            if (! is_null($fqcns[$i]['data']->view) && View::exists($fqcns[$i]['data']->view)) {
519
                                $fqcns[$i]['view_path'] = View($fqcns[$i]['data']->view)->getPath();
520
                            }
521
522
                            if (! is_null($fqcns[$i]['data']->textView) && View::exists($fqcns[$i]['data']->textView)) {
523
                                $fqcns[$i]['text_view_path'] = View($fqcns[$i]['data']->textView)->getPath();
524
                                $fqcns[$i]['text_view'] = $fqcns[$i]['data']->textView;
525
                            }
526
                        }
527
528
                        // break if you have one class per file (psr-4 compliant)
529
                        // otherwise you'll need to handle class constants (Foo::class)
530
                        break;
531
                    }
532
                }
533
            }
534
535
            $collection = collect($fqcns)->map(function ($mailable) {
536
                return $mailable;
537
            })->reject(function ($object) {
538
                return ! method_exists($object['namespace'], 'build');
539
            });
540
541
            return $collection;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $collection returns the type Illuminate\Support\Collection which is incompatible with the documented return type array.
Loading history...
542
        }
543
    }
544
545
    /**
546
     * Handle Mailable Constructor arguments and pass the fake ones.
547
     * @param $mailable
548
     * @return object|void
549
     * @throws \ReflectionException
550
     */
551
    public static function handleMailableViewDataArgs($mailable)
552
    {
553
        if (method_exists($mailable, '__construct')) {
554
            $reflection = new ReflectionClass($mailable);
555
556
            $params = $reflection->getConstructor()->getParameters();
557
558
            DB::beginTransaction();
559
560
            $eloquentFactory = app(EloquentFactory::class);
561
562
            $args = collect($params)->map(function ($param) {
563
                if ($param->getType() !== null) {
564
                    if (class_exists($param->getType()->getName())) {
565
                        $parameters = [
566
                            'is_instance' => true,
567
                            'instance' => $param->getType()->getName(),
568
                        ];
569
                    } elseif ($param->getType()->getName() === 'array') {
570
                        $parameters = [
571
                            'is_array' => true,
572
                            'arg' => $param->getName(),
573
                        ];
574
                    } else {
575
                        $parameters = $param->name;
576
                    }
577
578
                    return $parameters;
579
                }
580
581
                return $param->name;
582
            });
583
584
            $resolvedTypeHints = [];
585
586
            foreach ($args->all() as $arg) {
587
                if (is_array($arg)) {
588
                    if (isset($arg['is_instance'])) {
589
                        $model = $arg['instance'];
590
591
                        $resolvedTypeHints = self::resolveFactory($eloquentFactory, $model, $resolvedTypeHints);
592
                    } elseif (isset($arg['is_array'])) {
593
                        $resolvedTypeHints[] = [];
594
                    } else {
595
                        return;
596
                    }
597
                } else {
598
                    $resolvedTypeHints[] = self::getMissingParams($arg, $params);
599
                }
600
            }
601
602
            $reflector = new ReflectionClass($mailable);
603
604
            if ($args->isNotEmpty()) {
605
                return $reflector->newInstanceArgs($resolvedTypeHints);
606
            }
607
608
            DB::rollBack();
609
        }
610
    }
611
612
    /**
613
     * Gets any missing params that may not be collectable in the reflection.
614
     *
615
     * @param string $arg the argument string|array
616
     * @param array $params the reflection param list
617
     *
618
     * @return array|string|\ReeceM\Mocker\Mocked
619
     */
620
    private static function getMissingParams($arg, $params)
621
    {
622
        /**
623
         * Determine if a builtin type can be found.
624
         * Not a string or object as a Mocked::class can work there.
625
         *
626
         * getName() is undocumented alternative to casting to string.
627
         * https://www.php.net/manual/en/class.reflectiontype.php#124658
628
         *
629
         * @var \ReflectionType $reflection
630
         */
631
        $reflection = collect($params)->where('name', $arg)->first()->getType();
632
633
        if (version_compare(phpversion(), '7.1', '>=')) {
634
            $type = ! is_null($reflection)
635
                ? self::TYPES[$reflection->getName()]
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

635
                ? self::TYPES[$reflection->/** @scrutinizer ignore-call */ getName()]
Loading history...
636
                : null;
637
        } else {
638
            $type = ! is_null($reflection)
639
                ? self::TYPES[/** @scrutinizer ignore-deprecated */ $reflection->__toString()]
640
                : null;
641
        }
642
643
        try {
644
            return ! is_null($type)
645
                    ? $type
646
                    : new Mocked($arg, \ReeceM\Mocker\Utils\VarStore::singleton());
647
        } catch (\Exception $e) {
648
            return $arg;
649
        }
650
    }
651
652
    /**
653
     * @param $mailable
654
     * @param $mailable_data
655
     * @return array|Collection
656
     * @throws \ReflectionException
657
     */
658
    private static function getMailableViewData($mailable, $mailable_data)
659
    {
660
        $traitProperties = [];
661
662
        $data = new ReflectionClass($mailable);
663
664
        foreach ($data->getTraits() as $trait) {
665
            foreach ($trait->getProperties(ReflectionProperty::IS_PUBLIC) as $traitProperty) {
666
                $traitProperties[] = $traitProperty->name;
667
            }
668
        }
669
670
        $properties = $data->getProperties(ReflectionProperty::IS_PUBLIC);
671
        $allProps = [];
672
673
        foreach ($properties as $prop) {
674
            if ($prop->class == $data->getName() || $prop->class == get_parent_class($data->getName()) &&
675
                    get_parent_class($data->getName()) != 'Illuminate\Mail\Mailable' && ! $prop->isStatic()) {
676
                $allProps[] = $prop->name;
677
            }
678
        }
679
680
        $obj = self::buildMailable($mailable);
681
682
        if ($obj === null) {
683
            $obj = [];
684
685
            return collect($obj);
686
        }
687
688
        $classProps = array_diff($allProps, $traitProperties);
689
690
        $withFuncData = collect($obj->viewData)->keys();
691
692
        $mailableData = collect($classProps)->merge($withFuncData);
693
694
        $data = $mailableData->map(function ($parameter) use ($mailable_data) {
695
            return [
696
                'key' => $parameter,
697
                'value' => property_exists($mailable_data, $parameter) ? $mailable_data->$parameter : null,
698
                'data' => property_exists($mailable_data, $parameter) ? self::viewDataInspect($mailable_data->$parameter) : null,
699
            ];
700
        });
701
702
        return $data->all();
703
    }
704
705
    /**
706
     * @param $param
707
     * @return array
708
     */
709
    protected static function viewDataInspect($param): ?array
710
    {
711
        if ($param instanceof \Illuminate\Database\Eloquent\Model) {
712
            return [
713
                'type' => 'model',
714
                'attributes' => collect($param->getAttributes()),
715
            ];
716
        }
717
718
        if ($param instanceof \Illuminate\Database\Eloquent\Collection) {
719
            return [
720
                'type' => 'elequent-collection',
721
                'attributes' => $param->all(),
722
            ];
723
        }
724
725
        if ($param instanceof \Illuminate\Support\Collection) {
726
            return [
727
                'type' => 'collection',
728
                'attributes' => $param->all(),
729
            ];
730
        }
731
732
        return null;
733
    }
734
735
    /**
736
     * @param $mailable
737
     * @return bool
738
     */
739
    protected static function mailable_exists($mailable): bool
740
    {
741
        if (! class_exists($mailable)) {
742
            return false;
743
        }
744
745
        return true;
746
    }
747
748
    /**
749
     * @param $mailable
750
     * @return mixed|void
751
     * @throws \ReflectionException
752
     */
753
    protected static function getMarkdownViewName($mailable)
754
    {
755
        if ($mailable === null) {
756
            return;
757
        }
758
759
        $reflection = new ReflectionClass($mailable);
760
761
        $property = $reflection->getProperty('markdown');
762
763
        $property->setAccessible(true);
764
765
        return $property->getValue($mailable);
766
    }
767
768
    /**
769
     * @param $instance
770
     * @param string $type
771
     * @return mixed
772
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
773
     * @throws \ReflectionException
774
     */
775
    public static function buildMailable($instance, $type = 'call')
776
    {
777
        if ($type === 'call') {
778
            if (self::handleMailableViewDataArgs($instance) !== null) {
779
                return app()->call([self::handleMailableViewDataArgs($instance), 'build']);
780
            }
781
782
            return app()->call([new $instance, 'build']);
783
        }
784
785
        return app()->make($instance);
786
    }
787
788
    /**
789
     * @param $simpleview
790
     * @param $view
791
     * @param bool $template
792
     * @param null $instance
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $instance is correct as it would always require null to be passed?
Loading history...
793
     * @return string|void
794
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
795
     * @throws \ReflectionException
796
     */
797
    public static function renderPreview($simpleview, $view, $template = false, $instance = null)
798
    {
799
        if (! View::exists($view)) {
800
            return;
801
        }
802
803
        if (! $template) {
804
            $obj = self::buildMailable($instance);
805
            $viewData = $obj->viewData;
806
            $_data = array_merge($instance->buildViewData(), $viewData);
0 ignored issues
show
Bug introduced by
The method buildViewData() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

806
            $_data = array_merge($instance->/** @scrutinizer ignore-call */ buildViewData(), $viewData);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
807
808
            foreach ($_data as $key => $value) {
809
                if (! is_object($value)) {
810
                    $_data[$key] = '<span class="maileclipse-key" title="Variable">'.$key.'</span>';
811
                }
812
            }
813
        } else {
814
            $_data = [];
815
        }
816
817
        $_view = $view;
818
819
        try {
820
            if ($simpleview) {
821
                return htmlspecialchars_decode(view($_view, $_data)->render());
0 ignored issues
show
Bug introduced by
It seems like view($_view, $_data)->render() can also be of type array; however, parameter $string of htmlspecialchars_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

821
                return htmlspecialchars_decode(/** @scrutinizer ignore-type */ view($_view, $_data)->render());
Loading history...
822
            }
823
824
            $_md = self::buildMailable(Markdown::class, 'make');
825
826
            return htmlspecialchars_decode($_md->render($_view, $_data));
827
        } catch (ErrorException $e) {
828
            $error = '<div class="alert alert-warning">
829
	    	<h5 class="alert-heading">Error:</h5>
830
	    	<p>'.$e->getMessage().'</p>
831
	    	</div>';
832
833
            if ($template) {
834
                $error .= '<div class="alert alert-info">
835
				<h5 class="alert-heading">Notice:</h5>
836
				<p>You can\'t add variables within a template editor because they are undefined until you bind the template with a mailable that actually has data.</p>
837
	    	</div>';
838
            }
839
840
            return $error;
841
        }
842
    }
843
844
    /**
845
     * Class name has to satisfy those rules.
846
     *
847
     * https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class
848
     * https://www.php.net/manual/en/reserved.keywords.php
849
     *
850
     * @param $input
851
     * @return string|false class name or false on failure
852
     */
853
    public static function generateClassName($input)
854
    {
855
        $suffix = 'Mail';
856
857
        if (strtolower($input) === strtolower($suffix)) {
858
            return false;
859
        }
860
861
        // Avoid MailMail as a class name suffix
862
        $suffix = substr_compare($input, 'mail', -4, 4, true) === 0
863
            ? ''
864
            : $suffix;
865
866
        /**
867
         * - Suffix is needed to avoid usage of reserved word.
868
         * - Str::slug will remove all forbidden characters.
869
         */
870
        $name = Str::studly(Str::slug($input, '_')).$suffix;
871
872
        /**
873
         * Removal of reserved keywords.
874
         */
875
        if (! preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $name) ||
876
            substr_compare($name, $suffix, -strlen($suffix), strlen($suffix), true) !== 0
877
        ) {
878
            return false;
879
        }
880
881
        return $name;
882
    }
883
884
    /**
885
     * @todo Add dynamic search and population of related classes.
886
     *
887
     * @param $eloquentFactory
888
     * @param $model
889
     * @param array $resolvedTypeHints
890
     * @return array
891
     */
892
    private static function resolveFactory($eloquentFactory, $model, array $resolvedTypeHints): array
893
    {
894
        if (config('maileclipse.factory')) {
895
            // factory builder backwards compatibility
896
            if (isset($eloquentFactory[$model])) {
897
                $resolvedTypeHints[] = factory($model)->make();
898
            }
899
900
            /** @var array|false $modelHasFactory */
901
            $modelHasFactory = class_uses($model);
902
903
            if (isset($modelHasFactory['Illuminate\Database\Eloquent\Factories\HasFactory'])) {
904
                $resolvedTypeHints[] = $model::factory()->make();
905
            }
906
        } else {
907
            $resolvedTypeHints[] = app($model);
908
        }
909
910
        return $resolvedTypeHints;
911
    }
912
913
    /**
914
     * @param string $name
915
     * @return bool|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
916
     * @throws \ReflectionException
917
     */
918
    public static function renderMailable(string $name)
919
    {
920
        $mailable = self::getMailable('name', $name)->first();
921
922
        if (collect($mailable['data'])->isEmpty()) {
923
            return false;
924
        }
925
926
        $mailableInstance = self::resolveMailableInstance($mailable);
927
928
        $view = $mailable['markdown'] ?? $mailable['data']->view;
929
930
        if (view()->exists($view)) {
931
            return ($mailableInstance)->render();
932
        }
933
934
        return view(self::VIEW_NAMESPACE.'::previewerror', ['errorMessage' => 'No template associated with this mailable.']);
935
    }
936
937
    /**
938
     * @param string $name
939
     * @param string $recipient
940
     */
941
    public static function sendTest(string $name, string $recipient): void
942
    {
943
        $mailable = self::getMailable('name', $name)->first();
944
945
        $mailableInstance = self::resolveMailableInstance($mailable);
946
947
        $mailableInstance = self::setMailableSendTestRecipient($mailableInstance, $recipient);
948
949
        Mail::send($mailableInstance);
950
    }
951
952
    /**
953
     * @param $mailable
954
     * @param string $email
955
     * @return mixed
956
     */
957
    public static function setMailableSendTestRecipient($mailable, string $email)
958
    {
959
        $mailable->to($email);
960
        $mailable->cc([]);
961
        $mailable->bcc([]);
962
963
        return $mailable;
964
    }
965
966
    /**
967
     * @param $mailable
968
     * @return object|void
969
     * @throws \ReflectionException
970
     */
971
    private static function resolveMailableInstance($mailable)
972
    {
973
        if (self::handleMailableViewDataArgs($mailable['namespace']) !== null) {
974
            $mailableInstance = self::handleMailableViewDataArgs($mailable['namespace']);
975
        } else {
976
            $mailableInstance = new $mailable['namespace'];
977
        }
978
979
        return $mailableInstance;
980
    }
981
}
982