silverstripe-autotoc 

silverstripe-autotoc Git Source Tree

Root/src/Autotoc.php

1<?php
2
3namespace eNTiDi\Autotoc;
4
5use eNTiDi\Autotoc\Hacks;
6use SilverStripe\Core\Config\Config;
7use SilverStripe\Core\Injector\Injector;
8use SilverStripe\ORM\ArrayList;
9use SilverStripe\ORM\DataExtension;
10use SilverStripe\View\ArrayData;
11use SplObjectStorage;
12
13class 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;
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 \SilverStripe\ORM\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);
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();
188 }
189
190 /**
191 * Get the augmented content field.
192 * @return string
193 */
194 public function getContentField()
195 {
196 $tocifier = self::getTocifier($this->owner);
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}

Archive Download this file