Completed
Branch master (2ac5a6)
by Nicola
01:38
created

Autotoc::getAutotoc()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 0
1
<?php
2
3
namespace eNTiDi\Autotoc;
4
5
use eNTiDi\Autotoc\Hacks;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\Core\Injector\Injector;
8
use SilverStripe\ORM\ArrayList;
9
use SilverStripe\ORM\DataExtension;
10
use SilverStripe\View\ArrayData;
11
use SplObjectStorage;
12
13
class Autotoc extends DataExtension
14
{
15
    /**
16
     * @config
17
     * Callable to be used for augmenting a DOMElement: specify as a
18
     * string in the format "class::method".  `Tocifier::prependAnchor`
19
     * and `Tocifier::setId` are two valid callbacks.
20
     */
21
    private static $augment_callback;
0 ignored issues
show
Unused Code introduced by
The property $augment_callback is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
22
23
    protected static $tocifiers;
24
25
26
    /**
27
     * Initialize the Autotoc extension.
28
     *
29
     * Creates an internal SplObjectStorage where caching the table of
30
     * contents.
31
     */
32
    public function __construct()
33
    {
34
        parent::__construct();
35
        if (empty(self::$tocifiers)) {
36
            self::$tocifiers = new SplObjectStorage();
37
        }
38
    }
39
40
    private static function convertNode($node)
41
    {
42
        $data = new ArrayData([
43
            'Id'    => $node['id'],
44
            'Title' => $node['title']
45
        ]);
46
47
        if (isset($node['children'])) {
48
            $data->setField('Children', self::convertChildren($node['children']));
49
        }
50
51
        return $data;
52
    }
53
54
    private static function convertChildren($children)
55
    {
56
        $list = new ArrayList();
57
58
        foreach ($children as $child) {
59
            $list->push(self::convertNode($child));
60
        }
61
62
        return $list;
63
    }
64
65
    /**
66
     * Get the field name to be used as content.
67
     * @return string
68
     */
69
    private function contentField()
70
    {
71
        $field = $this->owner->config()->get('content_field');
72
        return $field ? $field : 'Content';
73
    }
74
75
    /**
76
     * Provide content_field customization on a class basis.
77
     *
78
     * Override the default setOwner() method so, when valorized, I can
79
     * enhance the (possibly custom) content field with anchors. I did
80
     * not find a better way to override a field other than directly
81
     * substituting it with setField().
82
     *
83
     * @param Object $owner
84
     */
85
    public function setOwner($owner)
86
    {
87
        parent::setOwner($owner);
88
        if ($owner) {
89
            Hacks::addCallbackMethodToInstance(
90
                $owner,
91
                'getContent',
92
                function () use ($owner) {
93
                    return $owner->getContentField();
94
                }
95
            );
96
        }
97
    }
98
99
    /**
100
     * Return the internal Tocifier instance bound to $owner.
101
     *
102
     * If not present, try to create and execute a new one. On failure
103
     * (e.g. because of malformed content) no further attempts will be
104
     * made.
105
     *
106
     * @param DataObject $owner
107
     * @return Tocifier|false|null
108
     */
109
    private static function getTocifier($owner)
110
    {
111
        if (!$owner) {
112
            $tocifier = null;
113
        } elseif (isset(self::$tocifiers[$owner])) {
114
            $tocifier = self::$tocifiers[$owner];
115
        } else {
116
            $tocifier = Injector::inst()->create(
117
                'eNTiDi\Autotoc\Tocifier',
118
                $owner->getOriginalContentField()
119
            );
120
            $callback = $owner->config()->get('augment_callback');
121
            if (empty($callback)) {
122
                $callback = Config::inst()->get(self::class, 'augment_callback');
123
            }
124
            $tocifier->setAugmentCallback(explode('::', $callback));
125
            if (! $tocifier->process()) {
126
                $tocifier = false;
127
            }
128
            self::$tocifiers[$owner] = $tocifier;
129
        }
130
131
        return $tocifier;
132
    }
133
134
    /**
135
     * Clear the internal Autotoc cache.
136
     *
137
     * The TOC is usually cached the first time you call (directly or
138
     * indirectly) getAutotoc() or getContentField(). This method allows
139
     * to clear the internal cache to force a recomputation.
140
     */
141
    public function clearAutotoc()
142
    {
143
        unset(self::$tocifiers[$this->owner]);
144
    }
145
146
    /**
147
     * Get the automatically generated table of contents.
148
     * @return ArrayData|null
149
     */
150
    public function getAutotoc()
151
    {
152
        $tocifier = self::getTocifier($this->owner);
0 ignored issues
show
Documentation introduced by
$this->owner is of type object<SilverStripe\ORM\DataObject>, but the function expects a object<eNTiDi\Autotoc\DataObject>.

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...
153
        if (! $tocifier) {
154
            return null;
155
        }
156
157
        $toc = $tocifier->getTOC();
158
        if (empty($toc)) {
159
            return null;
160
        }
161
162
        return new ArrayData([
163
            'Children' => self::convertChildren($toc)
164
        ]);
165
    }
166
167
    /**
168
     * Get the non-augmented content field.
169
     * @return string
170
     */
171
    public function getOriginalContentField()
172
    {
173
        $model = $this->owner->getCustomisedObj();
174
        if (! $model) {
175
            $model = $this->owner->data();
176
        }
177
        if (! $model) {
178
            return null;
179
        }
180
181
        $field = $this->contentField();
182
        if (! $model->hasField($field)) {
183
            return null;
184
        }
185
186
        return $model->getField($field);
187
        return $model->obj($field)->forTemplate();
0 ignored issues
show
Unused Code introduced by
return $model->obj($field)->forTemplate(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
188
    }
189
190
    /**
191
     * Get the augmented content field.
192
     * @return string
193
     */
194
    public function getContentField()
195
    {
196
        $tocifier = self::getTocifier($this->owner);
0 ignored issues
show
Documentation introduced by
$this->owner is of type object<SilverStripe\ORM\DataObject>, but the function expects a object<eNTiDi\Autotoc\DataObject>.

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...
197
        if (!$tocifier) {
198
            return $this->getOriginalContentField();
199
        }
200
201
        return $tocifier->getHTML();
202
    }
203
204
    /**
205
     * I don't remember what the hell is this...
206
     * @return string
207
     */
208
    public function getBodyAutotoc()
209
    {
210
        return ' data-spy="scroll" data-target=".toc"';
211
    }
212
}
213