Passed
Push — new-api ( 4bfe18...7ec1cc )
by Sebastian
05:06
created

Layout::renderSingle()   B

Complexity

Conditions 10
Paths 12

Size

Total Lines 37
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 10.0045

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 29
nc 12
nop 2
dl 0
loc 37
ccs 27
cts 28
cp 0.9643
crap 10.0045
rs 7.6666
c 1
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
/*
4
 * citeproc-php
5
 *
6
 * @link        http://github.com/seboettg/citeproc-php for the source repository
7
 * @copyright   Copyright (c) 2016 Sebastian Böttger.
8
 * @license     https://opensource.org/licenses/MIT
9
 */
10
11
namespace Seboettg\CiteProc\Rendering;
12
13
use Seboettg\CiteProc\CiteProc;
14
use Seboettg\CiteProc\Config\RenderingMode;
15
use Seboettg\CiteProc\Data\DataList;
16
use Seboettg\CiteProc\Config\RenderingState;
17
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
18
use Seboettg\CiteProc\Rendering\Observer\RenderingObserver;
19
use Seboettg\CiteProc\Rendering\Observer\RenderingObserverTrait;
20
use Seboettg\CiteProc\Rendering\Observer\StateChangedEvent;
21
use Seboettg\CiteProc\Style\Options\StyleOptions;
22
use Seboettg\CiteProc\Style\Sort\Sort;
23
use Seboettg\CiteProc\Styles\ConsecutivePunctuationCharacterTrait;
24
use Seboettg\CiteProc\Styles\StylesRenderer;
25
use Seboettg\CiteProc\Util\CiteProcHelper;
26
use Seboettg\CiteProc\Util\Factory;
27
use Seboettg\CiteProc\Util\StringHelper;
28
use Seboettg\Collection\ArrayList as ArrayList;
29
use Seboettg\Collection\ArrayList\ArrayListInterface;
30
use SimpleXMLElement;
31
use stdClass;
32
33
/**
34
 * Class Layout
35
 *
36
 * @package Seboettg\CiteProc\Rendering
37
 *
38
 * @author Sebastian Böttger <[email protected]>
39
 */
40
class Layout implements Rendering, RenderingObserver
41
{
42
    use ConsecutivePunctuationCharacterTrait,
43
        RenderingObserverTrait;
44
45
    private static $numberOfCitedItems = 0;
46
47
    /**
48
     * @var ArrayList
49
     */
50
    private $children;
51
52
    /**
53
     * When used within cs:citation, the delimiter attribute may be used to specify a delimiter for cites within a
54
     * citation.
55
     *
56
     * @var string
57
     */
58
    private $delimiter = "";
59
60
61
    private $parent;
62
63
    /**
64
     * @var StylesRenderer
65
     */
66
    private $stylesRenderer;
67
68
    /** @var Sort */
69
    private $sorting;
70
71
    /** @var StyleOptions */
72
    private $styleOptions;
73
74
    /**
75
     * @param SimpleXMLElement|null $node
76
     * @return Layout
77
     * @throws InvalidStylesheetException
78
     */
79 173
    public static function factory(?SimpleXMLElement $node): ?Layout
80
    {
81 173
        if (null === $node) {
82
            return null;
83
        }
84 173
        $children = new ArrayList();
85 173
        $layout = new Layout();
86 173
        foreach ($node->children() as $csChild) {
87 173
            $child = Factory::create($csChild);
88 173
            $child->setParent($layout);
89 173
            $children->append($child);
90
        }
91 173
        $stylesRenderer = StylesRenderer::factory($node);
92 173
        $layout->setChildren($children);
93 173
        $layout->setStylesRenderer($stylesRenderer);
94 173
        $layout->setDelimiter((string)$node->attributes()['delimiter']);
95 173
        CiteProc::getContext()->addObserver($layout);
96 173
        return $layout;
97
    }
98
99 173
    public function __construct()
100
    {
101 173
        $this->children = new ArrayList();
102 173
        $this->initObserver();
103 173
    }
104
105
    /**
106
     * @param  array|DataList  $data
107
     * @param  array|ArrayList $citationItems
108
     * @return string|array
109
     */
110 169
    public function render($data, $citationItems = [])
111
    {
112 169
        $ret = "";
113 169
        if (!empty($this->sorting)) {
114 50
            $this->notifyAll(new StateChangedEvent(RenderingState::SORTING()));
115 50
            $this->sorting->sort($data);
116 50
            $this->notifyAll(new StateChangedEvent(RenderingState::RENDERING()));
117
        }
118
119 169
        if ($this->mode->equals(RenderingMode::BIBLIOGRAPHY())) {
120 80
            foreach ($data as $citationNumber => $item) {
121 80
                ++self::$numberOfCitedItems;
122 80
                CiteProc::getContext()->getResults()->append(
123 80
                    $this->wrapBibEntry($item, $this->renderSingle($item, $citationNumber))
124
                );
125
            }
126 80
            $ret .= implode($this->delimiter, CiteProc::getContext()->getResults()->toArray());
127 80
            $ret = StringHelper::clearApostrophes($ret);
128 80
            return sprintf("<div class=\"csl-bib-body\">%s\n</div>", $ret);
129 103
        } elseif ($this->mode->equals(RenderingMode::CITATION())) {
130 103
            if ($citationItems->count() > 0) { //is there a filter for specific citations?
131 9
                if ($this->isGroupedCitations($citationItems)) { //if citation items grouped?
0 ignored issues
show
Bug introduced by
It seems like $citationItems can also be of type array; however, parameter $citationItems of Seboettg\CiteProc\Render...t::isGroupedCitations() does only seem to accept Seboettg\Collection\ArrayList\ArrayListInterface, 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

131
                if ($this->isGroupedCitations(/** @scrutinizer ignore-type */ $citationItems)) { //if citation items grouped?
Loading history...
132 1
                    return $this->renderGroupedCitations($data, $citationItems);
0 ignored issues
show
Bug introduced by
It seems like $citationItems can also be of type array; however, parameter $citationItems of Seboettg\CiteProc\Render...enderGroupedCitations() does only seem to accept Seboettg\Collection\ArrayList\ArrayListInterface, 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

132
                    return $this->renderGroupedCitations($data, /** @scrutinizer ignore-type */ $citationItems);
Loading history...
133
                } else {
134 8
                    $data = $this->filterCitationItems($data, $citationItems);
0 ignored issues
show
Bug introduced by
It seems like $citationItems can also be of type array; however, parameter $citationItems of Seboettg\CiteProc\Render...::filterCitationItems() does only seem to accept Seboettg\Collection\ArrayList\ArrayListInterface, 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

134
                    $data = $this->filterCitationItems($data, /** @scrutinizer ignore-type */ $citationItems);
Loading history...
135 8
                    $ret = $this->renderCitations($data, $ret);
136
                }
137
            } else {
138 95
                $ret = $this->renderCitations($data, $ret);
139
            }
140
        }
141 102
        $ret = StringHelper::clearApostrophes($ret);
142 102
        return $this->stylesRenderer->renderAffixes($ret);
143
    }
144
145
    /**
146
     * @param  $data
147
     * @param  int|null $citationNumber
148
     * @return string
149
     */
150 169
    private function renderSingle($data, $citationNumber = null)
151
    {
152 169
        $inMargin = [];
153 169
        $margin = [];
154 169
        foreach ($this->children as $key => $child) {
155 169
            $rendered = $child->render($data, $citationNumber);
156 169
            $this->getChildrenAffixesAndDelimiter($child);
157 169
            if ($this->mode->equals(RenderingMode::BIBLIOGRAPHY())
158 169
                && $this->styleOptions->getSecondFieldAlign() === "flush"
0 ignored issues
show
Bug introduced by
The method getSecondFieldAlign() does not exist on Seboettg\CiteProc\Style\Options\StyleOptions. It seems like you code against a sub-type of Seboettg\CiteProc\Style\Options\StyleOptions such as Seboettg\CiteProc\Style\...ons\BibliographyOptions. ( Ignorable by Annotation )

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

158
                && $this->styleOptions->/** @scrutinizer ignore-call */ getSecondFieldAlign() === "flush"
Loading history...
159
            ) {
160 10
                if ($key === 0 && !empty($rendered)) {
161 10
                    $inMargin[] = $rendered;
162
                } else {
163 10
                    $margin[] = $rendered;
164
                }
165
            } else {
166 169
                $inMargin[] = $rendered;
167
            }
168
        }
169 169
        $inMargin = array_filter($inMargin);
170 169
        $margin = array_filter($margin);
171 169
        if (!empty($inMargin) && !empty($margin) && $this->mode->equals(RenderingMode::BIBLIOGRAPHY())) {
172 9
            $leftMargin = $this->removeConsecutiveChars(
173 9
                $this->htmlentities($this->stylesRenderer->renderFormatting(implode("", $inMargin)))
174
            );
175 9
            $result = $this->htmlentities($this->stylesRenderer->renderFormatting(implode("", $margin)));
176 9
            $result = rtrim($result, $this->stylesRenderer->getAffixes()->getSuffix()) .
177 9
                $this->stylesRenderer->getAffixes()->getSuffix();
178 9
            $rightInline = $this->removeConsecutiveChars($result);
179 9
            $res  = sprintf('<div class="csl-left-margin">%s</div>', trim($leftMargin));
180 9
            $res .= sprintf('<div class="csl-right-inline">%s</div>', trim($rightInline));
181 9
            return $res;
182 161
        } elseif (!empty($inMargin)) {
183 161
            $res = $this->stylesRenderer->renderFormatting(implode("", $inMargin));
184 161
            return $this->htmlentities($this->removeConsecutiveChars($res));
185
        }
186
        return "";
187
    }
188
189
    /**
190
     * @return int
191
     */
192
    public static function getNumberOfCitedItems(): int
193
    {
194
        return self::$numberOfCitedItems;
195
    }
196
197
    /**
198
     * @param stdClass $dataItem
199
     * @param string $value
200
     * @return string
201
     */
202 80
    private function wrapBibEntry(stdClass $dataItem, string $value): string
203
    {
204 80
        $value = $this->stylesRenderer->renderAffixes($value);
205
        return "\n  ".
206
            "<div class=\"csl-entry\">" .
207 80
            $renderedItem = CiteProcHelper::applyAdditionMarkupFunction($dataItem, "csl-entry", $value) .
0 ignored issues
show
Unused Code introduced by
The assignment to $renderedItem is dead and can be removed.
Loading history...
208 80
            "</div>";
209
    }
210
211
    /**
212
     * @param  string $text
213
     * @return string
214
     */
215 169
    private function htmlentities($text)
216
    {
217 169
        $text = preg_replace("/(.*)&([^#38|amp];.*)/u", "$1&#38;$2", $text);
218 169
        return $text;
219
    }
220
221
    /**
222
     * @param $data
223
     * @param string $ret
224
     * @return string
225
     */
226 103
    private function renderCitations($data, string $ret)
227
    {
228 103
        CiteProc::getContext()->getResults()->replace([]);
229 103
        foreach ($data as $citationNumber => $item) {
230 103
            $renderedItem = $this->renderSingle($item, $citationNumber);
231 103
            $renderedItem = CiteProcHelper::applyAdditionMarkupFunction($item, "csl-entry", $renderedItem);
232 103
            CiteProc::getContext()->getResults()->append($renderedItem);
233 103
            CiteProc::getContext()->appendCitedItem($item);
234
        }
235 103
        $ret .= implode($this->delimiter, CiteProc::getContext()->getResults()->toArray());
236 103
        return $ret;
237
    }
238
239
    /**
240
     * @param DataList $data
241
     * @param ArrayListInterface $citationItems
242
     * @return mixed
243
     */
244 9
    private function filterCitationItems(DataList $data, ArrayListInterface $citationItems)
245
    {
246
        return $data->filter(function ($dataItem) use ($citationItems) {
247 9
            foreach ($citationItems as $citationItem) {
248 9
                if ($dataItem->id === $citationItem->id) {
249 9
                    return true;
250
                }
251
            }
252 3
            return false;
253 9
        });
254
    }
255
256
    /**
257
     * @param  ArrayListInterface $citationItems
258
     * @return bool
259
     */
260 9
    private function isGroupedCitations(ArrayListInterface $citationItems): bool
261
    {
262 9
        $firstItem = array_values($citationItems->toArray())[0];
263 9
        if (is_array($firstItem)) {
264 1
            return true;
265
        }
266 8
        return false;
267
    }
268
269
    /**
270
     * @param DataList $data
271
     * @param ArrayListInterface $citationItems
272
     * @return array|string
273
     */
274 1
    private function renderGroupedCitations(DataList $data, ArrayListInterface $citationItems)
275
    {
276 1
        $group = [];
277 1
        foreach ($citationItems as $citationItemGroup) {
278 1
            $data_ = $this->filterCitationItems($data, new ArrayList(...$citationItemGroup));
279 1
            CiteProc::getContext()->setCitationData($data_);
280 1
            $group[] = $this->stylesRenderer->renderAffixes(
281 1
                StringHelper::clearApostrophes($this->renderCitations($data_, ""))
282
            );
283
        }
284 1
        if (CiteProc::getContext()->isCitationsAsArray()) {
285 1
            return $group;
286
        }
287
        return implode("\n", $group);
288
    }
289
290 173
    public function setChildren(ArrayListInterface $children)
291
    {
292 173
        $this->children = $children;
0 ignored issues
show
Documentation Bug introduced by
$children is of type Seboettg\Collection\ArrayList\ArrayListInterface, but the property $children was declared to be of type Seboettg\Collection\ArrayList. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
293 173
    }
294
295 173
    public function setStylesRenderer(StylesRenderer $stylesRenderer)
296
    {
297 173
        $this->stylesRenderer = $stylesRenderer;
298 173
    }
299
300 173
    public function setDelimiter(string $delimiter)
301
    {
302 173
        $this->delimiter = $delimiter;
303 173
    }
304
305 173
    public function setParent($parent)
306
    {
307 173
        $this->parent = $parent;
308 173
    }
309
310 173
    public function setSorting(?Sort $sorting): void
311
    {
312 173
        $this->sorting = $sorting;
313 173
    }
314
315 173
    public function setStyleOptions(StyleOptions $styleOptions)
316
    {
317 173
        $this->styleOptions = $styleOptions;
318 173
    }
319
}
320