Autotoc::__construct()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2
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 5
    public function __construct()
33
    {
34 5
        parent::__construct();
35 5
        if (empty(self::$tocifiers)) {
36 1
            self::$tocifiers = new SplObjectStorage();
37 1
        }
38 5
    }
39
40 2
    private static function convertNode($node)
41
    {
42 2
        $data = new ArrayData([
43 2
            'Id'    => $node['id'],
44 2
            'Title' => $node['title']
45 2
        ]);
46
47 2
        if (isset($node['children'])) {
48 2
            $data->setField('Children', self::convertChildren($node['children']));
49 2
        }
50
51 2
        return $data;
52
    }
53
54 2
    private static function convertChildren($children)
55
    {
56 2
        $list = new ArrayList();
57
58 2
        foreach ($children as $child) {
59 2
            $list->push(self::convertNode($child));
60 2
        }
61
62 2
        return $list;
63
    }
64
65
    /**
66
     * Get the field name to be used as content.
67
     * @return string
68
     */
69 5
    private function contentField()
70
    {
71 5
        $field = $this->owner->config()->get('content_field');
72 5
        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 5
    public function setOwner($owner)
86
    {
87 5
        parent::setOwner($owner);
88 5
        if ($owner) {
89 5
            Hacks::addCallbackMethodToInstance(
90 5
                $owner,
91 5
                'get'.$this->contentField(),
92 3
                function () use ($owner) {
93 3
                    return $owner->getContentField();
94
                }
95 5
            );
96 5
        }
97 5
    }
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 \SilverStripe\ORM\DataObject $owner
107
     * @return Tocifier|false|null
108
     */
109 3
    private static function getTocifier($owner)
110
    {
111 3
        if (!$owner) {
112
            $tocifier = null;
113 3
        } elseif (isset(self::$tocifiers[$owner])) {
114 3
            $tocifier = self::$tocifiers[$owner];
115 3
        } else {
116 3
            $tocifier = Injector::inst()->create(
117 3
                'eNTiDi\Autotoc\Tocifier',
118 3
                $owner->getOriginalContentField()
119 3
            );
120 3
            $callback = $owner->config()->get('augment_callback');
121 3
            if (empty($callback)) {
122
                $callback = Config::inst()->get(self::class, 'augment_callback');
123
            }
124 3
            $tocifier->setAugmentCallback(explode('::', $callback));
125 3
            if (!$tocifier->process()) {
126 1
                $tocifier = false;
127 1
            }
128 3
            self::$tocifiers[$owner] = $tocifier;
129
        }
130
131 3
        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 2
    public function clearAutotoc()
142
    {
143 2
        unset(self::$tocifiers[$this->owner]);
144 2
    }
145
146
    /**
147
     * Get the automatically generated table of contents.
148
     * @return ArrayData|null
149
     */
150 2
    public function getAutotoc()
151
    {
152 2
        $tocifier = self::getTocifier($this->owner);
153 2
        if (!$tocifier) {
154 1
            return null;
155
        }
156
157 2
        $toc = $tocifier->getTOC();
158 2
        if (empty($toc)) {
159 1
            return null;
160
        }
161
162 2
        return new ArrayData([
163 2
            'Children' => self::convertChildren($toc)
164 2
        ]);
165
    }
166
167
    /**
168
     * Get the non-augmented content field.
169
     * @return string
170
     */
171 4
    public function getOriginalContentField()
172
    {
173 4
        $model = $this->owner->getCustomisedObj();
174 4
        if (!$model) {
175 4
            $model = $this->owner->data();
176 4
        }
177 4
        if (!$model) {
178
            return null;
179
        }
180
181 4
        $field = $this->contentField();
182 4
        if (!$model->hasField($field)) {
183
            return null;
184
        }
185
186 4
        return $model->getField($field);
187
    }
188
189
    /**
190
     * Get the augmented content field.
191
     * @return string
192
     */
193 3
    public function getContentField()
194
    {
195 3
        $tocifier = self::getTocifier($this->owner);
196 3
        if (!$tocifier) {
197
            return $this->getOriginalContentField();
198
        }
199
200 3
        return $tocifier->getHTML();
201
    }
202
203
    /**
204
     * I don't remember what the hell is this...
205
     * @return string
206
     */
207 1
    public function getBodyAutotoc()
208
    {
209 1
        return ' data-spy="scroll" data-target=".toc"';
210
    }
211
}
212