|
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
|
|
|
|
|
17
|
|
|
/** |
|
18
|
|
|
* This plugin extends Twig to provide some useful functionalities: |
|
19
|
|
|
* |
|
20
|
|
|
* <li> |
|
21
|
|
|
* <b>Use configuration options in book contents</b> (instead of only in templates). |
|
22
|
|
|
* For example, in "chapter1.md" you can write "The title is {{ book.title }}". |
|
23
|
|
|
* This is mainly useful with user-defined configuration options. |
|
24
|
|
|
* |
|
25
|
|
|
* <li> |
|
26
|
|
|
* <b>itemtoc()</b> function automatically generates the Table of Contents of the current item. |
|
27
|
|
|
* The deep of the itemtoc can be tweaked by the configuration option <i>edition.itemtoc.deep</i>, |
|
28
|
|
|
* which defaults to one less than the main TOC deep <i>edition.toc.deep</i>. |
|
29
|
|
|
* It uses de <i>itemtoc.twig</i> template that must be available either as local or global template. |
|
30
|
|
|
* |
|
31
|
|
|
* <li> |
|
32
|
|
|
* <b>file()</b> function (deprecated, use fragment()) works like PHP's <i>include()</i>, |
|
33
|
|
|
* allowing the inclusion of another file into the current item. |
|
34
|
|
|
* The syntax is <i>file(filename, variables, options)</i> where "variables" and "options" are |
|
35
|
|
|
* optional hash tables where you can pass variables {'variable': 'value'} or |
|
36
|
|
|
* options {'nopagebreak': true} to the included file. |
|
37
|
|
|
* |
|
38
|
|
|
* <li> |
|
39
|
|
|
* <b>fragment()</b> function works like PHP's <i>include()</i>, allowing the inclusion of |
|
40
|
|
|
* another file into the current item. |
|
41
|
|
|
* |
|
42
|
|
|
* is a simplified version of <i>file()</i> without any variables or options. |
|
43
|
|
|
* The syntax is <i>fragment(filename)</i>. No page break will be inserted after the file contents. |
|
44
|
|
|
* |
|
45
|
|
|
* |
|
46
|
|
|
*/ |
|
47
|
|
|
class TwigExtensionPlugin extends BasePlugin implements EventSubscriberInterface |
|
48
|
|
|
{ |
|
49
|
1 |
|
public static function getSubscribedEvents() |
|
50
|
1 |
|
{ |
|
51
|
|
|
return array( |
|
52
|
1 |
|
EasybookEvents::PRE_PARSE => 'onItemPreParse', |
|
53
|
1 |
|
EasybookEvents::POST_PARSE => array('onItemPostParse', -1010) |
|
54
|
1 |
|
); |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
1 |
|
public function onItemPreParse(ParseEvent $event) |
|
58
|
|
|
{ |
|
59
|
1 |
|
$this->init($event); |
|
60
|
|
|
|
|
61
|
1 |
|
$content = $event->getItemProperty('original'); |
|
62
|
|
|
|
|
63
|
|
|
// replace "{#" to avoid problems with markdown extra syntax for ids in headers |
|
64
|
1 |
|
$content = str_replace('{#', '@%@&', $content); |
|
65
|
|
|
|
|
66
|
|
|
// replace configuration options on PreParse to take care of normal replacements |
|
67
|
|
|
// and the first pass of "itemtoc()" |
|
68
|
1 |
|
$content = $this->renderString($content); |
|
69
|
|
|
|
|
70
|
|
|
# replace back "{#" |
|
71
|
1 |
|
$content = str_replace('@%@&', '{#', $content); |
|
72
|
|
|
|
|
73
|
1 |
|
$event->setItemProperty('original', $content); |
|
74
|
1 |
|
} |
|
75
|
|
|
|
|
76
|
1 |
|
public function onItemPostParse(ParseEvent $event) |
|
77
|
|
|
{ |
|
78
|
1 |
|
$this->init($event); |
|
79
|
|
|
|
|
80
|
1 |
|
$content = $event->getItemProperty('content'); |
|
81
|
|
|
|
|
82
|
|
|
// ensure the Twig function call is not enclosed into a '<p>..</p>' tag |
|
83
|
|
|
// as it will result in epub checking erros |
|
84
|
1 |
|
$content = preg_replace('/<p>\s*{{/', '{{', $content); |
|
85
|
1 |
|
$content = preg_replace('/}}\s*<\/p>/', '}}', $content); |
|
86
|
|
|
|
|
87
|
|
|
// replace "{#" to avoid problems with markdown extra syntax for ids in headers |
|
88
|
1 |
|
$content = str_replace('{#', '@%@&', $content); |
|
89
|
|
|
|
|
90
|
|
|
// replace also in PostParse to process the second pass of "itemtoc()" ("itemtoc_internal()") |
|
91
|
1 |
|
$content = $this->renderString($content); |
|
92
|
|
|
|
|
93
|
|
|
# replace back "{#" |
|
94
|
1 |
|
$content = str_replace('@%@&', '{#', $content); |
|
95
|
|
|
|
|
96
|
1 |
|
$event->setItemProperty('content', $content); |
|
97
|
1 |
|
} |
|
98
|
|
|
|
|
99
|
1 |
|
protected function renderString($string, $variables = array()) |
|
100
|
|
|
{ |
|
101
|
|
|
// we need a new Twig String Renderer environment |
|
102
|
1 |
|
$twig = new \Twig_Environment(new \Twig_Loader_String(), $this->app['twig.options']); |
|
103
|
|
|
|
|
104
|
1 |
|
$this->addTwigGlobals($twig); |
|
105
|
1 |
|
$this->registerExtensions($twig); |
|
106
|
|
|
|
|
107
|
1 |
|
return $twig->render($string, $variables); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
1 |
|
protected function addTwigGlobals(\Twig_Environment $twig) |
|
111
|
|
|
{ |
|
112
|
1 |
|
$twig->addGlobal('app', $this->app); |
|
113
|
|
|
|
|
114
|
1 |
|
if (null !== $this->app['publishing.book.config']['book']) { |
|
115
|
1 |
|
$twig->addGlobal('book', $this->app['publishing.book.config']['book']); |
|
116
|
|
|
|
|
117
|
1 |
|
$publishingEdition = $this->edition; |
|
118
|
1 |
|
$editions = $this->app->book('editions'); |
|
119
|
1 |
|
$twig->addGlobal('edition', $editions[$publishingEdition]); |
|
120
|
1 |
|
} |
|
121
|
|
|
|
|
122
|
1 |
|
$twig->addGlobal('item', $this->item); |
|
123
|
1 |
|
} |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* Register all the extensions here. |
|
127
|
|
|
*/ |
|
128
|
1 |
|
protected function registerExtensions(\Twig_Environment $twig) |
|
129
|
|
|
{ |
|
130
|
|
|
// file() |
|
131
|
1 |
|
$twig->addFunction(new \Twig_SimpleFunction('file', array($this, 'fileFunction'))); |
|
132
|
|
|
|
|
133
|
|
|
// fragment() |
|
134
|
1 |
|
$twig->addFunction(new \Twig_SimpleFunction('fragment', array($this, 'fragmentFunction'))); |
|
135
|
|
|
|
|
136
|
|
|
// itemtoc() and its internal counterpart |
|
137
|
1 |
|
$twig->addFunction(new \Twig_SimpleFunction('itemtoc', array($this, 'itemTocFunction'))); |
|
138
|
1 |
|
$twig->addFunction( |
|
139
|
1 |
|
new \Twig_SimpleFunction('_itemtoc_internal', array($this, 'itemTocInternalFunction')) |
|
140
|
1 |
|
); |
|
141
|
1 |
|
} |
|
142
|
|
|
|
|
143
|
|
|
/** |
|
144
|
|
|
* Twig function: <b>file(filename, variables, options)</b> |
|
145
|
|
|
* |
|
146
|
|
|
* @deprecated |
|
147
|
|
|
* |
|
148
|
|
|
* @param string $filename to be included (relative to book Contents dir) |
|
149
|
|
|
* @param array $variables to be passed to the template |
|
150
|
|
|
* @param array $options (default: 'nopagebreak: false' => add a page break after the included text) |
|
151
|
|
|
* |
|
152
|
|
|
* @return string included text with all the replacements done. |
|
153
|
|
|
*/ |
|
154
|
1 |
|
public function fileFunction($filename, $variables = array(), $options = array()) |
|
155
|
|
|
{ |
|
156
|
1 |
|
$dir = $this->app['publishing.dir.contents']; |
|
157
|
1 |
|
$file = $dir . '/' . $filename; |
|
158
|
|
|
|
|
159
|
1 |
View Code Duplication |
if (!file_exists($file)) { |
|
|
|
|
|
|
160
|
|
|
$this->writeLn( |
|
161
|
|
|
sprintf('Included content file "%s" not found in "%s"', $filename, $this->item['config']['content']), |
|
162
|
|
|
'error' |
|
163
|
|
|
); |
|
164
|
|
|
|
|
165
|
|
|
return $filename; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
1 |
|
$rendered = $this->renderString(file_get_contents($file), $variables); |
|
169
|
|
|
|
|
170
|
|
|
// pagebreak is added by default |
|
171
|
1 |
|
$addPageBreak = !isset($options['nopagebreak']) || (isset($options['nopagebreak']) && !$options['nopagebreak']); |
|
172
|
1 |
|
if ($addPageBreak) { |
|
173
|
1 |
|
$rendered .= '<div class="page-break"></div>'; |
|
174
|
1 |
|
} |
|
175
|
|
|
|
|
176
|
1 |
|
return $rendered; |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* Twig function: <b>fragment(filename)</b> |
|
181
|
|
|
* |
|
182
|
|
|
* @param string $filename to be included (relative to book Contents dir) |
|
183
|
|
|
* |
|
184
|
|
|
* @param array $variables to be passed to the template (example: {'name': 'John'} ) |
|
185
|
|
|
* @param array $options (example {'pagebreak' => true} will add a page break after the included text) |
|
186
|
|
|
* |
|
187
|
|
|
* @return string included text with all the replacements done. |
|
188
|
|
|
*/ |
|
189
|
|
|
public function fragmentFunction($filename, $variables = array(), $options = array()) |
|
190
|
|
|
{ |
|
191
|
|
|
$dir = $this->app['publishing.dir.contents']; |
|
192
|
|
|
$file = $dir . '/' . $filename; |
|
193
|
|
|
|
|
194
|
|
View Code Duplication |
if (!file_exists($file)) { |
|
|
|
|
|
|
195
|
|
|
$this->writeLn( |
|
196
|
|
|
sprintf('Fragment file "%s" not found in "%s"', $filename, $this->item['config']['content']), |
|
197
|
|
|
'error' |
|
198
|
|
|
); |
|
199
|
|
|
|
|
200
|
|
|
return $filename; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
$rendered = $this->renderString(file_get_contents($file), $variables); |
|
204
|
|
|
|
|
205
|
|
|
// add pagebreak if asked |
|
206
|
|
|
if (isset($options['pagebreak']) && $options['pagebreak']) { |
|
207
|
|
|
$rendered .= '<div class="page-break"></div>'; |
|
208
|
|
|
} |
|
209
|
|
|
|
|
210
|
|
|
return $rendered; |
|
211
|
|
|
} |
|
212
|
|
|
|
|
213
|
|
|
/** |
|
214
|
|
|
* Twig function: <b>itemtoc()</b> |
|
215
|
|
|
* |
|
216
|
|
|
* @return string The _itemtoc_internal() function call |
|
217
|
|
|
* |
|
218
|
|
|
* Generating the item toc requires two phases to ensure that included files got parsed after being |
|
219
|
|
|
* included (with file()). The second phase does the actual rendering of the itemtoc |
|
220
|
|
|
* template. |
|
221
|
|
|
*/ |
|
222
|
1 |
|
public function itemTocFunction() |
|
223
|
|
|
{ |
|
224
|
1 |
|
return '{{ _itemtoc_internal() }}'; |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
/** |
|
228
|
|
|
* Twig function: <b>_itemtoc_internal()</b> |
|
229
|
|
|
* <b>This is an internal function so is not to be used directly in content files.</b> |
|
230
|
|
|
* It will be invoked on itemPostParse, after all the file() functions have been resolved and all |
|
231
|
|
|
* the included item contents have been parsed. |
|
232
|
|
|
* |
|
233
|
|
|
* @uses Configuration option <i>edition.plugins.TwigExtension.itemtoc.deep</i> |
|
234
|
|
|
* (default: <i>edition.toc.deep + 1 </i> |
|
235
|
|
|
* |
|
236
|
|
|
* @return string The item toc rendered |
|
237
|
|
|
* |
|
238
|
|
|
*/ |
|
239
|
1 |
|
public function itemTocInternalFunction() |
|
240
|
|
|
{ |
|
241
|
1 |
|
$template = 'itemtoc.twig'; |
|
242
|
|
|
|
|
243
|
|
|
// note that we need to use the normal Twig template renderer, not our Twig string renderer |
|
244
|
1 |
|
$twig = $this->app['twig']; |
|
245
|
1 |
|
$this->addTwigGlobals($twig); |
|
246
|
|
|
|
|
247
|
1 |
|
$itemtoc_deep = $this->getEditionOption( |
|
248
|
1 |
|
'plugins.options.TwigExtension.itemtoc.deep', |
|
249
|
1 |
|
$this->getEditionOption('toc.deep') + 1 |
|
250
|
1 |
|
); |
|
251
|
|
|
|
|
252
|
1 |
|
$rendered = $twig->render($template, array('itemtoc_deep' => $itemtoc_deep)); |
|
253
|
|
|
|
|
254
|
1 |
|
return $rendered; |
|
255
|
|
|
} |
|
256
|
|
|
} |
|
257
|
|
|
|
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.