Passed
Push — master ( c72b97...8ec7bf )
by Yassine
05:28
created

MailEclipse::renderMailable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 3
eloc 8
c 2
b 0
f 1
nc 3
nop 1
dl 0
loc 17
rs 10
1
<?php
2
3
namespace Qoraiche\MailEclipse;
4
5
use ErrorException;
6
use Illuminate\Container\Container;
7
use Illuminate\Database\Eloquent\Factory as EloquentFactory;
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\Mail;
13
use Illuminate\Support\Facades\View;
14
use Illuminate\Support\Str;
15
use RecursiveDirectoryIterator;
16
use RecursiveIteratorIterator;
17
use ReeceM\Mocker\Mocked;
18
use ReflectionClass;
19
use ReflectionProperty;
20
use RegexIterator;
21
22
class MailEclipse
23
{
24
    public static $view_namespace = 'maileclipse';
25
26
    /**
27
     * Default type examples for being passed to reflected classes.
28
     *
29
     * @var array TYPES
30
     */
31
    public const TYPES = [
32
        'int' => 31,
33
        // 'string' => 'test_string', // not needed as it can be cast __toString()
34
        'bool' => false,
35
        'float' => 3.14159,
36
    ];
37
38
    public static function getMailables()
39
    {
40
        return self::mailablesList();
41
    }
42
43
    public static function getMailable($key, $name)
44
    {
45
        $filtered = collect(self::getMailables())->where($key, $name);
46
47
        return $filtered;
48
    }
49
50
    public static function deleteTemplate($templateSlug)
51
    {
52
        $template = self::getTemplates()
53
            ->where('template_slug', $templateSlug)->first();
54
55
        if (! is_null($template)) {
56
            self::saveTemplates(self::getTemplates()->reject(function ($value, $key) use ($template) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

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

56
            self::saveTemplates(self::getTemplates()->reject(function ($value, /** @scrutinizer ignore-unused */ $key) use ($template) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
57
                return $value->template_slug == $template->template_slug;
58
            }));
59
60
            $template_view = self::$view_namespace.'::templates.'.$templateSlug;
61
            $template_plaintext_view = $template_view.'_plain_text';
62
63
            if (View::exists($template_view)) {
64
                unlink(View($template_view)->getPath());
65
66
                if (View::exists($template_plaintext_view)) {
67
                    unlink(View($template_plaintext_view)->getPath());
68
                }
69
70
                return true;
71
            }
72
        }
73
74
        return false;
75
    }
76
77
    public static function getTemplatesFile()
78
    {
79
        $file = config('maileclipse.mailables_dir').'templates.json';
80
        if (! file_exists($file)) {
81
            if (! file_exists(config('maileclipse.mailables_dir'))) {
82
                mkdir(config('maileclipse.mailables_dir'));
83
            }
84
            file_put_contents($file, '[]');
85
        }
86
87
        return $file;
88
    }
89
90
    public static function saveTemplates(Collection $templates)
91
    {
92
        file_put_contents(self::getTemplatesFile(), $templates->toJson());
93
    }
94
95
    public static function updateTemplate($request)
96
    {
97
        $template = self::getTemplates()
98
            ->where('template_slug', $request->templateslug)->first();
99
100
        if (! is_null($template)) {
101
            if (! preg_match("/^[a-zA-Z0-9-_\s]+$/", $request->title)) {
102
                return response()->json([
103
                    'status' => 'failed',
104
                    'message' => 'Template name not valid',
105
                ]);
106
            }
107
108
            $templatename = Str::camel(preg_replace('/\s+/', '_', $request->title));
109
110
            // check if not already exists on db
111
            //
112
            //
113
114
            if (self::getTemplates()->contains('template_slug', '=', $templatename)) {
115
                return response()->json([
116
117
                    'status' => 'failed',
118
                    'message' => 'Template name already exists',
119
120
                ]);
121
            }
122
123
            // Update
124
            //
125
            $oldForm = self::getTemplates()->reject(function ($value, $key) use ($template) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed. ( Ignorable by Annotation )

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

125
            $oldForm = self::getTemplates()->reject(function ($value, /** @scrutinizer ignore-unused */ $key) use ($template) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
126
                return $value->template_slug == $template->template_slug;
127
            });
128
            $newForm = array_merge($oldForm->toArray(), [array_merge((array) $template, [
129
                'template_slug' => $templatename,
130
                'template_name' => $request->title,
131
                'template_description' => $request->description,
132
            ])]);
133
134
            self::saveTemplates(collect($newForm));
135
136
            $template_view = self::$view_namespace.'::templates.'.$request->templateslug;
137
            $template_plaintext_view = $template_view.'_plain_text';
138
139
            if (View::exists($template_view)) {
140
                $viewPath = View($template_view)->getPath();
141
142
                rename($viewPath, dirname($viewPath)."/{$templatename}.blade.php");
143
144
                if (View::exists($template_plaintext_view)) {
145
                    $textViewPath = View($template_plaintext_view)->getPath();
146
147
                    rename($textViewPath, dirname($viewPath)."/{$templatename}_plain_text.blade.php");
148
                }
149
            }
150
151
            return response()->json([
152
153
                'status' => 'ok',
154
                'message' => 'Updated Successfully',
155
                'template_url' => route('viewTemplate', ['templatename' => $templatename]),
156
157
            ]);
158
        }
159
    }
160
161
    public static function getTemplate($templateSlug)
162
    {
163
        $template = self::getTemplates()
164
            ->where('template_slug', $templateSlug)->first();
165
166
        if (! is_null($template)) {
167
            $template_view = self::$view_namespace.'::templates.'.$template->template_slug;
168
            $template_plaintext_view = $template_view.'_plain_text';
169
170
            // return $template_plaintext_view;
171
172
            if (View::exists($template_view)) {
173
                $viewPath = View($template_view)->getPath();
174
                $textViewPath = View($template_plaintext_view)->getPath();
175
176
                $templateData = collect([
177
                    'template' => self::templateComponentReplace(file_get_contents($viewPath), true),
178
                    'plain_text' => View::exists($template_plaintext_view) ? file_get_contents($textViewPath) : '',
179
                    'slug' => $template->template_slug,
180
                    'name' => $template->template_name,
181
                    'description' => $template->template_description,
182
                    'template_type' => $template->template_type,
183
                    'template_view_name' => $template->template_view_name,
184
                    'template_skeleton' => $template->template_skeleton,
185
                ]);
186
187
                return $templateData;
188
            }
189
        }
190
191
        // return;
192
    }
193
194
    public static function getTemplates()
195
    {
196
        $template = collect(json_decode(file_get_contents(self::getTemplatesFile())));
197
198
        return $template;
199
    }
200
201
    public static function createTemplate($request)
202
    {
203
        if (! preg_match("/^[a-zA-Z0-9-_\s]+$/", $request->template_name)) {
204
            return response()->json([
205
206
                'status' => 'error',
207
                'message' => 'Template name not valid',
208
209
            ]);
210
        }
211
212
        $view = self::$view_namespace.'::templates.'.$request->template_name;
213
214
        $templatename = Str::camel(preg_replace('/\s+/', '_', $request->template_name));
215
216
        if (! view()->exists($view) && ! self::getTemplates()->contains('template_slug', '=', $templatename)) {
217
            self::saveTemplates(self::getTemplates()
218
                ->push([
219
                    'template_name' => $request->template_name,
220
                    'template_slug' => $templatename,
221
                    'template_description' => $request->template_description,
222
                    'template_type' => $request->template_type,
223
                    'template_view_name' => $request->template_view_name,
224
                    'template_skeleton' => $request->template_skeleton,
225
                ]));
226
227
            $dir = resource_path('views/vendor/'.self::$view_namespace.'/templates');
228
229
            if (! \File::isDirectory($dir)) {
230
                \File::makeDirectory($dir, 0755, true);
231
            }
232
233
            file_put_contents($dir."/{$templatename}.blade.php", self::templateComponentReplace($request->content));
234
235
            file_put_contents($dir."/{$templatename}_plain_text.blade.php", $request->plain_text);
236
237
            return response()->json([
238
239
                'status' => 'ok',
240
                'message' => 'Template created<br> <small>Reloading...<small>',
241
                'template_url' => route('viewTemplate', ['templatename' => $templatename]),
242
243
            ]);
244
        }
245
246
        return response()->json([
247
248
            'status' => 'error',
249
            'message' => 'Template not created',
250
251
        ]);
252
    }
253
254
    public static function getTemplateSkeletons()
255
    {
256
        return collect(config('maileclipse.skeletons'));
257
    }
258
259
    public static function getTemplateSkeleton($type, $name, $skeleton)
260
    {
261
        $skeletonView = self::$view_namespace."::skeletons.{$type}.{$name}.{$skeleton}";
262
263
        if (view()->exists($skeletonView)) {
264
            $skeletonViewPath = View($skeletonView)->getPath();
265
            $templateContent = file_get_contents($skeletonViewPath);
266
267
            return [
268
                'type' => $type,
269
                'name' => $name,
270
                'skeleton' => $skeleton,
271
                'template' => self::templateComponentReplace($templateContent, true),
272
                'view' => $skeletonView,
273
                'view_path' => $skeletonViewPath,
274
            ];
275
        }
276
    }
277
278
    protected static function templateComponentReplace($content, $reverse = false)
279
    {
280
        if ($reverse) {
281
            $patterns = [
282
                '/@component/i',
283
                '/@endcomponent/i',
284
                '/@yield/',
285
                '/@section/',
286
                '/@endsection/',
287
                '/@extends/',
288
                '/@parent/',
289
                '/@slot/',
290
                '/@endslot/',
291
            ];
292
293
            $replacements = [
294
                '[component]: # ',
295
                '[endcomponent]: # ',
296
                '[yield]: # ',
297
                '[section]: # ',
298
                '[endsection]: # ',
299
                '[extends]: # ',
300
                '[parent]: # ',
301
                '[slot]: # ',
302
                '[endslot]: # ',
303
            ];
304
        } else {
305
            $patterns = [
306
                '/\[component]:\s?#\s?/i',
307
                '/\[endcomponent]:\s?#\s?/i',
308
                '/\[yield]:\s?#\s?/i',
309
                '/\[section]:\s?#\s?/i',
310
                '/\[endsection]:\s?#\s?/i',
311
                '/\[extends]:\s?#\s?/i',
312
                '/\[parent]:\s?#\s?/i',
313
                '/\[slot]:\s?#\s?/i',
314
                '/\[endslot]:\s?#\s?/i',
315
            ];
316
317
            $replacements = [
318
                '@component',
319
                '@endcomponent',
320
                '@yield',
321
                '@section',
322
                '@endsection',
323
                '@extends',
324
                '@parent',
325
                '@slot',
326
                '@endslot',
327
            ];
328
        }
329
330
        return preg_replace($patterns, $replacements, $content);
331
    }
332
333
    protected static function markdownedTemplate($viewPath)
334
    {
335
        $viewContent = file_get_contents($viewPath);
336
337
        return self::templateComponentReplace($viewContent, true);
338
339
        // return preg_replace($patterns, $replacements, $viewContent);
340
    }
341
342
    /**
343
     * Markdowned template view.
344
     */
345
    public static function markdownedTemplateToView($save = true, $content = '', $viewPath = '', $template = false)
346
    {
347
        if ($template && View::exists(self::$view_namespace.'::templates.'.$viewPath)) {
348
            $viewPath = View(self::$view_namespace.'::templates.'.$viewPath)->getPath();
349
        }
350
351
        $replaced = self::templateComponentReplace($content);
352
353
        if (! $save) {
354
            return $replaced;
355
        }
356
357
        return file_put_contents($viewPath, $replaced) === false ? false : true;
358
    }
359
360
    public static function previewMarkdownViewContent($simpleview, $content, $viewName, $template = false, $namespace = null)
361
    {
362
        $previewtoset = self::markdownedTemplateToView(false, $content);
363
        $dir = dirname(__FILE__, 2).'/resources/views/draft';
364
        $viewName = $template ? $viewName.'_template' : $viewName;
365
366
        if (file_exists($dir)) {
367
            file_put_contents($dir."/{$viewName}.blade.php", $previewtoset);
368
369
            if ($template) {
370
                $instance = null;
371
            } else {
372
                if (! is_null(self::handleMailableViewDataArgs($namespace))) {
373
                    $instance = self::handleMailableViewDataArgs($namespace);
374
                } else {
375
                    $instance = new $namespace;
376
                }
377
            }
378
379
            return self::renderPreview($simpleview, self::$view_namespace.'::draft.'.$viewName, $template, $instance);
380
        }
381
382
        return false;
383
    }
384
385
    public static function previewMarkdownHtml($instance, $view)
386
    {
387
        return self::renderPreview($instance, $view);
388
    }
389
390
    public static function getMailableTemplateData($mailableName)
391
    {
392
        $mailable = self::getMailable('name', $mailableName);
393
394
        if ($mailable->isEmpty()) {
395
            return false;
396
        }
397
398
        $templateData = collect($mailable->first())->only(['markdown', 'view_path', 'text_view_path', 'text_view', 'view_data', 'data', 'namespace'])->all();
399
400
        $templateExists = ! is_null($templateData['view_path']);
401
        $textTemplateExists = ! is_null($templateData['text_view_path']);
402
403
        if ($templateExists) {
404
            $viewPathParams = collect($templateData)->union([
405
406
                'text_template' => $textTemplateExists ? file_get_contents($templateData['text_view_path']) : null,
407
                'template' => file_get_contents($templateData['view_path']),
408
                'markdowned_template' => self::markdownedTemplate($templateData['view_path']),
409
                'template_name' => ! is_null($templateData['markdown']) ? $templateData['markdown'] : $templateData['data']->view,
410
                'is_markdown' => ! is_null($templateData['markdown']) ? true : false,
411
                // 'text_template' => file_get_contents($templateData['text_view_path']),
412
413
            ])->all();
414
415
            return $viewPathParams;
416
        }
417
418
        return $templateData;
419
    }
420
421
    public static function generateMailable($request = null)
422
    {
423
        $name = self::generateClassName($request->input('name'));
424
425
        if ($name === false) {
426
            return response()->json([
427
                'status' => 'error',
428
                'message' => 'Wrong name format.',
429
            ]);
430
        }
431
432
        if (! self::getMailable('name', $name)->isEmpty() && ! $request->has('force')) {
433
            // return redirect()->route('createMailable')->with('error', 'mailable already exists! to overide it enable force option.');
434
            //
435
            return response()->json([
436
                'status' => 'error',
437
                'message' => 'This mailable name already exists. names should be unique! to override it, enable "force" option.',
438
            ]);
439
        }
440
441
        if (strtolower($name) === 'mailable') {
442
            return response()->json([
443
                'status' => 'error',
444
                'message' => 'You cannot use this name',
445
            ]);
446
        }
447
448
        $params = collect([
449
            'name' => $name,
450
        ]);
451
452
        if ($request->input('markdown')) {
453
            $params->put('--markdown', $request->markdown);
454
        }
455
456
        if ($request->has('force')) {
457
            $params->put('--force', true);
458
        }
459
460
        $exitCode = Artisan::call('make:mail', $params->all());
461
462
        if ($exitCode > -1) {
463
464
            // return redirect()->route('mailableList');
465
            //
466
            return response()->json([
467
468
                'status' => 'ok',
469
                'message' => 'Mailable Created<br> <small>Reloading...<small>',
470
471
            ]);
472
        }
473
    }
474
475
    /**
476
     * Get mailables list.
477
     *
478
     * @return array
479
     */
480
    protected static function mailablesList()
481
    {
482
        $fqcns = [];
483
484
        if (! file_exists(config('maileclipse.mailables_dir'))) {
485
            return;
486
        } else {
487
            $allFiles = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(config('maileclipse.mailables_dir')));
488
            $phpFiles = new RegexIterator($allFiles, '/\.php$/');
489
            $i = 0;
490
491
            foreach ($phpFiles as $phpFile) {
492
                $i++;
493
                $content = file_get_contents($phpFile->getRealPath());
494
                $tokens = token_get_all($content);
495
                $namespace = '';
496
                for ($index = 0; isset($tokens[$index]); $index++) {
497
                    if (! isset($tokens[$index][0])) {
498
                        continue;
499
                    }
500
                    if (T_NAMESPACE === $tokens[$index][0]) {
501
                        $index += 2; // Skip namespace keyword and whitespace
502
                        while (isset($tokens[$index]) && is_array($tokens[$index])) {
503
                            $namespace .= $tokens[$index++][1];
504
                        }
505
                    }
506
                    if (T_CLASS === $tokens[$index][0] && T_WHITESPACE === $tokens[$index + 1][0] && T_STRING === $tokens[$index + 2][0]) {
507
                        $index += 2; // Skip class keyword and whitespace
508
509
                        [$name, $extension] = explode('.', $phpFile->getFilename());
510
511
                        $mailableClass = $namespace.'\\'.$tokens[$index][1];
512
513
                        if (! self::mailable_exists($mailableClass)) {
514
                            continue;
515
                        }
516
517
                        $reflector = new ReflectionClass($mailableClass);
518
519
                        if ($reflector->isAbstract()) {
520
                            continue;
521
                        }
522
523
                        $mailable_data = self::buildMailable($mailableClass);
524
525
                        if (! is_null(self::handleMailableViewDataArgs($mailableClass))) {
526
                            $mailable_view_data = self::getMailableViewData(self::handleMailableViewDataArgs($mailableClass), $mailable_data);
527
                        } else {
528
                            $mailable_view_data = self::getMailableViewData(new $mailableClass, $mailable_data);
529
                        }
530
531
                        $fqcns[$i]['data'] = $mailable_data;
532
                        $fqcns[$i]['markdown'] = self::getMarkdownViewName($mailable_data);
533
                        $fqcns[$i]['name'] = $name;
534
                        $fqcns[$i]['namespace'] = $mailableClass;
535
                        $fqcns[$i]['filename'] = $phpFile->getFilename();
536
                        $fqcns[$i]['modified'] = $phpFile->getCTime();
537
                        $fqcns[$i]['viewed'] = $phpFile->getATime();
538
                        $fqcns[$i]['view_data'] = $mailable_view_data;
539
                        // $fqcns[$i]['view_data'] = [];
540
                        $fqcns[$i]['path_name'] = $phpFile->getPathname();
541
                        $fqcns[$i]['readable'] = $phpFile->isReadable();
542
                        $fqcns[$i]['writable'] = $phpFile->isWritable();
543
                        $fqcns[$i]['view_path'] = null;
544
                        $fqcns[$i]['text_view_path'] = null;
545
546
                        if (! is_null($fqcns[$i]['markdown']) && View::exists($fqcns[$i]['markdown'])) {
547
                            $fqcns[$i]['view_path'] = View($fqcns[$i]['markdown'])->getPath();
548
                        }
549
550
                        if (! is_null($fqcns[$i]['data'])) {
551
                            if (! is_null($fqcns[$i]['data']->view) && View::exists($fqcns[$i]['data']->view)) {
552
                                $fqcns[$i]['view_path'] = View($fqcns[$i]['data']->view)->getPath();
553
                            }
554
555
                            if (! is_null($fqcns[$i]['data']->textView) && View::exists($fqcns[$i]['data']->textView)) {
556
                                $fqcns[$i]['text_view_path'] = View($fqcns[$i]['data']->textView)->getPath();
557
                                $fqcns[$i]['text_view'] = $fqcns[$i]['data']->textView;
558
                            }
559
                        }
560
561
                        // break if you have one class per file (psr-4 compliant)
562
                        // otherwise you'll need to handle class constants (Foo::class)
563
                        break;
564
                    }
565
                }
566
            }
567
568
            $collection = collect($fqcns)->map(function ($mailable) {
569
                return $mailable;
570
            })->reject(function ($object) {
571
                return ! method_exists($object['namespace'], 'build');
572
            });
573
574
            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...
575
        }
576
    }
577
578
    /**
579
     * Handle Mailable Constructor arguments and pass the fake ones.
580
     */
581
    public static function handleMailableViewDataArgs($mailable)
582
    {
583
        if (method_exists($mailable, '__construct')) {
584
            $reflection = new ReflectionClass($mailable);
585
586
            $params = $reflection->getConstructor()->getParameters();
587
588
            DB::beginTransaction();
589
590
            $eloquentFactory = app(EloquentFactory::class);
591
592
            $args = collect($params)->map(function ($param) {
593
                if ($param->getType() !== null) {
594
                    if (class_exists($param->getType()->getName())) {
595
                        $parameters = [
596
                            'is_instance' => true,
597
                            'instance' => $param->getType()->getName(),
598
                        ];
599
                    } elseif ($param->getType()->getName() == 'array') {
600
                        $parameters = [
601
                            'is_array' => true,
602
                            'arg' => $param->getName(),
603
                        ];
604
                    } else {
605
                        $parameters = $param->name;
606
                    }
607
608
                    return $parameters;
609
                }
610
611
                return $param->name;
612
            });
613
614
            $resolvedTypeHints = [];
615
616
            foreach ($args->all() as $arg) {
617
                if (is_array($arg)) {
618
                    if (isset($arg['is_instance'])) {
619
620
                        $model = $arg['instance'];
621
622
                        $resolvedTypeHints = self::resolveFactory($eloquentFactory, $model, $resolvedTypeHints);
623
624
                    } elseif (isset($arg['is_array'])) {
625
                        $resolvedTypeHints[] = [];
626
                    } else {
627
                        return;
628
                    }
629
                } else {
630
                    $resolvedTypeHints[] = self::getMissingParams($arg, $params);
631
                }
632
            }
633
634
            $reflector = new ReflectionClass($mailable);
635
636
            if (!$args->isEmpty()) {
637
                $foo = $reflector->newInstanceArgs($resolvedTypeHints);
638
639
                return $foo;
640
            }
641
642
            DB::rollBack();
643
        }
644
    }
645
646
    /**
647
     * Gets any missing params that may not be collectable in the reflection.
648
     *
649
     * @param string $arg the argument string|array
650
     * @param array $params the reflection param list
651
     *
652
     * @return array|string|\ReeceM\Mocker\Mocked
653
     */
654
    private static function getMissingParams($arg, $params)
655
    {
656
        /**
657
         * Determine if a builtin type can be found.
658
         * Not a string or object as a Mocked::class can work there.
659
         *
660
         * getName() is undocumented alternative to casting to string.
661
         * https://www.php.net/manual/en/class.reflectiontype.php#124658
662
         *
663
         * @var \ReflectionType $reflection
664
         */
665
        $reflection = collect($params)->where('name', $arg)->first()->getType();
666
667
        if (version_compare(phpversion(), '7.1', '>=')) {
668
            $type = ! is_null($reflection)
669
                ? self::TYPES[$reflection->getName()]
670
                : null;
671
        } else {
672
            $type = ! is_null($reflection)
673
                ? self::TYPES[/** @scrutinizer ignore-deprecated */ $reflection->__toString()]
674
                : null;
675
        }
676
677
        try {
678
            return ! is_null($type)
679
                    ? $type
680
                    : new Mocked($arg, \ReeceM\Mocker\Utils\VarStore::singleton());
681
        } catch (\Exception $e) {
682
            return $arg;
683
        }
684
    }
685
686
    private static function getMailableViewData($mailable, $mailable_data)
687
    {
688
        $traitProperties = [];
689
690
        $data = new ReflectionClass($mailable);
691
692
        foreach ($data->getTraits() as $trait) {
693
            foreach ($trait->getProperties(ReflectionProperty::IS_PUBLIC) as $traitProperty) {
694
                $traitProperties[] = $traitProperty->name;
695
            }
696
        }
697
698
        $properties = $data->getProperties(ReflectionProperty::IS_PUBLIC);
699
        $allProps = [];
700
701
        foreach ($properties as $prop) {
702
            if ($prop->class == $data->getName() || $prop->class == get_parent_class($data->getName()) &&
703
                    get_parent_class($data->getName()) != 'Illuminate\Mail\Mailable' && ! $prop->isStatic()) {
704
                $allProps[] = $prop->name;
705
            }
706
        }
707
708
        $obj = self::buildMailable($mailable);
709
710
        if (is_null($obj)) {
711
            $obj = [];
712
713
            return collect($obj);
714
        }
715
716
        $classProps = array_diff($allProps, $traitProperties);
717
718
        $withFuncData = collect($obj->viewData)->keys();
719
720
        $mailableData = collect($classProps)->merge($withFuncData);
721
722
        $data = $mailableData->map(function ($parameter) use ($mailable_data) {
723
            return [
724
                'key' => $parameter,
725
                'value' => property_exists($mailable_data, $parameter) ? $mailable_data->$parameter : null,
726
                'data' => property_exists($mailable_data, $parameter) ? self::viewDataInspect($mailable_data->$parameter) : null,
727
            ];
728
        });
729
730
        return $data->all();
731
    }
732
733
    protected static function viewDataInspect($param)
734
    {
735
        if ($param instanceof \Illuminate\Database\Eloquent\Model) {
736
            return [
737
                'type' => 'model',
738
                'attributes' => collect($param->getAttributes()),
739
            ];
740
        } elseif ($param instanceof \Illuminate\Database\Eloquent\Collection) {
741
            return [
742
                'type' => 'elequent-collection',
743
                'attributes' => $param->all(),
744
            ];
745
        } elseif ($param instanceof \Illuminate\Support\Collection) {
746
            return [
747
                'type' => 'collection',
748
                'attributes' => $param->all(),
749
            ];
750
        }
751
    }
752
753
    protected static function mailable_exists($mailable)
754
    {
755
        if (! class_exists($mailable)) {
756
            return false;
757
        }
758
759
        return true;
760
    }
761
762
    protected static function getMarkdownViewName($mailable)
763
    {
764
        if ($mailable === null) {
765
            return;
766
        }
767
768
        $reflection = new ReflectionClass($mailable);
769
770
        $property = $reflection->getProperty('markdown');
771
772
        $property->setAccessible(true);
773
774
        return $property->getValue($mailable);
775
    }
776
777
    public static function buildMailable($instance, $type = 'call')
778
    {
779
        if ($type == 'call') {
780
            if (! is_null(self::handleMailableViewDataArgs($instance))) {
781
                return Container::getInstance()->call([self::handleMailableViewDataArgs($instance), 'build']);
782
            }
783
784
            return Container::getInstance()->call([new $instance, 'build']);
785
        }
786
787
        return Container::getInstance()->make($instance);
788
    }
789
790
    public static function renderPreview($simpleview, $view, $template = false, $instance = null)
791
    {
792
        if (! View::exists($view)) {
793
            return;
794
        }
795
796
        if (! $template) {
797
            $obj = self::buildMailable($instance);
798
            $viewData = $obj->viewData;
799
            $_data = array_merge($instance->buildViewData(), $viewData);
800
801
            foreach ($_data as $key => $value) {
802
                if (! is_object($value)) {
803
                    $_data[$key] = '<span class="maileclipse-key" title="Variable">'.$key.'</span>';
804
                }
805
            }
806
        } else {
807
            $_data = [];
808
        }
809
810
        $_view = $view;
811
812
        try {
813
            if ($simpleview) {
814
                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

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