Recipe   F
last analyzed

Complexity

Total Complexity 86

Size/Duplication

Total Lines 770
Duplicated Lines 0 %

Importance

Changes 15
Bugs 1 Features 0
Metric Value
eloc 379
c 15
b 1
f 0
dl 0
loc 770
rs 2
wmc 86

17 Methods

Rating   Name   Duplication   Size   Complexity  
A renderJsonLd() 0 21 3
A getServes() 0 7 2
C getRecipeJSONLD() 0 108 9
C convertToFractions() 0 58 13
A getVideoUploadedDate() 0 11 3
F getIngredients() 0 107 23
A rules() 0 36 1
A init() 0 12 5
A getEquipment() 0 13 3
A getImageUrl() 0 11 3
A getDirections() 0 13 3
A getAggregateRating() 0 16 3
B createRecipeMetaJsonLd() 0 36 9
A renderRecipeJSONLD() 0 3 1
A renderNutritionFacts() 0 7 1
A getRatingsCount() 0 3 1
A getVideoUrl() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like Recipe often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Recipe, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Recipe plugin for Craft CMS 3.x
4
 *
5
 * A comprehensive recipe FieldType for Craft CMS that includes metric/imperial
6
 * conversion, portion calculation, and JSON-LD microdata support
7
 *
8
 * @link      https://nystudio107.com
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
9
 * @copyright Copyright (c) 2017 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
10
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
11
12
namespace nystudio107\recipe\models;
13
14
use Craft;
15
use craft\base\Model;
16
use craft\helpers\StringHelper;
17
use craft\helpers\Template;
18
use craft\validators\ArrayValidator;
19
use Exception;
20
use nystudio107\recipe\helpers\Json;
21
use nystudio107\recipe\helpers\PluginTemplate;
22
use nystudio107\seomatic\models\MetaJsonLd;
23
use nystudio107\seomatic\Seomatic;
24
use Twig\Markup;
25
26
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
27
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
28
 * @package   Recipe
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
29
 * @since     1.0.0
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
30
 */
0 ignored issues
show
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
Coding Style introduced by
Missing @category tag in class comment
Loading history...
31
class Recipe extends Model
32
{
33
    // Constants
34
    // =========================================================================
35
36
    public const SEOMATIC_PLUGIN_HANDLE = 'seomatic';
37
38
    public const MAIN_ENTITY_KEY = 'mainEntityOfPage';
39
40
    public const US_RDA = [
41
        'calories' => 2000,
42
        'carbohydrateContent' => 275,
43
        'cholesterolContent' => 300,
44
        'fatContent' => 78,
45
        'fiberContent' => 28,
46
        'proteinContent' => 50,
47
        'saturatedFatContent' => 20,
48
        'sodiumContent' => 2300,
49
        'sugarContent' => 50,
50
    ];
51
52
    // Mapping to convert any of the incorrect plural values
53
    public const NORMALIZE_PLURALS = [
54
        'tsps' => 'tsp',
55
        'tbsps' => 'tbsp',
56
        'flozs' => 'floz',
57
        'cups' => 'cups',
58
        'ozs' => 'oz',
59
        'lbs' => 'lb',
60
        'mls' => 'ml',
61
        'ls' => 'l',
62
        'mgs' => 'mg',
63
        'gs' => 'g',
64
        'kg' => 'kg',
65
    ];
66
67
    // Public Properties
68
    // =========================================================================
69
70
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
71
     * @var string
72
     */
73
    public string $name = '';
74
75
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
76
     * @var string
77
     */
78
    public string $author = '';
79
80
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
81
     * @var string
82
     */
83
    public string $description = '';
84
85
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
86
     * @var string
87
     */
88
    public string $keywords = '';
89
90
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
91
     * @var string
92
     */
93
    public string $recipeCategory = '';
94
95
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
96
     * @var string
97
     */
98
    public string $recipeCuisine = '';
99
100
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
101
     * @var string
102
     */
103
    public string $skill = 'intermediate';
104
105
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
106
     * @var int
107
     */
108
    public int $serves = 1;
109
110
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
111
     * @var string
112
     */
113
    public string $servesUnit = '';
114
115
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
116
     * @var array
117
     */
118
    public array $ingredients = [];
119
120
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
121
     * @var array
122
     */
123
    public array $directions = [];
124
125
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
126
     * @var array
127
     */
128
    public array $equipment = [];
129
130
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
131
     * @var ?int
132
     */
133
    public ?int $imageId = null;
134
135
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
136
     * @var ?int
137
     */
138
    public ?int $videoId = null;
139
140
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
141
     * @var int
142
     */
143
    public int $prepTime = 0;
144
145
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
146
     * @var int
147
     */
148
    public int $cookTime = 0;
149
150
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
151
     * @var int
152
     */
153
    public int $totalTime = 0;
154
155
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
156
     * @var ?array
157
     */
158
    public ?array $ratings = null;
159
160
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
161
     * @var string
162
     */
163
    public string $servingSize = '';
164
165
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
166
     * @var int
167
     */
168
    public int $calories = 0;
169
170
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
171
     * @var int
172
     */
173
    public int $carbohydrateContent = 0;
174
175
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
176
     * @var int
177
     */
178
    public int $cholesterolContent = 0;
179
180
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
181
     * @var int
182
     */
183
    public int $fatContent = 0;
184
185
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
186
     * @var int
187
     */
188
    public int $fiberContent = 0;
189
190
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
191
     * @var int
192
     */
193
    public int $proteinContent = 0;
194
195
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
196
     * @var int
197
     */
198
    public int $saturatedFatContent = 0;
199
200
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
201
     * @var int
202
     */
203
    public int $sodiumContent = 0;
204
205
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
206
     * @var int
207
     */
208
    public int $sugarContent = 0;
209
210
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
211
     * @var int
212
     */
213
    public int $transFatContent = 0;
214
215
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
216
     * @var int
217
     */
218
    public int $unsaturatedFatContent = 0;
219
220
    // Public Methods
221
    // =========================================================================
222
223
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
224
     * @inheritdoc
225
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
226
    public function init(): void
227
    {
228
        parent::init();
229
        // Fix any of the incorrect plural values
230
        if (!empty($this->ingredients)) {
231
            foreach ($this->ingredients as &$row) {
232
                if (!empty($row['units']) && !empty(self::NORMALIZE_PLURALS[$row['units']])) {
233
                    $row['units'] = self::NORMALIZE_PLURALS[$row['units']];
234
                }
235
            }
236
237
            unset($row);
238
        }
239
    }
240
241
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
242
     * @inheritdoc
243
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
244
    public function rules(): array
245
    {
246
        return [
247
            ['name', 'string'],
248
            ['author', 'string'],
249
            ['name', 'default', 'value' => ''],
250
            ['description', 'string'],
251
            ['keywords', 'string'],
252
            ['recipeCategory', 'string'],
253
            ['recipeCuisine', 'string'],
254
            ['skill', 'string'],
255
            ['serves', 'integer'],
256
            ['imageId', 'integer'],
257
            ['videoId', 'integer'],
258
            ['prepTime', 'integer'],
259
            ['cookTime', 'integer'],
260
            ['totalTime', 'integer'],
261
            ['servingSize', 'string'],
262
            ['calories', 'integer'],
263
            ['carbohydrateContent', 'integer'],
264
            ['cholesterolContent', 'integer'],
265
            ['fatContent', 'integer'],
266
            ['fiberContent', 'integer'],
267
            ['proteinContent', 'integer'],
268
            ['saturatedFatContent', 'integer'],
269
            ['sodiumContent', 'integer'],
270
            ['sugarContent', 'integer'],
271
            ['transFatContent', 'integer'],
272
            ['unsaturatedFatContent', 'integer'],
273
            [
274
                [
275
                    'ingredients',
276
                    'directions',
277
                    'equipment',
278
                ],
279
                ArrayValidator::class,
280
            ],
281
282
        ];
283
    }
284
285
    /**
286
     * Return the JSON-LD Structured Data for this recipe
287
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
288
    public function getRecipeJSONLD(): array
289
    {
290
        $recipeJSONLD = [
291
            'context' => 'https://schema.org',
292
            'type' => 'Recipe',
293
            'name' => $this->name,
294
            'image' => $this->getImageUrl(),
295
            'description' => $this->description,
296
            'keywords' => $this->keywords,
297
            'recipeCategory' => $this->recipeCategory,
298
            'recipeCuisine' => $this->recipeCuisine,
299
            'recipeYield' => $this->getServes(),
300
            'recipeIngredient' => $this->getIngredients('imperial', 0, false),
301
            'recipeInstructions' => $this->getDirections(false),
302
            'tool' => $this->getEquipment(false),
303
        ];
304
        $recipeJSONLD = array_filter($recipeJSONLD);
305
306
        if (!empty($this->author)) {
307
            $author = [
308
                'type' => 'Person',
309
                'name' => $this->author,
310
            ];
311
            $author = array_filter($author);
312
            $recipeJSONLD['author'] = $author;
313
        }
314
315
        $videoUrl = $this->getVideoUrl();
316
        if (!empty($videoUrl)) {
317
            $video = [
318
                'type' => 'VideoObject',
319
                'name' => $this->name,
320
                'description' => $this->description,
321
                'contentUrl' => $videoUrl,
322
                'thumbnailUrl' => $this->getImageUrl(),
323
                'uploadDate' => $this->getVideoUploadedDate(),
324
            ];
325
            $video = array_filter($video);
326
            $recipeJSONLD['video'] = $video;
327
        }
328
329
        $nutrition = [
330
            'type' => 'NutritionInformation',
331
            'servingSize' => $this->servingSize,
332
            'calories' => $this->calories,
333
            'carbohydrateContent' => $this->carbohydrateContent,
334
            'cholesterolContent' => $this->cholesterolContent,
335
            'fatContent' => $this->fatContent,
336
            'fiberContent' => $this->fiberContent,
337
            'proteinContent' => $this->proteinContent,
338
            'saturatedFatContent' => $this->saturatedFatContent,
339
            'sodiumContent' => $this->sodiumContent,
340
            'sugarContent' => $this->sugarContent,
341
            'transFatContent' => $this->transFatContent,
342
            'unsaturatedFatContent' => $this->unsaturatedFatContent,
343
        ];
344
        $nutrition = array_filter($nutrition);
345
        $recipeJSONLD['nutrition'] = $nutrition;
346
        if (count($recipeJSONLD['nutrition']) === 1) {
347
            unset($recipeJSONLD['nutrition']);
348
        }
349
350
        $aggregateRating = $this->getAggregateRating();
351
        if ($aggregateRating) {
352
            $aggregateRatings = [
353
                'type' => 'AggregateRating',
354
                'ratingCount' => $this->getRatingsCount(),
355
                'bestRating' => '5',
356
                'worstRating' => '1',
357
                'ratingValue' => $aggregateRating,
358
            ];
359
            $aggregateRatings = array_filter($aggregateRatings);
360
            $recipeJSONLD['aggregateRating'] = $aggregateRatings;
361
362
            $reviews = [];
363
            foreach ($this->ratings as $rating) {
364
                $review = [
365
                    'type' => 'Review',
366
                    'author' => $rating['author'],
367
                    'name' => $this->name . ' ' . Craft::t('recipe', 'Review'),
368
                    'description' => $rating['review'],
369
                    'reviewRating' => [
370
                        'type' => 'Rating',
371
                        'bestRating' => '5',
372
                        'worstRating' => '1',
373
                        'ratingValue' => $rating['rating'],
374
                    ],
375
                ];
376
                $reviews[] = $review;
377
            }
378
379
            $reviews = array_filter($reviews);
380
            $recipeJSONLD['review'] = $reviews;
381
        }
382
383
        if ($this->prepTime !== 0) {
384
            $recipeJSONLD['prepTime'] = 'PT' . $this->prepTime . 'M';
385
        }
386
387
        if ($this->cookTime !== 0) {
388
            $recipeJSONLD['cookTime'] = 'PT' . $this->cookTime . 'M';
389
        }
390
391
        if ($this->totalTime !== 0) {
392
            $recipeJSONLD['totalTime'] = 'PT' . $this->totalTime . 'M';
393
        }
394
395
        return $recipeJSONLD;
396
    }
397
398
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $add should have a doc-comment as per coding-style.
Loading history...
399
     * Create the SEOmatic MetaJsonLd object for this recipe
400
     *
401
     * @param null $key
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $key is correct as it would always require null to be passed?
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
402
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
403
    public function createRecipeMetaJsonLd($key = null, bool $add = true): ?MetaJsonLd
404
    {
405
        $result = null;
406
        if (Craft::$app->getPlugins()->getPlugin(self::SEOMATIC_PLUGIN_HANDLE)) {
407
            $seomatic = Seomatic::getInstance();
408
            if ($seomatic !== null) {
409
                $recipeJson = $this->getRecipeJSONLD();
410
                // If we're adding the MetaJsonLd to the container, and no key is provided, give it a random key
411
                if ($add && $key === null) {
412
                    try {
413
                        $key = StringHelper::UUID();
414
                    } catch (Exception) {
415
                        // That's okay
416
                    }
417
                }
418
419
                if ($key !== null) {
420
                    $recipeJson['key'] = $key;
421
                }
422
423
                // If the key is `mainEntityOfPage` add in the URL
424
                if ($key === self::MAIN_ENTITY_KEY) {
425
                    $mainEntity = Seomatic::$plugin->jsonLd->get(self::MAIN_ENTITY_KEY);
426
                    if ($mainEntity !== null) {
427
                        $recipeJson[self::MAIN_ENTITY_KEY] = $mainEntity[self::MAIN_ENTITY_KEY];
428
                    }
429
                }
430
431
                $result = Seomatic::$plugin->jsonLd->create(
432
                    $recipeJson,
433
                    $add
434
                );
435
            }
436
        }
437
438
        return $result;
439
    }
440
441
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $raw should have a doc-comment as per coding-style.
Loading history...
442
     * Render the JSON-LD Structured Data for this recipe
443
     *
444
     *
445
     */
0 ignored issues
show
Coding Style introduced by
Additional blank lines found at end of doc comment
Loading history...
Coding Style introduced by
Missing @return tag in function comment
Loading history...
446
    public function renderRecipeJSONLD(bool $raw = true): string|Markup
447
    {
448
        return $this->renderJsonLd($this->getRecipeJSONLD(), $raw);
449
    }
450
451
    /**
452
     * Get the URL to the recipe's image
453
     *
454
     * @param null $transform
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $transform is correct as it would always require null to be passed?
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
455
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
456
    public function getImageUrl($transform = null): ?string
457
    {
458
        $result = '';
459
        if ($this->imageId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->imageId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
460
            $image = Craft::$app->getAssets()->getAssetById($this->imageId);
461
            if ($image) {
462
                $result = $image->getUrl($transform);
463
            }
464
        }
465
466
        return $result;
467
    }
468
469
    /**
470
     * Get the URL to the recipe's video
471
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
472
    public function getVideoUrl(): ?string
473
    {
474
        $result = '';
475
        if ($this->videoId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->videoId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
476
            $video = Craft::$app->getAssets()->getAssetById($this->videoId);
477
            if ($video) {
478
                $result = $video->getUrl();
479
            }
480
        }
481
482
        return $result;
483
    }
484
485
    /**
486
     * Get the URL to the recipe's uploaded date
487
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
488
    public function getVideoUploadedDate(): ?string
489
    {
490
        $result = '';
491
        if ($this->videoId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->videoId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
492
            $video = Craft::$app->getAssets()->getAssetById($this->videoId);
493
            if ($video) {
494
                $result = $video->dateCreated->format('c');
495
            }
496
        }
497
498
        return $result;
499
    }
500
501
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $rda should have a doc-comment as per coding-style.
Loading history...
502
     * Render the Nutrition Facts template
503
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
504
    public function renderNutritionFacts(array $rda = self::US_RDA): Markup
505
    {
506
        return PluginTemplate::renderPluginTemplate(
507
            'recipe-nutrition-facts',
508
            [
509
                'value' => $this,
510
                'rda' => $rda,
511
            ]
512
        );
513
    }
514
515
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $outputUnits should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $serving should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $raw should have a doc-comment as per coding-style.
Loading history...
516
     * Get all the ingredients for this recipe
517
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
518
    public function getIngredients(string $outputUnits = 'imperial', int $serving = 0, bool $raw = true): array
519
    {
520
        $result = [];
521
522
        foreach ($this->ingredients as $row) {
523
            $convertedUnits = '';
524
            $ingredient = '';
525
            if ($row['quantity']) {
526
                // Multiply the quantity by how many servings we want
527
                $multiplier = 1;
528
                if ($serving > 0) {
529
                    $multiplier = $serving / $this->serves;
530
                }
531
532
                $quantity = $row['quantity'] * $multiplier;
533
                $originalQuantity = $quantity;
534
535
                // Do the imperial->metric units conversion
536
                if ($outputUnits === 'imperial') {
537
                    switch ($row['units']) {
538
                        case 'ml':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
539
                            $convertedUnits = 'tsp';
540
                            $quantity *= 0.2;
541
                            break;
542
                        case 'l':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
543
                            $convertedUnits = 'cups';
544
                            $quantity *= 4.2;
545
                            break;
546
                        case 'mg':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
547
                            $convertedUnits = 'oz';
548
                            $quantity *= 0.000035274;
549
                            break;
550
                        case 'g':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
551
                            $convertedUnits = 'oz';
552
                            $quantity *= 0.035274;
553
                            break;
554
                        case 'kg':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
555
                            $convertedUnits = 'lb';
556
                            $quantity *= 2.2046226218;
557
                            break;
558
                    }
559
                }
560
561
                // Do the metric->imperial units conversion
562
                if ($outputUnits === 'metric') {
563
                    switch ($row['units']) {
564
                        case 'tsp':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
565
                            $convertedUnits = 'ml';
566
                            $quantity *= 4.929;
567
                            break;
568
                        case 'tbsp':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
569
                            $convertedUnits = 'ml';
570
                            $quantity *= 14.787;
571
                            break;
572
                        case 'floz':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
573
                            $convertedUnits = 'ml';
574
                            $quantity *= 29.574;
575
                            break;
576
                        case 'cups':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
577
                            $convertedUnits = 'l';
578
                            $quantity *= 0.236588;
579
                            break;
580
                        case 'oz':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
581
                            $convertedUnits = 'g';
582
                            $quantity *= 28.3495;
583
                            break;
584
                        case 'lb':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 20 spaces, found 24
Loading history...
585
                            $convertedUnits = 'kg';
586
                            $quantity *= 0.45359237;
587
                            break;
588
                    }
589
590
                    $quantity = round($quantity, 1);
591
                }
592
593
                // Convert units to nice fractions
594
                $quantity = $this->convertToFractions($quantity);
595
596
                $ingredient .= $quantity;
597
598
                if ($row['units']) {
599
                    $units = $row['units'];
600
                    if ($convertedUnits !== '' && $convertedUnits !== '0') {
601
                        $units = $convertedUnits;
602
                    }
603
604
                    if ($originalQuantity <= 1) {
605
                        $units = rtrim($units);
606
                        $units = rtrim($units, 's');
607
                    }
608
609
                    $ingredient .= ' ' . $units;
610
                }
611
            }
612
613
            if ($row['ingredient']) {
614
                $ingredient .= ' ' . $row['ingredient'];
615
            }
616
617
            if ($raw) {
618
                $ingredient = Template::raw($ingredient);
619
            }
620
621
            $result[] = $ingredient;
622
        }
623
624
        return $result;
625
    }
626
627
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $raw should have a doc-comment as per coding-style.
Loading history...
628
     * Get all the directions for this recipe
629
     *
630
     *
631
     * @return array
0 ignored issues
show
Coding Style introduced by
There must be exactly one blank line before the tags in a doc comment
Loading history...
632
     */
633
    public function getDirections(bool $raw = true): array
634
    {
635
        $result = [];
636
        foreach ($this->directions as $row) {
637
            $direction = $row['direction'];
638
            if ($raw) {
639
                $direction = Template::raw($direction);
640
            }
641
642
            $result[] = $direction;
643
        }
644
645
        return $result;
646
    }
647
648
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $raw should have a doc-comment as per coding-style.
Loading history...
649
     * Get all the equipment for this recipe
650
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
651
    public function getEquipment(bool $raw = true): array
652
    {
653
        $result = [];
654
        foreach ($this->equipment as $row) {
655
            $equipment = $row['equipment'];
656
            if ($raw) {
657
                $equipment = Template::raw($equipment);
658
            }
659
660
            $result[] = $equipment;
661
        }
662
663
        return $result;
664
    }
665
666
    /**
667
     * Get the aggregate rating from all the ratings
668
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
669
    public function getAggregateRating(): float|int|string
670
    {
671
        $result = 0;
672
        $total = 0;
673
        if (!empty($this->ratings)) {
674
            foreach ($this->ratings as $row) {
675
                $result += $row['rating'];
676
                ++$total;
677
            }
678
679
            $result /= $total;
680
        } else {
681
            $result = '';
682
        }
683
684
        return $result;
685
    }
686
687
    /**
688
     * Get the total number of ratings
689
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
690
    public function getRatingsCount(): int
691
    {
692
        return count($this->ratings);
0 ignored issues
show
Bug introduced by
It seems like $this->ratings can also be of type null; however, parameter $value of count() does only seem to accept Countable|array, 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

692
        return count(/** @scrutinizer ignore-type */ $this->ratings);
Loading history...
693
    }
694
695
    /**
696
     * Returns concatenated serves with its unit
697
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
698
    public function getServes(): int|string
699
    {
700
        if (!empty($this->servesUnit)) {
701
            return $this->serves . ' ' . $this->servesUnit;
702
        }
703
704
        return $this->serves;
705
    }
706
707
    /**
708
     * Convert decimal numbers into fractions
709
     *
710
     * @param $quantity
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
711
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
712
    private function convertToFractions($quantity): string
0 ignored issues
show
Coding Style introduced by
Private method name "Recipe::convertToFractions" must be prefixed with an underscore
Loading history...
713
    {
714
        $whole = floor($quantity);
715
        // Round the mantissa so we can do a floating point comparison without
716
        // weirdness, per: https://www.php.net/manual/en/language.types.float.php#113703
717
        $fraction = round($quantity - $whole, 3);
718
        switch ($fraction) {
719
            case 0:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
720
                $fraction = '';
721
                break;
722
            case 0.25:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
723
                $fraction = ' &frac14;';
724
                break;
725
            case 0.33:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
726
                $fraction = ' &frac13;';
727
                break;
728
            case 0.66:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
729
                $fraction = ' &frac23;';
730
                break;
731
            case 0.165:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
732
                $fraction = ' &frac16;';
733
                break;
734
            case 0.5:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
735
                $fraction = ' &frac12;';
736
                break;
737
            case 0.75:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
738
                $fraction = ' &frac34;';
739
                break;
740
            case 0.125:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
741
                $fraction = ' &#x215B;';
742
                break;
743
            case 0.375:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
744
                $fraction = ' &#x215C;';
745
                break;
746
            case 0.625:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
747
                $fraction = ' &#x215D;';
748
                break;
749
            case 0.875:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
750
                $fraction = ' &#x215E;';
751
                break;
752
            default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
753
                $precision = 1;
754
                $pnum = round($fraction, $precision);
755
                $denominator = 10 ** $precision;
756
                $numerator = $pnum * $denominator;
757
                $fraction = ' <sup>'
758
                    . $numerator
759
                    . '</sup>&frasl;<sub>'
760
                    . $denominator
761
                    . '</sub>';
762
                break;
763
        }
764
765
        if ($whole == 0) {
766
            $whole = '';
767
        }
768
769
        return $whole . $fraction;
770
    }
771
772
    // Private Methods
773
    // =========================================================================
774
775
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $raw should have a doc-comment as per coding-style.
Loading history...
776
     * Renders a JSON-LD representation of the schema
777
     *
778
     * @param      $json
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 1 spaces but found 6
Loading history...
779
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
780
    private function renderJsonLd($json, bool $raw = true): string|Markup
0 ignored issues
show
Coding Style introduced by
Private method name "Recipe::renderJsonLd" must be prefixed with an underscore
Loading history...
781
    {
782
        $linebreak = '';
783
784
        // If `devMode` is enabled, make the JSON-LD human-readable
785
        if (Craft::$app->getConfig()->getGeneral()->devMode) {
786
            $linebreak = PHP_EOL;
787
        }
788
789
        // Render the resulting JSON-LD
790
        $result = '<script type="application/ld+json">'
791
            . $linebreak
792
            . Json::encode($json)
793
            . $linebreak
794
            . '</script>';
795
796
        if ($raw) {
797
            $result = Template::raw($result);
798
        }
799
800
        return $result;
801
    }
802
}
803