Passed
Push — develop ( 7625f3...7e2973 )
by Andrew
06:39 queued 19s
created

Recipe::renderNutritionFacts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
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 nystudio107\recipe\helpers\Json;
15
use nystudio107\recipe\helpers\PluginTemplate;
16
17
use Craft;
18
use craft\base\Model;
19
use craft\helpers\Template;
20
21
use Twig\Markup;
22
23
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
24
 * @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...
25
 * @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...
26
 * @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...
27
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
28
class Recipe extends Model
29
{
30
    // Constants
31
    // =========================================================================
32
33
    const US_RDA = [
34
        'calories' => 2000,
35
        'carbohydrateContent' => 275,
36
        'cholesterolContent' => 300,
37
        'fatContent' => 78,
38
        'fiberContent' => 28,
39
        'proteinContent' => 50,
40
        'saturatedFatContent' => 20,
41
        'sodiumContent' => 2300,
42
        'sugarContent' => 50,
43
    ];
44
45
    // Public Properties
46
    // =========================================================================
47
48
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
49
     * @var string
50
     */
51
    public $name;
52
53
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
54
     * @var string
55
     */
56
    public $description;
57
58
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
59
     * @var string
60
     */
61
    public $skill = 'intermediate';
62
63
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
64
     * @var int
65
     */
66
    public $serves = 1;
67
68
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
69
     * @var array
70
     */
71
    public $ingredients = [];
72
73
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
74
     * @var array
75
     */
76
    public $directions = [];
77
78
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
79
     * @var int
80
     */
81
    public $imageId = 0;
82
83
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
84
     * @var int
85
     */
86
    public $prepTime;
87
88
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
89
     * @var int
90
     */
91
    public $cookTime;
92
93
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
94
     * @var int
95
     */
96
    public $totalTime;
97
98
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
99
     * @var array
100
     */
101
    public $ratings = [];
102
103
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
104
     * @var string
105
     */
106
    public $servingSize;
107
108
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
109
     * @var int
110
     */
111
    public $calories;
112
113
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
114
     * @var int
115
     */
116
    public $carbohydrateContent;
117
118
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
119
     * @var int
120
     */
121
    public $cholesterolContent;
122
123
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
124
     * @var int
125
     */
126
    public $fatContent;
127
128
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
129
     * @var int
130
     */
131
    public $fiberContent;
132
133
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
134
     * @var int
135
     */
136
    public $proteinContent;
137
138
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
139
     * @var int
140
     */
141
    public $saturatedFatContent;
142
143
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
144
     * @var int
145
     */
146
    public $sodiumContent;
147
148
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
149
     * @var int
150
     */
151
    public $sugarContent;
152
153
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
154
     * @var int
155
     */
156
    public $transFatContent;
157
158
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
159
     * @var int
160
     */
161
    public $unsaturatedFatContent;
162
163
    // Public Methods
164
    // =========================================================================
165
166
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
167
     * @inheritdoc
168
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
169
    public function rules()
170
    {
171
        return [
172
            ['name', 'string'],
173
            ['name', 'default', 'value' => ''],
174
            ['description', 'string'],
175
            ['skill', 'string'],
176
            ['serves', 'integer'],
177
            ['imageId', 'integer'],
178
            ['prepTime', 'integer'],
179
            ['cookTime', 'integer'],
180
            ['totalTime', 'integer'],
181
            ['servingSize', 'string'],
182
            ['calories', 'integer'],
183
            ['carbohydrateContent', 'integer'],
184
            ['cholesterolContent', 'integer'],
185
            ['fatContent', 'integer'],
186
            ['fiberContent', 'integer'],
187
            ['proteinContent', 'integer'],
188
            ['saturatedFatContent', 'integer'],
189
            ['sodiumContent', 'integer'],
190
            ['sugarContent', 'integer'],
191
            ['transFatContent', 'integer'],
192
            ['unsaturatedFatContent', 'integer'],
193
        ];
194
    }
195
196
    /**
197
     * Render the JSON-LD Structured Data for this recipe
198
     *
199
     * @param bool $raw
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
200
     *
201
     * @return string|\Twig_Markup
202
     */
203
    public function renderRecipeJSONLD($raw = true)
204
    {
205
        $recipeJSONLD = [
206
            'context' => 'http://schema.org',
207
            'type' => 'Recipe',
208
            'name' => $this->name,
209
            'image' => $this->getImageUrl(),
210
            'description' => $this->description,
211
            'recipeYield' => $this->serves,
212
            'recipeIngredient' => $this->getIngredients('imperial', 0, false),
213
            'recipeInstructions' => $this->getDirections(false),
214
        ];
215
        $recipeJSONLD = array_filter($recipeJSONLD);
216
217
        $nutrition = [
218
            'type' => 'NutritionInformation',
219
            'servingSize' => $this->servingSize,
220
            'calories' => $this->calories,
221
            'carbohydrateContent' => $this->carbohydrateContent,
222
            'cholesterolContent' => $this->cholesterolContent,
223
            'fatContent' => $this->fatContent,
224
            'fiberContent' => $this->fiberContent,
225
            'proteinContent' => $this->proteinContent,
226
            'saturatedFatContent' => $this->saturatedFatContent,
227
            'sodiumContent' => $this->sodiumContent,
228
            'sugarContent' => $this->sugarContent,
229
            'transFatContent' => $this->transFatContent,
230
            'unsaturatedFatContent' => $this->unsaturatedFatContent,
231
        ];
232
        $nutrition = array_filter($nutrition);
233
        $recipeJSONLD['nutrition'] = $nutrition;
234
        if (count($recipeJSONLD['nutrition']) === 1) {
235
            unset($recipeJSONLD['nutrition']);
236
        }
237
        $aggregateRating = $this->getAggregateRating();
238
        if ($aggregateRating) {
239
            $aggregateRatings = [
240
                'type' => 'AggregateRating',
241
                'ratingCount' => $this->getRatingsCount(),
242
                'bestRating' => '5',
243
                'worstRating' => '1',
244
                'ratingValue' => $aggregateRating,
245
            ];
246
            $aggregateRatings = array_filter($aggregateRatings);
247
            $recipeJSONLD['aggregateRating'] = $aggregateRatings;
248
249
            $reviews = [];
250
            foreach ($this->ratings as $rating) {
251
                $review = [
252
                    'type' => 'Review',
253
                    'author' => $rating['author'],
254
                    'name' => $this->name . ' ' . Craft::t('recipe', 'Review'),
255
                    'description' => $rating['review'],
256
                    'reviewRating' => [
257
                        'type' => 'Rating',
258
                        'bestRating' => '5',
259
                        'worstRating' => '1',
260
                        'ratingValue' => $rating['rating'],
261
                    ],
262
                ];
263
                $reviews[] = $review;
264
            }
265
            $reviews = array_filter($reviews);
266
            $recipeJSONLD['review'] = $reviews;
267
        }
268
269
        if ($this->prepTime) {
270
            $recipeJSONLD['prepTime'] = 'PT' . $this->prepTime . 'M';
271
        }
272
        if ($this->cookTime) {
273
            $recipeJSONLD['cookTime'] = 'PT' . $this->cookTime . 'M';
274
        }
275
        if ($this->totalTime) {
276
            $recipeJSONLD['totalTime'] = 'PT' . $this->totalTime . 'M';
277
        }
278
279
        return $this->renderJsonLd($recipeJSONLD, $raw);
280
    }
281
282
    /**
283
     * Get the URL to the recipe's image
284
     *
285
     * @return null|string
286
     */
287
    public function getImageUrl()
288
    {
289
        $result = '';
290
        if (isset($this->imageId) && $this->imageId) {
291
            $image = Craft::$app->getAssets()->getAssetById($this->imageId[0]);
292
            if ($image) {
0 ignored issues
show
introduced by
$image is of type craft\elements\Asset, thus it always evaluated to true.
Loading history...
293
                $result = $image->url;
294
            }
295
        }
296
297
        return $result;
298
    }
299
300
    /**
301
     * Render the Nutrition Facts template
302
     *
303
     * @param array $rda
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 2 spaces but found 1
Loading history...
304
     * @return Markup
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
305
     */
306
    public function renderNutritionFacts(array $rda = self::US_RDA): Markup {
0 ignored issues
show
Coding Style introduced by
Opening brace should be on a new line
Loading history...
307
        return PluginTemplate::renderPluginTemplate(
308
            'recipe-nutrition-facts',
309
            [
310
                'value' => $this,
311
                'rda' => $rda,
312
            ]
313
        );
314
    }
315
316
    /**
317
     * Get all of the ingredients for this recipe
318
     *
319
     * @param string $outputUnits
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
320
     * @param int    $serving
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
321
     * @param bool   $raw
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
322
     *
323
     * @return array
324
     */
325
    public function getIngredients($outputUnits = 'imperial', $serving = 0, $raw = true): array
326
    {
327
        $result = [];
328
329
        if (!empty($this->ingredients)) {
330
            foreach ($this->ingredients as $row) {
331
                $convertedUnits = '';
332
                $ingredient = '';
333
                if ($row['quantity']) {
334
                    // Multiply the quantity by how many servings we want
335
                    $multiplier = 1;
336
                    if ($serving > 0) {
337
                        $multiplier = $serving / $this->serves;
338
                    }
339
                    $quantity = $row['quantity'] * $multiplier;
340
                    $originalQuantity = $quantity;
341
342
                    // Do the imperial->metric units conversion
343
                    if ($outputUnits === 'imperial') {
344
                        switch ($row['units']) {
345
                            case 'mls':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
346
                                $convertedUnits = 'tsps';
347
                                $quantity *= 0.2;
348
                                break;
349
                            case 'ls':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
350
                                $convertedUnits = 'cups';
351
                                $quantity *= 4.2;
352
                                break;
353
                            case 'mgs':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
354
                                $convertedUnits = 'ozs';
355
                                $quantity *= 0.000035274;
356
                                break;
357
                            case 'gs':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
358
                                $convertedUnits = 'ozs';
359
                                $quantity *= 0.035274;
360
                                break;
361
                            case 'kg':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
362
                                $convertedUnits = 'lbs';
363
                                $quantity *= 2.2046226218;
364
                                break;
365
                        }
366
                    }
367
                    // Do the metric->imperial units conversion
368
                    if ($outputUnits === 'metric') {
369
                        switch ($row['units']) {
370
                            case 'tsps':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
371
                                $convertedUnits = 'mls';
372
                                $quantity *= 4.929;
373
                                break;
374
                            case 'tbsps':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
375
                                $convertedUnits = 'mls';
376
                                $quantity *= 14.787;
377
                                break;
378
                            case 'flozs':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
379
                                $convertedUnits = 'mls';
380
                                $quantity *= 29.574;
381
                                break;
382
                            case 'cups':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
383
                                $convertedUnits = 'ls';
384
                                $quantity *= 0.236588;
385
                                break;
386
                            case 'ozs':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
387
                                $convertedUnits = 'gs';
388
                                $quantity *= 28.3495;
389
                                break;
390
                            case 'lbs':
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 24 spaces, found 28
Loading history...
391
                                $convertedUnits = 'kg';
392
                                $quantity *= 0.45359237;
393
                                break;
394
                        }
395
396
                        $quantity = round($quantity, 1);
397
                    }
398
399
                    // Convert imperial units to nice fractions
400
                    if ($outputUnits === 'imperial') {
401
                        $quantity = $this->convertToFractions($quantity);
402
                    }
403
                    $ingredient .= $quantity;
404
405
                    if ($row['units']) {
406
                        $units = $row['units'];
407
                        if ($convertedUnits) {
408
                            $units = $convertedUnits;
409
                        }
410
                        if ($originalQuantity <= 1) {
411
                            $units = rtrim($units);
412
                            $units = rtrim($units, 's');
413
                        }
414
                        $ingredient .= ' ' . $units;
415
                    }
416
                }
417
                if ($row['ingredient']) {
418
                    $ingredient .= ' ' . $row['ingredient'];
419
                }
420
                if ($raw) {
421
                    $ingredient = Template::raw($ingredient);
422
                }
423
                $result[] = $ingredient;
424
            }
425
        }
426
427
        return $result;
428
    }
429
430
    /**
431
     * Convert decimal numbers into fractions
432
     *
433
     * @param $quantity
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
434
     *
435
     * @return string
436
     */
437
    private function convertToFractions($quantity)
0 ignored issues
show
Coding Style introduced by
Private method name "Recipe::convertToFractions" must be prefixed with an underscore
Loading history...
438
    {
439
        $whole = floor($quantity);
440
        // Round the mantissa so we can do a floating point comparison without
441
        // weirdness, per: https://www.php.net/manual/en/language.types.float.php#113703
442
        $fraction = round($quantity - $whole, 3);
443
        switch ($fraction) {
444
            case 0:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
445
                $fraction = '';
446
                break;
447
            case 0.25:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
448
                $fraction = ' &frac14;';
449
                break;
450
            case 0.33:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
451
                $fraction = ' &frac13;';
452
                break;
453
            case 0.66:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
454
                $fraction = ' &frac23;';
455
                break;
456
            case 0.165:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
457
                $fraction = ' &frac16;';
458
                break;
459
            case 0.5:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
460
                $fraction = ' &frac12;';
461
                break;
462
            case 0.75:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
463
                $fraction = ' &frac34;';
464
                break;
465
            case 0.125:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
466
                $fraction = ' &#x215B;';
467
                break;
468
            case 0.375:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
469
                $fraction = ' &#x215C;';
470
                break;
471
            case 0.625:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
472
                $fraction = ' &#x215D;';
473
                break;
474
            case 0.875:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
475
                $fraction = ' &#x215E;';
476
                break;
477
            default:
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected 8 spaces, found 12
Loading history...
478
                $precision = 5;
479
                $pnum = round($fraction, $precision);
480
                $denominator = 10 ** $precision;
481
                $numerator = $pnum * $denominator;
482
                $fraction = '<sup>'
483
                    .$numerator
484
                    . '</sup>&frasl;<sub>'
485
                    .$denominator
486
                    . '</sub>';
487
                break;
488
        }
489
        if ($whole === 0) {
490
            $whole = '';
491
        }
492
493
        return $whole.$fraction;
494
    }
495
496
    /**
497
     * Get all of the directions for this recipe
498
     *
499
     * @param bool $raw
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
500
     *
501
     * @return array
502
     */
503
    public function getDirections($raw = true)
504
    {
505
        $result = [];
506
        if (!empty($this->directions)) {
507
            foreach ($this->directions as $row) {
508
                $direction = $row['direction'];
509
                if ($raw) {
510
                    $direction = Template::raw($direction);
511
                }
512
                $result[] = $direction;
513
            }
514
        }
515
516
        return $result;
517
    }
518
519
    /**
520
     * Get the aggregate rating from all of the ratings
521
     *
522
     * @return float|int|string
523
     */
524
    public function getAggregateRating()
525
    {
526
        $result = 0;
527
        $total = 0;
528
        if (isset($this->ratings) && !empty($this->ratings)) {
529
            foreach ($this->ratings as $row) {
530
                $result += $row['rating'];
531
                $total++;
532
            }
533
            $result /= $total;
534
        } else {
535
            $result = '';
536
        }
537
538
        return $result;
539
    }
540
541
    /**
542
     * Get the total number of ratings
543
     *
544
     * @return int
545
     */
546
    public function getRatingsCount(): int
547
    {
548
        return count($this->ratings);
549
    }
550
551
    // Private Methods
552
    // =========================================================================
553
554
    /**
555
     * Renders a JSON-LD representation of the schema
556
     *
557
     * @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...
558
     * @param bool $raw
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
559
     *
560
     * @return string|\Twig_Markup
561
     */
562
    private function renderJsonLd($json, $raw = true)
0 ignored issues
show
Coding Style introduced by
Private method name "Recipe::renderJsonLd" must be prefixed with an underscore
Loading history...
563
    {
564
        $linebreak = '';
565
566
        // If `devMode` is enabled, make the JSON-LD human-readable
567
        if (Craft::$app->getConfig()->getGeneral()->devMode) {
568
            $linebreak = PHP_EOL;
569
        }
570
571
        // Render the resulting JSON-LD
572
        $result = '<script type="application/ld+json">'
573
            .$linebreak
574
            .Json::encode($json)
575
            .$linebreak
576
            .'</script>';
577
578
        if ($raw === true) {
579
            $result = Template::raw($result);
580
        }
581
582
        return $result;
583
    }
584
}
585