Test Failed
Push — master ( 39b140...ee05ce )
by Sebastian
09:47
created

Name::getOptions()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 15
nc 5
nop 0
dl 0
loc 21
rs 7.551
c 0
b 0
f 0
1
<?php
2
/**
3
 * citeproc-php
4
 *
5
 * @link        http://github.com/seboettg/citeproc-php for the source repository
6
 * @copyright   Copyright (c) 2016 Sebastian Böttger.
7
 * @license     https://opensource.org/licenses/MIT
8
 */
9
10
namespace Seboettg\CiteProc\Rendering\Name;
11
use Seboettg\CiteProc\CiteProc;
12
use Seboettg\CiteProc\Style\InheritableNameAttributesTrait;
13
use Seboettg\CiteProc\Style\SubsequentAuthorSubstituteRule;
14
use Seboettg\CiteProc\Styles\AffixesTrait;
15
use Seboettg\CiteProc\Styles\DelimiterTrait;
16
use Seboettg\CiteProc\Styles\FormattingTrait;
17
use Seboettg\CiteProc\Util\Factory;
18
use Seboettg\CiteProc\Util\StringHelper;
19
20
21
/**
22
 * Class Name
23
 *
24
 * The cs:name element, an optional child element of cs:names, can be used to describe the formatting of individual
25
 * names, and the separation of names within a name variable.
26
 *
27
 * @package Seboettg\CiteProc\Rendering\Name
28
 *
29
 * @author Sebastian Böttger <[email protected]>
30
 */
31
class Name
32
{
33
    use InheritableNameAttributesTrait,
34
        FormattingTrait,
35
        AffixesTrait,
36
        DelimiterTrait;
37
38
    /**
39
     * @var array
40
     */
41
    protected $nameParts;
42
43
    /**
44
     * Specifies whether all the name-parts of personal names should be displayed (value “long”, the default), or only
45
     * the family name and the non-dropping-particle (value “short”). A third value, “count”, returns the total number
46
     * of names that would otherwise be rendered by the use of the cs:names element (taking into account the effects of
47
     * et-al abbreviation and editor/translator collapsing), which allows for advanced sorting.
48
     *
49
     * @var string
50
     */
51
    private $form = "long";
52
53
54
    /**
55
     * Specifies the text string used to separate names in a name variable. Default is ”, ” (e.g. “Doe, Smith”).
56
     * @var
57
     */
58
    private $delimiter = ", ";
59
60
61
    /**
62
     * @var Names
63
     */
64
    private $parent;
65
66
    /**
67
     * @var \SimpleXMLElement
68
     */
69
    private $node;
70
71
    /**
72
     * @var string
73
     */
74
    private $etAl;
75
76
    /**
77
     * Name constructor.
78
     * @param \SimpleXMLElement $node
79
     * @param Names $parent
80
     */
81
    public function __construct(\SimpleXMLElement $node, Names $parent)
82
    {
83
        $this->node = $node;
84
        $this->parent = $parent;
85
86
        $this->nameParts = [];
87
88
        /** @var \SimpleXMLElement $child */
89
        foreach ($node->children() as $child) {
90
91
            switch ($child->getName()) {
92
                case "name-part":
93
                    /** @var NamePart $namePart */
94
                    $namePart = Factory::create($child, $this);
95
                    $this->nameParts[$namePart->getName()] = $namePart;
96
            }
97
        }
98
99
        foreach ($node->attributes() as $attribute) {
100
            switch ($attribute->getName()) {
101
                case 'form':
102
                    $this->form = (string) $attribute;
103
                    break;
104
            }
105
106
        }
107
108
        $this->initFormattingAttributes($node);
109
        $this->initAffixesAttributes($node);
110
        $this->initDelimiterAttributes($node);
111
    }
112
113
    public function render($data, $citationNumber = null)
114
    {
115
        $lastName = null;
0 ignored issues
show
Unused Code introduced by
$lastName is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
116
117
        if (!$this->attributesInitialized) {
118
            $this->initInheritableNameAttributes($this->node);
119
        }
120
        $resultNames = [];
121
122
        $count = 0;
123
124
        $hasPreceding = CiteProc::getContext()->getCitationItems()->hasKey($citationNumber-1);
125
        if ($hasPreceding) {
126
            /** @var \stdClass $preceding */
127
            $preceding = CiteProc::getContext()->getCitationItems()->get($citationNumber-1);
128
        }
129
130
        $subsequentSubstitution = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstitute();
131
        $subsequentSubstitutionRule = CiteProc::getContext()->getCitationItems()->getSubsequentAuthorSubstituteRule();
132
133
        $useSubseqSubstitution = !empty($subsequentSubstitution) && !empty($subsequentSubstitutionRule);
134
135
        /**
136
         * @var string $type
137
         * @var array $name
138
         */
139
        foreach ($data as $rank => $name) {
140
            ++$count;
141
142
            if (!empty($preceding) && $useSubseqSubstitution) {
143
                if ($subsequentSubstitutionRule == SubsequentAuthorSubstituteRule::PARTIAL_EACH && $rank > 0) {
144 View Code Duplication
                    if ($preceding->author[$rank]->family === $name->family) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
                        $resultNames[] = $subsequentSubstitution;
146
                    } else {
147
                        $resultNames[] = $this->formatName($name, $rank);
148
                    }
149
                } else if ($subsequentSubstitutionRule == SubsequentAuthorSubstituteRule::PARTIAL_FIRST && $rank === 0) {
150 View Code Duplication
                    if ($preceding->author[0]->family === $name->family) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
151
                        $resultNames[] = $subsequentSubstitution;
152
                    } else {
153
                        $resultNames[] = $this->formatName($name, $rank);
154
                    }
155
                }
156
            }
157
            else {
158
                $resultNames[] = $this->formatName($name, $rank);
159
            }
160
        }
161
162
        $resultNames = $this->prepareAbbreviation($resultNames);
163
164
        if ($this->etAlUseLast) {
165
            /* When set to “true” (the default is “false”), name lists truncated by et-al abbreviation are followed by
166
            the name delimiter, the ellipsis character, and the last name of the original name list. This is only
167
            possible when the original name list has at least two more names than the truncated name list (for this
168
            the value of et-al-use-first/et-al-subsequent-min must be at least 2 less than the value of
169
            et-al-min/et-al-subsequent-use-first). */
170
            $this->and = "…"; // set "and"
171
            $this->etAl = null; //reset $etAl;
172
        }
173
174
        /* add "and" */
175
        $count = count($resultNames);
176
        if (!empty($this->and) && $count > 1 && empty($this->etAl)) {
177
            $new = $this->and . ' ' . end($resultNames); // add and-prefix of the last name if "and" is defined
178
            $resultNames[count($resultNames) - 1] = $new; //set prefixed last name at the last position of $resultNames array
179
180
        }
181
182
        $text = implode($this->delimiter, $resultNames);
183
184
        //append et al abbreviation
185
        if (count($data) > 1 && !empty($resultNames) && !empty($this->etAl)) {
186
            $text = $this->appendEtAl($text, $resultNames);
187
        }
188
189
        /* A third value, “count”, returns the total number of names that would otherwise be rendered by the use of the
190
        cs:names element (taking into account the effects of et-al abbreviation and editor/translator collapsing),
191
        which allows for advanced sorting. */
192
193
        if ($this->form == 'count') {
194
            return (int)count($resultNames);
195
        }
196
197
        return $text;
198
    }
199
200
    private function formatName($name, $rank)
201
    {
202
        $nameObj = $this->cloneNamePOSC($name);
203
204
        $useInitials = $this->initialize && !empty($this->initializeWith);
205
        if ($useInitials && isset($name->given)) {
206
            //TODO: initialize with hyphen
207
            //$nameObj->given = $name->given;
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
208
            $nameObj->given = "";
209
            $givenParts = StringHelper::explodeBySpaceOrHyphen($name->given);
210
            foreach ($givenParts as $givenPart) {
211
                $nameObj->given .= substr($givenPart, 0, 1) . $this->initializeWith;
212
            }
213
        }
214
215
        // format name-parts
216
        if (count($this->nameParts) > 0) {
217
            /** @var NamePart $namePart */
218
            foreach ($this->nameParts as $namePart) {
219
                $nameObj->{$namePart->getName()} =   $namePart->render($name);
220
            }
221
        }
222
223
        $return = $this->getNamesString($nameObj, $rank);
224
225
        return trim($return);
226
    }
227
228
    /**
229
     * @param $name
230
     * @return string
231
     */
232
    private function getNamesString($name, $rank)
233
    {
234
        $text = "";
235
236
        if (!isset($name->family)) {
237
            return $text;
238
        }
239
240
        $given = !empty($name->given) ? $this->format(trim($name->given)) : "";
241
        $nonDroppingParticle = isset($name->{'non-dropping-particle'}) ? $name->{'non-dropping-particle'} : "";
242
        $droppingParticle = isset($name->{'dropping-particle'}) ? $name->{'dropping-particle'} : "";
243
        $suffix = (isset($name->{'suffix'})) ? $name->{'suffix'} : "";
244
245
        if (isset($name->family)) {
246
            $family = $this->format($name->family);
247
            if ($this->form == 'short') {
248
                $text = (!empty($nonDroppingParticle) ? $nonDroppingParticle . " " : "") . $family;
249
            } else {
250
                switch ($this->nameAsSortOrder) {
251
252
                    case 'all':
253
                    case 'first':
254
                        if ($this->nameAsSortOrder === "first" && $rank !== 0) {
255
                            break;
256
                        }
257
                        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
258
                        use form "[non-dropping particel] family name,
259
                        given name [dropping particle], [suffix]"
260
                        */
261
                        $text  = !empty($nonDroppingParticle) ? "$nonDroppingParticle " : "";
262
                        $text .= $family;
263
                        $text .= !empty($given) ? ", $given" : "";
264
                        $text .= !empty($droppingParticle) ? " $droppingParticle" : "";
265
                        $text .= !empty($suffix) ? ", $suffix" : "";
266
267
                        //remove last comma when no suffix exist.
268
                        $text = trim($text);
269
                        $text = substr($text, -1) === "," ? substr($text, 0, strlen($text)-1) : $text;
270
                        break;
271
                    default:
272
                        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
273
                        use form "given name [dropping particles] [non-dropping particles] family name [suffix]"
274
                        e.g. [Jean] [de] [La] [Fontaine] [III]
275
                        */
276
                        $text = sprintf(
277
                            "%s %s %s %s %s",
278
                            $given,
279
                            $droppingParticle,
280
                            $nonDroppingParticle,
281
                            $family,
282
                            $suffix);
283
284
                }
285
            }
286
        }
287
288
        //contains nbsp prefixed by normal space or followed by normal space?
289
        $text = htmlentities($text);
290
        if (strpos($text, " &nbsp;") !== false || strpos($text, "&nbsp; ") !== false) {
291
292
            $text = preg_replace("/[\s]+/", "", $text); //remove normal spaces
293
            return preg_replace("/&nbsp;+/", " ", $text);
294
        }
295
        $text = preg_replace("/[\s]+/", " ", $text);
296
        return trim($text);
297
    }
298
299
    public function getOptions()
300
    {
301
        $ignore = ["namePart", "parent", "substitute"];
302
        $options = [];
303
        $reflectedName = new \ReflectionClass($this);
304
305
        foreach ($reflectedName->getProperties() as $property) {
306
            $property->setAccessible(true);
307
            if (in_array($property->getName(), $ignore)) {
308
                continue;
309
            } else if ($property->getName() == "and" && $property->getValue($this) === "&#38;") {
310
                $options["and"] = "symbol";
311
            } else {
312
                $propValue = $property->getValue($this);
313
                if (isset($propValue) && !empty($propValue)) {
314
                    $options[StringHelper::camelCase2Hyphen($property->getName())] = $propValue;
315
                }
316
            }
317
        }
318
        return $options;
319
    }
320
321
    /**
322
     * @param $name
323
     * @return \stdClass
324
     */
325
    private function cloneNamePOSC($name)
326
    {
327
        $nameObj = new \stdClass();
328
        if (isset($name->family)) {
329
            $nameObj->family = $name->family;
330
        }
331
        if (isset($name->given)) {
332
            $nameObj->given = $name->given;
333
        }
334
        if (isset($name->{'non-dropping-particle'})) {
335
            $nameObj->{'non-dropping-particle'} = $name->{'non-dropping-particle'};
336
        }
337
        if (isset($name->{'dropping-particle'})) {
338
            $nameObj->{'dropping-particle'} = $name->{'dropping-particle'};
339
        }
340
        if (isset($name->{'suffix'})) {
341
            $nameObj->{'suffix'} = $name->{'suffix'};
342
        }
343
        return $nameObj;
344
    }
345
346
    /**
347
     * @param $text
348
     * @param $etAl
349
     * @param $resultNames
350
     * @return string
351
     */
352
    protected function appendEtAl($text, $resultNames)
353
    {
354
        /* By default, when a name list is truncated to a single name, the name and the “et-al” (or “and others”)
355
        term are separated by a space (e.g. “Doe et al.”). When a name list is truncated to two or more names, the
356
        name delimiter is used (e.g. “Doe, Smith, et al.”). This behavior can be changed with the
357
        delimiter-precedes-et-al attribute. */
358
359
        switch ($this->delimiterPrecedesEtAl) {
360
            case 'never':
361
                $text = $text . " " . $this->etAl;
362
                break;
363
            case 'always':
364
                $text = $text . $this->delimiter . $this->etAl;
365
                break;
366
            case 'contextual':
367
            default:
368
                if (count($resultNames) === 1) {
369
                    $text .= " " . $this->etAl;
370
                } else {
371
                    $text .= $this->delimiter . $this->etAl;
372
                }
373
        }
374
375
        return $text;
376
    }
377
378
    /**
379
     * @param $resultNames
380
     * @return array
381
     */
382
    protected function prepareAbbreviation($resultNames)
383
    {
384
        /* Use of et-al-min and et-al-user-first enables et-al abbreviation. If the number of names in a name variable
385
        matches or exceeds the number set on et-al-min, the rendered name list is truncated after reaching the number of
386
        names set on et-al-use-first.  */
387
        if (isset($this->etAlMin) && isset($this->etAlUseFirst)) {
388
            $cnt = count($resultNames);
389
            if ($this->etAlMin >= count($cnt)) {
390
                if ($this->etAlUseLast && $this->etAlMin - $this->etAlUseFirst >= 2) {
391
                    /* et-al-use-last: When set to “true” (the default is “false”), name lists truncated by et-al
392
                    abbreviation are followed by the name delimiter, the ellipsis character, and the last name of the
393
                    original name list. This is only possible when the original name list has at least two more names
394
                    than the truncated name list (for this the value of et-al-use-first/et-al-subsequent-min must be at
395
                    least 2 less than the value of et-al-min/et-al-subsequent-use-first).*/
396
397
                    $lastName = array_pop($resultNames); //remove last Element and remember in $lastName
398
399
                }
400
                for ($i = $this->etAlUseFirst; $i < count($resultNames); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
401
                    unset($resultNames[$i]);
402
                }
403
404
                $resultNames = array_values($resultNames);
405
406
                if (!empty($lastName)) { // append $lastName if exist
407
                    $resultNames[] = $lastName;
408
                }
409
410
                if ($this->parent->hasEtAl()) {
411
                    $this->etAl = $this->parent->getEtAl()->render(null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a array|object<Seboettg\CiteProc\Data\DataList>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
412
                    return $resultNames;
413
                } else {
414
                    $this->etAl = CiteProc::getContext()->getLocale()->filter('terms', 'et-al')->single;
415
                    return $resultNames;
416
                }
417
            }
418
            return $resultNames;
419
        }
420
        return $resultNames;
421
    }
422
423
424
}
0 ignored issues
show
Coding Style introduced by
As per coding style, files should not end with a newline character.

This check marks files that end in a newline character, i.e. an empy line.

Loading history...
425