Timber - Version 0.21.9

Version Description

  • Much much much more inline docs
  • Fix to TimberComment::approved()
  • HHVM support confirmed (it always worked, but now the tests prove it)
  • Fixes to multisite handling of themes
  • Fix to comments pagination (thanks @newkind)
Download this release

Release Info

Developer jarednova
Plugin Icon 128x128 Timber
Version 0.21.9
Comparing to
See all releases

Code changes from version 0.21.8 to 0.21.9

Files changed (127) hide show
  1. README.md +12 -1
  2. lib/cache/TimberKeyGeneratorInterface.php +3 -2
  3. lib/timber-archives.php +251 -243
  4. lib/timber-comment.php +18 -6
  5. lib/timber-helper.php +26 -23
  6. lib/timber-image.php +93 -26
  7. lib/timber-loader.php +1 -0
  8. lib/timber-menu-item.php +90 -49
  9. lib/timber-menu.php +72 -6
  10. lib/timber-post-getter.php +1 -1
  11. lib/timber-post.php +520 -220
  12. lib/timber-site.php +31 -8
  13. lib/timber-term.php +364 -327
  14. lib/timber-theme.php +115 -54
  15. lib/timber-twig.php +10 -27
  16. lib/timber-user.php +13 -2
  17. readme.txt +10 -3
  18. timber-starter-theme/search.php +1 -0
  19. timber.php +3 -2
  20. vendor/autoload.php +1 -1
  21. vendor/composer/ClassLoader.php +26 -0
  22. vendor/composer/LICENSE +21 -0
  23. vendor/composer/autoload_real.php +4 -4
  24. vendor/composer/installed.json +7 -7
  25. vendor/twig/twig/CHANGELOG +19 -0
  26. vendor/twig/twig/composer.json +1 -1
  27. vendor/twig/twig/doc/advanced.rst +21 -0
  28. vendor/twig/twig/doc/deprecated.rst +21 -0
  29. vendor/twig/twig/doc/filters/batch.rst +6 -0
  30. vendor/twig/twig/doc/functions/source.rst +11 -0
  31. vendor/twig/twig/doc/internals.rst +1 -1
  32. vendor/twig/twig/doc/intro.rst +5 -1
  33. vendor/twig/twig/doc/tags/macro.rst +3 -0
  34. vendor/twig/twig/doc/tags/use.rst +1 -1
  35. vendor/twig/twig/doc/templates.rst +5 -2
  36. vendor/twig/twig/ext/twig/php_twig.h +1 -1
  37. vendor/twig/twig/ext/twig/twig.c +33 -5
  38. vendor/twig/twig/lib/Twig/BaseNodeVisitor.php +62 -0
  39. vendor/twig/twig/lib/Twig/Environment.php +15 -13
  40. vendor/twig/twig/lib/Twig/ExpressionParser.php +7 -1
  41. vendor/twig/twig/lib/Twig/Extension/Core.php +52 -38
  42. vendor/twig/twig/lib/Twig/Extension/Debug.php +1 -1
  43. vendor/twig/twig/lib/Twig/Extension/Sandbox.php +1 -1
  44. vendor/twig/twig/lib/Twig/Filter.php +5 -4
  45. vendor/twig/twig/lib/Twig/Filter/Function.php +1 -0
  46. vendor/twig/twig/lib/Twig/Filter/Method.php +1 -0
  47. vendor/twig/twig/lib/Twig/Filter/Node.php +1 -0
  48. vendor/twig/twig/lib/Twig/FilterCallableInterface.php +1 -0
  49. vendor/twig/twig/lib/Twig/FilterInterface.php +1 -0
  50. vendor/twig/twig/lib/Twig/Function.php +3 -2
  51. vendor/twig/twig/lib/Twig/Function/Function.php +1 -0
  52. vendor/twig/twig/lib/Twig/Function/Method.php +1 -0
  53. vendor/twig/twig/lib/Twig/Function/Node.php +1 -0
  54. vendor/twig/twig/lib/Twig/FunctionCallableInterface.php +1 -0
  55. vendor/twig/twig/lib/Twig/FunctionInterface.php +1 -0
  56. vendor/twig/twig/lib/Twig/Lexer.php +24 -24
  57. vendor/twig/twig/lib/Twig/LoaderInterface.php +3 -3
  58. vendor/twig/twig/lib/Twig/Node/CheckSecurity.php +3 -3
  59. vendor/twig/twig/lib/Twig/Node/Embed.php +2 -2
  60. vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php +3 -3
  61. vendor/twig/twig/lib/Twig/Node/Expression/Call.php +39 -2
  62. vendor/twig/twig/lib/Twig/Node/Expression/Filter.php +3 -0
  63. vendor/twig/twig/lib/Twig/Node/Expression/Function.php +3 -0
  64. vendor/twig/twig/lib/Twig/Node/Expression/Name.php +1 -1
  65. vendor/twig/twig/lib/Twig/Node/Expression/Parent.php +3 -3
  66. vendor/twig/twig/lib/Twig/Node/Expression/Test.php +3 -0
  67. vendor/twig/twig/lib/Twig/Node/For.php +1 -1
  68. vendor/twig/twig/lib/Twig/Node/If.php +1 -1
  69. vendor/twig/twig/lib/Twig/Node/Import.php +2 -2
  70. vendor/twig/twig/lib/Twig/Node/Include.php +2 -2
  71. vendor/twig/twig/lib/Twig/Node/Macro.php +43 -16
  72. vendor/twig/twig/lib/Twig/Node/Module.php +18 -18
  73. vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php +5 -15
  74. vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php +8 -8
  75. vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php +18 -3
  76. vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php +5 -15
  77. vendor/twig/twig/lib/Twig/Parser.php +15 -6
  78. vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php +1 -1
  79. vendor/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php +3 -3
  80. vendor/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php +3 -3
  81. vendor/twig/twig/lib/Twig/Profiler/Profile.php +1 -1
  82. vendor/twig/twig/lib/Twig/SimpleFilter.php +12 -6
  83. vendor/twig/twig/lib/Twig/SimpleFunction.php +10 -4
  84. vendor/twig/twig/lib/Twig/SimpleTest.php +6 -0
  85. vendor/twig/twig/lib/Twig/Template.php +38 -3
  86. vendor/twig/twig/lib/Twig/TemplateInterface.php +2 -2
  87. vendor/twig/twig/lib/Twig/Test.php +1 -0
  88. vendor/twig/twig/lib/Twig/Test/Function.php +1 -0
  89. vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php +12 -4
  90. vendor/twig/twig/lib/Twig/Test/Method.php +1 -0
  91. vendor/twig/twig/lib/Twig/Test/Node.php +1 -0
  92. vendor/twig/twig/lib/Twig/TestCallableInterface.php +1 -0
  93. vendor/twig/twig/lib/Twig/TestInterface.php +1 -0
  94. vendor/twig/twig/lib/Twig/Token.php +15 -15
  95. vendor/twig/twig/lib/Twig/TokenParser.php +1 -1
  96. vendor/twig/twig/lib/Twig/TokenParser/From.php +4 -0
  97. vendor/twig/twig/lib/Twig/TokenParserBroker.php +1 -0
  98. vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php +1 -0
  99. vendor/twig/twig/lib/Twig/TokenParserInterface.php +1 -1
  100. vendor/twig/twig/lib/Twig/TokenStream.php +7 -7
  101. vendor/twig/twig/test/Twig/Tests/CompilerTest.php +1 -1
  102. vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php +3 -3
  103. vendor/twig/twig/test/Twig/Tests/ErrorTest.php +1 -1
  104. vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php +10 -10
  105. vendor/twig/twig/test/Twig/Tests/Extension/SandboxTest.php +6 -6
  106. vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_template_in_child_template.test +15 -0
  107. vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_keys.test +10 -0
  108. vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_zero_elements.test +10 -0
  109. vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling.test +16 -0
  110. vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test +13 -0
  111. vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs.test +21 -0
  112. vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs_argument.test +8 -0
  113. vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test +9 -0
  114. vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test +11 -0
  115. vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test +10 -0
  116. vendor/twig/twig/test/Twig/Tests/IntegrationTest.php +2 -2
  117. vendor/twig/twig/test/Twig/Tests/LexerTest.php +2 -2
  118. vendor/twig/twig/test/Twig/Tests/NativeExtensionTest.php +2 -2
  119. vendor/twig/twig/test/Twig/Tests/Node/Expression/CallTest.php +15 -1
  120. vendor/twig/twig/test/Twig/Tests/Node/Expression/FilterTest.php +34 -1
  121. vendor/twig/twig/test/Twig/Tests/Node/Expression/FunctionTest.php +24 -1
  122. vendor/twig/twig/test/Twig/Tests/Node/Expression/TestTest.php +26 -0
  123. vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php +10 -1
  124. vendor/twig/twig/test/Twig/Tests/ParserTest.php +1 -1
  125. vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/AbstractTest.php +3 -3
  126. vendor/twig/twig/test/Twig/Tests/TemplateTest.php +54 -40
  127. vendor/twig/twig/test/Twig/Tests/escapingTest.php +100 -100
README.md CHANGED
@@ -8,6 +8,9 @@ By Jared Novack (<a href="http://twitter.com/jarednova">@JaredNova</a>) and <a h
8
  [![Coverage Status](https://coveralls.io/repos/jarednova/timber/badge.svg?branch=master)](https://coveralls.io/r/jarednova/timber?branch=master)
9
  [![Dependency Status](https://www.versioneye.com/user/projects/54e3c717d1ec5734f4000099/badge.svg?style=flat)](https://www.versioneye.com/user/projects/54e3c717d1ec5734f4000099)
10
  [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jarednova/timber/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jarednova/timber/?branch=master)
 
 
 
11
 
12
  ### Because WordPress is awesome, but the_loop isn't
13
  Timber helps you create fully-customized WordPress themes faster with more sustainable code. With Timber, you write your HTML using the [Twig Template Engine](http://twig.sensiolabs.org/) separate from your PHP files.
@@ -69,10 +72,18 @@ Timber is great for any WordPress developer who cares about writing good, mainta
69
  * [**Twig**](https://github.com/fabpot/Twig) The template language used by Timber.
70
 
71
  #### Should I use it?
72
- It's GPL-licensed, so please use in personal or commercial work. Just don't re-sell it. While Timber is still in development, it's also in-use on [hundreds of sites](http://jarednova.github.io/timber/#showcase). While much has been stabilized since the first major push back in June 2013, you should expect some breaking changes as development progresses towards a version 1.0.
73
 
74
  #### Contributing
75
  Read the [contributor guidelines](https://github.com/jarednova/timber/wiki#contributing) in the wiki.
76
 
 
 
 
 
 
 
 
 
77
 
78
 
8
  [![Coverage Status](https://coveralls.io/repos/jarednova/timber/badge.svg?branch=master)](https://coveralls.io/r/jarednova/timber?branch=master)
9
  [![Dependency Status](https://www.versioneye.com/user/projects/54e3c717d1ec5734f4000099/badge.svg?style=flat)](https://www.versioneye.com/user/projects/54e3c717d1ec5734f4000099)
10
  [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/jarednova/timber/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/jarednova/timber/?branch=master)
11
+ [![Latest Stable Version](https://poser.pugx.org/jarednova/timber/v/stable.svg)](https://packagist.org/packages/jarednova/timber)
12
+ [![WordPress Download Count](https://img.shields.io/wordpress/plugin/dt/timber-library.svg)](https://wordpress.org/plugins/timber-library/)
13
+ [![HHVM Status](http://hhvm.h4cc.de/badge/jarednova/timber.svg)](http://hhvm.h4cc.de/package/jarednova/timber)
14
 
15
  ### Because WordPress is awesome, but the_loop isn't
16
  Timber helps you create fully-customized WordPress themes faster with more sustainable code. With Timber, you write your HTML using the [Twig Template Engine](http://twig.sensiolabs.org/) separate from your PHP files.
72
  * [**Twig**](https://github.com/fabpot/Twig) The template language used by Timber.
73
 
74
  #### Should I use it?
75
+ It's MIT-licensed, so please use in personal or commercial work. Just don't re-sell it. While Timber is still in development, it's also in-use on [hundreds of sites](http://jarednova.github.io/timber/#showcase). While much has been stabilized since the first major push back in June 2013, you should expect some breaking changes as development progresses towards a version 1.0.
76
 
77
  #### Contributing
78
  Read the [contributor guidelines](https://github.com/jarednova/timber/wiki#contributing) in the wiki.
79
 
80
+ ## How To...
81
+
82
+ #### Generate documentation
83
+ ```bash
84
+ $ cd /srv/www/timber
85
+ $ ./bin/generate-docs.sh
86
+ ```
87
+
88
 
89
 
lib/cache/TimberKeyGeneratorInterface.php CHANGED
@@ -2,7 +2,8 @@
2
 
3
  namespace Timber\Cache;
4
 
5
- interface TimberKeyGeneratorInterface
6
- {
7
  public function _get_cache_key();
 
8
  }
2
 
3
  namespace Timber\Cache;
4
 
5
+ interface TimberKeyGeneratorInterface {
6
+
7
  public function _get_cache_key();
8
+
9
  }
lib/timber-archives.php CHANGED
@@ -1,266 +1,274 @@
1
  <?php
2
 
3
- class TimberArchives extends TimberCore
4
- {
5
 
6
- public $base = '';
7
- public $items;
 
 
 
 
8
 
9
- function __construct($args = null, $base = '') {
10
- $this->init($args, $base);
11
- }
12
 
13
- /**
14
- * @param array|string $args
15
- * @param string $base
16
- */
17
- function init($args = null, $base = '') {
18
- $this->base = $base;
19
- $this->items = $this->get_items($args);
20
- }
 
21
 
22
- /**
23
- * @param string $url
24
- * @param string $text
25
- * @return mixed
26
- */
27
- function get_archives_link($url, $text) {
28
- $ret = array();
29
- $ret['text'] = $ret['title'] = $ret['name'] = wptexturize($text);
30
- $ret['url'] = $ret['link'] = esc_url(TimberURLHelper::prepend_to_url($url, $this->base));
31
- return $ret;
32
- }
 
33
 
34
- /**
35
- * @param array|string $args
36
- * @param string $last_changed
37
- * @param string $join
38
- * @param string $where
39
- * @param string $order
40
- * @param string $limit
41
- * @return array
42
- */
43
- function get_items_yearly($args, $last_changed, $join, $where, $order, $limit) {
44
- global $wpdb;
45
- $output = array();
46
- $query = "SELECT YEAR(post_date) AS `year`, count(ID) as posts FROM $wpdb->posts $join $where GROUP BY YEAR(post_date) ORDER BY post_date $order $limit";
47
- $key = md5($query);
48
- $key = "wp_get_archives:$key:$last_changed";
49
- if (!$results = wp_cache_get($key, 'posts')) {
50
- $results = $wpdb->get_results($query);
51
- wp_cache_set($key, $results, 'posts');
52
- }
53
- if ($results) {
54
- foreach ((array)$results as $result) {
55
- $url = get_year_link($result->year);
56
- $text = sprintf('%d', $result->year);
57
- $output[] = $this->get_archives_link($url, $text);
58
- }
59
- }
60
- return $output;
61
- }
 
62
 
63
- /**
64
- * @param array|string $args
65
- * @param string $last_changed
66
- * @param string $join
67
- * @param string $where
68
- * @param string $order
69
- * @param int $limit
70
- * @param bool $nested
71
- * @return array
72
- */
73
- function get_items_monthly($args, $last_changed, $join, $where, $order, $limit = 1000, $nested = true) {
74
- global $wpdb, $wp_locale;
75
- $output = array();
76
- $defaults = array(
77
- 'show_year' => false,
78
- );
79
- $r = wp_parse_args($args, $defaults);
 
80
 
81
- $show_year = $r['show_year'];
82
- //will need to specify which year we're looking for
83
- $query = "SELECT YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts "
84
- . "FROM $wpdb->posts $join $where GROUP BY YEAR(post_date), MONTH(post_date) "
85
- . "ORDER BY post_date $order $limit";
86
- $key = md5($query);
87
- $key = "wp_get_archives:$key:$last_changed";
88
- if (!$results = wp_cache_get($key, 'posts')) {
89
- $results = $wpdb->get_results($query);
90
- wp_cache_set($key, $results, 'posts');
91
- }
92
- if ($results) {
93
- foreach ((array)$results as $result) {
94
- $url = get_month_link($result->year, $result->month);
95
- if ($show_year && !$nested) {
96
- $text = sprintf(__('%1$s %2$d'), $wp_locale->get_month($result->month), $result->year);
97
- } else {
98
- $text = sprintf(__('%1$s'), $wp_locale->get_month($result->month));
99
- }
100
- if ($nested) {
101
- $output[$result->year][] = $this->get_archives_link($url, $text);
102
- } else {
103
- $output[] = $this->get_archives_link($url, $text);
104
- }
105
- }
106
- }
107
- if ($nested) {
108
- $out2 = array();
109
- foreach ($output as $year => $months) {
110
- $out2[] = array('name' => $year, 'children' => $months);
111
- }
112
- return $out2;
113
- }
114
- return $output;
115
- }
116
 
117
- /**
118
- * @param array|string $args
119
- * @return array|string
120
- */
121
- function get_items($args = null) {
122
- global $wpdb;
 
123
 
124
- $defaults = array(
125
- 'type' => 'monthly-nested',
126
- 'limit' => '',
127
- 'show_post_count' => false,
128
- 'order' => 'DESC',
129
- 'post_type' => 'post',
130
- 'show_year' => false,
131
- 'nested' => false
132
- );
133
 
134
- $args = wp_parse_args($args, $defaults);
135
- $post_type = $args['post_type'];
136
- $order = $args['order'];
137
- $nested = $args['nested'];
138
- $type = $args['type'];
139
- $limit = '';
140
- if ( $type == 'yearlymonthly' || $type == 'yearmonth' ) {
141
- $type = 'monthly-nested';
142
- }
143
- if ( $type == 'monthly-nested' ) {
144
- $nested = true;
145
- }
146
 
147
- if (!empty($args['limit'])) {
148
- $limit = absint($limit);
149
- $limit = ' LIMIT ' . $limit;
150
- }
151
 
152
- $order = strtoupper($order);
153
- if ($order !== 'ASC') {
154
- $order = 'DESC';
155
- }
156
 
157
- // this is what will separate dates on weekly archive links
158
- $archive_week_separator = '&#8211;';
159
 
160
- // over-ride general date format ? 0 = no: use the date format set in Options, 1 = yes: over-ride
161
- $archive_date_format_over_ride = 0;
162
 
163
- // options for daily archive (only if you over-ride the general date format)
164
- $archive_day_date_format = 'Y/m/d';
165
 
166
- // options for weekly archive (only if you over-ride the general date format)
167
- $archive_week_start_date_format = 'Y/m/d';
168
- $archive_week_end_date_format = 'Y/m/d';
169
 
170
- if (!$archive_date_format_over_ride) {
171
- $archive_day_date_format = get_option('date_format');
172
- $archive_week_start_date_format = get_option('date_format');
173
- $archive_week_end_date_format = get_option('date_format');
174
- }
175
 
176
- $where = apply_filters('getarchives_where', 'WHERE post_type = "' . $post_type . '" AND post_status = "publish"', $args);
177
- $join = apply_filters('getarchives_join', '', $args);
178
 
179
- $output = array();
180
- $last_changed = wp_cache_get('last_changed', 'posts');
181
- if (!$last_changed) {
182
- $last_changed = microtime();
183
- wp_cache_set('last_changed', $last_changed, 'posts');
184
- }
185
- if ('monthly' == $type) {
186
- $output = $this->get_items_monthly($args, $last_changed, $join, $where, $order, $limit, $nested);
187
- } elseif ('yearly' == $type) {
188
- $output = $this->get_items_yearly($args, $last_changed, $join, $where, $order, $limit);
189
- } elseif ('monthly-nested' == $type) {
190
- $years = $this->get_items_yearly($args, $last_changed, $join, $where, $order, $limit);
191
- foreach ($years as &$year) {
192
- $args = array('show_year' => false);
193
- $year['children'] = $this->get_items_monthly($args, $last_changed, $join, $where, $order, $limit);
194
- }
195
- $output = $years;
196
- } elseif ('daily' == $type) {
197
- $query = "SELECT YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, DAYOFMONTH(post_date) AS `dayofmonth`, count(ID) as posts FROM $wpdb->posts $join $where GROUP BY YEAR(post_date), MONTH(post_date), DAYOFMONTH(post_date) ORDER BY post_date $order $limit";
198
- $key = md5($query);
199
- $key = "wp_get_archives:$key:$last_changed";
200
- if (!$results = wp_cache_get($key, 'posts')) {
201
- $results = $wpdb->get_results($query);
202
- $cache = array();
203
- $cache[$key] = $results;
204
- wp_cache_set($key, $results, 'posts');
205
- }
206
- if ($results) {
207
- foreach ((array)$results as $result) {
208
- $url = get_day_link($result->year, $result->month, $result->dayofmonth);
209
- $date = sprintf('%1$d-%2$02d-%3$02d 00:00:00', $result->year, $result->month, $result->dayofmonth);
210
- $text = mysql2date($archive_day_date_format, $date);
211
- $output[] = $this->get_archives_link($url, $text);
212
- }
213
- }
214
- } elseif ('weekly' == $type) {
215
- $week = _wp_mysql_week('`post_date`');
216
- $query = "SELECT DISTINCT $week AS `week`, YEAR( `post_date` ) AS `yr`, DATE_FORMAT( `post_date`, '%Y-%m-%d' ) AS `yyyymmdd`, "
217
- . "count( `ID` ) AS `posts` FROM `$wpdb->posts` $join $where GROUP BY $week, YEAR( `post_date` ) ORDER BY `post_date` $order $limit";
218
- $key = md5($query);
219
- $key = "wp_get_archives:$key:$last_changed";
220
- if (!$results = wp_cache_get($key, 'posts')) {
221
- $results = $wpdb->get_results($query);
222
- wp_cache_set($key, $results, 'posts');
223
- }
224
- $arc_w_last = '';
225
- if ($results) {
226
- foreach ((array)$results as $result) {
227
- if ($result->week != $arc_w_last) {
228
- $arc_year = $result->yr;
229
- $arc_w_last = $result->week;
230
- $arc_week = get_weekstartend($result->yyyymmdd, get_option('start_of_week'));
231
- $arc_week_start = date_i18n($archive_week_start_date_format, $arc_week['start']);
232
- $arc_week_end = date_i18n($archive_week_end_date_format, $arc_week['end']);
233
- $url = sprintf('%1$s/%2$s%3$sm%4$s%5$s%6$sw%7$s%8$d', home_url(), '', '?', '=', $arc_year, '&amp;', '=', $result->week);
234
- $text = $arc_week_start . $archive_week_separator . $arc_week_end;
235
- $output[] = $this->get_archives_link($url, $text);
236
- }
237
- }
238
- }
239
- } elseif ('postbypost' == $type || 'alpha' == $type) {
240
- $orderby = 'alpha' == $type ? 'post_title ASC ' : 'post_date DESC ';
241
- $query = "SELECT * FROM $wpdb->posts $join $where ORDER BY $orderby $limit";
242
- $key = md5($query);
243
- $key = "wp_get_archives:$key:$last_changed";
244
- if (!$results = wp_cache_get($key, 'posts')) {
245
- $results = $wpdb->get_results($query);
246
- wp_cache_set($key, $results, 'posts');
247
- }
248
- if ($results) {
249
- foreach ((array)$results as $result) {
250
- if ($result->post_date != '0000-00-00 00:00:00') {
251
- $url = get_permalink($result);
252
- if ($result->post_title) {
253
- /** This filter is documented in wp-includes/post-template.php */
254
- $text = strip_tags(apply_filters('the_title', $result->post_title, $result->ID));
255
- } else {
256
- $text = $result->ID;
257
- }
258
- $output[] = $this->get_archives_link($url, $text);
259
- }
260
- }
261
- }
262
- }
263
- return $output;
264
- }
265
 
266
  }
1
  <?php
2
 
3
+ class TimberArchives extends TimberCore {
 
4
 
5
+ public $base = '';
6
+ /**
7
+ * @api
8
+ * @var array the items of the archives to iterate through and markup for your page
9
+ */
10
+ public $items;
11
 
12
+ function __construct( $args = null, $base = '' ) {
13
+ $this->init($args, $base);
14
+ }
15
 
16
+ /**
17
+ * @internal
18
+ * @param array|string $args
19
+ * @param string $base
20
+ */
21
+ function init( $args = null, $base = '' ) {
22
+ $this->base = $base;
23
+ $this->items = $this->get_items($args);
24
+ }
25
 
26
+ /**
27
+ * @internal
28
+ * @param string $url
29
+ * @param string $text
30
+ * @return mixed
31
+ */
32
+ protected function get_archives_link( $url, $text ) {
33
+ $ret = array();
34
+ $ret['text'] = $ret['title'] = $ret['name'] = wptexturize($text);
35
+ $ret['url'] = $ret['link'] = esc_url(TimberURLHelper::prepend_to_url($url, $this->base));
36
+ return $ret;
37
+ }
38
 
39
+ /**
40
+ * @internal
41
+ * @param array|string $args
42
+ * @param string $last_changed
43
+ * @param string $join
44
+ * @param string $where
45
+ * @param string $order
46
+ * @param string $limit
47
+ * @return array
48
+ */
49
+ protected function get_items_yearly( $args, $last_changed, $join, $where, $order, $limit ) {
50
+ global $wpdb;
51
+ $output = array();
52
+ $query = "SELECT YEAR(post_date) AS `year`, count(ID) as posts FROM $wpdb->posts $join $where GROUP BY YEAR(post_date) ORDER BY post_date $order $limit";
53
+ $key = md5($query);
54
+ $key = "wp_get_archives:$key:$last_changed";
55
+ if (!$results = wp_cache_get($key, 'posts')) {
56
+ $results = $wpdb->get_results($query);
57
+ wp_cache_set($key, $results, 'posts');
58
+ }
59
+ if ($results) {
60
+ foreach ((array)$results as $result) {
61
+ $url = get_year_link($result->year);
62
+ $text = sprintf('%d', $result->year);
63
+ $output[] = $this->get_archives_link($url, $text);
64
+ }
65
+ }
66
+ return $output;
67
+ }
68
 
69
+ /**
70
+ * @internal
71
+ * @param array|string $args
72
+ * @param string $last_changed
73
+ * @param string $join
74
+ * @param string $where
75
+ * @param string $order
76
+ * @param int $limit
77
+ * @param bool $nested
78
+ * @return array
79
+ */
80
+ protected function get_items_monthly( $args, $last_changed, $join, $where, $order, $limit = 1000, $nested = true ) {
81
+ global $wpdb, $wp_locale;
82
+ $output = array();
83
+ $defaults = array(
84
+ 'show_year' => false,
85
+ );
86
+ $r = wp_parse_args($args, $defaults);
87
 
88
+ $show_year = $r['show_year'];
89
+ //will need to specify which year we're looking for
90
+ $query = "SELECT YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts "
91
+ . "FROM $wpdb->posts $join $where GROUP BY YEAR(post_date), MONTH(post_date) "
92
+ . "ORDER BY post_date $order $limit";
93
+ $key = md5($query);
94
+ $key = "wp_get_archives:$key:$last_changed";
95
+ if (!$results = wp_cache_get($key, 'posts')) {
96
+ $results = $wpdb->get_results($query);
97
+ wp_cache_set($key, $results, 'posts');
98
+ }
99
+ if ($results) {
100
+ foreach ((array)$results as $result) {
101
+ $url = get_month_link($result->year, $result->month);
102
+ if ($show_year && !$nested) {
103
+ $text = sprintf(__('%1$s %2$d'), $wp_locale->get_month($result->month), $result->year);
104
+ } else {
105
+ $text = sprintf(__('%1$s'), $wp_locale->get_month($result->month));
106
+ }
107
+ if ($nested) {
108
+ $output[$result->year][] = $this->get_archives_link($url, $text);
109
+ } else {
110
+ $output[] = $this->get_archives_link($url, $text);
111
+ }
112
+ }
113
+ }
114
+ if ($nested) {
115
+ $out2 = array();
116
+ foreach ($output as $year => $months) {
117
+ $out2[] = array('name' => $year, 'children' => $months);
118
+ }
119
+ return $out2;
120
+ }
121
+ return $output;
122
+ }
123
 
124
+ /**
125
+ * @api
126
+ * @param array|string $args
127
+ * @return array|string
128
+ */
129
+ function get_items( $args = null ) {
130
+ global $wpdb;
131
 
132
+ $defaults = array(
133
+ 'type' => 'monthly-nested',
134
+ 'limit' => '',
135
+ 'show_post_count' => false,
136
+ 'order' => 'DESC',
137
+ 'post_type' => 'post',
138
+ 'show_year' => false,
139
+ 'nested' => false
140
+ );
141
 
142
+ $args = wp_parse_args($args, $defaults);
143
+ $post_type = $args['post_type'];
144
+ $order = $args['order'];
145
+ $nested = $args['nested'];
146
+ $type = $args['type'];
147
+ $limit = '';
148
+ if ( $type == 'yearlymonthly' || $type == 'yearmonth' ) {
149
+ $type = 'monthly-nested';
150
+ }
151
+ if ( $type == 'monthly-nested' ) {
152
+ $nested = true;
153
+ }
154
 
155
+ if ( !empty($args['limit']) ) {
156
+ $limit = absint($limit);
157
+ $limit = ' LIMIT ' . $limit;
158
+ }
159
 
160
+ $order = strtoupper($order);
161
+ if ( $order !== 'ASC' ) {
162
+ $order = 'DESC';
163
+ }
164
 
165
+ // this is what will separate dates on weekly archive links
166
+ $archive_week_separator = '&#8211;';
167
 
168
+ // over-ride general date format ? 0 = no: use the date format set in Options, 1 = yes: over-ride
169
+ $archive_date_format_over_ride = 0;
170
 
171
+ // options for daily archive (only if you over-ride the general date format)
172
+ $archive_day_date_format = 'Y/m/d';
173
 
174
+ // options for weekly archive (only if you over-ride the general date format)
175
+ $archive_week_start_date_format = 'Y/m/d';
176
+ $archive_week_end_date_format = 'Y/m/d';
177
 
178
+ if (!$archive_date_format_over_ride) {
179
+ $archive_day_date_format = get_option('date_format');
180
+ $archive_week_start_date_format = get_option('date_format');
181
+ $archive_week_end_date_format = get_option('date_format');
182
+ }
183
 
184
+ $where = apply_filters('getarchives_where', 'WHERE post_type = "' . $post_type . '" AND post_status = "publish"', $args);
185
+ $join = apply_filters('getarchives_join', '', $args);
186
 
187
+ $output = array();
188
+ $last_changed = wp_cache_get('last_changed', 'posts');
189
+ if (!$last_changed) {
190
+ $last_changed = microtime();
191
+ wp_cache_set('last_changed', $last_changed, 'posts');
192
+ }
193
+ if ( 'monthly' == $type ) {
194
+ $output = $this->get_items_monthly($args, $last_changed, $join, $where, $order, $limit, $nested);
195
+ } elseif ( 'yearly' == $type ) {
196
+ $output = $this->get_items_yearly($args, $last_changed, $join, $where, $order, $limit);
197
+ } elseif ( 'monthly-nested' == $type ) {
198
+ $years = $this->get_items_yearly($args, $last_changed, $join, $where, $order, $limit);
199
+ foreach ( $years as &$year ) {
200
+ $args = array('show_year' => false);
201
+ $year['children'] = $this->get_items_monthly($args, $last_changed, $join, $where, $order, $limit);
202
+ }
203
+ $output = $years;
204
+ } elseif ( 'daily' == $type ) {
205
+ $query = "SELECT YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, DAYOFMONTH(post_date) AS `dayofmonth`, count(ID) as posts FROM $wpdb->posts $join $where GROUP BY YEAR(post_date), MONTH(post_date), DAYOFMONTH(post_date) ORDER BY post_date $order $limit";
206
+ $key = md5($query);
207
+ $key = "wp_get_archives:$key:$last_changed";
208
+ if (!$results = wp_cache_get($key, 'posts')) {
209
+ $results = $wpdb->get_results($query);
210
+ $cache = array();
211
+ $cache[$key] = $results;
212
+ wp_cache_set($key, $results, 'posts');
213
+ }
214
+ if ( $results ) {
215
+ foreach ( (array)$results as $result ) {
216
+ $url = get_day_link($result->year, $result->month, $result->dayofmonth);
217
+ $date = sprintf('%1$d-%2$02d-%3$02d 00:00:00', $result->year, $result->month, $result->dayofmonth);
218
+ $text = mysql2date($archive_day_date_format, $date);
219
+ $output[] = $this->get_archives_link($url, $text);
220
+ }
221
+ }
222
+ } elseif ( 'weekly' == $type ) {
223
+ $week = _wp_mysql_week('`post_date`');
224
+ $query = "SELECT DISTINCT $week AS `week`, YEAR( `post_date` ) AS `yr`, DATE_FORMAT( `post_date`, '%Y-%m-%d' ) AS `yyyymmdd`, "
225
+ . "count( `ID` ) AS `posts` FROM `$wpdb->posts` $join $where GROUP BY $week, YEAR( `post_date` ) ORDER BY `post_date` $order $limit";
226
+ $key = md5($query);
227
+ $key = "wp_get_archives:$key:$last_changed";
228
+ if (!$results = wp_cache_get($key, 'posts')) {
229
+ $results = $wpdb->get_results($query);
230
+ wp_cache_set($key, $results, 'posts');
231
+ }
232
+ $arc_w_last = '';
233
+ if ( $results ) {
234
+ foreach ( (array)$results as $result ) {
235
+ if ( $result->week != $arc_w_last ) {
236
+ $arc_year = $result->yr;
237
+ $arc_w_last = $result->week;
238
+ $arc_week = get_weekstartend($result->yyyymmdd, get_option('start_of_week'));
239
+ $arc_week_start = date_i18n($archive_week_start_date_format, $arc_week['start']);
240
+ $arc_week_end = date_i18n($archive_week_end_date_format, $arc_week['end']);
241
+ $url = sprintf('%1$s/%2$s%3$sm%4$s%5$s%6$sw%7$s%8$d', home_url(), '', '?', '=', $arc_year, '&amp;', '=', $result->week);
242
+ $text = $arc_week_start . $archive_week_separator . $arc_week_end;
243
+ $output[] = $this->get_archives_link($url, $text);
244
+ }
245
+ }
246
+ }
247
+ } elseif ( 'postbypost' == $type || 'alpha' == $type ) {
248
+ $orderby = 'alpha' == $type ? 'post_title ASC ' : 'post_date DESC ';
249
+ $query = "SELECT * FROM $wpdb->posts $join $where ORDER BY $orderby $limit";
250
+ $key = md5($query);
251
+ $key = "wp_get_archives:$key:$last_changed";
252
+ if ( !$results = wp_cache_get($key, 'posts') ) {
253
+ $results = $wpdb->get_results($query);
254
+ wp_cache_set($key, $results, 'posts');
255
+ }
256
+ if ( $results ) {
257
+ foreach ( (array)$results as $result ) {
258
+ if ($result->post_date != '0000-00-00 00:00:00') {
259
+ $url = get_permalink($result);
260
+ if ($result->post_title) {
261
+ /** This filter is documented in wp-includes/post-template.php */
262
+ $text = strip_tags(apply_filters('the_title', $result->post_title, $result->ID));
263
+ } else {
264
+ $text = $result->ID;
265
+ }
266
+ $output[] = $this->get_archives_link($url, $text);
267
+ }
268
+ }
269
+ }
270
+ }
271
+ return $output;
272
+ }
273
 
274
  }
lib/timber-comment.php CHANGED
@@ -30,6 +30,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
30
  }
31
 
32
  /**
 
33
  * @param integer $cid
34
  */
35
  function init($cid) {
@@ -45,6 +46,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
45
  }
46
 
47
  /**
 
48
  * @return TimberUser
49
  */
50
  public function author() {
@@ -94,24 +96,29 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
94
  }
95
 
96
  /**
 
97
  * @return string
98
  */
99
  public function content() {
100
- return $this->comment_content;
101
  }
102
 
103
  /**
104
- * @return string
 
105
  */
106
- public function status() {
107
- return $this->comment_status;
108
  }
109
 
110
  /**
 
111
  * @return string
112
  */
113
- public function date() {
114
- return $this->comment_date;
 
 
115
  }
116
 
117
  /**
@@ -123,6 +130,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
123
  }
124
 
125
  /**
 
126
  * @return bool
127
  */
128
  public function is_child() {
@@ -166,6 +174,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
166
  ======================= */
167
 
168
  /**
 
169
  * @return string
170
  */
171
  protected function avatar_email() {
@@ -180,6 +189,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
180
  }
181
 
182
  /**
 
183
  * @param string $email_hash
184
  * @return string
185
  */
@@ -197,6 +207,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
197
  }
198
 
199
  /**
 
200
  * @todo what if it's relative?
201
  * @param string $default
202
  * @param string $email
@@ -233,6 +244,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
233
  }
234
 
235
  /**
 
236
  * @param string $default
237
  * @param string $host
238
  * @param string $email_hash
30
  }
31
 
32
  /**
33
+ * @internal
34
  * @param integer $cid
35
  */
36
  function init($cid) {
46
  }
47
 
48
  /**
49
+ * @api
50
  * @return TimberUser
51
  */
52
  public function author() {
96
  }
97
 
98
  /**
99
+ * @api
100
  * @return string
101
  */
102
  public function content() {
103
+ return apply_filters('get_comment_text ', $this->comment_content);
104
  }
105
 
106
  /**
107
+ * @api
108
+ * @return boolean
109
  */
110
+ public function approved() {
111
+ return $this->comment_approved;
112
  }
113
 
114
  /**
115
+ * @api
116
  * @return string
117
  */
118
+ public function date( $date_format = '' ) {
119
+ $df = $date_format ? $date_format : get_option('date_format');
120
+ $the_date = (string)mysql2date($df, $this->comment_date);
121
+ return apply_filters('get_comment_date ', $the_date, $df);
122
  }
123
 
124
  /**
130
  }
131
 
132
  /**
133
+ * @api
134
  * @return bool
135
  */
136
  public function is_child() {
174
  ======================= */
175
 
176
  /**
177
+ * @internal
178
  * @return string
179
  */
180
  protected function avatar_email() {
189
  }
190
 
191
  /**
192
+ * @internal
193
  * @param string $email_hash
194
  * @return string
195
  */
207
  }
208
 
209
  /**
210
+ * @internal
211
  * @todo what if it's relative?
212
  * @param string $default
213
  * @param string $email
244
  }
245
 
246
  /**
247
+ * @internal
248
  * @param string $default
249
  * @param string $host
250
  * @param string $email_hash
lib/timber-helper.php CHANGED
@@ -3,12 +3,22 @@
3
  class TimberHelper {
4
 
5
  /**
6
- *
 
 
 
 
 
 
 
 
 
 
7
  *
8
  * @param string $slug Unique identifier for transient
9
- * @param callable $callback Callback that generates the data that's to be cached
10
  * @param int $transient_time (optional) Expiration of transients in seconds
11
- * @param int $lock_timeout (optional) How long to lock the transient to prevent race conditions
12
  * @param bool $force (optional) Force callback to be executed when transient is locked
13
  * @return mixed
14
  */
@@ -52,24 +62,27 @@ class TimberHelper {
52
  }
53
 
54
  /**
 
55
  * @param string $slug
56
  * @param integer $lock_timeout
57
  */
58
- public static function _lock_transient( $slug, $lock_timeout ) {
59
  set_transient( $slug . '_lock', true, $lock_timeout );
60
  }
61
 
62
  /**
 
63
  * @param string $slug
64
  */
65
- public static function _unlock_transient( $slug ) {
66
  delete_transient( $slug . '_lock', true );
67
  }
68
 
69
  /**
 
70
  * @param string $slug
71
  */
72
- public static function _is_transient_locked( $slug ) {
73
  return (bool)get_transient( $slug . '_lock' );
74
  }
75
 
@@ -172,13 +185,13 @@ class TimberHelper {
172
  * @param string $allowed_tags
173
  * @return string
174
  */
175
- public static function trim_words( $text, $num_words = 55, $more = null, $allowed_tags = 'p a span b i br' ) {
176
  if ( null === $more ) {
177
  $more = __( '&hellip;' );
178
  }
179
  $original_text = $text;
180
  $allowed_tag_string = '';
181
- foreach ( explode( ' ', $allowed_tags ) as $tag ) {
182
  $allowed_tag_string .= '<' . $tag . '>';
183
  }
184
  $text = strip_tags( $text, $allowed_tag_string );
@@ -299,7 +312,7 @@ class TimberHelper {
299
 
300
  /**
301
  *
302
- *
303
  * @param int $ttid
304
  * @return mixed
305
  */
@@ -462,15 +475,12 @@ class TimberHelper {
462
  /* Links, Forms, Etc. Utilities
463
  ======================== */
464
 
465
- /* this $args thing is a fucking mess, fix at some point:
466
-
467
- http://codex.wordpress.org/Function_Reference/comment_form */
468
-
469
  /**
470
  *
471
- *
472
- * @param int $post_id
473
- * @param array $args
 
474
  * @return string
475
  */
476
  public static function get_comment_form( $post_id = null, $args = array() ) {
@@ -579,13 +589,6 @@ class TimberHelper {
579
  return $page_links;
580
  }
581
 
582
- /**
583
- * @deprecated since 0.18.0
584
- */
585
- static function get_image_path( $iid ) {
586
- return TimberImageHelper::get_image_path( $iid );
587
- }
588
-
589
  /**
590
  * @deprecated since 0.18.0
591
  */
3
  class TimberHelper {
4
 
5
  /**
6
+ * A utility for a one-stop shop for Transients
7
+ * @api
8
+ * @example
9
+ * ```php
10
+ * $favorites = Timber::transient('user-'.$uid.'-favorites', function() use ($uid) {
11
+ * //some expensive query here that's doing something you want to store to a transient
12
+ * return $favorites;
13
+ * }, 600);
14
+ * Timber::context['favorites'] = $favorites;
15
+ * Timber::render('single.twig', $context);
16
+ * ```
17
  *
18
  * @param string $slug Unique identifier for transient
19
+ * @param callable $callback Callback that generates the data that's to be cached
20
  * @param int $transient_time (optional) Expiration of transients in seconds
21
+ * @param int $lock_timeout (optional) How long (in seconds) to lock the transient to prevent race conditions
22
  * @param bool $force (optional) Force callback to be executed when transient is locked
23
  * @return mixed
24
  */
62
  }
63
 
64
  /**
65
+ * @internal
66
  * @param string $slug
67
  * @param integer $lock_timeout
68
  */
69
+ static function _lock_transient( $slug, $lock_timeout ) {
70
  set_transient( $slug . '_lock', true, $lock_timeout );
71
  }
72
 
73
  /**
74
+ * @internal
75
  * @param string $slug
76
  */
77
+ static function _unlock_transient( $slug ) {
78
  delete_transient( $slug . '_lock', true );
79
  }
80
 
81
  /**
82
+ * @internal
83
  * @param string $slug
84
  */
85
+ static function _is_transient_locked( $slug ) {
86
  return (bool)get_transient( $slug . '_lock' );
87
  }
88
 
185
  * @param string $allowed_tags
186
  * @return string
187
  */
188
+ public static function trim_words( $text, $num_words = 55, $more = null, $allowed_tags = 'p a span b i br blockquote' ) {
189
  if ( null === $more ) {
190
  $more = __( '&hellip;' );
191
  }
192
  $original_text = $text;
193
  $allowed_tag_string = '';
194
+ foreach ( explode( ' ', apply_filters( 'timber/trim_words/allowed_tags', $allowed_tags ) ) as $tag ) {
195
  $allowed_tag_string .= '<' . $tag . '>';
196
  }
197
  $text = strip_tags( $text, $allowed_tag_string );
312
 
313
  /**
314
  *
315
+ * @deprecated since 0.21.8
316
  * @param int $ttid
317
  * @return mixed
318
  */
475
  /* Links, Forms, Etc. Utilities
476
  ======================== */
477
 
 
 
 
 
478
  /**
479
  *
480
+ * Gets the comment form for use on a single article page
481
+ * @deprecated since 0.21.8
482
+ * @param int $post_id which post_id should the form be tied to?
483
+ * @param array $args this $args thing is a fucking mess, [fix at some point](http://codex.wordpress.org/Function_Reference/comment_form)
484
  * @return string
485
  */
486
  public static function get_comment_form( $post_id = null, $args = array() ) {
589
  return $page_links;
590
  }
591
 
 
 
 
 
 
 
 
592
  /**
593
  * @deprecated since 0.18.0
594
  */
lib/timber-image.php CHANGED
@@ -2,32 +2,44 @@
2
 
3
  class TimberImage extends TimberPost implements TimberCoreInterface {
4
 
5
- public $_can_edit;
6
- public $_dimensions;
7
  public $abs_url;
8
- public $PostClass = 'TimberPost';
 
 
9
  public $object_type = 'image';
10
-
 
 
11
  public static $representation = 'image';
12
-
 
 
 
13
  public $file_loc;
14
  public $file;
15
  public $sizes = array();
16
- public $post_parent;
 
 
17
  public $caption;
18
- public $_wp_attached_file;
 
 
 
19
 
20
  /**
21
  * @param int $iid
22
  */
23
- function __construct($iid) {
24
  $this->init($iid);
25
  }
26
 
27
  /**
28
  * @return string
29
  */
30
- function __toString() {
31
  if ($this->get_src()) {
32
  return $this->get_src();
33
  }
@@ -35,6 +47,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
35
  }
36
 
37
  /**
 
38
  * @return mixed
39
  */
40
  function get_pathinfo() {
@@ -57,6 +70,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
57
  }
58
 
59
  /**
 
60
  * @param string|null $dim
61
  * @return array|int
62
  */
@@ -74,6 +88,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
74
  }
75
 
76
  /**
 
77
  * @return int
78
  */
79
  function get_width() {
@@ -81,6 +96,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
81
  }
82
 
83
  /**
 
84
  * @return int
85
  */
86
  function get_height() {
@@ -88,40 +104,45 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
88
  }
89
 
90
  /**
 
91
  * @param string $size
92
  * @return bool|string
93
  */
94
  function get_src( $size = '' ) {
95
- if (isset($this->abs_url)) {
96
  return $this->_maybe_secure_url($this->abs_url);
97
  }
98
 
99
- if ($size && is_string($size) && isset($this->sizes[$size])) {
100
  $image = image_downsize($this->ID, $size);
101
  return $this->_maybe_secure_url(reset($image));
102
  }
103
 
104
- if (!isset($this->file) && isset($this->_wp_attached_file)) {
105
  $this->file = $this->_wp_attached_file;
106
  }
107
 
108
- if (!isset($this->file)) {
109
  return false;
110
  }
111
 
112
  $dir = self::wp_upload_dir();
113
- $base = ($dir["baseurl"]);
114
 
115
  $src = trailingslashit($this->_maybe_secure_url($base)) . $this->file;
116
  $src = apply_filters('timber/image/src', $src, $this->ID);
117
  return apply_filters('timber_image_src', $src, $this->ID);
118
  }
119
 
120
- private static function _maybe_secure_url($url) {
 
 
 
 
 
121
  if (is_ssl() && strpos($url, 'https') !== 0 && strpos($url, 'http') === 0) {
122
  $url = 'https' . substr($url, strlen('http'));
123
  }
124
-
125
  return $url;
126
  }
127
 
@@ -136,6 +157,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
136
  }
137
 
138
  /**
 
139
  * @return string
140
  */
141
  function get_path() {
@@ -146,15 +168,38 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
146
  }
147
 
148
  /**
149
- * @return bool|TimberImage
 
150
  */
151
- function get_parent() {
 
 
 
 
 
 
 
 
152
  if (!$this->post_parent) {
153
  return false;
154
  }
155
  return new $this->PostClass($this->post_parent);
156
  }
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  function get_alt() {
159
  $alt = trim(strip_tags(get_post_meta($this->ID, '_wp_attachment_image_alt', true)));
160
  return $alt;
@@ -162,6 +207,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
162
 
163
 
164
  /**
 
165
  * @param int $iid
166
  */
167
  function init( $iid = false ) {
@@ -211,7 +257,11 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
211
  }
212
  }
213
 
214
- private function get_image_info( $iid ) {
 
 
 
 
215
  $image_info = $iid;
216
  if (is_numeric($iid)) {
217
  $image_info = wp_get_attachment_metadata($iid);
@@ -237,14 +287,22 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
237
  return $iid;
238
  }
239
 
240
- private function init_with_relative_path( $relative_path ) {
 
 
 
 
241
  $this->abs_url = home_url( $relative_path );
242
  $file_path = TimberURLHelper::get_full_path( $relative_path );
243
  $this->file_loc = $file_path;
244
  $this->file = $file_path;
245
  }
246
 
247
- private function init_with_file_path( $file_path ) {
 
 
 
 
248
  $url = TimberURLHelper::file_system_to_url( $file_path );
249
  $this->abs_url = $url;
250
  $this->file_loc = $file_path;
@@ -252,9 +310,10 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
252
  }
253
 
254
  /**
 
255
  * @param string $url
256
  */
257
- private function init_with_url($url) {
258
  $this->abs_url = $url;
259
  if (TimberURLHelper::is_local($url)) {
260
  $this->file = ABSPATH . TimberURLHelper::get_rel_url($url);
@@ -263,7 +322,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
263
  }
264
 
265
  /**
266
- * @deprecated; use src() instead
267
  * @return string
268
  */
269
  function get_url() {
@@ -271,16 +330,20 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
271
  }
272
 
273
  /**
274
- * @deprecated; use src() instead
275
  * @return string
276
  */
277
  function url() {
278
  return $this->get_src();
279
  }
280
 
281
- /* Alias */
282
-
283
  /**
 
 
 
 
 
 
284
  * @return float
285
  */
286
  public function aspect() {
@@ -290,6 +353,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
290
  }
291
 
292
  /**
 
293
  * @return int
294
  */
295
  public function height() {
@@ -297,6 +361,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
297
  }
298
 
299
  /**
 
300
  * @param string $size
301
  * @return bool|string
302
  */
@@ -305,6 +370,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
305
  }
306
 
307
  /**
 
308
  * @return int
309
  */
310
  public function width() {
@@ -312,6 +378,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
312
  }
313
 
314
  /**
 
315
  * @return string alt text stored in WordPress
316
  */
317
  public function alt() {
2
 
3
  class TimberImage extends TimberPost implements TimberCoreInterface {
4
 
5
+ protected $_can_edit;
6
+ protected $_dimensions;
7
  public $abs_url;
8
+ /**
9
+ * @var string $object_type what does this class represent in WordPress terms?
10
+ */
11
  public $object_type = 'image';
12
+ /**
13
+ * @var string $representation what does this class represent in WordPress terms?
14
+ */
15
  public static $representation = 'image';
16
+ /**
17
+ * @api
18
+ * @var string $file_loc the location of the image file in the filesystem (ex: `/var/www/htdocs/wp-content/uploads/2015/08/my-pic.jpg`)
19
+ */
20
  public $file_loc;
21
  public $file;
22
  public $sizes = array();
23
+ /**
24
+ * @var string $caption the string stored in the WordPress database
25
+ */
26
  public $caption;
27
+ /**
28
+ * @var $_wp_attached_file the file as stored in the WordPress database
29
+ */
30
+ protected $_wp_attached_file;
31
 
32
  /**
33
  * @param int $iid
34
  */
35
+ public function __construct($iid) {
36
  $this->init($iid);
37
  }
38
 
39
  /**
40
  * @return string
41
  */
42
+ public function __toString() {
43
  if ($this->get_src()) {
44
  return $this->get_src();
45
  }
47
  }
48
 
49
  /**
50
+ * @internal
51
  * @return mixed
52
  */
53
  function get_pathinfo() {
70
  }
71
 
72
  /**
73
+ * @internal
74
  * @param string|null $dim
75
  * @return array|int
76
  */
88
  }
89
 
90
  /**
91
+ * @internal
92
  * @return int
93
  */
94
  function get_width() {
96
  }
97
 
98
  /**
99
+ * @internal
100
  * @return int
101
  */
102
  function get_height() {
104
  }
105
 
106
  /**
107
+ * @internal
108
  * @param string $size
109
  * @return bool|string
110
  */
111
  function get_src( $size = '' ) {
112
+ if ( isset($this->abs_url) ) {
113
  return $this->_maybe_secure_url($this->abs_url);
114
  }
115
 
116
+ if ( $size && is_string($size) && isset($this->sizes[$size]) ) {
117
  $image = image_downsize($this->ID, $size);
118
  return $this->_maybe_secure_url(reset($image));
119
  }
120
 
121
+ if ( !isset($this->file) && isset($this->_wp_attached_file) ) {
122
  $this->file = $this->_wp_attached_file;
123
  }
124
 
125
+ if ( !isset($this->file) ) {
126
  return false;
127
  }
128
 
129
  $dir = self::wp_upload_dir();
130
+ $base = $dir['baseurl'];
131
 
132
  $src = trailingslashit($this->_maybe_secure_url($base)) . $this->file;
133
  $src = apply_filters('timber/image/src', $src, $this->ID);
134
  return apply_filters('timber_image_src', $src, $this->ID);
135
  }
136
 
137
+ /**
138
+ * @internal
139
+ * @param string $url for evaluation
140
+ * @return string with http/https corrected depending on what's appropriate for server
141
+ */
142
+ protected static function _maybe_secure_url($url) {
143
  if (is_ssl() && strpos($url, 'https') !== 0 && strpos($url, 'http') === 0) {
144
  $url = 'https' . substr($url, strlen('http'));
145
  }
 
146
  return $url;
147
  }
148
 
157
  }
158
 
159
  /**
160
+ * @internal
161
  * @return string
162
  */
163
  function get_path() {
168
  }
169
 
170
  /**
171
+ * @api
172
+ * @return string the /relative/path/to/the/file
173
  */
174
+ public function path() {
175
+ return TimberURLHelper::get_rel_path($this->src());
176
+ }
177
+
178
+ /**
179
+ * @api
180
+ * @return bool|TimberPost
181
+ */
182
+ function parent() {
183
  if (!$this->post_parent) {
184
  return false;
185
  }
186
  return new $this->PostClass($this->post_parent);
187
  }
188
 
189
+ /**
190
+ * @internal
191
+ * @deprecated 0.21.8
192
+ * @return bool|TimberPost
193
+ */
194
+ function get_parent() {
195
+ return $this->parent();
196
+ }
197
+
198
+ /**
199
+ * @internal
200
+ * @see TimberImage::alt
201
+ * @return string
202
+ */
203
  function get_alt() {
204
  $alt = trim(strip_tags(get_post_meta($this->ID, '_wp_attachment_image_alt', true)));
205
  return $alt;
207
 
208
 
209
  /**
210
+ * @internal
211
  * @param int $iid
212
  */
213
  function init( $iid = false ) {
257
  }
258
  }
259
 
260
+ /**
261
+ * @internal
262
+ * @param int $iid the id number of the image in the WP database
263
+ */
264
+ protected function get_image_info( $iid ) {
265
  $image_info = $iid;
266
  if (is_numeric($iid)) {
267
  $image_info = wp_get_attachment_metadata($iid);
287
  return $iid;
288
  }
289
 
290
+ /**
291
+ * @internal
292
+ * @param string $relative_path
293
+ */
294
+ protected function init_with_relative_path( $relative_path ) {
295
  $this->abs_url = home_url( $relative_path );
296
  $file_path = TimberURLHelper::get_full_path( $relative_path );
297
  $this->file_loc = $file_path;
298
  $this->file = $file_path;
299
  }
300
 
301
+ /**
302
+ * @internal
303
+ * @param string $file_path
304
+ */
305
+ protected function init_with_file_path( $file_path ) {
306
  $url = TimberURLHelper::file_system_to_url( $file_path );
307
  $this->abs_url = $url;
308
  $this->file_loc = $file_path;
310
  }
311
 
312
  /**
313
+ * @internal
314
  * @param string $url
315
  */
316
+ protected function init_with_url($url) {
317
  $this->abs_url = $url;
318
  if (TimberURLHelper::is_local($url)) {
319
  $this->file = ABSPATH . TimberURLHelper::get_rel_url($url);
322
  }
323
 
324
  /**
325
+ * @deprecated use src() instead
326
  * @return string
327
  */
328
  function get_url() {
330
  }
331
 
332
  /**
333
+ * @deprecated use src() instead
334
  * @return string
335
  */
336
  function url() {
337
  return $this->get_src();
338
  }
339
 
 
 
340
  /**
341
+ * @api
342
+ * @example
343
+ * ```twig
344
+ * {% if post.thumbnail.aspect < 1 %}
345
+ * {# handle vertical image #}
346
+ * <img
347
  * @return float
348
  */
349
  public function aspect() {
353
  }
354
 
355
  /**
356
+ * @api
357
  * @return int
358
  */
359
  public function height() {
361
  }
362
 
363
  /**
364
+ * @api
365
  * @param string $size
366
  * @return bool|string
367
  */
370
  }
371
 
372
  /**
373
+ * @api
374
  * @return int
375
  */
376
  public function width() {
378
  }
379
 
380
  /**
381
+ * @api
382
  * @return string alt text stored in WordPress
383
  */
384
  public function alt() {
lib/timber-loader.php CHANGED
@@ -253,6 +253,7 @@ class TimberLoader {
253
  $twig->addExtension($this->_get_cache_extension());
254
 
255
  $twig = apply_filters('twig_apply_filters', $twig);
 
256
  $twig = apply_filters('timber/loader/twig', $twig);
257
  return $twig;
258
  }
253
  $twig->addExtension($this->_get_cache_extension());
254
 
255
  $twig = apply_filters('twig_apply_filters', $twig);
256
+ $twig = apply_filters('timber/twig/filters', $twig);
257
  $twig = apply_filters('timber/loader/twig', $twig);
258
  return $twig;
259
  }
lib/timber-menu-item.php CHANGED
@@ -17,14 +17,15 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
17
  protected $_menu_item_object_id;
18
  protected $_menu_item_url;
19
  protected $menu_object;
20
- protected $parent_object;
21
 
22
  /**
23
  *
24
  *
25
  * @param array|object $data
26
  */
27
- function __construct( $data ) {
 
28
  $this->import( $data );
29
  $this->import_classes( $data );
30
  if ( isset( $this->name ) ) {
@@ -35,28 +36,30 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
35
  $this->menu_object = $data;
36
  }
37
 
38
- function __toString() {
 
 
 
39
  return $this->name();
40
  }
41
 
42
  /**
43
- *
44
- *
45
- * @param string $class_name
46
  */
47
- function add_class( $class_name ) {
48
  $this->classes[] = $class_name;
49
  $this->class .= ' ' . $class_name;
50
  }
51
 
52
  /**
53
- *
54
- *
55
  * @return string
56
  */
57
- function name() {
58
- if ( isset( $this->title ) ) {
59
- return $this->title;
60
  }
61
  if ( isset( $this->_name ) ) {
62
  return $this->_name;
@@ -65,30 +68,43 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
65
  }
66
 
67
  /**
68
- *
69
- *
70
- * @return string
 
 
 
 
 
 
 
 
 
71
  */
72
- function slug() {
73
- if ( !isset( $this->parent_object ) ) {
74
- $this->parent_object = $this->get_parent_object();
75
  }
76
- if ( isset( $this->parent_object->post_name ) && $this->parent_object->post_name ) {
77
- return $this->parent_object->post_name;
78
  }
79
  return $this->post_name;
80
  }
81
 
82
- function get_parent_object() {
 
 
 
 
83
  if ( isset( $this->_menu_item_object_id ) ) {
84
  return new $this->PostClass( $this->_menu_item_object_id );
85
  }
86
  }
87
 
88
  /**
89
- *
90
- *
91
- * @return string
92
  */
93
  function get_link() {
94
  if ( !isset( $this->url ) || !$this->url ) {
@@ -102,9 +118,9 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
102
  }
103
 
104
  /**
105
- *
106
- *
107
- * @return string
108
  */
109
  function get_path() {
110
  return TimberURLHelper::get_rel_url( $this->get_link() );
@@ -130,18 +146,24 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
130
  }
131
  }
132
 
133
- public function update_child_levels() {
 
 
 
 
 
134
  if (is_array($this->children)) {
135
  foreach( $this->children as $child ) {
136
  $child->level = $this->level + 1;
137
  $child->update_child_levels();
138
  }
 
139
  }
140
  }
141
 
142
  /**
143
  * Imports the classes to be used in CSS
144
- *
145
  * @param array|object $data
146
  */
147
  function import_classes( $data ) {
@@ -156,7 +178,7 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
156
 
157
  /**
158
  *
159
- *
160
  * @return array|bool
161
  */
162
  function get_children() {
@@ -167,8 +189,12 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
167
  }
168
 
169
  /**
170
- *
171
- *
 
 
 
 
172
  * @return bool
173
  */
174
  function is_external() {
@@ -179,9 +205,7 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
179
  }
180
 
181
  /**
182
- *
183
- *
184
- * @param unknown $key string lookup key
185
  * @return mixed whatever value is storied in the database
186
  */
187
  public function meta( $key ) {
@@ -196,8 +220,8 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
196
  /* Aliases */
197
 
198
  /**
199
- *
200
- *
201
  * @return array|bool
202
  */
203
  public function children() {
@@ -205,8 +229,8 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
205
  }
206
 
207
  /**
208
- *
209
- *
210
  * @return bool
211
  */
212
  public function external() {
@@ -214,8 +238,14 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
214
  }
215
 
216
  /**
217
- *
218
- *
 
 
 
 
 
 
219
  * @return string a full URL like http://mysite.com/thing/
220
  */
221
  public function link() {
@@ -223,8 +253,13 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
223
  }
224
 
225
  /**
226
- *
227
- *
 
 
 
 
 
228
  * @see get_path()
229
  * @return string the path of a URL like /foo
230
  */
@@ -233,8 +268,9 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
233
  }
234
 
235
  /**
236
- *
237
- *
 
238
  * @see link()
239
  * @return string a full URL like http://mysite.com/thing/
240
  */
@@ -243,8 +279,8 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
243
  }
244
 
245
  /**
246
- *
247
- *
248
  * @see link()
249
  * @return string a full URL like http://mysite.com/thing/
250
  */
@@ -253,8 +289,13 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
253
  }
254
 
255
  /**
256
- *
257
- *
 
 
 
 
 
258
  * @return string the public label like Foo
259
  */
260
  public function title() {
17
  protected $_menu_item_object_id;
18
  protected $_menu_item_url;
19
  protected $menu_object;
20
+ protected $master_object;
21
 
22
  /**
23
  *
24
  *
25
  * @param array|object $data
26
  */
27
+ public function __construct( $data ) {
28
+ $data = (object) $data;
29
  $this->import( $data );
30
  $this->import_classes( $data );
31
  if ( isset( $this->name ) ) {
36
  $this->menu_object = $data;
37
  }
38
 
39
+ /**
40
+ * @return string the label for the menu item
41
+ */
42
+ public function __toString() {
43
  return $this->name();
44
  }
45
 
46
  /**
47
+ * add a class the menu item should have
48
+ * @param string $class_name to be added
 
49
  */
50
+ public function add_class( $class_name ) {
51
  $this->classes[] = $class_name;
52
  $this->class .= ' ' . $class_name;
53
  }
54
 
55
  /**
56
+ * The label for the menu item
57
+ * @api
58
  * @return string
59
  */
60
+ public function name() {
61
+ if ( $title = $this->title() ) {
62
+ return $title;
63
  }
64
  if ( isset( $this->_name ) ) {
65
  return $this->_name;
68
  }
69
 
70
  /**
71
+ * The slug for the menu item
72
+ * @api
73
+ * @example
74
+ * ```twig
75
+ * <ul>
76
+ * {% for item in menu.items %}
77
+ * <li class="{{item.slug}}">
78
+ * <a href="{{item.link}}">{{item.name}}</a>
79
+ * </li>
80
+ * {% endfor %}
81
+ * </ul>
82
+ * @return string the slug of the menu item kinda-like-this
83
  */
84
+ public function slug() {
85
+ if ( !isset( $this->master_object ) ) {
86
+ $this->master_object = $this->get_master_object();
87
  }
88
+ if ( isset( $this->master_object->post_name ) && $this->master_object->post_name ) {
89
+ return $this->master_object->post_name;
90
  }
91
  return $this->post_name;
92
  }
93
 
94
+ /**
95
+ * @internal
96
+ * @return mixed whatever object (Post, Term, etc.) the menu item represents
97
+ */
98
+ protected function get_master_object() {
99
  if ( isset( $this->_menu_item_object_id ) ) {
100
  return new $this->PostClass( $this->_menu_item_object_id );
101
  }
102
  }
103
 
104
  /**
105
+ * @internal
106
+ * @see TimberMenuItem::link
107
+ * @return string an absolute URL http://example.org/my-page
108
  */
109
  function get_link() {
110
  if ( !isset( $this->url ) || !$this->url ) {
118
  }
119
 
120
  /**
121
+ * @internal
122
+ * @see TimberMenuItem::path()
123
+ * @return string a relative url /my-page
124
  */
125
  function get_path() {
126
  return TimberURLHelper::get_rel_url( $this->get_link() );
146
  }
147
  }
148
 
149
+ /**
150
+ *
151
+ * @internal
152
+ * @return bool
153
+ */
154
+ function update_child_levels() {
155
  if (is_array($this->children)) {
156
  foreach( $this->children as $child ) {
157
  $child->level = $this->level + 1;
158
  $child->update_child_levels();
159
  }
160
+ return true;
161
  }
162
  }
163
 
164
  /**
165
  * Imports the classes to be used in CSS
166
+ * @internal
167
  * @param array|object $data
168
  */
169
  function import_classes( $data ) {
178
 
179
  /**
180
  *
181
+ * @internal
182
  * @return array|bool
183
  */
184
  function get_children() {
189
  }
190
 
191
  /**
192
+ * Checks to see if the menu item is an external link so if my site is `example.org`, `google.com/whatever` is an external link. Helpful when creating rules for the target of a link
193
+ * @api
194
+ * @example
195
+ * ```twig
196
+ * <a href="{{ item.link }}" target="{{ item.is_external ? '_blank' : '_self' }}">
197
+ * ```
198
  * @return bool
199
  */
200
  function is_external() {
205
  }
206
 
207
  /**
208
+ * @param string $key lookup key
 
 
209
  * @return mixed whatever value is storied in the database
210
  */
211
  public function meta( $key ) {
220
  /* Aliases */
221
 
222
  /**
223
+ * Get the child [TimberMenuItems](#TimberMenuItem)s of a [TimberMenuItem](#TimberMenuItem)
224
+ * @api
225
  * @return array|bool
226
  */
227
  public function children() {
229
  }
230
 
231
  /**
232
+ * Checks to see if a link is external, helpful when creating rules for the target of a link
233
+ * @see TimberMenuItem::is_external
234
  * @return bool
235
  */
236
  public function external() {
238
  }
239
 
240
  /**
241
+ * Get the full link to a Menu Item
242
+ * @api
243
+ * @example
244
+ * ```twig
245
+ * {% for item in menu.items %}
246
+ * <li><a href="{{ item.link }}">{{ item.title }}</a></li>
247
+ * {% endfor %}
248
+ * ```
249
  * @return string a full URL like http://mysite.com/thing/
250
  */
251
  public function link() {
253
  }
254
 
255
  /**
256
+ * Return the relative path of a Menu Item's link
257
+ * @example
258
+ * ```twig
259
+ * {% for item in menu.items %}
260
+ * <li><a href="{{ item.path }}">{{ item.title }}</a></li>
261
+ * {% endfor %}
262
+ * ```
263
  * @see get_path()
264
  * @return string the path of a URL like /foo
265
  */
268
  }
269
 
270
  /**
271
+ * Gets the link a menu item points at
272
+ * @internal
273
+ * @deprecated since 0.21.7 use link instead
274
  * @see link()
275
  * @return string a full URL like http://mysite.com/thing/
276
  */
279
  }
280
 
281
  /**
282
+ * @internal
283
+ * @deprecated since 0.21.7, use link instead
284
  * @see link()
285
  * @return string a full URL like http://mysite.com/thing/
286
  */
289
  }
290
 
291
  /**
292
+ * Gets the public label for the menu item
293
+ * @example
294
+ * ```twig
295
+ * {% for item in menu.items %}
296
+ * <li><a href="{{ item.link }}">{{ item.title }}</a></li>
297
+ * {% endfor %}
298
+ * ```
299
  * @return string the public label like Foo
300
  */
301
  public function title() {
lib/timber-menu.php CHANGED
@@ -1,19 +1,79 @@
1
  <?php
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  class TimberMenu extends TimberCore {
4
 
5
  public $MenuItemClass = 'TimberMenuItem';
6
  public $PostClass = 'TimberPost';
7
 
 
 
 
 
8
  public $items = null;
9
- public $id = null;
10
- public $ID = null;
11
- public $name = null;
 
 
 
 
 
 
 
 
 
 
 
12
  public $term_id;
 
 
 
 
13
  public $title;
14
 
15
  /**
16
- * @param int $slug
17
  */
18
  function __construct($slug = 0) {
19
  $locations = get_nav_menu_locations();
@@ -30,11 +90,11 @@ class TimberMenu extends TimberCore {
30
  $this->init($menu_id);
31
  } else {
32
  $this->init_as_page_menu();
33
- //TimberHelper::error_log("Sorry, the menu you were looking for wasn't found ('" . $slug . "'). Here's what Timber did find:");
34
  }
35
  }
36
 
37
  /**
 
38
  * @param int $menu_id
39
  */
40
  protected function init($menu_id) {
@@ -53,6 +113,9 @@ class TimberMenu extends TimberCore {
53
  }
54
  }
55
 
 
 
 
56
  protected function init_as_page_menu() {
57
  $menu = get_pages();
58
  if ($menu) {
@@ -68,6 +131,7 @@ class TimberMenu extends TimberCore {
68
  }
69
 
70
  /**
 
71
  * @param string $slug
72
  * @param array $locations
73
  * @return integer
@@ -85,6 +149,7 @@ class TimberMenu extends TimberCore {
85
  }
86
 
87
  /**
 
88
  * @param int $slug
89
  * @return int
90
  */
@@ -123,10 +188,11 @@ class TimberMenu extends TimberCore {
123
  }
124
 
125
  /**
 
126
  * @param array $items
127
  * @return array
128
  */
129
- function order_children($items) {
130
  $index = array();
131
  $menu = array();
132
  foreach ($items as $item) {
1
  <?php
2
 
3
+ /**
4
+ * In Timber, you can use TimberMenu() to make a standard Wordpress menu available to the Twig template as an object you can loop through. And once the menu becomes available to the context, you can get items from it in a way that is a little smoother and more versatile than Wordpress's wp_nav_menu. (You need never again rely on a crazy "Walker Function!"). The first thing to do is to initialize the menu using TimberMenu(). This will make the menu available as an object to work with in the context. (TimberMenu can include a Wordpress menu slug or ID, or it can be sent with no parameter--and guess the right menu.)
5
+ * @example
6
+ * ```php
7
+ * <?php
8
+ * # functions.php
9
+ * add_filter('timber/context', 'add_to_context');
10
+ * function add_to_context($data){
11
+ * // So here you are adding data to Timber's context object, i.e...
12
+ * $data['foo'] = 'I am some other typical value set in your functions.php file, unrelated to the menu';
13
+ * // Now, in similar fashion, you add a Timber menu and send it along to the context.
14
+ * $data['menu'] = new TimberMenu(); // This is where you can also send a WordPress menu slug or ID
15
+ * return $data;
16
+ * }
17
+ *
18
+ * # index.php (or any PHP file)
19
+ * // Since you want a menu object available on every page, I added it to the universal Timber context via the functions.php file. You could also this in each PHP file if you find that too confusing.
20
+ * $context = Timber::get_context();
21
+ * $context['posts'] = Timber::get_posts();
22
+ * Timber::render('index.twig', $context);
23
+ * ?>
24
+ * ```
25
+ *
26
+ * ```twig
27
+ * <nav>
28
+ * <ul class="main-nav">
29
+ * {% for item in menu.get_items %}
30
+ * <li class="nav-main-item {{item.classes | join(' ')}}"><a class="nav-main-link" href="{{item.get_link}}">{{item.title}}</a>
31
+ * {% if item.get_children %}
32
+ * <ul class="nav-drop">
33
+ * {% for child in item.get_children %}
34
+ * <li class="nav-drop-item"><a href="{{child.get_link}}">{{child.title}}</a></li>
35
+ * {% endfor %}
36
+ * </ul>
37
+ * {% endif %}
38
+ * </li>
39
+ * {% endfor %}
40
+ * </ul>
41
+ * </nav>
42
+ * ```
43
+ */
44
  class TimberMenu extends TimberCore {
45
 
46
  public $MenuItemClass = 'TimberMenuItem';
47
  public $PostClass = 'TimberPost';
48
 
49
+ /**
50
+ * @api
51
+ * @var TimberMenuItem[]|null $items you need to iterate through
52
+ */
53
  public $items = null;
54
+ /**
55
+ * @api
56
+ * @var integer $id the ID# of the menu, corresponding to the wp_terms table
57
+ */
58
+ public $id;
59
+ public $ID;
60
+ /**
61
+ * @api
62
+ * @var string $name of the menu (ex: `Main Navigation`)
63
+ */
64
+ public $name;
65
+ /**
66
+ * @var integer $id the ID# of the menu, corresponding to the wp_terms table
67
+ */
68
  public $term_id;
69
+ /**
70
+ * @api
71
+ * @var string $name of the menu (ex: `Main Navigation`)
72
+ */
73
  public $title;
74
 
75
  /**
76
+ * @param int|string $slug
77
  */
78
  function __construct($slug = 0) {
79
  $locations = get_nav_menu_locations();
90
  $this->init($menu_id);
91
  } else {
92
  $this->init_as_page_menu();
 
93
  }
94
  }
95
 
96
  /**
97
+ * @internal
98
  * @param int $menu_id
99
  */
100
  protected function init($menu_id) {
113
  }
114
  }
115
 
116
+ /**
117
+ * @internal
118
+ */
119
  protected function init_as_page_menu() {
120
  $menu = get_pages();
121
  if ($menu) {
131
  }
132
 
133
  /**
134
+ * @internal
135
  * @param string $slug
136
  * @param array $locations
137
  * @return integer
149
  }
150
 
151
  /**
152
+ * @internal
153
  * @param int $slug
154
  * @return int
155
  */
188
  }
189
 
190
  /**
191
+ * @internal
192
  * @param array $items
193
  * @return array
194
  */
195
+ protected function order_children($items) {
196
  $index = array();
197
  $menu = array();
198
  foreach ($items as $item) {
lib/timber-post-getter.php CHANGED
@@ -21,7 +21,7 @@ class TimberPostGetter {
21
 
22
  static function query_post( $query = false, $PostClass = 'TimberPost' ) {
23
  $posts = self::query_posts( $query, $PostClass );
24
- if ( $post = $posts->current() ) {
25
  return $post;
26
  }
27
  }
21
 
22
  static function query_post( $query = false, $PostClass = 'TimberPost' ) {
23
  $posts = self::query_posts( $query, $PostClass );
24
+ if ( method_exists($posts, 'current') && $post = $posts->current() ) {
25
  return $post;
26
  }
27
  }
lib/timber-post.php CHANGED
@@ -1,55 +1,171 @@
1
  <?php
2
 
3
  /**
4
- * This is the object you use to access or extend WordPress posts,
5
- * Think of it as Timber's (more accessible) version of WP_Post
6
- * @package timber
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  */
8
-
9
  class TimberPost extends TimberCore implements TimberCoreInterface {
10
 
 
 
 
11
  public $ImageClass = 'TimberImage';
 
 
 
 
12
  public $PostClass = 'TimberPost';
13
 
 
 
 
14
  public $object_type = 'post';
 
 
 
 
15
  public static $representation = 'post';
16
 
17
- protected $_custom_imported = false;
 
 
 
18
  protected $_content;
 
 
 
 
 
19
  protected $_get_terms;
 
 
 
 
20
  protected $_permalink;
 
 
 
 
21
  protected $_next = array();
 
 
 
 
22
  protected $_prev = array();
23
 
 
 
 
 
24
  public $class;
 
 
 
 
 
25
  public $display_date;
 
 
 
 
 
26
  public $id;
 
 
 
 
27
  public $ID;
 
 
 
 
28
  public $post_author;
 
 
 
 
29
  public $post_content;
 
 
 
 
30
  public $post_date;
 
 
 
 
31
  public $post_excerpt;
 
 
 
 
32
  public $post_parent;
 
 
 
 
 
 
 
 
 
 
33
  public $post_title;
 
 
 
 
 
34
  public $post_type;
 
 
 
 
 
35
  public $slug;
36
 
37
  /**
38
- * If you send the constructor nothing it will try to figure out the current post id based on being inside The_Loop
 
 
 
 
 
39
  * @param mixed $pid
40
  */
41
- function __construct($pid = null) {
42
  $pid = $this->determine_id( $pid );
43
  $this->init($pid);
44
  }
45
 
46
  /**
 
 
47
  * @param mixed a value to test against
48
  * @return int the numberic id we should be using for this post object
49
  */
50
  protected function determine_id($pid) {
51
  global $wp_query;
52
- if ($pid === null &&
53
  isset($wp_query->queried_object_id)
54
  && $wp_query->queried_object_id
55
  && isset($wp_query->queried_object)
@@ -57,13 +173,13 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
57
  && get_class($wp_query->queried_object) == 'WP_Post'
58
  ) {
59
  $pid = $wp_query->queried_object_id;
60
- } else if ($pid === null && $wp_query->is_home && isset($wp_query->queried_object_id) && $wp_query->queried_object_id ) {
61
  //hack for static page as home page
62
  $pid = $wp_query->queried_object_id;
63
- } else if ($pid === null) {
64
  $gtid = false;
65
  $maybe_post = get_post();
66
- if (isset($maybe_post->ID)){
67
  $gtid = true;
68
  }
69
  if ( $gtid ) {
@@ -76,7 +192,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
76
  }
77
  }
78
  }
79
- if ($pid === null && ($pid_from_loop = TimberPostGetter::loop_to_id())) {
80
  $pid = $pid_from_loop;
81
  }
82
  return $pid;
@@ -86,20 +202,21 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
86
  * Outputs the title of the post if you do something like `<h1>{{post}}</h1>`
87
  * @return string
88
  */
89
- function __toString() {
90
  return $this->title();
91
  }
92
 
93
 
94
  /**
95
  * Initializes a TimberPost
 
96
  * @param int|bool $pid
97
  */
98
- function init($pid = false) {
99
- if ($pid === false) {
100
  $pid = get_the_ID();
101
  }
102
- if (is_numeric($pid)) {
103
  $this->ID = $pid;
104
  }
105
  $post_info = $this->get_info($pid);
@@ -113,23 +230,23 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
113
 
114
  /**
115
  * Get the URL that will edit the current post/object
116
- *
 
117
  * @return bool|string
118
  */
119
  function get_edit_url() {
120
- if ($this->can_edit()) {
121
  return get_edit_post_link($this->ID);
122
  }
123
  }
124
 
125
  /**
126
  * updates the post_meta of the current object with the given value
127
- *
128
  * @param string $field
129
  * @param mixed $value
130
  */
131
- public function update($field, $value) {
132
- if (isset($this->ID)) {
133
  update_post_meta($this->ID, $field, $value);
134
  $this->$field = $value;
135
  }
@@ -137,16 +254,17 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
137
 
138
 
139
  /**
140
- * takes a mix of integer (post ID), string (post slug), or object to return a WordPress post object from WP's built-in get_post() function
141
- *
 
142
  * @param mixed $pid
143
  * @return WP_Post on success
144
  */
145
- protected function prepare_post_info($pid = 0) {
146
- if (is_string($pid) || is_numeric($pid) || (is_object($pid) && !isset($pid->post_title)) || $pid === 0) {
147
  $pid = self::check_post_id($pid);
148
  $post = get_post($pid);
149
- if ($post) {
150
  return $post;
151
  } else {
152
  $post = get_page($pid);
@@ -159,21 +277,21 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
159
 
160
 
161
  /**
162
- * helps you find the post id regardless of whether you send a string or whatever
163
- *
164
  * @param integer $pid ;
 
165
  * @return integer ID number of a post
166
  */
167
- protected function check_post_id($pid) {
168
- if (is_numeric($pid) && $pid === 0) {
169
  $pid = get_the_ID();
170
  return $pid;
171
  }
172
- if (!is_numeric($pid) && is_string($pid)) {
173
  $pid = self::get_post_id_by_name($pid);
174
  return $pid;
175
  }
176
- if (!$pid) {
177
  return null;
178
  }
179
  return $pid;
@@ -181,8 +299,8 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
181
 
182
 
183
  /**
184
- * get_post_id_by_name($post_name)
185
- *
186
  * @param string $post_name
187
  * @return int
188
  */
@@ -200,63 +318,68 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
200
  * get a preview of your post, if you have an excerpt it will use that,
201
  * otherwise it will pull from the post_content.
202
  * If there's a <!-- more --> tag it will use that to mark where to pull through.
 
 
 
203
  * <p>{{post.get_preview(50)}}</p>
204
- * @param int $len
205
- * @param bool $force
206
- * @param string $readmore
207
- * @param bool $strip
208
- * @return string
 
209
  */
210
  function get_preview($len = 50, $force = false, $readmore = 'Read More', $strip = true) {
211
  $text = '';
212
  $trimmed = false;
213
- if (isset($this->post_excerpt) && strlen($this->post_excerpt)) {
214
- if ($force) {
215
  $text = TimberHelper::trim_words($this->post_excerpt, $len, false);
216
  $trimmed = true;
217
  } else {
218
  $text = $this->post_excerpt;
219
  }
220
  }
221
- if (!strlen($text) && strpos($this->post_content, '<!--more-->') !== false) {
222
- $pieces = explode('<!--more-->', $this->post_content);
223
  $text = $pieces[0];
224
- if ($force) {
225
  $text = TimberHelper::trim_words($text, $len, false);
226
  $trimmed = true;
227
  }
 
228
  }
229
- if (!strlen($text)) {
230
  $text = TimberHelper::trim_words($this->get_content(), $len, false);
231
  $trimmed = true;
232
  }
233
- if (!strlen(trim($text))) {
234
  return trim($text);
235
  }
236
- if ($strip) {
237
  $text = trim(strip_tags($text));
238
  }
239
- if (strlen($text)) {
240
  $text = trim($text);
241
  $last = $text[strlen($text) - 1];
242
- if ($last != '.' && $trimmed) {
243
  $text .= ' &hellip; ';
244
  }
245
- if (!$strip) {
246
  $last_p_tag = strrpos($text, '</p>');
247
- if ($last_p_tag !== false) {
248
  $text = substr($text, 0, $last_p_tag);
249
  }
250
- if ($last != '.' && $trimmed) {
251
  $text .= ' &hellip; ';
252
  }
253
  }
254
-
255
- if ($readmore) {
256
- $text .= ' <a href="' . $this->get_permalink() . '" class="read-more">' . $readmore . '</a>';
 
257
  }
258
-
259
- if (!$strip) {
260
  $text .= '</p>';
261
  }
262
  }
@@ -265,11 +388,11 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
265
 
266
  /**
267
  * gets the post custom and attaches it to the current object
 
268
  * @param bool|int $pid a post ID number
269
- * @nodoc
270
  */
271
- function import_custom($pid = false) {
272
- if (!$pid) {
273
  $pid = $this->ID;
274
  }
275
  $customs = $this->get_post_custom($pid);
@@ -279,17 +402,18 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
279
  /**
280
  * Used internally to fetch the metadata fields (wp_postmeta table)
281
  * and attach them to our TimberPost object
 
282
  * @param int $pid
283
  * @return array
284
  */
285
- protected function get_post_custom($pid) {
286
  apply_filters('timber_post_get_meta_pre', array(), $pid, $this);
287
  $customs = get_post_custom($pid);
288
- if (!is_array($customs) || empty($customs)) {
289
  return array();
290
  }
291
- foreach ($customs as $key => $value) {
292
- if (is_array($value) && count($value) == 1 && isset($value[0])) {
293
  $value = $value[0];
294
  }
295
  $customs[$key] = maybe_unserialize($value);
@@ -299,28 +423,26 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
299
  }
300
 
301
  /**
302
- * get the featured image as a TimberImage
303
- * In your templates you should just use thumbnail though:
304
- * <img src="{{post.thumbnail.get_src}}" />
305
  * @return null|TimberImage
306
  */
307
  function get_thumbnail() {
308
- if (function_exists('get_post_thumbnail_id')) {
309
  $tid = get_post_thumbnail_id($this->ID);
310
- if ($tid) {
311
  return new $this->ImageClass($tid);
312
  }
313
  }
314
  }
315
 
316
  /**
317
- * get the permalink for a post object
318
- * In your templates you should use link:
319
- * <a href="{{post.link}}">Read my post</a>
320
  * @return string
321
  */
322
  function get_permalink() {
323
- if (isset($this->_permalink)) {
324
  return $this->_permalink;
325
  }
326
  $this->_permalink = get_permalink($this->ID);
@@ -331,6 +453,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
331
  * get the permalink for a post object
332
  * In your templates you should use link:
333
  * <a href="{{post.link}}">Read my post</a>
 
334
  * @return string
335
  */
336
  function get_link() {
@@ -339,22 +462,23 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
339
 
340
  /**
341
  * Get the next post in WordPress's ordering
 
342
  * @param bool $taxonomy
343
  * @return TimberPost|boolean
344
  */
345
- function get_next($taxonomy = false) {
346
- if (!isset($this->_next) || !isset($this->_next[$taxonomy])) {
347
  global $post;
348
  $this->_next = array();
349
  $old_global = $post;
350
  $post = $this;
351
- if ($taxonomy) {
352
  $adjacent = get_adjacent_post(true, '', false, $taxonomy);
353
  } else {
354
  $adjacent = get_adjacent_post(false, '', false);
355
  }
356
 
357
- if ($adjacent) {
358
  $this->_next[$taxonomy] = new $this->PostClass($adjacent);
359
  } else {
360
  $this->_next[$taxonomy] = false;
@@ -372,22 +496,22 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
372
  global $post, $page, $numpages, $multipage;
373
  $post = $this;
374
  $ret = array();
375
- if ($multipage) {
376
- for ($i = 1; $i <= $numpages; $i++) {
377
  $link = self::get_wp_link_page($i);
378
  $data = array('name' => $i, 'title' => $i, 'text' => $i, 'link' => $link);
379
- if ($i == $page) {
380
  $data['current'] = true;
381
  }
382
  $ret['pages'][] = $data;
383
  }
384
  $i = $page - 1;
385
- if ($i) {
386
  $link = self::get_wp_link_page($i);
387
  $ret['prev'] = array('link' => $link);
388
  }
389
  $i = $page + 1;
390
- if ($i <= $numpages) {
391
  $link = self::get_wp_link_page($i);
392
  $ret['next'] = array('link' => $link);
393
  }
@@ -402,7 +526,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
402
  protected static function get_wp_link_page($i) {
403
  $link = _wp_link_page($i);
404
  $link = new SimpleXMLElement($link . '</a>');
405
- if (isset($link['href'])) {
406
  return $link['href'];
407
  }
408
  return '';
@@ -412,6 +536,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
412
  * Get the permalink for a post, but as a relative path
413
  * For example, where {{post.link}} would return "http://example.org/2015/07/04/my-cool-post"
414
  * this will return the relative version: "/2015/07/04/my-cool-post"
 
415
  * @return string
416
  */
417
  function get_path() {
@@ -420,11 +545,12 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
420
 
421
  /**
422
  * Get the next post in WordPress's ordering
 
423
  * @param bool $taxonomy
424
  * @return TimberPost|boolean
425
  */
426
- function get_prev($taxonomy = false) {
427
- if (isset($this->_prev) && isset($this->_prev[$taxonomy])) {
428
  return $this->_prev[$taxonomy];
429
  }
430
  global $post;
@@ -432,9 +558,8 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
432
  $post = $this;
433
  $within_taxonomy = ($taxonomy) ? $taxonomy : 'category';
434
  $adjacent = get_adjacent_post(($taxonomy), '', true, $within_taxonomy);
435
-
436
  $prev_in_taxonomy = false;
437
- if ($adjacent) {
438
  $prev_in_taxonomy = new $this->PostClass($adjacent);
439
  }
440
  $this->_prev[$taxonomy] = $prev_in_taxonomy;
@@ -444,10 +569,11 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
444
 
445
  /**
446
  * Get the parent post of the post
 
447
  * @return bool|TimberPost
448
  */
449
  function get_parent() {
450
- if (!$this->post_parent) {
451
  return false;
452
  }
453
  return new $this->PostClass($this->post_parent);
@@ -455,17 +581,18 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
455
 
456
  /**
457
  * Gets a User object from the author of the post
458
- * <p class="byline">{{post.get_author.name}}</p>
 
459
  * @return bool|TimberUser
460
  */
461
  function get_author() {
462
- if (isset($this->post_author)) {
463
  return new TimberUser($this->post_author);
464
  }
465
  }
466
 
467
  /**
468
- * Get the author (WordPress user) who last modified the post
469
  * @return bool|TimberUser
470
  */
471
  function get_modified_author() {
@@ -475,12 +602,13 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
475
 
476
  /**
477
  * Used internally by init, etc. to build TimberPost object
478
- * @param int $pid
 
479
  * @return null|object|WP_Post
480
  */
481
  protected function get_info($pid) {
482
  $post = $this->prepare_post_info($pid);
483
- if (!isset($post->post_status)) {
484
  return null;
485
  }
486
  $post->status = $post->post_status;
@@ -488,64 +616,71 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
488
  $post->slug = $post->post_name;
489
  $customs = $this->get_post_custom($post->ID);
490
  $post->custom = $customs;
491
- $post = (object)array_merge((array)$customs, (array)$post);
492
  return $post;
493
  }
494
 
495
  /**
496
- * This is deprecated!
497
- * But it would get the human-friendly date that should actually display in a .twig template
 
498
  * @param string $use
499
  * @return string
500
  */
501
- function get_display_date($use = 'post_date') {
502
  return date(get_option('date_format'), strtotime($this->$use));
503
  }
504
 
505
  /**
 
 
506
  * @param string $date_format
507
  * @return string
508
  */
509
- function get_date($date_format = '') {
510
  $df = $date_format ? $date_format : get_option('date_format');
511
  $the_date = (string)mysql2date($df, $this->post_date);
512
- return apply_filters('get_the_date', $the_date, $date_format);
513
  }
514
 
515
  /**
 
516
  * @param string $date_format
517
  * @return string
518
  */
519
- function get_modified_date($date_format = '') {
520
  $df = $date_format ? $date_format : get_option('date_format');
521
- $the_time = $this->get_modified_time($df, null, $this->ID, true);
522
  return apply_filters('get_the_modified_date', $the_time, $date_format);
523
  }
524
 
525
  /**
526
- * @param string $time_format
 
527
  * @return string
528
  */
529
- function get_modified_time($time_format = '') {
530
  $tf = $time_format ? $time_format : get_option('time_format');
531
  $the_time = get_post_modified_time($tf, false, $this->ID, true);
532
  return apply_filters('get_the_modified_time', $the_time, $time_format);
533
  }
534
 
535
  /**
536
- * @param string $post_type
537
- * @param bool $childPostClass
 
 
538
  * @return array
539
  */
540
- function get_children($post_type = 'any', $childPostClass = false) {
541
- if ($childPostClass === false) {
542
  $childPostClass = $this->PostClass;
543
  }
544
- if ($post_type == 'parent') {
545
  $post_type = $this->post_type;
546
  }
547
  $children = get_children('post_parent=' . $this->ID . '&post_type=' . $post_type . '&numberposts=-1&orderby=menu_order title&order=ASC');
548
- foreach ($children as &$child) {
549
  $child = new $childPostClass($child->ID);
550
  }
551
  $children = array_values($children);
@@ -554,9 +689,8 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
554
 
555
  /**
556
  * Get the comments for a post
557
- * {% for comment in post.get_comments %}
558
- * <p>{{comment.content}}</p>
559
- * {% endfor %}
560
  * @param int $ct
561
  * @param string $order
562
  * @param string $type
@@ -564,44 +698,52 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
564
  * @param string $CommentClass
565
  * @return array|mixed
566
  */
 
567
  function get_comments($ct = 0, $order = 'wp', $type = 'comment', $status = 'approve', $CommentClass = 'TimberComment') {
 
 
 
 
568
  $args = array('post_id' => $this->ID, 'status' => $status, 'order' => $order);
569
- if ($ct > 0) {
570
  $args['number'] = $ct;
571
  }
572
- if ($order == 'wp') {
573
  $args['order'] = get_option('comment_order');
574
  }
575
 
576
- $comments = get_comments($args);
577
- $tComments = array();
 
 
 
 
 
578
 
579
  foreach($comments as $key => &$comment) {
580
- $tComment = new $CommentClass($comment);
581
- $tComments[$tComment->id] = $tComment;
582
  }
583
 
584
- foreach($tComments as $key => $comment) {
585
- if ($comment->is_child()) {
586
- unset($tComments[$comment->id]);
587
 
588
- if (isset($tComments[$comment->comment_parent])) {
589
- $tComments[$comment->comment_parent]->children[] = $comment;
590
- }
591
- }
592
- }
593
- $tComments = array_values($tComments);
594
 
595
- return $tComments;
596
- }
 
 
597
 
598
  /**
599
  * Get the categories for a post
600
- * <ul class="categories">
601
- * {% for category in post.get_categories %}
602
- * <li>{{category.name}}</li>
603
- * {% endfor %}
604
- * </ul>
605
  * @return array of TimberTerms
606
  */
607
  function get_categories() {
@@ -609,66 +751,65 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
609
  }
610
 
611
  /**
 
 
612
  * @return mixed
613
  */
614
- function get_category() {
615
  $cats = $this->get_categories();
616
- if (count($cats) && isset($cats[0])) {
617
  return $cats[0];
618
  }
619
  }
620
 
621
- /** # get terms is good
622
- *
623
- */
624
-
625
  /**
 
626
  * @param string $tax
627
  * @param bool $merge
628
  * @param string $TermClass
629
  * @return array
630
  */
631
- function get_terms($tax = '', $merge = true, $TermClass = 'TimberTerm') {
632
- if (is_string($merge) && class_exists($merge)){
633
  $TermClass = $merge;
634
  }
635
- if (is_string($tax)) {
636
- if (isset($this->_get_terms) && isset($this->_get_terms[$tax])) {
637
  return $this->_get_terms[$tax];
638
  }
639
  }
640
- if (!strlen($tax) || $tax == 'all' || $tax == 'any') {
641
  $taxs = get_object_taxonomies($this->post_type);
642
- } else if (is_array($tax)) {
643
  $taxs = $tax;
644
  } else {
645
  $taxs = array($tax);
646
  }
647
  $ret = array();
648
- foreach ($taxs as $tax) {
649
- if ($tax == 'tags' || $tax == 'tag') {
650
  $tax = 'post_tag';
651
- } else if ($tax == 'categories') {
652
  $tax = 'category';
653
  }
654
  $terms = wp_get_post_terms($this->ID, $tax);
655
- if (!is_array($terms) && is_object($terms) && get_class($terms) == 'WP_Error') {
656
  //something is very wrong
657
  TimberHelper::error_log('You have an error retrieving terms on a post in timber-post.php:628');
658
  TimberHelper::error_log('tax = ' . $tax);
659
  TimberHelper::error_log($terms);
660
  } else {
661
- foreach ($terms as &$term) {
662
  $term = new $TermClass($term->term_id, $tax);
663
  }
664
- if ($merge && is_array($terms)) {
665
  $ret = array_merge($ret, $terms);
666
- } else if (count($terms)) {
667
  $ret[$tax] = $terms;
668
  }
669
  }
670
  }
671
- if (!isset($this->_get_terms)) {
672
  $this->_get_terms = array();
673
  }
674
  $this->_get_terms[$tax] = $ret;
@@ -680,12 +821,12 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
680
  * @param string $taxonomy
681
  * @return bool
682
  */
683
- function has_term($term_name_or_id, $taxonomy = 'all') {
684
- if ($taxonomy == 'all' || $taxonomy == 'any') {
685
  $taxes = get_object_taxonomies($this->post_type, 'names');
686
  $ret = false;
687
- foreach ($taxes as $tax) {
688
- if (has_term($term_name_or_id, $tax, $this->ID)) {
689
  $ret = true;
690
  break;
691
  }
@@ -699,20 +840,21 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
699
  * @param string $field
700
  * @return TimberImage
701
  */
702
- function get_image($field) {
703
  return new $this->ImageClass($this->$field);
704
  }
705
 
706
  /**
707
- * ## Gets an array of tags for you to use
708
- * <ul class="tags">
709
- * {% for tag in post.tags %}
710
- * <li>{{tag.name}}</li>
711
- * {% endfor %}
712
- * </ul>
713
- */
714
-
715
- /**
 
716
  * @return array
717
  */
718
  function get_tags() {
@@ -720,11 +862,15 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
720
  }
721
 
722
  /**
723
- * ## Outputs the title with filters applied
724
- * <h1>{{post.get_title}}</h1>
725
- */
726
-
727
- /**
 
 
 
 
728
  * @return string
729
  */
730
  function get_title() {
@@ -732,32 +878,35 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
732
  }
733
 
734
  /**
735
- * ## Displays the content of the post with filters, shortcodes and wpautop applied
736
- * <div class="article-text">{{post.get_content}}</div>
737
- */
738
-
739
- /**
 
 
 
740
  * @param int $len
741
  * @param int $page
742
  * @return string
743
  */
744
- function get_content($len = 0, $page = 0) {
745
- if ($len == 0 && $page == 0 && $this->_content) {
746
  return $this->_content;
747
  }
748
  $content = $this->post_content;
749
- if ($len) {
750
  $content = wp_trim_words($content, $len);
751
  }
752
- if ($page) {
753
  $contents = explode('<!--nextpage-->', $content);
754
  $page--;
755
- if (count($contents) > $page) {
756
  $content = $contents[$page];
757
  }
758
  }
759
  $content = apply_filters('the_content', ($content));
760
- if ($len == 0 && $page == 0) {
761
  $this->_content = $content;
762
  }
763
  return $content;
@@ -771,6 +920,16 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
771
  return $this->get_content(0, $page);
772
  }
773
  /**
 
 
 
 
 
 
 
 
 
 
774
  * @return mixed
775
  */
776
  public function get_post_type() {
@@ -781,7 +940,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
781
  * @return int
782
  */
783
  public function get_comment_count() {
784
- if (isset($this->ID)) {
785
  return get_comments_number($this->ID);
786
  } else {
787
  return 0;
@@ -792,14 +951,14 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
792
  * @param string $field_name
793
  * @return mixed
794
  */
795
- public function get_field($field_name) {
796
  $value = apply_filters('timber_post_get_meta_field_pre', null, $this->ID, $field_name, $this);
797
- if ($value === null) {
798
  $value = get_post_meta($this->ID, $field_name);
799
- if (is_array($value) && count($value) == 1) {
800
  $value = $value[0];
801
  }
802
- if (is_array($value) && count($value) == 0) {
803
  $value = null;
804
  }
805
  }
@@ -810,11 +969,12 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
810
  /**
811
  * @param string $field_name
812
  */
813
- function import_field($field_name) {
814
  $this->$field_name = $this->get_field($field_name);
815
  }
816
 
817
  /**
 
818
  * @return mixed
819
  */
820
  function get_format() {
@@ -822,16 +982,31 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
822
  }
823
 
824
  /**
825
- * @param string $class
826
- * @return string
827
- */
828
- public function post_class($class='') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
829
  global $post;
830
  $old_global_post = $post;
831
  $post = $this;
832
  $class_array = get_post_class($class, $this->ID);
833
  $post = $old_global_post;
834
- if (is_array($class_array)){
835
  return implode(' ', $class_array);
836
  }
837
  return $class_array;
@@ -866,36 +1041,71 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
866
  return $ret;
867
  }
868
 
869
- // Aliases
870
  /**
871
- * @return bool|TimberUser
 
 
 
 
 
 
 
 
 
872
  */
873
  public function author() {
874
  return $this->get_author();
875
  }
876
 
877
  /**
878
- * @return bool|TimberUser
 
 
 
 
 
 
 
 
879
  */
880
  public function modified_author() {
881
  return $this->get_modified_author();
882
  }
883
 
884
  /**
885
- * @return array
 
 
886
  */
887
  public function categories() {
888
  return $this->get_terms('category');
889
  }
890
 
891
  /**
892
- * @return mixed
 
 
 
893
  */
894
  public function category() {
895
  return $this->get_category();
896
  }
897
 
898
  /**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
  * @return array
900
  */
901
  public function children( $post_type = 'any', $childPostClass = false ) {
@@ -903,17 +1113,44 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
903
  }
904
 
905
  /**
906
- * @return mixed
907
- */
908
- public function comments() {
909
- return $this->get_comments();
910
- }
911
-
912
- /**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
  * @param int $page
914
  * @return string
915
  */
916
- public function content($page = 0) {
917
  return $this->get_content(0, $page);
918
  }
919
 
@@ -925,10 +1162,24 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
925
  }
926
 
927
  /**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
928
  * @param string $date_format
929
  * @return string
930
  */
931
- public function date($date_format = '') {
932
  return $this->get_date($date_format);
933
  }
934
 
@@ -940,6 +1191,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
940
  }
941
 
942
  /**
 
943
  * @return mixed
944
  */
945
  public function format() {
@@ -947,7 +1199,13 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
947
  }
948
 
949
  /**
950
- * @return string
 
 
 
 
 
 
951
  */
952
  public function link() {
953
  return $this->get_permalink();
@@ -957,8 +1215,9 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
957
  * @param string $field_name
958
  * @return mixed
959
  */
960
- public function meta($field_name = null) {
961
- if ($field_name == null) {
 
962
  $field_name = 'meta';
963
  }
964
  return $this->get_field($field_name);
@@ -975,7 +1234,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
975
  * @param string $date_format
976
  * @return string
977
  */
978
- public function modified_date($date_format = '') {
979
  return $this->get_modified_date($date_format);
980
  }
981
 
@@ -983,15 +1242,16 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
983
  * @param string $time_format
984
  * @return string
985
  */
986
- public function modified_time($time_format = '') {
987
  return $this->get_modified_time($time_format);
988
  }
989
 
990
  /**
 
991
  * @param bool $in_same_cat
992
  * @return mixed
993
  */
994
- public function next($in_same_cat = false) {
995
  return $this->get_next($in_same_cat);
996
  }
997
 
@@ -1003,6 +1263,12 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
1003
  }
1004
 
1005
  /**
 
 
 
 
 
 
1006
  * @return bool|TimberPost
1007
  */
1008
  public function parent() {
@@ -1010,6 +1276,13 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
1010
  }
1011
 
1012
  /**
 
 
 
 
 
 
 
1013
  * @return string
1014
  */
1015
  public function path() {
@@ -1017,6 +1290,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
1017
  }
1018
 
1019
  /**
 
1020
  * @return string
1021
  */
1022
  public function permalink() {
@@ -1024,22 +1298,36 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
1024
  }
1025
 
1026
  /**
 
 
 
 
 
 
 
 
1027
  * @param bool $in_same_cat
1028
  * @return mixed
1029
  */
1030
- public function prev($in_same_cat = false) {
1031
  return $this->get_prev($in_same_cat);
1032
  }
1033
 
1034
  /**
1035
- * @param string $tax
 
 
 
 
1036
  * @return array
1037
  */
1038
- public function terms($tax = '') {
1039
- return $this->get_terms($tax);
1040
  }
1041
 
1042
  /**
 
 
1043
  * @return array
1044
  */
1045
  public function tags() {
@@ -1047,13 +1335,25 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
1047
  }
1048
 
1049
  /**
1050
- * @return null|TimberImage
 
 
 
 
 
 
1051
  */
1052
  public function thumbnail() {
1053
  return $this->get_thumbnail();
1054
  }
1055
 
1056
  /**
 
 
 
 
 
 
1057
  * @return string
1058
  */
1059
  public function title() {
1
  <?php
2
 
3
  /**
4
+ * This is the object you use to access or extend WordPress posts. Think of it as Timber's (more accessible) version of WP_Post. This is used throughout Timber to represent posts retrieved from WordPress making them available to Twig templates. See the PHP and Twig examples for an example of what it's like to work with this object in your code.
5
+ * @example
6
+ * ```php
7
+ * <?php
8
+ * // single.php, see connected twig example
9
+ * $context = Timber::get_context();
10
+ * $context['post'] = new TimberPost(); // It's a new TimberPost object, but an existing post from WordPress.
11
+ * Timber::render('single.twig', $context);
12
+ * ?>
13
+ * ```
14
+ * ```twig
15
+ * {# single.twig #}
16
+ * <article>
17
+ * <h1 class="headline">{{post.post_title}}</h1>
18
+ * <div class="body">
19
+ * {{post.content}}
20
+ * </div>
21
+ * </article>
22
+ * ```
23
+ *
24
+ * @package Timber
25
  */
 
26
  class TimberPost extends TimberCore implements TimberCoreInterface {
27
 
28
+ /**
29
+ * @var string $ImageClass the name of the class to handle images by default
30
+ */
31
  public $ImageClass = 'TimberImage';
32
+
33
+ /**
34
+ * @var string $PostClass the name of the class to handle posts by default
35
+ */
36
  public $PostClass = 'TimberPost';
37
 
38
+ /**
39
+ * @var string $object_type what does this class represent in WordPress terms?
40
+ */
41
  public $object_type = 'post';
42
+
43
+ /**
44
+ * @var string $representation what does this class represent in WordPress terms?
45
+ */
46
  public static $representation = 'post';
47
 
48
+ /**
49
+ * @internal
50
+ * @var string $_content stores the processed content internally
51
+ */
52
  protected $_content;
53
+
54
+ /**
55
+ * @internal
56
+ * @var array $_get_terms stores the results of a get_terms method call
57
+ */
58
  protected $_get_terms;
59
+
60
+ /**
61
+ * @var string $_permalink the returned permalink from WP's get_permalink function
62
+ */
63
  protected $_permalink;
64
+
65
+ /**
66
+ * @var array $_next stores the results of the next TimberPost in a set inside an array (in order to manage by-taxonomy)
67
+ */
68
  protected $_next = array();
69
+
70
+ /**
71
+ * @var array $_prev stores the results of the previous TimberPost in a set inside an array (in order to manage by-taxonomy)
72
+ */
73
  protected $_prev = array();
74
 
75
+ /**
76
+ * @api
77
+ * @var string $class stores the CSS classes for the post (ex: "post post-type-book post-123")
78
+ */
79
  public $class;
80
+
81
+ /**
82
+ * @deprecated since 0.21.7
83
+ * @var string $display_date @deprecated stores the display date (ex: "October 6, 1984"),
84
+ */
85
  public $display_date;
86
+
87
+ /**
88
+ * @api
89
+ * @var string $id the numeric WordPress id of a post
90
+ */
91
  public $id;
92
+
93
+ /**
94
+ * @var string $ID the numeric WordPress id of a post, capitalized to match WP usage
95
+ */
96
  public $ID;
97
+
98
+ /**
99
+ * @var int $post_author the numeric ID of the a post's author corresponding to the wp_user dtable
100
+ */
101
  public $post_author;
102
+
103
+ /**
104
+ * @var string $post_content the raw text of a WP post as stored in the database
105
+ */
106
  public $post_content;
107
+
108
+ /**
109
+ * @var string $post_date the raw date string as stored in the WP database, ex: 2014-07-05 18:01:39
110
+ */
111
  public $post_date;
112
+
113
+ /**
114
+ * @var string $post_exceprt the raw text of a manual post exceprt as stored in the database
115
+ */
116
  public $post_excerpt;
117
+
118
+ /**
119
+ * @var int $post_parent the numeric ID of a post's parent post
120
+ */
121
  public $post_parent;
122
+
123
+ /**
124
+ * @api
125
+ * @var string $post_status the status of a post ("draft", "publish", etc.)
126
+ */
127
+ public $post_status;
128
+
129
+ /**
130
+ * @var string $post_title the raw text of a post's title as stored in the database
131
+ */
132
  public $post_title;
133
+
134
+ /**
135
+ * @api
136
+ * @var string $post_type the name of the post type, this is the machine name (so "my_custom_post_type" as opposed to "My Custom Post Type")
137
+ */
138
  public $post_type;
139
+
140
+ /**
141
+ * @api
142
+ * @var string $slug the URL-safe slug, this corresponds to the poorly-named "post_name" in the WP database, ex: "hello-world"
143
+ */
144
  public $slug;
145
 
146
  /**
147
+ * If you send the constructor nothing it will try to figure out the current post id based on being inside The_Loop
148
+ * @example
149
+ * ```php
150
+ * $post = new TimberPost();
151
+ * $other_post = new TimberPost($random_post_id);
152
+ * ```
153
  * @param mixed $pid
154
  */
155
+ public function __construct($pid = null) {
156
  $pid = $this->determine_id( $pid );
157
  $this->init($pid);
158
  }
159
 
160
  /**
161
+ * tries to figure out what post you want to get if not explictly defined (or if it is, allows it to be passed through)
162
+ * @internal
163
  * @param mixed a value to test against
164
  * @return int the numberic id we should be using for this post object
165
  */
166
  protected function determine_id($pid) {
167
  global $wp_query;
168
+ if ( $pid === null &&
169
  isset($wp_query->queried_object_id)
170
  && $wp_query->queried_object_id
171
  && isset($wp_query->queried_object)
173
  && get_class($wp_query->queried_object) == 'WP_Post'
174
  ) {
175
  $pid = $wp_query->queried_object_id;
176
+ } else if ( $pid === null && $wp_query->is_home && isset($wp_query->queried_object_id) && $wp_query->queried_object_id ) {
177
  //hack for static page as home page
178
  $pid = $wp_query->queried_object_id;
179
+ } else if ( $pid === null ) {
180
  $gtid = false;
181
  $maybe_post = get_post();
182
+ if ( isset($maybe_post->ID) ){
183
  $gtid = true;
184
  }
185
  if ( $gtid ) {
192
  }
193
  }
194
  }
195
+ if ( $pid === null && ($pid_from_loop = TimberPostGetter::loop_to_id()) ) {
196
  $pid = $pid_from_loop;
197
  }
198
  return $pid;
202
  * Outputs the title of the post if you do something like `<h1>{{post}}</h1>`
203
  * @return string
204
  */
205
+ public function __toString() {
206
  return $this->title();
207
  }
208
 
209
 
210
  /**
211
  * Initializes a TimberPost
212
+ * @internal
213
  * @param int|bool $pid
214
  */
215
+ protected function init($pid = false) {
216
+ if ( $pid === false ) {
217
  $pid = get_the_ID();
218
  }
219
+ if ( is_numeric($pid) ) {
220
  $this->ID = $pid;
221
  }
222
  $post_info = $this->get_info($pid);
230
 
231
  /**
232
  * Get the URL that will edit the current post/object
233
+ * @internal
234
+ * @see TimberPost::edit_link
235
  * @return bool|string
236
  */
237
  function get_edit_url() {
238
+ if ( $this->can_edit() ) {
239
  return get_edit_post_link($this->ID);
240
  }
241
  }
242
 
243
  /**
244
  * updates the post_meta of the current object with the given value
 
245
  * @param string $field
246
  * @param mixed $value
247
  */
248
+ public function update( $field, $value ) {
249
+ if ( isset($this->ID) ) {
250
  update_post_meta($this->ID, $field, $value);
251
  $this->$field = $value;
252
  }
254
 
255
 
256
  /**
257
+ * takes a mix of integer (post ID), string (post slug),
258
+ * or object to return a WordPress post object from WP's built-in get_post() function
259
+ * @internal
260
  * @param mixed $pid
261
  * @return WP_Post on success
262
  */
263
+ protected function prepare_post_info( $pid = 0 ) {
264
+ if ( is_string($pid) || is_numeric($pid) || (is_object($pid) && !isset($pid->post_title)) || $pid === 0 ) {
265
  $pid = self::check_post_id($pid);
266
  $post = get_post($pid);
267
+ if ( $post ) {
268
  return $post;
269
  } else {
270
  $post = get_page($pid);
277
 
278
 
279
  /**
280
+ * helps you find the post id regardless of whether you send a string or whatever
 
281
  * @param integer $pid ;
282
+ * @internal
283
  * @return integer ID number of a post
284
  */
285
+ protected function check_post_id( $pid ) {
286
+ if ( is_numeric($pid) && $pid === 0 ) {
287
  $pid = get_the_ID();
288
  return $pid;
289
  }
290
+ if ( !is_numeric($pid) && is_string($pid) ) {
291
  $pid = self::get_post_id_by_name($pid);
292
  return $pid;
293
  }
294
+ if ( !$pid ) {
295
  return null;
296
  }
297
  return $pid;
299
 
300
 
301
  /**
302
+ * get_post_id_by_name($post_name)
303
+ * @internal
304
  * @param string $post_name
305
  * @return int
306
  */
318
  * get a preview of your post, if you have an excerpt it will use that,
319
  * otherwise it will pull from the post_content.
320
  * If there's a <!-- more --> tag it will use that to mark where to pull through.
321
+ * @api
322
+ * @example
323
+ * ```twig
324
  * <p>{{post.get_preview(50)}}</p>
325
+ * ```
326
+ * @param int $len The number of words that WP should use to make the tease. (Isn't this better than [this mess](http://wordpress.org/support/topic/changing-the-default-length-of-the_excerpt-1?replies=14)?). If you've set a post_excerpt on a post, we'll use that for the preview text; otherwise the first X words of the post_content
327
+ * @param bool $force What happens if your custom post excerpt is longer then the length requested? By default (`$force = false`) it will use the full `post_excerpt`. However, you can set this to true to *force* your excerpt to be of the desired length
328
+ * @param string $readmore The text you want to use on the 'readmore' link
329
+ * @param bool $strip Strip tags? yes or no. tell me!
330
+ * @return string of the post preview
331
  */
332
  function get_preview($len = 50, $force = false, $readmore = 'Read More', $strip = true) {
333
  $text = '';
334
  $trimmed = false;
335
+ if ( isset($this->post_excerpt) && strlen($this->post_excerpt) ) {
336
+ if ( $force ) {
337
  $text = TimberHelper::trim_words($this->post_excerpt, $len, false);
338
  $trimmed = true;
339
  } else {
340
  $text = $this->post_excerpt;
341
  }
342
  }
343
+ if ( !strlen($text) && preg_match('/<!--\s?more(.*?)?-->/', $this->post_content, $readmore_matches) ) {
344
+ $pieces = explode($readmore_matches[0], $this->post_content);
345
  $text = $pieces[0];
346
+ if ( $force ) {
347
  $text = TimberHelper::trim_words($text, $len, false);
348
  $trimmed = true;
349
  }
350
+ $text = do_shortcode( $text );
351
  }
352
+ if ( !strlen($text) ) {
353
  $text = TimberHelper::trim_words($this->get_content(), $len, false);
354
  $trimmed = true;
355
  }
356
+ if ( !strlen(trim($text)) ) {
357
  return trim($text);
358
  }
359
+ if ( $strip ) {
360
  $text = trim(strip_tags($text));
361
  }
362
+ if ( strlen($text) ) {
363
  $text = trim($text);
364
  $last = $text[strlen($text) - 1];
365
+ if ( $last != '.' && $trimmed ) {
366
  $text .= ' &hellip; ';
367
  }
368
+ if ( !$strip ) {
369
  $last_p_tag = strrpos($text, '</p>');
370
+ if ( $last_p_tag !== false ) {
371
  $text = substr($text, 0, $last_p_tag);
372
  }
373
+ if ( $last != '.' && $trimmed ) {
374
  $text .= ' &hellip; ';
375
  }
376
  }
377
+ if ( $readmore && isset($readmore_matches) && !empty($readmore_matches[1]) ) {
378
+ $text .= ' <a href="' . $this->get_permalink() . '" class="read-more">' . trim($readmore_matches[1]) . '</a>';
379
+ } elseif ( $readmore ) {
380
+ $text .= ' <a href="' . $this->get_permalink() . '" class="read-more">' . trim($readmore) . '</a>';
381
  }
382
+ if ( !$strip ) {
 
383
  $text .= '</p>';
384
  }
385
  }
388
 
389
  /**
390
  * gets the post custom and attaches it to the current object
391
+ * @internal
392
  * @param bool|int $pid a post ID number
 
393
  */
394
+ function import_custom( $pid = false ) {
395
+ if ( !$pid ) {
396
  $pid = $this->ID;
397
  }
398
  $customs = $this->get_post_custom($pid);
402
  /**
403
  * Used internally to fetch the metadata fields (wp_postmeta table)
404
  * and attach them to our TimberPost object
405
+ * @internal
406
  * @param int $pid
407
  * @return array
408
  */
409
+ protected function get_post_custom( $pid ) {
410
  apply_filters('timber_post_get_meta_pre', array(), $pid, $this);
411
  $customs = get_post_custom($pid);
412
+ if ( !is_array($customs) || empty($customs) ) {
413
  return array();
414
  }
415
+ foreach ( $customs as $key => $value ) {
416
+ if ( is_array($value) && count($value) == 1 && isset($value[0]) ) {
417
  $value = $value[0];
418
  }
419
  $customs[$key] = maybe_unserialize($value);
423
  }
424
 
425
  /**
426
+ * @internal
427
+ * @see TimberPost::thumbnail
 
428
  * @return null|TimberImage
429
  */
430
  function get_thumbnail() {
431
+ if ( function_exists('get_post_thumbnail_id') ) {
432
  $tid = get_post_thumbnail_id($this->ID);
433
+ if ( $tid ) {
434
  return new $this->ImageClass($tid);
435
  }
436
  }
437
  }
438
 
439
  /**
440
+ * @internal
441
+ * @see TimberPost::link
 
442
  * @return string
443
  */
444
  function get_permalink() {
445
+ if ( isset($this->_permalink) ) {
446
  return $this->_permalink;
447
  }
448
  $this->_permalink = get_permalink($this->ID);
453
  * get the permalink for a post object
454
  * In your templates you should use link:
455
  * <a href="{{post.link}}">Read my post</a>
456
+ * @internal
457
  * @return string
458
  */
459
  function get_link() {
462
 
463
  /**
464
  * Get the next post in WordPress's ordering
465
+ * @internal
466
  * @param bool $taxonomy
467
  * @return TimberPost|boolean
468
  */
469
+ function get_next( $taxonomy = false ) {
470
+ if ( !isset($this->_next) || !isset($this->_next[$taxonomy]) ) {
471
  global $post;
472
  $this->_next = array();
473
  $old_global = $post;
474
  $post = $this;
475
+ if ( $taxonomy ) {
476
  $adjacent = get_adjacent_post(true, '', false, $taxonomy);
477
  } else {
478
  $adjacent = get_adjacent_post(false, '', false);
479
  }
480
 
481
+ if ( $adjacent ) {
482
  $this->_next[$taxonomy] = new $this->PostClass($adjacent);
483
  } else {
484
  $this->_next[$taxonomy] = false;
496
  global $post, $page, $numpages, $multipage;
497
  $post = $this;
498
  $ret = array();
499
+ if ( $multipage ) {
500
+ for ( $i = 1; $i <= $numpages; $i++ ) {
501
  $link = self::get_wp_link_page($i);
502
  $data = array('name' => $i, 'title' => $i, 'text' => $i, 'link' => $link);
503
+ if ( $i == $page ) {
504
  $data['current'] = true;
505
  }
506
  $ret['pages'][] = $data;
507
  }
508
  $i = $page - 1;
509
+ if ( $i ) {
510
  $link = self::get_wp_link_page($i);
511
  $ret['prev'] = array('link' => $link);
512
  }
513
  $i = $page + 1;
514
+ if ( $i <= $numpages ) {
515
  $link = self::get_wp_link_page($i);
516
  $ret['next'] = array('link' => $link);
517
  }
526
  protected static function get_wp_link_page($i) {
527
  $link = _wp_link_page($i);
528
  $link = new SimpleXMLElement($link . '</a>');
529
+ if ( isset($link['href']) ) {
530
  return $link['href'];
531
  }
532
  return '';
536
  * Get the permalink for a post, but as a relative path
537
  * For example, where {{post.link}} would return "http://example.org/2015/07/04/my-cool-post"
538
  * this will return the relative version: "/2015/07/04/my-cool-post"
539
+ * @internal
540
  * @return string
541
  */
542
  function get_path() {
545
 
546
  /**
547
  * Get the next post in WordPress's ordering
548
+ * @internal
549
  * @param bool $taxonomy
550
  * @return TimberPost|boolean
551
  */
552
+ function get_prev( $taxonomy = false ) {
553
+ if ( isset($this->_prev) && isset($this->_prev[$taxonomy]) ) {
554
  return $this->_prev[$taxonomy];
555
  }
556
  global $post;
558
  $post = $this;
559
  $within_taxonomy = ($taxonomy) ? $taxonomy : 'category';
560
  $adjacent = get_adjacent_post(($taxonomy), '', true, $within_taxonomy);
 
561
  $prev_in_taxonomy = false;
562
+ if ( $adjacent ) {
563
  $prev_in_taxonomy = new $this->PostClass($adjacent);
564
  }
565
  $this->_prev[$taxonomy] = $prev_in_taxonomy;
569
 
570
  /**
571
  * Get the parent post of the post
572
+ * @internal
573
  * @return bool|TimberPost
574
  */
575
  function get_parent() {
576
+ if ( !$this->post_parent ) {
577
  return false;
578
  }
579
  return new $this->PostClass($this->post_parent);
581
 
582
  /**
583
  * Gets a User object from the author of the post
584
+ * @internal
585
+ * @see TimberPost::author
586
  * @return bool|TimberUser
587
  */
588
  function get_author() {
589
+ if ( isset($this->post_author) ) {
590
  return new TimberUser($this->post_author);
591
  }
592
  }
593
 
594
  /**
595
+ * @internal
596
  * @return bool|TimberUser
597
  */
598
  function get_modified_author() {
602
 
603
  /**
604
  * Used internally by init, etc. to build TimberPost object
605
+ * @internal
606
+ * @param int $pid
607
  * @return null|object|WP_Post
608
  */
609
  protected function get_info($pid) {
610
  $post = $this->prepare_post_info($pid);
611
+ if ( !isset($post->post_status) ) {
612
  return null;
613
  }
614
  $post->status = $post->post_status;
616
  $post->slug = $post->post_name;
617
  $customs = $this->get_post_custom($post->ID);
618
  $post->custom = $customs;
619
+ $post = (object) array_merge((array)$customs, (array)$post);
620
  return $post;
621
  }
622
 
623
  /**
624
+ * Get the human-friendly date that should actually display in a .twig template
625
+ * @deprecated since 0.20.0
626
+ * @see TimberPost::date
627
  * @param string $use
628
  * @return string
629
  */
630
+ function get_display_date( $use = 'post_date' ) {
631
  return date(get_option('date_format'), strtotime($this->$use));
632
  }
633
 
634
  /**
635
+ * @internal
636
+ * @see TimberPost::date
637
  * @param string $date_format
638
  * @return string
639
  */
640
+ function get_date( $date_format = '' ) {
641
  $df = $date_format ? $date_format : get_option('date_format');
642
  $the_date = (string)mysql2date($df, $this->post_date);
643
+ return apply_filters('get_the_date', $the_date, $df);
644
  }
645
 
646
  /**
647
+ * @internal
648
  * @param string $date_format
649
  * @return string
650
  */
651
+ function get_modified_date( $date_format = '' ) {
652
  $df = $date_format ? $date_format : get_option('date_format');
653
+ $the_time = $this->get_modified_time($df);
654
  return apply_filters('get_the_modified_date', $the_time, $date_format);
655
  }
656
 
657
  /**
658
+ * @internal
659
+ * @param string $time_format
660
  * @return string
661
  */
662
+ function get_modified_time( $time_format = '' ) {
663
  $tf = $time_format ? $time_format : get_option('time_format');
664
  $the_time = get_post_modified_time($tf, false, $this->ID, true);
665
  return apply_filters('get_the_modified_time', $the_time, $time_format);
666
  }
667
 
668
  /**
669
+ * @internal
670
+ * @see TimberPost::children
671
+ * @param string $post_type
672
+ * @param bool|string $childPostClass
673
  * @return array
674
  */
675
+ function get_children( $post_type = 'any', $childPostClass = false ) {
676
+ if ( $childPostClass === false ) {
677
  $childPostClass = $this->PostClass;
678
  }
679
+ if ( $post_type == 'parent' ) {
680
  $post_type = $this->post_type;
681
  }
682
  $children = get_children('post_parent=' . $this->ID . '&post_type=' . $post_type . '&numberposts=-1&orderby=menu_order title&order=ASC');
683
+ foreach ( $children as &$child ) {
684
  $child = new $childPostClass($child->ID);
685
  }
686
  $children = array_values($children);
689
 
690
  /**
691
  * Get the comments for a post
692
+ * @internal
693
+ * @see TimberPost::comments
 
694
  * @param int $ct
695
  * @param string $order
696
  * @param string $type
698
  * @param string $CommentClass
699
  * @return array|mixed
700
  */
701
+
702
  function get_comments($ct = 0, $order = 'wp', $type = 'comment', $status = 'approve', $CommentClass = 'TimberComment') {
703
+
704
+ global $overridden_cpage;
705
+ $overridden_cpage = false;
706
+
707
  $args = array('post_id' => $this->ID, 'status' => $status, 'order' => $order);
708
+ if ( $ct > 0 ) {
709
  $args['number'] = $ct;
710
  }
711
+ if ( strtolower($order) == 'wp' || strtolower($order) == 'wordpress' ) {
712
  $args['order'] = get_option('comment_order');
713
  }
714
 
715
+ $comments = get_comments($args);
716
+ $timber_comments = array();
717
+
718
+ if ( '' == get_query_var('cpage') && get_option('page_comments') ) {
719
+ set_query_var( 'cpage', 'newest' == get_option('default_comments_page') ? get_comment_pages_count() : 1 );
720
+ $overridden_cpage = true;
721
+ }
722
 
723
  foreach($comments as $key => &$comment) {
724
+ $timber_comment = new $CommentClass($comment);
725
+ $timber_comments[$timber_comment->id] = $timber_comment;
726
  }
727
 
728
+ foreach( $timber_comments as $key => $comment ) {
729
+ if ( $comment->is_child() ) {
730
+ unset($timber_comments[$comment->id]);
731
 
732
+ if ( isset($timber_comments[$comment->comment_parent]) ) {
733
+ $timber_comments[$comment->comment_parent]->children[] = $comment;
734
+ }
735
+ }
736
+ }
 
737
 
738
+ $timber_comments = array_values($timber_comments);
739
+
740
+ return $timber_comments;
741
+ }
742
 
743
  /**
744
  * Get the categories for a post
745
+ * @internal
746
+ * @see TimberPost::categories
 
 
 
747
  * @return array of TimberTerms
748
  */
749
  function get_categories() {
751
  }
752
 
753
  /**
754
+ * @internal
755
+ * @see TimberPost::category
756
  * @return mixed
757
  */
758
+ function get_category( ) {
759
  $cats = $this->get_categories();
760
+ if ( count($cats) && isset($cats[0]) ) {
761
  return $cats[0];
762
  }
763
  }
764
 
 
 
 
 
765
  /**
766
+ * @internal
767
  * @param string $tax
768
  * @param bool $merge
769
  * @param string $TermClass
770
  * @return array
771
  */
772
+ function get_terms( $tax = '', $merge = true, $TermClass = 'TimberTerm' ) {
773
+ if ( is_string($merge) && class_exists($merge) ) {
774
  $TermClass = $merge;
775
  }
776
+ if ( is_string($tax) ) {
777
+ if ( isset($this->_get_terms) && isset($this->_get_terms[$tax]) ) {
778
  return $this->_get_terms[$tax];
779
  }
780
  }
781
+ if ( !strlen($tax) || $tax == 'all' || $tax == 'any' ) {
782
  $taxs = get_object_taxonomies($this->post_type);
783
+ } else if ( is_array($tax) ) {
784
  $taxs = $tax;
785
  } else {
786
  $taxs = array($tax);
787
  }
788
  $ret = array();
789
+ foreach ( $taxs as $tax ) {
790
+ if ( $tax == 'tags' || $tax == 'tag' ) {
791
  $tax = 'post_tag';
792
+ } else if ( $tax == 'categories' ) {
793
  $tax = 'category';
794
  }
795
  $terms = wp_get_post_terms($this->ID, $tax);
796
+ if ( !is_array($terms) && is_object($terms) && get_class($terms) == 'WP_Error' ) {
797
  //something is very wrong
798
  TimberHelper::error_log('You have an error retrieving terms on a post in timber-post.php:628');
799
  TimberHelper::error_log('tax = ' . $tax);
800
  TimberHelper::error_log($terms);
801
  } else {
802
+ foreach ( $terms as &$term ) {
803
  $term = new $TermClass($term->term_id, $tax);
804
  }
805
+ if ( $merge && is_array($terms) ) {
806
  $ret = array_merge($ret, $terms);
807
+ } else if ( count($terms) ) {
808
  $ret[$tax] = $terms;
809
  }
810
  }
811
  }
812
+ if ( !isset($this->_get_terms) ) {
813
  $this->_get_terms = array();
814
  }
815
  $this->_get_terms[$tax] = $ret;
821
  * @param string $taxonomy
822
  * @return bool
823
  */
824
+ function has_term( $term_name_or_id, $taxonomy = 'all' ) {
825
+ if ( $taxonomy == 'all' || $taxonomy == 'any' ) {
826
  $taxes = get_object_taxonomies($this->post_type, 'names');
827
  $ret = false;
828
+ foreach ( $taxes as $tax ) {
829
+ if ( has_term($term_name_or_id, $tax, $this->ID) ) {
830
  $ret = true;
831
  break;
832
  }
840
  * @param string $field
841
  * @return TimberImage
842
  */
843
+ function get_image( $field ) {
844
  return new $this->ImageClass($this->$field);
845
  }
846
 
847
  /**
848
+ * Gets an array of tags for you to use
849
+ * @internal
850
+ * @example
851
+ * ```twig
852
+ * <ul class="tags">
853
+ * {% for tag in post.tags %}
854
+ * <li>{{tag.name}}</li>
855
+ * {% endfor %}
856
+ * </ul>
857
+ * ```
858
  * @return array
859
  */
860
  function get_tags() {
862
  }
863
 
864
  /**
865
+ * Outputs the title with filters applied
866
+ * @internal
867
+ * @example
868
+ * ```twig
869
+ * <h1>{{post.get_title}}</h1>
870
+ * ```
871
+ * ```html
872
+ * <h1>Hello World!</h1>
873
+ * ```
874
  * @return string
875
  */
876
  function get_title() {
878
  }
879
 
880
  /**
881
+ * Displays the content of the post with filters, shortcodes and wpautop applied
882
+ * @example
883
+ * ```twig
884
+ * <div class="article-text">{{post.get_content}}</div>
885
+ * ```
886
+ * ```html
887
+ * <div class="article-text"><p>Blah blah blah</p><p>More blah blah blah.</p></div>
888
+ * ```
889
  * @param int $len
890
  * @param int $page
891
  * @return string
892
  */
893
+ function get_content( $len = 0, $page = 0 ) {
894
+ if ( $len == 0 && $page == 0 && $this->_content ) {
895
  return $this->_content;
896
  }
897
  $content = $this->post_content;
898
+ if ( $len ) {
899
  $content = wp_trim_words($content, $len);
900
  }
901
+ if ( $page ) {
902
  $contents = explode('<!--nextpage-->', $content);
903
  $page--;
904
+ if ( count($contents) > $page ) {
905
  $content = $contents[$page];
906
  }
907
  }
908
  $content = apply_filters('the_content', ($content));
909
+ if ( $len == 0 && $page == 0 ) {
910
  $this->_content = $content;
911
  }
912
  return $content;
920
  return $this->get_content(0, $page);
921
  }
922
  /**
923
+ *
924
+ * Here is my summary
925
+ * @example
926
+ * ```twig
927
+ * This post is from <span>{{ post.get_post_type.labels.plural }}</span>
928
+ * ```
929
+ *
930
+ * ```html
931
+ * This post is from <span>Recipes</span>
932
+ * ```
933
  * @return mixed
934
  */
935
  public function get_post_type() {
940
  * @return int
941
  */
942
  public function get_comment_count() {
943
+ if ( isset($this->ID) ) {
944
  return get_comments_number($this->ID);
945
  } else {
946
  return 0;
951
  * @param string $field_name
952
  * @return mixed
953
  */
954
+ public function get_field( $field_name ) {
955
  $value = apply_filters('timber_post_get_meta_field_pre', null, $this->ID, $field_name, $this);
956
+ if ( $value === null ) {
957
  $value = get_post_meta($this->ID, $field_name);
958
+ if ( is_array($value) && count($value) == 1 ) {
959
  $value = $value[0];
960
  }
961
+ if ( is_array($value) && count($value) == 0 ) {
962
  $value = null;
963
  }
964
  }
969
  /**
970
  * @param string $field_name
971
  */
972
+ function import_field( $field_name ) {
973
  $this->$field_name = $this->get_field($field_name);
974
  }
975
 
976
  /**
977
+ * @internal
978
  * @return mixed
979
  */
980
  function get_format() {
982
  }
983
 
984
  /**
985
+ * Get the CSS classes for a post. For usage you should use `{{post.class}}` instead of `{{post.post_class}}`
986
+ * @internal
987
+ * @param string $class additional classes you want to add
988
+ * @see TimberPost::$class
989
+ * @example
990
+ * ```twig
991
+ * <article class="{{ post.class }}">
992
+ * {# Some stuff here #}
993
+ * </article>
994
+ * ```
995
+ *
996
+ * ```html
997
+ * <article class="post-2612 post type-post status-publish format-standard has-post-thumbnail hentry category-data tag-charleston-church-shooting tag-dylann-roof tag-gun-violence tag-hate-crimes tag-national-incident-based-reporting-system">
998
+ * {# Some stuff here #}
999
+ * </article>
1000
+ * ```
1001
+ * @return string a space-seperated list of classes
1002
+ */
1003
+ public function post_class( $class='' ) {
1004
  global $post;
1005
  $old_global_post = $post;
1006
  $post = $this;
1007
  $class_array = get_post_class($class, $this->ID);
1008
  $post = $old_global_post;
1009
+ if ( is_array($class_array) ){
1010
  return implode(' ', $class_array);
1011
  }
1012
  return $class_array;
1041
  return $ret;
1042
  }
1043
 
 
1044
  /**
1045
+ * Return the author of a post
1046
+ * @api
1047
+ * @example
1048
+ * ```twig
1049
+ * <h1>{{post.title}}</h1>
1050
+ * <p class="byline">
1051
+ * <a href="{{post.author.link}}">{{post.author.name}}</a>
1052
+ * </p>
1053
+ * ```
1054
+ * @return TimberUser|bool A TimberUser object if found, false if not
1055
  */
1056
  public function author() {
1057
  return $this->get_author();
1058
  }
1059
 
1060
  /**
1061
+ * Get the author (WordPress user) who last modified the post
1062
+ * @example
1063
+ * ```twig
1064
+ * Last updated by {{ post.modified_author.name }}
1065
+ * ```
1066
+ * ```html
1067
+ * Last updated by Harper Lee
1068
+ * ```
1069
+ * @return TimberUser|bool A TimberUser object if found, false if not
1070
  */
1071
  public function modified_author() {
1072
  return $this->get_modified_author();
1073
  }
1074
 
1075
  /**
1076
+ * Get the categoires on a particular post
1077
+ * @api
1078
+ * @return array of TimberTerms
1079
  */
1080
  public function categories() {
1081
  return $this->get_terms('category');
1082
  }
1083
 
1084
  /**
1085
+ * Returns a category attached to a post
1086
+ * @api
1087
+ * If mulitpuile categories are set, it will return just the first one
1088
+ * @return TimberTerm|null
1089
  */
1090
  public function category() {
1091
  return $this->get_category();
1092
  }
1093
 
1094
  /**
1095
+ * Returns an array of children on the post as TimberPosts
1096
+ * (or other claass as you define).
1097
+ * @api
1098
+ * @example
1099
+ * ```twig
1100
+ * {% if post.children %}
1101
+ * Here are the child pages:
1102
+ * {% for child in page.children %}
1103
+ * <a href="{{ child.link }}">{{ child.title }}</a>
1104
+ * {% endfor %}
1105
+ * {% endif %}
1106
+ * ```
1107
+ * @param string $post_type _optional_ use to find children of a particular post type (attachment vs. page for example). You might want to restrict to certain types of children in case other stuff gets all mucked in there. You can use 'parent' to use the parent's post type
1108
+ * @param string|bool $childPostClass _optional_ a custom post class (ex: 'MyTimberPost') to return the objects as. By default (false) it will use TimberPost::$post_class value.
1109
  * @return array
1110
  */
1111
  public function children( $post_type = 'any', $childPostClass = false ) {
1113
  }
1114
 
1115
  /**
1116
+ * Gets the comments on a TimberPost and returns them as an array of [TimberComments](#TimberComment) (or whatever comment class you set).
1117
+ * @api
1118
+ * @param int $count Set the number of comments you want to get. `0` is analogous to "all"
1119
+ * @param string $order use ordering set in WordPress admin, or a different scheme
1120
+ * @param string $type For when other plugins use the comments table for their own special purposes, might be set to 'liveblog' or other depending on what's stored in yr comments table
1121
+ * @param string $status Could be 'pending', etc.
1122
+ * @param string $CommentClass What class to use when returning Comment objects. As you become a Timber pro, you might find yourself extending TimberComment for your site or app (obviously, totally optional)
1123
+ * @example
1124
+ * ```twig
1125
+ * {# single.twig #}
1126
+ * <h4>Comments:</h4>
1127
+ * {% for comment in post.comments %}
1128
+ * <div class="comment-{{comment.ID}} comment-order-{{loop.index}}">
1129
+ * <p>{{comment.author.name}} said:</p>
1130
+ * <p>{{comment.content}}</p>
1131
+ * </div>
1132
+ * {% endfor %}
1133
+ * ```
1134
+ * @return bool|array
1135
+ */
1136
+ public function comments( $count = 0, $order = 'wp', $type = 'comment', $status = 'approve', $CommentClass = 'TimberComment' ) {
1137
+ return $this->get_comments($count, $order, $type, $status, $CommentClass);
1138
+ }
1139
+
1140
+ /**
1141
+ * Gets the actual content of a WP Post, as opposed to post_content this will run the hooks/filters attached to the_content. \This guy will return your posts content with WordPress filters run on it (like for shortcodes and wpautop).
1142
+ * @api
1143
+ * @example
1144
+ * ```twig
1145
+ * <div class="article">
1146
+ * <h2>{{post.title}}</h2>
1147
+ * <div class="content">{{ post.content }}</div>
1148
+ * </div>
1149
+ * ```
1150
  * @param int $page
1151
  * @return string
1152
  */
1153
+ public function content( $page = 0 ) {
1154
  return $this->get_content(0, $page);
1155
  }
1156
 
1162
  }
1163
 
1164
  /**
1165
+ * Get the date to use in your template!
1166
+ * @api
1167
+ * @example
1168
+ * ```twig
1169
+ * Published on {{ post.date }} // Uses WP's formatting set in Admin
1170
+ * OR
1171
+ * Published on {{ post.date | date('F jS') }} // Jan 12th
1172
+ * ```
1173
+ *
1174
+ * ```html
1175
+ * Published on January 12, 2015
1176
+ * OR
1177
+ * Published on Jan 12th
1178
+ * ```
1179
  * @param string $date_format
1180
  * @return string
1181
  */
1182
+ public function date( $date_format = '' ) {
1183
  return $this->get_date($date_format);
1184
  }
1185
 
1191
  }
1192
 
1193
  /**
1194
+ * @api
1195
  * @return mixed
1196
  */
1197
  public function format() {
1199
  }
1200
 
1201
  /**
1202
+ * get the permalink for a post object
1203
+ * @api
1204
+ * @example
1205
+ * ```twig
1206
+ * <a href="{{post.link}}">Read my post</a>
1207
+ * ```
1208
+ * @return string ex: http://example.org/2015/07/my-awesome-post
1209
  */
1210
  public function link() {
1211
  return $this->get_permalink();
1215
  * @param string $field_name
1216
  * @return mixed
1217
  */
1218
+ public function meta( $field_name = null ) {
1219
+ if ( $field_name === null ) {
1220
+ //on the off-chance the field is actually named meta
1221
  $field_name = 'meta';
1222
  }
1223
  return $this->get_field($field_name);
1234
  * @param string $date_format
1235
  * @return string
1236
  */
1237
+ public function modified_date( $date_format = '' ) {
1238
  return $this->get_modified_date($date_format);
1239
  }
1240
 
1242
  * @param string $time_format
1243
  * @return string
1244
  */
1245
+ public function modified_time( $time_format = '' ) {
1246
  return $this->get_modified_time($time_format);
1247
  }
1248
 
1249
  /**
1250
+ * @api
1251
  * @param bool $in_same_cat
1252
  * @return mixed
1253
  */
1254
+ public function next( $in_same_cat = false ) {
1255
  return $this->get_next($in_same_cat);
1256
  }
1257
 
1263
  }
1264
 
1265
  /**
1266
+ * Gets the parent (if one exists) from a post as a TimberPost object (or whatever is set in TimberPost::$PostClass)
1267
+ * @api
1268
+ * @example
1269
+ * ```twig
1270
+ * Parent page: <a href="{{ post.parent.link }}">{{ post.parent.title }}</a>
1271
+ * ```
1272
  * @return bool|TimberPost
1273
  */
1274
  public function parent() {
1276
  }
1277
 
1278
  /**
1279
+ * Gets the relative path of a WP Post, so while link() will return http://example.org/2015/07/my-cool-post
1280
+ * this will return just /2015/07/my-cool-post
1281
+ * @api
1282
+ * @example
1283
+ * ```twig
1284
+ * <a href="{{post.path}}">{{post.title}}</a>
1285
+ * ```
1286
  * @return string
1287
  */
1288
  public function path() {
1290
  }
1291
 
1292
  /**
1293
+ * @deprecated use link() instead
1294
  * @return string
1295
  */
1296
  public function permalink() {
1298
  }
1299
 
1300
  /**
1301
+ * Get the previous post in a set
1302
+ * @api
1303
+ * @example
1304
+ * ```twig
1305
+ * <h4>Prior Entry:</h4>
1306
+ * <h3>{{post.prev.title}}</h3>
1307
+ * <p>{{post.prev.get_preview(25)}}</p>
1308
+ * ```
1309
  * @param bool $in_same_cat
1310
  * @return mixed
1311
  */
1312
+ public function prev( $in_same_cat = false ) {
1313
  return $this->get_prev($in_same_cat);
1314
  }
1315
 
1316
  /**
1317
+ * Get the terms associated with the post
1318
+ * This goes across all taxonomies by default
1319
+ * @api
1320
+ * @param string $tax What taxonomy to pull from, defaults to all of them. You can use custom ones, or built-in WordPress taxonomies (category, tag). Timber plays nice and figures out that tag/tags/post_tag are all the same (and categories/category), for custom taxonomies you're on your own.
1321
+ * @param bool $merge Should the resulting array be one big one (true)? Or should it be an array of sub-arrays for each taxonomy (false)?
1322
  * @return array
1323
  */
1324
+ public function terms( $tax = '', $merge = true ) {
1325
+ return $this->get_terms($tax, $merge);
1326
  }
1327
 
1328
  /**
1329
+ * Gets the tags on a post, uses WP's post_tag taxonomy
1330
+ * @api
1331
  * @return array
1332
  */
1333
  public function tags() {
1335
  }
1336
 
1337
  /**
1338
+ * get the featured image as a TimberImage
1339
+ * @api
1340
+ * @example
1341
+ * ```twig
1342
+ * <img src="{{post.thumbnail.src}}" />
1343
+ * ```
1344
+ * @return TimberImage|null of your thumbnail
1345
  */
1346
  public function thumbnail() {
1347
  return $this->get_thumbnail();
1348
  }
1349
 
1350
  /**
1351
+ * Returns the processed title to be used in templates. This returns the title of the post after WP's filters have run. This is analogous to `the_title()` in standard WP template tags.
1352
+ * @api
1353
+ * @example
1354
+ * ```twig
1355
+ * <h1>{{ post.title }}</h1>
1356
+ * ```
1357
  * @return string
1358
  */
1359
  public function title() {
lib/timber-site.php CHANGED
@@ -4,16 +4,36 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
4
 
5
  public $admin_email;
6
  public $blogname;
 
 
 
 
7
  public $charset;
 
 
 
 
8
  public $description;
9
  public $id;
10
  public $language;
11
  public $language_attributes;
 
 
 
 
12
  public $multisite;
13
  public $name;
14
  public $pingback_url;
15
  public $siteurl;
 
 
 
 
16
  public $theme;
 
 
 
 
17
  public $title;
18
  public $url;
19
 
@@ -31,8 +51,7 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
31
  }
32
 
33
  /**
34
- *
35
- *
36
  * @param string|int $site_name_or_id
37
  */
38
  protected function init_with_multisite( $site_name_or_id ) {
@@ -46,10 +65,10 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
46
  $info = get_blog_details( $site_name_or_id );
47
  $this->import( $info );
48
  $this->ID = $info->blog_id;
 
49
  $this->name = $this->blogname;
50
  $this->title = $this->blogname;
51
- $this->url = get_bloginfo( 'url' );
52
- $this->id = $this->ID;
53
  $theme_slug = get_blog_option( $info->blog_id, 'stylesheet' );
54
  $this->theme = new TimberTheme( $theme_slug );
55
  $this->language = get_bloginfo( 'language' );
@@ -61,6 +80,9 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
61
  $this->admin_email = get_blog_option( $info->blog_id, 'admin_email' );
62
  }
63
 
 
 
 
64
  protected function init() {
65
  $this->admin_email = get_bloginfo( 'admin_email' );
66
  $this->name = get_bloginfo( 'name' );
@@ -94,7 +116,7 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
94
 
95
  /**
96
  *
97
- *
98
  * @return string
99
  */
100
  function get_link() {
@@ -103,7 +125,7 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
103
 
104
  /**
105
  *
106
- *
107
  * @return string
108
  */
109
  function get_url() {
@@ -112,7 +134,7 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
112
 
113
  /**
114
  *
115
- *
116
  * @return string
117
  */
118
  public function link() {
@@ -144,7 +166,8 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
144
 
145
  /**
146
  *
147
- *
 
148
  * @return string
149
  */
150
  function url() {
4
 
5
  public $admin_email;
6
  public $blogname;
7
+ /**
8
+ * @api
9
+ * @var string
10
+ */
11
  public $charset;
12
+
13
+ /**
14
+ * @var string
15
+ */
16
  public $description;
17
  public $id;
18
  public $language;
19
  public $language_attributes;
20
+ /**
21
+ * @api
22
+ * @var bool
23
+ */
24
  public $multisite;
25
  public $name;
26
  public $pingback_url;
27
  public $siteurl;
28
+ /**
29
+ * @api
30
+ * @var [TimberTheme](#TimberTheme)
31
+ */
32
  public $theme;
33
+ /**
34
+ * @api
35
+ * @var string
36
+ */
37
  public $title;
38
  public $url;
39
 
51
  }
52
 
53
  /**
54
+ * @internal
 
55
  * @param string|int $site_name_or_id
56
  */
57
  protected function init_with_multisite( $site_name_or_id ) {
65
  $info = get_blog_details( $site_name_or_id );
66
  $this->import( $info );
67
  $this->ID = $info->blog_id;
68
+ $this->id = $this->ID;
69
  $this->name = $this->blogname;
70
  $this->title = $this->blogname;
71
+ $this->url = $this->siteurl;
 
72
  $theme_slug = get_blog_option( $info->blog_id, 'stylesheet' );
73
  $this->theme = new TimberTheme( $theme_slug );
74
  $this->language = get_bloginfo( 'language' );
80
  $this->admin_email = get_blog_option( $info->blog_id, 'admin_email' );
81
  }
82
 
83
+ /**
84
+ * @internal
85
+ */
86
  protected function init() {
87
  $this->admin_email = get_bloginfo( 'admin_email' );
88
  $this->name = get_bloginfo( 'name' );
116
 
117
  /**
118
  *
119
+ * @internal
120
  * @return string
121
  */
122
  function get_link() {
125
 
126
  /**
127
  *
128
+ * @internal
129
  * @return string
130
  */
131
  function get_url() {
134
 
135
  /**
136
  *
137
+ * @api
138
  * @return string
139
  */
140
  public function link() {
166
 
167
  /**
168
  *
169
+ * @api
170
+ * @see TimberSite::link
171
  * @return string
172
  */
173
  function url() {
lib/timber-term.php CHANGED
@@ -2,332 +2,369 @@
2
 
3
  class TimberTerm extends TimberCore implements TimberCoreInterface {
4
 
5
- public $PostClass = 'TimberPost';
6
- public $TermClass = 'TimberTerm';
7
-
8
- public $object_type = 'term';
9
- public static $representation = 'term';
10
-
11
- public $_children;
12
- public $name;
13
- public $taxonomy;
14
-
15
- /**
16
- * @param int $tid
17
- * @param string $tax
18
- */
19
- function __construct($tid = null, $tax = '') {
20
- if ($tid === null) {
21
- $tid = $this->get_term_from_query();
22
- }
23
- if (strlen($tax)) {
24
- $this->taxonomy = $tax;
25
- }
26
- $this->init($tid);
27
- }
28
-
29
- /**
30
- * @return string
31
- */
32
- function __toString() {
33
- return $this->name;
34
- }
35
-
36
-
37
-
38
- /* Setup
39
- ===================== */
40
-
41
- /**
42
- * @return integer
43
- */
44
- protected function get_term_from_query() {
45
- global $wp_query;
46
- if (isset($wp_query->queried_object)) {
47
- $qo = $wp_query->queried_object;
48
- return $qo->term_id;
49
- }
50
- if (isset($wp_query->tax_query->queries[0]['terms'][0])) {
51
- return $wp_query->tax_query->queries[0]['terms'][0];
52
- }
53
- }
54
-
55
- /**
56
- * @param int $tid
57
- */
58
- protected function init($tid) {
59
- $term = $this->get_term($tid);
60
- if (isset($term->id)) {
61
- $term->ID = $term->id;
62
- } else if (isset($term->term_id)) {
63
- $term->ID = $term->term_id;
64
- } else if (is_string($tid)) {
65
- //echo 'bad call using '.$tid;
66
- //TimberHelper::error_log(debug_backtrace());
67
- }
68
- if (isset($term->ID)){
69
- $term->id = $term->ID;
70
- $this->import($term);
71
- if (isset($term->term_id)) {
72
- $custom = $this->get_term_meta($term->term_id);
73
- $this->import($custom);
74
- }
75
- }
76
- }
77
-
78
- /**
79
- * @param int $tid
80
- * @return array
81
- */
82
- protected function get_term_meta($tid) {
83
- $customs = array();
84
- $customs = apply_filters('timber_term_get_meta', $customs, $tid, $this);
85
- return $customs;
86
- }
87
-
88
- /**
89
- * @param int $tid
90
- * @return mixed
91
- */
92
- protected function get_term($tid) {
93
- if (is_object($tid) || is_array($tid)) {
94
- return $tid;
95
- }
96
- $tid = self::get_tid($tid);
97
-
98
- if (isset($this->taxonomy) && strlen($this->taxonomy)) {
99
- return get_term($tid, $this->taxonomy);
100
- } else {
101
- global $wpdb;
102
- $query = $wpdb->prepare("SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d LIMIT 1", $tid);
103
- $tax = $wpdb->get_var($query);
104
- if (isset($tax) && strlen($tax)) {
105
- return get_term($tid, $tax);
106
- }
107
- }
108
- return null;
109
- }
110
-
111
- /**
112
- * @param int $tid
113
- * @return int
114
- */
115
- protected function get_tid($tid) {
116
- global $wpdb;
117
- if (is_numeric($tid)) {
118
- return $tid;
119
- }
120
- if (gettype($tid) == 'object') {
121
- $tid = $tid->term_id;
122
- }
123
- if (is_numeric($tid)) {
124
- $query = $wpdb->prepare("SELECT * FROM $wpdb->terms WHERE term_id = %d", $tid);
125
- } else {
126
- $query = $wpdb->prepare("SELECT * FROM $wpdb->terms WHERE slug = %s", $tid);
127
- }
128
- $result = $wpdb->get_row($query);
129
- if (isset($result->term_id)) {
130
- $result->ID = $result->term_id;
131
- $result->id = $result->term_id;
132
- return $result->ID;
133
- }
134
- return 0;
135
- }
136
-
137
- /* Public methods
138
- ===================== */
139
-
140
- /**
141
- * @return string
142
- */
143
- public function get_edit_url() {
144
- return get_edit_term_link($this->ID, $this->taxonomy);
145
- }
146
-
147
- /**
148
- * @param string $field_name
149
- * @return string
150
- */
151
- public function get_meta_field($field_name) {
152
- if (!isset($this->$field_name)) {
153
- $field_value = '';
154
- $field_value = apply_filters('timber_term_get_meta_field', $field_value, $this->ID, $field_name, $this);
155
- $this->$field_name = $field_value;
156
- }
157
- return $this->$field_name;
158
- }
159
-
160
- /**
161
- * @return string
162
- */
163
- public function get_path() {
164
- $link = $this->get_link();
165
- $rel = TimberURLHelper::get_rel_url($link, true);
166
- return apply_filters('timber_term_path', $rel, $this);
167
- }
168
-
169
- /**
170
- * @return string
171
- */
172
- public function get_link() {
173
- $link = get_term_link($this);
174
- return apply_filters('timber_term_link', $link, $this);
175
- }
176
-
177
- /**
178
- * @param int $numberposts
179
- * @param string $post_type
180
- * @param string $PostClass
181
- * @return array|bool|null
182
- */
183
- public function get_posts($numberposts = 10, $post_type = 'any', $PostClass = '') {
184
- if (!strlen($PostClass)) {
185
- $PostClass = $this->PostClass;
186
- }
187
- $default_tax_query = array(array(
188
- 'field' => 'id',
189
- 'terms' => $this->ID,
190
- 'taxonomy' => $this->taxonomy,
191
- ));
192
- if (is_string($numberposts) && strstr($numberposts, '=')) {
193
- $args = $numberposts;
194
- $new_args = array();
195
- parse_str($args, $new_args);
196
- $args = $new_args;
197
- $args['tax_query'] = $default_tax_query;
198
- if (!isset($args['post_type'])) {
199
- $args['post_type'] = 'any';
200
- }
201
- if (class_exists($post_type)) {
202
- $PostClass = $post_type;
203
- }
204
- } else if (is_array($numberposts)) {
205
- //they sent us an array already baked
206
- $args = $numberposts;
207
- if (!isset($args['tax_query'])) {
208
- $args['tax_query'] = $default_tax_query;
209
- }
210
- if (class_exists($post_type)) {
211
- $PostClass = $post_type;
212
- }
213
- if (!isset($args['post_type'])) {
214
- $args['post_type'] = 'any';
215
- }
216
- } else {
217
- $args = array(
218
- 'numberposts' => $numberposts,
219
- 'tax_query' => $default_tax_query,
220
- 'post_type' => $post_type
221
- );
222
- }
223
- return Timber::get_posts($args, $PostClass);
224
- }
225
-
226
- /**
227
- * @return array
228
- */
229
- public function get_children() {
230
- if (!isset($this->_children)) {
231
- $children = get_term_children($this->ID, $this->taxonomy);
232
- foreach ($children as &$child) {
233
- $child = new TimberTerm($child);
234
- }
235
- $this->_children = $children;
236
- }
237
- return $this->_children;
238
- }
239
-
240
- /**
241
- *
242
- *
243
- * @param string $key
244
- * @param mixed $value
245
- */
246
- function update( $key, $value ) {
247
- $value = apply_filters( 'timber_term_set_meta', $value, $key, $this->ID, $this );
248
- $this->$key = $value;
249
- }
250
-
251
- /* Alias
252
- ====================== */
253
-
254
- /**
255
- * @return array
256
- */
257
- public function children() {
258
- return $this->get_children();
259
- }
260
-
261
- /**
262
- * @return string
263
- */
264
- public function edit_link() {
265
- return $this->get_edit_url();
266
- }
267
-
268
- /**
269
- * @return string
270
- */
271
- public function get_url() {
272
- return $this->get_link();
273
- }
274
-
275
- /**
276
- * @return string
277
- */
278
- public function link() {
279
- return $this->get_link();
280
- }
281
-
282
- /**
283
- * @param string $field_name
284
- * @return string
285
- */
286
- public function meta($field_name) {
287
- return $this->get_meta_field($field_name);
288
- }
289
-
290
- /**
291
- * @return string
292
- */
293
- public function path() {
294
- return $this->get_path();
295
- }
296
-
297
- /**
298
- * @param int $numberposts_or_args
299
- * @param string $post_type_or_class
300
- * @param string $post_class
301
- * @return array|bool|null
302
- */
303
- public function posts($numberposts_or_args = 10, $post_type_or_class = 'any', $post_class = '') {
304
- return $this->get_posts($numberposts_or_args, $post_type_or_class, $post_class);
305
- }
306
-
307
- /**
308
- * @return string
309
- */
310
- public function title() {
311
- return $this->name;
312
- }
313
-
314
- /**
315
- * @return string
316
- */
317
- public function url() {
318
- return $this->get_url();
319
- }
320
-
321
- /* Deprecated
322
- ===================== */
323
-
324
- /**
325
- * @deprecated
326
- * @param int $i
327
- * @return string
328
- */
329
- function get_page($i) {
330
- return $this->get_path() . '/page/' . $i;
331
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
 
333
  }
2
 
3
  class TimberTerm extends TimberCore implements TimberCoreInterface {
4
 
5
+ public $PostClass = 'TimberPost';
6
+ public $TermClass = 'TimberTerm';
7
+
8
+ public $object_type = 'term';
9
+ public static $representation = 'term';
10
+
11
+ public $_children;
12
+ /**
13
+ * @api
14
+ * @var string the human-friendly name of the term (ex: French Cuisine)
15
+ */
16
+ public $name;
17
+ /**
18
+ * @api
19
+ * @var strng the WordPress taxonomy slug (ex: `post_tag` or `actors`)
20
+ */
21
+ public $taxonomy;
22
+
23
+ /**
24
+ * @param int $tid
25
+ * @param string $tax
26
+ */
27
+ public function __construct( $tid = null, $tax = '' ) {
28
+ if ($tid === null) {
29
+ $tid = $this->get_term_from_query();
30
+ }
31
+ if (strlen($tax)) {
32
+ $this->taxonomy = $tax;
33
+ }
34
+ $this->init($tid);
35
+ }
36
+
37
+ /**
38
+ * @return string
39
+ */
40
+ public function __toString() {
41
+ return $this->name;
42
+ }
43
+
44
+
45
+
46
+ /* Setup
47
+ ===================== */
48
+
49
+ /**
50
+ * @internal
51
+ * @return integer
52
+ */
53
+ protected function get_term_from_query() {
54
+ global $wp_query;
55
+ if ( isset($wp_query->queried_object) ) {
56
+ $qo = $wp_query->queried_object;
57
+ return $qo->term_id;
58
+ }
59
+ if ( isset($wp_query->tax_query->queries[0]['terms'][0]) ) {
60
+ return $wp_query->tax_query->queries[0]['terms'][0];
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @internal
66
+ * @param int $tid
67
+ */
68
+ protected function init( $tid ) {
69
+ $term = $this->get_term($tid);
70
+ if ( isset($term->id) ) {
71
+ $term->ID = $term->id;
72
+ } else if ( isset($term->term_id) ) {
73
+ $term->ID = $term->term_id;
74
+ } else if ( is_string($tid) ) {
75
+ //echo 'bad call using '.$tid;
76
+ //TimberHelper::error_log(debug_backtrace());
77
+ }
78
+ if ( isset($term->ID) ){
79
+ $term->id = $term->ID;
80
+ $this->import($term);
81
+ if ( isset($term->term_id) ) {
82
+ $custom = $this->get_term_meta($term->term_id);
83
+ $this->import($custom);
84
+ }
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @internal
90
+ * @param int $tid
91
+ * @return array
92
+ */
93
+ protected function get_term_meta($tid) {
94
+ $customs = array();
95
+ $customs = apply_filters('timber_term_get_meta', $customs, $tid, $this);
96
+ return apply_filters('timber/term/meta', $customs, $tid, $this);
97
+ }
98
+
99
+ /**
100
+ * @internal
101
+ * @param int $tid
102
+ * @return mixed
103
+ */
104
+ protected function get_term( $tid ) {
105
+ if ( is_object($tid) || is_array($tid) ) {
106
+ return $tid;
107
+ }
108
+ $tid = self::get_tid($tid);
109
+
110
+ if ( isset($this->taxonomy) && strlen($this->taxonomy) ) {
111
+ return get_term($tid, $this->taxonomy);
112
+ } else {
113
+ global $wpdb;
114
+ $query = $wpdb->prepare("SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d LIMIT 1", $tid);
115
+ $tax = $wpdb->get_var($query);
116
+ if (isset($tax) && strlen($tax)) {
117
+ return get_term($tid, $tax);
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * @internal
125
+ * @param int $tid
126
+ * @return int
127
+ */
128
+ protected function get_tid( $tid ) {
129
+ global $wpdb;
130
+ if ( is_numeric($tid) ) {
131
+ return $tid;
132
+ }
133
+ if ( gettype($tid) == 'object' ) {
134
+ $tid = $tid->term_id;
135
+ }
136
+ if ( is_numeric($tid) ) {
137
+ $query = $wpdb->prepare("SELECT * FROM $wpdb->terms WHERE term_id = %d", $tid);
138
+ } else {
139
+ $query = $wpdb->prepare("SELECT * FROM $wpdb->terms WHERE slug = %s", $tid);
140
+ }
141
+ $result = $wpdb->get_row($query);
142
+ if ( isset($result->term_id) ) {
143
+ $result->ID = $result->term_id;
144
+ $result->id = $result->term_id;
145
+ return $result->ID;
146
+ }
147
+ return 0;
148
+ }
149
+
150
+ /* Public methods
151
+ ===================== */
152
+
153
+ /**
154
+ * @internal
155
+ * @return string
156
+ */
157
+ public function get_edit_url() {
158
+ return get_edit_term_link($this->ID, $this->taxonomy);
159
+ }
160
+
161
+ /**
162
+ * @internal
163
+ * @param string $field_name
164
+ * @return string
165
+ */
166
+ public function get_meta_field( $field_name ) {
167
+ if (!isset($this->$field_name)) {
168
+ $field_value = '';
169
+ $field_value = apply_filters('timber_term_get_meta_field', $field_value, $this->ID, $field_name, $this);
170
+ $field_value = apply_filters('timber/term/meta/field', $field_value, $this->ID, $field_name, $this);
171
+ $this->$field_name = $field_value;
172
+ }
173
+ return $this->$field_name;
174
+ }
175
+
176
+ /**
177
+ * @internal
178
+ * @return string
179
+ */
180
+ public function get_path() {
181
+ $link = $this->get_link();
182
+ $rel = TimberURLHelper::get_rel_url($link, true);
183
+ $rel = apply_filters('timber_term_path', $rel, $this);
184
+ return apply_filters('timber/term/path', $rel, $this);
185
+ }
186
+
187
+ /**
188
+ * @internal
189
+ * @return string
190
+ */
191
+ public function get_link() {
192
+ $link = get_term_link($this);
193
+ $link = apply_filters('timber_term_link', $link, $this);
194
+ return apply_filters('timber/term/link', $link, $this);
195
+ }
196
+
197
+ /**
198
+ * @param int $numberposts
199
+ * @param string $post_type
200
+ * @param string $PostClass
201
+ * @return array|bool|null
202
+ */
203
+ public function get_posts( $numberposts = 10, $post_type = 'any', $PostClass = '' ) {
204
+ if (!strlen($PostClass)) {
205
+ $PostClass = $this->PostClass;
206
+ }
207
+ $default_tax_query = array(array(
208
+ 'field' => 'id',
209
+ 'terms' => $this->ID,
210
+ 'taxonomy' => $this->taxonomy,
211
+ ));
212
+ if ( is_string($numberposts) && strstr($numberposts, '=') ) {
213
+ $args = $numberposts;
214
+ $new_args = array();
215
+ parse_str($args, $new_args);
216
+ $args = $new_args;
217
+ $args['tax_query'] = $default_tax_query;
218
+ if (!isset($args['post_type'])) {
219
+ $args['post_type'] = 'any';
220
+ }
221
+ if (class_exists($post_type)) {
222
+ $PostClass = $post_type;
223
+ }
224
+ } else if ( is_array($numberposts) ) {
225
+ //they sent us an array already baked
226
+ $args = $numberposts;
227
+ if ( !isset($args['tax_query']) ) {
228
+ $args['tax_query'] = $default_tax_query;
229
+ }
230
+ if ( class_exists($post_type) ) {
231
+ $PostClass = $post_type;
232
+ }
233
+ if ( !isset($args['post_type']) ) {
234
+ $args['post_type'] = 'any';
235
+ }
236
+ } else {
237
+ $args = array(
238
+ 'numberposts' => $numberposts,
239
+ 'tax_query' => $default_tax_query,
240
+ 'post_type' => $post_type
241
+ );
242
+ }
243
+ return Timber::get_posts($args, $PostClass);
244
+ }
245
+
246
+ /**
247
+ * @internal
248
+ * @return array
249
+ */
250
+ public function get_children() {
251
+ if ( !isset($this->_children) ) {
252
+ $children = get_term_children($this->ID, $this->taxonomy);
253
+ foreach ($children as &$child) {
254
+ $child = new TimberTerm($child);
255
+ }
256
+ $this->_children = $children;
257
+ }
258
+ return $this->_children;
259
+ }
260
+
261
+ /**
262
+ *
263
+ *
264
+ * @param string $key
265
+ * @param mixed $value
266
+ */
267
+ function update( $key, $value ) {
268
+ $value = apply_filters( 'timber_term_set_meta', $value, $key, $this->ID, $this );
269
+ $this->$key = $value;
270
+ }
271
+
272
+ /* Alias
273
+ ====================== */
274
+
275
+ /**
276
+ * @api
277
+ * @return array
278
+ */
279
+ public function children() {
280
+ return $this->get_children();
281
+ }
282
+
283
+ /**
284
+ * @api
285
+ * @return string
286
+ */
287
+ public function edit_link() {
288
+ return $this->get_edit_url();
289
+ }
290
+
291
+ /**
292
+ * @internal
293
+ * @deprecated 0.21.8 use TimberTerm::link() instead
294
+ * @return string
295
+ */
296
+ public function get_url() {
297
+ return $this->get_link();
298
+ }
299
+
300
+ /**
301
+ * @api
302
+ * @return string
303
+ */
304
+ public function link() {
305
+ return $this->get_link();
306
+ }
307
+
308
+ /**
309
+ * @api
310
+ * @param string $field_name
311
+ * @return string
312
+ */
313
+ public function meta( $field_name ) {
314
+ return $this->get_meta_field($field_name);
315
+ }
316
+
317
+ /**
318
+ * @api
319
+ * @return string
320
+ */
321
+ public function path() {
322
+ return $this->get_path();
323
+ }
324
+
325
+ /**
326
+ * @api
327
+ * @param int $numberposts_or_args
328
+ * @param string $post_type_or_class
329
+ * @param string $post_class
330
+ * @example
331
+ * ```twig
332
+ * <h4>Recent posts in {{term.name}}</h4>
333
+ * <ul>
334
+ * {% for post in term.posts(3, 'post') %}
335
+ * <li><a href="{{post.link}}">{{post.title}}</a></li>
336
+ * {% endfor %}
337
+ * </ul>
338
+ * ```
339
+ * @return array|bool|null
340
+ */
341
+ public function posts( $numberposts_or_args = 10, $post_type_or_class = 'any', $post_class = '' ) {
342
+ return $this->get_posts($numberposts_or_args, $post_type_or_class, $post_class);
343
+ }
344
+
345
+ /**
346
+ * @api
347
+ * @return string
348
+ */
349
+ public function title() {
350
+ return $this->name;
351
+ }
352
+
353
+ /**
354
+ * @deprecated since 0.21.9
355
+ * @return string
356
+ */
357
+ public function url() {
358
+ return $this->get_url();
359
+ }
360
+
361
+ /**
362
+ * @deprecated
363
+ * @param int $i
364
+ * @return string
365
+ */
366
+ function get_page( $i ) {
367
+ return $this->get_path() . '/page/' . $i;
368
+ }
369
 
370
  }
lib/timber-theme.php CHANGED
@@ -1,58 +1,119 @@
1
  <?php
2
 
3
- class TimberTheme extends TimberCore
4
- {
5
-
6
- public $link;
7
- public $name;
8
- public $path;
9
- public $parent;
10
- public $parent_slug;
11
- public $slug;
12
- public $uri;
13
-
14
- /**
15
- * @param string $slug
16
- */
17
- function __construct($slug = null) {
18
- $this->init($slug);
19
- }
20
-
21
- /**
22
- * @param string $slug
23
- */
24
- function init($slug = null) {
25
- $data = wp_get_theme($slug);
26
- $this->name = $data->get('Name');
27
- $ss = $data->get_stylesheet();
28
- $this->slug = $ss;
29
- $this->path = WP_CONTENT_SUBDIR . str_replace(WP_CONTENT_DIR, '', get_stylesheet_directory());
30
- $this->uri = get_stylesheet_directory_uri();
31
- $this->link = $this->uri;
32
- $this->parent_slug = $data->get('Template');
33
- if (!$this->parent_slug) {
34
- $this->path = WP_CONTENT_SUBDIR . str_replace(WP_CONTENT_DIR, '', get_template_directory());
35
- $this->uri = get_template_directory_uri();
36
- }
37
- if ($this->parent_slug && $this->parent_slug != $this->slug) {
38
- $this->parent = new TimberTheme($this->parent_slug);
39
- }
40
- }
41
-
42
- /**
43
- * @param string $name
44
- * @param bool $default
45
- * @return string
46
- */
47
- public function theme_mod($name, $default = false) {
48
- return get_theme_mod($name, $default);
49
- }
50
-
51
- /**
52
- * @return string
53
- */
54
- public function theme_mods() {
55
- return get_theme_mods();
56
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  }
1
  <?php
2
 
3
+ /**
4
+ * Need to display info about your theme? Well you've come to the right place. By default info on the current theme comes for free with what's fetched by `Timber::get_context()` in which case you can access it your theme like so:
5
+ * @example
6
+ * ```php
7
+ * <?php
8
+ * $context = Timber::get_context();
9
+ * Timber::render('index.twig', $context);
10
+ * ?>
11
+ * ```
12
+ * ```twig
13
+ * <script src="{{theme.link}}/static/js/all.js"></script>
14
+ * ```
15
+ * ```html
16
+ * <script src="http://example.org/wp-content/themes/my-theme/static/js/all.js"></script>
17
+ * ```
18
+ * @package Timber
19
+ */
20
+ class TimberTheme extends TimberCore {
21
+
22
+ /**
23
+ * @api
24
+ * @var string the absolute path to the theme (ex: `http://example.org/wp-content/themes/my-timber-theme`)
25
+ */
26
+ public $link;
27
+
28
+ /**
29
+ * @api
30
+ * @var string the human-friendly name of the theme (ex: `My Timber Starter Theme`)
31
+ */
32
+ public $name;
33
+
34
+ /**
35
+ * @api
36
+ * @var string the relative path to the theme (ex: `/wp-content/themes/my-timber-theme`)
37
+ */
38
+ public $path;
39
+
40
+ /**
41
+ * @api
42
+ * @var TimberTheme|bool the TimberTheme object for the parent theme (if it exists), false otherwise
43
+ */
44
+ public $parent = false;
45
+
46
+ /**
47
+ * @api
48
+ * @var string the slug of the parent theme (ex: `_s`)
49
+ */
50
+ public $parent_slug;
51
+
52
+ /**
53
+ * @api
54
+ * @var string the slug of the theme (ex: `my-super-theme`)
55
+ */
56
+ public $slug;
57
+ public $uri;
58
+
59
+ /**
60
+ * Constructs a new TimberTheme object. NOTE the TimberTheme object of the current theme comes in the default `Timber::get_context()` call. You can access this in your twig template via `{{site.theme}}.
61
+ * @param string $slug
62
+ * @example
63
+ * ```php
64
+ * <?php
65
+ * $theme = new TimberTheme("my-theme");
66
+ * $context['theme_stuff'] = $theme;
67
+ * Timber::render('single.')
68
+ * ?>
69
+ * ```
70
+ * ```twig
71
+ * We are currently using the {{ theme_stuff.name }} theme.
72
+ * ```
73
+ * ```html
74
+ * We are currently using the My Theme theme.
75
+ * ```
76
+ */
77
+ function __construct($slug = null) {
78
+ $this->init($slug);
79
+ }
80
+
81
+ /**
82
+ * @internal
83
+ * @param string $slug
84
+ */
85
+ protected function init($slug = null) {
86
+ $data = wp_get_theme($slug);
87
+ $this->name = $data->get('Name');
88
+ $ss = $data->get_stylesheet();
89
+ $this->slug = $ss;
90
+ $this->path = WP_CONTENT_SUBDIR . str_replace(WP_CONTENT_DIR, '', get_stylesheet_directory());
91
+ $this->uri = get_stylesheet_directory_uri();
92
+ $this->link = $this->uri;
93
+ $this->parent_slug = $data->get('Template');
94
+ if (!$this->parent_slug) {
95
+ $this->path = WP_CONTENT_SUBDIR . str_replace(WP_CONTENT_DIR, '', get_template_directory());
96
+ $this->uri = get_template_directory_uri();
97
+ }
98
+ if ($this->parent_slug && $this->parent_slug != $this->slug) {
99
+ $this->parent = new TimberTheme($this->parent_slug);
100
+ }
101
+ }
102
+
103
+ /**
104
+ * @param string $name
105
+ * @param bool $default
106
+ * @return string
107
+ */
108
+ public function theme_mod($name, $default = false) {
109
+ return get_theme_mod($name, $default);
110
+ }
111
+
112
+ /**
113
+ * @return array
114
+ */
115
+ public function theme_mods() {
116
+ return get_theme_mods();
117
+ }
118
 
119
  }
lib/timber-twig.php CHANGED
@@ -12,8 +12,8 @@ class TimberTwig {
12
  }
13
 
14
  function __construct() {
15
- add_action( 'twig_apply_filters', array( $this, 'add_timber_filters_deprecated' ) );
16
- add_action( 'twig_apply_filters', array( $this, 'add_timber_filters' ) );
17
  }
18
 
19
  /**
@@ -28,6 +28,9 @@ class TimberTwig {
28
  $twig->addFilter( new Twig_SimpleFilter( 'wp_body_class', array( $this, 'body_class' ) ) );
29
  $twig->addFilter( new Twig_SimpleFilter( 'twitterify', array( 'TimberHelper', 'twitterify' ) ) );
30
  $twig->addFilter( new Twig_SimpleFilter( 'twitterfy', array( 'TimberHelper', 'twitterify' ) ) );
 
 
 
31
  return $twig;
32
  }
33
 
@@ -58,7 +61,6 @@ class TimberTwig {
58
  /* other filters */
59
  $twig->addFilter( new Twig_SimpleFilter( 'stripshortcodes', 'strip_shortcodes' ) );
60
  $twig->addFilter( new Twig_SimpleFilter( 'array', array( $this, 'to_array' ) ) );
61
- $twig->addFilter( new Twig_SimpleFilter( 'string', array( $this, 'to_string' ) ) );
62
  $twig->addFilter( new Twig_SimpleFilter( 'excerpt', 'wp_trim_words' ) );
63
  $twig->addFilter( new Twig_SimpleFilter( 'function', array( $this, 'exec_function' ) ) );
64
  $twig->addFilter( new Twig_SimpleFilter( 'pretags', array( $this, 'twig_pretags' ) ) );
@@ -94,6 +96,8 @@ class TimberTwig {
94
  $twig->addFunction( new Twig_SimpleFunction( 'function', array( &$this, 'exec_function' ) ) );
95
  $twig->addFunction( new Twig_SimpleFunction( 'fn', array( &$this, 'exec_function' ) ) );
96
 
 
 
97
  /* TimberObjects */
98
  $twig->addFunction( new Twig_SimpleFunction( 'TimberPost', function ( $pid, $PostClass = 'TimberPost' ) {
99
  if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
@@ -177,9 +181,9 @@ class TimberTwig {
177
  $twig->addFunction( '__', new Twig_SimpleFunction( '__', function ( $text, $domain = 'default' ) {
178
  return __( $text, $domain );
179
  } ) );
180
-
181
  $twig = apply_filters( 'get_twig', $twig );
182
-
183
  return $twig;
184
  }
185
 
@@ -197,26 +201,6 @@ class TimberTwig {
197
  return $arr;
198
  }
199
 
200
- /**
201
- *
202
- *
203
- * @param mixed $arr
204
- * @param string $glue
205
- * @return string
206
- */
207
- function to_string( $arr, $glue = ' ' ) {
208
- if ( is_string( $arr ) ) {
209
- return $arr;
210
- }
211
- if ( is_array( $arr ) && count( $arr ) == 1 ) {
212
- return $arr[0];
213
- }
214
- if ( is_array( $arr ) ) {
215
- return implode( $glue, $arr );
216
- }
217
- return null;
218
- }
219
-
220
  /**
221
  *
222
  *
@@ -253,9 +237,8 @@ class TimberTwig {
253
  }
254
 
255
  /**
256
- *
257
- *
258
  * @param mixed $body_classes
 
259
  * @return string
260
  */
261
  function body_class( $body_classes ) {
12
  }
13
 
14
  function __construct() {
15
+ add_action( 'timber/twig/filters', array( $this, 'add_timber_filters_deprecated' ) );
16
+ add_action( 'timber/twig/filters', array( $this, 'add_timber_filters' ) );
17
  }
18
 
19
  /**
28
  $twig->addFilter( new Twig_SimpleFilter( 'wp_body_class', array( $this, 'body_class' ) ) );
29
  $twig->addFilter( new Twig_SimpleFilter( 'twitterify', array( 'TimberHelper', 'twitterify' ) ) );
30
  $twig->addFilter( new Twig_SimpleFilter( 'twitterfy', array( 'TimberHelper', 'twitterify' ) ) );
31
+ $twig->addFilter( new Twig_SimpleFilter( 'string', function($arr, $glue = ' '){
32
+ return twig_join_filter($arr, $glue);
33
+ } ) );
34
  return $twig;
35
  }
36
 
61
  /* other filters */
62
  $twig->addFilter( new Twig_SimpleFilter( 'stripshortcodes', 'strip_shortcodes' ) );
63
  $twig->addFilter( new Twig_SimpleFilter( 'array', array( $this, 'to_array' ) ) );
 
64
  $twig->addFilter( new Twig_SimpleFilter( 'excerpt', 'wp_trim_words' ) );
65
  $twig->addFilter( new Twig_SimpleFilter( 'function', array( $this, 'exec_function' ) ) );
66
  $twig->addFilter( new Twig_SimpleFilter( 'pretags', array( $this, 'twig_pretags' ) ) );
96
  $twig->addFunction( new Twig_SimpleFunction( 'function', array( &$this, 'exec_function' ) ) );
97
  $twig->addFunction( new Twig_SimpleFunction( 'fn', array( &$this, 'exec_function' ) ) );
98
 
99
+ $twig->addFunction( new Twig_SimpleFunction( 'shortcode', 'do_shortcode' ) );
100
+
101
  /* TimberObjects */
102
  $twig->addFunction( new Twig_SimpleFunction( 'TimberPost', function ( $pid, $PostClass = 'TimberPost' ) {
103
  if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
181
  $twig->addFunction( '__', new Twig_SimpleFunction( '__', function ( $text, $domain = 'default' ) {
182
  return __( $text, $domain );
183
  } ) );
184
+ /* get_twig is deprecated, use timber/twig */
185
  $twig = apply_filters( 'get_twig', $twig );
186
+ $twig = apply_filters( 'timber/twig', $twig );
187
  return $twig;
188
  }
189
 
201
  return $arr;
202
  }
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  /**
205
  *
206
  *
237
  }
238
 
239
  /**
 
 
240
  * @param mixed $body_classes
241
+ * @deprecated 0.20.7
242
  * @return string
243
  */
244
  function body_class( $body_classes ) {
lib/timber-user.php CHANGED
@@ -35,6 +35,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
35
  }
36
 
37
  /**
 
38
  * @param string $field_name
39
  * @return null
40
  */
@@ -43,6 +44,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
43
  }
44
 
45
  /**
 
46
  * @param string $field
47
  * @param mixed $value
48
  */
@@ -54,6 +56,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
54
  }
55
 
56
  /**
 
57
  * @return string
58
  */
59
  public function get_link() {
@@ -64,9 +67,10 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
64
  }
65
 
66
  /**
 
67
  * @param int|bool $uid
68
  */
69
- function init($uid = false) {
70
  if ( $uid === false ) {
71
  $uid = get_current_user_id();
72
  }
@@ -137,13 +141,15 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
137
  }
138
 
139
  /**
140
- * @return string
 
141
  */
142
  function name() {
143
  return $this->display_name;
144
  }
145
 
146
  /**
 
147
  * @return string
148
  */
149
  function get_permalink() {
@@ -151,6 +157,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
151
  }
152
 
153
  /**
 
154
  * @return string
155
  */
156
  function permalink() {
@@ -158,6 +165,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
158
  }
159
 
160
  /**
 
161
  * @return string ex: /author/lincoln
162
  */
163
  function get_path() {
@@ -173,6 +181,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
173
  }
174
 
175
  /**
 
176
  * @return string
177
  */
178
  function path() {
@@ -180,6 +189,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
180
  }
181
 
182
  /**
 
183
  * @return string
184
  */
185
  function slug() {
@@ -187,6 +197,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
187
  }
188
 
189
  /**
 
190
  * @return string
191
  */
192
  function link() {
35
  }
36
 
37
  /**
38
+ * @internal
39
  * @param string $field_name
40
  * @return null
41
  */
44
  }
45
 
46
  /**
47
+ * @internal
48
  * @param string $field
49
  * @param mixed $value
50
  */
56
  }
57
 
58
  /**
59
+ * @internal
60
  * @return string
61
  */
62
  public function get_link() {
67
  }
68
 
69
  /**
70
+ * @internal
71
  * @param int|bool $uid
72
  */
73
+ protected function init($uid = false) {
74
  if ( $uid === false ) {
75
  $uid = get_current_user_id();
76
  }
141
  }
142
 
143
  /**
144
+ * @api
145
+ * @return string the human-friendly name of the user (ex: "Buster Bluth")
146
  */
147
  function name() {
148
  return $this->display_name;
149
  }
150
 
151
  /**
152
+ * @deprecated 0.21.8
153
  * @return string
154
  */
155
  function get_permalink() {
157
  }
158
 
159
  /**
160
+ * @deprecated 0.21.8
161
  * @return string
162
  */
163
  function permalink() {
165
  }
166
 
167
  /**
168
+ * @internal
169
  * @return string ex: /author/lincoln
170
  */
171
  function get_path() {
181
  }
182
 
183
  /**
184
+ * @api
185
  * @return string
186
  */
187
  function path() {
189
  }
190
 
191
  /**
192
+ * @api
193
  * @return string
194
  */
195
  function slug() {
197
  }
198
 
199
  /**
200
+ * @api
201
  * @return string
202
  */
203
  function link() {
readme.txt CHANGED
@@ -2,7 +2,7 @@
2
  Contributors: jarednova
3
  Tags: template engine, templates, twig
4
  Requires at least: 3.7
5
- Stable tag: 0.21.8
6
  Tested up to: 4.2.3
7
  PHP version: 5.3.0 or greater
8
  License: GPLv2 or later
@@ -34,13 +34,20 @@ Timber is great for any WordPress developer who cares about writing good, mainta
34
 
35
  #### Want to read more?
36
  * [Timber on GitHub](http://github.com/jarednova/timber/)
37
- * [Timber Overview on Tidy Repo](http://www.wpmayor.com/articles/timber-templating-language-wordpress/)
38
- * ["What is WordPress Missing? A Template Language" on Torque](http://torquemag.io/what-is-wordpress-lacking-a-template-language/)
39
 
40
 
41
 
42
  == Changelog ==
43
 
 
 
 
 
 
 
 
44
  = 0.21.8 =
45
  * Fixes to things in docs
46
  * Added ID to timber/image/src filter (thanks @aaronhippie)
2
  Contributors: jarednova
3
  Tags: template engine, templates, twig
4
  Requires at least: 3.7
5
+ Stable tag: 0.21.9
6
  Tested up to: 4.2.3
7
  PHP version: 5.3.0 or greater
8
  License: GPLv2 or later
34
 
35
  #### Want to read more?
36
  * [Timber on GitHub](http://github.com/jarednova/timber/)
37
+ * [Timber Overview on Tidy Repo](http://tidyrepo.com/timber/)
38
+ * ["Timber and Twig Reignited My Love for WordPress" on CSS-Tricks](https://css-tricks.com/timber-and-twig-reignited-my-love-for-wordpress/)
39
 
40
 
41
 
42
  == Changelog ==
43
 
44
+ = 0.21.9 =
45
+ * Much much much more inline docs
46
+ * Fix to TimberComment::approved()
47
+ * HHVM support confirmed (it always worked, but now the tests prove it)
48
+ * Fixes to multisite handling of themes
49
+ * Fix to comments pagination (thanks @newkind)
50
+
51
  = 0.21.8 =
52
  * Fixes to things in docs
53
  * Added ID to timber/image/src filter (thanks @aaronhippie)
timber-starter-theme/search.php CHANGED
@@ -15,4 +15,5 @@ $context = Timber::get_context();
15
  $context['title'] = 'Search results for '. get_search_query();
16
  $context['posts'] = Timber::get_posts();
17
 
 
18
  Timber::render( $templates, $context );
15
  $context['title'] = 'Search results for '. get_search_query();
16
  $context['posts'] = Timber::get_posts();
17
 
18
+
19
  Timber::render( $templates, $context );
timber.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: Timber
4
  Plugin URI: http://timber.upstatement.com
5
  Description: The WordPress Timber Library allows you to write themes using the power Twig templates
6
  Author: Jared Novack + Upstatement
7
- Version: 0.21.8
8
  Author URI: http://upstatement.com/
9
  */
10
 
@@ -208,7 +208,7 @@ class Timber {
208
  public static function get_sites( $blog_ids = false ) {
209
  if ( !is_array( $blog_ids ) ) {
210
  global $wpdb;
211
- $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
212
  }
213
  $return = array();
214
  foreach ( $blog_ids as $blog_id ) {
@@ -296,6 +296,7 @@ class Timber {
296
  $dummy_loader->get_twig();
297
  $loader = new Twig_Loader_String();
298
  $twig = new Twig_Environment( $loader );
 
299
  $twig = apply_filters( 'twig_apply_filters', $twig );
300
  return $twig->render( $string, $data );
301
  }
4
  Plugin URI: http://timber.upstatement.com
5
  Description: The WordPress Timber Library allows you to write themes using the power Twig templates
6
  Author: Jared Novack + Upstatement
7
+ Version: 0.21.9
8
  Author URI: http://upstatement.com/
9
  */
10
 
208
  public static function get_sites( $blog_ids = false ) {
209
  if ( !is_array( $blog_ids ) ) {
210
  global $wpdb;
211
+ $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs ORDER BY blog_id ASC" );
212
  }
213
  $return = array();
214
  foreach ( $blog_ids as $blog_id ) {
296
  $dummy_loader->get_twig();
297
  $loader = new Twig_Loader_String();
298
  $twig = new Twig_Environment( $loader );
299
+ $twig = apply_filters( 'timber/twig/filters', $twig );
300
  $twig = apply_filters( 'twig_apply_filters', $twig );
301
  return $twig->render( $string, $data );
302
  }
vendor/autoload.php CHANGED
@@ -4,4 +4,4 @@
4
 
5
  require_once __DIR__ . '/composer' . '/autoload_real.php';
6
 
7
- return ComposerAutoloaderInit5f4dd105040c53136ae075426ca9493e::getLoader();
4
 
5
  require_once __DIR__ . '/composer' . '/autoload_real.php';
6
 
7
+ return ComposerAutoloaderInit0998bd60918fc7cd5fbb070663b46709::getLoader();
vendor/composer/ClassLoader.php CHANGED
@@ -54,6 +54,8 @@ class ClassLoader
54
  private $useIncludePath = false;
55
  private $classMap = array();
56
 
 
 
57
  public function getPrefixes()
58
  {
59
  if (!empty($this->prefixesPsr0)) {
@@ -248,6 +250,27 @@ class ClassLoader
248
  return $this->useIncludePath;
249
  }
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  /**
252
  * Registers this instance as an autoloader.
253
  *
@@ -299,6 +322,9 @@ class ClassLoader
299
  if (isset($this->classMap[$class])) {
300
  return $this->classMap[$class];
301
  }
 
 
 
302
 
303
  $file = $this->findFileWithExtension($class, '.php');
304
 
54
  private $useIncludePath = false;
55
  private $classMap = array();
56
 
57
+ private $classMapAuthoritative = false;
58
+
59
  public function getPrefixes()
60
  {
61
  if (!empty($this->prefixesPsr0)) {
250
  return $this->useIncludePath;
251
  }
252
 
253
+ /**
254
+ * Turns off searching the prefix and fallback directories for classes
255
+ * that have not been registered with the class map.
256
+ *
257
+ * @param bool $classMapAuthoritative
258
+ */
259
+ public function setClassMapAuthoritative($classMapAuthoritative)
260
+ {
261
+ $this->classMapAuthoritative = $classMapAuthoritative;
262
+ }
263
+
264
+ /**
265
+ * Should class lookup fail if not found in the current class map?
266
+ *
267
+ * @return bool
268
+ */
269
+ public function isClassMapAuthoritative()
270
+ {
271
+ return $this->classMapAuthoritative;
272
+ }
273
+
274
  /**
275
  * Registers this instance as an autoloader.
276
  *
322
  if (isset($this->classMap[$class])) {
323
  return $this->classMap[$class];
324
  }
325
+ if ($this->classMapAuthoritative) {
326
+ return false;
327
+ }
328
 
329
  $file = $this->findFileWithExtension($class, '.php');
330
 
vendor/composer/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Copyright (c) 2015 Nils Adermann, Jordi Boggiano
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
vendor/composer/autoload_real.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
- class ComposerAutoloaderInit5f4dd105040c53136ae075426ca9493e
6
  {
7
  private static $loader;
8
 
@@ -19,9 +19,9 @@ class ComposerAutoloaderInit5f4dd105040c53136ae075426ca9493e
19
  return self::$loader;
20
  }
21
 
22
- spl_autoload_register(array('ComposerAutoloaderInit5f4dd105040c53136ae075426ca9493e', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
- spl_autoload_unregister(array('ComposerAutoloaderInit5f4dd105040c53136ae075426ca9493e', 'loadClassLoader'));
25
 
26
  $map = require __DIR__ . '/autoload_namespaces.php';
27
  foreach ($map as $namespace => $path) {
@@ -44,7 +44,7 @@ class ComposerAutoloaderInit5f4dd105040c53136ae075426ca9493e
44
  }
45
  }
46
 
47
- function composerRequire5f4dd105040c53136ae075426ca9493e($file)
48
  {
49
  require $file;
50
  }
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
+ class ComposerAutoloaderInit0998bd60918fc7cd5fbb070663b46709
6
  {
7
  private static $loader;
8
 
19
  return self::$loader;
20
  }
21
 
22
+ spl_autoload_register(array('ComposerAutoloaderInit0998bd60918fc7cd5fbb070663b46709', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit0998bd60918fc7cd5fbb070663b46709', 'loadClassLoader'));
25
 
26
  $map = require __DIR__ . '/autoload_namespaces.php';
27
  foreach ($map as $namespace => $path) {
44
  }
45
  }
46
 
47
+ function composerRequire0998bd60918fc7cd5fbb070663b46709($file)
48
  {
49
  require $file;
50
  }
vendor/composer/installed.json CHANGED
@@ -98,27 +98,27 @@
98
  },
99
  {
100
  "name": "twig/twig",
101
- "version": "v1.18.2",
102
- "version_normalized": "1.18.2.0",
103
  "source": {
104
  "type": "git",
105
  "url": "https://github.com/twigphp/Twig.git",
106
- "reference": "e8e6575abf6102af53ec283f7f14b89e304fa602"
107
  },
108
  "dist": {
109
  "type": "zip",
110
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/e8e6575abf6102af53ec283f7f14b89e304fa602",
111
- "reference": "e8e6575abf6102af53ec283f7f14b89e304fa602",
112
  "shasum": ""
113
  },
114
  "require": {
115
  "php": ">=5.2.7"
116
  },
117
- "time": "2015-06-06 23:31:24",
118
  "type": "library",
119
  "extra": {
120
  "branch-alias": {
121
- "dev-master": "1.18-dev"
122
  }
123
  },
124
  "installation-source": "dist",
98
  },
99
  {
100
  "name": "twig/twig",
101
+ "version": "v1.20.0",
102
+ "version_normalized": "1.20.0.0",
103
  "source": {
104
  "type": "git",
105
  "url": "https://github.com/twigphp/Twig.git",
106
+ "reference": "1ea4e5f81c6d005fe84d0b38e1c4f1955eb86844"
107
  },
108
  "dist": {
109
  "type": "zip",
110
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/1ea4e5f81c6d005fe84d0b38e1c4f1955eb86844",
111
+ "reference": "1ea4e5f81c6d005fe84d0b38e1c4f1955eb86844",
112
  "shasum": ""
113
  },
114
  "require": {
115
  "php": ">=5.2.7"
116
  },
117
+ "time": "2015-08-12 15:56:39",
118
  "type": "library",
119
  "extra": {
120
  "branch-alias": {
121
+ "dev-master": "1.20-dev"
122
  }
123
  },
124
  "installation-source": "dist",
vendor/twig/twig/CHANGELOG CHANGED
@@ -1,3 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  * 1.18.2 (2015-06-06)
2
 
3
  * fixed template/line guessing in exceptions for nested templates
1
+ * 1.20.0 (2015-08-12)
2
+
3
+ * forbid access to the Twig environment from templates and internal parts of Twig_Template
4
+ * fixed limited RCEs when in sandbox mode
5
+ * deprecated Twig_Template::getEnvironment()
6
+ * deprecated the _self variable for usage outside of the from and import tags
7
+ * added Twig_BaseNodeVisitor to ease the compatibility of node visitors
8
+ between 1.x and 2.x
9
+
10
+ * 1.19.0 (2015-07-31)
11
+
12
+ * fixed wrong error message when including an undefined template in a child template
13
+ * added support for variadic filters, functions, and tests
14
+ * added support for extra positional arguments in macros
15
+ * added ignore_missing flag to the source function
16
+ * fixed batch filter with zero items
17
+ * deprecated Twig_Environment::clearTemplateCache()
18
+ * fixed sandbox disabling when using the include function
19
+
20
  * 1.18.2 (2015-06-06)
21
 
22
  * fixed template/line guessing in exceptions for nested templates
vendor/twig/twig/composer.json CHANGED
@@ -36,7 +36,7 @@
36
  },
37
  "extra": {
38
  "branch-alias": {
39
- "dev-master": "1.18-dev"
40
  }
41
  }
42
  }
36
  },
37
  "extra": {
38
  "branch-alias": {
39
+ "dev-master": "1.20-dev"
40
  }
41
  }
42
  }
vendor/twig/twig/doc/advanced.rst CHANGED
@@ -224,6 +224,23 @@ through your filter::
224
 
225
  $filter = new Twig_SimpleFilter('somefilter', 'somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  Dynamic Filters
228
  ~~~~~~~~~~~~~~~
229
 
@@ -331,6 +348,10 @@ The ``node`` sub-node will contain an expression of ``my_value``. Node-based
331
  tests also have access to the ``arguments`` node. This node will contain the
332
  various other arguments that have been provided to your test.
333
 
 
 
 
 
334
  Tags
335
  ----
336
 
224
 
225
  $filter = new Twig_SimpleFilter('somefilter', 'somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
226
 
227
+ Variadic Filters
228
+ ~~~~~~~~~~~~~~~~
229
+
230
+ .. versionadded:: 1.19
231
+ Support for variadic filters was added in Twig 1.19.
232
+
233
+ When a filter should accept an arbitrary number of arguments, set the
234
+ ``is_variadic`` option to ``true``; Twig will pass the extra arguments as the
235
+ last argument to the filter call as an array::
236
+
237
+ $filter = new Twig_SimpleFilter('thumbnail', function ($file, array $options = array()) {
238
+ // ...
239
+ }, array('is_variadic' => true));
240
+
241
+ Be warned that named arguments passed to a variadic filter cannot be checked
242
+ for validity as they will automatically end up in the option array.
243
+
244
  Dynamic Filters
245
  ~~~~~~~~~~~~~~~
246
 
348
  tests also have access to the ``arguments`` node. This node will contain the
349
  various other arguments that have been provided to your test.
350
 
351
+ If you want to pass a variable number of positional or named arguments to the
352
+ test, set the ``is_variadic`` option to ``true``. Tests also support dynamic
353
+ name feature as filters and functions.
354
+
355
  Tags
356
  ----
357
 
vendor/twig/twig/doc/deprecated.rst CHANGED
@@ -107,9 +107,30 @@ Loaders
107
  * As of Twig 1.x, ``Twig_Loader_String`` is deprecated and will be removed in
108
  2.0.
109
 
 
 
 
 
 
 
 
110
  Globals
111
  -------
112
 
113
  * As of Twig 2.x, the ability to register a global variable after the runtime
114
  or the extensions have been initialized is not possible anymore (but
115
  changing the value of an already registered global is possible).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  * As of Twig 1.x, ``Twig_Loader_String`` is deprecated and will be removed in
108
  2.0.
109
 
110
+ Node Visitors
111
+ -------------
112
+
113
+ * Because of the removal of ``Twig_NodeInterface`` in 2.0, you need to extend
114
+ ``Twig_BaseNodeVistor`` instead of implementing ``Twig_NodeVisitorInterface``
115
+ directly to make your node visitors compatible with both Twig 1.x and 2.x.
116
+
117
  Globals
118
  -------
119
 
120
  * As of Twig 2.x, the ability to register a global variable after the runtime
121
  or the extensions have been initialized is not possible anymore (but
122
  changing the value of an already registered global is possible).
123
+
124
+ * As of Twig 1.x, the ``_self`` global variable is deprecated except for usage
125
+ in the ``from`` and the ``import`` tags. In Twig 2.0, ``_self`` is not
126
+ exposed anymore but still usable in the ``from`` and the ``import`` tags.
127
+
128
+ Miscellaneous
129
+ -------------
130
+
131
+ * As of Twig 1.x, ``Twig_Environment::clearTemplateCache()`` is deprecated and
132
+ will be removed in 2.0.
133
+
134
+ * As of Twig 1.x, ``Twig_Template::getEnvironment()`` and
135
+ ``Twig_TemplateInterface::getEnvironment()`` are deprecated and will be
136
+ removed in 2.0.
vendor/twig/twig/doc/filters/batch.rst CHANGED
@@ -43,3 +43,9 @@ The above example will be rendered as:
43
  <td>No item</td>
44
  </tr>
45
  </table>
 
 
 
 
 
 
43
  <td>No item</td>
44
  </tr>
45
  </table>
46
+
47
+ Arguments
48
+ ---------
49
+
50
+ * ``size``: The size of the batch; fractional numbers will be rounded up
51
+ * ``fill``: Used to fill in missing items
vendor/twig/twig/doc/functions/source.rst CHANGED
@@ -4,6 +4,9 @@
4
  .. versionadded:: 1.15
5
  The ``source`` function was added in Twig 1.15.
6
 
 
 
 
7
  The ``source`` function returns the content of a template without rendering it:
8
 
9
  .. code-block:: jinja
@@ -11,6 +14,13 @@ The ``source`` function returns the content of a template without rendering it:
11
  {{ source('template.html') }}
12
  {{ source(some_var) }}
13
 
 
 
 
 
 
 
 
14
  The function uses the same template loaders as the ones used to include
15
  templates. So, if you are using the filesystem loader, the templates are looked
16
  for in the paths defined by it.
@@ -19,3 +29,4 @@ Arguments
19
  ---------
20
 
21
  * ``name``: The name of the template to read
 
4
  .. versionadded:: 1.15
5
  The ``source`` function was added in Twig 1.15.
6
 
7
+ .. versionadded:: 1.18.3
8
+ The ``ignore_missing`` flag was added in Twig 1.18.3.
9
+
10
  The ``source`` function returns the content of a template without rendering it:
11
 
12
  .. code-block:: jinja
14
  {{ source('template.html') }}
15
  {{ source(some_var) }}
16
 
17
+ When you set the ``ignore_missing`` flag, Twig will return an empty string if
18
+ the template does not exist:
19
+
20
+ .. code-block:: jinja
21
+
22
+ {{ source('template.html', ignore_missing = true) }}
23
+
24
  The function uses the same template loaders as the ones used to include
25
  templates. So, if you are using the filesystem loader, the templates are looked
26
  for in the paths defined by it.
29
  ---------
30
 
31
  * ``name``: The name of the template to read
32
+ * ``ignore_missing``: Whether to ignore missing templates or not
vendor/twig/twig/doc/internals.rst CHANGED
@@ -124,7 +124,7 @@ using)::
124
  {
125
  // line 1
126
  echo "Hello ";
127
- echo twig_escape_filter($this->env, $this->getContext($context, "name"), "html", null, true);
128
  }
129
 
130
  // some more code
124
  {
125
  // line 1
126
  echo "Hello ";
127
+ echo twig_escape_filter($this->env, isset($context["name"]) ? $context["name"] : null), "html", null, true);
128
  }
129
 
130
  // some more code
vendor/twig/twig/doc/intro.rst CHANGED
@@ -21,10 +21,14 @@ The key-features are...
21
  * *Flexible*: Twig is powered by a flexible lexer and parser. This allows the
22
  developer to define its own custom tags and filters, and create its own DSL.
23
 
 
 
 
 
24
  Prerequisites
25
  -------------
26
 
27
- Twig needs at least **PHP 5.2.4** to run.
28
 
29
  Installation
30
  ------------
21
  * *Flexible*: Twig is powered by a flexible lexer and parser. This allows the
22
  developer to define its own custom tags and filters, and create its own DSL.
23
 
24
+ Twig is used by many Open-Source projects like Symfony, Drupal8, eZPublish,
25
+ phpBB, Piwik, OroCRM, and many frameworks have support for it as well like
26
+ Slim, Yii, Laravel, Codeigniter, and Kohana, just to name a few.
27
+
28
  Prerequisites
29
  -------------
30
 
31
+ Twig needs at least **PHP 5.2.7** to run.
32
 
33
  Installation
34
  ------------
vendor/twig/twig/doc/tags/macro.rst CHANGED
@@ -20,6 +20,9 @@ Macros differs from native PHP functions in a few ways:
20
 
21
  * Arguments of a macro are always optional.
22
 
 
 
 
23
  But as with PHP functions, macros don't have access to the current template
24
  variables.
25
 
20
 
21
  * Arguments of a macro are always optional.
22
 
23
+ * If extra positional arguments are passed to a macro, they end up in the
24
+ special ``varargs`` variable as a list of values.
25
+
26
  But as with PHP functions, macros don't have access to the current template
27
  variables.
28
 
vendor/twig/twig/doc/tags/use.rst CHANGED
@@ -74,7 +74,7 @@ is ignored. To avoid name conflicts, you can rename imported blocks:
74
 
75
  {% extends "base.html" %}
76
 
77
- {% use "blocks.html" with sidebar as base_sidebar %}
78
 
79
  {% block sidebar %}{% endblock %}
80
  {% block title %}{% endblock %}
74
 
75
  {% extends "base.html" %}
76
 
77
+ {% use "blocks.html" with sidebar as base_sidebar, title as base_title %}
78
 
79
  {% block sidebar %}{% endblock %}
80
  {% block title %}{% endblock %}
vendor/twig/twig/doc/templates.rst CHANGED
@@ -93,7 +93,7 @@ access the variable attribute:
93
  don't put the braces around them.
94
 
95
  If a variable or attribute does not exist, you will receive a ``null`` value
96
- when the ``strict_variables`` option is set to ``false``; alternatively, if ``strict_variables``
97
  is set, Twig will throw an error (see :ref:`environment options<environment_options>`).
98
 
99
  .. sidebar:: Implementation
@@ -124,7 +124,7 @@ Global Variables
124
 
125
  The following variables are always available in templates:
126
 
127
- * ``_self``: references the current template;
128
  * ``_context``: references the current context;
129
  * ``_charset``: references the current charset.
130
 
@@ -541,6 +541,9 @@ macro call:
541
  <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
542
  {% endmacro %}
543
 
 
 
 
544
  .. _twig-expressions:
545
 
546
  Expressions
93
  don't put the braces around them.
94
 
95
  If a variable or attribute does not exist, you will receive a ``null`` value
96
+ when the ``strict_variables`` option is set to ``false``; alternatively, if ``strict_variables``
97
  is set, Twig will throw an error (see :ref:`environment options<environment_options>`).
98
 
99
  .. sidebar:: Implementation
124
 
125
  The following variables are always available in templates:
126
 
127
+ * ``_self``: references the current template (deprecated since Twig 1.20);
128
  * ``_context``: references the current context;
129
  * ``_charset``: references the current charset.
130
 
541
  <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
542
  {% endmacro %}
543
 
544
+ If extra positional arguments are passed to a macro call, they end up in the
545
+ special ``varargs`` variable as a list of values.
546
+
547
  .. _twig-expressions:
548
 
549
  Expressions
vendor/twig/twig/ext/twig/php_twig.h CHANGED
@@ -15,7 +15,7 @@
15
  #ifndef PHP_TWIG_H
16
  #define PHP_TWIG_H
17
 
18
- #define PHP_TWIG_VERSION "1.18.2"
19
 
20
  #include "php.h"
21
 
15
  #ifndef PHP_TWIG_H
16
  #define PHP_TWIG_H
17
 
18
+ #define PHP_TWIG_VERSION "1.20.0"
19
 
20
  #include "php.h"
21
 
vendor/twig/twig/ext/twig/twig.c CHANGED
@@ -55,7 +55,7 @@ ZEND_BEGIN_ARG_INFO_EX(twig_template_get_attribute_args, ZEND_SEND_BY_VAL, ZEND_
55
  ZEND_END_ARG_INFO()
56
 
57
  #ifndef PHP_FE_END
58
- #define PHP_FE_END { NULL, NULL, NULL, 0, 0 }
59
  #endif
60
 
61
  static const zend_function_entry twig_functions[] = {
@@ -609,6 +609,7 @@ static char *TWIG_GET_CLASS_NAME(zval *object TSRMLS_DC)
609
 
610
  static int twig_add_method_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
611
  {
 
612
  zval *retval;
613
  char *item;
614
  size_t item_len;
@@ -619,12 +620,23 @@ static int twig_add_method_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, v
619
  return 0;
620
  }
621
 
 
622
  retval = va_arg(args, zval*);
623
 
624
  item_len = strlen(mptr->common.function_name);
625
  item = estrndup(mptr->common.function_name, item_len);
626
  php_strtolower(item, item_len);
627
 
 
 
 
 
 
 
 
 
 
 
628
  add_assoc_stringl_ex(retval, item, item_len+1, item, item_len, 0);
629
 
630
  return 0;
@@ -670,7 +682,7 @@ static void twig_add_class_to_cache(zval *cache, zval *object, char *class_name
670
  array_init(class_methods);
671
  array_init(class_properties);
672
  // add all methods to self::cache[$class]['methods']
673
- zend_hash_apply_with_arguments(&class_ce->function_table APPLY_TSRMLS_CC, twig_add_method_to_class, 1, class_methods);
674
  zend_hash_apply_with_arguments(&class_ce->properties_info APPLY_TSRMLS_CC, twig_add_property_to_class, 2, &class_ce, class_properties);
675
 
676
  add_assoc_zval(class_info, "methods", class_methods);
@@ -898,7 +910,7 @@ PHP_FUNCTION(twig_template_get_attributes)
898
 
899
  /*
900
  // object property
901
- if (Twig_Template::METHOD_CALL !== $type) {
902
  if (isset($object->$item) || array_key_exists((string) $item, $object)) {
903
  if ($isDefinedTest) {
904
  return true;
@@ -912,7 +924,7 @@ PHP_FUNCTION(twig_template_get_attributes)
912
  }
913
  }
914
  */
915
- if (strcmp("method", type) != 0) {
916
  zval *tmp_properties, *tmp_item;
917
 
918
  tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC);
@@ -939,7 +951,23 @@ PHP_FUNCTION(twig_template_get_attributes)
939
  /*
940
  // object method
941
  if (!isset(self::$cache[$class]['methods'])) {
942
- self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
943
  }
944
 
945
  $call = false;
55
  ZEND_END_ARG_INFO()
56
 
57
  #ifndef PHP_FE_END
58
+ #define PHP_FE_END { NULL, NULL, NULL}
59
  #endif
60
 
61
  static const zend_function_entry twig_functions[] = {
609
 
610
  static int twig_add_method_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
611
  {
612
+ zend_class_entry *ce;
613
  zval *retval;
614
  char *item;
615
  size_t item_len;
620
  return 0;
621
  }
622
 
623
+ ce = *va_arg(args, zend_class_entry**);
624
  retval = va_arg(args, zval*);
625
 
626
  item_len = strlen(mptr->common.function_name);
627
  item = estrndup(mptr->common.function_name, item_len);
628
  php_strtolower(item, item_len);
629
 
630
+ if (strcmp("getenvironment", item) == 0) {
631
+ zend_class_entry **twig_template_ce;
632
+ if (zend_lookup_class("Twig_Template", strlen("Twig_Template"), &twig_template_ce TSRMLS_CC) == FAILURE) {
633
+ return 0;
634
+ }
635
+ if (instanceof_function(ce, *twig_template_ce TSRMLS_CC)) {
636
+ return 0;
637
+ }
638
+ }
639
+
640
  add_assoc_stringl_ex(retval, item, item_len+1, item, item_len, 0);
641
 
642
  return 0;
682
  array_init(class_methods);
683
  array_init(class_properties);
684
  // add all methods to self::cache[$class]['methods']
685
+ zend_hash_apply_with_arguments(&class_ce->function_table APPLY_TSRMLS_CC, twig_add_method_to_class, 2, &class_ce, class_methods);
686
  zend_hash_apply_with_arguments(&class_ce->properties_info APPLY_TSRMLS_CC, twig_add_property_to_class, 2, &class_ce, class_properties);
687
 
688
  add_assoc_zval(class_info, "methods", class_methods);
910
 
911
  /*
912
  // object property
913
+ if (Twig_Template::METHOD_CALL !== $type && !$object instanceof Twig_Template) {
914
  if (isset($object->$item) || array_key_exists((string) $item, $object)) {
915
  if ($isDefinedTest) {
916
  return true;
924
  }
925
  }
926
  */
927
+ if (strcmp("method", type) != 0 && !TWIG_INSTANCE_OF_USERLAND(object, "Twig_Template" TSRMLS_CC)) {
928
  zval *tmp_properties, *tmp_item;
929
 
930
  tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC);
951
  /*
952
  // object method
953
  if (!isset(self::$cache[$class]['methods'])) {
954
+ if ($object instanceof self) {
955
+ $ref = new ReflectionClass($class);
956
+ $methods = array();
957
+
958
+ foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) {
959
+ $methodName = strtolower($refMethod->name);
960
+
961
+ // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment
962
+ if ('getenvironment' !== $methodName) {
963
+ $methods[$methodName] = true;
964
+ }
965
+ }
966
+
967
+ self::$cache[$class]['methods'] = $methods;
968
+ } else {
969
+ self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
970
+ }
971
  }
972
 
973
  $call = false;
vendor/twig/twig/lib/Twig/BaseNodeVisitor.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Twig_BaseNodeVisitor can be used to make node visitors compatible with Twig 1.x and 2.x.
14
+ *
15
+ * @author Fabien Potencier <fabien@symfony.com>
16
+ */
17
+ abstract class Twig_BaseNodeVisitor implements Twig_NodeVisitorInterface
18
+ {
19
+ /**
20
+ * {@inheritdoc}
21
+ */
22
+ final public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
23
+ {
24
+ if (!$node instanceof Twig_Node) {
25
+ throw new LogicException('Twig_BaseNodeVisitor only supports Twig_Node instances.');
26
+ }
27
+
28
+ return $this->doEnterNode($node, $env);
29
+ }
30
+
31
+ /**
32
+ * {@inheritdoc}
33
+ */
34
+ final public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
35
+ {
36
+ if (!$node instanceof Twig_Node) {
37
+ throw new LogicException('Twig_BaseNodeVisitor only supports Twig_Node instances.');
38
+ }
39
+
40
+ return $this->doLeaveNode($node, $env);
41
+ }
42
+
43
+ /**
44
+ * Called before child nodes are visited.
45
+ *
46
+ * @param Twig_Node $node The node to visit
47
+ * @param Twig_Environment $env The Twig environment instance
48
+ *
49
+ * @return Twig_Node The modified node
50
+ */
51
+ abstract protected function doEnterNode(Twig_Node $node, Twig_Environment $env);
52
+
53
+ /**
54
+ * Called after child nodes are visited.
55
+ *
56
+ * @param Twig_Node $node The node to visit
57
+ * @param Twig_Environment $env The Twig environment instance
58
+ *
59
+ * @return Twig_Node|false The modified node or false if the node must be removed
60
+ */
61
+ abstract protected function doLeaveNode(Twig_Node $node, Twig_Environment $env);
62
+ }
vendor/twig/twig/lib/Twig/Environment.php CHANGED
@@ -16,7 +16,7 @@
16
  */
17
  class Twig_Environment
18
  {
19
- const VERSION = '1.18.2';
20
 
21
  protected $charset;
22
  protected $loader;
@@ -89,21 +89,21 @@ class Twig_Environment
89
  }
90
 
91
  $options = array_merge(array(
92
- 'debug' => false,
93
- 'charset' => 'UTF-8',
94
  'base_template_class' => 'Twig_Template',
95
- 'strict_variables' => false,
96
- 'autoescape' => 'html',
97
- 'cache' => false,
98
- 'auto_reload' => null,
99
- 'optimizations' => -1,
100
  ), $options);
101
 
102
- $this->debug = (bool) $options['debug'];
103
- $this->charset = strtoupper($options['charset']);
104
- $this->baseTemplateClass = $options['base_template_class'];
105
- $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
106
- $this->strictVariables = (bool) $options['strict_variables'];
107
  $this->runtimeInitialized = false;
108
  $this->setCache($options['cache']);
109
  $this->functionCallbacks = array();
@@ -443,6 +443,8 @@ class Twig_Environment
443
 
444
  /**
445
  * Clears the internal template cache.
 
 
446
  */
447
  public function clearTemplateCache()
448
  {
16
  */
17
  class Twig_Environment
18
  {
19
+ const VERSION = '1.20.0';
20
 
21
  protected $charset;
22
  protected $loader;
89
  }
90
 
91
  $options = array_merge(array(
92
+ 'debug' => false,
93
+ 'charset' => 'UTF-8',
94
  'base_template_class' => 'Twig_Template',
95
+ 'strict_variables' => false,
96
+ 'autoescape' => 'html',
97
+ 'cache' => false,
98
+ 'auto_reload' => null,
99
+ 'optimizations' => -1,
100
  ), $options);
101
 
102
+ $this->debug = (bool) $options['debug'];
103
+ $this->charset = strtoupper($options['charset']);
104
+ $this->baseTemplateClass = $options['base_template_class'];
105
+ $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
106
+ $this->strictVariables = (bool) $options['strict_variables'];
107
  $this->runtimeInitialized = false;
108
  $this->setCache($options['cache']);
109
  $this->functionCallbacks = array();
443
 
444
  /**
445
  * Clears the internal template cache.
446
+ *
447
+ * @deprecated since 1.18.3 (to be removed in 2.0)
448
  */
449
  public function clearTemplateCache()
450
  {
vendor/twig/twig/lib/Twig/ExpressionParser.php CHANGED
@@ -387,7 +387,13 @@ class Twig_ExpressionParser
387
  throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
388
  }
389
 
390
- $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
 
 
 
 
 
 
391
  $node->setAttribute('safe', true);
392
 
393
  return $node;
387
  throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
388
  }
389
 
390
+ $name = $arg->getAttribute('value');
391
+
392
+ if ($this->parser->isReservedMacroName($name)) {
393
+ throw new Twig_Error_Syntax(sprintf('"%s" cannot be called as macro as it is a reserved keyword', $name), $token->getLine(), $this->parser->getFilename());
394
+ }
395
+
396
+ $node = new Twig_Node_Expression_MethodCall($node, 'get'.$name, $arguments, $lineno);
397
  $node->setAttribute('safe', true);
398
 
399
  return $node;
vendor/twig/twig/lib/Twig/Extension/Core.php CHANGED
@@ -255,37 +255,37 @@ class Twig_Extension_Core extends Twig_Extension
255
  return array(
256
  array(
257
  'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
258
- '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'),
259
- '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
260
  ),
261
  array(
262
- 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
263
- 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
264
- 'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
265
- 'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
266
- 'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
267
- '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
268
- '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
269
- '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
270
- '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
271
- '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
272
- '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
273
- 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
274
- 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
275
- 'matches' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
276
  'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
277
- 'ends with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
278
- '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
279
- '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
280
- '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
281
- '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
282
- '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
283
- '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
284
- '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
285
- '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
286
- 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
287
- 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
288
- '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
289
  ),
290
  );
291
  }
@@ -382,7 +382,7 @@ function twig_cycle($values, $position)
382
  * Returns a random value depending on the supplied parameter type:
383
  * - a random item from a Traversable or array
384
  * - a random character from a string
385
- * - a random integer between 0 and the integer parameter
386
  *
387
  * @param Twig_Environment $env A Twig_Environment instance
388
  * @param Traversable|array|int|string $values The values to pick a random item from
@@ -466,7 +466,7 @@ function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $
466
  }
467
 
468
  /**
469
- * Returns a new date object modified
470
  *
471
  * <pre>
472
  * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
@@ -1190,7 +1190,7 @@ function _twig_escape_html_attr_callback($matches)
1190
  $chr = $matches[0];
1191
  $ord = ord($chr);
1192
 
1193
- /**
1194
  * The following replaces characters undefined in HTML with the
1195
  * hex entity for the Unicode replacement character.
1196
  */
@@ -1198,7 +1198,7 @@ function _twig_escape_html_attr_callback($matches)
1198
  return '&#xFFFD;';
1199
  }
1200
 
1201
- /**
1202
  * Check if the current character to escape has a name entity we should
1203
  * replace it with while grabbing the hex value of the character.
1204
  */
@@ -1214,7 +1214,7 @@ function _twig_escape_html_attr_callback($matches)
1214
  return sprintf('&%s;', $entityMap[$int]);
1215
  }
1216
 
1217
- /**
1218
  * Per OWASP recommendations, we'll use hex entities for any other
1219
  * characters where a named entity does not exist.
1220
  */
@@ -1427,10 +1427,15 @@ function twig_include(Twig_Environment $env, $context, $template, $variables = a
1427
  }
1428
  }
1429
 
 
1430
  try {
1431
- return $env->resolveTemplate($template)->render($variables);
1432
  } catch (Twig_Error_Loader $e) {
1433
  if (!$ignoreMissing) {
 
 
 
 
1434
  throw $e;
1435
  }
1436
  }
@@ -1438,18 +1443,27 @@ function twig_include(Twig_Environment $env, $context, $template, $variables = a
1438
  if ($isSandboxed && !$alreadySandboxed) {
1439
  $sandbox->disableSandbox();
1440
  }
 
 
1441
  }
1442
 
1443
  /**
1444
  * Returns a template content without rendering it.
1445
  *
1446
- * @param string $name The template name
 
1447
  *
1448
  * @return string The template source
1449
  */
1450
- function twig_source(Twig_Environment $env, $name)
1451
  {
1452
- return $env->getLoader()->getSource($name);
 
 
 
 
 
 
1453
  }
1454
 
1455
  /**
@@ -1488,7 +1502,7 @@ function twig_array_batch($items, $size, $fill = null)
1488
 
1489
  $result = array_chunk($items, $size, true);
1490
 
1491
- if (null !== $fill) {
1492
  $last = count($result) - 1;
1493
  if ($fillCount = $size - count($result[$last])) {
1494
  $result[$last] = array_merge(
255
  return array(
256
  array(
257
  'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
258
+ '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'),
259
+ '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
260
  ),
261
  array(
262
+ 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
263
+ 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
264
+ 'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
265
+ 'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
266
+ 'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
267
+ '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
268
+ '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
269
+ '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
270
+ '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
271
+ '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
272
+ '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
273
+ 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
274
+ 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
275
+ 'matches' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
276
  'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
277
+ 'ends with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
278
+ '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
279
+ '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
280
+ '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
281
+ '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
282
+ '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
283
+ '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
284
+ '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
285
+ '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
286
+ 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
287
+ 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
288
+ '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
289
  ),
290
  );
291
  }
382
  * Returns a random value depending on the supplied parameter type:
383
  * - a random item from a Traversable or array
384
  * - a random character from a string
385
+ * - a random integer between 0 and the integer parameter.
386
  *
387
  * @param Twig_Environment $env A Twig_Environment instance
388
  * @param Traversable|array|int|string $values The values to pick a random item from
466
  }
467
 
468
  /**
469
+ * Returns a new date object modified.
470
  *
471
  * <pre>
472
  * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
1190
  $chr = $matches[0];
1191
  $ord = ord($chr);
1192
 
1193
+ /*
1194
  * The following replaces characters undefined in HTML with the
1195
  * hex entity for the Unicode replacement character.
1196
  */
1198
  return '&#xFFFD;';
1199
  }
1200
 
1201
+ /*
1202
  * Check if the current character to escape has a name entity we should
1203
  * replace it with while grabbing the hex value of the character.
1204
  */
1214
  return sprintf('&%s;', $entityMap[$int]);
1215
  }
1216
 
1217
+ /*
1218
  * Per OWASP recommendations, we'll use hex entities for any other
1219
  * characters where a named entity does not exist.
1220
  */
1427
  }
1428
  }
1429
 
1430
+ $result = null;
1431
  try {
1432
+ $result = $env->resolveTemplate($template)->render($variables);
1433
  } catch (Twig_Error_Loader $e) {
1434
  if (!$ignoreMissing) {
1435
+ if ($isSandboxed && !$alreadySandboxed) {
1436
+ $sandbox->disableSandbox();
1437
+ }
1438
+
1439
  throw $e;
1440
  }
1441
  }
1443
  if ($isSandboxed && !$alreadySandboxed) {
1444
  $sandbox->disableSandbox();
1445
  }
1446
+
1447
+ return $result;
1448
  }
1449
 
1450
  /**
1451
  * Returns a template content without rendering it.
1452
  *
1453
+ * @param string $name The template name
1454
+ * @param bool $ignoreMissing Whether to ignore missing templates or not
1455
  *
1456
  * @return string The template source
1457
  */
1458
+ function twig_source(Twig_Environment $env, $name, $ignoreMissing = false)
1459
  {
1460
+ try {
1461
+ return $env->getLoader()->getSource($name);
1462
+ } catch (Twig_Error_Loader $e) {
1463
+ if (!$ignoreMissing) {
1464
+ throw $e;
1465
+ }
1466
+ }
1467
  }
1468
 
1469
  /**
1502
 
1503
  $result = array_chunk($items, $size, true);
1504
 
1505
+ if (null !== $fill && !empty($result)) {
1506
  $last = count($result) - 1;
1507
  if ($fillCount = $size - count($result[$last])) {
1508
  $result[$last] = array_merge(
vendor/twig/twig/lib/Twig/Extension/Debug.php CHANGED
@@ -62,7 +62,7 @@ function twig_var_dump(Twig_Environment $env, $context)
62
 
63
  var_dump($vars);
64
  } else {
65
- for ($i = 2; $i < $count; $i++) {
66
  var_dump(func_get_arg($i));
67
  }
68
  }
62
 
63
  var_dump($vars);
64
  } else {
65
+ for ($i = 2; $i < $count; ++$i) {
66
  var_dump(func_get_arg($i));
67
  }
68
  }
vendor/twig/twig/lib/Twig/Extension/Sandbox.php CHANGED
@@ -16,7 +16,7 @@ class Twig_Extension_Sandbox extends Twig_Extension
16
 
17
  public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false)
18
  {
19
- $this->policy = $policy;
20
  $this->sandboxedGlobally = $sandboxed;
21
  }
22
 
16
 
17
  public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false)
18
  {
19
+ $this->policy = $policy;
20
  $this->sandboxedGlobally = $sandboxed;
21
  }
22
 
vendor/twig/twig/lib/Twig/Filter.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface
@@ -26,10 +27,10 @@ abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableI
26
  {
27
  $this->options = array_merge(array(
28
  'needs_environment' => false,
29
- 'needs_context' => false,
30
- 'pre_escape' => null,
31
- 'preserves_safety' => null,
32
- 'callable' => null,
33
  ), $options);
34
  }
35
 
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface
27
  {
28
  $this->options = array_merge(array(
29
  'needs_environment' => false,
30
+ 'needs_context' => false,
31
+ 'pre_escape' => null,
32
+ 'preserves_safety' => null,
33
+ 'callable' => null,
34
  ), $options);
35
  }
36
 
vendor/twig/twig/lib/Twig/Filter/Function.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  class Twig_Filter_Function extends Twig_Filter
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  class Twig_Filter_Function extends Twig_Filter
vendor/twig/twig/lib/Twig/Filter/Method.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  class Twig_Filter_Method extends Twig_Filter
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  class Twig_Filter_Method extends Twig_Filter
vendor/twig/twig/lib/Twig/Filter/Node.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  class Twig_Filter_Node extends Twig_Filter
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  class Twig_Filter_Node extends Twig_Filter
vendor/twig/twig/lib/Twig/FilterCallableInterface.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  interface Twig_FilterCallableInterface
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  interface Twig_FilterCallableInterface
vendor/twig/twig/lib/Twig/FilterInterface.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  interface Twig_FilterInterface
15
  * Use Twig_SimpleFilter instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  interface Twig_FilterInterface
vendor/twig/twig/lib/Twig/Function.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFunction instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface
@@ -26,8 +27,8 @@ abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCal
26
  {
27
  $this->options = array_merge(array(
28
  'needs_environment' => false,
29
- 'needs_context' => false,
30
- 'callable' => null,
31
  ), $options);
32
  }
33
 
15
  * Use Twig_SimpleFunction instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface
27
  {
28
  $this->options = array_merge(array(
29
  'needs_environment' => false,
30
+ 'needs_context' => false,
31
+ 'callable' => null,
32
  ), $options);
33
  }
34
 
vendor/twig/twig/lib/Twig/Function/Function.php CHANGED
@@ -16,6 +16,7 @@
16
  * Use Twig_SimpleFunction instead.
17
  *
18
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
 
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  class Twig_Function_Function extends Twig_Function
16
  * Use Twig_SimpleFunction instead.
17
  *
18
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
19
+ *
20
  * @deprecated since 1.12 (to be removed in 2.0)
21
  */
22
  class Twig_Function_Function extends Twig_Function
vendor/twig/twig/lib/Twig/Function/Method.php CHANGED
@@ -16,6 +16,7 @@
16
  * Use Twig_SimpleFunction instead.
17
  *
18
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
 
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  class Twig_Function_Method extends Twig_Function
16
  * Use Twig_SimpleFunction instead.
17
  *
18
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
19
+ *
20
  * @deprecated since 1.12 (to be removed in 2.0)
21
  */
22
  class Twig_Function_Method extends Twig_Function
vendor/twig/twig/lib/Twig/Function/Node.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFunction instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  class Twig_Function_Node extends Twig_Function
15
  * Use Twig_SimpleFunction instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  class Twig_Function_Node extends Twig_Function
vendor/twig/twig/lib/Twig/FunctionCallableInterface.php CHANGED
@@ -15,6 +15,7 @@
15
  * Use Twig_SimpleFunction instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
 
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  interface Twig_FunctionCallableInterface
15
  * Use Twig_SimpleFunction instead.
16
  *
17
  * @author Fabien Potencier <fabien@symfony.com>
18
+ *
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  interface Twig_FunctionCallableInterface
vendor/twig/twig/lib/Twig/FunctionInterface.php CHANGED
@@ -16,6 +16,7 @@
16
  * Use Twig_SimpleFunction instead.
17
  *
18
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
 
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  interface Twig_FunctionInterface
16
  * Use Twig_SimpleFunction instead.
17
  *
18
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
19
+ *
20
  * @deprecated since 1.12 (to be removed in 2.0)
21
  */
22
  interface Twig_FunctionInterface
vendor/twig/twig/lib/Twig/Lexer.php CHANGED
@@ -33,42 +33,42 @@ class Twig_Lexer implements Twig_LexerInterface
33
  protected $positions;
34
  protected $currentVarBlockLine;
35
 
36
- const STATE_DATA = 0;
37
- const STATE_BLOCK = 1;
38
- const STATE_VAR = 2;
39
- const STATE_STRING = 3;
40
- const STATE_INTERPOLATION = 4;
41
-
42
- const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
43
- const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
44
- const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
45
  const REGEX_DQ_STRING_DELIM = '/"/A';
46
- const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
47
- const PUNCTUATION = '()[]{}?:.,|';
48
 
49
  public function __construct(Twig_Environment $env, array $options = array())
50
  {
51
  $this->env = $env;
52
 
53
  $this->options = array_merge(array(
54
- 'tag_comment' => array('{#', '#}'),
55
- 'tag_block' => array('{%', '%}'),
56
- 'tag_variable' => array('{{', '}}'),
57
  'whitespace_trim' => '-',
58
- 'interpolation' => array('#{', '}'),
59
  ), $options);
60
 
61
  $this->regexes = array(
62
- 'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A',
63
- 'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A',
64
- 'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*(?:end%s)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s',
65
- 'operator' => $this->getOperatorRegex(),
66
- 'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s',
67
- 'lex_block_raw' => '/\s*(raw|verbatim)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As',
68
- 'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As',
69
- 'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s',
70
  'interpolation_start' => '/'.preg_quote($this->options['interpolation'][0], '/').'\s*/A',
71
- 'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A',
72
  );
73
  }
74
 
33
  protected $positions;
34
  protected $currentVarBlockLine;
35
 
36
+ const STATE_DATA = 0;
37
+ const STATE_BLOCK = 1;
38
+ const STATE_VAR = 2;
39
+ const STATE_STRING = 3;
40
+ const STATE_INTERPOLATION = 4;
41
+
42
+ const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
43
+ const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A';
44
+ const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
45
  const REGEX_DQ_STRING_DELIM = '/"/A';
46
+ const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
47
+ const PUNCTUATION = '()[]{}?:.,|';
48
 
49
  public function __construct(Twig_Environment $env, array $options = array())
50
  {
51
  $this->env = $env;
52
 
53
  $this->options = array_merge(array(
54
+ 'tag_comment' => array('{#', '#}'),
55
+ 'tag_block' => array('{%', '%}'),
56
+ 'tag_variable' => array('{{', '}}'),
57
  'whitespace_trim' => '-',
58
+ 'interpolation' => array('#{', '}'),
59
  ), $options);
60
 
61
  $this->regexes = array(
62
+ 'lex_var' => '/\s*'.preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_variable'][1], '/').'/A',
63
+ 'lex_block' => '/\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')\n?/A',
64
+ 'lex_raw_data' => '/('.preg_quote($this->options['tag_block'][0].$this->options['whitespace_trim'], '/').'|'.preg_quote($this->options['tag_block'][0], '/').')\s*(?:end%s)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/s',
65
+ 'operator' => $this->getOperatorRegex(),
66
+ 'lex_comment' => '/(?:'.preg_quote($this->options['whitespace_trim'], '/').preg_quote($this->options['tag_comment'][1], '/').'\s*|'.preg_quote($this->options['tag_comment'][1], '/').')\n?/s',
67
+ 'lex_block_raw' => '/\s*(raw|verbatim)\s*(?:'.preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '/').'\s*|\s*'.preg_quote($this->options['tag_block'][1], '/').')/As',
68
+ 'lex_block_line' => '/\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '/').'/As',
69
+ 'lex_tokens_start' => '/('.preg_quote($this->options['tag_variable'][0], '/').'|'.preg_quote($this->options['tag_block'][0], '/').'|'.preg_quote($this->options['tag_comment'][0], '/').')('.preg_quote($this->options['whitespace_trim'], '/').')?/s',
70
  'interpolation_start' => '/'.preg_quote($this->options['interpolation'][0], '/').'\s*/A',
71
+ 'interpolation_end' => '/\s*'.preg_quote($this->options['interpolation'][1], '/').'/A',
72
  );
73
  }
74
 
vendor/twig/twig/lib/Twig/LoaderInterface.php CHANGED
@@ -41,9 +41,9 @@ interface Twig_LoaderInterface
41
  /**
42
  * Returns true if the template is still fresh.
43
  *
44
- * @param string $name The template name
45
- * @param int $time Timestamp of the last modification time of the
46
- * cached template
47
  *
48
  * @return bool true if the template is fresh, false otherwise
49
  *
41
  /**
42
  * Returns true if the template is still fresh.
43
  *
44
+ * @param string $name The template name
45
+ * @param int $time Timestamp of the last modification time of the
46
+ * cached template
47
  *
48
  * @return bool true if the template is fresh, false otherwise
49
  *
vendor/twig/twig/lib/Twig/Node/CheckSecurity.php CHANGED
@@ -41,9 +41,9 @@ class Twig_Node_CheckSecurity extends Twig_Node
41
  }
42
 
43
  $compiler
44
- ->write("\$tags = ")->repr(array_filter($tags))->raw(";\n")
45
- ->write("\$filters = ")->repr(array_filter($filters))->raw(";\n")
46
- ->write("\$functions = ")->repr(array_filter($functions))->raw(";\n\n")
47
  ->write("try {\n")
48
  ->indent()
49
  ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
41
  }
42
 
43
  $compiler
44
+ ->write('$tags = ')->repr(array_filter($tags))->raw(";\n")
45
+ ->write('$filters = ')->repr(array_filter($filters))->raw(";\n")
46
+ ->write('$functions = ')->repr(array_filter($functions))->raw(";\n\n")
47
  ->write("try {\n")
48
  ->indent()
49
  ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
vendor/twig/twig/lib/Twig/Node/Embed.php CHANGED
@@ -28,7 +28,7 @@ class Twig_Node_Embed extends Twig_Node_Include
28
  protected function addGetTemplate(Twig_Compiler $compiler)
29
  {
30
  $compiler
31
- ->write("\$this->loadTemplate(")
32
  ->string($this->getAttribute('filename'))
33
  ->raw(', ')
34
  ->repr($compiler->getFilename())
@@ -36,7 +36,7 @@ class Twig_Node_Embed extends Twig_Node_Include
36
  ->repr($this->getLine())
37
  ->raw(', ')
38
  ->string($this->getAttribute('index'))
39
- ->raw(")")
40
  ;
41
  }
42
  }
28
  protected function addGetTemplate(Twig_Compiler $compiler)
29
  {
30
  $compiler
31
+ ->write('$this->loadTemplate(')
32
  ->string($this->getAttribute('filename'))
33
  ->raw(', ')
34
  ->repr($compiler->getFilename())
36
  ->repr($this->getLine())
37
  ->raw(', ')
38
  ->string($this->getAttribute('index'))
39
+ ->raw(')')
40
  ;
41
  }
42
  }
vendor/twig/twig/lib/Twig/Node/Expression/BlockReference.php CHANGED
@@ -36,15 +36,15 @@ class Twig_Node_Expression_BlockReference extends Twig_Node_Expression
36
  if ($this->getAttribute('output')) {
37
  $compiler
38
  ->addDebugInfo($this)
39
- ->write("\$this->displayBlock(")
40
  ->subcompile($this->getNode('name'))
41
  ->raw(", \$context, \$blocks);\n")
42
  ;
43
  } else {
44
  $compiler
45
- ->raw("\$this->renderBlock(")
46
  ->subcompile($this->getNode('name'))
47
- ->raw(", \$context, \$blocks)")
48
  ;
49
  }
50
  }
36
  if ($this->getAttribute('output')) {
37
  $compiler
38
  ->addDebugInfo($this)
39
+ ->write('$this->displayBlock(')
40
  ->subcompile($this->getNode('name'))
41
  ->raw(", \$context, \$blocks);\n")
42
  ;
43
  } else {
44
  $compiler
45
+ ->raw('$this->renderBlock(')
46
  ->subcompile($this->getNode('name'))
47
+ ->raw(', $context, $blocks)')
48
  ;
49
  }
50
  }
vendor/twig/twig/lib/Twig/Node/Expression/Call.php CHANGED
@@ -106,12 +106,19 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
106
  $parameters[$name] = $node;
107
  }
108
 
109
- if (!$named) {
 
110
  return $parameters;
111
  }
112
 
113
  if (!$callable) {
114
- throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $callType, $callName));
 
 
 
 
 
 
115
  }
116
 
117
  // manage named arguments
@@ -141,6 +148,19 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
141
  array_shift($definition);
142
  }
143
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
  $arguments = array();
146
  $names = array();
@@ -185,6 +205,23 @@ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
185
  }
186
  }
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  if (!empty($parameters)) {
189
  $unknownParameter = null;
190
  foreach ($parameters as $parameter) {
106
  $parameters[$name] = $node;
107
  }
108
 
109
+ $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic');
110
+ if (!$named && !$isVariadic) {
111
  return $parameters;
112
  }
113
 
114
  if (!$callable) {
115
+ if ($named) {
116
+ $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName);
117
+ } else {
118
+ $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName);
119
+ }
120
+
121
+ throw new LogicException($message);
122
  }
123
 
124
  // manage named arguments
148
  array_shift($definition);
149
  }
150
  }
151
+ if ($isVariadic) {
152
+ $argument = end($definition);
153
+ if ($argument && $argument->isArray() && $argument->isDefaultValueAvailable() && array() === $argument->getDefaultValue()) {
154
+ array_pop($definition);
155
+ } else {
156
+ $callableName = $r->name;
157
+ if ($r->getDeclaringClass()) {
158
+ $callableName = $r->getDeclaringClass()->name.'::'.$callableName;
159
+ }
160
+
161
+ throw new LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = array()".', $callableName, $callType, $callName));
162
+ }
163
+ }
164
 
165
  $arguments = array();
166
  $names = array();
205
  }
206
  }
207
 
208
+ if ($isVariadic) {
209
+ $arbitraryArguments = new Twig_Node_Expression_Array(array(), -1);
210
+ foreach ($parameters as $key => $value) {
211
+ if (is_int($key)) {
212
+ $arbitraryArguments->addElement($value);
213
+ } else {
214
+ $arbitraryArguments->addElement($value, new Twig_Node_Expression_Constant($key, -1));
215
+ }
216
+ unset($parameters[$key]);
217
+ }
218
+
219
+ if ($arbitraryArguments->count()) {
220
+ $arguments = array_merge($arguments, $optionalArguments);
221
+ $arguments[] = $arbitraryArguments;
222
+ }
223
+ }
224
+
225
  if (!empty($parameters)) {
226
  $unknownParameter = null;
227
  foreach ($parameters as $parameter) {
vendor/twig/twig/lib/Twig/Node/Expression/Filter.php CHANGED
@@ -30,6 +30,9 @@ class Twig_Node_Expression_Filter extends Twig_Node_Expression_Call
30
  if ($filter instanceof Twig_FilterCallableInterface || $filter instanceof Twig_SimpleFilter) {
31
  $this->setAttribute('callable', $filter->getCallable());
32
  }
 
 
 
33
 
34
  $this->compileCallable($compiler);
35
  }
30
  if ($filter instanceof Twig_FilterCallableInterface || $filter instanceof Twig_SimpleFilter) {
31
  $this->setAttribute('callable', $filter->getCallable());
32
  }
33
+ if ($filter instanceof Twig_SimpleFilter) {
34
+ $this->setAttribute('is_variadic', $filter->isVariadic());
35
+ }
36
 
37
  $this->compileCallable($compiler);
38
  }
vendor/twig/twig/lib/Twig/Node/Expression/Function.php CHANGED
@@ -29,6 +29,9 @@ class Twig_Node_Expression_Function extends Twig_Node_Expression_Call
29
  if ($function instanceof Twig_FunctionCallableInterface || $function instanceof Twig_SimpleFunction) {
30
  $this->setAttribute('callable', $function->getCallable());
31
  }
 
 
 
32
 
33
  $this->compileCallable($compiler);
34
  }
29
  if ($function instanceof Twig_FunctionCallableInterface || $function instanceof Twig_SimpleFunction) {
30
  $this->setAttribute('callable', $function->getCallable());
31
  }
32
+ if ($function instanceof Twig_SimpleFunction) {
33
+ $this->setAttribute('is_variadic', $function->isVariadic());
34
+ }
35
 
36
  $this->compileCallable($compiler);
37
  }
vendor/twig/twig/lib/Twig/Node/Expression/Name.php CHANGED
@@ -12,7 +12,7 @@
12
  class Twig_Node_Expression_Name extends Twig_Node_Expression
13
  {
14
  protected $specialVars = array(
15
- '_self' => '$this',
16
  '_context' => '$context',
17
  '_charset' => '$this->env->getCharset()',
18
  );
12
  class Twig_Node_Expression_Name extends Twig_Node_Expression
13
  {
14
  protected $specialVars = array(
15
+ '_self' => '$this',
16
  '_context' => '$context',
17
  '_charset' => '$this->env->getCharset()',
18
  );
vendor/twig/twig/lib/Twig/Node/Expression/Parent.php CHANGED
@@ -32,15 +32,15 @@ class Twig_Node_Expression_Parent extends Twig_Node_Expression
32
  if ($this->getAttribute('output')) {
33
  $compiler
34
  ->addDebugInfo($this)
35
- ->write("\$this->displayParentBlock(")
36
  ->string($this->getAttribute('name'))
37
  ->raw(", \$context, \$blocks);\n")
38
  ;
39
  } else {
40
  $compiler
41
- ->raw("\$this->renderParentBlock(")
42
  ->string($this->getAttribute('name'))
43
- ->raw(", \$context, \$blocks)")
44
  ;
45
  }
46
  }
32
  if ($this->getAttribute('output')) {
33
  $compiler
34
  ->addDebugInfo($this)
35
+ ->write('$this->displayParentBlock(')
36
  ->string($this->getAttribute('name'))
37
  ->raw(", \$context, \$blocks);\n")
38
  ;
39
  } else {
40
  $compiler
41
+ ->raw('$this->renderParentBlock(')
42
  ->string($this->getAttribute('name'))
43
+ ->raw(', $context, $blocks)')
44
  ;
45
  }
46
  }
vendor/twig/twig/lib/Twig/Node/Expression/Test.php CHANGED
@@ -26,6 +26,9 @@ class Twig_Node_Expression_Test extends Twig_Node_Expression_Call
26
  if ($test instanceof Twig_TestCallableInterface || $test instanceof Twig_SimpleTest) {
27
  $this->setAttribute('callable', $test->getCallable());
28
  }
 
 
 
29
 
30
  $this->compileCallable($compiler);
31
  }
26
  if ($test instanceof Twig_TestCallableInterface || $test instanceof Twig_SimpleTest) {
27
  $this->setAttribute('callable', $test->getCallable());
28
  }
29
+ if ($test instanceof Twig_SimpleTest) {
30
+ $this->setAttribute('is_variadic', $test->isVariadic());
31
+ }
32
 
33
  $this->compileCallable($compiler);
34
  }
vendor/twig/twig/lib/Twig/Node/For.php CHANGED
@@ -82,7 +82,7 @@ class Twig_Node_For extends Twig_Node
82
  $compiler
83
  ->write("foreach (\$context['_seq'] as ")
84
  ->subcompile($this->getNode('key_target'))
85
- ->raw(" => ")
86
  ->subcompile($this->getNode('value_target'))
87
  ->raw(") {\n")
88
  ->indent()
82
  $compiler
83
  ->write("foreach (\$context['_seq'] as ")
84
  ->subcompile($this->getNode('key_target'))
85
+ ->raw(' => ')
86
  ->subcompile($this->getNode('value_target'))
87
  ->raw(") {\n")
88
  ->indent()
vendor/twig/twig/lib/Twig/Node/If.php CHANGED
@@ -34,7 +34,7 @@ class Twig_Node_If extends Twig_Node
34
  if ($i > 0) {
35
  $compiler
36
  ->outdent()
37
- ->write("} elseif (")
38
  ;
39
  } else {
40
  $compiler
34
  if ($i > 0) {
35
  $compiler
36
  ->outdent()
37
+ ->write('} elseif (')
38
  ;
39
  } else {
40
  $compiler
vendor/twig/twig/lib/Twig/Node/Import.php CHANGED
@@ -36,7 +36,7 @@ class Twig_Node_Import extends Twig_Node
36
  ;
37
 
38
  if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) {
39
- $compiler->raw("\$this");
40
  } else {
41
  $compiler
42
  ->raw('$this->loadTemplate(')
@@ -45,7 +45,7 @@ class Twig_Node_Import extends Twig_Node
45
  ->repr($compiler->getFilename())
46
  ->raw(', ')
47
  ->repr($this->getLine())
48
- ->raw(")")
49
  ;
50
  }
51
 
36
  ;
37
 
38
  if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) {
39
+ $compiler->raw('$this');
40
  } else {
41
  $compiler
42
  ->raw('$this->loadTemplate(')
45
  ->repr($compiler->getFilename())
46
  ->raw(', ')
47
  ->repr($this->getLine())
48
+ ->raw(')')
49
  ;
50
  }
51
 
vendor/twig/twig/lib/Twig/Node/Include.php CHANGED
@@ -61,13 +61,13 @@ class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface
61
  protected function addGetTemplate(Twig_Compiler $compiler)
62
  {
63
  $compiler
64
- ->write("\$this->loadTemplate(")
65
  ->subcompile($this->getNode('expr'))
66
  ->raw(', ')
67
  ->repr($compiler->getFilename())
68
  ->raw(', ')
69
  ->repr($this->getLine())
70
- ->raw(")")
71
  ;
72
  }
73
 
61
  protected function addGetTemplate(Twig_Compiler $compiler)
62
  {
63
  $compiler
64
+ ->write('$this->loadTemplate(')
65
  ->subcompile($this->getNode('expr'))
66
  ->raw(', ')
67
  ->repr($compiler->getFilename())
68
  ->raw(', ')
69
  ->repr($this->getLine())
70
+ ->raw(')')
71
  ;
72
  }
73
 
vendor/twig/twig/lib/Twig/Node/Macro.php CHANGED
@@ -16,8 +16,16 @@
16
  */
17
  class Twig_Node_Macro extends Twig_Node
18
  {
 
 
19
  public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null)
20
  {
 
 
 
 
 
 
21
  parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag);
22
  }
23
 
@@ -30,7 +38,7 @@ class Twig_Node_Macro extends Twig_Node
30
  {
31
  $compiler
32
  ->addDebugInfo($this)
33
- ->write(sprintf("public function get%s(", $this->getAttribute('name')))
34
  ;
35
 
36
  $count = count($this->getNode('arguments'));
@@ -46,36 +54,55 @@ class Twig_Node_Macro extends Twig_Node
46
  }
47
  }
48
 
 
 
 
 
 
 
 
 
49
  $compiler
50
  ->raw(")\n")
51
  ->write("{\n")
52
  ->indent()
53
  ;
54
 
55
- if (!count($this->getNode('arguments'))) {
56
- $compiler->write("\$context = \$this->env->getGlobals();\n\n");
57
- } else {
 
 
 
58
  $compiler
59
- ->write("\$context = \$this->env->mergeGlobals(array(\n")
60
- ->indent()
 
 
61
  ;
 
62
 
63
- foreach ($this->getNode('arguments') as $name => $default) {
64
- $compiler
65
- ->write('')
66
- ->string($name)
67
- ->raw(' => $__'.$name.'__')
68
- ->raw(",\n")
69
- ;
70
- }
71
 
 
 
 
72
  $compiler
73
- ->outdent()
74
- ->write("));\n\n")
 
 
 
75
  ;
76
  }
77
 
78
  $compiler
 
 
79
  ->write("\$blocks = array();\n\n")
80
  ->write("ob_start();\n")
81
  ->write("try {\n")
16
  */
17
  class Twig_Node_Macro extends Twig_Node
18
  {
19
+ const VARARGS_NAME = 'varargs';
20
+
21
  public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null)
22
  {
23
+ foreach ($arguments as $argumentName => $argument) {
24
+ if (self::VARARGS_NAME === $argumentName) {
25
+ throw new Twig_Error_Syntax(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getLine());
26
+ }
27
+ }
28
+
29
  parent::__construct(array('body' => $body, 'arguments' => $arguments), array('name' => $name), $lineno, $tag);
30
  }
31
 
38
  {
39
  $compiler
40
  ->addDebugInfo($this)
41
+ ->write(sprintf('public function get%s(', $this->getAttribute('name')))
42
  ;
43
 
44
  $count = count($this->getNode('arguments'));
54
  }
55
  }
56
 
57
+ if (PHP_VERSION_ID >= 50600) {
58
+ if ($count) {
59
+ $compiler->raw(', ');
60
+ }
61
+
62
+ $compiler->raw('...$__varargs__');
63
+ }
64
+
65
  $compiler
66
  ->raw(")\n")
67
  ->write("{\n")
68
  ->indent()
69
  ;
70
 
71
+ $compiler
72
+ ->write("\$context = \$this->env->mergeGlobals(array(\n")
73
+ ->indent()
74
+ ;
75
+
76
+ foreach ($this->getNode('arguments') as $name => $default) {
77
  $compiler
78
+ ->addIndentation()
79
+ ->string($name)
80
+ ->raw(' => $__'.$name.'__')
81
+ ->raw(",\n")
82
  ;
83
+ }
84
 
85
+ $compiler
86
+ ->addIndentation()
87
+ ->string(self::VARARGS_NAME)
88
+ ->raw(' => ')
89
+ ;
 
 
 
90
 
91
+ if (PHP_VERSION_ID >= 50600) {
92
+ $compiler->raw("\$__varargs__,\n");
93
+ } else {
94
  $compiler
95
+ ->raw('func_num_args() > ')
96
+ ->repr($count)
97
+ ->raw(' ? array_slice(func_get_args(), ')
98
+ ->repr($count)
99
+ ->raw(") : array(),\n")
100
  ;
101
  }
102
 
103
  $compiler
104
+ ->outdent()
105
+ ->write("));\n\n")
106
  ->write("\$blocks = array();\n\n")
107
  ->write("ob_start();\n")
108
  ->write("try {\n")
vendor/twig/twig/lib/Twig/Node/Module.php CHANGED
@@ -107,20 +107,20 @@ class Twig_Node_Module extends Twig_Node
107
  ->write("protected function doGetParent(array \$context)\n", "{\n")
108
  ->indent()
109
  ->addDebugInfo($parent)
110
- ->write("return ")
111
  ;
112
 
113
  if ($parent instanceof Twig_Node_Expression_Constant) {
114
  $compiler->subcompile($parent);
115
  } else {
116
  $compiler
117
- ->raw("\$this->loadTemplate(")
118
  ->subcompile($parent)
119
  ->raw(', ')
120
  ->repr($compiler->getFilename())
121
  ->raw(', ')
122
  ->repr($this->getNode('parent')->getLine())
123
- ->raw(")")
124
  ;
125
  }
126
 
@@ -136,7 +136,7 @@ class Twig_Node_Module extends Twig_Node
136
  $compiler
137
  ->write("\n\n")
138
  // if the filename contains */, add a blank to avoid a PHP parse error
139
- ->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n")
140
  ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index')))
141
  ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
142
  ->write("{\n")
@@ -159,7 +159,7 @@ class Twig_Node_Module extends Twig_Node
159
  } elseif ($parent instanceof Twig_Node_Expression_Constant) {
160
  $compiler
161
  ->addDebugInfo($parent)
162
- ->write("\$this->parent = \$this->loadTemplate(")
163
  ->subcompile($parent)
164
  ->raw(', ')
165
  ->repr($compiler->getFilename())
@@ -189,23 +189,23 @@ class Twig_Node_Module extends Twig_Node
189
 
190
  foreach ($trait->getNode('targets') as $key => $value) {
191
  $compiler
192
- ->write(sprintf("if (!isset(\$_trait_%s_blocks[", $i))
193
  ->string($key)
194
  ->raw("])) {\n")
195
  ->indent()
196
  ->write("throw new Twig_Error_Runtime(sprintf('Block ")
197
  ->string($key)
198
- ->raw(" is not defined in trait ")
199
  ->subcompile($trait->getNode('template'))
200
  ->raw(".'));\n")
201
  ->outdent()
202
  ->write("}\n\n")
203
 
204
- ->write(sprintf("\$_trait_%s_blocks[", $i))
205
  ->subcompile($value)
206
- ->raw(sprintf("] = \$_trait_%s_blocks[", $i))
207
  ->string($key)
208
- ->raw(sprintf("]; unset(\$_trait_%s_blocks[", $i))
209
  ->string($key)
210
  ->raw("]);\n\n")
211
  ;
@@ -218,9 +218,9 @@ class Twig_Node_Module extends Twig_Node
218
  ->indent()
219
  ;
220
 
221
- for ($i = 0; $i < $countTraits; $i++) {
222
  $compiler
223
- ->write(sprintf("\$_trait_%s_blocks".($i == $countTraits - 1 ? '' : ',')."\n", $i))
224
  ;
225
  }
226
 
@@ -285,9 +285,9 @@ class Twig_Node_Module extends Twig_Node
285
  if (null !== $parent = $this->getNode('parent')) {
286
  $compiler->addDebugInfo($parent);
287
  if ($parent instanceof Twig_Node_Expression_Constant) {
288
- $compiler->write("\$this->parent");
289
  } else {
290
- $compiler->write("\$this->getParent(\$context)");
291
  }
292
  $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
293
  }
@@ -393,7 +393,7 @@ class Twig_Node_Module extends Twig_Node
393
  {
394
  if ($node instanceof Twig_Node_Expression_Constant) {
395
  $compiler
396
- ->write(sprintf("%s = \$this->loadTemplate(", $var))
397
  ->subcompile($node)
398
  ->raw(', ')
399
  ->repr($compiler->getFilename())
@@ -403,13 +403,13 @@ class Twig_Node_Module extends Twig_Node
403
  ;
404
  } else {
405
  $compiler
406
- ->write(sprintf("%s = ", $var))
407
  ->subcompile($node)
408
  ->raw(";\n")
409
- ->write(sprintf("if (!%s", $var))
410
  ->raw(" instanceof Twig_Template) {\n")
411
  ->indent()
412
- ->write(sprintf("%s = \$this->loadTemplate(%s")
413
  ->raw(', ')
414
  ->repr($compiler->getFilename())
415
  ->raw(', ')
107
  ->write("protected function doGetParent(array \$context)\n", "{\n")
108
  ->indent()
109
  ->addDebugInfo($parent)
110
+ ->write('return ')
111
  ;
112
 
113
  if ($parent instanceof Twig_Node_Expression_Constant) {
114
  $compiler->subcompile($parent);
115
  } else {
116
  $compiler
117
+ ->raw('$this->loadTemplate(')
118
  ->subcompile($parent)
119
  ->raw(', ')
120
  ->repr($compiler->getFilename())
121
  ->raw(', ')
122
  ->repr($this->getNode('parent')->getLine())
123
+ ->raw(')')
124
  ;
125
  }
126
 
136
  $compiler
137
  ->write("\n\n")
138
  // if the filename contains */, add a blank to avoid a PHP parse error
139
+ ->write('/* '.str_replace('*/', '* /', $this->getAttribute('filename'))." */\n")
140
  ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index')))
141
  ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
142
  ->write("{\n")
159
  } elseif ($parent instanceof Twig_Node_Expression_Constant) {
160
  $compiler
161
  ->addDebugInfo($parent)
162
+ ->write('$this->parent = $this->loadTemplate(')
163
  ->subcompile($parent)
164
  ->raw(', ')
165
  ->repr($compiler->getFilename())
189
 
190
  foreach ($trait->getNode('targets') as $key => $value) {
191
  $compiler
192
+ ->write(sprintf('if (!isset($_trait_%s_blocks[', $i))
193
  ->string($key)
194
  ->raw("])) {\n")
195
  ->indent()
196
  ->write("throw new Twig_Error_Runtime(sprintf('Block ")
197
  ->string($key)
198
+ ->raw(' is not defined in trait ')
199
  ->subcompile($trait->getNode('template'))
200
  ->raw(".'));\n")
201
  ->outdent()
202
  ->write("}\n\n")
203
 
204
+ ->write(sprintf('$_trait_%s_blocks[', $i))
205
  ->subcompile($value)
206
+ ->raw(sprintf('] = $_trait_%s_blocks[', $i))
207
  ->string($key)
208
+ ->raw(sprintf(']; unset($_trait_%s_blocks[', $i))
209
  ->string($key)
210
  ->raw("]);\n\n")
211
  ;
218
  ->indent()
219
  ;
220
 
221
+ for ($i = 0; $i < $countTraits; ++$i) {
222
  $compiler
223
+ ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i))
224
  ;
225
  }
226
 
285
  if (null !== $parent = $this->getNode('parent')) {
286
  $compiler->addDebugInfo($parent);
287
  if ($parent instanceof Twig_Node_Expression_Constant) {
288
+ $compiler->write('$this->parent');
289
  } else {
290
+ $compiler->write('$this->getParent($context)');
291
  }
292
  $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
293
  }
393
  {
394
  if ($node instanceof Twig_Node_Expression_Constant) {
395
  $compiler
396
+ ->write(sprintf('%s = $this->loadTemplate(', $var))
397
  ->subcompile($node)
398
  ->raw(', ')
399
  ->repr($compiler->getFilename())
403
  ;
404
  } else {
405
  $compiler
406
+ ->write(sprintf('%s = ', $var))
407
  ->subcompile($node)
408
  ->raw(";\n")
409
+ ->write(sprintf('if (!%s', $var))
410
  ->raw(" instanceof Twig_Template) {\n")
411
  ->indent()
412
+ ->write(sprintf('%s = $this->loadTemplate(%s')
413
  ->raw(', ')
414
  ->repr($compiler->getFilename())
415
  ->raw(', ')
vendor/twig/twig/lib/Twig/NodeVisitor/Escaper.php CHANGED
@@ -14,7 +14,7 @@
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
  */
17
- class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
18
  {
19
  protected $statusStack = array();
20
  protected $blocks = array();
@@ -29,14 +29,9 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
29
  }
30
 
31
  /**
32
- * Called before child nodes are visited.
33
- *
34
- * @param Twig_NodeInterface $node The node to visit
35
- * @param Twig_Environment $env The Twig environment instance
36
- *
37
- * @return Twig_NodeInterface The modified node
38
  */
39
- public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
40
  {
41
  if ($node instanceof Twig_Node_Module) {
42
  if ($env->hasExtension('escaper') && $defaultStrategy = $env->getExtension('escaper')->getDefaultStrategy($node->getAttribute('filename'))) {
@@ -55,14 +50,9 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
55
  }
56
 
57
  /**
58
- * Called after child nodes are visited.
59
- *
60
- * @param Twig_NodeInterface $node The node to visit
61
- * @param Twig_Environment $env The Twig environment instance
62
- *
63
- * @return Twig_NodeInterface The modified node
64
  */
65
- public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
66
  {
67
  if ($node instanceof Twig_Node_Module) {
68
  $this->defaultStrategy = false;
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
  */
17
+ class Twig_NodeVisitor_Escaper extends Twig_BaseNodeVisitor
18
  {
19
  protected $statusStack = array();
20
  protected $blocks = array();
29
  }
30
 
31
  /**
32
+ * {@inheritdoc}
 
 
 
 
 
33
  */
34
+ protected function doEnterNode(Twig_Node $node, Twig_Environment $env)
35
  {
36
  if ($node instanceof Twig_Node_Module) {
37
  if ($env->hasExtension('escaper') && $defaultStrategy = $env->getExtension('escaper')->getDefaultStrategy($node->getAttribute('filename'))) {
50
  }
51
 
52
  /**
53
+ * {@inheritdoc}
 
 
 
 
 
54
  */
55
+ protected function doLeaveNode(Twig_Node $node, Twig_Environment $env)
56
  {
57
  if ($node instanceof Twig_Node_Module) {
58
  $this->defaultStrategy = false;
vendor/twig/twig/lib/Twig/NodeVisitor/Optimizer.php CHANGED
@@ -19,13 +19,13 @@
19
  *
20
  * @author Fabien Potencier <fabien@symfony.com>
21
  */
22
- class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
23
  {
24
- const OPTIMIZE_ALL = -1;
25
- const OPTIMIZE_NONE = 0;
26
- const OPTIMIZE_FOR = 2;
27
- const OPTIMIZE_RAW_FILTER = 4;
28
- const OPTIMIZE_VAR_ACCESS = 8;
29
 
30
  protected $loops = array();
31
  protected $loopsTargets = array();
@@ -50,7 +50,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
50
  /**
51
  * {@inheritdoc}
52
  */
53
- public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
54
  {
55
  if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
56
  $this->enterOptimizeFor($node, $env);
@@ -76,7 +76,7 @@ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
76
  /**
77
  * {@inheritdoc}
78
  */
79
- public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
80
  {
81
  $expression = $node instanceof Twig_Node_Expression;
82
 
19
  *
20
  * @author Fabien Potencier <fabien@symfony.com>
21
  */
22
+ class Twig_NodeVisitor_Optimizer extends Twig_BaseNodeVisitor
23
  {
24
+ const OPTIMIZE_ALL = -1;
25
+ const OPTIMIZE_NONE = 0;
26
+ const OPTIMIZE_FOR = 2;
27
+ const OPTIMIZE_RAW_FILTER = 4;
28
+ const OPTIMIZE_VAR_ACCESS = 8;
29
 
30
  protected $loops = array();
31
  protected $loopsTargets = array();
50
  /**
51
  * {@inheritdoc}
52
  */
53
+ protected function doEnterNode(Twig_Node $node, Twig_Environment $env)
54
  {
55
  if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
56
  $this->enterOptimizeFor($node, $env);
76
  /**
77
  * {@inheritdoc}
78
  */
79
+ protected function doLeaveNode(Twig_Node $node, Twig_Environment $env)
80
  {
81
  $expression = $node instanceof Twig_Node_Expression;
82
 
vendor/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php CHANGED
@@ -1,6 +1,15 @@
1
  <?php
2
 
3
- class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface
 
 
 
 
 
 
 
 
 
4
  {
5
  protected $data = array();
6
  protected $safeVars = array();
@@ -48,12 +57,18 @@ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface
48
  );
49
  }
50
 
51
- public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
 
 
 
52
  {
53
  return $node;
54
  }
55
 
56
- public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
 
 
 
57
  {
58
  if ($node instanceof Twig_Node_Expression_Constant) {
59
  // constants are marked safe for all
1
  <?php
2
 
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_NodeVisitor_SafeAnalysis extends Twig_BaseNodeVisitor
13
  {
14
  protected $data = array();
15
  protected $safeVars = array();
57
  );
58
  }
59
 
60
+ /**
61
+ * {@inheritdoc}
62
+ */
63
+ protected function doEnterNode(Twig_Node $node, Twig_Environment $env)
64
  {
65
  return $node;
66
  }
67
 
68
+ /**
69
+ * {@inheritdoc}
70
+ */
71
+ protected function doLeaveNode(Twig_Node $node, Twig_Environment $env)
72
  {
73
  if ($node instanceof Twig_Node_Expression_Constant) {
74
  // constants are marked safe for all
vendor/twig/twig/lib/Twig/NodeVisitor/Sandbox.php CHANGED
@@ -14,7 +14,7 @@
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
  */
17
- class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
18
  {
19
  protected $inAModule = false;
20
  protected $tags;
@@ -22,14 +22,9 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
22
  protected $functions;
23
 
24
  /**
25
- * Called before child nodes are visited.
26
- *
27
- * @param Twig_NodeInterface $node The node to visit
28
- * @param Twig_Environment $env The Twig environment instance
29
- *
30
- * @return Twig_NodeInterface The modified node
31
  */
32
- public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
33
  {
34
  if ($node instanceof Twig_Node_Module) {
35
  $this->inAModule = true;
@@ -64,14 +59,9 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
64
  }
65
 
66
  /**
67
- * Called after child nodes are visited.
68
- *
69
- * @param Twig_NodeInterface $node The node to visit
70
- * @param Twig_Environment $env The Twig environment instance
71
- *
72
- * @return Twig_NodeInterface The modified node
73
  */
74
- public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
75
  {
76
  if ($node instanceof Twig_Node_Module) {
77
  $this->inAModule = false;
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
  */
17
+ class Twig_NodeVisitor_Sandbox extends Twig_BaseNodeVisitor
18
  {
19
  protected $inAModule = false;
20
  protected $tags;
22
  protected $functions;
23
 
24
  /**
25
+ * {@inheritdoc}
 
 
 
 
 
26
  */
27
+ protected function doEnterNode(Twig_Node $node, Twig_Environment $env)
28
  {
29
  if ($node instanceof Twig_Node_Module) {
30
  $this->inAModule = true;
59
  }
60
 
61
  /**
62
+ * {@inheritdoc}
 
 
 
 
 
63
  */
64
+ protected function doLeaveNode(Twig_Node $node, Twig_Environment $env)
65
  {
66
  if ($node instanceof Twig_Node_Module) {
67
  $this->inAModule = false;
vendor/twig/twig/lib/Twig/Parser.php CHANGED
@@ -253,20 +253,29 @@ class Twig_Parser implements Twig_ParserInterface
253
  }
254
 
255
  public function setMacro($name, Twig_Node_Macro $node)
 
 
 
 
 
 
 
 
 
256
  {
257
  if (null === $this->reservedMacroNames) {
258
  $this->reservedMacroNames = array();
259
  $r = new ReflectionClass($this->env->getBaseTemplateClass());
260
  foreach ($r->getMethods() as $method) {
261
- $this->reservedMacroNames[] = $method->getName();
262
- }
263
- }
264
 
265
- if (in_array($name, $this->reservedMacroNames)) {
266
- throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename());
 
 
267
  }
268
 
269
- $this->macros[$name] = $node;
270
  }
271
 
272
  public function addTrait($trait)
253
  }
254
 
255
  public function setMacro($name, Twig_Node_Macro $node)
256
+ {
257
+ if ($this->isReservedMacroName($name)) {
258
+ throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename());
259
+ }
260
+
261
+ $this->macros[$name] = $node;
262
+ }
263
+
264
+ public function isReservedMacroName($name)
265
  {
266
  if (null === $this->reservedMacroNames) {
267
  $this->reservedMacroNames = array();
268
  $r = new ReflectionClass($this->env->getBaseTemplateClass());
269
  foreach ($r->getMethods() as $method) {
270
+ $methodName = strtolower($method->getName());
 
 
271
 
272
+ if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) {
273
+ $this->reservedMacroNames[] = substr($methodName, 3);
274
+ }
275
+ }
276
  }
277
 
278
+ return in_array(strtolower($name), $this->reservedMacroNames);
279
  }
280
 
281
  public function addTrait($trait)
vendor/twig/twig/lib/Twig/Profiler/Dumper/Html.php CHANGED
@@ -14,7 +14,7 @@
14
  */
15
  class Twig_Profiler_Dumper_Html extends Twig_Profiler_Dumper_Text
16
  {
17
- static private $colors = array(
18
  'block' => '#dfd',
19
  'macro' => '#ddf',
20
  'template' => '#ffd',
14
  */
15
  class Twig_Profiler_Dumper_Html extends Twig_Profiler_Dumper_Text
16
  {
17
+ private static $colors = array(
18
  'block' => '#dfd',
19
  'macro' => '#ddf',
20
  'template' => '#ffd',
vendor/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php CHANGED
@@ -27,12 +27,12 @@ class Twig_Profiler_Node_EnterProfile extends Twig_Node
27
  public function compile(Twig_Compiler $compiler)
28
  {
29
  $compiler
30
- ->write(sprintf("\$%s = \$this->env->getExtension(", $this->getAttribute('var_name')))
31
  ->repr($this->getAttribute('extension_name'))
32
  ->raw(");\n")
33
- ->write(sprintf("\$%s->enter(\$%s = new Twig_Profiler_Profile(\$this->getTemplateName(), ", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof'))
34
  ->repr($this->getAttribute('type'))
35
- ->raw(", ")
36
  ->repr($this->getAttribute('name'))
37
  ->raw("));\n\n")
38
  ;
27
  public function compile(Twig_Compiler $compiler)
28
  {
29
  $compiler
30
+ ->write(sprintf('$%s = $this->env->getExtension(', $this->getAttribute('var_name')))
31
  ->repr($this->getAttribute('extension_name'))
32
  ->raw(");\n")
33
+ ->write(sprintf('$%s->enter($%s = new Twig_Profiler_Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof'))
34
  ->repr($this->getAttribute('type'))
35
+ ->raw(', ')
36
  ->repr($this->getAttribute('name'))
37
  ->raw("));\n\n")
38
  ;
vendor/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php CHANGED
@@ -12,7 +12,7 @@
12
  /**
13
  * @author Fabien Potencier <fabien@symfony.com>
14
  */
15
- class Twig_Profiler_NodeVisitor_Profiler implements Twig_NodeVisitorInterface
16
  {
17
  private $extensionName;
18
 
@@ -24,7 +24,7 @@ class Twig_Profiler_NodeVisitor_Profiler implements Twig_NodeVisitorInterface
24
  /**
25
  * {@inheritdoc}
26
  */
27
- public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
28
  {
29
  return $node;
30
  }
@@ -32,7 +32,7 @@ class Twig_Profiler_NodeVisitor_Profiler implements Twig_NodeVisitorInterface
32
  /**
33
  * {@inheritdoc}
34
  */
35
- public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
36
  {
37
  if ($node instanceof Twig_Node_Module) {
38
  $varName = $this->getVarName();
12
  /**
13
  * @author Fabien Potencier <fabien@symfony.com>
14
  */
15
+ class Twig_Profiler_NodeVisitor_Profiler extends Twig_BaseNodeVisitor
16
  {
17
  private $extensionName;
18
 
24
  /**
25
  * {@inheritdoc}
26
  */
27
+ protected function doEnterNode(Twig_Node $node, Twig_Environment $env)
28
  {
29
  return $node;
30
  }
32
  /**
33
  * {@inheritdoc}
34
  */
35
+ protected function doLeaveNode(Twig_Node $node, Twig_Environment $env)
36
  {
37
  if ($node instanceof Twig_Node_Module) {
38
  $varName = $this->getVarName();
vendor/twig/twig/lib/Twig/Profiler/Profile.php CHANGED
@@ -26,7 +26,7 @@ class Twig_Profiler_Profile implements IteratorAggregate, Serializable
26
  private $ends = array();
27
  private $profiles = array();
28
 
29
- public function __construct($template = 'main', $type = Twig_Profiler_Profile::ROOT, $name = 'main')
30
  {
31
  $this->template = $template;
32
  $this->type = $type;
26
  private $ends = array();
27
  private $profiles = array();
28
 
29
+ public function __construct($template = 'main', $type = self::ROOT, $name = 'main')
30
  {
31
  $this->template = $template;
32
  $this->type = $type;
vendor/twig/twig/lib/Twig/SimpleFilter.php CHANGED
@@ -27,12 +27,13 @@ class Twig_SimpleFilter
27
  $this->callable = $callable;
28
  $this->options = array_merge(array(
29
  'needs_environment' => false,
30
- 'needs_context' => false,
31
- 'is_safe' => null,
32
- 'is_safe_callback' => null,
33
- 'pre_escape' => null,
34
- 'preserves_safety' => null,
35
- 'node_class' => 'Twig_Node_Expression_Filter',
 
36
  ), $options);
37
  }
38
 
@@ -91,4 +92,9 @@ class Twig_SimpleFilter
91
  {
92
  return $this->options['pre_escape'];
93
  }
 
 
 
 
 
94
  }
27
  $this->callable = $callable;
28
  $this->options = array_merge(array(
29
  'needs_environment' => false,
30
+ 'needs_context' => false,
31
+ 'is_variadic' => false,
32
+ 'is_safe' => null,
33
+ 'is_safe_callback' => null,
34
+ 'pre_escape' => null,
35
+ 'preserves_safety' => null,
36
+ 'node_class' => 'Twig_Node_Expression_Filter',
37
  ), $options);
38
  }
39
 
92
  {
93
  return $this->options['pre_escape'];
94
  }
95
+
96
+ public function isVariadic()
97
+ {
98
+ return $this->options['is_variadic'];
99
+ }
100
  }
vendor/twig/twig/lib/Twig/SimpleFunction.php CHANGED
@@ -27,10 +27,11 @@ class Twig_SimpleFunction
27
  $this->callable = $callable;
28
  $this->options = array_merge(array(
29
  'needs_environment' => false,
30
- 'needs_context' => false,
31
- 'is_safe' => null,
32
- 'is_safe_callback' => null,
33
- 'node_class' => 'Twig_Node_Expression_Function',
 
34
  ), $options);
35
  }
36
 
@@ -81,4 +82,9 @@ class Twig_SimpleFunction
81
 
82
  return array();
83
  }
 
 
 
 
 
84
  }
27
  $this->callable = $callable;
28
  $this->options = array_merge(array(
29
  'needs_environment' => false,
30
+ 'needs_context' => false,
31
+ 'is_variadic' => false,
32
+ 'is_safe' => null,
33
+ 'is_safe_callback' => null,
34
+ 'node_class' => 'Twig_Node_Expression_Function',
35
  ), $options);
36
  }
37
 
82
 
83
  return array();
84
  }
85
+
86
+ public function isVariadic()
87
+ {
88
+ return $this->options['is_variadic'];
89
+ }
90
  }
vendor/twig/twig/lib/Twig/SimpleTest.php CHANGED
@@ -25,6 +25,7 @@ class Twig_SimpleTest
25
  $this->name = $name;
26
  $this->callable = $callable;
27
  $this->options = array_merge(array(
 
28
  'node_class' => 'Twig_Node_Expression_Test',
29
  ), $options);
30
  }
@@ -43,4 +44,9 @@ class Twig_SimpleTest
43
  {
44
  return $this->options['node_class'];
45
  }
 
 
 
 
 
46
  }
25
  $this->name = $name;
26
  $this->callable = $callable;
27
  $this->options = array_merge(array(
28
+ 'is_variadic' => false,
29
  'node_class' => 'Twig_Node_Expression_Test',
30
  ), $options);
31
  }
44
  {
45
  return $this->options['node_class'];
46
  }
47
+
48
+ public function isVariadic()
49
+ {
50
+ return $this->options['is_variadic'];
51
+ }
52
  }
vendor/twig/twig/lib/Twig/Template.php CHANGED
@@ -45,10 +45,12 @@ abstract class Twig_Template implements Twig_TemplateInterface
45
  abstract public function getTemplateName();
46
 
47
  /**
48
- * {@inheritdoc}
49
  */
50
  public function getEnvironment()
51
  {
 
 
52
  return $this->env;
53
  }
54
 
@@ -152,9 +154,25 @@ abstract class Twig_Template implements Twig_TemplateInterface
152
  }
153
 
154
  if (null !== $template) {
 
 
 
 
 
155
  try {
156
  $template->$block($context, $blocks);
157
  } catch (Twig_Error $e) {
 
 
 
 
 
 
 
 
 
 
 
158
  throw $e;
159
  } catch (Exception $e) {
160
  throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getTemplateName(), $e);
@@ -462,7 +480,7 @@ abstract class Twig_Template implements Twig_TemplateInterface
462
  }
463
 
464
  // object property
465
- if (self::METHOD_CALL !== $type) {
466
  if (isset($object->$item) || array_key_exists((string) $item, $object)) {
467
  if ($isDefinedTest) {
468
  return true;
@@ -480,7 +498,24 @@ abstract class Twig_Template implements Twig_TemplateInterface
480
 
481
  // object method
482
  if (!isset(self::$cache[$class]['methods'])) {
483
- self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  }
485
 
486
  $call = false;
45
  abstract public function getTemplateName();
46
 
47
  /**
48
+ * @deprecated since 1.20 (to be removed in 2.0)
49
  */
50
  public function getEnvironment()
51
  {
52
+ @trigger_error('The '.__METHOD__.' method is deprecated since version 1.20 and will be removed in 2.0.', E_USER_DEPRECATED);
53
+
54
  return $this->env;
55
  }
56
 
154
  }
155
 
156
  if (null !== $template) {
157
+ // avoid RCEs when sandbox is enabled
158
+ if (!$template instanceof Twig_Template) {
159
+ throw new \LogicException('A block must be a method on a Twig_Template instance.');
160
+ }
161
+
162
  try {
163
  $template->$block($context, $blocks);
164
  } catch (Twig_Error $e) {
165
+ if (!$e->getTemplateFile()) {
166
+ $e->setTemplateFile($template->getTemplateName());
167
+ }
168
+
169
+ // this is mostly useful for Twig_Error_Loader exceptions
170
+ // see Twig_Error_Loader
171
+ if (false === $e->getTemplateLine()) {
172
+ $e->setTemplateLine(-1);
173
+ $e->guess();
174
+ }
175
+
176
  throw $e;
177
  } catch (Exception $e) {
178
  throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getTemplateName(), $e);
480
  }
481
 
482
  // object property
483
+ if (self::METHOD_CALL !== $type && !$object instanceof self) { // Twig_Template does not have public properties, and we don't want to allow access to internal ones
484
  if (isset($object->$item) || array_key_exists((string) $item, $object)) {
485
  if ($isDefinedTest) {
486
  return true;
498
 
499
  // object method
500
  if (!isset(self::$cache[$class]['methods'])) {
501
+ // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates
502
+ if ($object instanceof self) {
503
+ $ref = new ReflectionClass($class);
504
+ $methods = array();
505
+
506
+ foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $refMethod) {
507
+ $methodName = strtolower($refMethod->name);
508
+
509
+ // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment
510
+ if ('getenvironment' !== $methodName) {
511
+ $methods[$methodName] = true;
512
+ }
513
+ }
514
+
515
+ self::$cache[$class]['methods'] = $methods;
516
+ } else {
517
+ self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
518
+ }
519
  }
520
 
521
  $call = false;
vendor/twig/twig/lib/Twig/TemplateInterface.php CHANGED
@@ -18,8 +18,8 @@
18
  */
19
  interface Twig_TemplateInterface
20
  {
21
- const ANY_CALL = 'any';
22
- const ARRAY_CALL = 'array';
23
  const METHOD_CALL = 'method';
24
 
25
  /**
18
  */
19
  interface Twig_TemplateInterface
20
  {
21
+ const ANY_CALL = 'any';
22
+ const ARRAY_CALL = 'array';
23
  const METHOD_CALL = 'method';
24
 
25
  /**
vendor/twig/twig/lib/Twig/Test.php CHANGED
@@ -13,6 +13,7 @@
13
  * Represents a template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
 
16
  * @deprecated since 1.12 (to be removed in 2.0)
17
  */
18
  abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface
13
  * Represents a template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
+ *
17
  * @deprecated since 1.12 (to be removed in 2.0)
18
  */
19
  abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface
vendor/twig/twig/lib/Twig/Test/Function.php CHANGED
@@ -13,6 +13,7 @@
13
  * Represents a function template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
 
16
  * @deprecated since 1.12 (to be removed in 2.0)
17
  */
18
  class Twig_Test_Function extends Twig_Test
13
  * Represents a function template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
+ *
17
  * @deprecated since 1.12 (to be removed in 2.0)
18
  */
19
  class Twig_Test_Function extends Twig_Test
vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php CHANGED
@@ -10,7 +10,7 @@
10
  */
11
 
12
  /**
13
- * Integration test helper
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
  * @author Karma Dordrak <drak@zikula.org>
@@ -74,7 +74,7 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase
74
 
75
  $loader = new Twig_Loader_Array($templates);
76
 
77
- foreach ($outputs as $match) {
78
  $config = array_merge(array(
79
  'cache' => false,
80
  'strict_variables' => true,
@@ -85,6 +85,14 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase
85
  $twig->addExtension($extension);
86
  }
87
 
 
 
 
 
 
 
 
 
88
  try {
89
  $template = $twig->loadTemplate('index.twig');
90
  } catch (Exception $e) {
@@ -122,14 +130,14 @@ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase
122
  }
123
 
124
  if (false !== $exception) {
125
- list($class, ) = explode(':', $exception);
126
  $this->assertThat(null, new PHPUnit_Framework_Constraint_Exception($class));
127
  }
128
 
129
  $expected = trim($match[3], "\n ");
130
 
131
  if ($expected != $output) {
132
- echo 'Compiled template that failed:';
133
 
134
  foreach (array_keys($templates) as $name) {
135
  echo "Template: $name\n";
10
  */
11
 
12
  /**
13
+ * Integration test helper.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
  * @author Karma Dordrak <drak@zikula.org>
74
 
75
  $loader = new Twig_Loader_Array($templates);
76
 
77
+ foreach ($outputs as $i => $match) {
78
  $config = array_merge(array(
79
  'cache' => false,
80
  'strict_variables' => true,
85
  $twig->addExtension($extension);
86
  }
87
 
88
+ // avoid using the same PHP class name for different cases
89
+ // only for PHP 5.2+
90
+ if (PHP_VERSION_ID >= 50300) {
91
+ $p = new ReflectionProperty($twig, 'templateClassPrefix');
92
+ $p->setAccessible(true);
93
+ $p->setValue($twig, '__TwigTemplate_'.hash('sha256', uniqid(mt_rand(), true), false).'_');
94
+ }
95
+
96
  try {
97
  $template = $twig->loadTemplate('index.twig');
98
  } catch (Exception $e) {
130
  }
131
 
132
  if (false !== $exception) {
133
+ list($class) = explode(':', $exception);
134
  $this->assertThat(null, new PHPUnit_Framework_Constraint_Exception($class));
135
  }
136
 
137
  $expected = trim($match[3], "\n ");
138
 
139
  if ($expected != $output) {
140
+ printf("Compiled templates that failed on case %d:\n", $i + 1);
141
 
142
  foreach (array_keys($templates) as $name) {
143
  echo "Template: $name\n";
vendor/twig/twig/lib/Twig/Test/Method.php CHANGED
@@ -13,6 +13,7 @@
13
  * Represents a method template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
 
16
  * @deprecated since 1.12 (to be removed in 2.0)
17
  */
18
  class Twig_Test_Method extends Twig_Test
13
  * Represents a method template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
+ *
17
  * @deprecated since 1.12 (to be removed in 2.0)
18
  */
19
  class Twig_Test_Method extends Twig_Test
vendor/twig/twig/lib/Twig/Test/Node.php CHANGED
@@ -13,6 +13,7 @@
13
  * Represents a template test as a Node.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
 
16
  * @deprecated since 1.12 (to be removed in 2.0)
17
  */
18
  class Twig_Test_Node extends Twig_Test
13
  * Represents a template test as a Node.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
+ *
17
  * @deprecated since 1.12 (to be removed in 2.0)
18
  */
19
  class Twig_Test_Node extends Twig_Test
vendor/twig/twig/lib/Twig/TestCallableInterface.php CHANGED
@@ -13,6 +13,7 @@
13
  * Represents a callable template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
 
16
  * @deprecated since 1.12 (to be removed in 2.0)
17
  */
18
  interface Twig_TestCallableInterface
13
  * Represents a callable template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
+ *
17
  * @deprecated since 1.12 (to be removed in 2.0)
18
  */
19
  interface Twig_TestCallableInterface
vendor/twig/twig/lib/Twig/TestInterface.php CHANGED
@@ -13,6 +13,7 @@
13
  * Represents a template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
 
16
  * @deprecated since 1.12 (to be removed in 2.0)
17
  */
18
  interface Twig_TestInterface
13
  * Represents a template test.
14
  *
15
  * @author Fabien Potencier <fabien@symfony.com>
16
+ *
17
  * @deprecated since 1.12 (to be removed in 2.0)
18
  */
19
  interface Twig_TestInterface
vendor/twig/twig/lib/Twig/Token.php CHANGED
@@ -21,19 +21,19 @@ class Twig_Token
21
  protected $type;
22
  protected $lineno;
23
 
24
- const EOF_TYPE = -1;
25
- const TEXT_TYPE = 0;
26
- const BLOCK_START_TYPE = 1;
27
- const VAR_START_TYPE = 2;
28
- const BLOCK_END_TYPE = 3;
29
- const VAR_END_TYPE = 4;
30
- const NAME_TYPE = 5;
31
- const NUMBER_TYPE = 6;
32
- const STRING_TYPE = 7;
33
- const OPERATOR_TYPE = 8;
34
- const PUNCTUATION_TYPE = 9;
35
- const INTERPOLATION_START_TYPE = 10;
36
- const INTERPOLATION_END_TYPE = 11;
37
 
38
  /**
39
  * Constructor.
@@ -44,8 +44,8 @@ class Twig_Token
44
  */
45
  public function __construct($type, $value, $lineno)
46
  {
47
- $this->type = $type;
48
- $this->value = $value;
49
  $this->lineno = $lineno;
50
  }
51
 
21
  protected $type;
22
  protected $lineno;
23
 
24
+ const EOF_TYPE = -1;
25
+ const TEXT_TYPE = 0;
26
+ const BLOCK_START_TYPE = 1;
27
+ const VAR_START_TYPE = 2;
28
+ const BLOCK_END_TYPE = 3;
29
+ const VAR_END_TYPE = 4;
30
+ const NAME_TYPE = 5;
31
+ const NUMBER_TYPE = 6;
32
+ const STRING_TYPE = 7;
33
+ const OPERATOR_TYPE = 8;
34
+ const PUNCTUATION_TYPE = 9;
35
+ const INTERPOLATION_START_TYPE = 10;
36
+ const INTERPOLATION_END_TYPE = 11;
37
 
38
  /**
39
  * Constructor.
44
  */
45
  public function __construct($type, $value, $lineno)
46
  {
47
+ $this->type = $type;
48
+ $this->value = $value;
49
  $this->lineno = $lineno;
50
  }
51
 
vendor/twig/twig/lib/Twig/TokenParser.php CHANGED
@@ -22,7 +22,7 @@ abstract class Twig_TokenParser implements Twig_TokenParserInterface
22
  protected $parser;
23
 
24
  /**
25
- * Sets the parser associated with this token parser
26
  *
27
  * @param Twig_Parser $parser A Twig_Parser instance
28
  */
22
  protected $parser;
23
 
24
  /**
25
+ * Sets the parser associated with this token parser.
26
  *
27
  * @param Twig_Parser $parser A Twig_Parser instance
28
  */
vendor/twig/twig/lib/Twig/TokenParser/From.php CHANGED
@@ -52,6 +52,10 @@ class Twig_TokenParser_From extends Twig_TokenParser
52
  $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag());
53
 
54
  foreach ($targets as $name => $alias) {
 
 
 
 
55
  $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var'));
56
  }
57
 
52
  $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag());
53
 
54
  foreach ($targets as $name => $alias) {
55
+ if ($this->parser->isReservedMacroName($name)) {
56
+ throw new Twig_Error_Syntax(sprintf('"%s" cannot be an imported macro as it is a reserved keyword', $name), $token->getLine(), $stream->getFilename());
57
+ }
58
+
59
  $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var'));
60
  }
61
 
vendor/twig/twig/lib/Twig/TokenParserBroker.php CHANGED
@@ -14,6 +14,7 @@
14
  * Default implementation of a token parser broker.
15
  *
16
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
 
17
  * @deprecated since 1.12 (to be removed in 2.0)
18
  */
19
  class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface
14
  * Default implementation of a token parser broker.
15
  *
16
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
17
+ *
18
  * @deprecated since 1.12 (to be removed in 2.0)
19
  */
20
  class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface
vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php CHANGED
@@ -16,6 +16,7 @@
16
  * Token parser brokers allows to implement custom logic in the process of resolving a token parser for a given tag name.
17
  *
18
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
 
19
  * @deprecated since 1.12 (to be removed in 2.0)
20
  */
21
  interface Twig_TokenParserBrokerInterface
16
  * Token parser brokers allows to implement custom logic in the process of resolving a token parser for a given tag name.
17
  *
18
  * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
19
+ *
20
  * @deprecated since 1.12 (to be removed in 2.0)
21
  */
22
  interface Twig_TokenParserBrokerInterface
vendor/twig/twig/lib/Twig/TokenParserInterface.php CHANGED
@@ -17,7 +17,7 @@
17
  interface Twig_TokenParserInterface
18
  {
19
  /**
20
- * Sets the parser associated with this token parser
21
  *
22
  * @param Twig_Parser $parser A Twig_Parser instance
23
  */
17
  interface Twig_TokenParserInterface
18
  {
19
  /**
20
+ * Sets the parser associated with this token parser.
21
  *
22
  * @param Twig_Parser $parser A Twig_Parser instance
23
  */
vendor/twig/twig/lib/Twig/TokenStream.php CHANGED
@@ -29,9 +29,9 @@ class Twig_TokenStream
29
  */
30
  public function __construct(array $tokens, $filename = null)
31
  {
32
- $this->tokens = $tokens;
33
- $this->current = 0;
34
- $this->filename = $filename;
35
  }
36
 
37
  /**
@@ -115,7 +115,7 @@ class Twig_TokenStream
115
  }
116
 
117
  /**
118
- * Tests the current token
119
  *
120
  * @return bool
121
  */
@@ -125,7 +125,7 @@ class Twig_TokenStream
125
  }
126
 
127
  /**
128
- * Checks if end of stream was reached
129
  *
130
  * @return bool
131
  */
@@ -135,7 +135,7 @@ class Twig_TokenStream
135
  }
136
 
137
  /**
138
- * Gets the current token
139
  *
140
  * @return Twig_Token
141
  */
@@ -145,7 +145,7 @@ class Twig_TokenStream
145
  }
146
 
147
  /**
148
- * Gets the filename associated with this stream
149
  *
150
  * @return string
151
  */
29
  */
30
  public function __construct(array $tokens, $filename = null)
31
  {
32
+ $this->tokens = $tokens;
33
+ $this->current = 0;
34
+ $this->filename = $filename;
35
  }
36
 
37
  /**
115
  }
116
 
117
  /**
118
+ * Tests the current token.
119
  *
120
  * @return bool
121
  */
125
  }
126
 
127
  /**
128
+ * Checks if end of stream was reached.
129
  *
130
  * @return bool
131
  */
135
  }
136
 
137
  /**
138
+ * Gets the current token.
139
  *
140
  * @return Twig_Token
141
  */
145
  }
146
 
147
  /**
148
+ * Gets the filename associated with this stream.
149
  *
150
  * @return string
151
  */
vendor/twig/twig/test/Twig/Tests/CompilerTest.php CHANGED
@@ -22,7 +22,7 @@ class Twig_Tests_CompilerTest extends PHPUnit_Framework_TestCase
22
 
23
  $required_locales = array('fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252');
24
  if (false === setlocale(LC_NUMERIC, $required_locales)) {
25
- $this->markTestSkipped('Could not set any of required locales: '.implode(", ", $required_locales));
26
  }
27
 
28
  $this->assertEquals('1.2', $compiler->repr(1.2)->getSource());
22
 
23
  $required_locales = array('fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252');
24
  if (false === setlocale(LC_NUMERIC, $required_locales)) {
25
+ $this->markTestSkipped('Could not set any of required locales: '.implode(', ', $required_locales));
26
  }
27
 
28
  $this->assertEquals('1.2', $compiler->repr(1.2)->getSource());
vendor/twig/twig/test/Twig/Tests/EnvironmentTest.php CHANGED
@@ -25,12 +25,12 @@ class Twig_Tests_EnvironmentTest extends PHPUnit_Framework_TestCase
25
  {
26
  $loader = new Twig_Loader_Array(array(
27
  'html' => '{{ foo }} {{ foo }}',
28
- 'js' => '{{ bar }} {{ bar }}',
29
  ));
30
 
31
  $twig = new Twig_Environment($loader, array(
32
- 'debug' => true,
33
- 'cache' => false,
34
  'autoescape' => array($this, 'escapingStrategyCallback'),
35
  ));
36
 
25
  {
26
  $loader = new Twig_Loader_Array(array(
27
  'html' => '{{ foo }} {{ foo }}',
28
+ 'js' => '{{ bar }} {{ bar }}',
29
  ));
30
 
31
  $twig = new Twig_Environment($loader, array(
32
+ 'debug' => true,
33
+ 'cache' => false,
34
  'autoescape' => array($this, 'escapingStrategyCallback'),
35
  ));
36
 
vendor/twig/twig/test/Twig/Tests/ErrorTest.php CHANGED
@@ -99,7 +99,7 @@ class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase
99
  // error occurs in an included template
100
  array(
101
  array(
102
- 'index' => "{% include 'partial' %}",
103
  'partial' => '{{ foo.bar }}',
104
  ),
105
  'partial', 1,
99
  // error occurs in an included template
100
  array(
101
  array(
102
+ 'index' => "{% include 'partial' %}",
103
  'partial' => '{{ foo.bar }}',
104
  ),
105
  'partial', 1,
vendor/twig/twig/test/Twig/Tests/Extension/CoreTest.php CHANGED
@@ -18,7 +18,7 @@ class Twig_Tests_Extension_CoreTest extends PHPUnit_Framework_TestCase
18
  {
19
  $env = new Twig_Environment();
20
 
21
- for ($i = 0; $i < 100; $i++) {
22
  $this->assertTrue(in_array(twig_random($env, $value), $expectedInArray, true)); // assertContains() would not consider the type
23
  }
24
  }
@@ -26,31 +26,31 @@ class Twig_Tests_Extension_CoreTest extends PHPUnit_Framework_TestCase
26
  public function getRandomFunctionTestData()
27
  {
28
  return array(
29
- array( // array
30
  array('apple', 'orange', 'citrus'),
31
  array('apple', 'orange', 'citrus'),
32
  ),
33
- array( // Traversable
34
  new ArrayObject(array('apple', 'orange', 'citrus')),
35
  array('apple', 'orange', 'citrus'),
36
  ),
37
- array( // unicode string
38
  'Ä€é',
39
  array('Ä', '€', 'é'),
40
  ),
41
- array( // numeric but string
42
  '123',
43
  array('1', '2', '3'),
44
  ),
45
- array( // integer
46
  5,
47
  range(0, 5, 1),
48
  ),
49
- array( // float
50
  5.9,
51
  range(0, 5, 1),
52
  ),
53
- array( // negative
54
  -2,
55
  array(0, -1, -2),
56
  ),
@@ -61,7 +61,7 @@ class Twig_Tests_Extension_CoreTest extends PHPUnit_Framework_TestCase
61
  {
62
  $max = mt_getrandmax();
63
 
64
- for ($i = 0; $i < 100; $i++) {
65
  $val = twig_random(new Twig_Environment());
66
  $this->assertTrue(is_int($val) && $val >= 0 && $val <= $max);
67
  }
@@ -94,7 +94,7 @@ class Twig_Tests_Extension_CoreTest extends PHPUnit_Framework_TestCase
94
  $twig->setCharset('ISO-8859-1');
95
 
96
  $text = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8');
97
- for ($i = 0; $i < 30; $i++) {
98
  $rand = twig_random($twig, $text);
99
  $this->assertTrue(in_array(twig_convert_encoding($rand, 'UTF-8', 'ISO-8859-1'), array('Ä', 'é'), true));
100
  }
18
  {
19
  $env = new Twig_Environment();
20
 
21
+ for ($i = 0; $i < 100; ++$i) {
22
  $this->assertTrue(in_array(twig_random($env, $value), $expectedInArray, true)); // assertContains() would not consider the type
23
  }
24
  }
26
  public function getRandomFunctionTestData()
27
  {
28
  return array(
29
+ array(// array
30
  array('apple', 'orange', 'citrus'),
31
  array('apple', 'orange', 'citrus'),
32
  ),
33
+ array(// Traversable
34
  new ArrayObject(array('apple', 'orange', 'citrus')),
35
  array('apple', 'orange', 'citrus'),
36
  ),
37
+ array(// unicode string
38
  'Ä€é',
39
  array('Ä', '€', 'é'),
40
  ),
41
+ array(// numeric but string
42
  '123',
43
  array('1', '2', '3'),
44
  ),
45
+ array(// integer
46
  5,
47
  range(0, 5, 1),
48
  ),
49
+ array(// float
50
  5.9,
51
  range(0, 5, 1),
52
  ),
53
+ array(// negative
54
  -2,
55
  array(0, -1, -2),
56
  ),
61
  {
62
  $max = mt_getrandmax();
63
 
64
+ for ($i = 0; $i < 100; ++$i) {
65
  $val = twig_random(new Twig_Environment());
66
  $this->assertTrue(is_int($val) && $val >= 0 && $val <= $max);
67
  }
94
  $twig->setCharset('ISO-8859-1');
95
 
96
  $text = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8');
97
+ for ($i = 0; $i < 30; ++$i) {
98
  $rand = twig_random($twig, $text);
99
  $this->assertTrue(in_array(twig_convert_encoding($rand, 'UTF-8', 'ISO-8859-1'), array('Ä', 'é'), true));
100
  }
vendor/twig/twig/test/Twig/Tests/Extension/SandboxTest.php CHANGED
@@ -17,8 +17,8 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
17
  {
18
  self::$params = array(
19
  'name' => 'Fabien',
20
- 'obj' => new FooObject(),
21
- 'arr' => array('obj' => new FooObject()),
22
  );
23
 
24
  self::$templates = array(
@@ -31,9 +31,9 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
31
  '1_basic7' => '{{ cycle(["foo","bar"], 1) }}',
32
  '1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}',
33
  '1_basic9' => '{{ obj.foobar }}{{ obj.fooBar }}',
34
- '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
35
  '1_layout' => '{% block content %}{% endblock %}',
36
- '1_child' => "{% extends \"1_layout\" %}\n{% block content %}\n{{ \"a\"|json_encode }}\n{% endblock %}",
37
  );
38
  }
39
 
@@ -141,7 +141,7 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
141
  public function testSandboxLocallySetForAnInclude()
142
  {
143
  self::$templates = array(
144
- '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}',
145
  '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
146
  );
147
 
@@ -149,7 +149,7 @@ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
149
  $this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
150
 
151
  self::$templates = array(
152
- '3_basic' => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}',
153
  '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
154
  );
155
 
17
  {
18
  self::$params = array(
19
  'name' => 'Fabien',
20
+ 'obj' => new FooObject(),
21
+ 'arr' => array('obj' => new FooObject()),
22
  );
23
 
24
  self::$templates = array(
31
  '1_basic7' => '{{ cycle(["foo","bar"], 1) }}',
32
  '1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}',
33
  '1_basic9' => '{{ obj.foobar }}{{ obj.fooBar }}',
34
+ '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
35
  '1_layout' => '{% block content %}{% endblock %}',
36
+ '1_child' => "{% extends \"1_layout\" %}\n{% block content %}\n{{ \"a\"|json_encode }}\n{% endblock %}",
37
  );
38
  }
39
 
141
  public function testSandboxLocallySetForAnInclude()
142
  {
143
  self::$templates = array(
144
+ '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}',
145
  '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
146
  );
147
 
149
  $this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
150
 
151
  self::$templates = array(
152
+ '3_basic' => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}',
153
  '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
154
  );
155
 
vendor/twig/twig/test/Twig/Tests/Fixtures/exceptions/undefined_template_in_child_template.test ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Exception for an undefined template in a child template
3
+ --TEMPLATE--
4
+ {% extends 'base.twig' %}
5
+
6
+ {% block sidebar %}
7
+ {{ include('include.twig') }}
8
+ {% endblock %}
9
+ --TEMPLATE(base.twig)--
10
+ {% block sidebar %}
11
+ {% endblock %}
12
+ --DATA--
13
+ return array()
14
+ --EXCEPTION--
15
+ Twig_Error_Loader: Template "include.twig" is not defined in "index.twig" at line 5.
vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_keys.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "batch" filter preserves array keys
3
+ --TEMPLATE--
4
+ {{ {'foo': 'bar', 'key': 'value'}|batch(4)|first|keys|join(',') }}
5
+ {{ {'foo': 'bar', 'key': 'value'}|batch(4, 'fill')|first|keys|join(',') }}
6
+ --DATA--
7
+ return array()
8
+ --EXPECT--
9
+ foo,key
10
+ foo,key,0,1
vendor/twig/twig/test/Twig/Tests/Fixtures/filters/batch_with_zero_elements.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "batch" filter with zero elements
3
+ --TEMPLATE--
4
+ {{ []|batch(3)|length }}
5
+ {{ []|batch(3, 'fill')|length }}
6
+ --DATA--
7
+ return array()
8
+ --EXPECT--
9
+ 0
10
+ 0
vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag sandboxed
3
+ --TEMPLATE--
4
+ {{ include("foo.twig", sandboxed = true) }}
5
+ {{ include("bar.twig") }}
6
+ --TEMPLATE(foo.twig)--
7
+ foo
8
+ --TEMPLATE(bar.twig)--
9
+ {{ foo|e }}
10
+ --DATA--
11
+ return array('foo' => 'bar<br />')
12
+ --EXPECT--
13
+ foo
14
+
15
+
16
+ bar&lt;br /&gt;
vendor/twig/twig/test/Twig/Tests/Fixtures/functions/include/sandbox_disabling_ignore_missing.test ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag sandboxed
3
+ --TEMPLATE--
4
+ {{ include("unknown.twig", sandboxed = true, ignore_missing = true) }}
5
+ {{ include("bar.twig") }}
6
+ --TEMPLATE(bar.twig)--
7
+ {{ foo|e }}
8
+ --DATA--
9
+ return array('foo' => 'bar<br />')
10
+ --EXPECT--
11
+
12
+
13
+ bar&lt;br /&gt;
vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs.test ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ macro with arbitrary arguments
3
+ --TEMPLATE--
4
+ {% from _self import test1, test2 %}
5
+
6
+ {% macro test1(var) %}
7
+ {{- var }}: {{ varargs|join(", ") }}
8
+ {% endmacro %}
9
+
10
+ {% macro test2() %}
11
+ {{- varargs|join(", ") }}
12
+ {% endmacro %}
13
+
14
+ {{ test1("foo", "bar", "foobar") }}
15
+ {{ test2("foo", "bar", "foobar") }}
16
+ --DATA--
17
+ return array();
18
+ --EXPECT--
19
+ foo: bar, foobar
20
+
21
+ foo, bar, foobar
vendor/twig/twig/test/Twig/Tests/Fixtures/macros/varargs_argument.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ macro with varargs argument
3
+ --TEMPLATE--
4
+ {% macro test(varargs) %}
5
+ {% endmacro %}
6
+ --EXCEPTION--
7
+ Twig_Error_Syntax: The argument "varargs" in macro "test" cannot be defined because the variable "varargs" is reserved for arbitrary arguments in "index.twig" at line 2
8
+
vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/from_with_reserved_name.test ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "from" tag with reserved name
3
+ --TEMPLATE--
4
+ {% from 'forms.twig' import templateName %}
5
+ --TEMPLATE(forms.twig)--
6
+ --DATA--
7
+ return array()
8
+ --EXCEPTION--
9
+ Twig_Error_Syntax: "templateName" cannot be an imported macro as it is a reserved keyword in "index.twig" at line 2
vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/import_with_reserved_nam.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "from" tag with reserved name
3
+ --TEMPLATE--
4
+ {% import 'forms.twig' as macros %}
5
+
6
+ {{ macros.parent() }}
7
+ --TEMPLATE(forms.twig)--
8
+ --DATA--
9
+ return array()
10
+ --EXCEPTION--
11
+ Twig_Error_Syntax: "parent" cannot be called as macro as it is a reserved keyword in "index.twig" at line 4
vendor/twig/twig/test/Twig/Tests/Fixtures/tags/macro/reserved_name.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "macro" tag with reserved name
3
+ --TEMPLATE--
4
+ {% macro parent(arg1, arg2) %}
5
+ parent
6
+ {% endmacro %}
7
+ --DATA--
8
+ return array()
9
+ --EXCEPTION--
10
+ Twig_Error_Syntax: "parent" cannot be used as a macro name as it is a reserved keyword in "index.twig" at line 2
vendor/twig/twig/test/Twig/Tests/IntegrationTest.php CHANGED
@@ -175,7 +175,7 @@ class TwigTestExtension extends Twig_Extension
175
  }
176
 
177
  /**
178
- * nl2br which also escapes, for testing escaper filters
179
  */
180
  public function escape_and_nl2br($env, $value, $sep = '<br />')
181
  {
@@ -183,7 +183,7 @@ class TwigTestExtension extends Twig_Extension
183
  }
184
 
185
  /**
186
- * nl2br only, for testing filters with pre_escape
187
  */
188
  public function nl2br($value, $sep = '<br />')
189
  {
175
  }
176
 
177
  /**
178
+ * nl2br which also escapes, for testing escaper filters.
179
  */
180
  public function escape_and_nl2br($env, $value, $sep = '<br />')
181
  {
183
  }
184
 
185
  /**
186
+ * nl2br only, for testing filters with pre_escape.
187
  */
188
  public function nl2br($value, $sep = '<br />')
189
  {
vendor/twig/twig/test/Twig/Tests/LexerTest.php CHANGED
@@ -146,14 +146,14 @@ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
146
  $stream = $lexer->tokenize($template);
147
  $stream->next();
148
  $node = $stream->next();
149
- $this->assertEquals("922337203685477580700", $node->getValue());
150
  }
151
 
152
  public function testStringWithEscapedDelimiter()
153
  {
154
  $tests = array(
155
  "{{ 'foo \' bar' }}" => 'foo \' bar',
156
- '{{ "foo \" bar" }}' => "foo \" bar",
157
  );
158
  $lexer = new Twig_Lexer(new Twig_Environment());
159
  foreach ($tests as $template => $expected) {
146
  $stream = $lexer->tokenize($template);
147
  $stream->next();
148
  $node = $stream->next();
149
+ $this->assertEquals('922337203685477580700', $node->getValue());
150
  }
151
 
152
  public function testStringWithEscapedDelimiter()
153
  {
154
  $tests = array(
155
  "{{ 'foo \' bar' }}" => 'foo \' bar',
156
+ '{{ "foo \" bar" }}' => 'foo " bar',
157
  );
158
  $lexer = new Twig_Lexer(new Twig_Environment());
159
  foreach ($tests as $template => $expected) {
vendor/twig/twig/test/Twig/Tests/NativeExtensionTest.php CHANGED
@@ -18,8 +18,8 @@ class Twig_Tests_NativeExtensionTest extends PHPUnit_Framework_TestCase
18
  }
19
 
20
  $twig = new Twig_Environment(new Twig_Loader_Array(array('index' => '{{ d1.date }}{{ d2.date }}')), array(
21
- 'debug' => true,
22
- 'cache' => false,
23
  'autoescape' => false,
24
  ));
25
 
18
  }
19
 
20
  $twig = new Twig_Environment(new Twig_Loader_Array(array('index' => '{{ d1.date }}{{ d2.date }}')), array(
21
+ 'debug' => true,
22
+ 'cache' => false,
23
  'autoescape' => false,
24
  ));
25
 
vendor/twig/twig/test/Twig/Tests/Node/Expression/CallTest.php CHANGED
@@ -73,7 +73,7 @@ class Twig_Tests_Node_Expression_CallTest extends PHPUnit_Framework_TestCase
73
 
74
  public function testResolveArgumentsOnlyNecessaryArgumentsForCustomFunction()
75
  {
76
- $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'custom_function'));
77
 
78
  $this->assertEquals(array('arg1'), $node->getArguments(array($this, 'customFunction'), array('arg1' => 'arg1')));
79
  }
@@ -84,6 +84,16 @@ class Twig_Tests_Node_Expression_CallTest extends PHPUnit_Framework_TestCase
84
  $this->assertEquals(array('arg1'), $node->getArguments(__CLASS__.'::customStaticFunction', array('arg1' => 'arg1')));
85
  }
86
 
 
 
 
 
 
 
 
 
 
 
87
  public static function customStaticFunction($arg1, $arg2 = 'default', $arg3 = array())
88
  {
89
  }
@@ -91,6 +101,10 @@ class Twig_Tests_Node_Expression_CallTest extends PHPUnit_Framework_TestCase
91
  public function customFunction($arg1, $arg2 = 'default', $arg3 = array())
92
  {
93
  }
 
 
 
 
94
  }
95
 
96
  class Twig_Tests_Node_Expression_Call extends Twig_Node_Expression_Call
73
 
74
  public function testResolveArgumentsOnlyNecessaryArgumentsForCustomFunction()
75
  {
76
+ $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'custom_function'));
77
 
78
  $this->assertEquals(array('arg1'), $node->getArguments(array($this, 'customFunction'), array('arg1' => 'arg1')));
79
  }
84
  $this->assertEquals(array('arg1'), $node->getArguments(__CLASS__.'::customStaticFunction', array('arg1' => 'arg1')));
85
  }
86
 
87
+ /**
88
+ * @expectedException LogicException
89
+ * @expectedExceptionMessage The last parameter of "Twig_Tests_Node_Expression_CallTest::customFunctionWithArbitraryArguments" for function "foo" must be an array with default value, eg. "array $arg = array()".
90
+ */
91
+ public function testResolveArgumentsWithMissingParameterForArbitraryArguments()
92
+ {
93
+ $node = new Twig_Tests_Node_Expression_Call(array(), array('type' => 'function', 'name' => 'foo', 'is_variadic' => true));
94
+ $node->getArguments(array($this, 'customFunctionWithArbitraryArguments'), array());
95
+ }
96
+
97
  public static function customStaticFunction($arg1, $arg2 = 'default', $arg3 = array())
98
  {
99
  }
101
  public function customFunction($arg1, $arg2 = 'default', $arg3 = array())
102
  {
103
  }
104
+
105
+ public function customFunctionWithArbitraryArguments()
106
+ {
107
+ }
108
  }
109
 
110
  class Twig_Tests_Node_Expression_Call extends Twig_Node_Expression_Call
vendor/twig/twig/test/Twig/Tests/Node/Expression/FilterTest.php CHANGED
@@ -25,6 +25,10 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Test_NodeTestCase
25
 
26
  public function getTests()
27
  {
 
 
 
 
28
  $tests = array();
29
 
30
  $expr = new Twig_Node_Expression_Constant('foo', 1);
@@ -41,7 +45,7 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Test_NodeTestCase
41
  $date = new Twig_Node_Expression_Constant(0, 1);
42
  $node = $this->createFilter($date, 'date', array(
43
  'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
44
- 'format' => new Twig_Node_Expression_Constant('d/m/Y H:i:s P', 1),
45
  ));
46
  $tests[] = array($node, 'twig_date_format_filter($this->env, 0, "d/m/Y H:i:s P", "America/Chicago")');
47
 
@@ -69,6 +73,31 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Test_NodeTestCase
69
  $tests[] = array($node, 'call_user_func_array($this->env->getFilter(\'anonymous\')->getCallable(), array("foo"))');
70
  }
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  return $tests;
73
  }
74
 
@@ -119,3 +148,7 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Test_NodeTestCase
119
  return parent::getEnvironment();
120
  }
121
  }
 
 
 
 
25
 
26
  public function getTests()
27
  {
28
+ $environment = new Twig_Environment();
29
+ $environment->addFilter(new Twig_SimpleFilter('bar', 'bar', array('needs_environment' => true)));
30
+ $environment->addFilter(new Twig_SimpleFilter('barbar', 'twig_tests_filter_barbar', array('needs_context' => true, 'is_variadic' => true)));
31
+
32
  $tests = array();
33
 
34
  $expr = new Twig_Node_Expression_Constant('foo', 1);
45
  $date = new Twig_Node_Expression_Constant(0, 1);
46
  $node = $this->createFilter($date, 'date', array(
47
  'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
48
+ 'format' => new Twig_Node_Expression_Constant('d/m/Y H:i:s P', 1),
49
  ));
50
  $tests[] = array($node, 'twig_date_format_filter($this->env, 0, "d/m/Y H:i:s P", "America/Chicago")');
51
 
73
  $tests[] = array($node, 'call_user_func_array($this->env->getFilter(\'anonymous\')->getCallable(), array("foo"))');
74
  }
75
 
76
+ // needs environment
77
+ $node = $this->createFilter($string, 'bar');
78
+ $tests[] = array($node, 'bar($this->env, "abc")', $environment);
79
+
80
+ $node = $this->createFilter($string, 'bar', array(new Twig_Node_Expression_Constant('bar', 1)));
81
+ $tests[] = array($node, 'bar($this->env, "abc", "bar")', $environment);
82
+
83
+ // arbitrary named arguments
84
+ $node = $this->createFilter($string, 'barbar');
85
+ $tests[] = array($node, 'twig_tests_filter_barbar($context, "abc")', $environment);
86
+
87
+ $node = $this->createFilter($string, 'barbar', array('foo' => new Twig_Node_Expression_Constant('bar', 1)));
88
+ $tests[] = array($node, 'twig_tests_filter_barbar($context, "abc", null, null, array("foo" => "bar"))', $environment);
89
+
90
+ $node = $this->createFilter($string, 'barbar', array('arg2' => new Twig_Node_Expression_Constant('bar', 1)));
91
+ $tests[] = array($node, 'twig_tests_filter_barbar($context, "abc", null, "bar")', $environment);
92
+
93
+ $node = $this->createFilter($string, 'barbar', array(
94
+ new Twig_Node_Expression_Constant('1', 1),
95
+ new Twig_Node_Expression_Constant('2', 1),
96
+ new Twig_Node_Expression_Constant('3', 1),
97
+ 'foo' => new Twig_Node_Expression_Constant('bar', 1),
98
+ ));
99
+ $tests[] = array($node, 'twig_tests_filter_barbar($context, "abc", "1", "2", array(0 => "3", "foo" => "bar"))', $environment);
100
+
101
  return $tests;
102
  }
103
 
148
  return parent::getEnvironment();
149
  }
150
  }
151
+
152
+ function twig_tests_filter_barbar($context, $string, $arg1 = null, $arg2 = null, array $args = array())
153
+ {
154
+ }
vendor/twig/twig/test/Twig/Tests/Node/Expression/FunctionTest.php CHANGED
@@ -28,6 +28,7 @@ class Twig_Tests_Node_Expression_FunctionTest extends Twig_Test_NodeTestCase
28
  $environment->addFunction(new Twig_SimpleFunction('bar', 'bar', array('needs_environment' => true)));
29
  $environment->addFunction(new Twig_SimpleFunction('foofoo', 'foofoo', array('needs_context' => true)));
30
  $environment->addFunction(new Twig_SimpleFunction('foobar', 'foobar', array('needs_environment' => true, 'needs_context' => true)));
 
31
 
32
  $tests = array();
33
 
@@ -58,10 +59,28 @@ class Twig_Tests_Node_Expression_FunctionTest extends Twig_Test_NodeTestCase
58
  // named arguments
59
  $node = $this->createFunction('date', array(
60
  'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
61
- 'date' => new Twig_Node_Expression_Constant(0, 1),
62
  ));
63
  $tests[] = array($node, 'twig_date_converter($this->env, 0, "America/Chicago")');
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  // function as an anonymous function
66
  if (PHP_VERSION_ID >= 50300) {
67
  $node = $this->createFunction('anonymous', array(new Twig_Node_Expression_Constant('foo', 1)));
@@ -85,3 +104,7 @@ class Twig_Tests_Node_Expression_FunctionTest extends Twig_Test_NodeTestCase
85
  return parent::getEnvironment();
86
  }
87
  }
 
 
 
 
28
  $environment->addFunction(new Twig_SimpleFunction('bar', 'bar', array('needs_environment' => true)));
29
  $environment->addFunction(new Twig_SimpleFunction('foofoo', 'foofoo', array('needs_context' => true)));
30
  $environment->addFunction(new Twig_SimpleFunction('foobar', 'foobar', array('needs_environment' => true, 'needs_context' => true)));
31
+ $environment->addFunction(new Twig_SimpleFunction('barbar', 'twig_tests_function_barbar', array('is_variadic' => true)));
32
 
33
  $tests = array();
34
 
59
  // named arguments
60
  $node = $this->createFunction('date', array(
61
  'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
62
+ 'date' => new Twig_Node_Expression_Constant(0, 1),
63
  ));
64
  $tests[] = array($node, 'twig_date_converter($this->env, 0, "America/Chicago")');
65
 
66
+ // arbitrary named arguments
67
+ $node = $this->createFunction('barbar');
68
+ $tests[] = array($node, 'twig_tests_function_barbar()', $environment);
69
+
70
+ $node = $this->createFunction('barbar', array('foo' => new Twig_Node_Expression_Constant('bar', 1)));
71
+ $tests[] = array($node, 'twig_tests_function_barbar(null, null, array("foo" => "bar"))', $environment);
72
+
73
+ $node = $this->createFunction('barbar', array('arg2' => new Twig_Node_Expression_Constant('bar', 1)));
74
+ $tests[] = array($node, 'twig_tests_function_barbar(null, "bar")', $environment);
75
+
76
+ $node = $this->createFunction('barbar', array(
77
+ new Twig_Node_Expression_Constant('1', 1),
78
+ new Twig_Node_Expression_Constant('2', 1),
79
+ new Twig_Node_Expression_Constant('3', 1),
80
+ 'foo' => new Twig_Node_Expression_Constant('bar', 1),
81
+ ));
82
+ $tests[] = array($node, 'twig_tests_function_barbar("1", "2", array(0 => "3", "foo" => "bar"))', $environment);
83
+
84
  // function as an anonymous function
85
  if (PHP_VERSION_ID >= 50300) {
86
  $node = $this->createFunction('anonymous', array(new Twig_Node_Expression_Constant('foo', 1)));
104
  return parent::getEnvironment();
105
  }
106
  }
107
+
108
+ function twig_tests_function_barbar($arg1 = null, $arg2 = null, array $args = array())
109
+ {
110
+ }
vendor/twig/twig/test/Twig/Tests/Node/Expression/TestTest.php CHANGED
@@ -25,6 +25,9 @@ class Twig_Tests_Node_Expression_TestTest extends Twig_Test_NodeTestCase
25
 
26
  public function getTests()
27
  {
 
 
 
28
  $tests = array();
29
 
30
  $expr = new Twig_Node_Expression_Constant('foo', 1);
@@ -37,6 +40,25 @@ class Twig_Tests_Node_Expression_TestTest extends Twig_Test_NodeTestCase
37
  $tests[] = array($node, 'call_user_func_array($this->env->getTest(\'anonymous\')->getCallable(), array("foo", "foo"))');
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  return $tests;
41
  }
42
 
@@ -54,3 +76,7 @@ class Twig_Tests_Node_Expression_TestTest extends Twig_Test_NodeTestCase
54
  return parent::getEnvironment();
55
  }
56
  }
 
 
 
 
25
 
26
  public function getTests()
27
  {
28
+ $environment = new Twig_Environment();
29
+ $environment->addTest(new Twig_SimpleTest('barbar', 'twig_tests_test_barbar', array('is_variadic' => true, 'need_context' => true)));
30
+
31
  $tests = array();
32
 
33
  $expr = new Twig_Node_Expression_Constant('foo', 1);
40
  $tests[] = array($node, 'call_user_func_array($this->env->getTest(\'anonymous\')->getCallable(), array("foo", "foo"))');
41
  }
42
 
43
+ // arbitrary named arguments
44
+ $string = new Twig_Node_Expression_Constant('abc', 1);
45
+ $node = $this->createTest($string, 'barbar');
46
+ $tests[] = array($node, 'twig_tests_test_barbar("abc")', $environment);
47
+
48
+ $node = $this->createTest($string, 'barbar', array('foo' => new Twig_Node_Expression_Constant('bar', 1)));
49
+ $tests[] = array($node, 'twig_tests_test_barbar("abc", null, null, array("foo" => "bar"))', $environment);
50
+
51
+ $node = $this->createTest($string, 'barbar', array('arg2' => new Twig_Node_Expression_Constant('bar', 1)));
52
+ $tests[] = array($node, 'twig_tests_test_barbar("abc", null, "bar")', $environment);
53
+
54
+ $node = $this->createTest($string, 'barbar', array(
55
+ new Twig_Node_Expression_Constant('1', 1),
56
+ new Twig_Node_Expression_Constant('2', 1),
57
+ new Twig_Node_Expression_Constant('3', 1),
58
+ 'foo' => new Twig_Node_Expression_Constant('bar', 1),
59
+ ));
60
+ $tests[] = array($node, 'twig_tests_test_barbar("abc", "1", "2", array(0 => "3", "foo" => "bar"))', $environment);
61
+
62
  return $tests;
63
  }
64
 
76
  return parent::getEnvironment();
77
  }
78
  }
79
+
80
+ function twig_tests_test_barbar($string, $arg1 = null, $arg2 = null, array $args = array())
81
+ {
82
+ }
vendor/twig/twig/test/Twig/Tests/Node/MacroTest.php CHANGED
@@ -31,14 +31,23 @@ class Twig_Tests_Node_MacroTest extends Twig_Test_NodeTestCase
31
  ), array(), 1);
32
  $node = new Twig_Node_Macro('foo', $body, $arguments, 1);
33
 
 
 
 
 
 
 
 
 
34
  return array(
35
  array($node, <<<EOF
36
  // line 1
37
- public function getfoo(\$__foo__ = null, \$__bar__ = "Foo")
38
  {
39
  \$context = \$this->env->mergeGlobals(array(
40
  "foo" => \$__foo__,
41
  "bar" => \$__bar__,
 
42
  ));
43
 
44
  \$blocks = array();
31
  ), array(), 1);
32
  $node = new Twig_Node_Macro('foo', $body, $arguments, 1);
33
 
34
+ if (PHP_VERSION_ID >= 50600) {
35
+ $declaration = ', ...$__varargs__';
36
+ $varargs = '$__varargs__';
37
+ } else {
38
+ $declaration = '';
39
+ $varargs = 'func_num_args() > 2 ? array_slice(func_get_args(), 2) : array()';
40
+ }
41
+
42
  return array(
43
  array($node, <<<EOF
44
  // line 1
45
+ public function getfoo(\$__foo__ = null, \$__bar__ = "Foo"$declaration)
46
  {
47
  \$context = \$this->env->mergeGlobals(array(
48
  "foo" => \$__foo__,
49
  "bar" => \$__bar__,
50
+ "varargs" => $varargs,
51
  ));
52
 
53
  \$blocks = array();
vendor/twig/twig/test/Twig/Tests/ParserTest.php CHANGED
@@ -16,7 +16,7 @@ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
16
  public function testSetMacroThrowsExceptionOnReservedMethods()
17
  {
18
  $parser = $this->getParser();
19
- $parser->setMacro('display', $this->getMock('Twig_Node_Macro', array(), array(), '', null));
20
  }
21
 
22
  /**
16
  public function testSetMacroThrowsExceptionOnReservedMethods()
17
  {
18
  $parser = $this->getParser();
19
+ $parser->setMacro('parent', $this->getMock('Twig_Node_Macro', array(), array(), '', null));
20
  }
21
 
22
  /**
vendor/twig/twig/test/Twig/Tests/Profiler/Dumper/AbstractTest.php CHANGED
@@ -73,11 +73,11 @@ abstract class Twig_Tests_Profiler_Dumper_AbstractTest extends PHPUnit_Framework
73
 
74
  /**
75
  * @param string $name
76
- * @param float $duration
77
- * @param bool $isTemplate
78
  * @param string $type
79
  * @param string $templateName
80
- * @param array $subProfiles
81
  *
82
  * @return Twig_Profiler_Profile
83
  */
73
 
74
  /**
75
  * @param string $name
76
+ * @param float $duration
77
+ * @param bool $isTemplate
78
  * @param string $type
79
  * @param string $templateName
80
+ * @param array $subProfiles
81
  *
82
  * @return Twig_Profiler_Profile
83
  */
vendor/twig/twig/test/Twig/Tests/TemplateTest.php CHANGED
@@ -10,6 +10,15 @@
10
  */
11
  class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
12
  {
 
 
 
 
 
 
 
 
 
13
  /**
14
  * @dataProvider getAttributeExceptions
15
  */
@@ -27,13 +36,13 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
27
  $template = $env->loadTemplate($name);
28
 
29
  $context = array(
30
- 'string' => 'foo',
31
- 'null' => null,
32
- 'empty_array' => array(),
33
- 'array' => array('foo' => 'foo'),
34
- 'array_access' => new Twig_TemplateArrayAccessObject(),
35
  'magic_exception' => new Twig_TemplateMagicPropertyObjectWithException(),
36
- 'object' => new stdClass(),
37
  );
38
 
39
  try {
@@ -60,7 +69,7 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
60
  array('{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1', false),
61
  array('{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1', false),
62
  array('{{ array_access.a }}', 'Method "a" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
63
- array('{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ _self.foo(array_access) }}', 'Method "missing_method" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
64
  array('{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.', false),
65
  array('{{ object["a"] }}', 'Impossible to access a key "a" on an object of class "stdClass" that does not implement ArrayAccess interface in "%s" at line 1', false),
66
  );
@@ -138,6 +147,11 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
138
 
139
  $this->assertNotInstanceof('Twig_Markup', $template->getAttribute($template1, 'empty'));
140
  $this->assertSame('', $template->getAttribute($template1, 'empty'));
 
 
 
 
 
141
  }
142
 
143
  public function getGetAttributeWithTemplateAsObject()
@@ -261,27 +275,27 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
261
  {
262
  $array = array(
263
  'defined' => 'defined',
264
- 'zero' => 0,
265
- 'null' => null,
266
- '1' => 1,
267
- 'bar' => true,
268
- '09' => '09',
269
- '+4' => '+4',
270
  );
271
 
272
- $objectArray = new Twig_TemplateArrayAccessObject();
273
- $stdObject = (object) $array;
274
  $magicPropertyObject = new Twig_TemplateMagicPropertyObject();
275
- $propertyObject = new Twig_TemplatePropertyObject();
276
- $propertyObject1 = new Twig_TemplatePropertyObjectAndIterator();
277
- $propertyObject2 = new Twig_TemplatePropertyObjectAndArrayAccess();
278
- $propertyObject3 = new Twig_TemplatePropertyObjectDefinedWithUndefinedValue();
279
- $methodObject = new Twig_TemplateMethodObject();
280
- $magicMethodObject = new Twig_TemplateMagicMethodObject();
281
-
282
- $anyType = Twig_Template::ANY_CALL;
283
  $methodType = Twig_Template::METHOD_CALL;
284
- $arrayType = Twig_Template::ARRAY_CALL;
285
 
286
  $basicTests = array(
287
  // array(defined, value, property to fetch)
@@ -374,7 +388,7 @@ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
374
  // tests when input is not an array or object
375
  $tests = array_merge($tests, array(
376
  array(false, null, 42, 'a', array(), $anyType, false, 'Impossible to access an attribute ("a") on a integer variable ("42")'),
377
- array(false, null, "string", 'a', array(), $anyType, false, 'Impossible to access an attribute ("a") on a string variable ("string")'),
378
  array(false, null, array(), 'a', array(), $anyType, false, 'Key "a" does not exist as the array is empty'),
379
  ));
380
 
@@ -456,12 +470,12 @@ class Twig_TemplateArrayAccessObject implements ArrayAccess
456
 
457
  public $attributes = array(
458
  'defined' => 'defined',
459
- 'zero' => 0,
460
- 'null' => null,
461
- '1' => 1,
462
- 'bar' => true,
463
- '09' => '09',
464
- '+4' => '+4',
465
  );
466
 
467
  public function offsetExists($name)
@@ -488,12 +502,12 @@ class Twig_TemplateMagicPropertyObject
488
  public $defined = 'defined';
489
 
490
  public $attributes = array(
491
- 'zero' => 0,
492
- 'null' => null,
493
- '1' => 1,
494
- 'bar' => true,
495
- '09' => '09',
496
- '+4' => '+4',
497
  );
498
 
499
  protected $protected = 'protected';
@@ -520,9 +534,9 @@ class Twig_TemplateMagicPropertyObjectWithException
520
  class Twig_TemplatePropertyObject
521
  {
522
  public $defined = 'defined';
523
- public $zero = 0;
524
- public $null = null;
525
- public $bar = true;
526
 
527
  protected $protected = 'protected';
528
  }
10
  */
11
  class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
12
  {
13
+ /**
14
+ * @expectedException LogicException
15
+ */
16
+ public function testDisplayBlocksAcceptTemplateOnlyAsBlocks()
17
+ {
18
+ $template = $this->getMockForAbstractClass('Twig_Template', array(), '', false);
19
+ $template->displayBlock('foo', array(), array('foo' => array(new stdClass(), 'foo')));
20
+ }
21
+
22
  /**
23
  * @dataProvider getAttributeExceptions
24
  */
36
  $template = $env->loadTemplate($name);
37
 
38
  $context = array(
39
+ 'string' => 'foo',
40
+ 'null' => null,
41
+ 'empty_array' => array(),
42
+ 'array' => array('foo' => 'foo'),
43
+ 'array_access' => new Twig_TemplateArrayAccessObject(),
44
  'magic_exception' => new Twig_TemplateMagicPropertyObjectWithException(),
45
+ 'object' => new stdClass(),
46
  );
47
 
48
  try {
69
  array('{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1', false),
70
  array('{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1', false),
71
  array('{{ array_access.a }}', 'Method "a" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
72
+ array('{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Method "missing_method" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
73
  array('{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.', false),
74
  array('{{ object["a"] }}', 'Impossible to access a key "a" on an object of class "stdClass" that does not implement ArrayAccess interface in "%s" at line 1', false),
75
  );
147
 
148
  $this->assertNotInstanceof('Twig_Markup', $template->getAttribute($template1, 'empty'));
149
  $this->assertSame('', $template->getAttribute($template1, 'empty'));
150
+
151
+ $this->assertFalse($template->getAttribute($template1, 'env', array(), Twig_Template::ANY_CALL, true));
152
+ $this->assertFalse($template->getAttribute($template1, 'environment', array(), Twig_Template::ANY_CALL, true));
153
+ $this->assertFalse($template->getAttribute($template1, 'getEnvironment', array(), Twig_Template::METHOD_CALL, true));
154
+ $this->assertFalse($template->getAttribute($template1, 'displayWithErrorHandling', array(), Twig_Template::METHOD_CALL, true));
155
  }
156
 
157
  public function getGetAttributeWithTemplateAsObject()
275
  {
276
  $array = array(
277
  'defined' => 'defined',
278
+ 'zero' => 0,
279
+ 'null' => null,
280
+ '1' => 1,
281
+ 'bar' => true,
282
+ '09' => '09',
283
+ '+4' => '+4',
284
  );
285
 
286
+ $objectArray = new Twig_TemplateArrayAccessObject();
287
+ $stdObject = (object) $array;
288
  $magicPropertyObject = new Twig_TemplateMagicPropertyObject();
289
+ $propertyObject = new Twig_TemplatePropertyObject();
290
+ $propertyObject1 = new Twig_TemplatePropertyObjectAndIterator();
291
+ $propertyObject2 = new Twig_TemplatePropertyObjectAndArrayAccess();
292
+ $propertyObject3 = new Twig_TemplatePropertyObjectDefinedWithUndefinedValue();
293
+ $methodObject = new Twig_TemplateMethodObject();
294
+ $magicMethodObject = new Twig_TemplateMagicMethodObject();
295
+
296
+ $anyType = Twig_Template::ANY_CALL;
297
  $methodType = Twig_Template::METHOD_CALL;
298
+ $arrayType = Twig_Template::ARRAY_CALL;
299
 
300
  $basicTests = array(
301
  // array(defined, value, property to fetch)
388
  // tests when input is not an array or object
389
  $tests = array_merge($tests, array(
390
  array(false, null, 42, 'a', array(), $anyType, false, 'Impossible to access an attribute ("a") on a integer variable ("42")'),
391
+ array(false, null, 'string', 'a', array(), $anyType, false, 'Impossible to access an attribute ("a") on a string variable ("string")'),
392
  array(false, null, array(), 'a', array(), $anyType, false, 'Key "a" does not exist as the array is empty'),
393
  ));
394
 
470
 
471
  public $attributes = array(
472
  'defined' => 'defined',
473
+ 'zero' => 0,
474
+ 'null' => null,
475
+ '1' => 1,
476
+ 'bar' => true,
477
+ '09' => '09',
478
+ '+4' => '+4',
479
  );
480
 
481
  public function offsetExists($name)
502
  public $defined = 'defined';
503
 
504
  public $attributes = array(
505
+ 'zero' => 0,
506
+ 'null' => null,
507
+ '1' => 1,
508
+ 'bar' => true,
509
+ '09' => '09',
510
+ '+4' => '+4',
511
  );
512
 
513
  protected $protected = 'protected';
534
  class Twig_TemplatePropertyObject
535
  {
536
  public $defined = 'defined';
537
+ public $zero = 0;
538
+ public $null = null;
539
+ public $bar = true;
540
 
541
  protected $protected = 'protected';
542
  }
vendor/twig/twig/test/Twig/Tests/escapingTest.php CHANGED
@@ -9,137 +9,137 @@
9
  class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
10
  {
11
  /**
12
- * All character encodings supported by htmlspecialchars()
13
  */
14
  protected $htmlSpecialChars = array(
15
- '\'' => '&#039;',
16
- '"' => '&quot;',
17
- '<' => '&lt;',
18
- '>' => '&gt;',
19
- '&' => '&amp;',
20
  );
21
 
22
  protected $htmlAttrSpecialChars = array(
23
- '\'' => '&#x27;',
24
  /* Characters beyond ASCII value 255 to unicode escape */
25
- 'Ā' => '&#x0100;',
26
  /* Immune chars excluded */
27
- ',' => ',',
28
- '.' => '.',
29
- '-' => '-',
30
- '_' => '_',
31
  /* Basic alnums excluded */
32
- 'a' => 'a',
33
- 'A' => 'A',
34
- 'z' => 'z',
35
- 'Z' => 'Z',
36
- '0' => '0',
37
- '9' => '9',
38
  /* Basic control characters and null */
39
- "\r" => '&#x0D;',
40
- "\n" => '&#x0A;',
41
- "\t" => '&#x09;',
42
- "\0" => '&#xFFFD;', // should use Unicode replacement char
43
  /* Encode chars as named entities where possible */
44
- '<' => '&lt;',
45
- '>' => '&gt;',
46
- '&' => '&amp;',
47
- '"' => '&quot;',
48
  /* Encode spaces for quoteless attribute protection */
49
- ' ' => '&#x20;',
50
  );
51
 
52
  protected $jsSpecialChars = array(
53
  /* HTML special chars - escape without exception to hex */
54
- '<' => '\\x3C',
55
- '>' => '\\x3E',
56
- '\'' => '\\x27',
57
- '"' => '\\x22',
58
- '&' => '\\x26',
59
  /* Characters beyond ASCII value 255 to unicode escape */
60
- 'Ā' => '\\u0100',
61
  /* Immune chars excluded */
62
- ',' => ',',
63
- '.' => '.',
64
- '_' => '_',
65
  /* Basic alnums excluded */
66
- 'a' => 'a',
67
- 'A' => 'A',
68
- 'z' => 'z',
69
- 'Z' => 'Z',
70
- '0' => '0',
71
- '9' => '9',
72
  /* Basic control characters and null */
73
- "\r" => '\\x0D',
74
- "\n" => '\\x0A',
75
- "\t" => '\\x09',
76
- "\0" => '\\x00',
77
  /* Encode spaces for quoteless attribute protection */
78
- ' ' => '\\x20',
79
  );
80
 
81
  protected $urlSpecialChars = array(
82
  /* HTML special chars - escape without exception to percent encoding */
83
- '<' => '%3C',
84
- '>' => '%3E',
85
- '\'' => '%27',
86
- '"' => '%22',
87
- '&' => '%26',
88
  /* Characters beyond ASCII value 255 to hex sequence */
89
- 'Ā' => '%C4%80',
90
  /* Punctuation and unreserved check */
91
- ',' => '%2C',
92
- '.' => '.',
93
- '_' => '_',
94
- '-' => '-',
95
- ':' => '%3A',
96
- ';' => '%3B',
97
- '!' => '%21',
98
  /* Basic alnums excluded */
99
- 'a' => 'a',
100
- 'A' => 'A',
101
- 'z' => 'z',
102
- 'Z' => 'Z',
103
- '0' => '0',
104
- '9' => '9',
105
  /* Basic control characters and null */
106
- "\r" => '%0D',
107
- "\n" => '%0A',
108
- "\t" => '%09',
109
- "\0" => '%00',
110
  /* PHP quirks from the past */
111
- ' ' => '%20',
112
- '~' => '~',
113
- '+' => '%2B',
114
  );
115
 
116
  protected $cssSpecialChars = array(
117
  /* HTML special chars - escape without exception to hex */
118
- '<' => '\\3C ',
119
- '>' => '\\3E ',
120
- '\'' => '\\27 ',
121
- '"' => '\\22 ',
122
- '&' => '\\26 ',
123
  /* Characters beyond ASCII value 255 to unicode escape */
124
- 'Ā' => '\\100 ',
125
  /* Immune chars excluded */
126
- ',' => '\\2C ',
127
- '.' => '\\2E ',
128
- '_' => '\\5F ',
129
  /* Basic alnums excluded */
130
- 'a' => 'a',
131
- 'A' => 'A',
132
- 'z' => 'z',
133
- 'Z' => 'Z',
134
- '0' => '0',
135
- '9' => '9',
136
  /* Basic control characters and null */
137
- "\r" => '\\D ',
138
- "\n" => '\\A ',
139
- "\t" => '\\9 ',
140
- "\0" => '\\0 ',
141
  /* Encode spaces for quoteless attribute protection */
142
- ' ' => '\\20 ',
143
  );
144
 
145
  protected $env;
@@ -205,16 +205,16 @@ class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
205
  }
206
 
207
  /**
208
- * Range tests to confirm escaped range of characters is within OWASP recommendation
209
  */
210
 
211
  /**
212
  * Only testing the first few 2 ranges on this prot. function as that's all these
213
- * other range tests require
214
  */
215
  public function testUnicodeCodepointConversionToUtf8()
216
  {
217
- $expected = "";
218
  $codepoints = array(0x20, 0x7e, 0x799);
219
  $result = '';
220
  foreach ($codepoints as $value) {
@@ -226,7 +226,7 @@ class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
226
  /**
227
  * Convert a Unicode Codepoint to a literal UTF-8 character.
228
  *
229
- * @param int $codepoint Unicode codepoint in hex notation
230
  *
231
  * @return string UTF-8 literal string
232
  */
@@ -256,7 +256,7 @@ class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
256
  public function testJavascriptEscapingEscapesOwaspRecommendedRanges()
257
  {
258
  $immune = array(',', '.', '_'); // Exceptions to escaping ranges
259
- for ($chr = 0; $chr < 0xFF; $chr++) {
260
  if ($chr >= 0x30 && $chr <= 0x39
261
  || $chr >= 0x41 && $chr <= 0x5A
262
  || $chr >= 0x61 && $chr <= 0x7A) {
@@ -279,7 +279,7 @@ class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
279
  public function testHtmlAttributeEscapingEscapesOwaspRecommendedRanges()
280
  {
281
  $immune = array(',', '.', '-', '_'); // Exceptions to escaping ranges
282
- for ($chr = 0; $chr < 0xFF; $chr++) {
283
  if ($chr >= 0x30 && $chr <= 0x39
284
  || $chr >= 0x41 && $chr <= 0x5A
285
  || $chr >= 0x61 && $chr <= 0x7A) {
@@ -302,7 +302,7 @@ class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
302
  public function testCssEscapingEscapesOwaspRecommendedRanges()
303
  {
304
  // CSS has no exceptions to escaping ranges
305
- for ($chr = 0; $chr < 0xFF; $chr++) {
306
  if ($chr >= 0x30 && $chr <= 0x39
307
  || $chr >= 0x41 && $chr <= 0x5A
308
  || $chr >= 0x61 && $chr <= 0x7A) {
9
  class Twig_Test_EscapingTest extends PHPUnit_Framework_TestCase
10
  {
11
  /**
12
+ * All character encodings supported by htmlspecialchars().
13
  */
14
  protected $htmlSpecialChars = array(
15
+ '\'' => '&#039;',
16
+ '"' => '&quot;',
17
+ '<' => '&lt;',
18
+ '>' => '&gt;',
19
+ '&' => '&amp;',
20
  );
21
 
22
  protected $htmlAttrSpecialChars = array(
23
+ '\'' => '&#x27;',
24
  /* Characters beyond ASCII value 255 to unicode escape */
25
+ 'Ā' => '&#x0100;',
26
  /* Immune chars excluded */
27
+ ',' => ',',
28
+ '.' => '.',
29
+ '-' => '-',
30
+ '_' => '_',
31
  /* Basic alnums excluded */
32
+ 'a' => 'a',
33
+ 'A' => 'A',
34
+ 'z' => 'z',
35
+ 'Z' => 'Z',
36
+ '0' => '0',
37
+ '9' => '9',
38
  /* Basic control characters and null */
39
+ "\r" => '&#x0D;',
40
+ "\n" => '&#x0A;',
41
+ "\t" => '&#x09;',
42
+ "\0" => '&#xFFFD;', // should use Unicode replacement char
43
  /* Encode chars as named entities where possible */
44
+ '<' => '&lt;',
45
+ '>' => '&gt;',
46
+ '&' => '&amp;',
47
+ '"' => '&quot;',
48
  /* Encode spaces for quoteless attribute protection */
49
+ ' ' => '&#x20;',
50
  );
51
 
52
  protected $jsSpecialChars = array(
53
  /* HTML special chars - escape without exception to hex */
54
+ '<' => '\\x3C',
55
+ '>' => '\\x3E',
56
+ '\'' => '\\x27',
57
+ '"' => '\\x22',
58
+ '&' => '\\x26',
59
  /* Characters beyond ASCII value 255 to unicode escape */
60
+ 'Ā' => '\\u0100',
61
  /* Immune chars excluded */
62
+ ',' => ',',
63
+ '.' => '.',
64
+ '_' => '_',
65
  /* Basic alnums excluded */
66
+ 'a' => 'a',
67
+ 'A' => 'A',
68
+ 'z' => 'z',
69
+ 'Z' => 'Z',
70
+ '0' => '0',
71
+ '9' => '9',
72
  /* Basic control characters and null */
73
+ "\r" => '\\x0D',
74
+ "\n" => '\\x0A',
75
+ "\t" => '\\x09',
76
+ "\0" => '\\x00',
77
  /* Encode spaces for quoteless attribute protection */
78
+ ' ' => '\\x20',
79
  );
80
 
81
  protected $urlSpecialChars = array(
82
  /* HTML special chars - escape without exception to percent encoding */
83
+ '<' => '%3C',
84
+ '>' => '%3E',
85
+ '\'' => '%27',
86
+ '"' => '%22',
87
+ '&' => '%26',
88
  /* Characters beyond ASCII value 255 to hex sequence */
89
+ 'Ā' => '%C4%80',
90
  /* Punctuation and unreserved check */
91
+ ',' => '%2C',
92
+ '.' => '.',
93
+ '_' => '_',
94
+ '-' => '-',
95
+ ':' => '%3A',
96
+ ';' => '%3B',
97
+ '!' => '%21',
98
  /* Basic alnums excluded */
99
+ 'a' => 'a',
100
+ 'A' => 'A',
101
+ 'z' => 'z',
102
+ 'Z' => 'Z',
103
+ '0' => '0',
104
+ '9' => '9',
105
  /* Basic control characters and null */
106
+ "\r" => '%0D',
107
+ "\n" => '%0A',
108
+ "\t" => '%09',
109
+ "\0" => '%00',
110
  /* PHP quirks from the past */
111
+ ' ' => '%20',
112
+ '~' => '~',
113
+ '+' => '%2B',
114
  );
115
 
116
  protected $cssSpecialChars = array(
117
  /* HTML special chars - escape without exception to hex */
118
+ '<' => '\\3C ',
119
+ '>' => '\\3E ',
120
+ '\'' => '\\27 ',
121
+ '"' => '\\22 ',
122
+ '&' => '\\26 ',
123
  /* Characters beyond ASCII value 255 to unicode escape */
124
+ 'Ā' => '\\100 ',
125
  /* Immune chars excluded */
126
+ ',' => '\\2C ',
127
+ '.' => '\\2E ',
128
+ '_' => '\\5F ',
129
  /* Basic alnums excluded */
130
+ 'a' => 'a',
131
+ 'A' => 'A',
132
+ 'z' => 'z',
133
+ 'Z' => 'Z',
134
+ '0' => '0',
135
+ '9' => '9',
136
  /* Basic control characters and null */
137
+ "\r" => '\\D ',
138
+ "\n" => '\\A ',
139
+ "\t" => '\\9 ',
140
+ "\0" => '\\0 ',
141
  /* Encode spaces for quoteless attribute protection */
142
+ ' ' => '\\20 ',
143
  );
144
 
145
  protected $env;
205
  }
206
 
207
  /**
208
+ * Range tests to confirm escaped range of characters is within OWASP recommendation.
209
  */
210
 
211
  /**
212
  * Only testing the first few 2 ranges on this prot. function as that's all these
213
+ * other range tests require.
214
  */
215
  public function testUnicodeCodepointConversionToUtf8()
216
  {
217
+ $expected = '';
218
  $codepoints = array(0x20, 0x7e, 0x799);
219
  $result = '';
220
  foreach ($codepoints as $value) {
226
  /**
227
  * Convert a Unicode Codepoint to a literal UTF-8 character.
228
  *
229
+ * @param int $codepoint Unicode codepoint in hex notation
230
  *
231
  * @return string UTF-8 literal string
232
  */
256
  public function testJavascriptEscapingEscapesOwaspRecommendedRanges()
257
  {
258
  $immune = array(',', '.', '_'); // Exceptions to escaping ranges
259
+ for ($chr = 0; $chr < 0xFF; ++$chr) {
260
  if ($chr >= 0x30 && $chr <= 0x39
261
  || $chr >= 0x41 && $chr <= 0x5A
262
  || $chr >= 0x61 && $chr <= 0x7A) {
279
  public function testHtmlAttributeEscapingEscapesOwaspRecommendedRanges()
280
  {
281
  $immune = array(',', '.', '-', '_'); // Exceptions to escaping ranges
282
+ for ($chr = 0; $chr < 0xFF; ++$chr) {
283
  if ($chr >= 0x30 && $chr <= 0x39
284
  || $chr >= 0x41 && $chr <= 0x5A
285
  || $chr >= 0x61 && $chr <= 0x7A) {
302
  public function testCssEscapingEscapesOwaspRecommendedRanges()
303
  {
304
  // CSS has no exceptions to escaping ranges
305
+ for ($chr = 0; $chr < 0xFF; ++$chr) {
306
  if ($chr >= 0x30 && $chr <= 0x39
307
  || $chr >= 0x41 && $chr <= 0x5A
308
  || $chr >= 0x61 && $chr <= 0x7A) {