Passed
Push — main ( ffd35e...44834b )
by Marc
03:18
created

ModelFactory::getContentObject()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
namespace html_go\model;
3
4
use DateTimeInterface;
5
use InvalidArgumentException;
6
use html_go\exceptions\InternalException;
7
use html_go\indexing\IndexManager;
8
use html_go\markdown\MarkdownParser;
9
10
/**
11
 * Responsible for creating <code>Content</code> objects ready to be used in templates.
12
 * @author Marc L. Veary
13
 * @since 1.0
14
 */
15
final class ModelFactory
16
{
17
    private Config $config;
18
    private MarkdownParser $parser;
19
    private IndexManager $manager;
20
21
    /**
22
     * ModelFactory constructor.
23
     * @param Config $config
24
     * @param MarkdownParser $parser Implementation of the
25
     * <code>MarkdownParser</code> interface.
26
     */
27
    public function __construct(Config $config, MarkdownParser $parser, IndexManager $manager) {
28
        $this->config = $config;
29
        $this->parser = $parser;
30
        $this->manager = $manager;
31
    }
32
33
    /**
34
     * Create a content object (stdClass) from an index object (stdClass).
35
     * @param \stdClass $indexElement As obtained from the <code>IndexManager</code>
36
     * @return \stdClass
37
     */
38
    public function createContentObject(\stdClass $indexElement): \stdClass {
39
        $contentObject = $this->getContentObject($indexElement);
40
        $contentObject->key = $indexElement->key;
41
        $contentObject->list = [];
42
        $contentObject->site = $this->getSiteObject();
43
44
        if (!empty($indexElement->category)) {
45
            $contentObject->category = $this->getCategoryObject($indexElement->category);
46
        }
47
        if (isset($indexElement->tags)) {
48
            $contentObject->tags = $indexElement->tags;
49
        }
50
        $dt = $this->getContentDateAndTimestamp($indexElement);
51
        $contentObject->date = $dt[0];
52
        $contentObject->timestamp = $dt[1];
53
54
        if (empty($contentObject->summary)) {
55
            $contentObject->summary = $this->getSummary($contentObject->body);
56
        }
57
58
        return $contentObject;
59
    }
60
61
    private function getContentObject(\stdClass $indexElement): \stdClass {
62
        if ($indexElement->section === TAG_SECTION) {
63
            $contentObject = $this->createEmptyContentObject($indexElement);
64
            $contentObject->title = \substr($indexElement->key, \strlen(TAG_SECTION) + 1);
65
        } else {
66
            $contentObject = $this->loadDataFile($indexElement);
67
            $contentObject->body = $this->restoreNewlines($contentObject->body);
68
        }
69
        return $contentObject;
70
    }
71
72
    /**
73
     * @param \stdClass $indexElement
74
     * @return array<string>
75
     */
76
    private function getContentDateAndTimestamp(\stdClass $indexElement): array {
77
        if (empty($indexElement->timestamp) === false) {
78
            $dt = new \DateTime($indexElement->timestamp);
79
            $date = $dt->format($this->config->getString(Config::KEY_POST_DATE_FMT));
80
            $timestamp = $dt->format(DateTimeInterface::W3C);
81
        } else {
82
            $date = $timestamp = EMPTY_VALUE;
83
        }
84
        return [$date, $timestamp];
85
    }
86
87
    private function getCategoryObject(string $slug): \stdClass {
88
        if ($this->manager->elementExists($slug) === false) {
89
            throw new \UnexpectedValueException("Element does not exist [$slug]"); // @codeCoverageIgnore
90
        }
91
        return $this->loadDataFile($this->manager->getElementFromSlugIndex($slug));
92
    }
93
94
    private function getSiteObject(): \stdClass {
95
        static $site = null;
96
        if (empty($site)) {
97
            $site = new \stdClass();
98
            $site->url = $this->config->getString(Config::KEY_SITE_URL);
99
            $site->name = $this->config->getString(Config::KEY_SITE_NAME);
100
            $site->title = $this->config->getString(Config::KEY_SITE_TITLE);
101
            $site->description = $this->config->getString(Config::KEY_SITE_DESCRIPTION);
102
            $site->tagline = $this->config->getString(Config::KEY_SITE_TAGLINE);
103
            $site->copyright = $this->config->getString(Config::KEY_SITE_COPYRIGHT);
104
            $site->language = $this->config->getString(Config::KEY_LANG);
105
            $site->theme = $this->config->getString(Config::KEY_THEME_NAME);
106
            $site->tpl_engine = $this->config->getString(Config::KEY_TPL_ENGINE);
107
        }
108
        return $site;
109
    }
110
111
    /**
112
     * This loads the content's associated file and merges it with the index element.
113
     * @param \stdClass $indexElement
114
     * @throws InvalidArgumentException
115
     * @throws InternalException
116
     * @return \stdClass
117
     */
118
    private function loadDataFile(\stdClass $indexElement): \stdClass {
119
        if (empty($indexElement->path)) {
120
            throw new InvalidArgumentException("Object does not have 'path' property "./** @scrutinizer ignore-type */print_r($indexElement, true)); // @codeCoverageIgnore
121
        }
122
        if (($data = \file_get_contents($indexElement->path)) === false) {
123
            throw new InternalException("file_get_contents() failed opening [$indexElement->path]"); // @codeCoverageIgnore
124
        }
125
        if (($fileData = \json_decode($data, true)) === null) {
126
            throw new InternalException("json_decode returned null decoding [$fileData] from [$indexElement->path]"); // @codeCoverageIgnore
127
        }
128
        return (object)\array_merge((array)$indexElement, $fileData);
129
    }
130
131
    /**
132
     * This is used for tags only as tags don't have an associated file on the
133
     * filesystem.
134
     * @return \stdClass
135
     */
136
    private function createEmptyContentObject(\stdClass $indexElement): \stdClass {
137
        $obj = new \stdClass();
138
        $obj->key = EMPTY_VALUE;
139
        $obj->section = TAG_SECTION;
140
        $obj->body = EMPTY_VALUE;
141
        $obj->title = EMPTY_VALUE;
142
        $obj->description = EMPTY_VALUE;
143
        $obj->timestamp = EMPTY_VALUE;
144
        return (object)\array_merge((array)$indexElement, (array)$obj);
145
    }
146
147
    /**
148
     * Returns the summary for the content object. If '<!--more-->' is used within
149
     * the body, then this is removed once the summary is obtained.
150
     * @param string $body the body. The '<!--more--> removed if found thus passed by reference.
151
     * @return string The summary text. If no summary is defined, returns an empty string
152
     */
153
    private function getSummary(string &$body): string {
154
        $pos = \strpos($body, SUMMARY_MARKER);
155
        if ($pos !== false) {
156
            $summary = \substr($body, 0, $pos);
157
            $body = \str_replace(SUMMARY_MARKER, '', $body);
158
            return $summary;
159
        }
160
        return '';
161
    }
162
163
    /**
164
     * Newlines must be encoded for PHP functions. So we use '<nl>' to for '\n'.
165
     * This method replaces '<nl>' with '\n'.
166
     * @param string $text
167
     * @return string
168
     */
169
    private function restoreNewlines(string $text): string {
170
        return \str_replace(NEWLINE_MARKER, '\n', $text);
171
    }
172
}
173