GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — develop ( 3a1348...4e3bf1 )
by Miguel Angel
05:43
created

removeUnneededFootnotesItem()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.3244

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 24
ccs 8
cts 11
cp 0.7272
rs 8.6845
cc 4
eloc 9
nc 4
nop 0
crap 4.3244
1
<?php
2
/*
3
 * This file is part of the trefoil application.
4
 *
5
 * (c) Miguel Angel Gabriel <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Trefoil\Plugins\Optional;
11
12
use Easybook\Events\EasybookEvents;
13
use Easybook\Events\ParseEvent;
14
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15
use Trefoil\Plugins\BasePlugin;
16
use Trefoil\Util\Toolkit;
17
18
/**
19
 * This plugin extends footnotes to support several formats.
20
 *
21
 * Options are specified on an per-edition basis:
22
 *
23
 *     editions:
24
 *         <edition-name>
25
 *             plugins:
26
 *                 ...
27
 *                 options:
28
 *                     FootnotesExtend:
29
 *                         type: end  # [end, inject, item, inline]
30
 *
31
 * Where:
32
 *
33
 * - type 'end': This is the normal Markdown-rendered footnotes.
34
 *   They will be at the end of each book item, separated by a <hr/> tag.
35
 *   This is the default.
36
 *
37
 * - type 'inject: This is a variant of type 'end', where each item's
38
 *   footnotes will be injected to a certain injection point.
39
 *   Just write '<div class="footnotes"></div>' anywhere in each item
40
 *   where the footnotes should be injected.
41
 *
42
 * - type 'item': All the footnotes in the book will be collected and
43
 *   rendered in a separated item called 'footnotes' that need to
44
 *   exist in the book.
45
 *
46
 * - type 'inline: PrinceXML support inline footnotes, where the text
47
 *   of the note must be inlined into the text, instead of just a
48
 *   reference. Prince will manage the numbering.
49
 *
50
 *   Note that Prince manages footnotes as:
51
 *
52
 *      "text<span class="fn">Text of the footnote</span> more text"
53
 *
54
 *   One limitation is that the footnote text cannot contain block
55
 *   elements (as paragraphs, tables, lists). The plugin overcomes this
56
 *   partially by replacing paragraph tags with <br/> tags.
57
 */
58
class FootnotesExtendPlugin extends BasePlugin implements EventSubscriberInterface
59
{
60
    const FOOTNOTES_TYPE_END = 'end';
61
    const FOOTNOTES_TYPE_ITEM = 'item';
62
    const FOOTNOTES_TYPE_INJECT = 'inject';
63
    const FOOTNOTES_TYPE_INLINE = 'inline';
64
65
    /**
66
     * @var string Type of footnotes to generate
67
     */
68
    protected $footnotesType = '';
69
70
    /**
71
     * @var array The extracted footnotes the current book item
72
     */
73
    protected $footnotesCurrentItem = array();
74
75
    /**
76
     * @var string The current item footnotes (as text)
77
     */
78
    protected $itemFootnotesText = '';
79
80
    /* ********************************************************************************
81
     * Event handlers
82
     * ********************************************************************************
83
     */
84 4
    public static function getSubscribedEvents()
85
    {
86
        return array(
87 4
            EasybookEvents::POST_PARSE => array('onItemPostParse')
88 4
        );
89
    }
90
91 4
    public function onItemPostParse(ParseEvent $event)
92
    {
93 4
        $this->init($event);
94
95 4
        $this->processItem();
96
97
        // reload changed item
98 4
        $event->setItem($this->item);
99 4
    }
100
101
    /* ********************************************************************************
102
     * Implementation
103
     * ********************************************************************************
104
     */
105
106
    /**
107
     * Process a content item
108
     */
109 4
    protected function processItem()
110
    {
111
        // lazy initialize
112 4
        if (!isset($this->app['publishing.footnotes.items'])) {
113 4
            $this->app['publishing.footnotes.items'] = array();
114 4
        }
115
116 4
        $this->footnotesCurrentItem = array();
117
118
        // options
119 4
        $this->footnotesType = $this->getEditionOption('plugins.options.FootnotesExtend.type', 'end');
120
121 4
        $this->fixFootnotes();
122
123 4
        switch ($this->footnotesType) {
124 4
            case self::FOOTNOTES_TYPE_END:
125
                // nothing else to do
126 1
                break;
127
128 3
            case self::FOOTNOTES_TYPE_INLINE:
129
130 1
                $this->extractFootnotes();
131 1
                $this->inlineFootnotes();
132 1
                break;
133
134 2
            case self::FOOTNOTES_TYPE_ITEM:
135
136 1
                $this->extractFootnotes();
137 1
                $this->renumberReferences();
138 1
                break;
139
140 1
            case self::FOOTNOTES_TYPE_INJECT:
141
142 1
                $this->saveInjectionTarget();
143 1
                $this->extractFootnotes();
144 1
                $this->restoreInjectionTarget();
145 1
                $this->injectFootnotes();
146 1
                break;
147 4
        }
148
149
        // look if we need to remove the footnotes book item
150 4
        $this->removeUnneededFootnotesItem();
151 4
    }
152
153
    /**
154
     * Replace character ':' by '-' in footnotes ids because epubcheck does not like it.
155
     */
156 4
    protected function fixFootnotes()
157
    {
158 4
        $content = $this->item['content'];
159
160
        // fix footnotes ref in text
161 4
        $content = preg_replace('/id="fnref(\d*):/', 'id="fnref$1-', $content);
162 4
        $content = str_replace('href="#fn:', 'href="#fn-', $content);
163
164
        // fix double class in footnote ref (note the ungreedy modifier)
165 4
        $content = preg_replace(
166 4
            '/<a class="internal"(.*) class="footnote-ref">/U', 
167 4
            '<a class="footnote-ref internal" $1>', $content);
168
        
169
        // fix footnotes
170 4
        $content = str_replace('id="fn:', 'id="fn-', $content);
171 4
        $content = preg_replace('/href="#fnref(\d*):/', 'href="#fnref$1-', $content);
172
173
        // fix return sign used
174 4
        $content = str_replace('&#8617;', '[&crarr;]', $content);
175
176 4
        $this->item['content'] = $content;
177 4
    }
178
179
    /**
180
     * Extracts anc collects all footnotes in the item.
181
     */
182 3
    protected function extractFootnotes()
183
    {
184 3
        $content = $this->item['content'];
185
186 3
        $this->itemFootnotesText = '';
187
188 3
        $regExp = '/';
189 3
        $regExp .= '<div class="footnotes">.*<ol>(?<fns>.*)<\/ol>.*<\/div>';
190 3
        $regExp .= '/Ums'; // Ungreedy, multiline, dotall
191
192
        // PHP 5.3 compat
193 3
        $me = $this;
194
195 3
        $content = preg_replace_callback(
196 3
            $regExp,
197
            function ($matches) use ($me) {
198
199 3
                $this->itemFootnotesText = $matches[0];
200
201 3
                $regExp2 = '/';
202 3
                $regExp2 .= '<li.*id="(?<id>.*)">.*';
203 3
                $regExp2 .= '<p>(?<text>.*)&#160;<a .*href="#(?<backref>.*)"';
204 3
                $regExp2 .= '/Ums'; // Ungreedy, multiline, dotall
205
206 3
                preg_match_all($regExp2, $matches[0], $matches2, PREG_SET_ORDER);
207
208 3
                if ($matches2) {
209 3
                    foreach ($matches2 as $match2) {
210
                        $footnote = array(
211 3
                            'item'       => $this->item['toc'][0]['slug'],
212 3
                            'text'       => $match2['text'],
213 3
                            'id'         => $match2['id'],
214 3
                            'text'       => $match2['text'],
215 3
                            'backref'    => $match2['backref'],
216 3
                            'new_number' => count($this->app['publishing.footnotes.items']) + 1
217 3
                        );
218
219
                        // save for current item
220 3
                        $this->footnotesCurrentItem[$match2['id']] = $footnote;
221
222
                        // save for all items
223 3
                        $footnotes = $this->app['publishing.footnotes.items'];
224 3
                        $footnotes[$match2['id']] = $footnote;
225 3
                        $this->app['publishing.footnotes.items'] = $footnotes;
226 3
                    }
227 3
                }
228
229 3
                return '';
230 3
            },
231
            $content
232 3
        );
233
234 3
        $this->item['content'] = $content;
235 3
    }
236
237
    /**
238
     * Inline footnotes in the text, after the note reference.
239
     *
240
     * This is only useful for renderers that support automatic
241
     * inline footnotes, like PrinceXML.
242
     */
243 1 View Code Duplication
    protected function inlineFootnotes()
244
    {
245 1
        $content = $this->item['content'];
246
247 1
        $regExp = '/';
248 1
        $regExp .= '<sup id="(?<supid>fnref.?-.*)">';
249 1
        $regExp .= '<a(?<prev>.*)href="#(?<href>fn-.*)"(?<post>.*)>(?<number>.*)<\/a><\/sup>';
250 1
        $regExp .= '/Ums'; // Ungreedy, multiline, dotall
251
252
        // PHP 5.3 compat
253 1
        $me = $this;
254
255 1
        $content = preg_replace_callback(
256 1
            $regExp,
257
            function ($matches) use ($me) {
258 1
                $footnotes = $me->footnotesCurrentItem;
259 1
                $footnote = $footnotes[$matches['href']];
260 1
                $text = $footnote['text'];
261
262
                // replace <p>...</p> with <br/> because no block elements are 
263
                // allowed inside a <span>.
264
                // The paragraph contents are also put inside a fake paragraph <span>
265
                // so they can be styled.
266
267 1
                $text = str_replace(
268 1
                    ['<p>', '</p>'],
269 1
                    ['<span class="p">', '<br/></span>'],
270
                    $text
271 1
                );
272 1
                $text = '<span class="p" >' . $text . '</span>';
273
274 1
                $html = sprintf(
275 1
                    '<span class="fn">%s</span>',
276
                    $text
277 1
                );
278
279 1
                return $html;
280 1
            },
281
            $content
282 1
        );
283
284 1
        $this->item['content'] = $content;
285 1
    }
286
287
    /**
288
     * Renumber all footnotes references to be correlative for the whole book.
289
     */
290 1
    protected function renumberReferences()
291
    {
292 1
        $content = $this->item['content'];
293
294 1
        $regExp = '/';
295 1
        $regExp .= '<sup id="(?<supid>fnref.?-.*)">';
296 1
        $regExp .= '<a(?<prev>.*)href="#(?<href>fn-.*)"(?<post>.*)>(?<number>.*)<\/a>';
297 1
        $regExp .= '/Ums'; // Ungreedy, multiline, dotall
298
299
        // PHP 5.3 compat
300 1
        $me = $this;
301
302 1
        $content = preg_replace_callback(
303 1
            $regExp,
304
            function ($matches) use ($me) {
305 1
                $newNumber = $this->app['publishing.footnotes.items'][$matches['href']]['new_number'];
306
307 1
                $html = sprintf(
308 1
                    '<sup id="%s"><a%shref="#%s"%s>%s</a>',
309 1
                    $matches['supid'],
310 1
                    $matches['prev'],
311 1
                    $matches['href'],
312 1
                    $matches['post'],
313
                    $newNumber
314 1
                );
315
316 1
                return $html;
317 1
            },
318
            $content
319 1
        );
320
321 1
        $this->item['content'] = $content;
322 1
    }
323
324
    /**
325
     * Replace the footnotes injection target to keep extractFootnotes() from removing it.
326
     */
327 1 View Code Duplication
    protected function saveInjectionTarget()
328
    {
329 1
        $content = $this->item['content'];
330
331 1
        $content = preg_replace('/<div class="footnotes">\s*<\/div>/', '<div class="__footnotes"></div>', $content);
332
333 1
        $this->item['content'] = $content;
334 1
    }
335
336
    /**
337
     * Restore the injection target
338
     */
339 1 View Code Duplication
    protected function restoreInjectionTarget()
340
    {
341 1
        $content = $this->item['content'];
342
343 1
        $content = preg_replace('/<div class="__footnotes">\s*<\/div>/', '<div class="footnotes"></div>', $content);
344
345 1
        $this->item['content'] = $content;
346 1
    }
347
348
    /**
349
     * Inject footnotes at the injection target.
350
     *
351
     * The injection target is a '<div class="footnotes"></div>' placed anywhere in the item text.
352
     */
353 1
    protected function injectFootnotes()
354
    {
355 1
        $content = $this->item['content'];
356
357 1
        $regExp = '/';
358 1
        $regExp .= '<div class="footnotes">\s*<\/div>';
359 1
        $regExp .= '/Ums'; // Ungreedy, multiline, dotall
360
361
        // PHP 5.3 compat
362 1
        $me = $this;
363
364 1
        $content = preg_replace_callback(
365 1
            $regExp,
366 1
            function ($matches) use ($me) {
0 ignored issues
show
Unused Code introduced by
The parameter $matches is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
367
368 1
                $footnotes = $this->app->render(
369 1
                    '_footnotes.twig',
370 1
                    array('footnotes' => $this->footnotesCurrentItem)
371 1
                );
372
373 1
                return Toolkit::renderHTMLTag(
374 1
                    'div',
375 1
                    $footnotes,
376 1
                    array('class' => 'footnotes')
377 1
                );
378 1
            },
379
            $content
380 1
        );
381
382 1
        $this->item['content'] = $content;
383 1
    }
384
385
    /**
386
     * Ensure the footnotes item is removed from the book if it is not needed.
387
     */
388 4
    protected function removeUnneededFootnotesItem()
389
    {
390
        // only for footnotes item
391 4
        if ($this->item['config']['element'] !== 'footnotes') {
392 4
            return;
393
        }
394
395
        // instruct the publisher to remove 'footnotes' item from book
396
        // if footnotes type is not 'item'
397 4
        if ($this->footnotesType !== self::FOOTNOTES_TYPE_ITEM) {
398
399 3
            $this->item['remove'] = true;
400
401 3
            return;
402
        }
403
404
        // instruct the publisher to remove 'footnotes' item from book
405
        // if footnotes type is 'item' but not footnotes
406 1
        if (count($this->app['publishing.footnotes.items']) == 0) {
407
            $this->item['remove'] = true;
408
            $this->writeLn("No footnotes found in text.", 'info');
409
        }
410
411 1
    }
412
}
413