Timber - Version 1.0.3

Version Description

Download this release

Release Info

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

Code changes from version 0.22.6 to 1.0.3

Files changed (68) hide show
  1. README.md +29 -21
  2. init.php +8 -0
  3. lib/Admin.php +70 -0
  4. lib/{timber-archives.php → Archives.php} +34 -25
  5. lib/{cache → Cache}/KeyGenerator.php +0 -0
  6. lib/{cache → Cache}/TimberKeyGeneratorInterface.php +0 -0
  7. lib/{cache → Cache}/WPObjectCacheAdapter.php +6 -6
  8. lib/{timber-comment.php → Comment.php} +56 -49
  9. lib/Core.php +118 -0
  10. lib/{timber-core-interface.php → CoreInterface.php} +3 -1
  11. lib/FunctionWrapper.php +98 -0
  12. lib/{timber-helper.php → Helper.php} +110 -239
  13. lib/{timber-image.php → Image.php} +76 -96
  14. lib/{image/timber-image-operation.php → Image/Operation.php} +13 -9
  15. lib/{image/timber-image-operation-letterbox.php → Image/Operation/Letterbox.php} +31 -25
  16. lib/{image/timber-image-operation-resize.php → Image/Operation/Resize.php} +44 -35
  17. lib/Image/Operation/Retina.php +80 -0
  18. lib/{image/timber-image-operation-tojpg.php → Image/Operation/ToJpg.php} +19 -15
  19. lib/{timber-image-helper.php → ImageHelper.php} +106 -94
  20. lib/Integrations.php +29 -0
  21. lib/Integrations/ACF.php +60 -0
  22. lib/Integrations/Command.php +40 -0
  23. lib/Integrations/Timber_WP_CLI_Command.php +62 -0
  24. lib/{timber-loader.php → Loader.php} +97 -95
  25. lib/{timber-menu.php → Menu.php} +43 -38
  26. lib/{timber-menu-item.php → MenuItem.php} +59 -60
  27. lib/{timber-post.php → Post.php} +781 -680
  28. lib/PostGetter.php +131 -0
  29. lib/PostsCollection.php +100 -0
  30. lib/QueryIterator.php +162 -0
  31. lib/{timber-request.php → Request.php} +8 -1
  32. lib/{timber-site.php → Site.php} +60 -51
  33. lib/{timber-term.php → Term.php} +41 -55
  34. lib/{timber-term-getter.php → TermGetter.php} +22 -17
  35. lib/{timber-theme.php → Theme.php} +14 -8
  36. lib/{timber.php → Timber.php} +142 -138
  37. lib/Twig.php +324 -0
  38. lib/{timber-url-helper.php → URLHelper.php} +60 -89
  39. lib/{timber-user.php → User.php} +26 -43
  40. lib/image/timber-image-operation-retina.php +0 -74
  41. lib/integrations/acf-timber.php +0 -58
  42. lib/integrations/timber-command.php +0 -36
  43. lib/integrations/wpcli-timber.php +0 -57
  44. lib/timber-admin.php +0 -67
  45. lib/timber-core.php +0 -117
  46. lib/timber-function-wrapper.php +0 -92
  47. lib/timber-integrations.php +0 -24
  48. lib/timber-page.php +0 -9
  49. lib/timber-post-getter.php +0 -104
  50. lib/timber-posts-collection.php +0 -97
  51. lib/timber-query-iterator.php +0 -156
  52. lib/timber-routes.php +0 -33
  53. lib/timber-twig.php +0 -347
  54. readme.txt +43 -12
  55. timber-starter-theme/single.php +0 -1
  56. timber-starter-theme/templates/base.twig +1 -1
  57. timber-starter-theme/templates/single.twig +3 -1
  58. timber.php +7 -14
  59. vendor/autoload.php +1 -1
  60. vendor/composer/autoload_classmap.php +0 -40
  61. vendor/composer/autoload_files.php +10 -0
  62. vendor/composer/autoload_psr4.php +1 -0
  63. vendor/composer/autoload_real.php +17 -3
  64. vendor/composer/installed.json +6 -6
  65. vendor/composer/installers/README.md +1 -0
  66. vendor/composer/installers/src/Composer/Installers/Installer.php +1 -0
  67. vendor/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php +16 -0
  68. vendor/composer/installers/tests/Composer/Installers/Test/InstallerTest.php +2 -0
README.md CHANGED
@@ -1,17 +1,17 @@
1
  <div style="text-align:center">
2
- <a href="http://jarednova.github.com/timber"><img src="http://i.imgur.com/oM1AHrz.jpg" style="display:block; margin:auto; width:100%; max-width:100%"/></a>
3
  <div>
4
- By Jared Novack (<a href="http://twitter.com/jarednova">@JaredNova</a>) and <a href="http://upstatement.com">Upstatement</a> (<a href="http://twitter.com/upstatement">@Upstatement</a>)</div>
5
  </div>
6
 
7
- [![Build Status](https://img.shields.io/travis/jarednova/timber/master.svg?style=flat-square)](https://travis-ci.org/jarednova/timber)
8
- [![Coverage Status](https://img.shields.io/coveralls/jarednova/timber.svg?style=flat-square)](https://coveralls.io/r/jarednova/timber?branch=master)
9
  [![Dependency Status](https://img.shields.io/versioneye/d/ruby/rails.svg?style=flat-square)](https://www.versioneye.com/user/projects/54e3c717d1ec5734f4000099)
10
- [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/jarednova/timber.svg?style=flat-square)](https://scrutinizer-ci.com/g/jarednova/timber/?branch=master)
11
- [![Latest Stable Version](https://img.shields.io/packagist/v/jarednova/timber.svg?style=flat-square)](https://packagist.org/packages/jarednova/timber)
12
  [![WordPress Download Count](https://img.shields.io/wordpress/plugin/dt/timber-library.svg?style=flat-square)](https://wordpress.org/plugins/timber-library/)
13
- [![HHVM Status](https://img.shields.io/hhvm/jarednova/timber.svg?style=flat-square)](http://hhvm.h4cc.de/package/jarednova/timber)
14
- [![Join the chat at https://gitter.im/jarednova/timber](https://img.shields.io/gitter/room/jarednova/timber.svg?style=flat-square)](https://gitter.im/jarednova/timber?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
15
 
16
 
17
  ### Because WordPress is awesome, but the_loop isn't
@@ -35,23 +35,28 @@ This is what Timber's `.twig` files look like:
35
  Once Timber is installed and activated in your plugin directory, it gives any WordPress theme the ability to take advantage of the power of Twig and other Timber features.
36
 
37
  ### Looking for docs?
38
- * [Timber Documentation](https://github.com/jarednova/timber/wiki/)
39
  * [Twig Reference](http://twig.sensiolabs.org/doc/templates.html)
40
- * [Video Tutorials](https://github.com/jarednova/timber/wiki/Video-Tutorials)
41
- * [Overview / Getting Started Guide](https://github.com/jarednova/timber/wiki/getting-started)
42
 
43
  * * *
44
 
45
  ### Installation
46
 
47
- The GitHub version of Timber requires [Composer](https://getcomposer.org/download/). If you'd prefer one-click installation, you should use the [WordPress.org](http://wordpress.org/plugins/timber-library/) version.
48
 
49
  ```shell
50
- composer create-project --no-dev jarednova/timber ~/MYSITE/wp-content/plugins/timber
51
  ```
52
 
53
- Once this is complete, activate Timber your WordPress admin. If you're looking for a 'blank' theme to start developing with, download the [timber-starter-theme](https://github.com/upstatement/timber-starter-theme) into your themes directory.
54
 
 
 
 
 
 
55
  * * *
56
 
57
  ### Mission Statement
@@ -67,27 +72,30 @@ Nothing. Timber is meant for you to build a theme on. Like the [Starkers](https:
67
  Timber is great for any WordPress developer who cares about writing good, maintainable code. It helps teams of designers and developers working together. At [Upstatement](http://upstatement.com) we made Timber because while our entire team needs to participate in building WordPress sites, not everyone knows the ins-and-outs of the_loop(), codex and PHP (nor should they). With Timber your best WordPress dev can focus on building the .php files with requests from WordPress and pass the data into .twig files. Once there, designers can easily mark-up data and build out a site's look-and-feel.
68
 
69
  #### Related Projects
70
- * [**Timber Starter Theme**](https://github.com/upstatement/timber-starter-theme) The "_s" of Timber to give you an easy start to the most basic theme you can build upon and customize.
71
- * [**Timber Debug Bar**](https://github.com/upstatement/debug-bar-timber) Adds a debug bar panel that will show you which template is in-use and the data sent to your twig file.
72
  * [**TimberPhoton**](https://github.com/slimndap/TimberPhoton) Plug-in to use JetPack's free Photon image manipulation and CDN with Timber.
73
- * [**Timber Sugar**](https://github.com/Upstatement/timber-sugar) A catch-all for goodies to use w Timber.
 
 
74
  * [**Twig**](https://github.com/fabpot/Twig) The template language used by Timber.
75
 
76
  #### Projects that use Timber
77
  * [**Gantry5**](https://wordpress.org/plugins/gantry5/) a framework for theme development
78
 
79
  #### Helpful Links
80
- * [**CSS Tricks**](https://css-tricks.com/timber-and-twig-reignited-my-love-for-wordpress/) introduction to Timber
81
- * [**Twig for Timber Cheatsheet**](http://notlaura.com/the-twig-for-timber-cheatsheet/) by @laras126
 
82
 
83
  #### Support
84
  Please post on [StackOverflow under the "Timber" tag](http://stackoverflow.com/questions/tagged/timber). Please use GitHub issues only for specific bugs, feature requests and other types of issues.
85
 
86
  #### Should I use it?
87
- 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.
88
 
89
  #### Contributing
90
- Read the [contributor guidelines](https://github.com/jarednova/timber/wiki#contributing) in the wiki.
91
 
92
 
93
 
1
  <div style="text-align:center">
2
+ <a href="http://timber.github.io/timber"><img src="http://i.imgur.com/oM1AHrz.jpg" style="display:block; margin:auto; width:100%; max-width:100%"/></a>
3
  <div>
4
+ By Jared Novack (<a href="https://twitter.com/jarednova">@JaredNova</a>) and <a href="http://upstatement.com">Upstatement</a> (<a href="https://twitter.com/upstatement">@Upstatement</a>)</div>
5
  </div>
6
 
7
+ [![Build Status](https://img.shields.io/travis/timber/timber/master.svg?style=flat-square)](https://travis-ci.org/timber/timber)
8
+ [![Coverage Status](https://img.shields.io/coveralls/timber/timber.svg?style=flat-square)](https://coveralls.io/r/timber/timber?branch=master)
9
  [![Dependency Status](https://img.shields.io/versioneye/d/ruby/rails.svg?style=flat-square)](https://www.versioneye.com/user/projects/54e3c717d1ec5734f4000099)
10
+ [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/timber/timber.svg?style=flat-square)](https://scrutinizer-ci.com/g/timber/timber/?branch=master)
11
+ [![Latest Stable Version](https://img.shields.io/packagist/v/timber/timber.svg?style=flat-square)](https://packagist.org/packages/timber/timber)
12
  [![WordPress Download Count](https://img.shields.io/wordpress/plugin/dt/timber-library.svg?style=flat-square)](https://wordpress.org/plugins/timber-library/)
13
+ [![HHVM Status](https://img.shields.io/hhvm/timber/timber.svg?style=flat-square)](http://hhvm.h4cc.de/package/timber/timber)
14
+ [![Join the chat at https://gitter.im/timber/timber](https://img.shields.io/gitter/room/timber/timber.svg?style=flat-square)](https://gitter.im/timber/timber?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
15
 
16
 
17
  ### Because WordPress is awesome, but the_loop isn't
35
  Once Timber is installed and activated in your plugin directory, it gives any WordPress theme the ability to take advantage of the power of Twig and other Timber features.
36
 
37
  ### Looking for docs?
38
+ * [Timber Documentation](https://github.com/timber/timber/wiki)
39
  * [Twig Reference](http://twig.sensiolabs.org/doc/templates.html)
40
+ * [Video Tutorials](https://github.com/timber/timber/wiki/Video-Tutorials)
41
+ * [Overview / Getting Started Guide](https://github.com/timber/timber/wiki/getting-started)
42
 
43
  * * *
44
 
45
  ### Installation
46
 
47
+ The GitHub version of Timber requires [Composer](https://getcomposer.org/download/). If you'd prefer one-click installation, you should use the [WordPress.org](https://wordpress.org/plugins/timber-library/) version.
48
 
49
  ```shell
50
+ composer require timber/timber
51
  ```
52
 
53
+ If your theme is not setup to pull in Composer's autoload file, you will need to
54
 
55
+ ```php
56
+ require_once(__DIR__ . '/vendor/autoload.php');
57
+ ```
58
+
59
+ At the top of your `functions.php` file.
60
  * * *
61
 
62
  ### Mission Statement
72
  Timber is great for any WordPress developer who cares about writing good, maintainable code. It helps teams of designers and developers working together. At [Upstatement](http://upstatement.com) we made Timber because while our entire team needs to participate in building WordPress sites, not everyone knows the ins-and-outs of the_loop(), codex and PHP (nor should they). With Timber your best WordPress dev can focus on building the .php files with requests from WordPress and pass the data into .twig files. Once there, designers can easily mark-up data and build out a site's look-and-feel.
73
 
74
  #### Related Projects
75
+ * [**Timber Starter Theme**](https://github.com/timber/starter-theme) The "_s" of Timber to give you an easy start to the most basic theme you can build upon and customize.
76
+ * [**Timber Debug Bar**](https://github.com/timber/debug-bar-timber) Adds a debug bar panel that will show you which template is in-use and the data sent to your twig file.
77
  * [**TimberPhoton**](https://github.com/slimndap/TimberPhoton) Plug-in to use JetPack's free Photon image manipulation and CDN with Timber.
78
+ * [**Timber CLI**](https://github.com/nclud/wp-timber-cli) A CLI for Timber.
79
+ * [**Timber Sugar**](https://github.com/timber/sugar) A catch-all for goodies to use w Timber.
80
+ * [**Timmy**](https://github.com/MINDKomm/Timmy) Advanced image manipulation for Timber.
81
  * [**Twig**](https://github.com/fabpot/Twig) The template language used by Timber.
82
 
83
  #### Projects that use Timber
84
  * [**Gantry5**](https://wordpress.org/plugins/gantry5/) a framework for theme development
85
 
86
  #### Helpful Links
87
+ * [**CSS Tricks**](https://css-tricks.com/timber-and-twig-reignited-my-love-for-wordpress/) introduction to Timber by [@tjFogarty](https://github.com/tjFogarty)
88
+ * [**Twig for Timber Cheatsheet**](http://notlaura.com/the-twig-for-timber-cheatsheet/) by [@laras126](https://github.com/laras126)
89
+ * [**TutsPlus**](http://code.tutsplus.com/articles/kick-start-wordpress-development-with-twig-introduction--cms-24781) A guide to getting started by [@ahmadawais](https://github.com/ahmadawais)
90
 
91
  #### Support
92
  Please post on [StackOverflow under the "Timber" tag](http://stackoverflow.com/questions/tagged/timber). Please use GitHub issues only for specific bugs, feature requests and other types of issues.
93
 
94
  #### Should I use it?
95
+ 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://timber.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.
96
 
97
  #### Contributing
98
+ Read the [contributor guidelines](https://github.com/timber/timber/wiki#contributing) in the wiki.
99
 
100
 
101
 
init.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ use Timber\Timber;
4
+
5
+ $timber = new Timber();
6
+ Timber::$dirname = 'views';
7
+
8
+
lib/Admin.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber;
4
+
5
+ class Admin {
6
+
7
+ public static function init() {
8
+ $filter = add_filter('plugin_row_meta', array( __CLASS__, 'meta_links' ), 10, 2);
9
+ $action = add_action('in_plugin_update_message-timber-library/timber.php', array( __CLASS__, 'in_plugin_update_message'), 10, 2);
10
+ $action = add_action('in_plugin_update_message-timber/timber.php', array( __CLASS__, 'in_plugin_update_message'), 10, 2);
11
+ if ($filter && $action) {
12
+ return true;
13
+ }
14
+ }
15
+
16
+ /**
17
+ * @param array $links
18
+ * @param string $file
19
+ * @return array
20
+ */
21
+ public static function meta_links( $links, $file ) {
22
+ if ( strstr($file, '/timber.php') ) {
23
+ unset($links[2]);
24
+ $links[] = '<a href="/wp-admin/plugin-install.php?tab=plugin-information&amp;plugin=timber-library&amp;TB_iframe=true&amp;width=600&amp;height=550" class="thickbox" aria-label="More information about Timber" data-title="Timber">View details</a>';
25
+ $links[] = '<a href="http://upstatement.com/timber" target="_blank">Homepage</a>';
26
+ $links[] = '<a href="https://github.com/timber/timber/wiki" target="_blank">Documentation</a>';
27
+ $links[] = '<a href="https://github.com/timber/timber/wiki/getting-started" target="_blank">Starter Guide</a>';
28
+ $links[] = '<p><a href="https://downloads.wordpress.org/plugin/timber-library.0.22.6.zip">Site not working on 1.0? Downgrade to version 0.22.6</a></p>';
29
+ return $links;
30
+ }
31
+ return $links;
32
+ }
33
+
34
+ /**
35
+ * in_plugin_update_message
36
+ *
37
+ * Displays an update message for plugin list screens.
38
+ * Shows only the version updates from the current until the newest version
39
+ *
40
+ * @type function
41
+ * @date 4/22/16
42
+ *
43
+ * @param {array} $plugin_data
44
+ * @param {object} $r
45
+ */
46
+ public static function in_plugin_update_message( $plugin_data, $r ) {
47
+ $m = '';
48
+
49
+ if ( version_compare("1.0.0", $plugin_data->new_version) <= 0 ) {
50
+ //a version of 1.0.0 or greater is availalbe
51
+ $m .= '<p><b>Warning:</b> Timber 1.0 removed a number of features and methods. Before upgrading please test your theme on a local or staging site to ensure that your theme will work with the newest version.</p>
52
+
53
+ <p><strong>Is your theme in active development?</strong> That is, is someone actively in PHP files writing new code? If you answered "no", then <i>do not upgrade</i>. You will not benefit from Timber 1.0</p>';
54
+
55
+ $m .= '<p>Read the <strong><a href="https://github.com/timber/timber/wiki/1.0-Upgrade-Guide">Upgrade Guide</a></strong> for more information</p>';
56
+
57
+ $m .= "<p>You can also <b><a href='https://downloads.wordpress.org/plugin/timber-library.0.22.6.zip'>upgrade to version 0.22.6</a></b> if you want to upgrade, but are unsure if you're ready for 1.0";
58
+
59
+ }
60
+
61
+ if ( version_compare("1.0.0", $plugin_data->Version) <= 0 ) {
62
+ $m .= "<p>Are you seeing errors since upgrading to 1.0? Download <b><a href='https://downloads.wordpress.org/plugin/timber-library.0.22.6.zip'>Version 0.22.6</a></b> to bring things back to stability.";
63
+ }
64
+
65
+ // show message
66
+ echo '<br />'.sprintf($m);
67
+
68
+ }
69
+
70
+ }
lib/{timber-archives.php → Archives.php} RENAMED
@@ -1,6 +1,13 @@
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * The TimberArchives class is used to generate a menu based on the date archives of your posts. The [Nieman Foundation News site](http://nieman.harvard.edu/news/) has an example of how the output can be used in a real site ([screenshot](https://cloud.githubusercontent.com/assets/1298086/9610076/3cdca596-50a5-11e5-82fd-acb74c09c482.png)).
 
4
  * @example
5
  * ```php
6
  * $context['archives'] = new TimberArchives( $args );
@@ -30,8 +37,8 @@
30
  * </ul>
31
  * ```
32
  */
33
- class TimberArchives extends TimberCore {
34
-
35
  public $base = '';
36
  /**
37
  * @api
@@ -55,6 +62,8 @@ class TimberArchives extends TimberCore {
55
  * @param string $base any additional paths that need to be prepended to the URLs that are generated, for example: "tags"
56
  */
57
  function __construct( $args = null, $base = '' ) {
 
 
58
  $this->init($args, $base);
59
  }
60
 
@@ -77,7 +86,7 @@ class TimberArchives extends TimberCore {
77
  protected function get_archives_link( $url, $text ) {
78
  $ret = array();
79
  $ret['text'] = $ret['title'] = $ret['name'] = wptexturize($text);
80
- $ret['url'] = $ret['link'] = esc_url(TimberURLHelper::prepend_to_url($url, $this->base));
81
  return $ret;
82
  }
83
 
@@ -97,13 +106,13 @@ class TimberArchives extends TimberCore {
97
  $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";
98
  $key = md5($query);
99
  $key = "wp_get_archives:$key:$last_changed";
100
- if (!$results = wp_cache_get($key, 'posts')) {
101
  $results = $wpdb->get_results($query);
102
  wp_cache_set($key, $results, 'posts');
103
  }
104
- if ($results) {
105
- foreach ( (array)$results as $result ) {
106
- $url = get_year_link( $result->year );
107
  $text = sprintf('%d', $result->year);
108
  $output[] = $this->get_archives_link($url, $text);
109
  }
@@ -137,28 +146,28 @@ class TimberArchives extends TimberCore {
137
  . "ORDER BY post_date $order $limit";
138
  $key = md5($query);
139
  $key = "wp_get_archives:$key:$last_changed";
140
- if (!$results = wp_cache_get($key, 'posts')) {
141
  $results = $wpdb->get_results($query);
142
  wp_cache_set($key, $results, 'posts');
143
  }
144
- if ($results) {
145
- foreach ((array)$results as $result) {
146
  $url = get_month_link($result->year, $result->month);
147
- if ($show_year && !$nested) {
148
  $text = sprintf(__('%1$s %2$d'), $wp_locale->get_month($result->month), $result->year);
149
  } else {
150
  $text = sprintf(__('%1$s'), $wp_locale->get_month($result->month));
151
  }
152
- if ($nested) {
153
  $output[$result->year][] = $this->get_archives_link($url, $text);
154
  } else {
155
  $output[] = $this->get_archives_link($url, $text);
156
  }
157
  }
158
  }
159
- if ($nested) {
160
  $out2 = array();
161
- foreach ($output as $year => $months) {
162
  $out2[] = array('name' => $year, 'children' => $months);
163
  }
164
  return $out2;
@@ -199,7 +208,7 @@ class TimberArchives extends TimberCore {
199
 
200
  if ( !empty($args['limit']) ) {
201
  $limit = absint($limit);
202
- $limit = ' LIMIT ' . $limit;
203
  }
204
 
205
  $order = strtoupper($order);
@@ -220,7 +229,7 @@ class TimberArchives extends TimberCore {
220
  $archive_week_start_date_format = 'Y/m/d';
221
  $archive_week_end_date_format = 'Y/m/d';
222
 
223
- if (!$archive_date_format_over_ride) {
224
  $archive_day_date_format = get_option('date_format');
225
  $archive_week_start_date_format = get_option('date_format');
226
  $archive_week_end_date_format = get_option('date_format');
@@ -232,7 +241,7 @@ class TimberArchives extends TimberCore {
232
 
233
  $output = array();
234
  $last_changed = wp_cache_get('last_changed', 'posts');
235
- if (!$last_changed) {
236
  $last_changed = microtime();
237
  wp_cache_set('last_changed', $last_changed, 'posts');
238
  }
@@ -246,14 +255,14 @@ class TimberArchives extends TimberCore {
246
  $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";
247
  $key = md5($query);
248
  $key = "wp_get_archives:$key:$last_changed";
249
- if (!$results = wp_cache_get($key, 'posts')) {
250
  $results = $wpdb->get_results($query);
251
  $cache = array();
252
  $cache[$key] = $results;
253
  wp_cache_set($key, $results, 'posts');
254
  }
255
  if ( $results ) {
256
- foreach ( (array)$results as $result ) {
257
  $url = get_day_link($result->year, $result->month, $result->dayofmonth);
258
  $date = sprintf('%1$d-%2$02d-%3$02d 00:00:00', $result->year, $result->month, $result->dayofmonth);
259
  $text = mysql2date($archive_day_date_format, $date);
@@ -266,13 +275,13 @@ class TimberArchives extends TimberCore {
266
  . "count( `ID` ) AS `posts` FROM `$wpdb->posts` $join $where GROUP BY $week, YEAR( `post_date` ) ORDER BY `post_date` $order $limit";
267
  $key = md5($query);
268
  $key = "wp_get_archives:$key:$last_changed";
269
- if (!$results = wp_cache_get($key, 'posts')) {
270
  $results = $wpdb->get_results($query);
271
  wp_cache_set($key, $results, 'posts');
272
  }
273
  $arc_w_last = '';
274
  if ( $results ) {
275
- foreach ( (array)$results as $result ) {
276
  if ( $result->week != $arc_w_last ) {
277
  $arc_year = $result->yr;
278
  $arc_w_last = $result->week;
@@ -280,7 +289,7 @@ class TimberArchives extends TimberCore {
280
  $arc_week_start = date_i18n($archive_week_start_date_format, $arc_week['start']);
281
  $arc_week_end = date_i18n($archive_week_end_date_format, $arc_week['end']);
282
  $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);
283
- $text = $arc_week_start . $archive_week_separator . $arc_week_end;
284
  $output[] = $this->get_archives_link($url, $text);
285
  }
286
  }
@@ -295,10 +304,10 @@ class TimberArchives extends TimberCore {
295
  wp_cache_set($key, $results, 'posts');
296
  }
297
  if ( $results ) {
298
- foreach ( (array)$results as $result ) {
299
- if ($result->post_date != '0000-00-00 00:00:00') {
300
  $url = get_permalink($result);
301
- if ($result->post_title) {
302
  /** This filter is documented in wp-includes/post-template.php */
303
  $text = strip_tags(apply_filters('the_title', $result->post_title, $result->ID));
304
  } else {
1
  <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\URLHelper;
7
+
8
  /**
9
  * The TimberArchives class is used to generate a menu based on the date archives of your posts. The [Nieman Foundation News site](http://nieman.harvard.edu/news/) has an example of how the output can be used in a real site ([screenshot](https://cloud.githubusercontent.com/assets/1298086/9610076/3cdca596-50a5-11e5-82fd-acb74c09c482.png)).
10
+ *
11
  * @example
12
  * ```php
13
  * $context['archives'] = new TimberArchives( $args );
37
  * </ul>
38
  * ```
39
  */
40
+ class Archives extends Core {
41
+
42
  public $base = '';
43
  /**
44
  * @api
62
  * @param string $base any additional paths that need to be prepended to the URLs that are generated, for example: "tags"
63
  */
64
  function __construct( $args = null, $base = '' ) {
65
+
66
+
67
  $this->init($args, $base);
68
  }
69
 
86
  protected function get_archives_link( $url, $text ) {
87
  $ret = array();
88
  $ret['text'] = $ret['title'] = $ret['name'] = wptexturize($text);
89
+ $ret['url'] = $ret['link'] = esc_url(URLHelper::prepend_to_url($url, $this->base));
90
  return $ret;
91
  }
92
 
106
  $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";
107
  $key = md5($query);
108
  $key = "wp_get_archives:$key:$last_changed";
109
+ if ( !$results = wp_cache_get($key, 'posts') ) {
110
  $results = $wpdb->get_results($query);
111
  wp_cache_set($key, $results, 'posts');
112
  }
113
+ if ( $results ) {
114
+ foreach ( (array) $results as $result ) {
115
+ $url = get_year_link($result->year);
116
  $text = sprintf('%d', $result->year);
117
  $output[] = $this->get_archives_link($url, $text);
118
  }
146
  . "ORDER BY post_date $order $limit";
147
  $key = md5($query);
148
  $key = "wp_get_archives:$key:$last_changed";
149
+ if ( !$results = wp_cache_get($key, 'posts') ) {
150
  $results = $wpdb->get_results($query);
151
  wp_cache_set($key, $results, 'posts');
152
  }
153
+ if ( $results ) {
154
+ foreach ( (array) $results as $result ) {
155
  $url = get_month_link($result->year, $result->month);
156
+ if ( $show_year && !$nested ) {
157
  $text = sprintf(__('%1$s %2$d'), $wp_locale->get_month($result->month), $result->year);
158
  } else {
159
  $text = sprintf(__('%1$s'), $wp_locale->get_month($result->month));
160
  }
161
+ if ( $nested ) {
162
  $output[$result->year][] = $this->get_archives_link($url, $text);
163
  } else {
164
  $output[] = $this->get_archives_link($url, $text);
165
  }
166
  }
167
  }
168
+ if ( $nested ) {
169
  $out2 = array();
170
+ foreach ( $output as $year => $months ) {
171
  $out2[] = array('name' => $year, 'children' => $months);
172
  }
173
  return $out2;
208
 
209
  if ( !empty($args['limit']) ) {
210
  $limit = absint($limit);
211
+ $limit = ' LIMIT '.$limit;
212
  }
213
 
214
  $order = strtoupper($order);
229
  $archive_week_start_date_format = 'Y/m/d';
230
  $archive_week_end_date_format = 'Y/m/d';
231
 
232
+ if ( !$archive_date_format_over_ride ) {
233
  $archive_day_date_format = get_option('date_format');
234
  $archive_week_start_date_format = get_option('date_format');
235
  $archive_week_end_date_format = get_option('date_format');
241
 
242
  $output = array();
243
  $last_changed = wp_cache_get('last_changed', 'posts');
244
+ if ( !$last_changed ) {
245
  $last_changed = microtime();
246
  wp_cache_set('last_changed', $last_changed, 'posts');
247
  }
255
  $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";
256
  $key = md5($query);
257
  $key = "wp_get_archives:$key:$last_changed";
258
+ if ( !$results = wp_cache_get($key, 'posts') ) {
259
  $results = $wpdb->get_results($query);
260
  $cache = array();
261
  $cache[$key] = $results;
262
  wp_cache_set($key, $results, 'posts');
263
  }
264
  if ( $results ) {
265
+ foreach ( (array) $results as $result ) {
266
  $url = get_day_link($result->year, $result->month, $result->dayofmonth);
267
  $date = sprintf('%1$d-%2$02d-%3$02d 00:00:00', $result->year, $result->month, $result->dayofmonth);
268
  $text = mysql2date($archive_day_date_format, $date);
275
  . "count( `ID` ) AS `posts` FROM `$wpdb->posts` $join $where GROUP BY $week, YEAR( `post_date` ) ORDER BY `post_date` $order $limit";
276
  $key = md5($query);
277
  $key = "wp_get_archives:$key:$last_changed";
278
+ if ( !$results = wp_cache_get($key, 'posts') ) {
279
  $results = $wpdb->get_results($query);
280
  wp_cache_set($key, $results, 'posts');
281
  }
282
  $arc_w_last = '';
283
  if ( $results ) {
284
+ foreach ( (array) $results as $result ) {
285
  if ( $result->week != $arc_w_last ) {
286
  $arc_year = $result->yr;
287
  $arc_w_last = $result->week;
289
  $arc_week_start = date_i18n($archive_week_start_date_format, $arc_week['start']);
290
  $arc_week_end = date_i18n($archive_week_end_date_format, $arc_week['end']);
291
  $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);
292
+ $text = $arc_week_start.$archive_week_separator.$arc_week_end;
293
  $output[] = $this->get_archives_link($url, $text);
294
  }
295
  }
304
  wp_cache_set($key, $results, 'posts');
305
  }
306
  if ( $results ) {
307
+ foreach ( (array) $results as $result ) {
308
+ if ( $result->post_date != '0000-00-00 00:00:00' ) {
309
  $url = get_permalink($result);
310
+ if ( $result->post_title ) {
311
  /** This filter is documented in wp-includes/post-template.php */
312
  $text = strip_tags(apply_filters('the_title', $result->post_title, $result->ID));
313
  } else {
lib/{cache → Cache}/KeyGenerator.php RENAMED
File without changes
lib/{cache → Cache}/TimberKeyGeneratorInterface.php RENAMED
File without changes
lib/{cache → Cache}/WPObjectCacheAdapter.php RENAMED
@@ -1,7 +1,7 @@
1
  <?php namespace Timber\Cache;
2
 
3
  use Asm89\Twig\CacheExtension\CacheProviderInterface;
4
- use TimberLoader;
5
 
6
  class WPObjectCacheAdapter implements CacheProviderInterface {
7
 
@@ -12,17 +12,17 @@ class WPObjectCacheAdapter implements CacheProviderInterface {
12
  */
13
  private $timberloader;
14
 
15
- public function __construct(TimberLoader $timberloader, $cache_group = 'timber') {
16
  $this->cache_group = $cache_group;
17
  $this->timberloader = $timberloader;
18
  }
19
 
20
- public function fetch($key) {
21
- return $this->timberloader->get_cache($key, $this->cache_group, TimberLoader::CACHE_USE_DEFAULT);
22
  }
23
 
24
- public function save($key, $value, $expire = 0) {
25
- return $this->timberloader->set_cache($key, $value, $this->cache_group, $expire, TimberLoader::CACHE_USE_DEFAULT);
26
  }
27
 
28
  }
1
  <?php namespace Timber\Cache;
2
 
3
  use Asm89\Twig\CacheExtension\CacheProviderInterface;
4
+ use Timber\Loader;
5
 
6
  class WPObjectCacheAdapter implements CacheProviderInterface {
7
 
12
  */
13
  private $timberloader;
14
 
15
+ public function __construct( Loader $timberloader, $cache_group = 'timber' ) {
16
  $this->cache_group = $cache_group;
17
  $this->timberloader = $timberloader;
18
  }
19
 
20
+ public function fetch( $key ) {
21
+ return $this->timberloader->get_cache($key, $this->cache_group, Loader::CACHE_USE_DEFAULT);
22
  }
23
 
24
+ public function save( $key, $value, $expire = 0 ) {
25
+ return $this->timberloader->set_cache($key, $value, $this->cache_group, $expire, Loader::CACHE_USE_DEFAULT);
26
  }
27
 
28
  }
lib/{timber-comment.php → Comment.php} RENAMED
@@ -1,5 +1,11 @@
1
  <?php
2
 
 
 
 
 
 
 
3
  /**
4
  * The TimberComment class is used to view the output of comments. 99% of the time this will be in the context of the comments on a post. However you can also fetch a comment directly using its comment ID.
5
  * @example
@@ -19,9 +25,9 @@
19
  * <p class="comment-attribution">- Sullivan Ballou</p>
20
  * ```
21
  */
22
- class TimberComment extends TimberCore implements TimberCoreInterface {
23
 
24
- public $PostClass = 'TimberPost';
25
  public $object_type = 'comment';
26
 
27
  public static $representation = 'comment';
@@ -40,7 +46,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
40
  /**
41
  * @param int $cid
42
  */
43
- function __construct($cid) {
44
  $this->init($cid);
45
  }
46
 
@@ -52,9 +58,9 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
52
  * @internal
53
  * @param integer $cid
54
  */
55
- function init($cid) {
56
  $comment_data = $cid;
57
- if (is_integer($cid)) {
58
  $comment_data = get_comment($cid);
59
  }
60
  $this->import($comment_data);
@@ -83,14 +89,14 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
83
  * <li>Rebecca Pearl, who is a author</li>
84
  * </ol>
85
  * ```
86
- * @return TimberUser
87
  */
88
  public function author() {
89
- if ($this->user_id) {
90
- return new TimberUser($this->user_id);
91
  } else {
92
- $author = new TimberUser(0);
93
- if (isset($this->comment_author) && $this->comment_author) {
94
  $author->name = $this->comment_author;
95
  } else {
96
  $author->name = 'Anonymous';
@@ -113,22 +119,22 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
113
  * @param string $default
114
  * @return bool|mixed|string
115
  */
116
- public function avatar($size = 92, $default = '') {
117
- if (!get_option('show_avatars')) {
118
  return false;
119
  }
120
- if (!is_numeric($size)) {
121
  $size = '92';
122
  }
123
 
124
  $email = $this->avatar_email();
125
  $email_hash = '';
126
- if (!empty($email)) {
127
  $email_hash = md5(strtolower(trim($email)));
128
  }
129
  $host = $this->avatar_host($email_hash);
130
  $default = $this->avatar_default($default, $email, $size, $host);
131
- if (!empty($email)) {
132
  $avatar = $this->avatar_out($default, $host, $email_hash, $size);
133
  } else {
134
  $avatar = $default;
@@ -181,7 +187,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
181
  */
182
  public function date( $date_format = '' ) {
183
  $df = $date_format ? $date_format : get_option('date_format');
184
- $the_date = (string)mysql2date($df, $this->comment_date);
185
  return apply_filters('get_comment_date ', $the_date, $df);
186
  }
187
 
@@ -206,7 +212,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
206
  */
207
  public function time( $time_format = '' ) {
208
  $tf = $time_format ? $time_format : get_option('time_format');
209
- $the_time = (string)mysql2date($tf, $this->comment_date);
210
  return apply_filters('get_comment_time', $the_time, $tf);
211
  }
212
 
@@ -214,7 +220,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
214
  * @param string $field_name
215
  * @return mixed
216
  */
217
- public function meta($field_name) {
218
  return $this->get_meta_field($field_name);
219
  }
220
 
@@ -231,15 +237,15 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
231
  * @param int $comment_id
232
  * @return mixed
233
  */
234
- protected function get_meta_fields($comment_id = null) {
235
- if ($comment_id === null) {
236
  $comment_id = $this->ID;
237
  }
238
  //Could not find a WP function to fetch all comment meta data, so I made one.
239
  apply_filters('timber_comment_get_meta_pre', array(), $comment_id);
240
  $comment_metas = get_comment_meta($comment_id);
241
- foreach ($comment_metas as &$cm) {
242
- if (is_array($cm) && count($cm) == 1) {
243
  $cm = $cm[0];
244
  }
245
  }
@@ -248,13 +254,14 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
248
  }
249
 
250
  /**
 
251
  * @internal
252
  * @param string $field_name
253
  * @return mixed
254
  */
255
- protected function get_meta_field($field_name) {
256
  $value = apply_filters('timber_comment_get_meta_field_pre', null, $this->ID, $field_name, $this);
257
- if ($value === null) {
258
  $value = get_comment_meta($this->ID, $field_name, true);
259
  }
260
  $value = apply_filters('timber_comment_get_meta_field', $value, $this->ID, $field_name, $this);
@@ -269,7 +276,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
269
  */
270
  public function reply_link( $reply_text = 'Reply' ) {
271
  if ( is_singular() && comments_open() && get_option('thread_comments') ) {
272
- wp_enqueue_script( 'comment-reply' );
273
  }
274
 
275
  // Get the comments depth option from the admin panel
@@ -284,7 +291,7 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
284
  'max_depth' => $max_depth,
285
  );
286
 
287
- return get_comment_reply_link( $args, $this->ID, $this->post_id );
288
  }
289
 
290
  /* AVATAR Stuff
@@ -295,9 +302,9 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
295
  * @return string
296
  */
297
  protected function avatar_email() {
298
- $id = (int)$this->user_id;
299
  $user = get_userdata($id);
300
- if ($user) {
301
  $email = $user->user_email;
302
  } else {
303
  $email = $this->comment_author_email;
@@ -310,11 +317,11 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
310
  * @param string $email_hash
311
  * @return string
312
  */
313
- protected function avatar_host($email_hash) {
314
- if (is_ssl()) {
315
  $host = 'https://secure.gravatar.com';
316
  } else {
317
- if (!empty($email_hash)) {
318
  $host = sprintf("http://%d.gravatar.com", (hexdec($email_hash[0]) % 2));
319
  } else {
320
  $host = 'http://0.gravatar.com';
@@ -332,30 +339,30 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
332
  * @param string $host
333
  * @return string
334
  */
335
- protected function avatar_default($default, $email, $size, $host) {
336
- if (substr($default, 0, 1) == '/') {
337
- $default = home_url() . $default;
338
  }
339
 
340
- if (empty($default)) {
341
  $avatar_default = get_option('avatar_default');
342
- if (empty($avatar_default)) {
343
  $default = 'mystery';
344
  } else {
345
  $default = $avatar_default;
346
  }
347
  }
348
- if ('mystery' == $default) {
349
- $default = $host . '/avatar/ad516503a11cd5ca435acc9bb6523536?s=' . $size;
350
  // ad516503a11cd5ca435acc9bb6523536 == md5('unknown@gravatar.com')
351
- } else if ('blank' == $default) {
352
  $default = $email ? 'blank' : includes_url('images/blank.gif');
353
- } else if (!empty($email) && 'gravatar_default' == $default) {
354
  $default = '';
355
- } else if ('gravatar_default' == $default) {
356
- $default = $host . '/avatar/?s=' . $size;
357
- } else if (empty($email) && !strstr($default, 'http://')) {
358
- $default = $host . '/avatar/?d=' . $default . '&amp;s=' . $size;
359
  }
360
  return $default;
361
  }
@@ -368,13 +375,13 @@ class TimberComment extends TimberCore implements TimberCoreInterface {
368
  * @param string $size
369
  * @return mixed
370
  */
371
- protected function avatar_out($default, $host, $email_hash, $size) {
372
- $out = $host . '/avatar/' . $email_hash . '?s=' . $size . '&amp;d=' . urlencode($default);
373
  $rating = get_option('avatar_rating');
374
- if (!empty($rating)) {
375
- $out .= '&amp;r=' . $rating;
376
  }
377
  return str_replace('&#038;', '&amp;', esc_url($out));
378
  }
379
 
380
- }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\User;
6
+ use Timber\Core;
7
+ use Timber\CoreInterface;
8
+
9
  /**
10
  * The TimberComment class is used to view the output of comments. 99% of the time this will be in the context of the comments on a post. However you can also fetch a comment directly using its comment ID.
11
  * @example
25
  * <p class="comment-attribution">- Sullivan Ballou</p>
26
  * ```
27
  */
28
+ class Comment extends Core implements CoreInterface {
29
 
30
+ public $PostClass = 'Post';
31
  public $object_type = 'comment';
32
 
33
  public static $representation = 'comment';
46
  /**
47
  * @param int $cid
48
  */
49
+ function __construct( $cid ) {
50
  $this->init($cid);
51
  }
52
 
58
  * @internal
59
  * @param integer $cid
60
  */
61
+ function init( $cid ) {
62
  $comment_data = $cid;
63
+ if ( is_integer($cid) ) {
64
  $comment_data = get_comment($cid);
65
  }
66
  $this->import($comment_data);
89
  * <li>Rebecca Pearl, who is a author</li>
90
  * </ol>
91
  * ```
92
+ * @return User
93
  */
94
  public function author() {
95
+ if ( $this->user_id ) {
96
+ return new User($this->user_id);
97
  } else {
98
+ $author = new User(0);
99
+ if ( isset($this->comment_author) && $this->comment_author ) {
100
  $author->name = $this->comment_author;
101
  } else {
102
  $author->name = 'Anonymous';
119
  * @param string $default
120
  * @return bool|mixed|string
121
  */
122
+ public function avatar( $size = 92, $default = '' ) {
123
+ if ( !get_option('show_avatars') ) {
124
  return false;
125
  }
126
+ if ( !is_numeric($size) ) {
127
  $size = '92';
128
  }
129
 
130
  $email = $this->avatar_email();
131
  $email_hash = '';
132
+ if ( !empty($email) ) {
133
  $email_hash = md5(strtolower(trim($email)));
134
  }
135
  $host = $this->avatar_host($email_hash);
136
  $default = $this->avatar_default($default, $email, $size, $host);
137
+ if ( !empty($email) ) {
138
  $avatar = $this->avatar_out($default, $host, $email_hash, $size);
139
  } else {
140
  $avatar = $default;
187
  */
188
  public function date( $date_format = '' ) {
189
  $df = $date_format ? $date_format : get_option('date_format');
190
+ $the_date = (string) mysql2date($df, $this->comment_date);
191
  return apply_filters('get_comment_date ', $the_date, $df);
192
  }
193
 
212
  */
213
  public function time( $time_format = '' ) {
214
  $tf = $time_format ? $time_format : get_option('time_format');
215
+ $the_time = (string) mysql2date($tf, $this->comment_date);
216
  return apply_filters('get_comment_time', $the_time, $tf);
217
  }
218
 
220
  * @param string $field_name
221
  * @return mixed
222
  */
223
+ public function meta( $field_name ) {
224
  return $this->get_meta_field($field_name);
225
  }
226
 
237
  * @param int $comment_id
238
  * @return mixed
239
  */
240
+ protected function get_meta_fields( $comment_id = null ) {
241
+ if ( $comment_id === null ) {
242
  $comment_id = $this->ID;
243
  }
244
  //Could not find a WP function to fetch all comment meta data, so I made one.
245
  apply_filters('timber_comment_get_meta_pre', array(), $comment_id);
246
  $comment_metas = get_comment_meta($comment_id);
247
+ foreach ( $comment_metas as &$cm ) {
248
+ if ( is_array($cm) && count($cm) == 1 ) {
249
  $cm = $cm[0];
250
  }
251
  }
254
  }
255
 
256
  /**
257
+ *
258
  * @internal
259
  * @param string $field_name
260
  * @return mixed
261
  */
262
+ protected function get_meta_field( $field_name ) {
263
  $value = apply_filters('timber_comment_get_meta_field_pre', null, $this->ID, $field_name, $this);
264
+ if ( $value === null ) {
265
  $value = get_comment_meta($this->ID, $field_name, true);
266
  }
267
  $value = apply_filters('timber_comment_get_meta_field', $value, $this->ID, $field_name, $this);
276
  */
277
  public function reply_link( $reply_text = 'Reply' ) {
278
  if ( is_singular() && comments_open() && get_option('thread_comments') ) {
279
+ wp_enqueue_script('comment-reply');
280
  }
281
 
282
  // Get the comments depth option from the admin panel
291
  'max_depth' => $max_depth,
292
  );
293
 
294
+ return get_comment_reply_link($args, $this->ID, $this->post_id);
295
  }
296
 
297
  /* AVATAR Stuff
302
  * @return string
303
  */
304
  protected function avatar_email() {
305
+ $id = (int) $this->user_id;
306
  $user = get_userdata($id);
307
+ if ( $user ) {
308
  $email = $user->user_email;
309
  } else {
310
  $email = $this->comment_author_email;
317
  * @param string $email_hash
318
  * @return string
319
  */
320
+ protected function avatar_host( $email_hash ) {
321
+ if ( is_ssl() ) {
322
  $host = 'https://secure.gravatar.com';
323
  } else {
324
+ if ( !empty($email_hash) ) {
325
  $host = sprintf("http://%d.gravatar.com", (hexdec($email_hash[0]) % 2));
326
  } else {
327
  $host = 'http://0.gravatar.com';
339
  * @param string $host
340
  * @return string
341
  */
342
+ protected function avatar_default( $default, $email, $size, $host ) {
343
+ if ( substr($default, 0, 1) == '/' ) {
344
+ $default = home_url().$default;
345
  }
346
 
347
+ if ( empty($default) ) {
348
  $avatar_default = get_option('avatar_default');
349
+ if ( empty($avatar_default) ) {
350
  $default = 'mystery';
351
  } else {
352
  $default = $avatar_default;
353
  }
354
  }
355
+ if ( 'mystery' == $default ) {
356
+ $default = $host.'/avatar/ad516503a11cd5ca435acc9bb6523536?s='.$size;
357
  // ad516503a11cd5ca435acc9bb6523536 == md5('unknown@gravatar.com')
358
+ } else if ( 'blank' == $default ) {
359
  $default = $email ? 'blank' : includes_url('images/blank.gif');
360
+ } else if ( !empty($email) && 'gravatar_default' == $default ) {
361
  $default = '';
362
+ } else if ( 'gravatar_default' == $default ) {
363
+ $default = $host.'/avatar/?s='.$size;
364
+ } else if ( empty($email) && !strstr($default, 'http://') ) {
365
+ $default = $host.'/avatar/?d='.$default.'&amp;s='.$size;
366
  }
367
  return $default;
368
  }
375
  * @param string $size
376
  * @return mixed
377
  */
378
+ protected function avatar_out( $default, $host, $email_hash, $size ) {
379
+ $out = $host.'/avatar/'.$email_hash.'?s='.$size.'&amp;d='.urlencode($default);
380
  $rating = get_option('avatar_rating');
381
+ if ( !empty($rating) ) {
382
+ $out .= '&amp;r='.$rating;
383
  }
384
  return str_replace('&#038;', '&amp;', esc_url($out));
385
  }
386
 
387
+ }
lib/Core.php ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber;
4
+
5
+ abstract class Core {
6
+
7
+ public $id;
8
+ public $ID;
9
+ public $object_type;
10
+
11
+ /**
12
+ *
13
+ * @return boolean
14
+ */
15
+ function __isset( $field ) {
16
+ if ( isset($this->$field) ) {
17
+ return $this->$field;
18
+ }
19
+ return false;
20
+ }
21
+
22
+ /**
23
+ * This is helpful for twig to return properties and methods see: https://github.com/fabpot/Twig/issues/2
24
+ * @return mixed
25
+ */
26
+ function __call( $field, $args ) {
27
+ return $this->__get($field);
28
+ }
29
+
30
+ /**
31
+ * This is helpful for twig to return properties and methods see: https://github.com/fabpot/Twig/issues/2
32
+ *
33
+ * @return mixed
34
+ */
35
+ function __get( $field ) {
36
+ if ( property_exists($this, $field) ) {
37
+ return $this->$field;
38
+ }
39
+ if ( method_exists($this, 'meta') && $meta_value = $this->meta($field) ) {
40
+ return $this->$field = $meta_value;
41
+ }
42
+ if ( method_exists($this, $field) ) {
43
+ return $this->$field = $this->$field();
44
+ }
45
+ return $this->$field = false;
46
+ }
47
+
48
+ /**
49
+ * Takes an array or object and adds the properties to the parent object
50
+ * @example
51
+ * ```php
52
+ * $data = array('airplane' => '757-200', 'flight' => '5316');
53
+ * $post = new TimberPost()
54
+ * $post->import(data);
55
+ * echo $post->airplane; //757-200
56
+ * ```
57
+ * @param array|object $info an object or array you want to grab data from to attach to the Timber object
58
+ */
59
+ function import( $info, $force = false ) {
60
+ if ( is_object($info) ) {
61
+ $info = get_object_vars($info);
62
+ }
63
+ if ( is_array($info) ) {
64
+ foreach ( $info as $key => $value ) {
65
+ if ( !empty($key) && $force ) {
66
+ $this->$key = $value;
67
+ } else if ( !empty($key) && !method_exists($this, $key) ) {
68
+ $this->$key = $value;
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+
75
+ /**
76
+ * @ignore
77
+ * @param string $key
78
+ * @param mixed $value
79
+ */
80
+ function update( $key, $value ) {
81
+ update_metadata($this->object_type, $this->ID, $key, $value);
82
+ }
83
+
84
+ /**
85
+ * Can you edit this post/term/user? Well good for you. You're no better than me.
86
+ * @example
87
+ * ```twig
88
+ * {% if post.can_edit %}
89
+ * <a href="{{ post.edit_link }}">Edit</a>
90
+ * {% endif %}
91
+ * ```
92
+ * ```html
93
+ * <a href="http://example.org/wp-admin/edit.php?p=242">Edit</a>
94
+ * ```
95
+ * @return bool
96
+ */
97
+ function can_edit() {
98
+ if ( !function_exists('current_user_can') ) {
99
+ return false;
100
+ }
101
+ if ( current_user_can('edit_post', $this->ID) ) {
102
+ return true;
103
+ }
104
+ return false;
105
+ }
106
+
107
+ /**
108
+ *
109
+ *
110
+ * @return array
111
+ */
112
+ function get_method_values() {
113
+ $ret = array();
114
+ $ret['can_edit'] = $this->can_edit();
115
+ return $ret;
116
+ }
117
+
118
+ }
lib/{timber-core-interface.php → CoreInterface.php} RENAMED
@@ -1,6 +1,8 @@
1
  <?php
2
 
3
- interface TimberCoreInterface {
 
 
4
 
5
  public function __call( $field, $args );
6
 
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ interface CoreInterface {
6
 
7
  public function __call( $field, $args );
8
 
lib/FunctionWrapper.php ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\Helper;
6
+
7
+ class FunctionWrapper {
8
+
9
+ private $_class;
10
+ private $_function;
11
+ private $_args;
12
+ private $_use_ob;
13
+
14
+ public function __toString() {
15
+ try {
16
+ return (string) $this->call();
17
+ } catch ( \Exception $e ) {
18
+ return 'Caught exception: '.$e->getMessage()."\n";
19
+ }
20
+ }
21
+
22
+ /**
23
+ *
24
+ *
25
+ * @param callable $function
26
+ * @param array $args
27
+ * @param bool $return_output_buffer
28
+ */
29
+ public function __construct( $function, $args = array(), $return_output_buffer = false ) {
30
+ if ( is_array($function) ) {
31
+ if ( (is_string($function[0]) && class_exists($function[0])) || gettype($function[0]) === 'object' ) {
32
+ $this->_class = $function[0];
33
+ }
34
+
35
+ if ( is_string($function[1]) ) {
36
+ $this->_function = $function[1];
37
+ }
38
+ } else {
39
+ $this->_function = $function;
40
+ }
41
+
42
+ $this->_args = $args;
43
+ $this->_use_ob = $return_output_buffer;
44
+
45
+ add_filter('get_twig', array(&$this, 'add_to_twig'));
46
+ }
47
+
48
+ /**
49
+ *
50
+ *
51
+ * @param Twig_Environment $twig
52
+ * @return Twig_Environment
53
+ */
54
+ public function add_to_twig( $twig ) {
55
+ $wrapper = $this;
56
+
57
+ $twig->addFunction(new \Twig_SimpleFunction($this->_function, function() use ($wrapper) {
58
+ return call_user_func_array(array($wrapper, 'call'), func_get_args());
59
+ } ));
60
+
61
+ return $twig;
62
+ }
63
+
64
+ /**
65
+ *
66
+ *
67
+ * @return string
68
+ */
69
+ public function call() {
70
+ $args = $this->_parse_args(func_get_args(), $this->_args);
71
+ $callable = (isset($this->_class)) ? array($this->_class, $this->_function) : $this->_function;
72
+
73
+ if ( $this->_use_ob ) {
74
+ return Helper::ob_function($callable, $args);
75
+ } else {
76
+ return call_user_func_array($callable, $args);
77
+ }
78
+ }
79
+
80
+ /**
81
+ *
82
+ *
83
+ * @param array $args
84
+ * @param array $defaults
85
+ * @return array
86
+ */
87
+ private function _parse_args( $args, $defaults ) {
88
+ $_arg = reset($defaults);
89
+
90
+ foreach ( $args as $index => $arg ) {
91
+ $defaults[$index] = is_null($arg) ? $_arg : $arg;
92
+ $_arg = next($defaults);
93
+ }
94
+
95
+ return $defaults;
96
+ }
97
+
98
+ }
lib/{timber-helper.php → Helper.php} RENAMED
@@ -1,9 +1,14 @@
1
  <?php
2
 
 
 
 
 
 
3
  /**
4
  * As the name suggests these are helpers for Timber (and you!) when developing. You can find additional (mainly internally-focused helpers) in TimberURLHelper
5
  */
6
- class TimberHelper {
7
 
8
  /**
9
  * A utility for a one-stop shop for Transients
@@ -26,17 +31,17 @@ class TimberHelper {
26
  * @return mixed
27
  */
28
  public static function transient( $slug, $callback, $transient_time = 0, $lock_timeout = 5, $force = false ) {
29
- $slug = apply_filters( 'timber/transient/slug', $slug );
30
 
31
- $enable_transients = ( $transient_time === false || ( defined( 'WP_DISABLE_TRANSIENTS' ) && WP_DISABLE_TRANSIENTS ) ) ? false : true;
32
- $data = $enable_transients ? get_transient( $slug ) : false;
33
 
34
  if ( false === $data ) {
35
 
36
- if ( $enable_transients && self::_is_transient_locked( $slug ) ) {
37
 
38
- $force = apply_filters( 'timber_force_transients', $force );
39
- $force = apply_filters( 'timber_force_transient_' . $slug, $force );
40
 
41
  if ( !$force ) {
42
  //the server is currently executing the process.
@@ -49,14 +54,15 @@ class TimberHelper {
49
 
50
  // lock timeout shouldn't be higher than 5 seconds, unless
51
  // remote calls with high timeouts are made here
52
- if ( $enable_transients )
53
- self::_lock_transient( $slug, $lock_timeout );
 
54
 
55
  $data = $callback();
56
 
57
  if ( $enable_transients ) {
58
- set_transient( $slug, $data, $transient_time );
59
- self::_unlock_transient( $slug );
60
  }
61
 
62
  }
@@ -71,7 +77,7 @@ class TimberHelper {
71
  * @param integer $lock_timeout
72
  */
73
  static function _lock_transient( $slug, $lock_timeout ) {
74
- set_transient( $slug . '_lock', true, $lock_timeout );
75
  }
76
 
77
  /**
@@ -79,7 +85,7 @@ class TimberHelper {
79
  * @param string $slug
80
  */
81
  static function _unlock_transient( $slug ) {
82
- delete_transient( $slug . '_lock', true );
83
  }
84
 
85
  /**
@@ -87,7 +93,7 @@ class TimberHelper {
87
  * @param string $slug
88
  */
89
  static function _is_transient_locked( $slug ) {
90
- return (bool)get_transient( $slug . '_lock' );
91
  }
92
 
93
  /* These are for measuring page render time */
@@ -99,7 +105,7 @@ class TimberHelper {
99
  */
100
  public static function start_timer() {
101
  $time = microtime();
102
- $time = explode( ' ', $time );
103
  $time = $time[1] + $time[0];
104
  return $time;
105
  }
@@ -117,11 +123,11 @@ class TimberHelper {
117
  */
118
  public static function stop_timer( $start ) {
119
  $time = microtime();
120
- $time = explode( ' ', $time );
121
  $time = $time[1] + $time[0];
122
  $finish = $time;
123
- $total_time = round( ( $finish - $start ), 4 );
124
- return $total_time . ' seconds.';
125
  }
126
 
127
  /* Function Utilities
@@ -153,24 +159,22 @@ class TimberHelper {
153
  * @param array $args
154
  * @return string
155
  */
156
- public static function ob_function( $function, $args = array( null ) ) {
157
  ob_start();
158
- call_user_func_array( $function, $args );
159
  $data = ob_get_contents();
160
  ob_end_clean();
161
  return $data;
162
  }
163
 
164
  /**
165
- *
166
- *
167
- * @param string $function_name
168
- * @param integer[] $defaults
169
- * @param bool $return_output_buffer
170
- * @return TimberFunctionWrapper
171
  */
172
  public static function function_wrapper( $function_name, $defaults = array(), $return_output_buffer = false ) {
173
- return new TimberFunctionWrapper( $function_name, $defaults, $return_output_buffer );
174
  }
175
 
176
  /**
@@ -183,12 +187,20 @@ class TimberHelper {
183
  if ( !WP_DEBUG ) {
184
  return;
185
  }
186
- if ( is_object( $arg ) || is_array( $arg ) ) {
187
- $arg = print_r( $arg, true );
188
  }
189
- return error_log( $arg );
190
  }
191
 
 
 
 
 
 
 
 
 
192
  /**
193
  *
194
  *
@@ -197,8 +209,8 @@ class TimberHelper {
197
  * @return string
198
  */
199
  public static function get_wp_title( $separator = ' ', $seplocation = 'left' ) {
200
- $separator = apply_filters( 'timber_wp_title_seperator', $separator );
201
- return trim( wp_title( $separator, false, $seplocation ) );
202
  }
203
 
204
  /* Text Utilities
@@ -215,33 +227,33 @@ class TimberHelper {
215
  */
216
  public static function trim_words( $text, $num_words = 55, $more = null, $allowed_tags = 'p a span b i br blockquote' ) {
217
  if ( null === $more ) {
218
- $more = __( '&hellip;' );
219
  }
220
  $original_text = $text;
221
  $allowed_tag_string = '';
222
- foreach ( explode( ' ', apply_filters( 'timber/trim_words/allowed_tags', $allowed_tags ) ) as $tag ) {
223
- $allowed_tag_string .= '<' . $tag . '>';
224
  }
225
- $text = strip_tags( $text, $allowed_tag_string );
226
  /* translators: If your word count is based on single characters (East Asian characters), enter 'characters'. Otherwise, enter 'words'. Do not translate into your own language. */
227
- if ( 'characters' == _x( 'words', 'word count: words or characters?' ) && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ) ) {
228
- $text = trim( preg_replace( "/[\n\r\t ]+/", ' ', $text ), ' ' );
229
- preg_match_all( '/./u', $text, $words_array );
230
- $words_array = array_slice( $words_array[0], 0, $num_words + 1 );
231
  $sep = '';
232
  } else {
233
- $words_array = preg_split( "/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY );
234
  $sep = ' ';
235
  }
236
- if ( count( $words_array ) > $num_words ) {
237
- array_pop( $words_array );
238
- $text = implode( $sep, $words_array );
239
- $text = $text . $more;
240
  } else {
241
- $text = implode( $sep, $words_array );
242
  }
243
- $text = self::close_tags( $text );
244
- return apply_filters( 'wp_trim_words', $text, $num_words, $more, $original_text );
245
  }
246
 
247
  /**
@@ -252,87 +264,30 @@ class TimberHelper {
252
  */
253
  public static function close_tags( $html ) {
254
  //put all opened tags into an array
255
- preg_match_all( '#<([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result );
256
  $openedtags = $result[1];
257
  //put all closed tags into an array
258
- preg_match_all( '#</([a-z]+)>#iU', $html, $result );
259
  $closedtags = $result[1];
260
- $len_opened = count( $openedtags );
261
  // all tags are closed
262
- if ( count( $closedtags ) == $len_opened ) {
263
  return $html;
264
  }
265
- $openedtags = array_reverse( $openedtags );
266
  // close tags
267
  for ( $i = 0; $i < $len_opened; $i++ ) {
268
- if ( !in_array( $openedtags[$i], $closedtags ) ) {
269
- $html .= '</' . $openedtags[$i] . '>';
270
  } else {
271
- unset( $closedtags[array_search( $openedtags[$i], $closedtags )] );
272
  }
273
  }
274
- $html = str_replace(array('</br>','</hr>','</wbr>'), '', $html);
275
- $html = str_replace(array('<br>','<hr>','<wbr>'), array('<br />','<hr />','<wbr />'), $html);
276
  return $html;
277
  }
278
 
279
- /* WordPress Query Utilities
280
- ======================== */
281
-
282
- /**
283
- * @param string $key
284
- * @param string $value
285
- * @return array|int
286
- * @deprecated 0.20.0
287
- */
288
- public static function get_posts_by_meta( $key, $value ) {
289
- global $wpdb;
290
- $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s", $key, $value );
291
- $results = $wpdb->get_col( $query );
292
- $pids = array();
293
- foreach ( $results as $result ) {
294
- if ( get_post( $result ) ) {
295
- $pids[] = $result;
296
- }
297
- }
298
- if ( count( $pids ) ) {
299
- return $pids;
300
- }
301
- return 0;
302
- }
303
-
304
- /**
305
- *
306
- *
307
- * @param string $key
308
- * @param string $value
309
- * @return int
310
- * @deprecated 0.20.0
311
- */
312
- public static function get_post_by_meta( $key, $value ) {
313
- global $wpdb;
314
- $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s ORDER BY post_id", $key, $value );
315
- $results = $wpdb->get_col( $query );
316
- foreach ( $results as $result ) {
317
- if ( $result && get_post( $result ) ) {
318
- return $result;
319
- }
320
- }
321
- return 0;
322
- }
323
-
324
- /**
325
- *
326
- * @deprecated 0.21.8
327
- * @param int $ttid
328
- * @return mixed
329
- */
330
- public static function get_term_id_by_term_taxonomy_id( $ttid ) {
331
- global $wpdb;
332
- $query = $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %s", $ttid );
333
- return $wpdb->get_var( $query );
334
- }
335
-
336
  /* Object Utilities
337
  ======================== */
338
 
@@ -344,7 +299,7 @@ class TimberHelper {
344
  * @return void
345
  */
346
  public static function osort( &$array, $prop ) {
347
- usort( $array, function ( $a, $b ) use ( $prop ) {
348
  return $a->$prop > $b->$prop ? 1 : -1;
349
  } );
350
  }
@@ -356,10 +311,10 @@ class TimberHelper {
356
  * @return bool
357
  */
358
  public static function is_array_assoc( $arr ) {
359
- if ( !is_array( $arr ) ) {
360
  return false;
361
  }
362
- return (bool)count( array_filter( array_keys( $arr ), 'is_string' ) );
363
  }
364
 
365
  /**
@@ -369,10 +324,10 @@ class TimberHelper {
369
  * @return stdClass
370
  */
371
  public static function array_to_object( $array ) {
372
- $obj = new stdClass;
373
  foreach ( $array as $k => $v ) {
374
- if ( is_array( $v ) ) {
375
- $obj->{$k} = self::array_to_object( $v ); //RECURSION
376
  } else {
377
  $obj->{$k} = $v;
378
  }
@@ -389,10 +344,10 @@ class TimberHelper {
389
  * @return bool|int
390
  */
391
  public static function get_object_index_by_property( $array, $key, $value ) {
392
- if ( is_array( $array ) ) {
393
  $i = 0;
394
  foreach ( $array as $arr ) {
395
- if ( is_array( $arr ) ) {
396
  if ( $arr[$key] == $value ) {
397
  return $i;
398
  }
@@ -417,15 +372,15 @@ class TimberHelper {
417
  * @throws Exception
418
  */
419
  public static function get_object_by_property( $array, $key, $value ) {
420
- if ( is_array( $array ) ) {
421
  foreach ( $array as $arr ) {
422
  if ( $arr->$key == $value ) {
423
  return $arr;
424
  }
425
  }
426
  } else {
427
- throw new InvalidArgumentException( '$array is not an array, got:' );
428
- TimberHelper::error_log( $array );
429
  }
430
  }
431
 
@@ -437,8 +392,8 @@ class TimberHelper {
437
  * @return array
438
  */
439
  public static function array_truncate( $array, $len ) {
440
- if ( sizeof( $array ) > $len ) {
441
- $array = array_splice( $array, 0, $len );
442
  }
443
  return $array;
444
  }
@@ -453,11 +408,11 @@ class TimberHelper {
453
  * @return bool
454
  */
455
  public static function is_true( $value ) {
456
- if ( isset( $value ) ) {
457
- if (is_string($value)) {
458
  $value = strtolower($value);
459
  }
460
- if ( ($value == 'true' || $value === 1 || $value === '1' || $value == true) && $value !== false && $value !== 'false') {
461
  return true;
462
  }
463
  }
@@ -471,7 +426,7 @@ class TimberHelper {
471
  * @return bool
472
  */
473
  public static function iseven( $i ) {
474
- return ( $i % 2 ) == 0;
475
  }
476
 
477
  /**
@@ -481,7 +436,7 @@ class TimberHelper {
481
  * @return bool
482
  */
483
  public static function isodd( $i ) {
484
- return ( $i % 2 ) != 0;
485
  }
486
 
487
  /* Links, Forms, Etc. Utilities
@@ -513,28 +468,28 @@ class TimberHelper {
513
  'current' => 0,
514
  'show_all' => false,
515
  'prev_next' => false,
516
- 'prev_text' => __( '&laquo; Previous' ),
517
- 'next_text' => __( 'Next &raquo;' ),
518
  'end_size' => 1,
519
  'mid_size' => 2,
520
  'type' => 'array',
521
  'add_args' => false, // array of query args to add
522
  'add_fragment' => ''
523
  );
524
- $args = wp_parse_args( $args, $defaults );
525
  // Who knows what else people pass in $args
526
- $args['total'] = intval( (int)$args['total'] );
527
  if ( $args['total'] < 2 ) {
528
  return array();
529
  }
530
- $args['current'] = (int)$args['current'];
531
- $args['end_size'] = 0 < (int)$args['end_size'] ? (int)$args['end_size'] : 1; // Out of bounds? Make it the default.
532
- $args['mid_size'] = 0 <= (int)$args['mid_size'] ? (int)$args['mid_size'] : 2;
533
- $args['add_args'] = is_array( $args['add_args'] ) ? $args['add_args'] : false;
534
  $page_links = array();
535
  $dots = false;
536
  for ( $n = 1; $n <= $args['total']; $n++ ) {
537
- $n_display = number_format_i18n( $n );
538
  if ( $n == $args['current'] ) {
539
  $page_links[] = array(
540
  'class' => 'page-number page-numbers current',
@@ -545,18 +500,18 @@ class TimberHelper {
545
  );
546
  $dots = true;
547
  } else {
548
- if ( $args['show_all'] || ( $n <= $args['end_size'] || ( $args['current'] && $n >= $args['current'] - $args['mid_size'] && $n <= $args['current'] + $args['mid_size'] ) || $n > $args['total'] - $args['end_size'] ) ) {
549
- $link = str_replace( '%_%', 1 == $n ? '' : $args['format'], $args['base'] );
550
- $link = str_replace( '%#%', $n, $link );
551
- $link = trailingslashit( $link ) . ltrim( $args['add_fragment'], '/' );
552
  if ( $args['add_args'] ) {
553
- $link = rtrim( add_query_arg( $args['add_args'], $link ), '/' );
554
  }
555
  $link = str_replace(' ', '+', $link);
556
- $link = untrailingslashit( $link );
557
  $page_links[] = array(
558
  'class' => 'page-number page-numbers',
559
- 'link' => esc_url( apply_filters( 'paginate_links', $link ) ),
560
  'title' => $n_display,
561
  'name' => $n_display,
562
  'current' => $args['current'] == $n
@@ -565,7 +520,7 @@ class TimberHelper {
565
  } elseif ( $dots && !$args['show_all'] ) {
566
  $page_links[] = array(
567
  'class' => 'dots',
568
- 'title' => __( '&hellip;' )
569
  );
570
  $dots = false;
571
  }
@@ -575,94 +530,10 @@ class TimberHelper {
575
  }
576
 
577
  /**
578
- * @deprecated 0.18.0
579
- */
580
- static function get_current_url() {
581
- return TimberURLHelper::get_current_url();
582
- }
583
-
584
- /**
585
- * @deprecated 0.18.0
586
- */
587
- static function is_url( $url ) {
588
- return TimberURLHelper::is_url( $url );
589
- }
590
-
591
- /**
592
- * @deprecated 0.18.0
593
- */
594
- static function get_path_base() {
595
- return TimberURLHelper::get_path_base();
596
- }
597
-
598
- /**
599
- * @deprecated 0.18.0
600
- */
601
- static function get_rel_url( $url, $force = false ) {
602
- return TimberURLHelper::get_rel_url( $url, $force );
603
- }
604
-
605
- /**
606
- * @deprecated 0.18.0
607
- */
608
- static function is_local( $url ) {
609
- return TimberURLHelper::is_local( $url );
610
- }
611
-
612
- /**
613
- * @deprecated 0.18.0
614
- */
615
- static function get_full_path( $src ) {
616
- return TimberURLHelper::get_full_path( $src );
617
- }
618
-
619
- /**
620
- * @deprecated 0.18.0
621
- */
622
- static function get_rel_path( $src ) {
623
- return TimberURLHelper::get_rel_path( $src );
624
- }
625
-
626
- /**
627
- * @deprecated 0.18.0
628
- */
629
- static function remove_double_slashes( $url ) {
630
- return TimberURLHelper::remove_double_slashes( $url );
631
- }
632
-
633
- /**
634
- * @deprecated 0.18.0
635
- */
636
- static function prepend_to_url( $url, $path ) {
637
- return TimberURLHelper::prepend_to_url( $url, $path );
638
- }
639
-
640
- /**
641
- * @deprecated 0.18.0
642
- */
643
- static function preslashit( $path ) {
644
- return TimberURLHelper::preslashit( $path );
645
- }
646
-
647
- /**
648
- * @deprecated 0.18.0
649
- */
650
- static function is_external( $url ) {
651
- return TimberURLHelper::is_external( $url );
652
- }
653
-
654
- /**
655
- * @deprecated 0.18.0
656
- */
657
- static function download_url( $url, $timeout = 300 ) {
658
- return TimberURLHelper::download_url( $url, $timeout );
659
- }
660
-
661
- /**
662
- * @deprecated 0.18.0
663
  */
664
- static function get_params( $i = -1 ) {
665
- return TimberURLHelper::get_params( $i );
 
666
  }
667
-
668
- }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\FunctionWrapper;
6
+ use Timber\URLHelper;
7
+
8
  /**
9
  * As the name suggests these are helpers for Timber (and you!) when developing. You can find additional (mainly internally-focused helpers) in TimberURLHelper
10
  */
11
+ class Helper {
12
 
13
  /**
14
  * A utility for a one-stop shop for Transients
31
  * @return mixed
32
  */
33
  public static function transient( $slug, $callback, $transient_time = 0, $lock_timeout = 5, $force = false ) {
34
+ $slug = apply_filters('timber/transient/slug', $slug);
35
 
36
+ $enable_transients = ($transient_time === false || (defined('WP_DISABLE_TRANSIENTS') && WP_DISABLE_TRANSIENTS)) ? false : true;
37
+ $data = $enable_transients ? get_transient($slug) : false;
38
 
39
  if ( false === $data ) {
40
 
41
+ if ( $enable_transients && self::_is_transient_locked($slug) ) {
42
 
43
+ $force = apply_filters('timber_force_transients', $force);
44
+ $force = apply_filters('timber_force_transient_'.$slug, $force);
45
 
46
  if ( !$force ) {
47
  //the server is currently executing the process.
54
 
55
  // lock timeout shouldn't be higher than 5 seconds, unless
56
  // remote calls with high timeouts are made here
57
+ if ( $enable_transients ) {
58
+ self::_lock_transient($slug, $lock_timeout);
59
+ }
60
 
61
  $data = $callback();
62
 
63
  if ( $enable_transients ) {
64
+ set_transient($slug, $data, $transient_time);
65
+ self::_unlock_transient($slug);
66
  }
67
 
68
  }
77
  * @param integer $lock_timeout
78
  */
79
  static function _lock_transient( $slug, $lock_timeout ) {
80
+ set_transient($slug.'_lock', true, $lock_timeout);
81
  }
82
 
83
  /**
85
  * @param string $slug
86
  */
87
  static function _unlock_transient( $slug ) {
88
+ delete_transient($slug.'_lock', true);
89
  }
90
 
91
  /**
93
  * @param string $slug
94
  */
95
  static function _is_transient_locked( $slug ) {
96
+ return (bool) get_transient($slug.'_lock');
97
  }
98
 
99
  /* These are for measuring page render time */
105
  */
106
  public static function start_timer() {
107
  $time = microtime();
108
+ $time = explode(' ', $time);
109
  $time = $time[1] + $time[0];
110
  return $time;
111
  }
123
  */
124
  public static function stop_timer( $start ) {
125
  $time = microtime();
126
+ $time = explode(' ', $time);
127
  $time = $time[1] + $time[0];
128
  $finish = $time;
129
+ $total_time = round(($finish - $start), 4);
130
+ return $total_time.' seconds.';
131
  }
132
 
133
  /* Function Utilities
159
  * @param array $args
160
  * @return string
161
  */
162
+ public static function ob_function( $function, $args = array(null) ) {
163
  ob_start();
164
+ call_user_func_array($function, $args);
165
  $data = ob_get_contents();
166
  ob_end_clean();
167
  return $data;
168
  }
169
 
170
  /**
171
+ * @param mixed $function_name or array( $class( string|object ), $function_name )
172
+ * @param array (optional) $defaults
173
+ * @param bool (optional) $return_output_buffer Return function output instead of return value (default: false)
174
+ * @return \TimberFunctionWrapper
 
 
175
  */
176
  public static function function_wrapper( $function_name, $defaults = array(), $return_output_buffer = false ) {
177
+ return new FunctionWrapper($function_name, $defaults, $return_output_buffer);
178
  }
179
 
180
  /**
187
  if ( !WP_DEBUG ) {
188
  return;
189
  }
190
+ if ( is_object($arg) || is_array($arg) ) {
191
+ $arg = print_r($arg, true);
192
  }
193
+ return error_log($arg);
194
  }
195
 
196
+ /**
197
+ * @param string $message that you want to output
198
+ * @return void
199
+ */
200
+ public static function warn( $message ) {
201
+ return trigger_error($message, E_USER_WARNING);
202
+ }
203
+
204
  /**
205
  *
206
  *
209
  * @return string
210
  */
211
  public static function get_wp_title( $separator = ' ', $seplocation = 'left' ) {
212
+ $separator = apply_filters('timber_wp_title_seperator', $separator);
213
+ return trim(wp_title($separator, false, $seplocation));
214
  }
215
 
216
  /* Text Utilities
227
  */
228
  public static function trim_words( $text, $num_words = 55, $more = null, $allowed_tags = 'p a span b i br blockquote' ) {
229
  if ( null === $more ) {
230
+ $more = __('&hellip;');
231
  }
232
  $original_text = $text;
233
  $allowed_tag_string = '';
234
+ foreach ( explode(' ', apply_filters('timber/trim_words/allowed_tags', $allowed_tags)) as $tag ) {
235
+ $allowed_tag_string .= '<'.$tag.'>';
236
  }
237
+ $text = strip_tags($text, $allowed_tag_string);
238
  /* translators: If your word count is based on single characters (East Asian characters), enter 'characters'. Otherwise, enter 'words'. Do not translate into your own language. */
239
+ if ( 'characters' == _x('words', 'word count: words or characters?') && preg_match('/^utf\-?8$/i', get_option('blog_charset')) ) {
240
+ $text = trim(preg_replace("/[\n\r\t ]+/", ' ', $text), ' ');
241
+ preg_match_all('/./u', $text, $words_array);
242
+ $words_array = array_slice($words_array[0], 0, $num_words + 1);
243
  $sep = '';
244
  } else {
245
+ $words_array = preg_split("/[\n\r\t ]+/", $text, $num_words + 1, PREG_SPLIT_NO_EMPTY);
246
  $sep = ' ';
247
  }
248
+ if ( count($words_array) > $num_words ) {
249
+ array_pop($words_array);
250
+ $text = implode($sep, $words_array);
251
+ $text = $text.$more;
252
  } else {
253
+ $text = implode($sep, $words_array);
254
  }
255
+ $text = self::close_tags($text);
256
+ return apply_filters('wp_trim_words', $text, $num_words, $more, $original_text);
257
  }
258
 
259
  /**
264
  */
265
  public static function close_tags( $html ) {
266
  //put all opened tags into an array
267
+ preg_match_all('#<([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result);
268
  $openedtags = $result[1];
269
  //put all closed tags into an array
270
+ preg_match_all('#</([a-z]+)>#iU', $html, $result);
271
  $closedtags = $result[1];
272
+ $len_opened = count($openedtags);
273
  // all tags are closed
274
+ if ( count($closedtags) == $len_opened ) {
275
  return $html;
276
  }
277
+ $openedtags = array_reverse($openedtags);
278
  // close tags
279
  for ( $i = 0; $i < $len_opened; $i++ ) {
280
+ if ( !in_array($openedtags[$i], $closedtags) ) {
281
+ $html .= '</'.$openedtags[$i].'>';
282
  } else {
283
+ unset($closedtags[array_search($openedtags[$i], $closedtags)]);
284
  }
285
  }
286
+ $html = str_replace(array('</br>', '</hr>', '</wbr>'), '', $html);
287
+ $html = str_replace(array('<br>', '<hr>', '<wbr>'), array('<br />', '<hr />', '<wbr />'), $html);
288
  return $html;
289
  }
290
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  /* Object Utilities
292
  ======================== */
293
 
299
  * @return void
300
  */
301
  public static function osort( &$array, $prop ) {
302
+ usort($array, function( $a, $b ) use ($prop) {
303
  return $a->$prop > $b->$prop ? 1 : -1;
304
  } );
305
  }
311
  * @return bool
312
  */
313
  public static function is_array_assoc( $arr ) {
314
+ if ( !is_array($arr) ) {
315
  return false;
316
  }
317
+ return (bool) count(array_filter(array_keys($arr), 'is_string'));
318
  }
319
 
320
  /**
324
  * @return stdClass
325
  */
326
  public static function array_to_object( $array ) {
327
+ $obj = new \stdClass;
328
  foreach ( $array as $k => $v ) {
329
+ if ( is_array($v) ) {
330
+ $obj->{$k} = self::array_to_object($v); //RECURSION
331
  } else {
332
  $obj->{$k} = $v;
333
  }
344
  * @return bool|int
345
  */
346
  public static function get_object_index_by_property( $array, $key, $value ) {
347
+ if ( is_array($array) ) {
348
  $i = 0;
349
  foreach ( $array as $arr ) {
350
+ if ( is_array($arr) ) {
351
  if ( $arr[$key] == $value ) {
352
  return $i;
353
  }
372
  * @throws Exception
373
  */
374
  public static function get_object_by_property( $array, $key, $value ) {
375
+ if ( is_array($array) ) {
376
  foreach ( $array as $arr ) {
377
  if ( $arr->$key == $value ) {
378
  return $arr;
379
  }
380
  }
381
  } else {
382
+ throw new \InvalidArgumentException('$array is not an array, got:');
383
+ Helper::error_log($array);
384
  }
385
  }
386
 
392
  * @return array
393
  */
394
  public static function array_truncate( $array, $len ) {
395
+ if ( sizeof($array) > $len ) {
396
+ $array = array_splice($array, 0, $len);
397
  }
398
  return $array;
399
  }
408
  * @return bool
409
  */
410
  public static function is_true( $value ) {
411
+ if ( isset($value) ) {
412
+ if ( is_string($value) ) {
413
  $value = strtolower($value);
414
  }
415
+ if ( ($value == 'true' || $value === 1 || $value === '1' || $value == true) && $value !== false && $value !== 'false' ) {
416
  return true;
417
  }
418
  }
426
  * @return bool
427
  */
428
  public static function iseven( $i ) {
429
+ return ($i % 2) == 0;
430
  }
431
 
432
  /**
436
  * @return bool
437
  */
438
  public static function isodd( $i ) {
439
+ return ($i % 2) != 0;
440
  }
441
 
442
  /* Links, Forms, Etc. Utilities
468
  'current' => 0,
469
  'show_all' => false,
470
  'prev_next' => false,
471
+ 'prev_text' => __('&laquo; Previous'),
472
+ 'next_text' => __('Next &raquo;'),
473
  'end_size' => 1,
474
  'mid_size' => 2,
475
  'type' => 'array',
476
  'add_args' => false, // array of query args to add
477
  'add_fragment' => ''
478
  );
479
+ $args = wp_parse_args($args, $defaults);
480
  // Who knows what else people pass in $args
481
+ $args['total'] = intval((int) $args['total']);
482
  if ( $args['total'] < 2 ) {
483
  return array();
484
  }
485
+ $args['current'] = (int) $args['current'];
486
+ $args['end_size'] = 0 < (int) $args['end_size'] ? (int) $args['end_size'] : 1; // Out of bounds? Make it the default.
487
+ $args['mid_size'] = 0 <= (int) $args['mid_size'] ? (int) $args['mid_size'] : 2;
488
+ $args['add_args'] = is_array($args['add_args']) ? $args['add_args'] : false;
489
  $page_links = array();
490
  $dots = false;
491
  for ( $n = 1; $n <= $args['total']; $n++ ) {
492
+ $n_display = number_format_i18n($n);
493
  if ( $n == $args['current'] ) {
494
  $page_links[] = array(
495
  'class' => 'page-number page-numbers current',
500
  );
501
  $dots = true;
502
  } else {
503
+ if ( $args['show_all'] || ($n <= $args['end_size'] || ($args['current'] && $n >= $args['current'] - $args['mid_size'] && $n <= $args['current'] + $args['mid_size']) || $n > $args['total'] - $args['end_size']) ) {
504
+ $link = str_replace('%_%', 1 == $n ? '' : $args['format'], $args['base']);
505
+ $link = str_replace('%#%', $n, $link);
506
+ $link = trailingslashit($link).ltrim($args['add_fragment'], '/');
507
  if ( $args['add_args'] ) {
508
+ $link = rtrim(add_query_arg($args['add_args'], $link), '/');
509
  }
510
  $link = str_replace(' ', '+', $link);
511
+ $link = untrailingslashit($link);
512
  $page_links[] = array(
513
  'class' => 'page-number page-numbers',
514
+ 'link' => esc_url(apply_filters('paginate_links', $link)),
515
  'title' => $n_display,
516
  'name' => $n_display,
517
  'current' => $args['current'] == $n
520
  } elseif ( $dots && !$args['show_all'] ) {
521
  $page_links[] = array(
522
  'class' => 'dots',
523
+ 'title' => __('&hellip;')
524
  );
525
  $dots = false;
526
  }
530
  }
531
 
532
  /**
533
+ *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
534
  */
535
+ function get_current_url() {
536
+ Helper::warn('TimberHelper::get_current_url() is deprecated and will be removed in future versions, use Timber\URLHelper::get_current_url()');
537
+ return URLHelper::get_current_url();
538
  }
539
+ }
 
lib/{timber-image.php → Image.php} RENAMED
@@ -1,5 +1,13 @@
1
  <?php
2
 
 
 
 
 
 
 
 
 
3
  /**
4
  * If TimberPost is the class you're going to spend the most time, TimberImage is the class you're going to have the most fun with.
5
  * @example
@@ -37,7 +45,7 @@
37
  * </article>
38
  * ```
39
  */
40
- class TimberImage extends TimberPost implements TimberCoreInterface {
41
 
42
  protected $_can_edit;
43
  protected $_dimensions;
@@ -88,7 +96,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
88
  * ```
89
  * @param int|string $iid
90
  */
91
- public function __construct($iid) {
92
  $this->init($iid);
93
  }
94
 
@@ -96,8 +104,8 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
96
  * @return string the src of the file
97
  */
98
  public function __toString() {
99
- if ( $this->get_src() ) {
100
- return $this->get_src();
101
  }
102
  return '';
103
  }
@@ -115,7 +123,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
115
  * @param string $dim
116
  * @return array|int
117
  */
118
- protected function get_dimensions($dim = null) {
119
  if ( isset($this->_dimensions) ) {
120
  return $this->get_dimensions_loaded($dim);
121
  }
@@ -133,7 +141,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
133
  * @param string|null $dim
134
  * @return array|int
135
  */
136
- protected function get_dimensions_loaded($dim) {
137
  if ( $dim === null ) {
138
  return $this->_dimensions;
139
  }
@@ -146,31 +154,42 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
146
  return null;
147
  }
148
 
 
 
 
 
 
 
 
 
 
 
 
149
  /**
150
  * @internal
151
  * @param int $iid the id number of the image in the WP database
152
  */
153
  protected function get_image_info( $iid ) {
154
  $image_info = $iid;
155
- if (is_numeric($iid)) {
156
  $image_info = wp_get_attachment_metadata($iid);
157
- if (!is_array($image_info)) {
158
  $image_info = array();
159
  }
160
- $image_custom = get_post_custom($iid);
161
  $basic = get_post($iid);
162
- if ($basic) {
163
- if (isset($basic->post_excerpt)) {
164
  $this->caption = $basic->post_excerpt;
165
  }
166
  $image_custom = array_merge($image_custom, get_object_vars($basic));
167
  }
168
  return array_merge($image_info, $image_custom);
169
  }
170
- if (is_array($image_info) && isset($image_info['image'])) {
171
  return $image_info['image'];
172
  }
173
- if (is_object($image_info)) {
174
  return get_object_vars($image_info);
175
  }
176
  return $iid;
@@ -181,9 +200,9 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
181
  * @param string $url for evaluation
182
  * @return string with http/https corrected depending on what's appropriate for server
183
  */
184
- protected static function _maybe_secure_url($url) {
185
  if ( is_ssl() && strpos($url, 'https') !== 0 && strpos($url, 'http') === 0 ) {
186
- $url = 'https' . substr($url, strlen('http'));
187
  }
188
  return $url;
189
  }
@@ -203,8 +222,12 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
203
  * @param int $iid
204
  */
205
  function init( $iid = false ) {
206
- if ( !is_numeric( $iid ) && is_string( $iid ) ) {
207
- if (strstr($iid, '://')) {
 
 
 
 
208
  $this->init_with_url($iid);
209
  return;
210
  }
@@ -215,23 +238,23 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
215
 
216
  $relative = false;
217
  $iid_lower = strtolower($iid);
218
- foreach( $this->file_types as $type ) { if( strstr( $iid_lower, $type ) ) { $relative = true; break; } };
219
  if ( $relative ) {
220
- $this->init_with_relative_path( $iid );
221
  return;
222
  }
223
- } else if ( $iid instanceof WP_Post ) {
224
- $ref = new ReflectionClass($this);
225
  $post = $ref->getParentClass()->newInstance($iid->ID);
226
- if (isset($post->_thumbnail_id) && $post->_thumbnail_id) {
227
  return $this->init((int) $post->_thumbnail_id);
228
  }
229
  return $this->init($iid->ID);
230
- } else if ( $iid instanceof TimberPost ) {
231
  /**
232
  * This will catch TimberPost and any post classes that extend TimberPost,
233
  * see http://php.net/manual/en/internals2.opcodes.instanceof.php#109108
234
- * and https://github.com/jarednova/timber/wiki/Extending-Timber
235
  */
236
  $iid = (int) $iid->_thumbnail_id;
237
  }
@@ -242,10 +265,10 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
242
  $basedir = self::wp_upload_dir();
243
  $basedir = $basedir['basedir'];
244
  if ( isset($this->file) ) {
245
- $this->file_loc = $basedir . DIRECTORY_SEPARATOR . $this->file;
246
  } else if ( isset($this->_wp_attached_file) ) {
247
  $this->file = reset($this->_wp_attached_file);
248
- $this->file_loc = $basedir . DIRECTORY_SEPARATOR . $this->file;
249
  }
250
  if ( isset($image_info['id']) ) {
251
  $this->ID = $image_info['id'];
@@ -253,17 +276,17 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
253
  $this->ID = $iid;
254
  }
255
  if ( isset($this->ID) ) {
256
- $custom = get_post_custom($this->ID);
257
- foreach ($custom as $key => $value) {
258
  $this->$key = $value[0];
259
  }
260
  $this->id = $this->ID;
261
  } else {
262
  if ( is_array($iid) || is_object($iid) ) {
263
- TimberHelper::error_log('Not able to init in TimberImage with iid=');
264
- TimberHelper::error_log($iid);
265
  } else {
266
- TimberHelper::error_log('Not able to init in TimberImage with iid=' . $iid);
267
  }
268
  }
269
  }
@@ -273,8 +296,8 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
273
  * @param string $relative_path
274
  */
275
  protected function init_with_relative_path( $relative_path ) {
276
- $this->abs_url = home_url( $relative_path );
277
- $file_path = TimberURLHelper::get_full_path( $relative_path );
278
  $this->file_loc = $file_path;
279
  $this->file = $file_path;
280
  }
@@ -284,7 +307,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
284
  * @param string $file_path
285
  */
286
  protected function init_with_file_path( $file_path ) {
287
- $url = TimberURLHelper::file_system_to_url( $file_path );
288
  $this->abs_url = $url;
289
  $this->file_loc = $file_path;
290
  $this->file = $file_path;
@@ -294,11 +317,11 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
294
  * @internal
295
  * @param string $url
296
  */
297
- protected function init_with_url($url) {
298
  $this->abs_url = $url;
299
- if ( TimberURLHelper::is_local($url) ) {
300
- $this->file = ABSPATH . TimberURLHelper::get_rel_url($url);
301
- $this->file_loc = ABSPATH . TimberURLHelper::get_rel_url($url);
302
  }
303
  }
304
 
@@ -393,7 +416,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
393
  * @return string the /relative/path/to/the/file
394
  */
395
  public function path() {
396
- return TimberURLHelper::get_rel_path($this->src());
397
  }
398
 
399
  /**
@@ -409,7 +432,7 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
409
  * ```
410
  * @return bool|string
411
  */
412
- public function src($size = '') {
413
  if ( isset($this->abs_url) ) {
414
  return $this->_maybe_secure_url($this->abs_url);
415
  }
@@ -430,19 +453,11 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
430
  $dir = self::wp_upload_dir();
431
  $base = $dir['baseurl'];
432
 
433
- $src = trailingslashit($this->_maybe_secure_url($base)) . $this->file;
434
  $src = apply_filters('timber/image/src', $src, $this->ID);
435
  return apply_filters('timber_image_src', $src, $this->ID);
436
  }
437
 
438
- /**
439
- * @deprecated use src() instead
440
- * @return string
441
- */
442
- function url() {
443
- return $this->get_src();
444
- }
445
-
446
  /**
447
  * @api
448
  * @example
@@ -458,69 +473,34 @@ class TimberImage extends TimberPost implements TimberCoreInterface {
458
  return $this->get_dimensions('width');
459
  }
460
 
461
-
462
- /**
463
- * @deprecated 0.21.9 use TimberImage::width() instead
464
- * @internal
465
- * @return int
466
- */
467
- function get_width() {
468
- return $this->width();
469
- }
470
-
471
- /**
472
- * @deprecated 0.21.9 use TimberImage::height() instead
473
- * @internal
474
- * @return int
475
- */
476
- function get_height() {
477
- return $this->height();
478
- }
479
-
480
  /**
481
  * @deprecated 0.21.9 use TimberImage::src
482
  * @internal
483
  * @param string $size
484
  * @return bool|string
485
  */
486
- function get_src( $size = '' ) {
487
- return $this->src( $size );
 
488
  }
489
 
490
- /**
491
- * @deprecated 0.21.9 use TimberImage::path()
492
- * @internal
493
- * @return string
494
- */
495
- function get_path() {
496
- return $this->link();
497
- }
498
 
499
  /**
500
- * @deprecated use src() instead
501
  * @return string
502
  */
503
- function get_url() {
504
- return $this->get_src();
 
505
  }
506
 
507
- /**
508
- * @internal
509
- * @deprecated 0.21.8
510
- * @return bool|TimberPost
511
- */
512
- function get_parent() {
513
- return $this->parent();
514
- }
515
 
516
  /**
517
- * @internal
518
- * @deprecated 0.21.9
519
- * @see TimberImage::alt
520
  * @return string
521
  */
522
- function get_alt() {
523
- return $this->alt();
 
524
  }
525
-
526
- }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\CoreInterface;
6
+ use Timber\Helper;
7
+ use Timber\Post;
8
+ use Timber\URLHelper;
9
+
10
+
11
  /**
12
  * If TimberPost is the class you're going to spend the most time, TimberImage is the class you're going to have the most fun with.
13
  * @example
45
  * </article>
46
  * ```
47
  */
48
+ class Image extends Post implements CoreInterface {
49
 
50
  protected $_can_edit;
51
  protected $_dimensions;
96
  * ```
97
  * @param int|string $iid
98
  */
99
+ public function __construct( $iid ) {
100
  $this->init($iid);
101
  }
102
 
104
  * @return string the src of the file
105
  */
106
  public function __toString() {
107
+ if ( $this->src() ) {
108
+ return $this->src();
109
  }
110
  return '';
111
  }
123
  * @param string $dim
124
  * @return array|int
125
  */
126
+ protected function get_dimensions( $dim = null ) {
127
  if ( isset($this->_dimensions) ) {
128
  return $this->get_dimensions_loaded($dim);
129
  }
141
  * @param string|null $dim
142
  * @return array|int
143
  */
144
+ protected function get_dimensions_loaded( $dim ) {
145
  if ( $dim === null ) {
146
  return $this->_dimensions;
147
  }
154
  return null;
155
  }
156
 
157
+ /**
158
+ * @return array
159
+ */
160
+ protected function get_post_custom($iid) {
161
+ $pc = get_post_custom($iid);
162
+ if ( is_bool($pc) ) {
163
+ return array();
164
+ }
165
+ return $pc;
166
+ }
167
+
168
  /**
169
  * @internal
170
  * @param int $iid the id number of the image in the WP database
171
  */
172
  protected function get_image_info( $iid ) {
173
  $image_info = $iid;
174
+ if ( is_numeric($iid) ) {
175
  $image_info = wp_get_attachment_metadata($iid);
176
+ if ( !is_array($image_info) ) {
177
  $image_info = array();
178
  }
179
+ $image_custom = self::get_post_custom($iid);
180
  $basic = get_post($iid);
181
+ if ( $basic ) {
182
+ if ( isset($basic->post_excerpt) ) {
183
  $this->caption = $basic->post_excerpt;
184
  }
185
  $image_custom = array_merge($image_custom, get_object_vars($basic));
186
  }
187
  return array_merge($image_info, $image_custom);
188
  }
189
+ if ( is_array($image_info) && isset($image_info['image']) ) {
190
  return $image_info['image'];
191
  }
192
+ if ( is_object($image_info) ) {
193
  return get_object_vars($image_info);
194
  }
195
  return $iid;
200
  * @param string $url for evaluation
201
  * @return string with http/https corrected depending on what's appropriate for server
202
  */
203
+ protected static function _maybe_secure_url( $url ) {
204
  if ( is_ssl() && strpos($url, 'https') !== 0 && strpos($url, 'http') === 0 ) {
205
+ $url = 'https'.substr($url, strlen('http'));
206
  }
207
  return $url;
208
  }
222
  * @param int $iid
223
  */
224
  function init( $iid = false ) {
225
+ if ( !$iid ) { Helper::error_log('Initalized TimberImage without providing first parameter.'); return; }
226
+ if ( $iid instanceof self ) {
227
+ $iid = (int) $iid->ID;
228
+ }
229
+ if ( !is_numeric($iid) && is_string($iid) ) {
230
+ if ( strstr($iid, '://') ) {
231
  $this->init_with_url($iid);
232
  return;
233
  }
238
 
239
  $relative = false;
240
  $iid_lower = strtolower($iid);
241
+ foreach ( $this->file_types as $type ) { if ( strstr($iid_lower, $type) ) { $relative = true; break; } };
242
  if ( $relative ) {
243
+ $this->init_with_relative_path($iid);
244
  return;
245
  }
246
+ } else if ( $iid instanceof \WP_Post ) {
247
+ $ref = new \ReflectionClass($this);
248
  $post = $ref->getParentClass()->newInstance($iid->ID);
249
+ if ( isset($post->_thumbnail_id) && $post->_thumbnail_id ) {
250
  return $this->init((int) $post->_thumbnail_id);
251
  }
252
  return $this->init($iid->ID);
253
+ } else if ( $iid instanceof Post ) {
254
  /**
255
  * This will catch TimberPost and any post classes that extend TimberPost,
256
  * see http://php.net/manual/en/internals2.opcodes.instanceof.php#109108
257
+ * and https://github.com/timber/timber/wiki/Extending-Timber
258
  */
259
  $iid = (int) $iid->_thumbnail_id;
260
  }
265
  $basedir = self::wp_upload_dir();
266
  $basedir = $basedir['basedir'];
267
  if ( isset($this->file) ) {
268
+ $this->file_loc = $basedir.DIRECTORY_SEPARATOR.$this->file;
269
  } else if ( isset($this->_wp_attached_file) ) {
270
  $this->file = reset($this->_wp_attached_file);
271
+ $this->file_loc = $basedir.DIRECTORY_SEPARATOR.$this->file;
272
  }
273
  if ( isset($image_info['id']) ) {
274
  $this->ID = $image_info['id'];
276
  $this->ID = $iid;
277
  }
278
  if ( isset($this->ID) ) {
279
+ $custom = self::get_post_custom($this->ID);
280
+ foreach ( $custom as $key => $value ) {
281
  $this->$key = $value[0];
282
  }
283
  $this->id = $this->ID;
284
  } else {
285
  if ( is_array($iid) || is_object($iid) ) {
286
+ Helper::error_log('Not able to init in TimberImage with iid=');
287
+ Helper::error_log($iid);
288
  } else {
289
+ Helper::error_log('Not able to init in TimberImage with iid='.$iid);
290
  }
291
  }
292
  }
296
  * @param string $relative_path
297
  */
298
  protected function init_with_relative_path( $relative_path ) {
299
+ $this->abs_url = home_url($relative_path);
300
+ $file_path = URLHelper::get_full_path($relative_path);
301
  $this->file_loc = $file_path;
302
  $this->file = $file_path;
303
  }
307
  * @param string $file_path
308
  */
309
  protected function init_with_file_path( $file_path ) {
310
+ $url = URLHelper::file_system_to_url($file_path);
311
  $this->abs_url = $url;
312
  $this->file_loc = $file_path;
313
  $this->file = $file_path;
317
  * @internal
318
  * @param string $url
319
  */
320
+ protected function init_with_url( $url ) {
321
  $this->abs_url = $url;
322
+ if ( URLHelper::is_local($url) ) {
323
+ $this->file = URLHelper::remove_double_slashes(ABSPATH.URLHelper::get_rel_url($url));
324
+ $this->file_loc = URLHelper::remove_double_slashes(ABSPATH.URLHelper::get_rel_url($url));
325
  }
326
  }
327
 
416
  * @return string the /relative/path/to/the/file
417
  */
418
  public function path() {
419
+ return URLHelper::get_rel_path($this->src());
420
  }
421
 
422
  /**
432
  * ```
433
  * @return bool|string
434
  */
435
+ public function src( $size = '' ) {
436
  if ( isset($this->abs_url) ) {
437
  return $this->_maybe_secure_url($this->abs_url);
438
  }
453
  $dir = self::wp_upload_dir();
454
  $base = $dir['baseurl'];
455
 
456
+ $src = trailingslashit($this->_maybe_secure_url($base)).$this->file;
457
  $src = apply_filters('timber/image/src', $src, $this->ID);
458
  return apply_filters('timber_image_src', $src, $this->ID);
459
  }
460
 
 
 
 
 
 
 
 
 
461
  /**
462
  * @api
463
  * @example
473
  return $this->get_dimensions('width');
474
  }
475
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
476
  /**
477
  * @deprecated 0.21.9 use TimberImage::src
478
  * @internal
479
  * @param string $size
480
  * @return bool|string
481
  */
482
+ public function get_src( $size = '' ) {
483
+ Helper::warn('{{image.get_src}} is deprecated and will be removed in 1.1; use {{image.src}}');
484
+ return $this->src($size);
485
  }
486
 
 
 
 
 
 
 
 
 
487
 
488
  /**
489
+ * @deprecated since 0.21.9 use src() instead
490
  * @return string
491
  */
492
+ public function url( $size = '' ) {
493
+ Helper::warn('{{image.url}} is deprecated and will be removed in 1.1; use {{image.src}}');
494
+ return $this->src($size);
495
  }
496
 
 
 
 
 
 
 
 
 
497
 
498
  /**
499
+ * @deprecated since 0.21.9 use src() instead
 
 
500
  * @return string
501
  */
502
+ public function get_url( $size = '' ) {
503
+ Helper::warn('{{image.get_url}} is deprecated and will be removed in 1.1; use {{image.src}}');
504
+ return $this->src($size);
505
  }
506
+ }
 
lib/{image/timber-image-operation.php → Image/Operation.php} RENAMED
@@ -1,4 +1,7 @@
1
  <?php
 
 
 
2
  /**
3
  * Each image filter is represented by a subclass of this class,m
4
  * and each filter call is a new instance, with call arguments as properties.
@@ -8,8 +11,9 @@
8
  * - filename
9
  * - run
10
  */
11
- abstract class TimberImageOperation {
12
  /**
 
13
  * Builds the result filename, based on source filename and extension
14
  *
15
  * @param string $src_filename source filename (excluding extension and path)
@@ -17,7 +21,7 @@ abstract class TimberImageOperation {
17
  * @return string resulting filename (including extension but excluding path)
18
  * ex: my-awesome-file.jpg
19
  */
20
- public abstract function filename($src_filename, $src_extension);
21
 
22
  /**
23
  * Performs the actual image manipulation,
@@ -27,7 +31,7 @@ abstract class TimberImageOperation {
27
  * @param string $save_filename filepath (not URL) where result file should be saved
28
  * @return bool true if everything went fine, false otherwise
29
  */
30
- public abstract function run($load_filename, $save_filename);
31
 
32
  /**
33
  * Helper method to convert hex string to rgb array
@@ -37,13 +41,13 @@ abstract class TimberImageOperation {
37
  * ex: array('red' => 255, 'green' => 20, 'blue' => 85);
38
  */
39
  public static function hexrgb( $hexstr ) {
40
- if ( !strstr( $hexstr, '#' ) ) {
41
- $hexstr = '#' . $hexstr;
42
  }
43
- if ( strlen( $hexstr ) == 4 ) {
44
- $hexstr = '#' . $hexstr[1] . $hexstr[1] . $hexstr[2] . $hexstr[2] . $hexstr[3] . $hexstr[3];
45
  }
46
- $int = hexdec( $hexstr );
47
- return array( "red" => 0xFF & ( $int >> 0x10 ), "green" => 0xFF & ( $int >> 0x8 ), "blue" => 0xFF & $int );
48
  }
49
  }
1
  <?php
2
+
3
+ namespace Timber\Image;
4
+
5
  /**
6
  * Each image filter is represented by a subclass of this class,m
7
  * and each filter call is a new instance, with call arguments as properties.
11
  * - filename
12
  * - run
13
  */
14
+ abstract class Operation {
15
  /**
16
+ *
17
  * Builds the result filename, based on source filename and extension
18
  *
19
  * @param string $src_filename source filename (excluding extension and path)
21
  * @return string resulting filename (including extension but excluding path)
22
  * ex: my-awesome-file.jpg
23
  */
24
+ public abstract function filename( $src_filename, $src_extension );
25
 
26
  /**
27
  * Performs the actual image manipulation,
31
  * @param string $save_filename filepath (not URL) where result file should be saved
32
  * @return bool true if everything went fine, false otherwise
33
  */
34
+ public abstract function run( $load_filename, $save_filename );
35
 
36
  /**
37
  * Helper method to convert hex string to rgb array
41
  * ex: array('red' => 255, 'green' => 20, 'blue' => 85);
42
  */
43
  public static function hexrgb( $hexstr ) {
44
+ if ( !strstr($hexstr, '#') ) {
45
+ $hexstr = '#'.$hexstr;
46
  }
47
+ if ( strlen($hexstr) == 4 ) {
48
+ $hexstr = '#'.$hexstr[1].$hexstr[1].$hexstr[2].$hexstr[2].$hexstr[3].$hexstr[3];
49
  }
50
+ $int = hexdec($hexstr);
51
+ return array("red" => 0xFF & ($int >> 0x10), "green" => 0xFF & ($int >> 0x8), "blue" => 0xFF & $int);
52
  }
53
  }
lib/{image/timber-image-operation-letterbox.php → Image/Operation/Letterbox.php} RENAMED
@@ -1,4 +1,10 @@
1
  <?php
 
 
 
 
 
 
2
  /*
3
  * Changes image to new size, by shrinking/enlarging then padding with colored bands,
4
  * so that no part of the image is cropped or stretched.
@@ -8,7 +14,7 @@
8
  * - height of new image
9
  * - color of padding
10
  */
11
- class TimberImageOperationLetterbox extends TimberImageOperation {
12
 
13
  private $w, $h, $color;
14
 
@@ -17,7 +23,7 @@ class TimberImageOperationLetterbox extends TimberImageOperation {
17
  * @param int $h height
18
  * @param string $color hex string, for color of padding bands
19
  */
20
- function __construct($w, $h, $color) {
21
  $this->w = $w;
22
  $this->h = $h;
23
  $this->color = $color;
@@ -29,10 +35,10 @@ class TimberImageOperationLetterbox extends TimberImageOperation {
29
  * @return string the final filename to be used
30
  * (ex: my-awesome-pic-lbox-300x200-FF3366.jpg)
31
  */
32
- public function filename($src_filename, $src_extension) {
33
- $color = str_replace( '#', '', $this->color );
34
- $newbase = $src_filename . '-lbox-' . $this->w . 'x' . $this->h . '-' . $color;
35
- $new_name = $newbase . '.' . $src_extension;
36
  return $new_name;
37
  }
38
 
@@ -46,16 +52,16 @@ class TimberImageOperationLetterbox extends TimberImageOperation {
46
  * (ex: /src/var/www/wp-content/uploads/my-pic-lbox-300x200-FF3366.jpg)
47
  * @return bool true if everything went fine, false otherwise
48
  */
49
- public function run($load_filename, $save_filename) {
50
  $w = $this->w;
51
  $h = $this->h;
52
 
53
- $bg = imagecreatetruecolor( $w, $h );
54
- $c = self::hexrgb( $this->color );
55
- $bgColor = imagecolorallocate( $bg, $c['red'], $c['green'], $c['blue'] );
56
- imagefill( $bg, 0, 0, $bgColor );
57
- $image = wp_get_image_editor( $load_filename );
58
- if ( !is_wp_error( $image ) ) {
59
  $current_size = $image->get_size();
60
  $quality = $image->get_quality();
61
  $ow = $current_size['width'];
@@ -69,38 +75,38 @@ class TimberImageOperationLetterbox extends TimberImageOperation {
69
  $y = 0;
70
  $x = $w / 2 - $owt / 2;
71
  $oht = $h;
72
- $image->crop( 0, 0, $ow, $oh, $owt, $oht );
73
  } else {
74
  $w_scale = $w / $ow;
75
  $oht = $oh * $w_scale;
76
  $x = 0;
77
  $y = $h / 2 - $oht / 2;
78
  $owt = $w;
79
- $image->crop( 0, 0, $ow, $oh, $owt, $oht );
80
  }
81
- $result = $image->save( $save_filename );
82
  $func = 'imagecreatefromjpeg';
83
  $save_func = 'imagejpeg';
84
- $ext = pathinfo( $save_filename, PATHINFO_EXTENSION );
85
  if ( $ext == 'gif' ) {
86
  $func = 'imagecreatefromgif';
87
  $save_func = 'imagegif';
88
  } else if ( $ext == 'png' ) {
89
  $func = 'imagecreatefrompng';
90
  $save_func = 'imagepng';
91
- if ($quality > 9) {
92
- $quality = $quality/10;
93
  $quality = round(10 - $quality);
94
  }
95
  }
96
- $image = $func( $save_filename );
97
- imagecopy( $bg, $image, $x, $y, 0, 0, $owt, $oht );
98
- if ($save_func === 'imagegif') {
99
- return $save_func( $bg, $save_filename );
100
  }
101
- return $save_func( $bg, $save_filename, $quality );
102
  } else {
103
- TimberHelper::error_log( $image );
104
  }
105
  return false;
106
  }
1
  <?php
2
+
3
+ namespace Timber\Image\Operation;
4
+
5
+ use Timber\Helper;
6
+ use Timber\Image\Operation as ImageOperation;
7
+
8
  /*
9
  * Changes image to new size, by shrinking/enlarging then padding with colored bands,
10
  * so that no part of the image is cropped or stretched.
14
  * - height of new image
15
  * - color of padding
16
  */
17
+ class Letterbox extends ImageOperation {
18
 
19
  private $w, $h, $color;
20
 
23
  * @param int $h height
24
  * @param string $color hex string, for color of padding bands
25
  */
26
+ function __construct( $w, $h, $color ) {
27
  $this->w = $w;
28
  $this->h = $h;
29
  $this->color = $color;
35
  * @return string the final filename to be used
36
  * (ex: my-awesome-pic-lbox-300x200-FF3366.jpg)
37
  */
38
+ public function filename( $src_filename, $src_extension ) {
39
+ $color = str_replace('#', '', $this->color);
40
+ $newbase = $src_filename.'-lbox-'.$this->w.'x'.$this->h.'-'.$color;
41
+ $new_name = $newbase.'.'.$src_extension;
42
  return $new_name;
43
  }
44
 
52
  * (ex: /src/var/www/wp-content/uploads/my-pic-lbox-300x200-FF3366.jpg)
53
  * @return bool true if everything went fine, false otherwise
54
  */
55
+ public function run( $load_filename, $save_filename ) {
56
  $w = $this->w;
57
  $h = $this->h;
58
 
59
+ $bg = imagecreatetruecolor($w, $h);
60
+ $c = self::hexrgb($this->color);
61
+ $bgColor = imagecolorallocate($bg, $c['red'], $c['green'], $c['blue']);
62
+ imagefill($bg, 0, 0, $bgColor);
63
+ $image = wp_get_image_editor($load_filename);
64
+ if ( !is_wp_error($image) ) {
65
  $current_size = $image->get_size();
66
  $quality = $image->get_quality();
67
  $ow = $current_size['width'];
75
  $y = 0;
76
  $x = $w / 2 - $owt / 2;
77
  $oht = $h;
78
+ $image->crop(0, 0, $ow, $oh, $owt, $oht);
79
  } else {
80
  $w_scale = $w / $ow;
81
  $oht = $oh * $w_scale;
82
  $x = 0;
83
  $y = $h / 2 - $oht / 2;
84
  $owt = $w;
85
+ $image->crop(0, 0, $ow, $oh, $owt, $oht);
86
  }
87
+ $result = $image->save($save_filename);
88
  $func = 'imagecreatefromjpeg';
89
  $save_func = 'imagejpeg';
90
+ $ext = pathinfo($save_filename, PATHINFO_EXTENSION);
91
  if ( $ext == 'gif' ) {
92
  $func = 'imagecreatefromgif';
93
  $save_func = 'imagegif';
94
  } else if ( $ext == 'png' ) {
95
  $func = 'imagecreatefrompng';
96
  $save_func = 'imagepng';
97
+ if ( $quality > 9 ) {
98
+ $quality = $quality / 10;
99
  $quality = round(10 - $quality);
100
  }
101
  }
102
+ $image = $func($save_filename);
103
+ imagecopy($bg, $image, $x, $y, 0, 0, $owt, $oht);
104
+ if ( $save_func === 'imagegif' ) {
105
+ return $save_func($bg, $save_filename);
106
  }
107
+ return $save_func($bg, $save_filename, $quality);
108
  } else {
109
+ Helper::error_log($image);
110
  }
111
  return false;
112
  }
lib/{image/timber-image-operation-resize.php → Image/Operation/Resize.php} RENAMED
@@ -1,4 +1,10 @@
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * Changes image to new size, by shrinking/enlarging
4
  * then cropping to respect new ratio.
@@ -8,7 +14,7 @@
8
  * - height of new image
9
  * - crop method
10
  */
11
- class TimberImageOperationResize extends TimberImageOperation {
12
 
13
  private $w, $h, $crop;
14
 
@@ -17,12 +23,12 @@ class TimberImageOperationResize extends TimberImageOperation {
17
  * @param int $h height of new image
18
  * @param string $crop cropping method, one of: 'default', 'center', 'top', 'bottom', 'left', 'right', 'top-center', 'bottom-center'.
19
  */
20
- function __construct($w, $h, $crop) {
21
  $this->w = $w;
22
  $this->h = $h;
23
  // Sanitize crop position
24
- $allowed_crop_positions = array( 'default', 'center', 'top', 'bottom', 'left', 'right', 'top-center', 'bottom-center' );
25
- if ( $crop !== false && !in_array( $crop, $allowed_crop_positions ) ) {
26
  $crop = $allowed_crop_positions[0];
27
  }
28
  $this->crop = $crop;
@@ -33,7 +39,7 @@ class TimberImageOperationResize extends TimberImageOperation {
33
  * @param string $src_extension the extension (ex: .jpg)
34
  * @return string the final filename to be used (ex: my-awesome-pic-300x200-c-default.jpg)
35
  */
36
- public function filename($src_filename, $src_extension) {
37
  $w = 0;
38
  $h = 0;
39
  if ( $this->w ) {
@@ -42,8 +48,8 @@ class TimberImageOperationResize extends TimberImageOperation {
42
  if ( $this->h ) {
43
  $h = $this->h;
44
  }
45
- $result = $src_filename . '-' . $w . 'x' . $h . '-c-' . ( $this->crop ? $this->crop : 'f' ); // Crop will be either user named or f (false)
46
- if($src_extension) {
47
  $result .= '.'.$src_extension;
48
  }
49
  return $result;
@@ -54,7 +60,7 @@ class TimberImageOperationResize extends TimberImageOperation {
54
  * @param string $save_filename
55
  */
56
  protected function run_animated_gif( $load_filename, $save_filename ) {
57
- $image = wp_get_image_editor( $load_filename );
58
  $current_size = $image->get_size();
59
  $src_w = $current_size['width'];
60
  $src_h = $current_size['height'];
@@ -63,10 +69,10 @@ class TimberImageOperationResize extends TimberImageOperation {
63
  if ( !class_exists('Imagick') ) {
64
  return false;
65
  }
66
- $image = new Imagick($load_filename);
67
  $image = $image->coalesceImages();
68
- $crop = self::get_target_sizes( $load_filename );
69
- foreach ($image as $frame) {
70
  $frame->cropImage($crop['src_w'], $crop['src_h'], $crop['x'], $crop['y']);
71
  $frame->thumbnailImage($w, $h);
72
  $frame->setImagePage($w, $h, 0, 0);
@@ -75,8 +81,11 @@ class TimberImageOperationResize extends TimberImageOperation {
75
  return $image->writeImages($save_filename, true);
76
  }
77
 
 
 
 
78
  protected function get_target_sizes( $load_filename ) {
79
- $image = wp_get_image_editor( $load_filename );
80
  $w = $this->w;
81
  $h = $this->h;
82
  $crop = $this->crop;
@@ -86,11 +95,11 @@ class TimberImageOperationResize extends TimberImageOperation {
86
  $src_h = $current_size['height'];
87
  $src_ratio = $src_w / $src_h;
88
  if ( !$h ) {
89
- $h = round( $w / $src_ratio );
90
  }
91
  if ( !$w ) {
92
  //the user wants to resize based on constant height
93
- $w = round( $h * $src_ratio );
94
  }
95
  if ( !$crop ) {
96
  return array(
@@ -104,13 +113,13 @@ class TimberImageOperationResize extends TimberImageOperation {
104
  $src_wt = $src_h * $dest_ratio;
105
  $src_ht = $src_w / $dest_ratio;
106
  $src_x = $src_w / 2 - $src_wt / 2;
107
- $src_y = ( $src_h - $src_ht ) / 6;
108
  //now specific overrides based on options:
109
  switch ( $crop ) {
110
  case 'center':
111
  // Get source x and y
112
- $src_x = round( ( $src_w - $src_wt ) / 2 );
113
- $src_y = round( ( $src_h - $src_ht ) / 2 );
114
  break;
115
 
116
  case 'top':
@@ -122,11 +131,11 @@ class TimberImageOperationResize extends TimberImageOperation {
122
  break;
123
 
124
  case 'top-center':
125
- $src_y = round( ( $src_h - $src_ht ) / 4 );
126
  break;
127
 
128
  case 'bottom-center':
129
- $src_y = $src_h - $src_ht - round( ( $src_h - $src_ht ) / 4 );
130
  break;
131
 
132
  case 'left':
@@ -138,7 +147,7 @@ class TimberImageOperationResize extends TimberImageOperation {
138
  break;
139
  }
140
  // Crop the image
141
- return ( $dest_ratio > $src_ratio )
142
  ? array(
143
  'x' => 0, 'y' => $src_y,
144
  'src_w' => $src_w, 'src_h' => $src_ht,
@@ -159,44 +168,44 @@ class TimberImageOperationResize extends TimberImageOperation {
159
  * (ex: /src/var/www/wp-content/uploads/my-pic.jpg)
160
  * @param string $save_filename filepath (not URL) where result file should be saved
161
  * (ex: /src/var/www/wp-content/uploads/my-pic-300x200-c-default.jpg)
162
- * @return bool true if everything went fine, false otherwise
163
  */
164
- public function run($load_filename, $save_filename) {
165
  //should be resized by gif resizer
166
- if ( TimberImageHelper::is_animated_gif($load_filename) ) {
167
  //attempt to resize
168
  //return if successful
169
  //proceed if not
170
  $gif = self::run_animated_gif($load_filename, $save_filename);
171
- if ($gif) {
172
  return true;
173
  }
174
  }
175
- $image = wp_get_image_editor( $load_filename );
176
- if ( !is_wp_error( $image ) ) {
177
- $crop = self::get_target_sizes( $load_filename );
178
- $image->crop( $crop['x'],
179
  $crop['y'],
180
  $crop['src_w'],
181
  $crop['src_h'],
182
  $crop['target_w'],
183
  $crop['target_h']
184
  );
185
- $result = $image->save( $save_filename );
186
- if ( is_wp_error( $result ) ) {
187
  // @codeCoverageIgnoreStart
188
- TimberHelper::error_log( 'Error resizing image' );
189
- TimberHelper::error_log( $result );
190
  return false;
191
  // @codeCoverageIgnoreEnd
192
  } else {
193
  return true;
194
  }
195
- } else if ( isset( $image->error_data['error_loading_image'] ) ) {
196
  // @codeCoverageIgnoreStart
197
- TimberHelper::error_log( 'Error loading ' . $image->error_data['error_loading_image'] );
198
  } else {
199
- TimberHelper::error_log( $image );
200
  // @codeCoverageIgnoreEnd
201
  }
202
  }
1
  <?php
2
+
3
+ namespace Timber\Image\Operation;
4
+
5
+ use Timber\Helper;
6
+ use Timber\Image\Operation as ImageOperation;
7
+
8
  /**
9
  * Changes image to new size, by shrinking/enlarging
10
  * then cropping to respect new ratio.
14
  * - height of new image
15
  * - crop method
16
  */
17
+ class Resize extends ImageOperation {
18
 
19
  private $w, $h, $crop;
20
 
23
  * @param int $h height of new image
24
  * @param string $crop cropping method, one of: 'default', 'center', 'top', 'bottom', 'left', 'right', 'top-center', 'bottom-center'.
25
  */
26
+ function __construct( $w, $h, $crop ) {
27
  $this->w = $w;
28
  $this->h = $h;
29
  // Sanitize crop position
30
+ $allowed_crop_positions = array('default', 'center', 'top', 'bottom', 'left', 'right', 'top-center', 'bottom-center');
31
+ if ( $crop !== false && !in_array($crop, $allowed_crop_positions) ) {
32
  $crop = $allowed_crop_positions[0];
33
  }
34
  $this->crop = $crop;
39
  * @param string $src_extension the extension (ex: .jpg)
40
  * @return string the final filename to be used (ex: my-awesome-pic-300x200-c-default.jpg)
41
  */
42
+ public function filename( $src_filename, $src_extension ) {
43
  $w = 0;
44
  $h = 0;
45
  if ( $this->w ) {
48
  if ( $this->h ) {
49
  $h = $this->h;
50
  }
51
+ $result = $src_filename.'-'.$w.'x'.$h.'-c-'.($this->crop ? $this->crop : 'f'); // Crop will be either user named or f (false)
52
+ if ( $src_extension ) {
53
  $result .= '.'.$src_extension;
54
  }
55
  return $result;
60
  * @param string $save_filename
61
  */
62
  protected function run_animated_gif( $load_filename, $save_filename ) {
63
+ $image = wp_get_image_editor($load_filename);
64
  $current_size = $image->get_size();
65
  $src_w = $current_size['width'];
66
  $src_h = $current_size['height'];
69
  if ( !class_exists('Imagick') ) {
70
  return false;
71
  }
72
+ $image = new \Imagick($load_filename);
73
  $image = $image->coalesceImages();
74
+ $crop = self::get_target_sizes($load_filename);
75
+ foreach ( $image as $frame ) {
76
  $frame->cropImage($crop['src_w'], $crop['src_h'], $crop['x'], $crop['y']);
77
  $frame->thumbnailImage($w, $h);
78
  $frame->setImagePage($w, $h, 0, 0);
81
  return $image->writeImages($save_filename, true);
82
  }
83
 
84
+ /**
85
+ * @param string $load_filename
86
+ */
87
  protected function get_target_sizes( $load_filename ) {
88
+ $image = wp_get_image_editor($load_filename);
89
  $w = $this->w;
90
  $h = $this->h;
91
  $crop = $this->crop;
95
  $src_h = $current_size['height'];
96
  $src_ratio = $src_w / $src_h;
97
  if ( !$h ) {
98
+ $h = round($w / $src_ratio);
99
  }
100
  if ( !$w ) {
101
  //the user wants to resize based on constant height
102
+ $w = round($h * $src_ratio);
103
  }
104
  if ( !$crop ) {
105
  return array(
113
  $src_wt = $src_h * $dest_ratio;
114
  $src_ht = $src_w / $dest_ratio;
115
  $src_x = $src_w / 2 - $src_wt / 2;
116
+ $src_y = ($src_h - $src_ht) / 6;
117
  //now specific overrides based on options:
118
  switch ( $crop ) {
119
  case 'center':
120
  // Get source x and y
121
+ $src_x = round(($src_w - $src_wt) / 2);
122
+ $src_y = round(($src_h - $src_ht) / 2);
123
  break;
124
 
125
  case 'top':
131
  break;
132
 
133
  case 'top-center':
134
+ $src_y = round(($src_h - $src_ht) / 4);
135
  break;
136
 
137
  case 'bottom-center':
138
+ $src_y = $src_h - $src_ht - round(($src_h - $src_ht) / 4);
139
  break;
140
 
141
  case 'left':
147
  break;
148
  }
149
  // Crop the image
150
+ return ($dest_ratio > $src_ratio)
151
  ? array(
152
  'x' => 0, 'y' => $src_y,
153
  'src_w' => $src_w, 'src_h' => $src_ht,
168
  * (ex: /src/var/www/wp-content/uploads/my-pic.jpg)
169
  * @param string $save_filename filepath (not URL) where result file should be saved
170
  * (ex: /src/var/www/wp-content/uploads/my-pic-300x200-c-default.jpg)
171
+ * @return boolean|null true if everything went fine, false otherwise
172
  */
173
+ public function run( $load_filename, $save_filename ) {
174
  //should be resized by gif resizer
175
+ if ( \Timber\ImageHelper::is_animated_gif($load_filename) ) {
176
  //attempt to resize
177
  //return if successful
178
  //proceed if not
179
  $gif = self::run_animated_gif($load_filename, $save_filename);
180
+ if ( $gif ) {
181
  return true;
182
  }
183
  }
184
+ $image = wp_get_image_editor($load_filename);
185
+ if ( !is_wp_error($image) ) {
186
+ $crop = self::get_target_sizes($load_filename);
187
+ $image->crop($crop['x'],
188
  $crop['y'],
189
  $crop['src_w'],
190
  $crop['src_h'],
191
  $crop['target_w'],
192
  $crop['target_h']
193
  );
194
+ $result = $image->save($save_filename);
195
+ if ( is_wp_error($result) ) {
196
  // @codeCoverageIgnoreStart
197
+ Helper::error_log('Error resizing image');
198
+ Helper::error_log($result);
199
  return false;
200
  // @codeCoverageIgnoreEnd
201
  } else {
202
  return true;
203
  }
204
+ } else if ( isset($image->error_data['error_loading_image']) ) {
205
  // @codeCoverageIgnoreStart
206
+ Helper::error_log('Error loading '.$image->error_data['error_loading_image']);
207
  } else {
208
+ Helper::error_log($image);
209
  // @codeCoverageIgnoreEnd
210
  }
211
  }
lib/Image/Operation/Retina.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber\Image\Operation;
4
+
5
+ use Timber\Helper;
6
+ use Timber\Image\Operation as ImageOperation;
7
+
8
+ /**
9
+ * Contains the class for running image retina-izing operations
10
+ */
11
+
12
+ /**
13
+ * Increases image size by a given factor
14
+ * Arguments:
15
+ * - factor by which to multiply image dimensions
16
+ * @property float $factor the factor (ex: 2, 1.5, 1.75) to multiply dimension by
17
+ */
18
+ class Retina extends ImageOperation {
19
+
20
+ private $factor;
21
+
22
+ /**
23
+ * Construct our operation
24
+ * @param float $factor to multiply original dimensions by
25
+ */
26
+ function __construct( $factor ) {
27
+ $this->factor = $factor;
28
+ }
29
+
30
+ /**
31
+ * Generates the final filename based on the source's name and extension
32
+ *
33
+ * @param string $src_filename the basename of the file (ex: my-awesome-pic)
34
+ * @param string $src_extension the extension (ex: .jpg)
35
+ * @return string the final filename to be used (ex: my-awesome-pic@2x.jpg)
36
+ */
37
+ function filename( $src_filename, $src_extension ) {
38
+ $newbase = $src_filename.'@'.$this->factor.'x'; // add @2x, @3x, @1.5x, etc.
39
+ $new_name = $newbase.'.'.$src_extension;
40
+ return $new_name;
41
+ }
42
+
43
+ /**
44
+ * Performs the actual image manipulation,
45
+ * including saving the target file.
46
+ *
47
+ * @param string $load_filename filepath (not URL) to source file
48
+ * (ex: /src/var/www/wp-content/uploads/my-pic.jpg)
49
+ * @param string $save_filename filepath (not URL) where result file should be saved
50
+ * (ex: /src/var/www/wp-content/uploads/my-pic@2x.jpg)
51
+ * @return bool true if everything went fine, false otherwise
52
+ */
53
+ function run( $load_filename, $save_filename ) {
54
+ $image = wp_get_image_editor($load_filename);
55
+ if ( !is_wp_error($image) ) {
56
+ $current_size = $image->get_size();
57
+ $src_w = $current_size['width'];
58
+ $src_h = $current_size['height'];
59
+ // Get ratios
60
+ $w = $src_w * $this->factor;
61
+ $h = $src_h * $this->factor;
62
+ $image->crop(0, 0, $src_w, $src_h, $w, $h);
63
+ $result = $image->save($save_filename);
64
+ if ( is_wp_error($result) ) {
65
+ // @codeCoverageIgnoreStart
66
+ Helper::error_log('Error resizing image');
67
+ Helper::error_log($result);
68
+ return false;
69
+ // @codeCoverageIgnoreEnd
70
+ } else {
71
+ return true;
72
+ }
73
+ } else if ( isset($image->error_data['error_loading_image']) ) {
74
+ Helper::error_log('Error loading '.$image->error_data['error_loading_image']);
75
+ } else {
76
+ Helper::error_log($image);
77
+ }
78
+ return false;
79
+ }
80
+ }
lib/{image/timber-image-operation-tojpg.php → Image/Operation/ToJpg.php} RENAMED
@@ -1,18 +1,22 @@
1
  <?php
2
 
 
 
 
 
3
  /**
4
  * Implements converting a PNG file to JPG.
5
  * Argument:
6
  * - color to fill transparent zones
7
  */
8
- class TimberImageOperationToJpg extends TimberImageOperation {
9
 
10
  private $color;
11
 
12
  /**
13
  * @param string $color hex string of color to use for transparent zones
14
  */
15
- function __construct($color) {
16
  $this->color = $color;
17
  }
18
 
@@ -21,8 +25,8 @@ class TimberImageOperationToJpg extends TimberImageOperation {
21
  * @param string $src_extension ignored
22
  * @return string the final filename to be used (ex: my-awesome-pic.jpg)
23
  */
24
- function filename($src_filename, $src_extension = 'jpg') {
25
- $new_name = $src_filename . '.jpg';
26
  return $new_name;
27
  }
28
 
@@ -35,15 +39,15 @@ class TimberImageOperationToJpg extends TimberImageOperation {
35
  * (ex: /src/var/www/wp-content/uploads/my-pic.png)
36
  * @return bool true if everything went fine, false otherwise
37
  */
38
- function run($load_filename, $save_filename) {
39
- $input = self::image_create( $load_filename );
40
- list( $width, $height ) = getimagesize( $load_filename );
41
- $output = imagecreatetruecolor( $width, $height );
42
- $c = self::hexrgb( $this->color );
43
- $color = imagecolorallocate( $output, $c['red'], $c['green'], $c['blue'] );
44
- imagefilledrectangle( $output, 0, 0, $width, $height, $color );
45
- imagecopy( $output, $input, 0, 0, 0, 0, $width, $height );
46
- imagejpeg( $output, $save_filename );
47
  return true;
48
  }
49
 
@@ -55,7 +59,7 @@ class TimberImageOperationToJpg extends TimberImageOperation {
55
  function image_create( $filename, $ext = 'auto' ) {
56
  if ( $ext == 'auto' ) {
57
  $ext = wp_check_filetype($filename);
58
- if (isset($ext['ext'])) {
59
  $ext = $ext['ext'];
60
  }
61
  }
@@ -69,6 +73,6 @@ class TimberImageOperationToJpg extends TimberImageOperation {
69
  if ( $ext == 'jpg' || $ext == 'jpeg' ) {
70
  return imagecreatefromjpeg($filename);
71
  }
72
- throw new InvalidArgumentException( 'image_create only accepts PNG, GIF and JPGs. File extension was: '.$ext );
73
  }
74
  }
1
  <?php
2
 
3
+ namespace Timber\Image\Operation;
4
+
5
+ use Timber\Image\Operation as ImageOperation;
6
+
7
  /**
8
  * Implements converting a PNG file to JPG.
9
  * Argument:
10
  * - color to fill transparent zones
11
  */
12
+ class ToJpg extends ImageOperation {
13
 
14
  private $color;
15
 
16
  /**
17
  * @param string $color hex string of color to use for transparent zones
18
  */
19
+ function __construct( $color ) {
20
  $this->color = $color;
21
  }
22
 
25
  * @param string $src_extension ignored
26
  * @return string the final filename to be used (ex: my-awesome-pic.jpg)
27
  */
28
+ function filename( $src_filename, $src_extension = 'jpg' ) {
29
+ $new_name = $src_filename.'.jpg';
30
  return $new_name;
31
  }
32
 
39
  * (ex: /src/var/www/wp-content/uploads/my-pic.png)
40
  * @return bool true if everything went fine, false otherwise
41
  */
42
+ function run( $load_filename, $save_filename ) {
43
+ $input = self::image_create($load_filename);
44
+ list($width, $height) = getimagesize($load_filename);
45
+ $output = imagecreatetruecolor($width, $height);
46
+ $c = self::hexrgb($this->color);
47
+ $color = imagecolorallocate($output, $c['red'], $c['green'], $c['blue']);
48
+ imagefilledrectangle($output, 0, 0, $width, $height, $color);
49
+ imagecopy($output, $input, 0, 0, 0, 0, $width, $height);
50
+ imagejpeg($output, $save_filename);
51
  return true;
52
  }
53
 
59
  function image_create( $filename, $ext = 'auto' ) {
60
  if ( $ext == 'auto' ) {
61
  $ext = wp_check_filetype($filename);
62
+ if ( isset($ext['ext']) ) {
63
  $ext = $ext['ext'];
64
  }
65
  }
73
  if ( $ext == 'jpg' || $ext == 'jpeg' ) {
74
  return imagecreatefromjpeg($filename);
75
  }
76
+ throw new \InvalidArgumentException('image_create only accepts PNG, GIF and JPGs. File extension was: '.$ext);
77
  }
78
  }
lib/{timber-image-helper.php → ImageHelper.php} RENAMED
@@ -1,8 +1,18 @@
1
  <?php
2
 
 
 
 
 
 
 
 
 
 
 
3
  /**
4
  * Implements the Twig image filters:
5
- * https://github.com/jarednova/timber/wiki/Image-cookbook#arbitrary-resizing-of-images
6
  * - resize
7
  * - retina
8
  * - letterbox
@@ -13,7 +23,7 @@
13
  * - most of the work is common to all filters (URL analysis, directory gymnastics, file caching, error management) and done by private static functions
14
  * - the specific part (actual image processing) is delegated to dedicated subclasses of TimberImageOperation
15
  */
16
- class TimberImageHelper {
17
 
18
  const BASE_UPLOADS = 1;
19
  const BASE_CONTENT = 2;
@@ -52,7 +62,7 @@ class TimberImageHelper {
52
  return $src;
53
  }
54
  }
55
- $op = new TimberImageOperationResize($w, $h, $crop);
56
  return self::_operate($src, $op, $force);
57
  }
58
 
@@ -91,7 +101,7 @@ class TimberImageHelper {
91
  * @return string url to the new image
92
  */
93
  public static function retina_resize( $src, $multiplier = 2, $force = false ) {
94
- $op = new TimberImageOperationRetina($multiplier);
95
  return self::_operate($src, $op, $force);
96
  }
97
 
@@ -106,7 +116,7 @@ class TimberImageHelper {
106
  return false;
107
  }
108
  //its a gif so test
109
- if( !($fh = @fopen($file, 'rb')) ) {
110
  return false;
111
  }
112
  $count = 0;
@@ -118,13 +128,13 @@ class TimberImageHelper {
118
 
119
  // We read through the file til we reach the end of the file, or we've found
120
  // at least 2 frame headers
121
- while(!feof($fh) && $count < 2) {
122
  $chunk = fread($fh, 1024 * 100); //read 100kb at a time
123
  $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x21]#s', $chunk, $matches);
124
- }
125
 
126
- fclose($fh);
127
- return $count > 1;
128
  }
129
 
130
  /**
@@ -139,7 +149,7 @@ class TimberImageHelper {
139
  * @return mixed|null|string
140
  */
141
  public static function letterbox( $src, $w, $h, $color = '#000000', $force = false ) {
142
- $op = new TimberImageOperationLetterbox($w, $h, $color);
143
  return self::_operate($src, $op, $force);
144
  }
145
 
@@ -151,7 +161,7 @@ class TimberImageHelper {
151
  * @return string
152
  */
153
  public static function img_to_jpg( $src, $bghex = '#FFFFFF', $force = false ) {
154
- $op = new TimberImageOperationToJpg($bghex);
155
  return self::_operate($src, $op, $force);
156
  }
157
 
@@ -159,13 +169,13 @@ class TimberImageHelper {
159
  * Deletes all resized versions of an image when the source is deleted
160
  */
161
  protected static function add_actions() {
162
- add_action( 'delete_attachment', function ( $post_id ) {
163
- $post = get_post( $post_id );
164
- $image_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/jpg' );
165
- if ( in_array( $post->post_mime_type, $image_types ) ) {
166
- $attachment = new TimberImage( $post_id );
167
  if ( $attachment->file_loc ) {
168
- TimberImageHelper::delete_generated_files( $attachment->file_loc );
169
  }
170
  }
171
  } );
@@ -176,9 +186,9 @@ class TimberImageHelper {
176
  * for example /wp-content or /content
177
  */
178
  protected static function add_constants() {
179
- if ( !defined( 'WP_CONTENT_SUBDIR' ) ) {
180
- $wp_content_path = str_replace( home_url(), '', WP_CONTENT_URL );
181
- define( 'WP_CONTENT_SUBDIR', $wp_content_path );
182
  }
183
  }
184
 
@@ -188,8 +198,8 @@ class TimberImageHelper {
188
  * @return void
189
  */
190
  static function add_filters() {
191
- add_filter( 'upload_dir', function ( $arr ) {
192
- $arr['relative'] = str_replace( home_url(), '', $arr['baseurl'] );
193
  return $arr;
194
  } );
195
  }
@@ -201,17 +211,17 @@ class TimberImageHelper {
201
  * or: http://example.org/wp-content/uploads/2015/my-pic.jpg
202
  */
203
  static function delete_generated_files( $local_file ) {
204
- if (TimberURLHelper::is_absolute( $local_file ) ) {
205
- $local_file = TimberURLHelper::url_to_file_system( $local_file );
206
  }
207
- $info = pathinfo( $local_file );
208
  $dir = $info['dirname'];
209
  $ext = $info['extension'];
210
  $filename = $info['filename'];
211
- self::process_delete_generated_files( $filename, $ext, $dir, '-[0-9999999]*', '-[0-9]*x[0-9]*-c-[a-z]*.' );
212
- self::process_delete_generated_files( $filename, $ext, $dir, '-lbox-[0-9999999]*', '-lbox-[0-9]*x[0-9]*-[a-zA-Z0-9]*.' );
213
- self::process_delete_generated_files( $filename, 'jpg', $dir, '-tojpg.*' );
214
- self::process_delete_generated_files( $filename, 'jpg', $dir, '-tojpg-[0-9999999]*' );
215
  }
216
 
217
  /**
@@ -229,18 +239,17 @@ class TimberImageHelper {
229
  * @param string $match_pattern pattern of files to go forth and delete
230
  */
231
  protected static function process_delete_generated_files( $filename, $ext, $dir, $search_pattern, $match_pattern = null ) {
232
- $searcher = '/' . $filename . $search_pattern;
233
- foreach ( glob( $dir . $searcher ) as $found_file ) {
234
- $regexdir = str_replace( '/', '\/', $dir );
235
- $pattern = '/' . ( $regexdir ) . '\/' . $filename . $match_pattern . $ext . '/';
236
- $match = preg_match( $pattern, $found_file );
237
- if ( ! $match_pattern || $match ) {
238
- unlink( $found_file );
239
  }
240
  }
241
  }
242
 
243
-
244
  /**
245
  * Determines the filepath corresponding to a given URL
246
  *
@@ -249,7 +258,7 @@ class TimberImageHelper {
249
  */
250
  public static function get_server_location( $url ) {
251
  // if we're already an absolute dir, just return
252
- if ( 0 === strpos( $url, ABSPATH ) ) {
253
  return $url;
254
  }
255
  // otherwise, analyze URL then build mapping path
@@ -268,14 +277,14 @@ class TimberImageHelper {
268
  $upload = wp_upload_dir();
269
  $dir = $upload['path'];
270
  $filename = $file;
271
- $file = parse_url( $file );
272
- $path_parts = pathinfo( $file['path'] );
273
- $basename = md5( $filename );
274
  $ext = 'jpg';
275
- if ( isset( $path_parts['extension'] ) ) {
276
  $ext = $path_parts['extension'];
277
  }
278
- return $dir . '/' . $basename . '.' . $ext;
279
  }
280
 
281
  /**
@@ -285,27 +294,27 @@ class TimberImageHelper {
285
  * @return string the URL to the downloaded file
286
  */
287
  public static function sideload_image( $file ) {
288
- $loc = self::get_sideloaded_file_loc( $file );
289
- if ( file_exists( $loc ) ) {
290
- return TimberURLHelper::preslashit( TimberURLHelper::get_rel_path( $loc ) );
291
  }
292
  // Download file to temp location
293
- if ( !function_exists( 'download_url' ) ) {
294
- require_once ABSPATH . '/wp-admin/includes/file.php';
295
  }
296
- $tmp = download_url( $file );
297
- preg_match( '/[^\?]+\.(jpe?g|jpe|gif|png)\b/i', $file, $matches );
298
  $file_array = array();
299
- $file_array['name'] = basename( $matches[0] );
300
  $file_array['tmp_name'] = $tmp;
301
  // If error storing temporarily, unlink
302
- if ( is_wp_error( $tmp ) ) {
303
- @unlink( $file_array['tmp_name'] );
304
  $file_array['tmp_name'] = '';
305
  }
306
  // do the validation and storage stuff
307
- $locinfo = pathinfo( $loc );
308
- $file = wp_upload_bits( $locinfo['basename'], null, file_get_contents( $file_array['tmp_name'] ) );
309
  return $file['url'];
310
  }
311
 
@@ -317,10 +326,10 @@ class TimberImageHelper {
317
  * @param string $url an URL (absolute or relative) pointing to an image
318
  * @return array an array (see keys in code below)
319
  */
320
- private static function analyze_url($url) {
321
  $result = array(
322
  'url' => $url, // the initial url
323
- 'absolute' => TimberURLHelper::is_absolute($url), // is the url absolute or relative (to home_url)
324
  'base' => 0, // is the image in uploads dir, or in content dir (theme or plugin)
325
  'subdir' => '', // the path between base (uploads or content) and file
326
  'filename' => '', // the filename, without extension
@@ -329,31 +338,32 @@ class TimberImageHelper {
329
  );
330
  $upload_dir = wp_upload_dir();
331
  $tmp = $url;
332
- if ( 0 === strpos($tmp, ABSPATH) ) { // we've been given a dir, not an url
 
333
  $result['absolute'] = true;
334
  if ( 0 === strpos($tmp, $upload_dir['basedir']) ) {
335
- $result['base']= self::BASE_UPLOADS; // upload based
336
  $tmp = str_replace($upload_dir['basedir'], '', $tmp);
337
  }
338
  if ( 0 === strpos($tmp, WP_CONTENT_DIR) ) {
339
- $result['base']= self::BASE_CONTENT; // content based
340
  $tmp = str_replace(WP_CONTENT_DIR, '', $tmp);
341
  }
342
  } else {
343
- if (!$result['absolute']) {
344
  $tmp = home_url().$tmp;
345
  }
346
- if (0 === strpos($tmp, $upload_dir['baseurl'])) {
347
- $result['base']= self::BASE_UPLOADS; // upload based
348
  $tmp = str_replace($upload_dir['baseurl'], '', $tmp);
349
  }
350
- if (0 === strpos($tmp, content_url())) {
351
- $result['base']= self::BASE_CONTENT; // content-based
352
  $tmp = str_replace(content_url(), '', $tmp);
353
  }
354
  }
355
  $parts = pathinfo($tmp);
356
- $result['subdir'] = $parts['dirname'];
357
  $result['filename'] = $parts['filename'];
358
  $result['extension'] = $parts['extension'];
359
  $result['basename'] = $parts['basename'];
@@ -370,20 +380,20 @@ class TimberImageHelper {
370
  * @param bool $absolute should the returned URL be absolute (include protocol+host), or relative
371
  * @return string the URL
372
  */
373
- private static function _get_file_url($base, $subdir, $filename, $absolute) {
374
  $url = '';
375
- if( self::BASE_UPLOADS == $base ) {
376
  $upload_dir = wp_upload_dir();
377
  $url = $upload_dir['baseurl'];
378
  }
379
- if( self::BASE_CONTENT == $base ) {
380
  $url = content_url();
381
  }
382
- if(!empty($subdir)) {
383
  $url .= $subdir;
384
  }
385
  $url .= '/'.$filename;
386
- if(!$absolute) {
387
  $url = str_replace(home_url(), '', $url);
388
  }
389
  // $url = TimberURLHelper::remove_double_slashes( $url);
@@ -398,16 +408,16 @@ class TimberImageHelper {
398
  * @param string $filename file name, including extension (but no path)
399
  * @return string the file location
400
  */
401
- private static function _get_file_path($base, $subdir, $filename) {
402
  $path = '';
403
- if(self::BASE_UPLOADS == $base) {
404
  $upload_dir = wp_upload_dir();
405
  $path = $upload_dir['basedir'];
406
  }
407
- if(self::BASE_CONTENT == $base) {
408
  $path = WP_CONTENT_DIR;
409
  }
410
- if(!empty($subdir)) {
411
  $path .= $subdir;
412
  }
413
  $path .= '/'.$filename;
@@ -429,14 +439,14 @@ class TimberImageHelper {
429
  *
430
  */
431
  private static function _operate( $src, $op, $force = false ) {
432
- if ( empty( $src ) ) {
433
  return '';
434
  }
435
  $external = false;
436
 
437
  // if external image, load it first
438
- if ( TimberURLHelper::is_external_content( $src ) ) {
439
- $src = self::sideload_image( $src );
440
  $external = true;
441
  }
442
  // break down URL into components
@@ -458,20 +468,24 @@ class TimberImageHelper {
458
  $au['subdir'],
459
  $au['basename']
460
  );
 
 
 
 
461
  // if already exists...
462
- if ( file_exists( $new_server_path ) ) {
463
  if ( $force ) {
464
  // Force operation - warning: will regenerate the image on every pageload, use for testing purposes only!
465
- unlink( $new_server_path );
466
  } else {
467
  // return existing file (caching)
468
  return $new_url;
469
  }
470
  }
471
  // otherwise generate result file
472
- if($op->run($old_server_path, $new_server_path)) {
473
- if( get_class( $op ) === 'TimberImageOperationResize' && $external ) {
474
- $new_url = strtolower( $new_url );
475
  }
476
  return $new_url;
477
  } else {
@@ -483,9 +497,9 @@ class TimberImageHelper {
483
 
484
  // -- the below methods are just used for unit testing the URL generation code
485
  //
486
- static function get_letterbox_file_url($url, $w, $h, $color) {
487
  $au = self::analyze_url($url);
488
- $op = new TimberImageOperationLetterbox($w, $h, $color);
489
  $new_url = self::_get_file_url(
490
  $au['base'],
491
  $au['subdir'],
@@ -494,9 +508,9 @@ class TimberImageHelper {
494
  );
495
  return $new_url;
496
  }
497
- public static function get_letterbox_file_path($url, $w, $h, $color ) {
498
  $au = self::analyze_url($url);
499
- $op = new TimberImageOperationLetterbox($w, $h, $color);
500
  $new_path = self::_get_file_path(
501
  $au['base'],
502
  $au['subdir'],
@@ -504,9 +518,9 @@ class TimberImageHelper {
504
  );
505
  return $new_path;
506
  }
507
- static function get_resize_file_url($url, $w, $h, $crop) {
508
  $au = self::analyze_url($url);
509
- $op = new TimberImageOperationResize($w, $h, $crop);
510
  $new_url = self::_get_file_url(
511
  $au['base'],
512
  $au['subdir'],
@@ -515,9 +529,9 @@ class TimberImageHelper {
515
  );
516
  return $new_url;
517
  }
518
- static function get_resize_file_path($url, $w, $h, $crop) {
519
  $au = self::analyze_url($url);
520
- $op = new TimberImageOperationResize($w, $h, $crop);
521
  $new_path = self::_get_file_path(
522
  $au['base'],
523
  $au['subdir'],
@@ -525,6 +539,4 @@ class TimberImageHelper {
525
  );
526
  return $new_path;
527
  }
528
-
529
-
530
- }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Image;
6
+ use Timber\Image\Operation\ToJpg;
7
+ use Timber\Image\Operation\Resize;
8
+ use Timber\Image\Operation\Retina;
9
+ use Timber\Image\Operation\Letterbox;
10
+
11
+ use Timber\URLHelper;
12
+
13
  /**
14
  * Implements the Twig image filters:
15
+ * https://github.com/timber/timber/wiki/Image-cookbook#arbitrary-resizing-of-images
16
  * - resize
17
  * - retina
18
  * - letterbox
23
  * - most of the work is common to all filters (URL analysis, directory gymnastics, file caching, error management) and done by private static functions
24
  * - the specific part (actual image processing) is delegated to dedicated subclasses of TimberImageOperation
25
  */
26
+ class ImageHelper {
27
 
28
  const BASE_UPLOADS = 1;
29
  const BASE_CONTENT = 2;
62
  return $src;
63
  }
64
  }
65
+ $op = new Image\Operation\Resize($w, $h, $crop);
66
  return self::_operate($src, $op, $force);
67
  }
68
 
101
  * @return string url to the new image
102
  */
103
  public static function retina_resize( $src, $multiplier = 2, $force = false ) {
104
+ $op = new Image\Operation\Retina($multiplier);
105
  return self::_operate($src, $op, $force);
106
  }
107
 
116
  return false;
117
  }
118
  //its a gif so test
119
+ if ( !($fh = @fopen($file, 'rb')) ) {
120
  return false;
121
  }
122
  $count = 0;
128
 
129
  // We read through the file til we reach the end of the file, or we've found
130
  // at least 2 frame headers
131
+ while ( !feof($fh) && $count < 2 ) {
132
  $chunk = fread($fh, 1024 * 100); //read 100kb at a time
133
  $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x21]#s', $chunk, $matches);
134
+ }
135
 
136
+ fclose($fh);
137
+ return $count > 1;
138
  }
139
 
140
  /**
149
  * @return mixed|null|string
150
  */
151
  public static function letterbox( $src, $w, $h, $color = '#000000', $force = false ) {
152
+ $op = new Letterbox($w, $h, $color);
153
  return self::_operate($src, $op, $force);
154
  }
155
 
161
  * @return string
162
  */
163
  public static function img_to_jpg( $src, $bghex = '#FFFFFF', $force = false ) {
164
+ $op = new Image\Operation\ToJpg($bghex);
165
  return self::_operate($src, $op, $force);
166
  }
167
 
169
  * Deletes all resized versions of an image when the source is deleted
170
  */
171
  protected static function add_actions() {
172
+ add_action('delete_attachment', function( $post_id ) {
173
+ $post = get_post($post_id);
174
+ $image_types = array('image/jpeg', 'image/png', 'image/gif', 'image/jpg');
175
+ if ( in_array($post->post_mime_type, $image_types) ) {
176
+ $attachment = new Image($post_id);
177
  if ( $attachment->file_loc ) {
178
+ ImageHelper::delete_generated_files($attachment->file_loc);
179
  }
180
  }
181
  } );
186
  * for example /wp-content or /content
187
  */
188
  protected static function add_constants() {
189
+ if ( !defined('WP_CONTENT_SUBDIR') ) {
190
+ $wp_content_path = str_replace(home_url(), '', WP_CONTENT_URL);
191
+ define('WP_CONTENT_SUBDIR', $wp_content_path);
192
  }
193
  }
194
 
198
  * @return void
199
  */
200
  static function add_filters() {
201
+ add_filter('upload_dir', function( $arr ) {
202
+ $arr['relative'] = str_replace(home_url(), '', $arr['baseurl']);
203
  return $arr;
204
  } );
205
  }
211
  * or: http://example.org/wp-content/uploads/2015/my-pic.jpg
212
  */
213
  static function delete_generated_files( $local_file ) {
214
+ if ( URLHelper::is_absolute($local_file) ) {
215
+ $local_file = URLHelper::url_to_file_system($local_file);
216
  }
217
+ $info = pathinfo($local_file);
218
  $dir = $info['dirname'];
219
  $ext = $info['extension'];
220
  $filename = $info['filename'];
221
+ self::process_delete_generated_files($filename, $ext, $dir, '-[0-9999999]*', '-[0-9]*x[0-9]*-c-[a-z]*.');
222
+ self::process_delete_generated_files($filename, $ext, $dir, '-lbox-[0-9999999]*', '-lbox-[0-9]*x[0-9]*-[a-zA-Z0-9]*.');
223
+ self::process_delete_generated_files($filename, 'jpg', $dir, '-tojpg.*');
224
+ self::process_delete_generated_files($filename, 'jpg', $dir, '-tojpg-[0-9999999]*');
225
  }
226
 
227
  /**
239
  * @param string $match_pattern pattern of files to go forth and delete
240
  */
241
  protected static function process_delete_generated_files( $filename, $ext, $dir, $search_pattern, $match_pattern = null ) {
242
+ $searcher = '/'.$filename.$search_pattern;
243
+ foreach ( glob($dir.$searcher) as $found_file ) {
244
+ $regexdir = str_replace('/', '\/', $dir);
245
+ $pattern = '/'.($regexdir).'\/'.$filename.$match_pattern.$ext.'/';
246
+ $match = preg_match($pattern, $found_file);
247
+ if ( !$match_pattern || $match ) {
248
+ unlink($found_file);
249
  }
250
  }
251
  }
252
 
 
253
  /**
254
  * Determines the filepath corresponding to a given URL
255
  *
258
  */
259
  public static function get_server_location( $url ) {
260
  // if we're already an absolute dir, just return
261
+ if ( 0 === strpos($url, ABSPATH) ) {
262
  return $url;
263
  }
264
  // otherwise, analyze URL then build mapping path
277
  $upload = wp_upload_dir();
278
  $dir = $upload['path'];
279
  $filename = $file;
280
+ $file = parse_url($file);
281
+ $path_parts = pathinfo($file['path']);
282
+ $basename = md5($filename);
283
  $ext = 'jpg';
284
+ if ( isset($path_parts['extension']) ) {
285
  $ext = $path_parts['extension'];
286
  }
287
+ return $dir.'/'.$basename.'.'.$ext;
288
  }
289
 
290
  /**
294
  * @return string the URL to the downloaded file
295
  */
296
  public static function sideload_image( $file ) {
297
+ $loc = self::get_sideloaded_file_loc($file);
298
+ if ( file_exists($loc) ) {
299
+ return URLHelper::preslashit(URLHelper::get_rel_path($loc));
300
  }
301
  // Download file to temp location
302
+ if ( !function_exists('download_url') ) {
303
+ require_once ABSPATH.'/wp-admin/includes/file.php';
304
  }
305
+ $tmp = download_url($file);
306
+ preg_match('/[^\?]+\.(jpe?g|jpe|gif|png)\b/i', $file, $matches);
307
  $file_array = array();
308
+ $file_array['name'] = basename($matches[0]);
309
  $file_array['tmp_name'] = $tmp;
310
  // If error storing temporarily, unlink
311
+ if ( is_wp_error($tmp) ) {
312
+ @unlink($file_array['tmp_name']);
313
  $file_array['tmp_name'] = '';
314
  }
315
  // do the validation and storage stuff
316
+ $locinfo = pathinfo($loc);
317
+ $file = wp_upload_bits($locinfo['basename'], null, file_get_contents($file_array['tmp_name']));
318
  return $file['url'];
319
  }
320
 
326
  * @param string $url an URL (absolute or relative) pointing to an image
327
  * @return array an array (see keys in code below)
328
  */
329
+ private static function analyze_url( $url ) {
330
  $result = array(
331
  'url' => $url, // the initial url
332
+ 'absolute' => URLHelper::is_absolute($url), // is the url absolute or relative (to home_url)
333
  'base' => 0, // is the image in uploads dir, or in content dir (theme or plugin)
334
  'subdir' => '', // the path between base (uploads or content) and file
335
  'filename' => '', // the filename, without extension
338
  );
339
  $upload_dir = wp_upload_dir();
340
  $tmp = $url;
341
+ if ( 0 === strpos($tmp, ABSPATH) ) {
342
+ // we've been given a dir, not an url
343
  $result['absolute'] = true;
344
  if ( 0 === strpos($tmp, $upload_dir['basedir']) ) {
345
+ $result['base'] = self::BASE_UPLOADS; // upload based
346
  $tmp = str_replace($upload_dir['basedir'], '', $tmp);
347
  }
348
  if ( 0 === strpos($tmp, WP_CONTENT_DIR) ) {
349
+ $result['base'] = self::BASE_CONTENT; // content based
350
  $tmp = str_replace(WP_CONTENT_DIR, '', $tmp);
351
  }
352
  } else {
353
+ if ( !$result['absolute'] ) {
354
  $tmp = home_url().$tmp;
355
  }
356
+ if ( 0 === strpos($tmp, $upload_dir['baseurl']) ) {
357
+ $result['base'] = self::BASE_UPLOADS; // upload based
358
  $tmp = str_replace($upload_dir['baseurl'], '', $tmp);
359
  }
360
+ if ( 0 === strpos($tmp, content_url()) ) {
361
+ $result['base'] = self::BASE_CONTENT; // content-based
362
  $tmp = str_replace(content_url(), '', $tmp);
363
  }
364
  }
365
  $parts = pathinfo($tmp);
366
+ $result['subdir'] = ($parts['dirname'] === '/') ? '' : $parts['dirname'];
367
  $result['filename'] = $parts['filename'];
368
  $result['extension'] = $parts['extension'];
369
  $result['basename'] = $parts['basename'];
380
  * @param bool $absolute should the returned URL be absolute (include protocol+host), or relative
381
  * @return string the URL
382
  */
383
+ private static function _get_file_url( $base, $subdir, $filename, $absolute ) {
384
  $url = '';
385
+ if ( self::BASE_UPLOADS == $base ) {
386
  $upload_dir = wp_upload_dir();
387
  $url = $upload_dir['baseurl'];
388
  }
389
+ if ( self::BASE_CONTENT == $base ) {
390
  $url = content_url();
391
  }
392
+ if ( !empty($subdir) ) {
393
  $url .= $subdir;
394
  }
395
  $url .= '/'.$filename;
396
+ if ( !$absolute ) {
397
  $url = str_replace(home_url(), '', $url);
398
  }
399
  // $url = TimberURLHelper::remove_double_slashes( $url);
408
  * @param string $filename file name, including extension (but no path)
409
  * @return string the file location
410
  */
411
+ private static function _get_file_path( $base, $subdir, $filename ) {
412
  $path = '';
413
+ if ( self::BASE_UPLOADS == $base ) {
414
  $upload_dir = wp_upload_dir();
415
  $path = $upload_dir['basedir'];
416
  }
417
+ if ( self::BASE_CONTENT == $base ) {
418
  $path = WP_CONTENT_DIR;
419
  }
420
+ if ( !empty($subdir) ) {
421
  $path .= $subdir;
422
  }
423
  $path .= '/'.$filename;
439
  *
440
  */
441
  private static function _operate( $src, $op, $force = false ) {
442
+ if ( empty($src) ) {
443
  return '';
444
  }
445
  $external = false;
446
 
447
  // if external image, load it first
448
+ if ( URLHelper::is_external_content($src) ) {
449
+ $src = self::sideload_image($src);
450
  $external = true;
451
  }
452
  // break down URL into components
468
  $au['subdir'],
469
  $au['basename']
470
  );
471
+
472
+ $new_url = apply_filters( 'timber/image/new_url', $new_url );
473
+ $new_server_path = apply_filters( 'timber/image/new_path', $new_server_path );
474
+
475
  // if already exists...
476
+ if ( file_exists($new_server_path) ) {
477
  if ( $force ) {
478
  // Force operation - warning: will regenerate the image on every pageload, use for testing purposes only!
479
+ unlink($new_server_path);
480
  } else {
481
  // return existing file (caching)
482
  return $new_url;
483
  }
484
  }
485
  // otherwise generate result file
486
+ if ( $op->run($old_server_path, $new_server_path) ) {
487
+ if ( get_class($op) === 'TimberImageOperationResize' && $external ) {
488
+ $new_url = strtolower($new_url);
489
  }
490
  return $new_url;
491
  } else {
497
 
498
  // -- the below methods are just used for unit testing the URL generation code
499
  //
500
+ static function get_letterbox_file_url( $url, $w, $h, $color ) {
501
  $au = self::analyze_url($url);
502
+ $op = new Image\Operation\Letterbox($w, $h, $color);
503
  $new_url = self::_get_file_url(
504
  $au['base'],
505
  $au['subdir'],
508
  );
509
  return $new_url;
510
  }
511
+ public static function get_letterbox_file_path( $url, $w, $h, $color ) {
512
  $au = self::analyze_url($url);
513
+ $op = new Image\Operation\Letterbox($w, $h, $color);
514
  $new_path = self::_get_file_path(
515
  $au['base'],
516
  $au['subdir'],
518
  );
519
  return $new_path;
520
  }
521
+ static function get_resize_file_url( $url, $w, $h, $crop ) {
522
  $au = self::analyze_url($url);
523
+ $op = new Image\Operation\Resize($w, $h, $crop);
524
  $new_url = self::_get_file_url(
525
  $au['base'],
526
  $au['subdir'],
529
  );
530
  return $new_url;
531
  }
532
+ static function get_resize_file_path( $url, $w, $h, $crop ) {
533
  $au = self::analyze_url($url);
534
+ $op = new Image\Operation\Resize($w, $h, $crop);
535
  $new_path = self::_get_file_path(
536
  $au['base'],
537
  $au['subdir'],
539
  );
540
  return $new_path;
541
  }
542
+ }
 
 
lib/Integrations.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\Integrations\ACF;
6
+
7
+ /**
8
+ * This is for integrating external plugins into timber
9
+ * @package timber
10
+ */
11
+ class Integrations {
12
+
13
+ public static function init() {
14
+
15
+ add_action( 'init', array( __CLASS__, 'maybe_init_acftimber' ) );
16
+
17
+ if ( class_exists( 'WP_CLI_Command' ) ) {
18
+ \WP_CLI::add_command( 'timber', 'Timber\Integrations\Timber_WP_CLI_Command' );
19
+ }
20
+ }
21
+
22
+ public static function maybe_init_acftimber() {
23
+
24
+ if ( class_exists( 'ACF' ) ) {
25
+ new ACF();
26
+ }
27
+
28
+ }
29
+ }
lib/Integrations/ACF.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber\Integrations;
4
+
5
+ class ACF {
6
+
7
+ function __construct() {
8
+ add_filter('timber_post_get_meta', array($this, 'post_get_meta'), 10, 2);
9
+ add_filter('timber_post_get_meta_field', array($this, 'post_get_meta_field'), 10, 3);
10
+ add_filter('timber_term_get_meta', array($this, 'term_get_meta'), 10, 3);
11
+ add_filter('timber_term_get_meta_field', array($this, 'term_get_meta_field'), 10, 4);
12
+ add_filter('timber_user_get_meta_field_pre', array($this, 'user_get_meta_field'), 10, 3);
13
+ add_filter('timber_term_set_meta', array($this, 'term_set_meta'), 10, 4);
14
+ }
15
+
16
+ function post_get_meta( $customs, $post_id ) {
17
+ return $customs;
18
+ }
19
+
20
+ function post_get_meta_field( $value, $post_id, $field_name ) {
21
+ return get_field($field_name, $post_id);
22
+ }
23
+
24
+ function term_get_meta_field( $value, $term_id, $field_name, $term ) {
25
+ $searcher = $term->taxonomy."_".$term->ID;
26
+ return get_field($field_name, $searcher);
27
+ }
28
+
29
+ function term_set_meta( $value, $field, $term_id, $term ) {
30
+ $searcher = $term->taxonomy."_".$term->ID;
31
+ update_field($field, $value, $searcher);
32
+ return $value;
33
+ }
34
+
35
+ function term_get_meta( $fields, $term_id, $term ) {
36
+ $searcher = $term->taxonomy."_".$term->ID; // save to a specific category
37
+ $fds = get_fields($searcher);
38
+ if ( is_array($fds) ) {
39
+ foreach ( $fds as $key => $value ) {
40
+ $key = preg_replace('/_/', '', $key, 1);
41
+ $key = str_replace($searcher, '', $key);
42
+ $key = preg_replace('/_/', '', $key, 1);
43
+ $field = get_field($key, $searcher);
44
+ $fields[$key] = $field;
45
+ }
46
+ $fields = array_merge($fields, $fds);
47
+ }
48
+ return $fields;
49
+ }
50
+
51
+ function user_get_meta( $fields, $user_id ) {
52
+ return $fields;
53
+ }
54
+
55
+ function user_get_meta_field( $value, $uid, $field ) {
56
+ return get_field($field, 'user_'.$uid);
57
+ }
58
+ }
59
+
60
+
lib/Integrations/Command.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber\Integrations;
4
+
5
+ use Timber\Loader;
6
+
7
+ /**
8
+ * These are methods that can be executed by WPCLI, other CLI mechanism or other external controllers
9
+ * @package timber
10
+ */
11
+ class Command {
12
+
13
+ public static function clear_cache( $mode = 'all' ) {
14
+ if ( is_array($mode) ) {
15
+ $mode = reset($mode);
16
+ }
17
+ if ( $mode == 'all' ) {
18
+ $twig_cache = self::clear_cache_twig();
19
+ $timber_cache = self::clear_cache_timber();
20
+ if ( $twig_cache && $timber_cache ) {
21
+ return true;
22
+ }
23
+ } else if ( $mode == 'twig' ) {
24
+ return self::clear_cache_twig();
25
+ } else if ( $mode == 'timber' ) {
26
+ return self::clear_cache_timber();
27
+ }
28
+ }
29
+
30
+ static function clear_cache_timber() {
31
+ $loader = new Loader();
32
+ return $loader->clear_cache_timber();
33
+ }
34
+
35
+ static function clear_cache_twig() {
36
+ $loader = new Loader();
37
+ return $loader->clear_cache_twig();
38
+ }
39
+
40
+ }
lib/Integrations/Timber_WP_CLI_Command.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber\Integrations;
4
+
5
+ use Timber\Integrations\Command;
6
+
7
+ if ( !class_exists('WP_CLI_Command') ) {
8
+ return;
9
+ }
10
+
11
+ class Timber_WP_CLI_Command extends \WP_CLI_Command {
12
+
13
+ /**
14
+ * Clears Timber and Twig's Cache
15
+ *
16
+ * ## EXAMPLES
17
+ *
18
+ * wp timber clear_cache
19
+ *
20
+ */
21
+ public function clear_cache( $mode = 'all' ) {
22
+ Command::clear_cache($mode);
23
+ }
24
+
25
+ /**
26
+ * Clears Twig's Cache
27
+ *
28
+ * ## EXAMPLES
29
+ *
30
+ * wp timber clear_cache_twig
31
+ *
32
+ */
33
+ function clear_cache_twig() {
34
+ $clear = Command::clear_cache_twig();
35
+ if ( $clear ) {
36
+ WP_CLI::success('Cleared contents of twig cache');
37
+ } else {
38
+ WP_CLI::warning('Failed to clear twig cache');
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Clears Timber's Cache
44
+ *
45
+ * ## EXAMPLES
46
+ *
47
+ * wp timber clear_cache_timber
48
+ *
49
+ */
50
+ function clear_cache_timber() {
51
+ $clear = Command::clear_cache_timber();
52
+ $message = 'Failed to clear timber cache';
53
+ if ( $clear ) {
54
+ $message = "Cleared contents of Timber's Cache";
55
+ WP_CLI::success($message);
56
+ } else {
57
+ WP_CLI::warning($message);
58
+ }
59
+ return $message;
60
+ }
61
+
62
+ }
lib/{timber-loader.php → Loader.php} RENAMED
@@ -1,6 +1,8 @@
1
  <?php
2
 
3
- class TimberLoader {
 
 
4
 
5
  const CACHEGROUP = 'timberloader';
6
 
@@ -26,7 +28,7 @@ class TimberLoader {
26
  /**
27
  * @param bool|string $caller the calling directory or false
28
  */
29
- function __construct($caller = false) {
30
  $this->locations = $this->get_locations($caller);
31
  $this->cache_mode = apply_filters('timber_cache_mode', $this->cache_mode);
32
  $this->cache_mode = apply_filters('timber/cache/mode', $this->cache_mode);
@@ -39,10 +41,10 @@ class TimberLoader {
39
  * @param string $cache_mode
40
  * @return bool|string
41
  */
42
- function render($file, $data = null, $expires = false, $cache_mode = self::CACHE_USE_DEFAULT) {
43
  // Different $expires if user is anonymous or logged in
44
- if (is_array($expires)) {
45
- if (is_user_logged_in() && isset($expires[1])) {
46
  $expires = $expires[1];
47
  } else {
48
  $expires = $expires[0];
@@ -51,15 +53,15 @@ class TimberLoader {
51
 
52
  $key = null;
53
  $output = false;
54
- if (false !== $expires) {
55
  ksort($data);
56
- $key = md5($file . json_encode($data));
57
  $output = $this->get_cache($key, self::CACHEGROUP, $cache_mode);
58
  }
59
 
60
- if (false === $output || null === $output) {
61
  $twig = $this->get_twig();
62
- if (strlen($file)) {
63
  $loader = $this->get_loader();
64
  $result = $loader->getCacheKey($file);
65
  do_action('timber_loader_render_file', $result);
@@ -69,7 +71,7 @@ class TimberLoader {
69
  $output = $twig->render($file, $data);
70
  }
71
 
72
- if (false !== $output && false !== $expires && null !== $key) {
73
  $this->set_cache($key, $output, self::CACHEGROUP, $expires, $cache_mode);
74
  }
75
  $output = apply_filters('timber_output', $output);
@@ -80,11 +82,11 @@ class TimberLoader {
80
  * @param array $filenames
81
  * @return bool
82
  */
83
- public function choose_template($filenames) {
84
- if (is_array($filenames)) {
85
  /* its an array so we have to figure out which one the dev wants */
86
- foreach ($filenames as $filename) {
87
- if (self::template_exists($filename)) {
88
  return $filename;
89
  }
90
  }
@@ -97,10 +99,10 @@ class TimberLoader {
97
  * @param string $file
98
  * @return bool
99
  */
100
- protected function template_exists($file) {
101
- foreach ($this->locations as $dir) {
102
- $look_for = trailingslashit($dir) . $file;
103
- if (file_exists($look_for)) {
104
  return true;
105
  }
106
  }
@@ -114,18 +116,18 @@ class TimberLoader {
114
  $theme_locs = array();
115
  $child_loc = get_stylesheet_directory();
116
  $parent_loc = get_template_directory();
117
- if (DIRECTORY_SEPARATOR == '\\') {
118
  $child_loc = str_replace('/', '\\', $child_loc);
119
  $parent_loc = str_replace('/', '\\', $parent_loc);
120
  }
121
  $theme_locs[] = $child_loc;
122
- foreach ($this->get_locations_theme_dir() as $dirname) {
123
- $theme_locs[] = trailingslashit($child_loc) . trailingslashit($dirname);
124
  }
125
- if ($child_loc != $parent_loc) {
126
  $theme_locs[] = $parent_loc;
127
- foreach ($this->get_locations_theme_dir() as $dirname) {
128
- $theme_locs[] = trailingslashit($parent_loc) . trailingslashit($dirname);
129
  }
130
  }
131
  //now make sure theres a trailing slash on everything
@@ -138,24 +140,25 @@ class TimberLoader {
138
  * @return string[] the names of directores, ie: array('templats', 'views');
139
  */
140
  private function get_locations_theme_dir() {
141
- if (is_string(Timber::$dirname)) {
142
  return array(Timber::$dirname);
143
  }
144
  return Timber::$dirname;
145
  }
146
 
147
  /**
 
148
  * @return array
149
  */
150
  function get_locations_user() {
151
  $locs = array();
152
- if (isset(Timber::$locations)) {
153
- if (is_string(Timber::$locations)) {
154
  Timber::$locations = array(Timber::$locations);
155
  }
156
- foreach (Timber::$locations as $tloc) {
157
  $tloc = realpath($tloc);
158
- if (is_dir($tloc)) {
159
  $locs[] = $tloc;
160
  }
161
  }
@@ -167,16 +170,16 @@ class TimberLoader {
167
  * @param bool|string $caller the calling directory
168
  * @return array
169
  */
170
- function get_locations_caller($caller = false) {
171
  $locs = array();
172
- if ($caller && is_string($caller)) {
173
  $caller = trailingslashit($caller);
174
- if (is_dir($caller)) {
175
  $locs[] = $caller;
176
  }
177
- foreach ($this->get_locations_theme_dir() as $dirname) {
178
- $caller_sub = $caller . trailingslashit($dirname);
179
- if (is_dir($caller_sub)) {
180
  $locs[] = $caller_sub;
181
  }
182
  }
@@ -188,7 +191,7 @@ class TimberLoader {
188
  * @param bool|string $caller the calling directory (or false)
189
  * @return array
190
  */
191
- function get_locations($caller = false) {
192
  //prioirty: user locations, caller (but not theme), child theme, parent theme, caller
193
  $locs = array();
194
  $locs = array_merge($locs, $this->get_locations_user());
@@ -204,26 +207,26 @@ class TimberLoader {
204
  }
205
 
206
  /**
207
- * @return Twig_Loader_Filesystem
208
  */
209
  function get_loader() {
210
  $paths = array();
211
- foreach ($this->locations as $loc) {
212
  $loc = realpath($loc);
213
- if (is_dir($loc)) {
214
  $loc = realpath($loc);
215
  $paths[] = $loc;
216
  } else {
217
  //error_log($loc.' is not a directory');
218
  }
219
  }
220
- if (!ini_get('open_basedir')) {
221
  $paths[] = '/';
222
  } else {
223
  $paths[] = ABSPATH;
224
  }
225
  $paths = apply_filters('timber/loader/paths', $paths);
226
- $loader = new Twig_Loader_Filesystem($paths);
227
  return $loader;
228
  }
229
 
@@ -233,22 +236,22 @@ class TimberLoader {
233
  function get_twig() {
234
  $loader = $this->get_loader();
235
  $params = array('debug' => WP_DEBUG, 'autoescape' => false);
236
- if (isset(Timber::$autoescape)) {
237
  $params['autoescape'] = Timber::$autoescape;
238
  }
239
- if (Timber::$cache === true) {
240
  Timber::$twig_cache = true;
241
  }
242
- if (Timber::$twig_cache) {
243
- $twig_cache_loc = apply_filters( 'timber/cache/location', TIMBER_LOC . '/cache/twig' );
244
- if (!file_exists($twig_cache_loc)) {
245
  mkdir($twig_cache_loc, 0777, true);
246
  }
247
  $params['cache'] = $twig_cache_loc;
248
  }
249
- $twig = new Twig_Environment($loader, $params);
250
  if ( WP_DEBUG ) {
251
- $twig->addExtension(new Twig_Extension_Debug());
252
  }
253
  $twig->addExtension($this->_get_cache_extension());
254
 
@@ -258,26 +261,26 @@ class TimberLoader {
258
  return $twig;
259
  }
260
 
261
- public function clear_cache_timber($cache_mode = self::CACHE_USE_DEFAULT){
262
  //_transient_timberloader
263
  $object_cache = false;
264
- if (isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache'])) {
265
  $object_cache = true;
266
  }
267
  $cache_mode = $this->_get_cache_mode($cache_mode);
268
- if (self::CACHE_TRANSIENT === $cache_mode) {
269
  global $wpdb;
270
  $query = $wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE '%s'", '_transient_timberloader_%');
271
- $wpdb->query( $query );
272
  return true;
273
- } else if (self::CACHE_SITE_TRANSIENT === $cache_mode) {
274
  global $wpdb;
275
  $query = $wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE '%s'", '_transient_timberloader_%');
276
- $wpdb->query( $query );
277
  return true;
278
- } else if (self::CACHE_OBJECT === $cache_mode && $object_cache) {
279
  global $wp_object_cache;
280
- if (isset($wp_object_cache->cache[self::CACHEGROUP])){
281
  unset($wp_object_cache->cache[self::CACHEGROUP]);
282
  return true;
283
  }
@@ -289,7 +292,7 @@ class TimberLoader {
289
  $twig = $this->get_twig();
290
  $twig->clearCacheFiles();
291
  $cache = $twig->getCache();
292
- if ($cache){
293
  self::rrmdir($twig->getCache());
294
  return true;
295
  }
@@ -299,16 +302,16 @@ class TimberLoader {
299
  /**
300
  * @param string|false $dirPath
301
  */
302
- public static function rrmdir($dirPath) {
303
- if (! is_dir($dirPath)) {
304
- throw new InvalidArgumentException("$dirPath must be a directory");
305
  }
306
- if (substr($dirPath, strlen($dirPath) - 1, 1) != '/') {
307
  $dirPath .= '/';
308
  }
309
- $files = glob($dirPath . '*', GLOB_MARK);
310
- foreach ($files as $file) {
311
- if (is_dir($file)) {
312
  self::rrmdir($file);
313
  } else {
314
  unlink($file);
@@ -323,9 +326,9 @@ class TimberLoader {
323
  private function _get_cache_extension() {
324
 
325
  $key_generator = new \Timber\Cache\KeyGenerator();
326
- $cache_provider = new \Timber\Cache\WPObjectCacheAdapter( $this );
327
- $cache_strategy = new \Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy( $cache_provider, $key_generator );
328
- $cache_extension = new \Asm89\Twig\CacheExtension\Extension( $cache_strategy );
329
 
330
  return $cache_extension;
331
  }
@@ -336,10 +339,10 @@ class TimberLoader {
336
  * @param string $cache_mode
337
  * @return bool
338
  */
339
- public function get_cache($key, $group = self::CACHEGROUP, $cache_mode = self::CACHE_USE_DEFAULT) {
340
  $object_cache = false;
341
 
342
- if (isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache'])) {
343
  $object_cache = true;
344
  }
345
 
@@ -347,15 +350,14 @@ class TimberLoader {
347
 
348
  $value = false;
349
 
350
- $trans_key = substr($group . '_' . $key, 0, self::TRANS_KEY_LEN);
351
- if (self::CACHE_TRANSIENT === $cache_mode)
352
- $value = get_transient($trans_key);
353
-
354
- elseif (self::CACHE_SITE_TRANSIENT === $cache_mode)
355
- $value = get_site_transient($trans_key);
356
-
357
- elseif (self::CACHE_OBJECT === $cache_mode && $object_cache)
358
- $value = wp_cache_get($key, $group);
359
 
360
  return $value;
361
  }
@@ -368,27 +370,27 @@ class TimberLoader {
368
  * @param string $cache_mode
369
  * @return string|boolean
370
  */
371
- public function set_cache($key, $value, $group = self::CACHEGROUP, $expires = 0, $cache_mode = self::CACHE_USE_DEFAULT) {
372
  $object_cache = false;
373
 
374
- if (isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache'])) {
375
  $object_cache = true;
376
  }
377
 
378
- if ((int)$expires < 1)
379
- $expires = 0;
 
380
 
381
  $cache_mode = self::_get_cache_mode($cache_mode);
382
- $trans_key = substr($group . '_' . $key, 0, self::TRANS_KEY_LEN);
383
-
384
- if (self::CACHE_TRANSIENT === $cache_mode)
385
- set_transient($trans_key, $value, $expires);
386
-
387
- elseif (self::CACHE_SITE_TRANSIENT === $cache_mode)
388
- set_site_transient($trans_key, $value, $expires);
389
-
390
- elseif (self::CACHE_OBJECT === $cache_mode && $object_cache)
391
- wp_cache_set($key, $value, $group, $expires);
392
 
393
  return $value;
394
  }
@@ -397,13 +399,13 @@ class TimberLoader {
397
  * @param string $cache_mode
398
  * @return string
399
  */
400
- private function _get_cache_mode($cache_mode) {
401
- if (empty($cache_mode) || self::CACHE_USE_DEFAULT === $cache_mode) {
402
  $cache_mode = $this->cache_mode;
403
  }
404
 
405
  // Fallback if self::$cache_mode did not get a valid value
406
- if (!in_array($cache_mode, self::$cache_modes)) {
407
  $cache_mode = self::CACHE_OBJECT;
408
  }
409
 
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ class Loader {
6
 
7
  const CACHEGROUP = 'timberloader';
8
 
28
  /**
29
  * @param bool|string $caller the calling directory or false
30
  */
31
+ function __construct( $caller = false ) {
32
  $this->locations = $this->get_locations($caller);
33
  $this->cache_mode = apply_filters('timber_cache_mode', $this->cache_mode);
34
  $this->cache_mode = apply_filters('timber/cache/mode', $this->cache_mode);
41
  * @param string $cache_mode
42
  * @return bool|string
43
  */
44
+ function render( $file, $data = null, $expires = false, $cache_mode = self::CACHE_USE_DEFAULT ) {
45
  // Different $expires if user is anonymous or logged in
46
+ if ( is_array($expires) ) {
47
+ if ( is_user_logged_in() && isset($expires[1]) ) {
48
  $expires = $expires[1];
49
  } else {
50
  $expires = $expires[0];
53
 
54
  $key = null;
55
  $output = false;
56
+ if ( false !== $expires ) {
57
  ksort($data);
58
+ $key = md5($file.json_encode($data));
59
  $output = $this->get_cache($key, self::CACHEGROUP, $cache_mode);
60
  }
61
 
62
+ if ( false === $output || null === $output ) {
63
  $twig = $this->get_twig();
64
+ if ( strlen($file) ) {
65
  $loader = $this->get_loader();
66
  $result = $loader->getCacheKey($file);
67
  do_action('timber_loader_render_file', $result);
71
  $output = $twig->render($file, $data);
72
  }
73
 
74
+ if ( false !== $output && false !== $expires && null !== $key ) {
75
  $this->set_cache($key, $output, self::CACHEGROUP, $expires, $cache_mode);
76
  }
77
  $output = apply_filters('timber_output', $output);
82
  * @param array $filenames
83
  * @return bool
84
  */
85
+ public function choose_template( $filenames ) {
86
+ if ( is_array($filenames) ) {
87
  /* its an array so we have to figure out which one the dev wants */
88
+ foreach ( $filenames as $filename ) {
89
+ if ( self::template_exists($filename) ) {
90
  return $filename;
91
  }
92
  }
99
  * @param string $file
100
  * @return bool
101
  */
102
+ protected function template_exists( $file ) {
103
+ foreach ( $this->locations as $dir ) {
104
+ $look_for = trailingslashit($dir).$file;
105
+ if ( file_exists($look_for) ) {
106
  return true;
107
  }
108
  }
116
  $theme_locs = array();
117
  $child_loc = get_stylesheet_directory();
118
  $parent_loc = get_template_directory();
119
+ if ( DIRECTORY_SEPARATOR == '\\' ) {
120
  $child_loc = str_replace('/', '\\', $child_loc);
121
  $parent_loc = str_replace('/', '\\', $parent_loc);
122
  }
123
  $theme_locs[] = $child_loc;
124
+ foreach ( $this->get_locations_theme_dir() as $dirname ) {
125
+ $theme_locs[] = trailingslashit($child_loc).trailingslashit($dirname);
126
  }
127
+ if ( $child_loc != $parent_loc ) {
128
  $theme_locs[] = $parent_loc;
129
+ foreach ( $this->get_locations_theme_dir() as $dirname ) {
130
+ $theme_locs[] = trailingslashit($parent_loc).trailingslashit($dirname);
131
  }
132
  }
133
  //now make sure theres a trailing slash on everything
140
  * @return string[] the names of directores, ie: array('templats', 'views');
141
  */
142
  private function get_locations_theme_dir() {
143
+ if ( is_string(Timber::$dirname) ) {
144
  return array(Timber::$dirname);
145
  }
146
  return Timber::$dirname;
147
  }
148
 
149
  /**
150
+ *
151
  * @return array
152
  */
153
  function get_locations_user() {
154
  $locs = array();
155
+ if ( isset(Timber::$locations) ) {
156
+ if ( is_string(Timber::$locations) ) {
157
  Timber::$locations = array(Timber::$locations);
158
  }
159
+ foreach ( Timber::$locations as $tloc ) {
160
  $tloc = realpath($tloc);
161
+ if ( is_dir($tloc) ) {
162
  $locs[] = $tloc;
163
  }
164
  }
170
  * @param bool|string $caller the calling directory
171
  * @return array
172
  */
173
+ function get_locations_caller( $caller = false ) {
174
  $locs = array();
175
+ if ( $caller && is_string($caller) ) {
176
  $caller = trailingslashit($caller);
177
+ if ( is_dir($caller) ) {
178
  $locs[] = $caller;
179
  }
180
+ foreach ( $this->get_locations_theme_dir() as $dirname ) {
181
+ $caller_sub = $caller.trailingslashit($dirname);
182
+ if ( is_dir($caller_sub) ) {
183
  $locs[] = $caller_sub;
184
  }
185
  }
191
  * @param bool|string $caller the calling directory (or false)
192
  * @return array
193
  */
194
+ function get_locations( $caller = false ) {
195
  //prioirty: user locations, caller (but not theme), child theme, parent theme, caller
196
  $locs = array();
197
  $locs = array_merge($locs, $this->get_locations_user());
207
  }
208
 
209
  /**
210
+ * @return \Twig_Loader_Filesystem
211
  */
212
  function get_loader() {
213
  $paths = array();
214
+ foreach ( $this->locations as $loc ) {
215
  $loc = realpath($loc);
216
+ if ( is_dir($loc) ) {
217
  $loc = realpath($loc);
218
  $paths[] = $loc;
219
  } else {
220
  //error_log($loc.' is not a directory');
221
  }
222
  }
223
+ if ( !ini_get('open_basedir') ) {
224
  $paths[] = '/';
225
  } else {
226
  $paths[] = ABSPATH;
227
  }
228
  $paths = apply_filters('timber/loader/paths', $paths);
229
+ $loader = new \Twig_Loader_Filesystem($paths);
230
  return $loader;
231
  }
232
 
236
  function get_twig() {
237
  $loader = $this->get_loader();
238
  $params = array('debug' => WP_DEBUG, 'autoescape' => false);
239
+ if ( isset(Timber::$autoescape) ) {
240
  $params['autoescape'] = Timber::$autoescape;
241
  }
242
+ if ( Timber::$cache === true ) {
243
  Timber::$twig_cache = true;
244
  }
245
+ if ( Timber::$twig_cache ) {
246
+ $twig_cache_loc = apply_filters('timber/cache/location', TIMBER_LOC.'/cache/twig');
247
+ if ( !file_exists($twig_cache_loc) ) {
248
  mkdir($twig_cache_loc, 0777, true);
249
  }
250
  $params['cache'] = $twig_cache_loc;
251
  }
252
+ $twig = new \Twig_Environment($loader, $params);
253
  if ( WP_DEBUG ) {
254
+ $twig->addExtension(new \Twig_Extension_Debug());
255
  }
256
  $twig->addExtension($this->_get_cache_extension());
257
 
261
  return $twig;
262
  }
263
 
264
+ public function clear_cache_timber( $cache_mode = self::CACHE_USE_DEFAULT ) {
265
  //_transient_timberloader
266
  $object_cache = false;
267
+ if ( isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache']) ) {
268
  $object_cache = true;
269
  }
270
  $cache_mode = $this->_get_cache_mode($cache_mode);
271
+ if ( self::CACHE_TRANSIENT === $cache_mode ) {
272
  global $wpdb;
273
  $query = $wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE '%s'", '_transient_timberloader_%');
274
+ $wpdb->query($query);
275
  return true;
276
+ } else if ( self::CACHE_SITE_TRANSIENT === $cache_mode ) {
277
  global $wpdb;
278
  $query = $wpdb->prepare("DELETE FROM $wpdb->options WHERE option_name LIKE '%s'", '_transient_timberloader_%');
279
+ $wpdb->query($query);
280
  return true;
281
+ } else if ( self::CACHE_OBJECT === $cache_mode && $object_cache ) {
282
  global $wp_object_cache;
283
+ if ( isset($wp_object_cache->cache[self::CACHEGROUP]) ) {
284
  unset($wp_object_cache->cache[self::CACHEGROUP]);
285
  return true;
286
  }
292
  $twig = $this->get_twig();
293
  $twig->clearCacheFiles();
294
  $cache = $twig->getCache();
295
+ if ( $cache ) {
296
  self::rrmdir($twig->getCache());
297
  return true;
298
  }
302
  /**
303
  * @param string|false $dirPath
304
  */
305
+ public static function rrmdir( $dirPath ) {
306
+ if ( !is_dir($dirPath) ) {
307
+ throw new \InvalidArgumentException("$dirPath must be a directory");
308
  }
309
+ if ( substr($dirPath, strlen($dirPath) - 1, 1) != '/' ) {
310
  $dirPath .= '/';
311
  }
312
+ $files = glob($dirPath.'*', GLOB_MARK);
313
+ foreach ( $files as $file ) {
314
+ if ( is_dir($file) ) {
315
  self::rrmdir($file);
316
  } else {
317
  unlink($file);
326
  private function _get_cache_extension() {
327
 
328
  $key_generator = new \Timber\Cache\KeyGenerator();
329
+ $cache_provider = new \Timber\Cache\WPObjectCacheAdapter($this);
330
+ $cache_strategy = new \Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy($cache_provider, $key_generator);
331
+ $cache_extension = new \Asm89\Twig\CacheExtension\Extension($cache_strategy);
332
 
333
  return $cache_extension;
334
  }
339
  * @param string $cache_mode
340
  * @return bool
341
  */
342
+ public function get_cache( $key, $group = self::CACHEGROUP, $cache_mode = self::CACHE_USE_DEFAULT ) {
343
  $object_cache = false;
344
 
345
+ if ( isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache']) ) {
346
  $object_cache = true;
347
  }
348
 
350
 
351
  $value = false;
352
 
353
+ $trans_key = substr($group.'_'.$key, 0, self::TRANS_KEY_LEN);
354
+ if ( self::CACHE_TRANSIENT === $cache_mode ) {
355
+ $value = get_transient($trans_key);
356
+ } elseif ( self::CACHE_SITE_TRANSIENT === $cache_mode ) {
357
+ $value = get_site_transient($trans_key);
358
+ } elseif ( self::CACHE_OBJECT === $cache_mode && $object_cache ) {
359
+ $value = wp_cache_get($key, $group);
360
+ }
 
361
 
362
  return $value;
363
  }
370
  * @param string $cache_mode
371
  * @return string|boolean
372
  */
373
+ public function set_cache( $key, $value, $group = self::CACHEGROUP, $expires = 0, $cache_mode = self::CACHE_USE_DEFAULT ) {
374
  $object_cache = false;
375
 
376
+ if ( isset($GLOBALS['wp_object_cache']) && is_object($GLOBALS['wp_object_cache']) ) {
377
  $object_cache = true;
378
  }
379
 
380
+ if ( (int) $expires < 1 ) {
381
+ $expires = 0;
382
+ }
383
 
384
  $cache_mode = self::_get_cache_mode($cache_mode);
385
+ $trans_key = substr($group.'_'.$key, 0, self::TRANS_KEY_LEN);
386
+
387
+ if ( self::CACHE_TRANSIENT === $cache_mode ) {
388
+ set_transient($trans_key, $value, $expires);
389
+ } elseif ( self::CACHE_SITE_TRANSIENT === $cache_mode ) {
390
+ set_site_transient($trans_key, $value, $expires);
391
+ } elseif ( self::CACHE_OBJECT === $cache_mode && $object_cache ) {
392
+ wp_cache_set($key, $value, $group, $expires);
393
+ }
 
394
 
395
  return $value;
396
  }
399
  * @param string $cache_mode
400
  * @return string
401
  */
402
+ private function _get_cache_mode( $cache_mode ) {
403
+ if ( empty($cache_mode) || self::CACHE_USE_DEFAULT === $cache_mode ) {
404
  $cache_mode = $this->cache_mode;
405
  }
406
 
407
  // Fallback if self::$cache_mode did not get a valid value
408
+ if ( !in_array($cache_mode, self::$cache_modes) ) {
409
  $cache_mode = self::CACHE_OBJECT;
410
  }
411
 
lib/{timber-menu.php → Menu.php} RENAMED
@@ -1,5 +1,10 @@
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
@@ -27,11 +32,11 @@
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 %}
@@ -41,10 +46,10 @@
41
  * </nav>
42
  * ```
43
  */
44
- class TimberMenu extends TimberCore {
45
 
46
- public $MenuItemClass = 'TimberMenuItem';
47
- public $PostClass = 'TimberPost';
48
 
49
  /**
50
  * @api
@@ -75,18 +80,18 @@ class TimberMenu extends TimberCore {
75
  /**
76
  * @param int|string $slug
77
  */
78
- function __construct($slug = 0) {
79
  $locations = get_nav_menu_locations();
80
- if ($slug != 0 && is_numeric($slug)) {
81
  $menu_id = $slug;
82
- } else if (is_array($locations) && count($locations)) {
83
  $menu_id = $this->get_menu_id_from_locations($slug, $locations);
84
- } else if ($slug === false) {
85
  $menu_id = false;
86
  } else {
87
  $menu_id = $this->get_menu_id_from_terms($slug);
88
  }
89
- if ($menu_id) {
90
  $this->init($menu_id);
91
  } else {
92
  $this->init_as_page_menu();
@@ -97,11 +102,11 @@ class TimberMenu extends TimberCore {
97
  * @internal
98
  * @param int $menu_id
99
  */
100
- protected function init($menu_id) {
101
  $menu = wp_get_nav_menu_items($menu_id);
102
- if ($menu) {
103
  _wp_menu_item_classes_by_context($menu);
104
- if (is_array($menu)){
105
  $menu = self::order_children($menu);
106
  }
107
  $this->items = $menu;
@@ -118,12 +123,12 @@ class TimberMenu extends TimberCore {
118
  */
119
  protected function init_as_page_menu() {
120
  $menu = get_pages();
121
- if ($menu) {
122
- foreach($menu as $mi) {
123
  $mi->__title = $mi->post_title;
124
  }
125
  _wp_menu_item_classes_by_context($menu);
126
- if (is_array($menu)){
127
  $menu = self::order_children($menu);
128
  }
129
  $this->items = $menu;
@@ -136,14 +141,14 @@ class TimberMenu extends TimberCore {
136
  * @param array $locations
137
  * @return integer
138
  */
139
- protected function get_menu_id_from_locations($slug, $locations) {
140
- if ($slug === 0) {
141
  $slug = $this->get_menu_id_from_terms($slug);
142
  }
143
- if (is_numeric($slug)) {
144
  $slug = array_search($slug, $locations);
145
  }
146
- if (isset($locations[$slug])) {
147
  $menu_id = $locations[$slug];
148
  return $menu_id;
149
  }
@@ -154,21 +159,21 @@ class TimberMenu extends TimberCore {
154
  * @param int $slug
155
  * @return int
156
  */
157
- protected function get_menu_id_from_terms($slug = 0) {
158
- if (!is_numeric($slug) && is_string($slug)) {
159
  //we have a string so lets search for that
160
  $menu_id = get_term_by('slug', $slug, 'nav_menu');
161
- if ($menu_id) {
162
  return $menu_id;
163
  }
164
  $menu_id = get_term_by('name', $slug, 'nav_menu');
165
- if ($menu_id) {
166
  return $menu_id;
167
  }
168
  }
169
  $menus = get_terms('nav_menu', array('hide_empty' => true));
170
- if (is_array($menus) && count($menus)) {
171
- if (isset($menus[0]->term_id)) {
172
  return $menus[0]->term_id;
173
  }
174
  }
@@ -180,9 +185,9 @@ class TimberMenu extends TimberCore {
180
  * @param int $parent_id
181
  * @return TimberMenuItem|null
182
  */
183
- function find_parent_item_in_menu($menu_items, $parent_id) {
184
- foreach ($menu_items as &$item) {
185
- if ($item->ID == $parent_id) {
186
  return $item;
187
  }
188
  }
@@ -193,29 +198,29 @@ class TimberMenu extends TimberCore {
193
  * @param array $items
194
  * @return array
195
  */
196
- protected function order_children($items) {
197
  $index = array();
198
  $menu = array();
199
- foreach ($items as $item) {
200
- if (isset($item->title)) {
201
  //items from wp can come with a $title property which conflicts with methods
202
  $item->__title = $item->title;
203
  unset($item->title);
204
  }
205
- if(isset($item->ID)){
206
- if (is_object($item) && get_class($item) == 'WP_Post'){
207
  $old_menu_item = $item;
208
  $item = new $this->PostClass($item);
209
  }
210
  $menu_item = new $this->MenuItemClass($item);
211
- if (isset($old_menu_item)){
212
  $menu_item->import_classes($old_menu_item);
213
  }
214
  $index[$item->ID] = $menu_item;
215
  }
216
  }
217
- foreach ($index as $item) {
218
- if (isset($item->menu_item_parent) && $item->menu_item_parent && isset($index[$item->menu_item_parent])) {
219
  $index[$item->menu_item_parent]->add_child($item);
220
  } else {
221
  $menu[] = $item;
@@ -228,7 +233,7 @@ class TimberMenu extends TimberCore {
228
  * @return array
229
  */
230
  function get_items() {
231
- if (is_array($this->items)) {
232
  return $this->items;
233
  }
234
  return array();
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\Post;
7
+
8
  /**
9
  * 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.)
10
  * @example
32
  * <nav>
33
  * <ul class="main-nav">
34
  * {% for item in menu.get_items %}
35
+ * <li class="nav-main-item {{item.classes | join(' ')}}"><a class="nav-main-link" href="{{item.link}}">{{item.title}}</a>
36
  * {% if item.get_children %}
37
  * <ul class="nav-drop">
38
  * {% for child in item.get_children %}
39
+ * <li class="nav-drop-item"><a href="{{child.link}}">{{child.title}}</a></li>
40
  * {% endfor %}
41
  * </ul>
42
  * {% endif %}
46
  * </nav>
47
  * ```
48
  */
49
+ class Menu extends Core {
50
 
51
+ public $MenuItemClass = 'Timber\MenuItem';
52
+ public $PostClass = 'Timber\Post';
53
 
54
  /**
55
  * @api
80
  /**
81
  * @param int|string $slug
82
  */
83
+ function __construct( $slug = 0 ) {
84
  $locations = get_nav_menu_locations();
85
+ if ( $slug != 0 && is_numeric($slug) ) {
86
  $menu_id = $slug;
87
+ } else if ( is_array($locations) && count($locations) ) {
88
  $menu_id = $this->get_menu_id_from_locations($slug, $locations);
89
+ } else if ( $slug === false ) {
90
  $menu_id = false;
91
  } else {
92
  $menu_id = $this->get_menu_id_from_terms($slug);
93
  }
94
+ if ( $menu_id ) {
95
  $this->init($menu_id);
96
  } else {
97
  $this->init_as_page_menu();
102
  * @internal
103
  * @param int $menu_id
104
  */
105
+ protected function init( $menu_id ) {
106
  $menu = wp_get_nav_menu_items($menu_id);
107
+ if ( $menu ) {
108
  _wp_menu_item_classes_by_context($menu);
109
+ if ( is_array($menu) ) {
110
  $menu = self::order_children($menu);
111
  }
112
  $this->items = $menu;
123
  */
124
  protected function init_as_page_menu() {
125
  $menu = get_pages();
126
+ if ( $menu ) {
127
+ foreach ( $menu as $mi ) {
128
  $mi->__title = $mi->post_title;
129
  }
130
  _wp_menu_item_classes_by_context($menu);
131
+ if ( is_array($menu) ) {
132
  $menu = self::order_children($menu);
133
  }
134
  $this->items = $menu;
141
  * @param array $locations
142
  * @return integer
143
  */
144
+ protected function get_menu_id_from_locations( $slug, $locations ) {
145
+ if ( $slug === 0 ) {
146
  $slug = $this->get_menu_id_from_terms($slug);
147
  }
148
+ if ( is_numeric($slug) ) {
149
  $slug = array_search($slug, $locations);
150
  }
151
+ if ( isset($locations[$slug]) ) {
152
  $menu_id = $locations[$slug];
153
  return $menu_id;
154
  }
159
  * @param int $slug
160
  * @return int
161
  */
162
+ protected function get_menu_id_from_terms( $slug = 0 ) {
163
+ if ( !is_numeric($slug) && is_string($slug) ) {
164
  //we have a string so lets search for that
165
  $menu_id = get_term_by('slug', $slug, 'nav_menu');
166
+ if ( $menu_id ) {
167
  return $menu_id;
168
  }
169
  $menu_id = get_term_by('name', $slug, 'nav_menu');
170
+ if ( $menu_id ) {
171
  return $menu_id;
172
  }
173
  }
174
  $menus = get_terms('nav_menu', array('hide_empty' => true));
175
+ if ( is_array($menus) && count($menus) ) {
176
+ if ( isset($menus[0]->term_id) ) {
177
  return $menus[0]->term_id;
178
  }
179
  }
185
  * @param int $parent_id
186
  * @return TimberMenuItem|null
187
  */
188
+ function find_parent_item_in_menu( $menu_items, $parent_id ) {
189
+ foreach ( $menu_items as &$item ) {
190
+ if ( $item->ID == $parent_id ) {
191
  return $item;
192
  }
193
  }
198
  * @param array $items
199
  * @return array
200
  */
201
+ protected function order_children( $items ) {
202
  $index = array();
203
  $menu = array();
204
+ foreach ( $items as $item ) {
205
+ if ( isset($item->title) ) {
206
  //items from wp can come with a $title property which conflicts with methods
207
  $item->__title = $item->title;
208
  unset($item->title);
209
  }
210
+ if ( isset($item->ID) ) {
211
+ if ( is_object($item) && get_class($item) == 'WP_Post' ) {
212
  $old_menu_item = $item;
213
  $item = new $this->PostClass($item);
214
  }
215
  $menu_item = new $this->MenuItemClass($item);
216
+ if ( isset($old_menu_item) ) {
217
  $menu_item->import_classes($old_menu_item);
218
  }
219
  $index[$item->ID] = $menu_item;
220
  }
221
  }
222
+ foreach ( $index as $item ) {
223
+ if ( isset($item->menu_item_parent) && $item->menu_item_parent && isset($index[$item->menu_item_parent]) ) {
224
  $index[$item->menu_item_parent]->add_child($item);
225
  } else {
226
  $menu[] = $item;
233
  * @return array
234
  */
235
  function get_items() {
236
+ if ( is_array($this->items) ) {
237
  return $this->items;
238
  }
239
  return array();
lib/{timber-menu-item.php → MenuItem.php} RENAMED
@@ -1,6 +1,13 @@
1
  <?php
2
 
3
- class TimberMenuItem extends TimberCore implements TimberCoreInterface {
 
 
 
 
 
 
 
4
 
5
  public $children;
6
  public $has_child_class = false;
@@ -26,13 +33,13 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
26
  */
27
  public function __construct( $data ) {
28
  $data = (object) $data;
29
- $this->import( $data );
30
- $this->import_classes( $data );
31
- if ( isset( $this->name ) ) {
32
  $this->_name = $this->name;
33
  }
34
  $this->name = $this->name();
35
- $this->add_class( 'menu-item-' . $this->ID );
36
  $this->menu_object = $data;
37
  }
38
 
@@ -49,7 +56,7 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
49
  */
50
  public function add_class( $class_name ) {
51
  $this->classes[] = $class_name;
52
- $this->class .= ' ' . $class_name;
53
  }
54
 
55
  /**
@@ -61,7 +68,7 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
61
  if ( $title = $this->title() ) {
62
  return $title;
63
  }
64
- if ( isset( $this->_name ) ) {
65
  return $this->_name;
66
  }
67
  return '';
@@ -82,10 +89,10 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
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;
@@ -96,34 +103,29 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
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 ) {
111
- if ( isset( $this->_menu_item_type ) && $this->_menu_item_type == 'custom' ) {
112
- $this->url = $this->_menu_item_url;
113
- } else if ( isset( $this->menu_object ) && method_exists( $this->menu_object, 'get_link' ) ) {
114
- $this->url = $this->menu_object->get_link();
115
- }
116
- }
117
- return $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() );
127
  }
128
 
129
  /**
@@ -133,15 +135,15 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
133
  */
134
  function add_child( $item ) {
135
  if ( !$this->has_child_class ) {
136
- $this->add_class( 'menu-item-has-children' );
137
  $this->has_child_class = true;
138
  }
139
- if ( !isset( $this->children ) ) {
140
  $this->children = array();
141
  }
142
  $this->children[] = $item;
143
  $item->level = $this->level + 1;
144
- if ($item->children) {
145
  $this->update_child_levels();
146
  }
147
  }
@@ -152,8 +154,8 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
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
  }
@@ -170,10 +172,10 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
170
  if ( is_array($data) ) {
171
  $data = (object) $data;
172
  }
173
- $this->classes = array_merge( $this->classes, $data->classes );
174
- $this->classes = array_unique( $this->classes );
175
- $this->classes = apply_filters( 'nav_menu_css_class', $this->classes, $this );
176
- $this->class = trim( implode( ' ', $this->classes ) );
177
  }
178
 
179
  /**
@@ -182,7 +184,7 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
182
  * @return array|bool
183
  */
184
  function get_children() {
185
- if ( isset( $this->children ) ) {
186
  return $this->children;
187
  }
188
  return false;
@@ -201,7 +203,7 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
201
  if ( $this->type != 'custom' ) {
202
  return false;
203
  }
204
- return TimberURLHelper::is_external( $this->url );
205
  }
206
 
207
  /**
@@ -209,10 +211,10 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
209
  * @return mixed whatever value is storied in the database
210
  */
211
  public function meta( $key ) {
212
- if ( is_object( $this->menu_object ) && method_exists( $this->menu_object, 'meta' ) ) {
213
- return $this->menu_object->meta( $key );
214
  }
215
- if ( isset( $this->$key ) ) {
216
  return $this->$key;
217
  }
218
  }
@@ -249,22 +251,14 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
249
  * @return string a full URL like http://mysite.com/thing/
250
  */
251
  public function link() {
252
- return $this->get_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
- */
266
- public function path() {
267
- return $this->get_path();
268
  }
269
 
270
  /**
@@ -275,17 +269,23 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
275
  * @return string a full URL like http://mysite.com/thing/
276
  */
277
  public function permalink() {
278
- return $this->get_link();
 
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
  */
287
- public function get_permalink() {
288
- return $this->get_link();
289
  }
290
 
291
  /**
@@ -299,9 +299,8 @@ class TimberMenuItem extends TimberCore implements TimberCoreInterface {
299
  * @return string the public label like Foo
300
  */
301
  public function title() {
302
- if ( isset( $this->__title ) ) {
303
  return $this->__title;
304
  }
305
  }
306
-
307
- }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\CoreInterface;
7
+
8
+ use Timber\URLHelper;
9
+
10
+ class MenuItem extends Core implements CoreInterface {
11
 
12
  public $children;
13
  public $has_child_class = false;
33
  */
34
  public function __construct( $data ) {
35
  $data = (object) $data;
36
+ $this->import($data);
37
+ $this->import_classes($data);
38
+ if ( isset($this->name) ) {
39
  $this->_name = $this->name;
40
  }
41
  $this->name = $this->name();
42
+ $this->add_class('menu-item-'.$this->ID);
43
  $this->menu_object = $data;
44
  }
45
 
56
  */
57
  public function add_class( $class_name ) {
58
  $this->classes[] = $class_name;
59
+ $this->class .= ' '.$class_name;
60
  }
61
 
62
  /**
68
  if ( $title = $this->title() ) {
69
  return $title;
70
  }
71
+ if ( isset($this->_name) ) {
72
  return $this->_name;
73
  }
74
  return '';
89
  * @return string the slug of the menu item kinda-like-this
90
  */
91
  public function slug() {
92
+ if ( !isset($this->master_object) ) {
93
  $this->master_object = $this->get_master_object();
94
  }
95
+ if ( isset($this->master_object->post_name) && $this->master_object->post_name ) {
96
  return $this->master_object->post_name;
97
  }
98
  return $this->post_name;
103
  * @return mixed whatever object (Post, Term, etc.) the menu item represents
104
  */
105
  protected function get_master_object() {
106
+ if ( isset($this->_menu_item_object_id) ) {
107
+ return new $this->PostClass($this->_menu_item_object_id);
108
  }
109
  }
110
 
111
  /**
112
  * @internal
113
  * @see TimberMenuItem::link
114
+ * @deprecated 1.0
115
  * @return string an absolute URL http://example.org/my-page
116
  */
117
  function get_link() {
118
+ return $this->link();
 
 
 
 
 
 
 
119
  }
120
 
121
  /**
122
  * @internal
123
  * @see TimberMenuItem::path()
124
+ * @deprecated 1.0
125
  * @return string a relative url /my-page
126
  */
127
  function get_path() {
128
+ return $this->path();
129
  }
130
 
131
  /**
135
  */
136
  function add_child( $item ) {
137
  if ( !$this->has_child_class ) {
138
+ $this->add_class('menu-item-has-children');
139
  $this->has_child_class = true;
140
  }
141
+ if ( !isset($this->children) ) {
142
  $this->children = array();
143
  }
144
  $this->children[] = $item;
145
  $item->level = $this->level + 1;
146
+ if ( $item->children ) {
147
  $this->update_child_levels();
148
  }
149
  }
154
  * @return bool
155
  */
156
  function update_child_levels() {
157
+ if ( is_array($this->children) ) {
158
+ foreach ( $this->children as $child ) {
159
  $child->level = $this->level + 1;
160
  $child->update_child_levels();
161
  }
172
  if ( is_array($data) ) {
173
  $data = (object) $data;
174
  }
175
+ $this->classes = array_merge($this->classes, $data->classes);
176
+ $this->classes = array_unique($this->classes);
177
+ $this->classes = apply_filters('nav_menu_css_class', $this->classes, $this);
178
+ $this->class = trim(implode(' ', $this->classes));
179
  }
180
 
181
  /**
184
  * @return array|bool
185
  */
186
  function get_children() {
187
+ if ( isset($this->children) ) {
188
  return $this->children;
189
  }
190
  return false;
203
  if ( $this->type != 'custom' ) {
204
  return false;
205
  }
206
+ return URLHelper::is_external($this->url);
207
  }
208
 
209
  /**
211
  * @return mixed whatever value is storied in the database
212
  */
213
  public function meta( $key ) {
214
+ if ( is_object($this->menu_object) && method_exists($this->menu_object, 'meta') ) {
215
+ return $this->menu_object->meta($key);
216
  }
217
+ if ( isset($this->$key) ) {
218
  return $this->$key;
219
  }
220
  }
251
  * @return string a full URL like http://mysite.com/thing/
252
  */
253
  public function link() {
254
+ if ( !isset($this->url) || !$this->url ) {
255
+ if ( isset($this->_menu_item_type) && $this->_menu_item_type == 'custom' ) {
256
+ $this->url = $this->_menu_item_url;
257
+ } else if ( isset($this->menu_object) && method_exists($this->menu_object, 'get_link') ) {
258
+ $this->url = $this->menu_object->get_link();
259
+ }
260
+ }
261
+ return $this->url;
 
 
 
 
 
 
 
 
262
  }
263
 
264
  /**
269
  * @return string a full URL like http://mysite.com/thing/
270
  */
271
  public function permalink() {
272
+ Helper::warn('{{item.permalink}} is deprecated, use {{item.link}} instead');
273
+ return $this->link();
274
  }
275
 
276
  /**
277
+ * Return the relative path of a Menu Item's link
278
+ * @example
279
+ * ```twig
280
+ * {% for item in menu.items %}
281
+ * <li><a href="{{ item.path }}">{{ item.title }}</a></li>
282
+ * {% endfor %}
283
+ * ```
284
+ * @see get_path()
285
+ * @return string the path of a URL like /foo
286
  */
287
+ public function path() {
288
+ return URLHelper::get_rel_url($this->link());
289
  }
290
 
291
  /**
299
  * @return string the public label like Foo
300
  */
301
  public function title() {
302
+ if ( isset($this->__title) ) {
303
  return $this->__title;
304
  }
305
  }
306
+ }
 
lib/{timber-post.php → Post.php} RENAMED
@@ -1,5 +1,17 @@
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
@@ -7,7 +19,7 @@
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
  * ```
@@ -32,22 +44,22 @@
32
  *
33
  * @package Timber
34
  */
35
- class TimberPost extends TimberCore implements TimberCoreInterface {
36
-
37
  /**
38
  * @var string $ImageClass the name of the class to handle images by default
39
  */
40
- public $ImageClass = 'TimberImage';
41
 
42
  /**
43
  * @var string $PostClass the name of the class to handle posts by default
44
  */
45
- public $PostClass = 'TimberPost';
46
 
47
  /**
48
  * @var string $TermClass the name of the class to handle terms by default
49
  */
50
- public $TermClass = 'TimberTerm';
51
 
52
  /**
53
  * @var string $object_type what does this class represent in WordPress terms?
@@ -65,25 +77,18 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
65
  */
66
  protected $_content;
67
 
68
- /**
69
- * @internal
70
- * @var array $_get_terms stores the results of a get_terms method call
71
- * @deprecated
72
- */
73
- protected $_get_terms;
74
-
75
  /**
76
  * @var string $_permalink the returned permalink from WP's get_permalink function
77
  */
78
  protected $_permalink;
79
 
80
  /**
81
- * @var array $_next stores the results of the next TimberPost in a set inside an array (in order to manage by-taxonomy)
82
  */
83
  protected $_next = array();
84
 
85
  /**
86
- * @var array $_prev stores the results of the previous TimberPost in a set inside an array (in order to manage by-taxonomy)
87
  */
88
  protected $_prev = array();
89
 
@@ -156,13 +161,13 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
156
  * If you send the constructor nothing it will try to figure out the current post id based on being inside The_Loop
157
  * @example
158
  * ```php
159
- * $post = new TimberPost();
160
- * $other_post = new TimberPost($random_post_id);
161
  * ```
162
  * @param mixed $pid
163
  */
164
- public function __construct($pid = null) {
165
- $pid = $this->determine_id( $pid );
166
  $this->init($pid);
167
  }
168
 
@@ -172,7 +177,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
172
  * @param mixed a value to test against
173
  * @return int the numberic id we should be using for this post object
174
  */
175
- protected function determine_id($pid) {
176
  global $wp_query;
177
  if ( $pid === null &&
178
  isset($wp_query->queried_object_id)
@@ -181,18 +186,18 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
181
  && is_object($wp_query->queried_object)
182
  && get_class($wp_query->queried_object) == 'WP_Post'
183
  ) {
184
- if( isset( $_GET['preview'] ) && isset( $_GET['preview_nonce'] ) && wp_verify_nonce( $_GET['preview_nonce'], 'post_preview_' . $wp_query->queried_object_id ) ) {
185
- $pid = $this->get_post_preview_id( $wp_query );
186
  } else if ( !$pid ) {
187
  $pid = $wp_query->queried_object_id;
188
  }
189
- } else if ( $pid === null && $wp_query->is_home && isset($wp_query->queried_object_id) && $wp_query->queried_object_id ) {
190
  //hack for static page as home page
191
  $pid = $wp_query->queried_object_id;
192
  } else if ( $pid === null ) {
193
  $gtid = false;
194
  $maybe_post = get_post();
195
- if ( isset($maybe_post->ID) ){
196
  $gtid = true;
197
  }
198
  if ( $gtid ) {
@@ -205,7 +210,7 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
205
  }
206
  }
207
  }
208
- if ( $pid === null && ($pid_from_loop = TimberPostGetter::loop_to_id()) ) {
209
  $pid = $pid_from_loop;
210
  }
211
  return $pid;
@@ -221,28 +226,28 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
221
 
222
  protected function get_post_preview_id( $query ) {
223
  $can = array(
224
- 'edit_' . $query->queried_object->post_type . 's',
225
  );
226
 
227
  if ( $query->queried_object->author_id !== get_current_user_id() ) {
228
- $can[] = 'edit_others_' . $query->queried_object->post_type . 's';
229
  }
230
 
231
  $can_preview = array();
232
 
233
- foreach( $can as $type ) {
234
- if( current_user_can( $type ) ) {
235
- $can_preview[] = true;
236
- }
237
  }
238
 
239
- if ( count( $can_preview ) !== count( $can ) ) {
240
- return;
241
  }
242
 
243
- $revisions = wp_get_post_revisions( $query->queried_object_id );
244
 
245
- if( !empty( $revisions ) ) {
246
  $last = end($revisions);
247
  return $last->ID;
248
  }
@@ -251,11 +256,11 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
251
  }
252
 
253
  /**
254
- * Initializes a TimberPost
255
  * @internal
256
  * @param int|bool $pid
257
  */
258
- protected function init($pid = false) {
259
  if ( $pid === false ) {
260
  $pid = get_the_ID();
261
  }
@@ -272,13 +277,13 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
272
  /**
273
  * Get the URL that will edit the current post/object
274
  * @internal
275
- * @see TimberPost::edit_link
 
 
276
  * @return bool|string
277
  */
278
  function get_edit_url() {
279
- if ( $this->can_edit() ) {
280
- return get_edit_post_link($this->ID);
281
- }
282
  }
283
 
284
  /**
@@ -342,11 +347,11 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
342
  * @param string $post_name
343
  * @return int
344
  */
345
- static function get_post_id_by_name($post_name) {
346
  global $wpdb;
347
  $query = $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_name = %s LIMIT 1", $post_name);
348
  $result = $wpdb->get_row($query);
349
- if (!$result) {
350
  return null;
351
  }
352
  return $result->ID;
@@ -368,12 +373,12 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
368
  * @param string $end The text to end the preview with (defaults to ...)
369
  * @return string of the post preview
370
  */
371
- function get_preview($len = 50, $force = false, $readmore = 'Read More', $strip = true, $end = '&hellip;') {
372
  $text = '';
373
  $trimmed = false;
374
  if ( isset($this->post_excerpt) && strlen($this->post_excerpt) ) {
375
  if ( $force ) {
376
- $text = TimberHelper::trim_words($this->post_excerpt, $len, false);
377
  $trimmed = true;
378
  } else {
379
  $text = $this->post_excerpt;
@@ -383,13 +388,13 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
383
  $pieces = explode($readmore_matches[0], $this->post_content);
384
  $text = $pieces[0];
385
  if ( $force ) {
386
- $text = TimberHelper::trim_words($text, $len, false);
387
  $trimmed = true;
388
  }
389
- $text = do_shortcode( $text );
390
  }
391
  if ( !strlen($text) ) {
392
- $text = TimberHelper::trim_words($this->get_content(), $len, false);
393
  $trimmed = true;
394
  }
395
  if ( !strlen(trim($text)) ) {
@@ -411,16 +416,16 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
411
  $text = substr($text, 0, $last_p_tag);
412
  }
413
  if ( $last != '.' && $trimmed ) {
414
- $text .= $end . ' ';
415
  }
416
  }
417
  $read_more_class = apply_filters('timber/post/get_preview/read_more_class', "read-more");
418
  if ( $readmore && isset($readmore_matches) && !empty($readmore_matches[1]) ) {
419
- $text .= ' <a href="' . $this->get_permalink() . '" class="'.$read_more_class .'">' . trim($readmore_matches[1]) . '</a>';
420
  } elseif ( $readmore ) {
421
- $text .= ' <a href="' . $this->get_permalink() . '" class="'.$read_more_class .'">' . trim($readmore) . '</a>';
422
  }
423
- if ( !$strip && $last_p_tag && ( strpos($text, '<p>') || strpos($text, '<p ') ) ) {
424
  $text .= '</p>';
425
  }
426
  }
@@ -464,252 +469,326 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
464
  }
465
 
466
  /**
467
- * @internal
468
- * @see TimberPost::thumbnail
469
- * @return null|TimberImage
470
  */
471
- function get_thumbnail() {
472
- if ( function_exists('get_post_thumbnail_id') ) {
473
- $tid = get_post_thumbnail_id($this->ID);
474
- if ( $tid ) {
475
- return new $this->ImageClass($tid);
476
- }
477
  }
 
478
  }
479
 
480
  /**
 
481
  * @internal
482
- * @see TimberPost::link
483
- * @return string
484
  */
485
- function get_permalink() {
486
- if ( isset($this->_permalink) ) {
487
- return $this->_permalink;
 
488
  }
489
- $this->_permalink = get_permalink($this->ID);
490
- return $this->_permalink;
 
 
 
 
 
 
 
 
491
  }
492
 
493
  /**
494
- * get the permalink for a post object
495
- * In your templates you should use link:
496
- * <a href="{{post.link}}">Read my post</a>
497
- * @internal
498
- * @return string
499
  */
500
- function get_link() {
501
- return $this->get_permalink();
502
  }
503
 
 
504
  /**
505
- * Get the next post in WordPress's ordering
506
- * @internal
507
- * @param bool $taxonomy
508
- * @return TimberPost|boolean
 
 
509
  */
510
- function get_next( $taxonomy = false ) {
511
- if ( !isset($this->_next) || !isset($this->_next[$taxonomy]) ) {
512
- global $post;
513
- $this->_next = array();
514
- $old_global = $post;
515
- $post = $this;
516
- if ( $taxonomy ) {
517
- $adjacent = get_adjacent_post(true, '', false, $taxonomy);
 
 
 
 
 
518
  } else {
519
- $adjacent = get_adjacent_post(false, '', false);
520
  }
 
521
 
522
- if ( $adjacent ) {
523
- $this->_next[$taxonomy] = new $this->PostClass($adjacent);
524
- } else {
525
- $this->_next[$taxonomy] = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  }
527
- $post = $old_global;
528
  }
529
- return $this->_next[$taxonomy];
530
  }
531
 
532
  /**
533
- * Get a data array of pagination so you can navigate to the previous/next for a paginated post
534
- * @return array
 
535
  */
536
- public function get_pagination() {
537
- global $post, $page, $numpages, $multipage;
538
- $post = $this;
539
- $ret = array();
540
- if ( $multipage ) {
541
- for ( $i = 1; $i <= $numpages; $i++ ) {
542
- $link = self::get_wp_link_page($i);
543
- $data = array('name' => $i, 'title' => $i, 'text' => $i, 'link' => $link);
544
- if ( $i == $page ) {
545
- $data['current'] = true;
546
  }
547
- $ret['pages'][] = $data;
548
- }
549
- $i = $page - 1;
550
- if ( $i ) {
551
- $link = self::get_wp_link_page($i);
552
- $ret['prev'] = array('link' => $link);
553
- }
554
- $i = $page + 1;
555
- if ( $i <= $numpages ) {
556
- $link = self::get_wp_link_page($i);
557
- $ret['next'] = array('link' => $link);
558
  }
 
559
  }
560
- return $ret;
561
  }
562
 
563
  /**
564
- * @param int $i
565
  * @return string
566
  */
567
- protected static function get_wp_link_page($i) {
568
- $link = _wp_link_page($i);
569
- $link = new SimpleXMLElement($link . '</a>');
570
- if ( isset($link['href']) ) {
571
- return $link['href'];
572
- }
573
- return '';
574
  }
575
-
576
  /**
577
- * Get the permalink for a post, but as a relative path
578
- * For example, where {{post.link}} would return "http://example.org/2015/07/04/my-cool-post"
579
- * this will return the relative version: "/2015/07/04/my-cool-post"
580
- * @internal
581
- * @return string
 
 
 
 
 
 
582
  */
583
- function get_path() {
584
- return TimberURLHelper::get_rel_url($this->get_link());
585
  }
586
 
587
  /**
588
- * Get the next post in WordPress's ordering
589
- * @internal
590
- * @param bool $taxonomy
591
- * @return TimberPost|boolean
592
  */
593
- function get_prev( $taxonomy = false ) {
594
- if ( isset($this->_prev) && isset($this->_prev[$taxonomy]) ) {
595
- return $this->_prev[$taxonomy];
596
- }
597
- global $post;
598
- $old_global = $post;
599
- $post = $this;
600
- $within_taxonomy = ($taxonomy) ? $taxonomy : 'category';
601
- $adjacent = get_adjacent_post(($taxonomy), '', true, $within_taxonomy);
602
- $prev_in_taxonomy = false;
603
- if ( $adjacent ) {
604
- $prev_in_taxonomy = new $this->PostClass($adjacent);
605
- }
606
- $this->_prev[$taxonomy] = $prev_in_taxonomy;
607
- $post = $old_global;
608
- return $this->_prev[$taxonomy];
609
  }
610
 
611
  /**
612
- * Get the parent post of the post
613
- * @internal
614
- * @return bool|TimberPost
615
  */
616
- function get_parent() {
617
- if ( !$this->post_parent ) {
618
- return false;
 
 
 
 
 
 
 
619
  }
620
- return new $this->PostClass($this->post_parent);
 
 
621
  }
622
 
623
  /**
624
- * Gets a User object from the author of the post
625
- * @internal
626
- * @see TimberPost::author
627
- * @return bool|TimberUser
628
  */
629
- function get_author() {
630
- if ( isset($this->post_author) ) {
631
- return new TimberUser($this->post_author);
632
- }
633
  }
634
 
635
  /**
 
636
  * @internal
637
- * @return bool|TimberUser
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  */
639
- function get_modified_author() {
640
- $user_id = get_post_meta($this->ID, '_edit_last', true);
641
- return ($user_id ? new TimberUser($user_id) : $this->get_author());
 
 
 
 
 
 
 
642
  }
643
 
 
 
644
  /**
645
- * Used internally by init, etc. to build TimberPost object
646
- * @internal
647
- * @param int $pid
648
- * @return null|object|WP_Post
649
  */
650
- protected function get_info($pid) {
651
- $post = $this->prepare_post_info($pid);
652
- if ( !isset($post->post_status) ) {
653
- return null;
654
- }
655
- $post->status = $post->post_status;
656
- $post->id = $post->ID;
657
- $post->slug = $post->post_name;
658
- $customs = $this->get_post_custom($post->ID);
659
- $post->custom = $customs;
660
- $post = (object) array_merge((array)$customs, (array)$post);
661
- return $post;
 
 
 
 
 
 
 
 
 
662
  }
663
 
664
  /**
665
- * @internal
666
- * @see TimberPost::date
667
- * @param string $date_format
668
- * @return string
 
 
 
 
 
 
669
  */
670
- function get_date( $date_format = '' ) {
671
- $df = $date_format ? $date_format : get_option('date_format');
672
- $the_date = (string)mysql2date($df, $this->post_date);
673
- return apply_filters('get_the_date', $the_date, $df);
674
  }
675
 
676
  /**
677
- * @internal
678
- * @param string $date_format
679
- * @return string
 
 
 
 
 
 
680
  */
681
- function get_modified_date( $date_format = '' ) {
682
- $df = $date_format ? $date_format : get_option('date_format');
683
- $the_time = $this->get_modified_time($df);
684
- return apply_filters('get_the_modified_date', $the_time, $date_format);
685
  }
686
 
687
  /**
688
- * @internal
689
- * @param string $time_format
690
- * @return string
691
  */
692
- function get_modified_time( $time_format = '' ) {
693
- $tf = $time_format ? $time_format : get_option('time_format');
694
- $the_time = get_post_modified_time($tf, false, $this->ID, true);
695
- return apply_filters('get_the_modified_time', $the_time, $time_format);
696
  }
697
 
698
  /**
699
- * @internal
700
- * @see TimberPost::children
701
- * @param string $post_type
702
- * @param bool|string $childPostClass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  * @return array
704
  */
705
- function get_children( $post_type = 'any', $childPostClass = false ) {
706
  if ( $childPostClass === false ) {
707
  $childPostClass = $this->PostClass;
708
  }
709
  if ( $post_type == 'parent' ) {
710
  $post_type = $this->post_type;
711
  }
712
- $children = get_children('post_parent=' . $this->ID . '&post_type=' . $post_type . '&numberposts=-1&orderby=menu_order title&order=ASC&post_status=publish');
713
  foreach ( $children as &$child ) {
714
  $child = new $childPostClass($child->ID);
715
  }
@@ -717,21 +796,28 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
717
  return $children;
718
  }
719
 
720
-
721
  /**
722
- * Get the comments for a post
723
- * @internal
724
- * @see TimberPost::comments
725
- * @param int $ct
726
- * @param string $order
727
- * @param string $type
728
- * @param string $status
729
- * @param string $CommentClass
730
- * @return array|mixed
 
 
 
 
 
 
 
 
 
 
731
  */
732
-
733
- function get_comments($ct = 0, $order = 'wp', $type = 'comment', $status = 'approve', $CommentClass = 'TimberComment') {
734
-
735
  global $overridden_cpage, $user_ID;
736
  $overridden_cpage = false;
737
 
@@ -739,50 +825,50 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
739
  $comment_author_email = $commenter['comment_author_email'];
740
 
741
  $args = array('post_id' => $this->ID, 'status' => $status, 'order' => $order);
742
- if ( $ct > 0 ) {
743
- $args['number'] = $ct;
744
  }
745
  if ( strtolower($order) == 'wp' || strtolower($order) == 'wordpress' ) {
746
  $args['order'] = get_option('comment_order');
747
  }
748
 
749
  if ( $user_ID ) {
750
- $args['include_unapproved'] = array( $user_ID );
751
- } elseif ( ! empty( $comment_author_email ) ) {
752
- $args['include_unapproved'] = array( $comment_author_email );
753
  }
754
 
755
  $comments = get_comments($args);
756
  $timber_comments = array();
757
 
758
  if ( '' == get_query_var('cpage') && get_option('page_comments') ) {
759
- set_query_var( 'cpage', 'newest' == get_option('default_comments_page') ? get_comment_pages_count() : 1 );
760
  $overridden_cpage = true;
761
  }
762
 
763
- foreach($comments as $key => &$comment) {
764
  $timber_comment = new $CommentClass($comment);
765
  $timber_comments[$timber_comment->id] = $timber_comment;
766
  }
767
 
768
  // Build a flattened (depth=1) comment tree
769
  $comments_tree = array();
770
- foreach( $timber_comments as $key => $comment ) {
771
- if ( ! $comment->is_child() ) {
772
  continue;
773
  }
774
 
775
  $tree_element = $comment;
776
  do {
777
  $tree_element = $timber_comments[$tree_element->comment_parent];
778
- } while( $tree_element->is_child() );
779
 
780
  $comments_tree[$tree_element->id][] = $comment->id;
781
  }
782
 
783
  // Add child comments to the relative "super parents"
784
- foreach($comments_tree as $comment_parent => $comment_children) {
785
- foreach($comment_children as $comment_child) {
786
  $timber_comments[$comment_parent]->children[] = $timber_comments[$comment_child];
787
  unset($timber_comments[$comment_child]);
788
  }
@@ -794,649 +880,664 @@ class TimberPost extends TimberCore implements TimberCoreInterface {
794
  }
795
 
796
  /**
797
- * Get the categories for a post
798
- * @internal
799
- * @see TimberPost::categories
800
- * @return array of TimberTerms
801
- */
802
- function get_categories() {
803
- return $this->get_terms('category');
804
- }
805
-
806
- /**
807
- * @internal
808
- * @see TimberPost::category
809
- * @return mixed
810
- */
811
- function get_category( ) {
812
- $cats = $this->get_categories();
813
- if ( count($cats) && isset($cats[0]) ) {
814
- return $cats[0];
815
- }
816
- }
817
-
818
- /**
819
- * @internal
820
- * @param string|array $tax
821
- * @param bool $merge
822
- * @param string $TermClass
823
- * @return array
824
  */
825
- function get_terms( $tax = '', $merge = true, $TermClass = '' ) {
826
-
827
- $TermClass = $TermClass ?: $this->TermClass;
828
-
829
- if ( is_string($merge) && class_exists($merge) ) {
830
- $TermClass = $merge;
831
- }
832
- if ( is_array($tax) ) {
833
- $taxonomies = $tax;
834
  }
835
- if ( is_string($tax) ) {
836
- if ( in_array($tax, array('all','any','')) ) {
837
- $taxonomies = get_object_taxonomies($this->post_type);
838
- } else {
839
- $taxonomies = array($tax);
840
- }
841
  }
842
-
843
- $term_class_objects = array();
844
-
845
- foreach ( $taxonomies as $taxonomy ) {
846
- if ( in_array($taxonomy, array('tag','tags')) ) {
847
- $taxonomy = 'post_tag';
848
- }
849
- if ( $taxonomy == 'categories' ) {
850
- $taxonomy = 'category';
851
- }
852
-
853
- $terms = wp_get_post_terms($this->ID, $taxonomy);
854
-
855
- if ( is_wp_error($terms) ) {
856
- /* @var $terms WP_Error */
857
- TimberHelper::error_log("Error retrieving terms for taxonomy '$taxonomy' on a post in timber-post.php");
858
- TimberHelper::error_log('tax = ' . print_r($tax, true));
859
- TimberHelper::error_log('WP_Error: ' . $terms->get_error_message());
860
-
861
- return $term_class_objects;
862
- }
863
-
864
- // map over array of wordpress terms, and transform them into instances of the TermClass
865
- $terms = array_map(function($term) use ($TermClass, $taxonomy) {
866
- return call_user_func(array($TermClass, 'from'), $term->term_id, $taxonomy);
867
- }, $terms);
868
-
869
- if ( $merge && is_array($terms) ) {
870
- $term_class_objects = array_merge($term_class_objects, $terms);
871
- } else if ( count($terms) ) {
872
- $term_class_objects[$taxonomy] = $terms;
873
  }
874
  }
875
- return $term_class_objects;
876
- }
877
-
878
- /**
879
- * @param string|int $term_name_or_id
880
- * @param string $taxonomy
881
- * @return bool
882
- */
883
- function has_term( $term_name_or_id, $taxonomy = 'all' ) {
884
- if ( $taxonomy == 'all' || $taxonomy == 'any' ) {
885
- $taxes = get_object_taxonomies($this->post_type, 'names');
886
- $ret = false;
887
- foreach ( $taxes as $tax ) {
888
- if ( has_term($term_name_or_id, $tax, $this->ID) ) {
889
- $ret = true;
890
- break;
891
- }
892
- }
893
- return $ret;
894
  }
895
- return has_term($term_name_or_id, $taxonomy, $this->ID);
896
  }
897
 
898
  /**
899
- * @param string $field
900
- * @return TimberImage
901
  */
902
- function get_image( $field ) {
903
- return new $this->ImageClass($this->$field);
 
904
  }
905
 
906
  /**
907
- * Gets an array of tags for you to use
908
- * @internal
909
  * @example
910
  * ```twig
911
- * <ul class="tags">
912
- * {% for tag in post.tags %}
913
- * <li>{{tag.name}}</li>
914
- * {% endfor %}
915
- * </ul>
916
  * ```
917
- * @return array
 
 
 
 
 
 
 
918
  */
919
- function get_tags() {
920
- return $this->get_terms('post_tag');
 
 
921
  }
922
 
923
  /**
924
- * Outputs the title with filters applied
925
- * @internal
926
  * @example
927
  * ```twig
928
- * <h1>{{post.get_title}}</h1>
 
 
929
  * ```
 
930
  * ```html
931
- * <h1>Hello World!</h1>
 
 
932
  * ```
 
933
  * @return string
934
  */
935
- function get_title() {
936
- return apply_filters('the_title', $this->post_title, $this->ID);
 
 
937
  }
938
 
939
  /**
940
- * Displays the content of the post with filters, shortcodes and wpautop applied
941
- * @example
942
- * ```twig
943
- * <div class="article-text">{{post.get_content}}</div>
944
- * ```
945
- * ```html
946
- * <div class="article-text"><p>Blah blah blah</p><p>More blah blah blah.</p></div>
947
- * ```
948
- * @param int $len
949
- * @param int $page
950
- * @return string
951
  */
952
- function get_content( $len = 0, $page = 0 ) {
953
- if ( $len == 0 && $page == 0 && $this->_content ) {
954
- return $this->_content;
955
- }
956
- $content = $this->post_content;
957
- if ( $len ) {
958
- $content = wp_trim_words($content, $len);
959
- }
960
- if ( $page ) {
961
- $contents = explode('<!--nextpage-->', $content);
962
- $page--;
963
- if ( count($contents) > $page ) {
964
- $content = $contents[$page];
965
- }
966
- }
967
- $content = apply_filters('the_content', ($content));
968
- if ( $len == 0 && $page == 0 ) {
969
- $this->_content = $content;
970
  }
971
- return $content;
972
  }
973
 
974
  /**
975
- * @return string
 
976
  */
977
- function get_paged_content() {
978
- global $page;
979
- return $this->get_content(0, $page);
980
  }
 
981
  /**
982
- *
983
- * Here is my summary
984
  * @example
985
  * ```twig
986
- * This post is from <span>{{ post.get_post_type.labels.plural }}</span>
987
- * ```
988
- *
989
- * ```html
990
- * This post is from <span>Recipes</span>
991
  * ```
 
 
 
 
 
 
 
 
 
 
 
 
992
  * @return mixed
993
  */
994
- public function get_post_type() {
995
- return get_post_type_object($this->post_type);
 
 
 
 
996
  }
997
 
998
  /**
999
- * @return int the number of comments on a post
1000
  */
1001
- public function get_comment_count() {
1002
- return get_comments_number($this->ID);
1003
  }
1004
 
1005
  /**
1006
- * @param string $field_name
1007
- * @return mixed
1008
  */
1009
- public function get_field( $field_name ) {
1010
- $value = apply_filters('timber_post_get_meta_field_pre', null, $this->ID, $field_name, $this);
1011
- if ( $value === null ) {
1012
- $value = get_post_meta($this->ID, $field_name);
1013
- if ( is_array($value) && count($value) == 1 ) {
1014
- $value = $value[0];
1015
- }
1016
- if ( is_array($value) && count($value) == 0 ) {
1017
- $value = null;
1018
- }
1019
- }
1020
- $value = apply_filters('timber_post_get_meta_field', $value, $this->ID, $field_name, $this);
1021
- return $value;
1022
  }
1023
 
1024
  /**
1025
- * @param string $field_name
 
1026
  */
1027
- function import_field( $field_name ) {
1028
- $this->$field_name = $this->get_field($field_name);
1029
  }
1030
 
1031
  /**
1032
- * @internal
 
1033
  * @return mixed
1034
  */
1035
- function get_format() {
1036
- return get_post_format($this->ID);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1037
  }
1038
 
1039
  /**
1040
- * Get the CSS classes for a post. For usage you should use `{{post.class}}` instead of `{{post.post_class}}`
1041
- * @internal
1042
- * @param string $class additional classes you want to add
1043
- * @see TimberPost::$class
1044
- * @example
1045
- * ```twig
1046
- * <article class="{{ post.class }}">
1047
- * {# Some stuff here #}
1048
- * </article>
1049
- * ```
1050
- *
1051
- * ```html
1052
- * <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">
1053
- * {# Some stuff here #}
1054
- * </article>
1055
- * ```
1056
- * @return string a space-seperated list of classes
1057
  */
1058
- public function post_class( $class='' ) {
1059
- global $post;
1060
- $old_global_post = $post;
1061
  $post = $this;
1062
- $class_array = get_post_class($class, $this->ID);
1063
- $post = $old_global_post;
1064
- if ( is_array($class_array) ){
1065
- return implode(' ', $class_array);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1066
  }
1067
- return $class_array;
1068
  }
1069
 
1070
- // Docs
1071
 
1072
  /**
1073
- * @return array
1074
- * @codeCoverageIgnore
1075
  */
1076
- public function get_method_values() {
1077
- $ret = parent::get_method_values();
1078
- $ret['author'] = $this->author();
1079
- $ret['categories'] = $this->categories();
1080
- $ret['category'] = $this->category();
1081
- $ret['children'] = $this->children();
1082
- $ret['comments'] = $this->comments();
1083
- $ret['content'] = $this->content();
1084
- $ret['edit_link'] = $this->edit_link();
1085
- $ret['format'] = $this->format();
1086
- $ret['link'] = $this->link();
1087
- $ret['next'] = $this->next();
1088
- $ret['pagination'] = $this->pagination();
1089
- $ret['parent'] = $this->parent();
1090
- $ret['path'] = $this->path();
1091
- $ret['prev'] = $this->prev();
1092
- $ret['terms'] = $this->terms();
1093
- $ret['tags'] = $this->tags();
1094
- $ret['thumbnail'] = $this->thumbnail();
1095
- $ret['title'] = $this->title();
1096
- return $ret;
1097
  }
1098
 
1099
  /**
1100
- * Return the author of a post
1101
  * @api
1102
  * @example
1103
  * ```twig
1104
- * <h1>{{post.title}}</h1>
1105
- * <p class="byline">
1106
- * <a href="{{post.author.link}}">{{post.author.name}}</a>
1107
- * </p>
1108
  * ```
1109
- * @return TimberUser|bool A TimberUser object if found, false if not
 
1110
  */
1111
- public function author() {
1112
- return $this->get_author();
 
 
 
1113
  }
1114
 
1115
  /**
1116
- * Get the author (WordPress user) who last modified the post
 
 
1117
  * @example
1118
  * ```twig
1119
- * Last updated by {{ post.modified_author.name }}
1120
- * ```
1121
- * ```html
1122
- * Last updated by Harper Lee
1123
  * ```
1124
- * @return TimberUser|bool A TimberUser object if found, false if not
1125
  */
1126
- public function modified_author() {
1127
- return $this->get_modified_author();
1128
  }
1129
 
1130
  /**
1131
- * Get the categoires on a particular post
1132
  * @api
1133
- * @return array of TimberTerms
 
 
 
 
 
 
 
1134
  */
1135
- public function categories() {
1136
- return $this->get_terms('category');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1137
  }
1138
 
1139
  /**
1140
- * Returns a category attached to a post
1141
  * @api
1142
- * If mulitpuile categories are set, it will return just the first one
1143
- * @return TimberTerm|null
1144
  */
1145
- public function category() {
1146
- return $this->get_category();
1147
  }
1148
 
1149
  /**
1150
- * Returns an array of children on the post as TimberPosts
1151
- * (or other claass as you define).
1152
  * @api
1153
  * @example
1154
  * ```twig
1155
- * {% if post.children %}
1156
- * Here are the child pages:
1157
- * {% for child in page.children %}
1158
- * <a href="{{ child.link }}">{{ child.title }}</a>
1159
- * {% endfor %}
1160
- * {% endif %}
1161
  * ```
1162
- * @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
1163
- * @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.
1164
- * @return array
1165
  */
1166
- public function children( $post_type = 'any', $childPostClass = false ) {
1167
- return $this->get_children( $post_type, $childPostClass );
 
 
 
 
 
 
1168
  }
1169
 
1170
  /**
1171
- * Gets the comments on a TimberPost and returns them as an array of [TimberComments](#TimberComment) (or whatever comment class you set).
1172
  * @api
1173
- * @param int $count Set the number of comments you want to get. `0` is analogous to "all"
1174
- * @param string $order use ordering set in WordPress admin, or a different scheme
1175
- * @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
1176
- * @param string $status Could be 'pending', etc.
1177
- * @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)
1178
  * @example
1179
  * ```twig
1180
- * {# single.twig #}
1181
- * <h4>Comments:</h4>
1182
- * {% for comment in post.comments %}
1183
- * <div class="comment-{{comment.ID}} comment-order-{{loop.index}}">
1184
- * <p>{{comment.author.name}} said:</p>
1185
- * <p>{{comment.content}}</p>
1186
- * </div>
1187
- * {% endfor %}
1188
  * ```
1189
- * @return bool|array
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1190
  */
1191
- public function comments( $count = 0, $order = 'wp', $type = 'comment', $status = 'approve', $CommentClass = 'TimberComment' ) {
1192
- return $this->get_comments($count, $order, $type, $status, $CommentClass);
1193
  }
1194
 
1195
  /**
1196
- * 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).
1197
- * @api
1198
- * @example
1199
- * ```twig
1200
- * <div class="article">
1201
- * <h2>{{post.title}}</h2>
1202
- * <div class="content">{{ post.content }}</div>
1203
- * </div>
1204
- * ```
1205
- * @param int $page
1206
- * @return string
1207
  */
1208
- public function content( $page = 0 ) {
1209
- return $this->get_content(0, $page);
 
 
 
1210
  }
1211
 
1212
  /**
1213
- * @return string
 
1214
  */
1215
- public function paged_content() {
1216
- return $this->get_paged_content();
1217
  }
1218
 
1219
  /**
1220
- * Get the date to use in your template!
1221
- * @api
 
 
1222
  * @example
1223
  * ```twig
1224
- * Published on {{ post.date }} // Uses WP's formatting set in Admin
1225
- * OR
1226
- * Published on {{ post.date | date('F jS') }} // Jan 12th
1227
- * ```
1228
- *
1229
- * ```html
1230
- * Published on January 12, 2015
1231
- * OR
1232
- * Published on Jan 12th
1233
  * ```
1234
- * @param string $date_format
1235
- * @return string
1236
  */
1237
- public function date( $date_format = '' ) {
1238
- return $this->get_date($date_format);
1239
  }
1240
 
1241
  /**
1242
- * Get the time to use in your template
1243
- * @api
 
 
1244
  * @example
1245
  * ```twig
1246
- * Published at {{ post.time }} // Uses WP's formatting set in Admin
1247
- * OR
1248
- * Published at {{ post.time | time('G:i') }} // 13:25
1249
  * ```
1250
- *
1251
  * ```html
1252
- * Published at 1:25 pm
1253
- * OR
1254
- * Published at 13:25
1255
  * ```
1256
- * @param string $time_format
1257
  * @return string
1258
  */
1259
- public function time( $time_format = '' ) {
1260
- $tf = $time_format ? $time_format : get_option('time_format');
1261
- $the_time = (string)mysql2date($tf, $this->post_date);
1262
- return apply_filters('get_the_time', $the_time, $tf);
1263
  }
1264
 
1265
- /**
1266
- * @return bool|string
1267
- */
1268
- public function edit_link() {
1269
- return $this->get_edit_url();
 
 
 
 
 
 
 
 
 
 
 
 
 
1270
  }
1271
 
1272
  /**
1273
- * @api
 
1274
  * @return mixed
1275
  */
1276
- public function format() {
1277
- return $this->get_format();
1278
  }
1279
 
1280
  /**
1281
- * get the permalink for a post object
1282
- * @api
1283
- * @example
1284
- * ```twig
1285
- * <a href="{{post.link}}">Read my post</a>
1286
- * ```
1287
- * @return string ex: http://example.org/2015/07/my-awesome-post
 
1288
  */
1289
- public function link() {
1290
- return $this->get_permalink();
1291
  }
1292
 
1293
  /**
1294
- * @param string $field_name
1295
- * @return mixed
 
1296
  */
1297
- public function meta( $field_name = null ) {
1298
- if ( $field_name === null ) {
1299
- //on the off-chance the field is actually named meta
1300
- $field_name = 'meta';
1301
- }
1302
- return $this->get_field($field_name);
 
 
 
 
 
 
 
 
 
1303
  }
1304
 
1305
  /**
 
 
 
 
 
1306
  * @return string
1307
  */
1308
- public function name(){
1309
- return $this->title();
1310
  }
1311
 
1312
  /**
1313
- * @param string $date_format
 
1314
  * @return string
1315
  */
1316
- public function modified_date( $date_format = '' ) {
1317
- return $this->get_modified_date($date_format);
 
 
1318
  }
1319
 
1320
  /**
1321
- * @param string $time_format
1322
- * @return string
 
 
 
 
 
1323
  */
1324
- public function modified_time( $time_format = '' ) {
1325
- return $this->get_modified_time($time_format);
 
 
 
 
 
 
 
 
 
 
 
 
 
1326
  }
1327
 
1328
  /**
1329
- * @api
1330
- * @param bool $in_same_cat
1331
- * @return mixed
 
 
 
1332
  */
1333
- public function next( $in_same_cat = false ) {
1334
- return $this->get_next($in_same_cat);
1335
  }
1336
 
1337
  /**
1338
- * @return array
 
 
 
 
1339
  */
1340
- public function pagination() {
1341
- return $this->get_pagination();
1342
  }
1343
 
1344
  /**
1345
- * Gets the parent (if one exists) from a post as a TimberPost object (or whatever is set in TimberPost::$PostClass)
1346
- * @api
1347
- * @example
1348
- * ```twig
1349
- * Parent page: <a href="{{ post.parent.link }}">{{ post.parent.title }}</a>
1350
- * ```
1351
- * @return bool|TimberPost
1352
  */
1353
- public function parent() {
1354
- return $this->get_parent();
 
 
1355
  }
1356
 
1357
  /**
1358
- * Gets the relative path of a WP Post, so while link() will return http://example.org/2015/07/my-cool-post
1359
- * this will return just /2015/07/my-cool-post
1360
- * @api
1361
- * @example
1362
- * ```twig
1363
- * <a href="{{post.path}}">{{post.title}}</a>
1364
- * ```
1365
- * @return string
1366
  */
1367
- public function path() {
1368
- return $this->get_path();
 
 
 
 
 
 
 
 
 
 
 
1369
  }
1370
 
1371
  /**
1372
- * @deprecated 0.20.0 use link() instead
 
 
 
1373
  * @return string
1374
  */
1375
- public function permalink() {
1376
- return $this->get_permalink();
1377
  }
1378
 
1379
  /**
1380
- * Get the previous post in a set
1381
- * @api
1382
- * @example
1383
- * ```twig
1384
- * <h4>Prior Entry:</h4>
1385
- * <h3>{{post.prev.title}}</h3>
1386
- * <p>{{post.prev.get_preview(25)}}</p>
1387
- * ```
1388
- * @param bool $in_same_cat
1389
- * @return mixed
1390
  */
1391
- public function prev( $in_same_cat = false ) {
1392
- return $this->get_prev($in_same_cat);
1393
  }
1394
 
1395
  /**
1396
- * Get the terms associated with the post
1397
- * This goes across all taxonomies by default
1398
- * @api
1399
- * @param string|array $tax What taxonom(y|ies) to pull from. Defaults to all registered taxonomies for the post type. 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.
1400
- * @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)?
1401
- * @return array
1402
  */
1403
- public function terms( $tax = '', $merge = true ) {
1404
- return $this->get_terms($tax, $merge);
1405
  }
1406
 
1407
  /**
1408
- * Gets the tags on a post, uses WP's post_tag taxonomy
1409
- * @api
 
 
 
1410
  * @return array
1411
  */
1412
- public function tags() {
1413
- return $this->get_tags();
1414
  }
1415
 
1416
- /**
1417
- * get the featured image as a TimberImage
1418
- * @api
1419
- * @example
1420
- * ```twig
1421
- * <img src="{{post.thumbnail.src}}" />
1422
- * ```
1423
- * @return TimberImage|null of your thumbnail
1424
- */
1425
- public function thumbnail() {
1426
- return $this->get_thumbnail();
1427
- }
1428
 
1429
  /**
1430
- * 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.
1431
- * @api
1432
- * @example
1433
- * ```twig
1434
- * <h1>{{ post.title }}</h1>
1435
- * ```
1436
- * @return string
 
 
1437
  */
1438
- public function title() {
1439
- return $this->get_title();
1440
  }
1441
 
1442
  }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\CoreInterface;
7
+
8
+ use Timber\Term;
9
+ use Timber\User;
10
+ use Timber\Image;
11
+ use Timber\Helper;
12
+ use Timber\URLHelper;
13
+ use Timber\PostGetter;
14
+
15
  /**
16
  * 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.
17
  * @example
19
  * <?php
20
  * // single.php, see connected twig example
21
  * $context = Timber::get_context();
22
+ * $context['post'] = new Timber\Post(); // It's a new Timber\Post object, but an existing post from WordPress.
23
  * Timber::render('single.twig', $context);
24
  * ?>
25
  * ```
44
  *
45
  * @package Timber
46
  */
47
+ class Post extends Core implements CoreInterface {
48
+
49
  /**
50
  * @var string $ImageClass the name of the class to handle images by default
51
  */
52
+ public $ImageClass = 'Timber\Image';
53
 
54
  /**
55
  * @var string $PostClass the name of the class to handle posts by default
56
  */
57
+ public $PostClass = 'Timber\Post';
58
 
59
  /**
60
  * @var string $TermClass the name of the class to handle terms by default
61
  */
62
+ public $TermClass = 'Timber\Term';
63
 
64
  /**
65
  * @var string $object_type what does this class represent in WordPress terms?
77
  */
78
  protected $_content;
79
 
 
 
 
 
 
 
 
80
  /**
81
  * @var string $_permalink the returned permalink from WP's get_permalink function
82
  */
83
  protected $_permalink;
84
 
85
  /**
86
+ * @var array $_next stores the results of the next Timber\Post in a set inside an array (in order to manage by-taxonomy)
87
  */
88
  protected $_next = array();
89
 
90
  /**
91
+ * @var array $_prev stores the results of the previous Timber\Post in a set inside an array (in order to manage by-taxonomy)
92
  */
93
  protected $_prev = array();
94
 
161
  * If you send the constructor nothing it will try to figure out the current post id based on being inside The_Loop
162
  * @example
163
  * ```php
164
+ * $post = new Timber\Post();
165
+ * $other_post = new Timber\Post($random_post_id);
166
  * ```
167
  * @param mixed $pid
168
  */
169
+ public function __construct( $pid = null ) {
170
+ $pid = $this->determine_id($pid);
171
  $this->init($pid);
172
  }
173
 
177
  * @param mixed a value to test against
178
  * @return int the numberic id we should be using for this post object
179
  */
180
+ protected function determine_id( $pid ) {
181
  global $wp_query;
182
  if ( $pid === null &&
183
  isset($wp_query->queried_object_id)
186
  && is_object($wp_query->queried_object)
187
  && get_class($wp_query->queried_object) == 'WP_Post'
188
  ) {
189
+ if ( isset($_GET['preview']) && isset($_GET['preview_nonce']) && wp_verify_nonce($_GET['preview_nonce'], 'post_preview_'.$wp_query->queried_object_id) ) {
190
+ $pid = $this->get_post_preview_id($wp_query);
191
  } else if ( !$pid ) {
192
  $pid = $wp_query->queried_object_id;
193
  }
194
+ } else if ( $pid === null && $wp_query->is_home && isset($wp_query->queried_object_id) && $wp_query->queried_object_id ) {
195
  //hack for static page as home page
196
  $pid = $wp_query->queried_object_id;
197
  } else if ( $pid === null ) {
198
  $gtid = false;
199
  $maybe_post = get_post();
200
+ if ( isset($maybe_post->ID) ) {
201
  $gtid = true;
202
  }
203
  if ( $gtid ) {
210
  }
211
  }
212
  }
213
+ if ( $pid === null && ($pid_from_loop = PostGetter::loop_to_id()) ) {
214
  $pid = $pid_from_loop;
215
  }
216
  return $pid;
226
 
227
  protected function get_post_preview_id( $query ) {
228
  $can = array(
229
+ 'edit_'.$query->queried_object->post_type.'s',
230
  );
231
 
232
  if ( $query->queried_object->author_id !== get_current_user_id() ) {
233
+ $can[] = 'edit_others_'.$query->queried_object->post_type.'s';
234
  }
235
 
236
  $can_preview = array();
237
 
238
+ foreach ( $can as $type ) {
239
+ if ( current_user_can($type) ) {
240
+ $can_preview[] = true;
241
+ }
242
  }
243
 
244
+ if ( count($can_preview) !== count($can) ) {
245
+ return;
246
  }
247
 
248
+ $revisions = wp_get_post_revisions($query->queried_object_id);
249
 
250
+ if ( !empty($revisions) ) {
251
  $last = end($revisions);
252
  return $last->ID;
253
  }
256
  }
257
 
258
  /**
259
+ * Initializes a Post
260
  * @internal
261
  * @param int|bool $pid
262
  */
263
+ protected function init( $pid = false ) {
264
  if ( $pid === false ) {
265
  $pid = get_the_ID();
266
  }
277
  /**
278
  * Get the URL that will edit the current post/object
279
  * @internal
280
+ * @deprecated since 1.0
281
+ * @codeCoverageIgnore
282
+ * @see Timber\Post::edit_link
283
  * @return bool|string
284
  */
285
  function get_edit_url() {
286
+ return $this->edit_link();
 
 
287
  }
288
 
289
  /**
347
  * @param string $post_name
348
  * @return int
349
  */
350
+ static function get_post_id_by_name( $post_name ) {
351
  global $wpdb;
352
  $query = $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE post_name = %s LIMIT 1", $post_name);
353
  $result = $wpdb->get_row($query);
354
+ if ( !$result ) {
355
  return null;
356
  }
357
  return $result->ID;
373
  * @param string $end The text to end the preview with (defaults to ...)
374
  * @return string of the post preview
375
  */
376
+ function get_preview( $len = 50, $force = false, $readmore = 'Read More', $strip = true, $end = '&hellip;' ) {
377
  $text = '';
378
  $trimmed = false;
379
  if ( isset($this->post_excerpt) && strlen($this->post_excerpt) ) {
380
  if ( $force ) {
381
+ $text = Helper::trim_words($this->post_excerpt, $len, false);
382
  $trimmed = true;
383
  } else {
384
  $text = $this->post_excerpt;
388
  $pieces = explode($readmore_matches[0], $this->post_content);
389
  $text = $pieces[0];
390
  if ( $force ) {
391
+ $text = Helper::trim_words($text, $len, false);
392
  $trimmed = true;
393
  }
394
+ $text = do_shortcode($text);
395
  }
396
  if ( !strlen($text) ) {
397
+ $text = Helper::trim_words($this->get_content(), $len, false);
398
  $trimmed = true;
399
  }
400
  if ( !strlen(trim($text)) ) {
416
  $text = substr($text, 0, $last_p_tag);
417
  }
418
  if ( $last != '.' && $trimmed ) {
419
+ $text .= $end.' ';
420
  }
421
  }
422
  $read_more_class = apply_filters('timber/post/get_preview/read_more_class', "read-more");
423
  if ( $readmore && isset($readmore_matches) && !empty($readmore_matches[1]) ) {
424
+ $text .= ' <a href="'.$this->link().'" class="'.$read_more_class.'">'.trim($readmore_matches[1]).'</a>';
425
  } elseif ( $readmore ) {
426
+ $text .= ' <a href="'.$this->link().'" class="'.$read_more_class.'">'.trim($readmore).'</a>';
427
  }
428
+ if ( !$strip && $last_p_tag && (strpos($text, '<p>') || strpos($text, '<p ')) ) {
429
  $text .= '</p>';
430
  }
431
  }
469
  }
470
 
471
  /**
472
+ * @param int $i
473
+ * @return string
 
474
  */
475
+ protected static function get_wp_link_page( $i ) {
476
+ $link = _wp_link_page($i);
477
+ $link = new \SimpleXMLElement($link.'</a>');
478
+ if ( isset($link['href']) ) {
479
+ return $link['href'];
 
480
  }
481
+ return '';
482
  }
483
 
484
  /**
485
+ * Used internally by init, etc. to build TimberPost object
486
  * @internal
487
+ * @param int $pid
488
+ * @return null|object|WP_Post
489
  */
490
+ protected function get_info( $pid ) {
491
+ $post = $this->prepare_post_info($pid);
492
+ if ( !isset($post->post_status) ) {
493
+ return null;
494
  }
495
+
496
+ do_action_ref_array( 'the_post', array( &$post, &$GLOBALS['wp_query'] ) );
497
+
498
+ $post->status = $post->post_status;
499
+ $post->id = $post->ID;
500
+ $post->slug = $post->post_name;
501
+ $customs = $this->get_post_custom($post->ID);
502
+ $post->custom = $customs;
503
+ $post = (object) array_merge((array) $customs, (array) $post);
504
+ return $post;
505
  }
506
 
507
  /**
508
+ *
509
+ * Gets the comment form for use on a single article page
510
+ * @param array $args this $args thing is a fucking mess, [fix at some point](http://codex.wordpress.org/Function_Reference/comment_form)
511
+ * @return string of HTML for the form
 
512
  */
513
+ public function comment_form( $args = array() ) {
514
+ return Helper::get_comment_form( $this->ID, $args );
515
  }
516
 
517
+
518
  /**
519
+ * Get the terms associated with the post
520
+ * This goes across all taxonomies by default
521
+ * @api
522
+ * @param string|array $tax What taxonom(y|ies) to pull from. Defaults to all registered taxonomies for the post type. 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.
523
+ * @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)?
524
+ * @return array
525
  */
526
+ public function terms( $tax = '', $merge = true, $TermClass = '' ) {
527
+
528
+ $TermClass = $TermClass ?: $this->TermClass;
529
+
530
+ if ( is_string($merge) && class_exists($merge) ) {
531
+ $TermClass = $merge;
532
+ }
533
+ if ( is_array($tax) ) {
534
+ $taxonomies = $tax;
535
+ }
536
+ if ( is_string($tax) ) {
537
+ if ( in_array($tax, array('all', 'any', '')) ) {
538
+ $taxonomies = get_object_taxonomies($this->post_type);
539
  } else {
540
+ $taxonomies = array($tax);
541
  }
542
+ }
543
 
544
+ $term_class_objects = array();
545
+
546
+ foreach ( $taxonomies as $taxonomy ) {
547
+ if ( in_array($taxonomy, array('tag', 'tags')) ) {
548
+ $taxonomy = 'post_tag';
549
+ }
550
+ if ( $taxonomy == 'categories' ) {
551
+ $taxonomy = 'category';
552
+ }
553
+
554
+ $terms = wp_get_post_terms($this->ID, $taxonomy);
555
+
556
+ if ( is_wp_error($terms) ) {
557
+ /* @var $terms WP_Error */
558
+ Helper::error_log("Error retrieving terms for taxonomy '$taxonomy' on a post in timber-post.php");
559
+ Helper::error_log('tax = '.print_r($tax, true));
560
+ Helper::error_log('WP_Error: '.$terms->get_error_message());
561
+
562
+ return $term_class_objects;
563
+ }
564
+
565
+ // map over array of wordpress terms, and transform them into instances of the TermClass
566
+ $terms = array_map(function( $term ) use ($TermClass, $taxonomy) {
567
+ return call_user_func(array($TermClass, 'from'), $term->term_id, $taxonomy);
568
+ }, $terms);
569
+
570
+ if ( $merge && is_array($terms) ) {
571
+ $term_class_objects = array_merge($term_class_objects, $terms);
572
+ } else if ( count($terms) ) {
573
+ $term_class_objects[$taxonomy] = $terms;
574
  }
 
575
  }
576
+ return $term_class_objects;
577
  }
578
 
579
  /**
580
+ * @param string|int $term_name_or_id
581
+ * @param string $taxonomy
582
+ * @return bool
583
  */
584
+ function has_term( $term_name_or_id, $taxonomy = 'all' ) {
585
+ if ( $taxonomy == 'all' || $taxonomy == 'any' ) {
586
+ $taxes = get_object_taxonomies($this->post_type, 'names');
587
+ $ret = false;
588
+ foreach ( $taxes as $tax ) {
589
+ if ( has_term($term_name_or_id, $tax, $this->ID) ) {
590
+ $ret = true;
591
+ break;
 
 
592
  }
 
 
 
 
 
 
 
 
 
 
 
593
  }
594
+ return $ret;
595
  }
596
+ return has_term($term_name_or_id, $taxonomy, $this->ID);
597
  }
598
 
599
  /**
 
600
  * @return string
601
  */
602
+ function get_paged_content() {
603
+ return $this->paged_content();
 
 
 
 
 
604
  }
 
605
  /**
606
+ *
607
+ * Here is my summary
608
+ * @example
609
+ * ```twig
610
+ * This post is from <span>{{ post.get_post_type.labels.plural }}</span>
611
+ * ```
612
+ *
613
+ * ```html
614
+ * This post is from <span>Recipes</span>
615
+ * ```
616
+ * @return mixed
617
  */
618
+ public function get_post_type() {
619
+ return get_post_type_object($this->post_type);
620
  }
621
 
622
  /**
623
+ * @return int the number of comments on a post
 
 
 
624
  */
625
+ public function get_comment_count() {
626
+ return get_comments_number($this->ID);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
  }
628
 
629
  /**
630
+ * @param string $field_name
631
+ * @return mixed
 
632
  */
633
+ public function get_field( $field_name ) {
634
+ $value = apply_filters('timber_post_get_meta_field_pre', null, $this->ID, $field_name, $this);
635
+ if ( $value === null ) {
636
+ $value = get_post_meta($this->ID, $field_name);
637
+ if ( is_array($value) && count($value) == 1 ) {
638
+ $value = $value[0];
639
+ }
640
+ if ( is_array($value) && count($value) == 0 ) {
641
+ $value = null;
642
+ }
643
  }
644
+ $value = apply_filters('timber_post_get_meta_field', $value, $this->ID, $field_name, $this);
645
+ $value = $this->convert($value, __CLASS__);
646
+ return $value;
647
  }
648
 
649
  /**
650
+ * @param string $field_name
 
 
 
651
  */
652
+ function import_field( $field_name ) {
653
+ $this->$field_name = $this->get_field($field_name);
 
 
654
  }
655
 
656
  /**
657
+ * Get the CSS classes for a post. For usage you should use `{{post.class}}` instead of `{{post.post_class}}`
658
  * @internal
659
+ * @param string $class additional classes you want to add
660
+ * @see Timber\Post::$class
661
+ * @example
662
+ * ```twig
663
+ * <article class="{{ post.class }}">
664
+ * {# Some stuff here #}
665
+ * </article>
666
+ * ```
667
+ *
668
+ * ```html
669
+ * <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">
670
+ * {# Some stuff here #}
671
+ * </article>
672
+ * ```
673
+ * @return string a space-seperated list of classes
674
  */
675
+ public function post_class( $class = '' ) {
676
+ global $post;
677
+ $old_global_post = $post;
678
+ $post = $this;
679
+ $class_array = get_post_class($class, $this->ID);
680
+ $post = $old_global_post;
681
+ if ( is_array($class_array) ) {
682
+ return implode(' ', $class_array);
683
+ }
684
+ return $class_array;
685
  }
686
 
687
+ // Docs
688
+
689
  /**
690
+ * @return array
691
+ * @codeCoverageIgnore
 
 
692
  */
693
+ public function get_method_values() {
694
+ $ret = parent::get_method_values();
695
+ $ret['author'] = $this->author();
696
+ $ret['categories'] = $this->categories();
697
+ $ret['category'] = $this->category();
698
+ $ret['children'] = $this->children();
699
+ $ret['comments'] = $this->comments();
700
+ $ret['content'] = $this->content();
701
+ $ret['edit_link'] = $this->edit_link();
702
+ $ret['format'] = $this->format();
703
+ $ret['link'] = $this->link();
704
+ $ret['next'] = $this->next();
705
+ $ret['pagination'] = $this->pagination();
706
+ $ret['parent'] = $this->parent();
707
+ $ret['path'] = $this->path();
708
+ $ret['prev'] = $this->prev();
709
+ $ret['terms'] = $this->terms();
710
+ $ret['tags'] = $this->tags();
711
+ $ret['thumbnail'] = $this->thumbnail();
712
+ $ret['title'] = $this->title();
713
+ return $ret;
714
  }
715
 
716
  /**
717
+ * Return the author of a post
718
+ * @api
719
+ * @example
720
+ * ```twig
721
+ * <h1>{{post.title}}</h1>
722
+ * <p class="byline">
723
+ * <a href="{{post.author.link}}">{{post.author.name}}</a>
724
+ * </p>
725
+ * ```
726
+ * @return TimberUser|bool A TimberUser object if found, false if not
727
  */
728
+ public function author() {
729
+ return $this->get_author();
 
 
730
  }
731
 
732
  /**
733
+ * Get the author (WordPress user) who last modified the post
734
+ * @example
735
+ * ```twig
736
+ * Last updated by {{ post.modified_author.name }}
737
+ * ```
738
+ * ```html
739
+ * Last updated by Harper Lee
740
+ * ```
741
+ * @return TimberUser|bool A TimberUser object if found, false if not
742
  */
743
+ public function modified_author() {
744
+ $user_id = get_post_meta($this->ID, '_edit_last', true);
745
+ return ($user_id ? new User($user_id) : $this->get_author());
 
746
  }
747
 
748
  /**
749
+ * Get the categoires on a particular post
750
+ * @api
751
+ * @return array of TimberTerms
752
  */
753
+ public function categories() {
754
+ return $this->get_terms('category');
 
 
755
  }
756
 
757
  /**
758
+ * Returns a category attached to a post
759
+ * @api
760
+ * If mulitpuile categories are set, it will return just the first one
761
+ * @return TimberTerm|null
762
+ */
763
+ public function category() {
764
+ return $this->get_category();
765
+ }
766
+
767
+ /**
768
+ * Returns an array of children on the post as Timber\Posts
769
+ * (or other claass as you define).
770
+ * @api
771
+ * @example
772
+ * ```twig
773
+ * {% if post.children %}
774
+ * Here are the child pages:
775
+ * {% for child in page.children %}
776
+ * <a href="{{ child.link }}">{{ child.title }}</a>
777
+ * {% endfor %}
778
+ * {% endif %}
779
+ * ```
780
+ * @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
781
+ * @param string|bool $childPostClass _optional_ a custom post class (ex: 'MyTimber\Post') to return the objects as. By default (false) it will use Timber\Post::$post_class value.
782
  * @return array
783
  */
784
+ public function children( $post_type = 'any', $childPostClass = false ) {
785
  if ( $childPostClass === false ) {
786
  $childPostClass = $this->PostClass;
787
  }
788
  if ( $post_type == 'parent' ) {
789
  $post_type = $this->post_type;
790
  }
791
+ $children = get_children('post_parent='.$this->ID.'&post_type='.$post_type.'&numberposts=-1&orderby=menu_order title&order=ASC&post_status=publish');
792
  foreach ( $children as &$child ) {
793
  $child = new $childPostClass($child->ID);
794
  }
796
  return $children;
797
  }
798
 
 
799
  /**
800
+ * Gets the comments on a Timber\Post and returns them as an array of [TimberComments](#TimberComment) (or whatever comment class you set).
801
+ * @api
802
+ * @param int $count Set the number of comments you want to get. `0` is analogous to "all"
803
+ * @param string $order use ordering set in WordPress admin, or a different scheme
804
+ * @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
805
+ * @param string $status Could be 'pending', etc.
806
+ * @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)
807
+ * @example
808
+ * ```twig
809
+ * {# single.twig #}
810
+ * <h4>Comments:</h4>
811
+ * {% for comment in post.comments %}
812
+ * <div class="comment-{{comment.ID}} comment-order-{{loop.index}}">
813
+ * <p>{{comment.author.name}} said:</p>
814
+ * <p>{{comment.content}}</p>
815
+ * </div>
816
+ * {% endfor %}
817
+ * ```
818
+ * @return bool|array
819
  */
820
+ public function comments( $count = 0, $order = 'wp', $type = 'comment', $status = 'approve', $CommentClass = 'TimberComment' ) {
 
 
821
  global $overridden_cpage, $user_ID;
822
  $overridden_cpage = false;
823
 
825
  $comment_author_email = $commenter['comment_author_email'];
826
 
827
  $args = array('post_id' => $this->ID, 'status' => $status, 'order' => $order);
828
+ if ( $count > 0 ) {
829
+ $args['number'] = $count;
830
  }
831
  if ( strtolower($order) == 'wp' || strtolower($order) == 'wordpress' ) {
832
  $args['order'] = get_option('comment_order');
833
  }
834
 
835
  if ( $user_ID ) {
836
+ $args['include_unapproved'] = array($user_ID);
837
+ } elseif ( !empty($comment_author_email) ) {
838
+ $args['include_unapproved'] = array($comment_author_email);
839
  }
840
 
841
  $comments = get_comments($args);
842
  $timber_comments = array();
843
 
844
  if ( '' == get_query_var('cpage') && get_option('page_comments') ) {
845
+ set_query_var('cpage', 'newest' == get_option('default_comments_page') ? get_comment_pages_count() : 1);
846
  $overridden_cpage = true;
847
  }
848
 
849
+ foreach ( $comments as $key => &$comment ) {
850
  $timber_comment = new $CommentClass($comment);
851
  $timber_comments[$timber_comment->id] = $timber_comment;
852
  }
853
 
854
  // Build a flattened (depth=1) comment tree
855
  $comments_tree = array();
856
+ foreach ( $timber_comments as $key => $comment ) {
857
+ if ( !$comment->is_child() ) {
858
  continue;
859
  }
860
 
861
  $tree_element = $comment;
862
  do {
863
  $tree_element = $timber_comments[$tree_element->comment_parent];
864
+ } while ( $tree_element->is_child() );
865
 
866
  $comments_tree[$tree_element->id][] = $comment->id;
867
  }
868
 
869
  // Add child comments to the relative "super parents"
870
+ foreach ( $comments_tree as $comment_parent => $comment_children ) {
871
+ foreach ( $comment_children as $comment_child ) {
872
  $timber_comments[$comment_parent]->children[] = $timber_comments[$comment_child];
873
  unset($timber_comments[$comment_child]);
874
  }
880
  }
881
 
882
  /**
883
+ * 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).
884
+ * @api
885
+ * @example
886
+ * ```twig
887
+ * <div class="article">
888
+ * <h2>{{post.title}}</h2>
889
+ * <div class="content">{{ post.content }}</div>
890
+ * </div>
891
+ * ```
892
+ * @param int $page
893
+ * @return string
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
894
  */
895
+ public function content( $page = 0, $len = -1 ) {
896
+ if ( $len == -1 && $page == 0 && $this->_content ) {
897
+ return $this->_content;
 
 
 
 
 
 
898
  }
899
+ $content = $this->post_content;
900
+ if ( $len > 0 ) {
901
+ $content = wp_trim_words($content, $len);
 
 
 
902
  }
903
+ if ( $page ) {
904
+ $contents = explode('<!--nextpage-->', $content);
905
+ $page--;
906
+ if ( count($contents) > $page ) {
907
+ $content = $contents[$page];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
908
  }
909
  }
910
+ $content = apply_filters('the_content', ($content));
911
+ if ( $len == -1 && $page == 0 ) {
912
+ $this->_content = $content;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
  }
914
+ return $content;
915
  }
916
 
917
  /**
918
+ * @return string
 
919
  */
920
+ public function paged_content() {
921
+ global $page;
922
+ return $this->content($page, -1);
923
  }
924
 
925
  /**
926
+ * Get the date to use in your template!
927
+ * @api
928
  * @example
929
  * ```twig
930
+ * Published on {{ post.date }} // Uses WP's formatting set in Admin
931
+ * OR
932
+ * Published on {{ post.date | date('F jS') }} // Jan 12th
 
 
933
  * ```
934
+ *
935
+ * ```html
936
+ * Published on January 12, 2015
937
+ * OR
938
+ * Published on Jan 12th
939
+ * ```
940
+ * @param string $date_format
941
+ * @return string
942
  */
943
+ public function date( $date_format = '' ) {
944
+ $df = $date_format ? $date_format : get_option('date_format');
945
+ $the_date = (string) mysql2date($df, $this->post_date);
946
+ return apply_filters('get_the_date', $the_date, $df);
947
  }
948
 
949
  /**
950
+ * Get the time to use in your template
951
+ * @api
952
  * @example
953
  * ```twig
954
+ * Published at {{ post.time }} // Uses WP's formatting set in Admin
955
+ * OR
956
+ * Published at {{ post.time | time('G:i') }} // 13:25
957
  * ```
958
+ *
959
  * ```html
960
+ * Published at 1:25 pm
961
+ * OR
962
+ * Published at 13:25
963
  * ```
964
+ * @param string $time_format
965
  * @return string
966
  */
967
+ public function time( $time_format = '' ) {
968
+ $tf = $time_format ? $time_format : get_option('time_format');
969
+ $the_time = (string) mysql2date($tf, $this->post_date);
970
+ return apply_filters('get_the_time', $the_time, $tf);
971
  }
972
 
973
  /**
974
+ * Returns the edit URL of a post if the user has access to it
975
+ * @return bool|string the edit URL of a post in the WordPress admin
 
 
 
 
 
 
 
 
 
976
  */
977
+ public function edit_link() {
978
+ if ( $this->can_edit() ) {
979
+ return get_edit_post_link($this->ID);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
980
  }
 
981
  }
982
 
983
  /**
984
+ * @api
985
+ * @return mixed
986
  */
987
+ public function format() {
988
+ return get_post_format($this->ID);
 
989
  }
990
+
991
  /**
992
+ * get the permalink for a post object
993
+ * @api
994
  * @example
995
  * ```twig
996
+ * <a href="{{post.link}}">Read my post</a>
 
 
 
 
997
  * ```
998
+ * @return string ex: http://example.org/2015/07/my-awesome-post
999
+ */
1000
+ public function link() {
1001
+ if ( isset($this->_permalink) ) {
1002
+ return $this->_permalink;
1003
+ }
1004
+ $this->_permalink = get_permalink($this->ID);
1005
+ return $this->_permalink;
1006
+ }
1007
+
1008
+ /**
1009
+ * @param string $field_name
1010
  * @return mixed
1011
  */
1012
+ public function meta( $field_name = null ) {
1013
+ if ( $field_name === null ) {
1014
+ //on the off-chance the field is actually named meta
1015
+ $field_name = 'meta';
1016
+ }
1017
+ return $this->get_field($field_name);
1018
  }
1019
 
1020
  /**
1021
+ * @return string
1022
  */
1023
+ public function name() {
1024
+ return $this->title();
1025
  }
1026
 
1027
  /**
1028
+ * @param string $date_format
1029
+ * @return string
1030
  */
1031
+ public function modified_date( $date_format = '' ) {
1032
+ $df = $date_format ? $date_format : get_option('date_format');
1033
+ $the_time = $this->get_modified_time($df);
1034
+ return apply_filters('get_the_modified_date', $the_time, $date_format);
 
 
 
 
 
 
 
 
 
1035
  }
1036
 
1037
  /**
1038
+ * @param string $time_format
1039
+ * @return string
1040
  */
1041
+ public function modified_time( $time_format = '' ) {
1042
+ return $this->get_modified_time($time_format);
1043
  }
1044
 
1045
  /**
1046
+ * @api
1047
+ * @param bool $in_same_cat
1048
  * @return mixed
1049
  */
1050
+ public function next( $in_same_term = false ) {
1051
+ if ( !isset($this->_next) || !isset($this->_next[$in_same_term]) ) {
1052
+ global $post;
1053
+ $this->_next = array();
1054
+ $old_global = $post;
1055
+ $post = $this;
1056
+ if ( $in_same_term ) {
1057
+ $adjacent = get_adjacent_post(true, '', false, $in_same_term);
1058
+ } else {
1059
+ $adjacent = get_adjacent_post(false, '', false);
1060
+ }
1061
+
1062
+ if ( $adjacent ) {
1063
+ $this->_next[$in_same_term] = new $this->PostClass($adjacent);
1064
+ } else {
1065
+ $this->_next[$in_same_term] = false;
1066
+ }
1067
+ $post = $old_global;
1068
+ }
1069
+ return $this->_next[$in_same_term];
1070
  }
1071
 
1072
  /**
1073
+ * Get a data array of pagination so you can navigate to the previous/next for a paginated post
1074
+ * @return array
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1075
  */
1076
+ public function pagination() {
1077
+ global $post, $page, $numpages, $multipage;
 
1078
  $post = $this;
1079
+ $ret = array();
1080
+ if ( $multipage ) {
1081
+ for ( $i = 1; $i <= $numpages; $i++ ) {
1082
+ $link = self::get_wp_link_page($i);
1083
+ $data = array('name' => $i, 'title' => $i, 'text' => $i, 'link' => $link);
1084
+ if ( $i == $page ) {
1085
+ $data['current'] = true;
1086
+ }
1087
+ $ret['pages'][] = $data;
1088
+ }
1089
+ $i = $page - 1;
1090
+ if ( $i ) {
1091
+ $link = self::get_wp_link_page($i);
1092
+ $ret['prev'] = array('link' => $link);
1093
+ }
1094
+ $i = $page + 1;
1095
+ if ( $i <= $numpages ) {
1096
+ $link = self::get_wp_link_page($i);
1097
+ $ret['next'] = array('link' => $link);
1098
+ }
1099
  }
1100
+ return $ret;
1101
  }
1102
 
1103
+
1104
 
1105
  /**
1106
+ * Finds any WP_Post objects and converts them to Timber\Posts
1107
+ * @param array $data
1108
  */
1109
+ public function convert( $data, $class ) {
1110
+ if ( is_array($data) ) {
1111
+ $func = __FUNCTION__;
1112
+ foreach ( $data as &$ele ) {
1113
+ if ( gettype($ele) === 'array' ) {
1114
+ $ele = $this->$func($ele, $class);
1115
+ } else {
1116
+ if ( $ele instanceof WP_Post ) {
1117
+ $ele = new $class($ele);
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ return $data;
 
 
 
 
 
 
1124
  }
1125
 
1126
  /**
1127
+ * Gets the parent (if one exists) from a post as a Timber\Post object (or whatever is set in Timber\Post::$PostClass)
1128
  * @api
1129
  * @example
1130
  * ```twig
1131
+ * Parent page: <a href="{{ post.parent.link }}">{{ post.parent.title }}</a>
 
 
 
1132
  * ```
1133
+ * @return bool|Timber\Post
1134
+ * @param string $field_name
1135
  */
1136
+ public function parent() {
1137
+ if ( !$this->post_parent ) {
1138
+ return false;
1139
+ }
1140
+ return new $this->PostClass($this->post_parent);
1141
  }
1142
 
1143
  /**
1144
+ * Gets the relative path of a WP Post, so while link() will return http://example.org/2015/07/my-cool-post
1145
+ * this will return just /2015/07/my-cool-post
1146
+ * @api
1147
  * @example
1148
  * ```twig
1149
+ * <a href="{{post.path}}">{{post.title}}</a>
 
 
 
1150
  * ```
1151
+ * @return string
1152
  */
1153
+ public function path() {
1154
+ return URLHelper::get_rel_url($this->get_link());
1155
  }
1156
 
1157
  /**
1158
+ * Get the previous post in a set
1159
  * @api
1160
+ * @example
1161
+ * ```twig
1162
+ * <h4>Prior Entry:</h4>
1163
+ * <h3>{{post.prev.title}}</h3>
1164
+ * <p>{{post.prev.get_preview(25)}}</p>
1165
+ * ```
1166
+ * @param bool $in_same_term
1167
+ * @return mixed
1168
  */
1169
+ public function prev( $in_same_term = false ) {
1170
+ if ( isset($this->_prev) && isset($this->_prev[$in_same_term]) ) {
1171
+ return $this->_prev[$in_same_term];
1172
+ }
1173
+ global $post;
1174
+ $old_global = $post;
1175
+ $post = $this;
1176
+ $within_taxonomy = ($in_same_term) ? $in_same_term : 'category';
1177
+ $adjacent = get_adjacent_post(($in_same_term), '', true, $within_taxonomy);
1178
+ $prev_in_taxonomy = false;
1179
+ if ( $adjacent ) {
1180
+ $prev_in_taxonomy = new $this->PostClass($adjacent);
1181
+ }
1182
+ $this->_prev[$in_same_term] = $prev_in_taxonomy;
1183
+ $post = $old_global;
1184
+ return $this->_prev[$in_same_term];
1185
  }
1186
 
1187
  /**
1188
+ * Gets the tags on a post, uses WP's post_tag taxonomy
1189
  * @api
1190
+ * @return array
 
1191
  */
1192
+ public function tags() {
1193
+ return $this->get_tags();
1194
  }
1195
 
1196
  /**
1197
+ * get the featured image as a TimberImage
 
1198
  * @api
1199
  * @example
1200
  * ```twig
1201
+ * <img src="{{post.thumbnail.src}}" />
 
 
 
 
 
1202
  * ```
1203
+ * @return TimberImage|null of your thumbnail
 
 
1204
  */
1205
+ public function thumbnail() {
1206
+ if ( function_exists('get_post_thumbnail_id') ) {
1207
+ $tid = get_post_thumbnail_id($this->ID);
1208
+ if ( $tid ) {
1209
+ //return new Image($tid);
1210
+ return new $this->ImageClass($tid);
1211
+ }
1212
+ }
1213
  }
1214
 
1215
  /**
1216
+ * 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.
1217
  * @api
 
 
 
 
 
1218
  * @example
1219
  * ```twig
1220
+ * <h1>{{ post.title }}</h1>
 
 
 
 
 
 
 
1221
  * ```
1222
+ * @return string
1223
+ */
1224
+ public function title() {
1225
+ return apply_filters('the_title', $this->post_title, $this->ID);
1226
+ }
1227
+
1228
+ /**
1229
+ *
1230
+ * ===================================
1231
+ * DEPRECATED FUNCTIONS LIVE DOWN HERE
1232
+ * ===================================
1233
+ *
1234
+ */
1235
+
1236
+ /**
1237
+ * Get the categories for a post
1238
+ * @internal
1239
+ * @deprecated since 1.0
1240
+ * @codeCoverageIgnore
1241
+ * @see Timber\Post::categories
1242
+ * @return array of TimberTerms
1243
  */
1244
+ function get_categories() {
1245
+ return $this->get_terms('category');
1246
  }
1247
 
1248
  /**
1249
+ * @internal
1250
+ * @deprecated since 1.0
1251
+ * @codeCoverageIgnore
1252
+ * @see Timber\Post::category
1253
+ * @return mixed
 
 
 
 
 
 
1254
  */
1255
+ function get_category( ) {
1256
+ $cats = $this->get_categories();
1257
+ if ( count($cats) && isset($cats[0]) ) {
1258
+ return $cats[0];
1259
+ }
1260
  }
1261
 
1262
  /**
1263
+ * @param string $field
1264
+ * @return TimberImage
1265
  */
1266
+ function get_image( $field ) {
1267
+ return new $this->ImageClass($this->$field);
1268
  }
1269
 
1270
  /**
1271
+ * Gets an array of tags for you to use
1272
+ * @internal
1273
+ * @deprecated since 1.0
1274
+ * @codeCoverageIgnore
1275
  * @example
1276
  * ```twig
1277
+ * <ul class="tags">
1278
+ * {% for tag in post.tags %}
1279
+ * <li>{{tag.name}}</li>
1280
+ * {% endfor %}
1281
+ * </ul>
 
 
 
 
1282
  * ```
1283
+ * @return array
 
1284
  */
1285
+ function get_tags() {
1286
+ return $this->get_terms('post_tag');
1287
  }
1288
 
1289
  /**
1290
+ * Outputs the title with filters applied
1291
+ * @internal
1292
+ * @deprecated since 1.0
1293
+ * @codeCoverageIgnore
1294
  * @example
1295
  * ```twig
1296
+ * <h1>{{post.get_title}}</h1>
 
 
1297
  * ```
 
1298
  * ```html
1299
+ * <h1>Hello World!</h1>
 
 
1300
  * ```
 
1301
  * @return string
1302
  */
1303
+ function get_title() {
1304
+ return $this->title();
 
 
1305
  }
1306
 
1307
+ /**
1308
+ * Displays the content of the post with filters, shortcodes and wpautop applied
1309
+ * @example
1310
+ * ```twig
1311
+ * <div class="article-text">{{post.get_content}}</div>
1312
+ * ```
1313
+ * ```html
1314
+ * <div class="article-text"><p>Blah blah blah</p><p>More blah blah blah.</p></div>
1315
+ * ```
1316
+ * @param int $len
1317
+ * @param int $page
1318
+ * @return string
1319
+ */
1320
+ function get_content( $len = -1, $page = 0 ) {
1321
+ if ( $len === 0 ) {
1322
+ $len = -1;
1323
+ }
1324
+ return $this->content($page, $len);
1325
  }
1326
 
1327
  /**
1328
+ * @internal
1329
+ * @deprecated since 1.0
1330
  * @return mixed
1331
  */
1332
+ function get_format() {
1333
+ return $this->format();
1334
  }
1335
 
1336
  /**
1337
+ * Get the terms associated with the post
1338
+ * This goes across all taxonomies by default
1339
+ * @internal
1340
+ * @deprecated since 1.0
1341
+ * @codeCoverageIgnore
1342
+ * @param string|array $tax What taxonom(y|ies) to pull from. Defaults to all registered taxonomies for the post type. 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.
1343
+ * @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)?
1344
+ * @return array
1345
  */
1346
+ public function get_terms( $tax = '', $merge = true ) {
1347
+ return $this->terms($tax, $merge);
1348
  }
1349
 
1350
  /**
1351
+ * @deprecated 0.20.0 use link() instead
1352
+ * @codeCoverageIgnore
1353
+ * @return string
1354
  */
1355
+ public function permalink() {
1356
+ Helper::warn('post.permalink has been removed, please use post.link');
1357
+ return $this->link();
1358
+ }
1359
+
1360
+ /**
1361
+ * @internal
1362
+ * @see Timber\Post::date
1363
+ * @deprecated since 1.0
1364
+ * @codeCoverageIgnore
1365
+ * @param string $date_format
1366
+ * @return string
1367
+ */
1368
+ function get_date( $date_format = '' ) {
1369
+ return $this->date($date_format);
1370
  }
1371
 
1372
  /**
1373
+ * @internal
1374
+ * @see Timber\Post::modified_date
1375
+ * @deprecated since 1.0
1376
+ * @codeCoverageIgnore
1377
+ * @param string $date_format
1378
  * @return string
1379
  */
1380
+ function get_modified_date( $date_format = '' ) {
1381
+ return $this->modified_date($date_format);
1382
  }
1383
 
1384
  /**
1385
+ * @internal
1386
+ * @param string $time_format
1387
  * @return string
1388
  */
1389
+ function get_modified_time( $time_format = '' ) {
1390
+ $tf = $time_format ? $time_format : get_option('time_format');
1391
+ $the_time = get_post_modified_time($tf, false, $this->ID, true);
1392
+ return apply_filters('get_the_modified_time', $the_time, $time_format);
1393
  }
1394
 
1395
  /**
1396
+ * @internal
1397
+ * @see Timber\Post::children
1398
+ * @deprecated since 1.0
1399
+ * @codeCoverageIgnore
1400
+ * @param string $post_type
1401
+ * @param bool|string $childPostClass
1402
+ * @return array
1403
  */
1404
+ function get_children( $post_type = 'any', $childPostClass = false ) {
1405
+ return $this->children($post_type, $childPostClass);
1406
+ }
1407
+
1408
+ /**
1409
+ * Get the permalink for a post, but as a relative path
1410
+ * For example, where {{post.link}} would return "http://example.org/2015/07/04/my-cool-post"
1411
+ * this will return the relative version: "/2015/07/04/my-cool-post"
1412
+ * @internal
1413
+ * @deprecated since 1.0
1414
+ * @codeCoverageIgnore
1415
+ * @return string
1416
+ */
1417
+ function get_path() {
1418
+ return $this->path();
1419
  }
1420
 
1421
  /**
1422
+ * Get the next post in WordPress's ordering
1423
+ * @internal
1424
+ * @deprecated since 1.0
1425
+ * @codeCoverageIgnore
1426
+ * @param bool $taxonomy
1427
+ * @return TimberPost|boolean
1428
  */
1429
+ function get_prev( $in_same_term = false ) {
1430
+ return $this->prev($in_same_term);
1431
  }
1432
 
1433
  /**
1434
+ * Get the parent post of the post
1435
+ * @internal
1436
+ * @deprecated since 1.0
1437
+ * @codeCoverageIgnore
1438
+ * @return bool|TimberPost
1439
  */
1440
+ function get_parent() {
1441
+ return $this->parent();
1442
  }
1443
 
1444
  /**
1445
+ * Gets a User object from the author of the post
1446
+ * @internal
1447
+ * @deprecated since 1.0
1448
+ * @codeCoverageIgnore
1449
+ * @see TimberPost::author
1450
+ * @return bool|TimberUser
 
1451
  */
1452
+ function get_author() {
1453
+ if ( isset($this->post_author) ) {
1454
+ return new User($this->post_author);
1455
+ }
1456
  }
1457
 
1458
  /**
1459
+ * @internal
1460
+ * @deprecated since 1.0
1461
+ * @codeCoverageIgnore
1462
+ * @return bool|TimberUser
 
 
 
 
1463
  */
1464
+ function get_modified_author() {
1465
+ return $this->modified_author();
1466
+ }
1467
+
1468
+ /**
1469
+ * @internal
1470
+ * @see TimberPost::thumbnail
1471
+ * @deprecated since 1.0
1472
+ * @codeCoverageIgnore
1473
+ * @return null|TimberImage
1474
+ */
1475
+ function get_thumbnail() {
1476
+ return $this->thumbnail();
1477
  }
1478
 
1479
  /**
1480
+ * @internal
1481
+ * @see TimberPost::link
1482
+ * @deprecated since 1.0
1483
+ * @codeCoverageIgnore
1484
  * @return string
1485
  */
1486
+ function get_permalink() {
1487
+ return $this->link();
1488
  }
1489
 
1490
  /**
1491
+ * get the permalink for a post object
1492
+ * In your templates you should use link:
1493
+ * <a href="{{post.link}}">Read my post</a>
1494
+ * @internal
1495
+ * @deprecated since 1.0
1496
+ * @codeCoverageIgnore
1497
+ * @return string
 
 
 
1498
  */
1499
+ function get_link() {
1500
+ return $this->get_permalink();
1501
  }
1502
 
1503
  /**
1504
+ * Get the next post in WordPress's ordering
1505
+ * @internal
1506
+ * @deprecated since 1.0
1507
+ * @codeCoverageIgnore
1508
+ * @param bool $taxonomy
1509
+ * @return TimberPost|boolean
1510
  */
1511
+ function get_next( $taxonomy = false ) {
1512
+ return $this->next($taxonomy);
1513
  }
1514
 
1515
  /**
1516
+ * Get a data array of pagination so you can navigate to the previous/next for a paginated post
1517
+ * @internal
1518
+ * @see Timber\Post::pagination();
1519
+ * @deprecated since 1.0
1520
+ * @codeCoverageIgnore
1521
  * @return array
1522
  */
1523
+ public function get_pagination() {
1524
+ return $this->pagination();
1525
  }
1526
 
 
 
 
 
 
 
 
 
 
 
 
 
1527
 
1528
  /**
1529
+ * Get the comments for a post
1530
+ * @internal
1531
+ * @see Timber\Post::comments
1532
+ * @param int $count
1533
+ * @param string $order
1534
+ * @param string $type
1535
+ * @param string $status
1536
+ * @param string $CommentClass
1537
+ * @return array|mixed
1538
  */
1539
+ function get_comments( $count = 0, $order = 'wp', $type = 'comment', $status = 'approve', $CommentClass = 'TimberComment' ) {
1540
+ return $this->comments($count, $order, $type, $status, $CommentClass);
1541
  }
1542
 
1543
  }
lib/PostGetter.php ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\PostsCollection;
6
+ use Timber\QueryIterator;
7
+
8
+ class PostGetter {
9
+
10
+ /**
11
+ * @param mixed $query
12
+ * @param string $PostClass
13
+ * @return array|bool|null
14
+ */
15
+ static function get_post( $query = false, $PostClass = '\Timber\Post' ) {
16
+ $posts = self::get_posts($query, $PostClass);
17
+ if ( $post = reset($posts) ) {
18
+ return $post;
19
+ }
20
+ }
21
+
22
+ static function get_posts( $query = false, $PostClass = '\Timber\Post', $return_collection = false ) {
23
+ $posts = self::query_posts($query, $PostClass);
24
+ return apply_filters('timber_post_getter_get_posts', $posts->get_posts($return_collection));
25
+ }
26
+
27
+ static function query_post( $query = false, $PostClass = '\Timber\Post' ) {
28
+ $posts = self::query_posts($query, $PostClass);
29
+ if ( method_exists($posts, 'current') && $post = $posts->current() ) {
30
+ return $post;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * @param mixed $query
36
+ * @param string $PostClass
37
+ * @return array|bool|null
38
+ */
39
+ static function query_posts( $query = false, $PostClass = '\Timber\Post' ) {
40
+ if ( $type = self::get_class_for_use_as_timber_post($query) ) {
41
+ $PostClass = $type;
42
+ if ( self::is_post_class_or_class_map($query) ) {
43
+ $query = false;
44
+ }
45
+ }
46
+
47
+ if ( is_object($query) && !is_a($query, 'WP_Query') ) {
48
+ // The only object other than a query is a type of post object
49
+ $query = array($query);
50
+ }
51
+
52
+ if ( is_array($query) && count($query) && isset($query[0]) && is_object($query[0]) ) {
53
+ // We have an array of post objects that already have data
54
+ return new PostsCollection($query, $PostClass);
55
+ } else {
56
+ // We have a query (of sorts) to work with
57
+ $tqi = new QueryIterator($query, $PostClass);
58
+ return $tqi;
59
+ }
60
+ }
61
+
62
+ static function get_pids( $query ) {
63
+ $posts = self::get_posts($query);
64
+ $pids = array();
65
+ foreach ( $posts as $post ) {
66
+ if ( isset($post->ID) ) {
67
+ $pids[] = $post->ID;
68
+ }
69
+ }
70
+ return $pids;
71
+ }
72
+
73
+ static function loop_to_id() {
74
+ if ( !self::wp_query_has_posts() ) { return false; }
75
+
76
+ global $wp_query;
77
+ $post_num = property_exists($wp_query, 'current_post')
78
+ ? $wp_query->current_post + 1
79
+ : 0
80
+ ;
81
+
82
+ if ( !isset($wp_query->posts[$post_num]) ) { return false; }
83
+
84
+ return $wp_query->posts[$post_num]->ID;
85
+ }
86
+
87
+ /**
88
+ * @return bool
89
+ */
90
+ static function wp_query_has_posts() {
91
+ global $wp_query;
92
+ return ($wp_query && property_exists($wp_query, 'posts') && $wp_query->posts);
93
+ }
94
+
95
+ /**
96
+ * @param string|array $arg
97
+ * @return bool
98
+ */
99
+ static function is_post_class_or_class_map( $arg ) {
100
+ $maybe_type = self::get_class_for_use_as_timber_post($arg);
101
+ if ( is_array($arg) && isset($arg['post_type']) ) {
102
+ //the user has passed a true WP_Query-style query array that needs to be used later, so the $arg is not a class map or post class
103
+ return false;
104
+ }
105
+ if ( $maybe_type ) {
106
+ return true;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * @param string|array $arg
112
+ * @return string|bool if a $type is found; false if not
113
+ */
114
+ static function get_class_for_use_as_timber_post( $arg ) {
115
+ $type = false;
116
+
117
+ if ( is_string($arg) ) {
118
+ $type = $arg;
119
+ } else if ( is_array($arg) && isset($arg['post_type']) ) {
120
+ $type = $arg['post_type'];
121
+ }
122
+
123
+ if ( !$type ) {
124
+ return false;
125
+ }
126
+
127
+ if ( !is_array($type) && class_exists($type) && is_subclass_of($type, '\Timber\Post') ) {
128
+ return $type;
129
+ }
130
+ }
131
+ }
lib/PostsCollection.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\Helper;
6
+ use Timber\Post;
7
+
8
+ // Exit if accessed directly
9
+ if ( !defined('ABSPATH') )
10
+ exit;
11
+
12
+ class PostsCollection extends \ArrayObject {
13
+
14
+ public function __construct( $posts = array(), $post_class = '\Timber\Post' ) {
15
+ $returned_posts = array();
16
+ if ( is_null($posts) ) {
17
+ $posts = array();
18
+ }
19
+ foreach ( $posts as $post_object ) {
20
+ $post_class_use = $post_class;
21
+ if ( is_array($post_class) ) {
22
+ $post_type = get_post_type($post_object);
23
+ $post_class_use = '\Timber\Post';
24
+
25
+ if ( isset($post_class[$post_type]) ) {
26
+ $post_class_use = $post_class[$post_type];
27
+
28
+ } else {
29
+ if ( is_array($post_class) ) {
30
+ Helper::error_log($post_type.' of '.$post_object->ID.' not found in '.print_r($post_class, true));
31
+ } else {
32
+ Helper::error_log($post_type.' not found in '.$post_class);
33
+ }
34
+ }
35
+ }
36
+ // Don't create yet another object if $post_object is already of the right type
37
+ if ( is_a($post_object, $post_class_use) ) {
38
+ $post = $post_object;
39
+ } else {
40
+ $post = new $post_class_use($post_object);
41
+ }
42
+
43
+ if ( isset($post->ID) ) {
44
+ $returned_posts[] = $post;
45
+ }
46
+ }
47
+
48
+ $returned_posts = self::maybe_set_preview($returned_posts);
49
+
50
+ parent::__construct($returned_posts, $flags = 0, 'Timber\PostsIterator');
51
+ }
52
+
53
+ public function get_posts() {
54
+ return $this->getArrayCopy();
55
+ }
56
+
57
+ /**
58
+ * @param array $posts
59
+ * @return array
60
+ */
61
+ static function maybe_set_preview( $posts ) {
62
+ if ( is_array($posts) && isset($_GET['preview']) && $_GET['preview']
63
+ && isset($_GET['preview_id']) && $_GET['preview_id']
64
+ && current_user_can('edit_post', $_GET['preview_id']) ) {
65
+ // No need to check the nonce, that already happened in _show_post_preview on init
66
+
67
+ $preview_id = $_GET['preview_id'];
68
+ foreach ( $posts as &$post ) {
69
+ if ( is_object($post) && $post->ID == $preview_id ) {
70
+ // Based on _set_preview( $post ), but adds import_custom
71
+ $preview = wp_get_post_autosave($preview_id);
72
+ if ( is_object($preview) ) {
73
+
74
+ $preview = sanitize_post($preview);
75
+
76
+ $post->post_content = $preview->post_content;
77
+ $post->post_title = $preview->post_title;
78
+ $post->post_excerpt = $preview->post_excerpt;
79
+ $post->import_custom($preview_id);
80
+
81
+ add_filter('get_the_terms', '_wp_preview_terms_filter', 10, 3);
82
+ }
83
+ }
84
+ }
85
+
86
+ }
87
+
88
+ return $posts;
89
+ }
90
+
91
+ }
92
+
93
+ class PostsIterator extends \ArrayIterator {
94
+
95
+ public function current() {
96
+ global $post;
97
+ $post = parent::current();
98
+ return $post;
99
+ }
100
+ }
lib/QueryIterator.php ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\Helper;
6
+ use Timber\PostsCollection;
7
+
8
+ // Exit if accessed directly
9
+ if ( !defined('ABSPATH') )
10
+ exit;
11
+
12
+ class QueryIterator implements \Iterator {
13
+
14
+ /**
15
+ *
16
+ *
17
+ * @var WP_Query
18
+ */
19
+ private $_query = null;
20
+ private $_posts_class = 'TimberPost';
21
+
22
+ public function __construct( $query = false, $posts_class = 'TimberPost' ) {
23
+ add_action('pre_get_posts', array($this, 'fix_number_posts_wp_quirk'));
24
+ if ( $posts_class )
25
+ $this->_posts_class = $posts_class;
26
+
27
+ if ( is_a($query, 'WP_Query') ) {
28
+ // We got a full-fledged WP Query, look no further!
29
+ $the_query = $query;
30
+
31
+ } elseif ( false === $query ) {
32
+ // If query is explicitly set to false, use the main loop
33
+ global $wp_query;
34
+ $the_query = & $wp_query;
35
+ //if we're on a custom posts page?
36
+ $the_query = self::handle_maybe_custom_posts_page($the_query);
37
+ } elseif ( Helper::is_array_assoc($query) || (is_string($query) && strstr($query, '=')) ) {
38
+ // We have a regularly formed WP query string or array to use
39
+ $the_query = new \WP_Query($query);
40
+
41
+ } elseif ( is_numeric($query) || is_string($query) ) {
42
+ // We have what could be a post name or post ID to pull out
43
+ $the_query = self::get_query_from_string($query);
44
+
45
+ } elseif ( is_array($query) && count($query) && (is_integer($query[0]) || is_string($query[0])) ) {
46
+ // We have a list of pids (post IDs) to extract from
47
+ $the_query = self::get_query_from_array_of_ids($query);
48
+ } elseif ( is_array($query) && empty($query) ) {
49
+ // it's an empty array
50
+ $the_query = array();
51
+ } else {
52
+ Helper::error_log('I have failed you! in '.basename(__FILE__).'::'.__LINE__);
53
+ Helper::error_log($query);
54
+
55
+ // We have failed hard, at least let get something.
56
+ $the_query = new \WP_Query();
57
+ }
58
+
59
+ $this->_query = $the_query;
60
+
61
+ }
62
+
63
+ public function get_posts( $return_collection = false ) {
64
+ if ( isset($this->_query->posts) ) {
65
+ $posts = new PostsCollection($this->_query->posts, $this->_posts_class);
66
+ return ($return_collection) ? $posts : $posts->get_posts();
67
+ }
68
+ }
69
+
70
+ //
71
+ // GET POSTS
72
+ //
73
+ public static function get_query_from_array_of_ids( $query = array() ) {
74
+ if ( !is_array($query) || !count($query) )
75
+ return null;
76
+
77
+ return new \WP_Query(array(
78
+ 'post_type'=> 'any',
79
+ 'ignore_sticky_posts' => true,
80
+ 'post__in' => $query,
81
+ 'orderby' => 'post__in',
82
+ 'nopaging' => true
83
+ ));
84
+ }
85
+
86
+ public static function get_query_from_string( $string = '' ) {
87
+ $post_type = false;
88
+
89
+ if ( is_string($string) && strstr($string, '#') ) {
90
+ //we have a post_type directive here
91
+ list($post_type, $string) = explode('#', $string);
92
+ }
93
+
94
+ $query = array(
95
+ 'post_type' => ($post_type) ? $post_type : 'any'
96
+ );
97
+
98
+ if ( is_numeric($string) ) {
99
+ $query['p'] = $string;
100
+
101
+ } else {
102
+ $query['name'] = $string;
103
+ }
104
+
105
+ return new \WP_Query($query);
106
+ }
107
+
108
+ //
109
+ // Iterator Interface
110
+ //
111
+
112
+ public function valid() {
113
+ return $this->_query->have_posts();
114
+ }
115
+
116
+ public function current() {
117
+ global $post;
118
+
119
+ $this->_query->the_post();
120
+
121
+ // Sets up the global post, but also return the post, for use in Twig template
122
+ $posts_class = $this->_posts_class;
123
+ return new $posts_class($post);
124
+ }
125
+
126
+ /**
127
+ * Don't implement next, because current already advances the loop
128
+ */
129
+ final public function next() {}
130
+
131
+ public function rewind() {
132
+ $this->_query->rewind_posts();
133
+ }
134
+
135
+ public function key() {
136
+ $this->_query->current_post;
137
+ }
138
+
139
+ //get_posts users numberposts
140
+ static function fix_number_posts_wp_quirk( $query ) {
141
+ if ( isset($query->query) && isset($query->query['numberposts'])
142
+ && !isset($query->query['posts_per_page']) ) {
143
+ $query->set('posts_per_page', $query->query['numberposts']);
144
+ }
145
+ return $query;
146
+ }
147
+
148
+ /**
149
+ * this will test for whether a custom page to display posts is active, and if so, set the query to the default
150
+ * @param WP_Query $query the original query recived from WordPress
151
+ * @return WP_Query
152
+ */
153
+ static function handle_maybe_custom_posts_page( $query ) {
154
+ if ( $custom_posts_page = get_option('page_for_posts') ) {
155
+ if ( isset($query->query['p']) && $query->query['p'] == $custom_posts_page ) {
156
+ return new \WP_Query(array('post_type' => 'post'));
157
+ }
158
+ }
159
+ return $query;
160
+ }
161
+
162
+ }
lib/{timber-request.php → Request.php} RENAMED
@@ -1,8 +1,15 @@
1
  <?php
 
 
 
 
 
 
2
  /**
3
  * TimberRequest exposes $_GET and $_POST to the context
4
  */
5
- class TimberRequest extends TimberCore implements TimberCoreInterface {
 
6
  public $post = array();
7
  public $get = array();
8
 
1
  <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\CoreInterface;
7
+
8
  /**
9
  * TimberRequest exposes $_GET and $_POST to the context
10
  */
11
+
12
+ class Request extends Core implements CoreInterface {
13
  public $post = array();
14
  public $get = array();
15
 
lib/{timber-site.php → Site.php} RENAMED
@@ -1,5 +1,13 @@
1
  <?php
2
 
 
 
 
 
 
 
 
 
3
  /**
4
  * TimberSite gives you access to information you need about your site. In Multisite setups, you can get info on other sites in your network.
5
  * @example
@@ -16,7 +24,7 @@
16
  * My site is called Jared's blog, another site on my network is Upstatement.com
17
  * ```
18
  */
19
- class TimberSite extends TimberCore implements TimberCoreInterface {
20
 
21
  /**
22
  * @api
@@ -64,7 +72,7 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
64
 
65
  /** @api
66
  * @var string for people who like trackback spam
67
- */
68
  public $pingback_url;
69
  public $siteurl;
70
  /**
@@ -104,7 +112,7 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
104
  function __construct( $site_name_or_id = null ) {
105
  $this->init();
106
  if ( is_multisite() ) {
107
- $this->init_as_multisite( $site_name_or_id );
108
  } else {
109
  $this->init_as_singlesite();
110
  }
@@ -122,17 +130,17 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
122
  $site_name_or_id = get_current_blog_id();
123
  }
124
  }
125
- $info = get_blog_details( $site_name_or_id );
126
- $this->import( $info );
127
  $this->ID = $info->blog_id;
128
  $this->id = $this->ID;
129
  $this->name = $this->blogname;
130
  $this->title = $this->blogname;
131
  $this->url = $this->siteurl;
132
- $theme_slug = get_blog_option( $info->blog_id, 'stylesheet' );
133
- $this->theme = new TimberTheme( $theme_slug );
134
- $this->description = get_blog_option( $info->blog_id, 'blogdescription' );
135
- $this->admin_email = get_blog_option( $info->blog_id, 'admin_email' );
136
  $this->multisite = true;
137
  }
138
 
@@ -141,13 +149,13 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
141
  * @internal
142
  */
143
  protected function init_as_singlesite() {
144
- $this->admin_email = get_bloginfo( 'admin_email' );
145
- $this->name = get_bloginfo( 'name' );
146
  $this->title = $this->name;
147
- $this->description = get_bloginfo( 'description' );
148
- $this->url = get_bloginfo( 'url' );
149
- $this->theme = new TimberTheme();
150
- $this->language_attributes = TimberHelper::function_wrapper( 'language_attributes' );
151
  $this->multisite = false;
152
  }
153
 
@@ -156,16 +164,14 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
156
  * @internal
157
  */
158
  protected function init() {
159
- $this->rdf = get_bloginfo( 'rdf_url' );
160
- $this->rss = get_bloginfo( 'rss_url' );
161
- $this->rss2 = get_bloginfo( 'rss2_url' );
162
- $this->atom = get_bloginfo( 'atom_url' );
163
- $this->language = get_bloginfo( 'language' );
164
- $this->charset = get_bloginfo( 'charset' );
165
- $this->pingback = get_bloginfo( 'pingback_url' );
166
- $this->language_attributes = TimberHelper::function_wrapper( 'language_attributes' );
167
- /* deprecated benath this comment */
168
- $this->pingback_url = get_bloginfo( 'pingback_url' );
169
  }
170
 
171
  /**
@@ -175,34 +181,16 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
175
  * @return mixed
176
  */
177
  function __get( $field ) {
178
- if ( !isset( $this->$field ) ) {
179
  if ( is_multisite() ) {
180
- $this->$field = get_blog_option( $this->ID, $field );
181
  } else {
182
- $this->$field = get_option( $field );
183
  }
184
  }
185
  return $this->$field;
186
  }
187
 
188
- /**
189
- * @deprecated 0.21.9
190
- * @internal
191
- * @return string
192
- */
193
- function get_link() {
194
- return $this->link();
195
- }
196
-
197
- /**
198
- * @deprecated 0.21.9
199
- * @internal
200
- * @return string
201
- */
202
- function get_url() {
203
- return $this->get_link();
204
- }
205
-
206
  /**
207
  * Returns the link to the site's home.
208
  * @example
@@ -223,11 +211,22 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
223
  return $this->url;
224
  }
225
 
 
 
 
 
 
 
 
 
 
 
 
226
  /**
227
  * @ignore
228
  */
229
  public function meta( $field ) {
230
- return $this->__get( $field );
231
  }
232
 
233
  /**
@@ -237,11 +236,11 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
237
  * @param mixed $value
238
  */
239
  public function update( $key, $value ) {
240
- $value = apply_filters( 'timber_site_set_meta', $value, $key, $this->ID, $this );
241
  if ( is_multisite() ) {
242
- update_blog_option( $this->ID, $key, $value );
243
  } else {
244
- update_option( $key, $value );
245
  }
246
  $this->$key = $value;
247
  }
@@ -253,7 +252,17 @@ class TimberSite extends TimberCore implements TimberCoreInterface {
253
  * @return string
254
  */
255
  function url() {
256
- return $this->get_link();
 
 
 
 
 
 
 
 
 
 
257
  }
258
 
259
  }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\CoreInterface;
7
+
8
+ use Timber\Theme;
9
+ use Timber\Helper;
10
+
11
  /**
12
  * TimberSite gives you access to information you need about your site. In Multisite setups, you can get info on other sites in your network.
13
  * @example
24
  * My site is called Jared's blog, another site on my network is Upstatement.com
25
  * ```
26
  */
27
+ class Site extends Core implements CoreInterface {
28
 
29
  /**
30
  * @api
72
 
73
  /** @api
74
  * @var string for people who like trackback spam
75
+ */
76
  public $pingback_url;
77
  public $siteurl;
78
  /**
112
  function __construct( $site_name_or_id = null ) {
113
  $this->init();
114
  if ( is_multisite() ) {
115
+ $this->init_as_multisite($site_name_or_id);
116
  } else {
117
  $this->init_as_singlesite();
118
  }
130
  $site_name_or_id = get_current_blog_id();
131
  }
132
  }
133
+ $info = get_blog_details($site_name_or_id);
134
+ $this->import($info);
135
  $this->ID = $info->blog_id;
136
  $this->id = $this->ID;
137
  $this->name = $this->blogname;
138
  $this->title = $this->blogname;
139
  $this->url = $this->siteurl;
140
+ $theme_slug = get_blog_option($info->blog_id, 'stylesheet');
141
+ $this->theme = new Theme($theme_slug);
142
+ $this->description = get_blog_option($info->blog_id, 'blogdescription');
143
+ $this->admin_email = get_blog_option($info->blog_id, 'admin_email');
144
  $this->multisite = true;
145
  }
146
 
149
  * @internal
150
  */
151
  protected function init_as_singlesite() {
152
+ $this->admin_email = get_bloginfo('admin_email');
153
+ $this->name = get_bloginfo('name');
154
  $this->title = $this->name;
155
+ $this->description = get_bloginfo('description');
156
+ $this->url = get_bloginfo('url');
157
+ $this->theme = new Theme();
158
+ $this->language_attributes = Helper::function_wrapper('language_attributes');
159
  $this->multisite = false;
160
  }
161
 
164
  * @internal
165
  */
166
  protected function init() {
167
+ $this->rdf = get_bloginfo('rdf_url');
168
+ $this->rss = get_bloginfo('rss_url');
169
+ $this->rss2 = get_bloginfo('rss2_url');
170
+ $this->atom = get_bloginfo('atom_url');
171
+ $this->language = get_bloginfo('language');
172
+ $this->charset = get_bloginfo('charset');
173
+ $this->pingback = get_bloginfo('pingback_url');
174
+ $this->language_attributes = Helper::function_wrapper('language_attributes');
 
 
175
  }
176
 
177
  /**
181
  * @return mixed
182
  */
183
  function __get( $field ) {
184
+ if ( !isset($this->$field) ) {
185
  if ( is_multisite() ) {
186
+ $this->$field = get_blog_option($this->ID, $field);
187
  } else {
188
+ $this->$field = get_option($field);
189
  }
190
  }
191
  return $this->$field;
192
  }
193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  /**
195
  * Returns the link to the site's home.
196
  * @example
211
  return $this->url;
212
  }
213
 
214
+ /**
215
+ * @deprecated 0.21.9
216
+ * @internal
217
+ * @return string
218
+ */
219
+ function get_link() {
220
+ Helper::warn('{{site.get_link}} is deprecated, use {{site.link}}');
221
+ return $this->link();
222
+ }
223
+
224
+
225
  /**
226
  * @ignore
227
  */
228
  public function meta( $field ) {
229
+ return $this->__get($field);
230
  }
231
 
232
  /**
236
  * @param mixed $value
237
  */
238
  public function update( $key, $value ) {
239
+ $value = apply_filters('timber_site_set_meta', $value, $key, $this->ID, $this);
240
  if ( is_multisite() ) {
241
+ update_blog_option($this->ID, $key, $value);
242
  } else {
243
+ update_option($key, $value);
244
  }
245
  $this->$key = $value;
246
  }
252
  * @return string
253
  */
254
  function url() {
255
+ return $this->link();
256
+ }
257
+
258
+ /**
259
+ * @deprecated 0.21.9
260
+ * @internal
261
+ * @return string
262
+ */
263
+ function get_url() {
264
+ Helper::warn('{{site.get_url}} is deprecated, use {{site.link}} instead');
265
+ return $this->link();
266
  }
267
 
268
  }
lib/{timber-term.php → Term.php} RENAMED
@@ -1,4 +1,14 @@
1
  <?php
 
 
 
 
 
 
 
 
 
 
2
  /**
3
  * Terms: WordPress has got 'em, you want 'em. Categories. Tags. Custom Taxonomies. You don't care, you're a fiend. Well let's get this under control
4
  * @example
@@ -30,10 +40,10 @@
30
  * </ul>
31
  * ```
32
  */
33
- class TimberTerm extends TimberCore implements TimberCoreInterface {
34
 
35
- public $PostClass = 'TimberPost';
36
- public $TermClass = 'TimberTerm';
37
 
38
  public $object_type = 'term';
39
  public static $representation = 'term';
@@ -55,10 +65,10 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
55
  * @param string $tax
56
  */
57
  public function __construct( $tid = null, $tax = '' ) {
58
- if ($tid === null) {
59
  $tid = $this->get_term_from_query();
60
  }
61
- if (strlen($tax)) {
62
  $this->taxonomy = $tax;
63
  }
64
  $this->init($tid);
@@ -93,7 +103,7 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
93
  global $wp_query;
94
  if ( isset($wp_query->queried_object) ) {
95
  $qo = $wp_query->queried_object;
96
- if (isset($qo->term_id)) {
97
  return $qo->term_id;
98
  }
99
  }
@@ -114,9 +124,9 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
114
  $term->ID = $term->term_id;
115
  } else if ( is_string($tid) ) {
116
  //echo 'bad call using '.$tid;
117
- //TimberHelper::error_log(debug_backtrace());
118
  }
119
- if ( isset($term->ID) ){
120
  $term->id = $term->ID;
121
  $this->import($term);
122
  if ( isset($term->term_id) ) {
@@ -131,7 +141,7 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
131
  * @param int $tid
132
  * @return array
133
  */
134
- protected function get_term_meta($tid) {
135
  $customs = array();
136
  $customs = apply_filters('timber_term_get_meta', $customs, $tid, $this);
137
  return apply_filters('timber/term/meta', $customs, $tid, $this);
@@ -154,7 +164,7 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
154
  global $wpdb;
155
  $query = $wpdb->prepare("SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d LIMIT 1", $tid);
156
  $tax = $wpdb->get_var($query);
157
- if (isset($tax) && strlen($tax)) {
158
  $this->taxonomy = $tax;
159
  return get_term($tid, $tax);
160
  }
@@ -206,7 +216,7 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
206
  * @return string
207
  */
208
  public function get_meta_field( $field_name ) {
209
- if (!isset($this->$field_name)) {
210
  $field_value = '';
211
  $field_value = apply_filters('timber_term_get_meta_field', $field_value, $this->ID, $field_name, $this);
212
  $field_value = apply_filters('timber/term/meta/field', $field_value, $this->ID, $field_name, $this);
@@ -217,23 +227,20 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
217
 
218
  /**
219
  * @internal
 
220
  * @return string
221
  */
222
  public function get_path() {
223
- $link = $this->get_link();
224
- $rel = TimberURLHelper::get_rel_url($link, true);
225
- $rel = apply_filters('timber_term_path', $rel, $this);
226
- return apply_filters('timber/term/path', $rel, $this);
227
  }
228
 
229
  /**
230
  * @internal
 
231
  * @return string
232
  */
233
  public function get_link() {
234
- $link = get_term_link($this);
235
- $link = apply_filters('timber_term_link', $link, $this);
236
- return apply_filters('timber/term/link', $link, $this);
237
  }
238
 
239
  /**
@@ -245,7 +252,7 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
245
  * @return array|bool|null
246
  */
247
  public function get_posts( $numberposts = 10, $post_type = 'any', $PostClass = '' ) {
248
- if (!strlen($PostClass)) {
249
  $PostClass = $this->PostClass;
250
  }
251
  $default_tax_query = array(array(
@@ -259,10 +266,10 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
259
  parse_str($args, $new_args);
260
  $args = $new_args;
261
  $args['tax_query'] = $default_tax_query;
262
- if (!isset($args['post_type'])) {
263
  $args['post_type'] = 'any';
264
  }
265
- if (class_exists($post_type)) {
266
  $PostClass = $post_type;
267
  }
268
  } else if ( is_array($numberposts) ) {
@@ -294,8 +301,8 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
294
  public function get_children() {
295
  if ( !isset($this->_children) ) {
296
  $children = get_term_children($this->ID, $this->taxonomy);
297
- foreach ($children as &$child) {
298
- $child = new TimberTerm($child);
299
  }
300
  $this->_children = $children;
301
  }
@@ -309,7 +316,7 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
309
  * @param mixed $value
310
  */
311
  function update( $key, $value ) {
312
- $value = apply_filters( 'timber_term_set_meta', $value, $key, $this->ID, $this );
313
  $this->$key = $value;
314
  }
315
 
@@ -331,11 +338,11 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
331
  public function description() {
332
  $prefix = '<p>';
333
  $suffix = '</p>';
334
- $desc = term_description( $this->ID, $this->taxonomy );
335
- if (substr($desc, 0, strlen($prefix)) == $prefix) {
336
  $desc = substr($desc, strlen($prefix));
337
  }
338
- $desc = preg_replace('/'. preg_quote('</p>', '/') . '$/', '', $desc);
339
  return trim($desc);
340
  }
341
 
@@ -347,21 +354,15 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
347
  return $this->get_edit_url();
348
  }
349
 
350
- /**
351
- * @internal
352
- * @deprecated 0.21.8 use TimberTerm::link() instead
353
- * @return string
354
- */
355
- public function get_url() {
356
- return $this->get_link();
357
- }
358
 
359
  /**
360
  * @api
361
  * @return string
362
  */
363
  public function link() {
364
- return $this->get_link();
 
 
365
  }
366
 
367
  /**
@@ -378,7 +379,10 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
378
  * @return string
379
  */
380
  public function path() {
381
- return $this->get_path();
 
 
 
382
  }
383
 
384
  /**
@@ -408,22 +412,4 @@ class TimberTerm extends TimberCore implements TimberCoreInterface {
408
  public function title() {
409
  return $this->name;
410
  }
411
-
412
- /**
413
- * @deprecated 0.21.9 use TimberTerm::link() instead
414
- * @return string
415
- */
416
- public function url() {
417
- return $this->get_url();
418
- }
419
-
420
- /**
421
- * @deprecated 0.20.0 this was a dumb idea
422
- * @param int $i
423
- * @return string
424
- */
425
- function get_page( $i ) {
426
- return $this->get_path() . '/page/' . $i;
427
- }
428
-
429
  }
1
  <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\CoreInterface;
7
+
8
+ use Timber\Post;
9
+ use Timber\Helper;
10
+ use Timber\URLHelper;
11
+
12
  /**
13
  * Terms: WordPress has got 'em, you want 'em. Categories. Tags. Custom Taxonomies. You don't care, you're a fiend. Well let's get this under control
14
  * @example
40
  * </ul>
41
  * ```
42
  */
43
+ class Term extends Core implements CoreInterface {
44
 
45
+ public $PostClass = 'Timber\Post';
46
+ public $TermClass = 'Term';
47
 
48
  public $object_type = 'term';
49
  public static $representation = 'term';
65
  * @param string $tax
66
  */
67
  public function __construct( $tid = null, $tax = '' ) {
68
+ if ( $tid === null ) {
69
  $tid = $this->get_term_from_query();
70
  }
71
+ if ( strlen($tax) ) {
72
  $this->taxonomy = $tax;
73
  }
74
  $this->init($tid);
103
  global $wp_query;
104
  if ( isset($wp_query->queried_object) ) {
105
  $qo = $wp_query->queried_object;
106
+ if ( isset($qo->term_id) ) {
107
  return $qo->term_id;
108
  }
109
  }
124
  $term->ID = $term->term_id;
125
  } else if ( is_string($tid) ) {
126
  //echo 'bad call using '.$tid;
127
+ //Helper::error_log(debug_backtrace());
128
  }
129
+ if ( isset($term->ID) ) {
130
  $term->id = $term->ID;
131
  $this->import($term);
132
  if ( isset($term->term_id) ) {
141
  * @param int $tid
142
  * @return array
143
  */
144
+ protected function get_term_meta( $tid ) {
145
  $customs = array();
146
  $customs = apply_filters('timber_term_get_meta', $customs, $tid, $this);
147
  return apply_filters('timber/term/meta', $customs, $tid, $this);
164
  global $wpdb;
165
  $query = $wpdb->prepare("SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d LIMIT 1", $tid);
166
  $tax = $wpdb->get_var($query);
167
+ if ( isset($tax) && strlen($tax) ) {
168
  $this->taxonomy = $tax;
169
  return get_term($tid, $tax);
170
  }
216
  * @return string
217
  */
218
  public function get_meta_field( $field_name ) {
219
+ if ( !isset($this->$field_name) ) {
220
  $field_value = '';
221
  $field_value = apply_filters('timber_term_get_meta_field', $field_value, $this->ID, $field_name, $this);
222
  $field_value = apply_filters('timber/term/meta/field', $field_value, $this->ID, $field_name, $this);
227
 
228
  /**
229
  * @internal
230
+ * @deprecated since 1.0
231
  * @return string
232
  */
233
  public function get_path() {
234
+ return $this->path();
 
 
 
235
  }
236
 
237
  /**
238
  * @internal
239
+ * @deprecated since 1.0
240
  * @return string
241
  */
242
  public function get_link() {
243
+ return $this->link();
 
 
244
  }
245
 
246
  /**
252
  * @return array|bool|null
253
  */
254
  public function get_posts( $numberposts = 10, $post_type = 'any', $PostClass = '' ) {
255
+ if ( !strlen($PostClass) ) {
256
  $PostClass = $this->PostClass;
257
  }
258
  $default_tax_query = array(array(
266
  parse_str($args, $new_args);
267
  $args = $new_args;
268
  $args['tax_query'] = $default_tax_query;
269
+ if ( !isset($args['post_type']) ) {
270
  $args['post_type'] = 'any';
271
  }
272
+ if ( class_exists($post_type) ) {
273
  $PostClass = $post_type;
274
  }
275
  } else if ( is_array($numberposts) ) {
301
  public function get_children() {
302
  if ( !isset($this->_children) ) {
303
  $children = get_term_children($this->ID, $this->taxonomy);
304
+ foreach ( $children as &$child ) {
305
+ $child = new Term($child);
306
  }
307
  $this->_children = $children;
308
  }
316
  * @param mixed $value
317
  */
318
  function update( $key, $value ) {
319
+ $value = apply_filters('timber_term_set_meta', $value, $key, $this->ID, $this);
320
  $this->$key = $value;
321
  }
322
 
338
  public function description() {
339
  $prefix = '<p>';
340
  $suffix = '</p>';
341
+ $desc = term_description($this->ID, $this->taxonomy);
342
+ if ( substr($desc, 0, strlen($prefix)) == $prefix ) {
343
  $desc = substr($desc, strlen($prefix));
344
  }
345
+ $desc = preg_replace('/'.preg_quote('</p>', '/').'$/', '', $desc);
346
  return trim($desc);
347
  }
348
 
354
  return $this->get_edit_url();
355
  }
356
 
 
 
 
 
 
 
 
 
357
 
358
  /**
359
  * @api
360
  * @return string
361
  */
362
  public function link() {
363
+ $link = get_term_link($this);
364
+ $link = apply_filters('timber_term_link', $link, $this);
365
+ return apply_filters('timber/term/link', $link, $this);
366
  }
367
 
368
  /**
379
  * @return string
380
  */
381
  public function path() {
382
+ $link = $this->get_link();
383
+ $rel = URLHelper::get_rel_url($link, true);
384
+ $rel = apply_filters('timber_term_path', $rel, $this);
385
+ return apply_filters('timber/term/path', $rel, $this);
386
  }
387
 
388
  /**
412
  public function title() {
413
  return $this->name;
414
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
lib/{timber-term-getter.php → TermGetter.php} RENAMED
@@ -1,6 +1,11 @@
1
  <?php
2
 
3
- class TimberTermGetter {
 
 
 
 
 
4
 
5
  /**
6
  * @param string|array $args
@@ -8,7 +13,7 @@ class TimberTermGetter {
8
  * @param string $TermClass
9
  * @return mixed
10
  */
11
- public static function get_terms($args = null, $maybe_args = array(), $TermClass = 'TimberTerm') {
12
  if ( is_string($maybe_args) && !strstr($maybe_args, '=') ) {
13
  //the user is sending the $TermClass in the second argument
14
  $TermClass = $maybe_args;
@@ -30,7 +35,7 @@ class TimberTermGetter {
30
  $parsed->args = array_merge($parsed->args, $maybe_args);
31
  }
32
  return self::handle_term_query($parsed->taxonomies, $parsed->args, $TermClass);
33
- } else if ( is_array($args) && TimberHelper::is_array_assoc($args) ) {
34
  //its an associative array, like a good ole query
35
  $parsed = self::get_term_query_from_assoc_array($args);
36
  return self::handle_term_query($parsed->taxonomies, $parsed->args, $TermClass);
@@ -53,7 +58,7 @@ class TimberTermGetter {
53
  * @param string $TermClass
54
  * @return mixed
55
  */
56
- public static function handle_term_query($taxonomies, $args, $TermClass) {
57
  if ( !isset($args['hide_empty']) ) {
58
  $args['hide_empty'] = false;
59
  }
@@ -64,7 +69,7 @@ class TimberTermGetter {
64
  $args['include'] = $args['term_id'];
65
  }
66
  $terms = get_terms($taxonomies, $args);
67
- foreach ($terms as &$term) {
68
  $term = new $TermClass($term->term_id, $term->taxonomy);
69
  }
70
  return $terms;
@@ -74,7 +79,7 @@ class TimberTermGetter {
74
  * @param string $query_string
75
  * @return stdClass
76
  */
77
- protected static function get_term_query_from_query_string($query_string) {
78
  $args = array();
79
  parse_str($query_string, $args);
80
  $ret = self::get_term_query_from_assoc_array($args);
@@ -85,8 +90,8 @@ class TimberTermGetter {
85
  * @param string $taxs
86
  * @return stdClass
87
  */
88
- protected static function get_term_query_from_string($taxs) {
89
- $ret = new stdClass();
90
  $ret->args = array();
91
  if ( is_string($taxs) ) {
92
  $taxs = array($taxs);
@@ -99,8 +104,8 @@ class TimberTermGetter {
99
  * @param array $args
100
  * @return stdClass
101
  */
102
- public static function get_term_query_from_assoc_array($args) {
103
- $ret = new stdClass();
104
  $ret->args = $args;
105
  if ( isset($ret->args['tax']) ) {
106
  $ret->taxonomies = $ret->args['tax'];
@@ -126,7 +131,7 @@ class TimberTermGetter {
126
  * @param array $args
127
  * @return stdClass
128
  */
129
- public static function get_term_query_from_array($args) {
130
  if ( is_array($args) && !empty($args) ) {
131
  //okay its an array with content
132
  if ( is_int($args[0]) ) {
@@ -142,8 +147,8 @@ class TimberTermGetter {
142
  * @param integer[] $args
143
  * @return stdClass
144
  */
145
- public static function get_term_query_from_array_of_ids($args) {
146
- $ret = new stdClass();
147
  $ret->taxonomies = get_taxonomies();
148
  $ret->args['include'] = $args;
149
  return $ret;
@@ -153,8 +158,8 @@ class TimberTermGetter {
153
  * @param string[] $args
154
  * @return stdClass
155
  */
156
- public static function get_term_query_from_array_of_strings($args) {
157
- $ret = new stdClass();
158
  $ret->taxonomies = self::correct_taxonomy_names($args);
159
  $ret->args = array();
160
  return $ret;
@@ -164,11 +169,11 @@ class TimberTermGetter {
164
  * @param string|array $taxs
165
  * @return array
166
  */
167
- private static function correct_taxonomy_names($taxs) {
168
  if ( is_string($taxs) ) {
169
  $taxs = array($taxs);
170
  }
171
- foreach ($taxs as &$tax) {
172
  if ( $tax == 'tags' || $tax == 'tag' ) {
173
  $tax = 'post_tag';
174
  } else if ( $tax == 'categories' ) {
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Term;
6
+ use Timber\Helper;
7
+
8
+ class TermGetter {
9
 
10
  /**
11
  * @param string|array $args
13
  * @param string $TermClass
14
  * @return mixed
15
  */
16
+ public static function get_terms( $args = null, $maybe_args = array(), $TermClass = 'Term' ) {
17
  if ( is_string($maybe_args) && !strstr($maybe_args, '=') ) {
18
  //the user is sending the $TermClass in the second argument
19
  $TermClass = $maybe_args;
35
  $parsed->args = array_merge($parsed->args, $maybe_args);
36
  }
37
  return self::handle_term_query($parsed->taxonomies, $parsed->args, $TermClass);
38
+ } else if ( is_array($args) && Helper::is_array_assoc($args) ) {
39
  //its an associative array, like a good ole query
40
  $parsed = self::get_term_query_from_assoc_array($args);
41
  return self::handle_term_query($parsed->taxonomies, $parsed->args, $TermClass);
58
  * @param string $TermClass
59
  * @return mixed
60
  */
61
+ public static function handle_term_query( $taxonomies, $args, $TermClass ) {
62
  if ( !isset($args['hide_empty']) ) {
63
  $args['hide_empty'] = false;
64
  }
69
  $args['include'] = $args['term_id'];
70
  }
71
  $terms = get_terms($taxonomies, $args);
72
+ foreach ( $terms as &$term ) {
73
  $term = new $TermClass($term->term_id, $term->taxonomy);
74
  }
75
  return $terms;
79
  * @param string $query_string
80
  * @return stdClass
81
  */
82
+ protected static function get_term_query_from_query_string( $query_string ) {
83
  $args = array();
84
  parse_str($query_string, $args);
85
  $ret = self::get_term_query_from_assoc_array($args);
90
  * @param string $taxs
91
  * @return stdClass
92
  */
93
+ protected static function get_term_query_from_string( $taxs ) {
94
+ $ret = new \stdClass();
95
  $ret->args = array();
96
  if ( is_string($taxs) ) {
97
  $taxs = array($taxs);
104
  * @param array $args
105
  * @return stdClass
106
  */
107
+ public static function get_term_query_from_assoc_array( $args ) {
108
+ $ret = new \stdClass();
109
  $ret->args = $args;
110
  if ( isset($ret->args['tax']) ) {
111
  $ret->taxonomies = $ret->args['tax'];
131
  * @param array $args
132
  * @return stdClass
133
  */
134
+ public static function get_term_query_from_array( $args ) {
135
  if ( is_array($args) && !empty($args) ) {
136
  //okay its an array with content
137
  if ( is_int($args[0]) ) {
147
  * @param integer[] $args
148
  * @return stdClass
149
  */
150
+ public static function get_term_query_from_array_of_ids( $args ) {
151
+ $ret = new \stdClass();
152
  $ret->taxonomies = get_taxonomies();
153
  $ret->args['include'] = $args;
154
  return $ret;
158
  * @param string[] $args
159
  * @return stdClass
160
  */
161
+ public static function get_term_query_from_array_of_strings( $args ) {
162
+ $ret = new \stdClass();
163
  $ret->taxonomies = self::correct_taxonomy_names($args);
164
  $ret->args = array();
165
  return $ret;
169
  * @param string|array $taxs
170
  * @return array
171
  */
172
+ private static function correct_taxonomy_names( $taxs ) {
173
  if ( is_string($taxs) ) {
174
  $taxs = array($taxs);
175
  }
176
+ foreach ( $taxs as &$tax ) {
177
  if ( $tax == 'tags' || $tax == 'tag' ) {
178
  $tax = 'post_tag';
179
  } else if ( $tax == 'categories' ) {
lib/{timber-theme.php → Theme.php} RENAMED
@@ -1,5 +1,11 @@
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
@@ -17,7 +23,7 @@
17
  * ```
18
  * @package Timber
19
  */
20
- class TimberTheme extends TimberCore {
21
 
22
  /**
23
  * @api
@@ -62,7 +68,7 @@ class TimberTheme extends TimberCore {
62
  * We are currently using the My Theme theme.
63
  * ```
64
  */
65
- function __construct($slug = null) {
66
  $this->init($slug);
67
  }
68
 
@@ -70,14 +76,14 @@ class TimberTheme extends TimberCore {
70
  * @internal
71
  * @param string $slug
72
  */
73
- protected function init($slug = null) {
74
  $data = wp_get_theme($slug);
75
  $this->name = $data->get('Name');
76
  $ss = $data->get_stylesheet();
77
  $this->slug = $ss;
78
 
79
- if ( ! function_exists( 'get_home_path' ) ) {
80
- require_once(ABSPATH . 'wp-admin/includes/file.php');
81
  }
82
 
83
  $this->uri = get_stylesheet_directory_uri();
@@ -86,7 +92,7 @@ class TimberTheme extends TimberCore {
86
  $this->uri = get_template_directory_uri();
87
  }
88
  if ( $this->parent_slug && $this->parent_slug != $this->slug ) {
89
- $this->parent = new TimberTheme($this->parent_slug);
90
  }
91
  }
92
 
@@ -103,7 +109,7 @@ class TimberTheme extends TimberCore {
103
  * @return string the relative path to the theme (ex: `/wp-content/themes/my-timber-theme`)
104
  */
105
  public function path() {
106
- return TimberURLHelper::get_rel_url( $this->link() );
107
  }
108
 
109
  /**
@@ -111,7 +117,7 @@ class TimberTheme extends TimberCore {
111
  * @param bool $default
112
  * @return string
113
  */
114
- public function theme_mod($name, $default = false) {
115
  return get_theme_mod($name, $default);
116
  }
117
 
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\Theme;
7
+ use Timber\URLHelper;
8
+
9
  /**
10
  * 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:
11
  * @example
23
  * ```
24
  * @package Timber
25
  */
26
+ class Theme extends Core {
27
 
28
  /**
29
  * @api
68
  * We are currently using the My Theme theme.
69
  * ```
70
  */
71
+ function __construct( $slug = null ) {
72
  $this->init($slug);
73
  }
74
 
76
  * @internal
77
  * @param string $slug
78
  */
79
+ protected function init( $slug = null ) {
80
  $data = wp_get_theme($slug);
81
  $this->name = $data->get('Name');
82
  $ss = $data->get_stylesheet();
83
  $this->slug = $ss;
84
 
85
+ if ( !function_exists('get_home_path') ) {
86
+ require_once(ABSPATH.'wp-admin/includes/file.php');
87
  }
88
 
89
  $this->uri = get_stylesheet_directory_uri();
92
  $this->uri = get_template_directory_uri();
93
  }
94
  if ( $this->parent_slug && $this->parent_slug != $this->slug ) {
95
+ $this->parent = new Theme($this->parent_slug);
96
  }
97
  }
98
 
109
  * @return string the relative path to the theme (ex: `/wp-content/themes/my-timber-theme`)
110
  */
111
  public function path() {
112
+ return URLHelper::get_rel_url($this->link());
113
  }
114
 
115
  /**
117
  * @param bool $default
118
  * @return string
119
  */
120
+ public function theme_mod( $name, $default = false ) {
121
  return get_theme_mod($name, $default);
122
  }
123
 
lib/{timber.php → Timber.php} RENAMED
@@ -1,5 +1,20 @@
1
  <?php
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  /**
4
  * Timber Class.
5
  *
@@ -24,6 +39,8 @@ class Timber {
24
  public static $auto_meta = true;
25
  public static $autoescape = false;
26
 
 
 
27
  /**
28
  * @codeCoverageIgnore
29
  */
@@ -32,6 +49,7 @@ class Timber {
32
  return;
33
  }
34
  $this->test_compatibility();
 
35
  $this->init_constants();
36
  $this->init();
37
  }
@@ -45,27 +63,38 @@ class Timber {
45
  if ( is_admin() || $_SERVER['PHP_SELF'] == '/wp-login.php' ) {
46
  return;
47
  }
48
- if ( version_compare( phpversion(), '5.3.0', '<' ) && !is_admin() ) {
49
- trigger_error( 'Timber requires PHP 5.3.0 or greater. You have '.phpversion(), E_USER_ERROR );
 
 
 
 
 
 
 
 
 
 
50
  }
51
- if ( !class_exists( 'Twig_Autoloader' ) ) {
52
- trigger_error( 'You have not run "composer install" to download required dependencies for Timber, you can read more on https://github.com/jarednova/timber#installation', E_USER_ERROR );
 
 
53
  }
54
  }
55
 
56
  function init_constants() {
57
- defined( "TIMBER_LOC" ) or define( "TIMBER_LOC", realpath( dirname(__DIR__) ) );
58
  }
59
 
60
  /**
61
  * @codeCoverageIgnore
62
  */
63
  protected function init() {
64
- TimberTwig::init();
65
- TimberRoutes::init( $this );
66
- TimberImageHelper::init();
67
- TimberAdmin::init();
68
- TimberIntegrations::init();
69
  }
70
 
71
  /* Post Retrieval Routine
@@ -79,18 +108,24 @@ class Timber {
79
  * @return array|bool|null
80
  */
81
  public static function get_post( $query = false, $PostClass = 'TimberPost' ) {
82
- return TimberPostGetter::get_post( $query, $PostClass );
83
  }
84
 
85
  /**
86
  * Get posts.
87
- *
 
 
 
 
 
 
88
  * @param mixed $query
89
- * @param string $PostClass
90
  * @return array|bool|null
91
  */
92
  public static function get_posts( $query = false, $PostClass = 'TimberPost', $return_collection = false ) {
93
- return TimberPostGetter::get_posts( $query, $PostClass, $return_collection );
94
  }
95
 
96
  /**
@@ -101,7 +136,7 @@ class Timber {
101
  * @return array|bool|null
102
  */
103
  public static function query_post( $query = false, $PostClass = 'TimberPost' ) {
104
- return TimberPostGetter::query_post( $query, $PostClass );
105
  }
106
 
107
  /**
@@ -112,17 +147,7 @@ class Timber {
112
  * @return array|bool|null
113
  */
114
  public static function query_posts( $query = false, $PostClass = 'TimberPost' ) {
115
- return TimberPostGetter::query_posts( $query, $PostClass );
116
- }
117
-
118
- /**
119
- * WP_Query has posts.
120
- *
121
- * @return bool
122
- * @deprecated since 0.20.0
123
- */
124
- static function wp_query_has_posts() {
125
- return TimberPostGetter::wp_query_has_posts();
126
  }
127
 
128
  /* Term Retrieval
@@ -137,7 +162,7 @@ class Timber {
137
  * @return mixed
138
  */
139
  public static function get_terms( $args = null, $maybe_args = array(), $TermClass = 'TimberTerm' ) {
140
- return TimberTermGetter::get_terms( $args, $maybe_args, $TermClass );
141
  }
142
 
143
  /* Site Retrieval
@@ -150,13 +175,13 @@ class Timber {
150
  * @return array
151
  */
152
  public static function get_sites( $blog_ids = false ) {
153
- if ( !is_array( $blog_ids ) ) {
154
  global $wpdb;
155
- $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs ORDER BY blog_id ASC" );
156
  }
157
  $return = array();
158
  foreach ( $blog_ids as $blog_id ) {
159
- $return[] = new TimberSite( $blog_id );
160
  }
161
  return $return;
162
  }
@@ -171,22 +196,27 @@ class Timber {
171
  * @return array
172
  */
173
  public static function get_context() {
174
- $data = array();
175
- $data['http_host'] = 'http://' . TimberURLHelper::get_host();
176
- $data['wp_title'] = TimberHelper::get_wp_title();
177
- $data['wp_head'] = TimberHelper::function_wrapper( 'wp_head' );
178
- $data['wp_footer'] = TimberHelper::function_wrapper( 'wp_footer' );
179
- $data['body_class'] = implode( ' ', get_body_class() );
180
-
181
- $data['site'] = new TimberSite();
182
- $data['request'] = new TimberRequest();
183
- $data['theme'] = $data['site']->theme;
184
-
185
- $data['posts'] = Timber::query_posts();
 
 
 
 
 
 
186
 
187
- $data = apply_filters( 'timber_context', $data );
188
- $data = apply_filters( 'timber/context', $data );
189
- return $data;
190
  }
191
 
192
  /**
@@ -199,27 +229,27 @@ class Timber {
199
  * @param bool $via_render
200
  * @return bool|string
201
  */
202
- public static function compile( $filenames, $data = array(), $expires = false, $cache_mode = TimberLoader::CACHE_USE_DEFAULT, $via_render = false ) {
203
  $caller = self::get_calling_script_dir();
204
  $caller_file = self::get_calling_script_file();
205
- $caller_file = apply_filters( 'timber_calling_php_file', $caller_file );
206
- $loader = new TimberLoader( $caller );
207
- $file = $loader->choose_template( $filenames );
208
  $output = '';
209
- if ( is_null( $data ) ) {
210
  $data = array();
211
  }
212
- if ( strlen( $file ) ) {
213
  if ( $via_render ) {
214
- $file = apply_filters( 'timber_render_file', $file );
215
- $data = apply_filters( 'timber_render_data', $data );
216
  } else {
217
- $file = apply_filters( 'timber_compile_file', $file );
218
- $data = apply_filters( 'timber_compile_data', $data );
219
  }
220
- $output = $loader->render( $file, $data, $expires, $cache_mode );
221
  }
222
- do_action( 'timber_compile_done' );
223
  return $output;
224
  }
225
 
@@ -231,13 +261,10 @@ class Timber {
231
  * @return bool|string
232
  */
233
  public static function compile_string( $string, $data = array() ) {
234
- $dummy_loader = new TimberLoader();
235
- $dummy_loader->get_twig();
236
- $loader = new Twig_Loader_String();
237
- $twig = new Twig_Environment( $loader );
238
- $twig = apply_filters( 'timber/twig/filters', $twig );
239
- $twig = apply_filters( 'twig_apply_filters', $twig );
240
- return $twig->render( $string, $data );
241
  }
242
 
243
  /**
@@ -249,15 +276,15 @@ class Timber {
249
  * @param string $cache_mode
250
  * @return bool|string
251
  */
252
- public static function fetch( $filenames, $data = array(), $expires = false, $cache_mode = TimberLoader::CACHE_USE_DEFAULT ) {
253
  if ( $expires === true ) {
254
  //if this is reading as true; the user probably is using the old $echo param
255
  //so we should move all vars up by a spot
256
  $expires = $cache_mode;
257
- $cache_mode = TimberLoader::CACHE_USE_DEFAULT;
258
  }
259
- $output = self::compile( $filenames, $data, $expires, $cache_mode, true );
260
- $output = apply_filters( 'timber_compile_result', $output );
261
  return $output;
262
  }
263
 
@@ -270,8 +297,8 @@ class Timber {
270
  * @param string $cache_mode
271
  * @return bool|string
272
  */
273
- public static function render( $filenames, $data = array(), $expires = false, $cache_mode = TimberLoader::CACHE_USE_DEFAULT ) {
274
- $output = static::fetch( $filenames, $data, $expires, $cache_mode );
275
  echo $output;
276
  return $output;
277
  }
@@ -284,7 +311,7 @@ class Timber {
284
  * @return bool|string
285
  */
286
  public static function render_string( $string, $data = array() ) {
287
- $compiled = self::compile_string( $string, $data );
288
  echo $compiled;
289
  return $compiled;
290
  }
@@ -304,10 +331,10 @@ class Timber {
304
  if ( $sidebar == '' ) {
305
  $sidebar = 'sidebar.php';
306
  }
307
- if ( strstr( strtolower( $sidebar ), '.php' ) ) {
308
- return self::get_sidebar_from_php( $sidebar, $data );
309
  }
310
- return self::compile( $sidebar, $data );
311
  }
312
 
313
  /**
@@ -319,19 +346,19 @@ class Timber {
319
  */
320
  public static function get_sidebar_from_php( $sidebar = '', $data ) {
321
  $caller = self::get_calling_script_dir();
322
- $loader = new TimberLoader();
323
- $uris = $loader->get_locations( $caller );
324
  ob_start();
325
  $found = false;
326
  foreach ( $uris as $uri ) {
327
- if ( file_exists( trailingslashit( $uri ) . $sidebar ) ) {
328
- include trailingslashit( $uri ) . $sidebar;
329
  $found = true;
330
  break;
331
  }
332
  }
333
  if ( !$found ) {
334
- TimberHelper::error_log( 'error loading your sidebar, check to make sure the file exists' );
335
  }
336
  $ret = ob_get_contents();
337
  ob_end_clean();
@@ -348,44 +375,9 @@ class Timber {
348
  * @return TimberFunctionWrapper
349
  */
350
  public static function get_widgets( $widget_id ) {
351
- return trim( TimberHelper::function_wrapper( 'dynamic_sidebar', array( $widget_id ), true ) );
352
  }
353
 
354
-
355
- /* Routes
356
- ================================ */
357
-
358
- /**
359
- * Add route.
360
- *
361
- * @param string $route
362
- * @param callable $callback
363
- * @param array $args
364
- * @deprecated since 0.20.0
365
- */
366
- public static function add_route( $route, $callback, $args = array() ) {
367
- Routes::map( $route, $callback, $args );
368
- }
369
-
370
- /**
371
- * Load template.
372
- *
373
- * @deprecated since 0.20.0
374
- */
375
- public static function load_template( $template, $query = false, $status_code = 200, $tparams = false ) {
376
- return Routes::load( $template, $tparams, $query, $status_code );
377
- }
378
-
379
- /**
380
- * Load view.
381
- *
382
- * @deprecated since 0.20.2
383
- */
384
- public static function load_view( $template, $query = false, $status_code = 200, $tparams = false ) {
385
- return Routes::load( $template, $tparams, $query, $status_code );
386
- }
387
-
388
-
389
  /* Pagination
390
  ================================ */
391
 
@@ -400,43 +392,43 @@ class Timber {
400
  global $paged;
401
  global $wp_rewrite;
402
  $args = array();
403
- $args['total'] = ceil( $wp_query->found_posts / $wp_query->query_vars['posts_per_page'] );
404
  if ( $wp_rewrite->using_permalinks() ) {
405
- $url = explode( '?', get_pagenum_link( 0 ) );
406
- if ( isset( $url[1] ) ) {
407
- parse_str( $url[1], $query );
408
  $args['add_args'] = $query;
409
  }
410
- $args['format'] = 'page/%#%';
411
- $args['base'] = trailingslashit( $url[0] ).'%_%';
412
  } else {
413
  $big = 999999999;
414
- $args['base'] = str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) );
415
  }
416
  $args['type'] = 'array';
417
- $args['current'] = max( 1, get_query_var( 'paged' ) );
418
- $args['mid_size'] = max( 9 - $args['current'], 3 );
419
- if ( is_int( $prefs ) ) {
420
  $args['mid_size'] = $prefs - 2;
421
  } else {
422
- $args = array_merge( $args, $prefs );
423
  }
424
  $data = array();
425
  $data['current'] = $args['current'];
426
  $data['total'] = $args['total'];
427
- $data['pages'] = TimberHelper::paginate_links( $args );
428
- $next = get_next_posts_page_link( $args['total'] );
429
  if ( $next ) {
430
- $data['next'] = array( 'link' => untrailingslashit( $next ), 'class' => 'page-numbers next' );
431
  }
432
- $prev = previous_posts( false );
433
  if ( $prev ) {
434
- $data['prev'] = array( 'link' => untrailingslashit( $prev ), 'class' => 'page-numbers prev' );
435
  }
436
  if ( $paged < 2 ) {
437
  $data['prev'] = '';
438
  }
439
- if ( $data['total'] === ( double ) 0 ) {
440
  $data['next'] = '';
441
  }
442
  return $data;
@@ -451,14 +443,28 @@ class Timber {
451
  * @return string
452
  */
453
  public static function get_calling_script_dir( $offset = 0 ) {
454
- $caller = self::get_calling_script_file( $offset );
455
- if ( !is_null( $caller ) ) {
456
- $pathinfo = pathinfo( $caller );
457
  $dir = $pathinfo['dirname'];
458
  return $dir;
459
  }
460
  }
461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  /**
463
  * Get calling script file.
464
  *
@@ -482,6 +488,4 @@ class Timber {
482
  }
483
  return $caller;
484
  }
485
-
486
-
487
- }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Twig;
6
+ use Timber\ImageHelper;
7
+ use Timber\Admin;
8
+ use Timber\Integrations;
9
+ use Timber\PostGetter;
10
+ use Timber\TermGetter;
11
+ use Timber\Site;
12
+ use Timber\URLHelper;
13
+ use Timber\Helper;
14
+ use Timber\Request;
15
+ use Timber\User;
16
+ use Timber\Loader;
17
+
18
  /**
19
  * Timber Class.
20
  *
39
  public static $auto_meta = true;
40
  public static $autoescape = false;
41
 
42
+ public static $context_cache = array();
43
+
44
  /**
45
  * @codeCoverageIgnore
46
  */
49
  return;
50
  }
51
  $this->test_compatibility();
52
+ $this->backwards_compatibility();
53
  $this->init_constants();
54
  $this->init();
55
  }
63
  if ( is_admin() || $_SERVER['PHP_SELF'] == '/wp-login.php' ) {
64
  return;
65
  }
66
+ if ( version_compare(phpversion(), '5.3.0', '<') && !is_admin() ) {
67
+ trigger_error('Timber requires PHP 5.3.0 or greater. You have '.phpversion(), E_USER_ERROR);
68
+ }
69
+ if ( !class_exists('Twig_Autoloader') ) {
70
+ trigger_error('You have not run "composer install" to download required dependencies for Timber, you can read more on https://github.com/timber/timber#installation', E_USER_ERROR);
71
+ }
72
+ }
73
+
74
+ private function backwards_compatibility() {
75
+ if ( class_exists('TimberArchives') ) {
76
+ //already run, so bail
77
+ return;
78
  }
79
+ $names = array('Archives', 'Comment', 'Core', 'FunctionWrapper', 'Helper', 'Image', 'ImageHelper', 'Integrations', 'Loader', 'Menu', 'MenuItem', 'Post', 'PostGetter', 'PostsCollection', 'QueryIterator', 'Request', 'Site', 'Term', 'TermGetter', 'Theme', 'Twig', 'URLHelper', 'User');
80
+ class_alias(get_class($this), 'Timber');
81
+ foreach ( $names as $name ) {
82
+ class_alias('Timber\\'.$name, 'Timber'.$name);
83
  }
84
  }
85
 
86
  function init_constants() {
87
+ defined("TIMBER_LOC") or define("TIMBER_LOC", realpath(dirname(__DIR__)));
88
  }
89
 
90
  /**
91
  * @codeCoverageIgnore
92
  */
93
  protected function init() {
94
+ Twig::init();
95
+ ImageHelper::init();
96
+ Admin::init();
97
+ Integrations::init();
 
98
  }
99
 
100
  /* Post Retrieval Routine
108
  * @return array|bool|null
109
  */
110
  public static function get_post( $query = false, $PostClass = 'TimberPost' ) {
111
+ return PostGetter::get_post($query, $PostClass);
112
  }
113
 
114
  /**
115
  * Get posts.
116
+ * @example
117
+ * ```php
118
+ * $posts = Timber::get_posts();
119
+ * $posts = Timber::get_posts('post_type = article')
120
+ * $posts = Timber::get_posts(array('post_type' => 'article', 'category_name' => 'sports')); // uses wp_query format.
121
+ * $posts = Timber::get_posts('post_type=any', array('portfolio' => 'MyPortfolioClass', 'alert' => 'MyAlertClass')); //use a classmap for the $PostClass
122
+ * ```
123
  * @param mixed $query
124
+ * @param string|array $PostClass
125
  * @return array|bool|null
126
  */
127
  public static function get_posts( $query = false, $PostClass = 'TimberPost', $return_collection = false ) {
128
+ return PostGetter::get_posts($query, $PostClass, $return_collection);
129
  }
130
 
131
  /**
136
  * @return array|bool|null
137
  */
138
  public static function query_post( $query = false, $PostClass = 'TimberPost' ) {
139
+ return PostGetter::query_post($query, $PostClass);
140
  }
141
 
142
  /**
147
  * @return array|bool|null
148
  */
149
  public static function query_posts( $query = false, $PostClass = 'TimberPost' ) {
150
+ return PostGetter::query_posts($query, $PostClass);
 
 
 
 
 
 
 
 
 
 
151
  }
152
 
153
  /* Term Retrieval
162
  * @return mixed
163
  */
164
  public static function get_terms( $args = null, $maybe_args = array(), $TermClass = 'TimberTerm' ) {
165
+ return TermGetter::get_terms($args, $maybe_args, $TermClass);
166
  }
167
 
168
  /* Site Retrieval
175
  * @return array
176
  */
177
  public static function get_sites( $blog_ids = false ) {
178
+ if ( !is_array($blog_ids) ) {
179
  global $wpdb;
180
+ $blog_ids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs ORDER BY blog_id ASC");
181
  }
182
  $return = array();
183
  foreach ( $blog_ids as $blog_id ) {
184
+ $return[] = new Site($blog_id);
185
  }
186
  return $return;
187
  }
196
  * @return array
197
  */
198
  public static function get_context() {
199
+ if( empty(self::$context_cache) ) {
200
+ self::$context_cache['http_host'] = 'http://'.URLHelper::get_host();
201
+ self::$context_cache['wp_title'] = Helper::get_wp_title();
202
+ self::$context_cache['wp_head'] = Helper::function_wrapper('wp_head');
203
+ self::$context_cache['wp_footer'] = Helper::function_wrapper('wp_footer');
204
+ self::$context_cache['body_class'] = implode(' ', get_body_class());
205
+
206
+ self::$context_cache['site'] = new Site();
207
+ self::$context_cache['request'] = new Request();
208
+ $user = new User();
209
+ self::$context_cache['user'] = ($user->ID) ? $user : false;
210
+ self::$context_cache['theme'] = self::$context_cache['site']->theme;
211
+
212
+ self::$context_cache['posts'] = Timber::query_posts();
213
+
214
+ self::$context_cache = apply_filters('timber_context', self::$context_cache);
215
+ self::$context_cache = apply_filters('timber/context', self::$context_cache);
216
+ }
217
 
218
+
219
+ return self::$context_cache;
 
220
  }
221
 
222
  /**
229
  * @param bool $via_render
230
  * @return bool|string
231
  */
232
+ public static function compile( $filenames, $data = array(), $expires = false, $cache_mode = Loader::CACHE_USE_DEFAULT, $via_render = false ) {
233
  $caller = self::get_calling_script_dir();
234
  $caller_file = self::get_calling_script_file();
235
+ $caller_file = apply_filters('timber_calling_php_file', $caller_file);
236
+ $loader = new Loader($caller);
237
+ $file = $loader->choose_template($filenames);
238
  $output = '';
239
+ if ( is_null($data) ) {
240
  $data = array();
241
  }
242
+ if ( strlen($file) ) {
243
  if ( $via_render ) {
244
+ $file = apply_filters('timber_render_file', $file);
245
+ $data = apply_filters('timber_render_data', $data);
246
  } else {
247
+ $file = apply_filters('timber_compile_file', $file);
248
+ $data = apply_filters('timber_compile_data', $data);
249
  }
250
+ $output = $loader->render($file, $data, $expires, $cache_mode);
251
  }
252
+ do_action('timber_compile_done');
253
  return $output;
254
  }
255
 
261
  * @return bool|string
262
  */
263
  public static function compile_string( $string, $data = array() ) {
264
+ $dummy_loader = new Loader();
265
+ $twig = $dummy_loader->get_twig();
266
+ $template = $twig->createTemplate($string);
267
+ return $template->render($data);
 
 
 
268
  }
269
 
270
  /**
276
  * @param string $cache_mode
277
  * @return bool|string
278
  */
279
+ public static function fetch( $filenames, $data = array(), $expires = false, $cache_mode = Loader::CACHE_USE_DEFAULT ) {
280
  if ( $expires === true ) {
281
  //if this is reading as true; the user probably is using the old $echo param
282
  //so we should move all vars up by a spot
283
  $expires = $cache_mode;
284
+ $cache_mode = Loader::CACHE_USE_DEFAULT;
285
  }
286
+ $output = self::compile($filenames, $data, $expires, $cache_mode, true);
287
+ $output = apply_filters('timber_compile_result', $output);
288
  return $output;
289
  }
290
 
297
  * @param string $cache_mode
298
  * @return bool|string
299
  */
300
+ public static function render( $filenames, $data = array(), $expires = false, $cache_mode = Loader::CACHE_USE_DEFAULT ) {
301
+ $output = self::fetch($filenames, $data, $expires, $cache_mode);
302
  echo $output;
303
  return $output;
304
  }
311
  * @return bool|string
312
  */
313
  public static function render_string( $string, $data = array() ) {
314
+ $compiled = self::compile_string($string, $data);
315
  echo $compiled;
316
  return $compiled;
317
  }
331
  if ( $sidebar == '' ) {
332
  $sidebar = 'sidebar.php';
333
  }
334
+ if ( strstr(strtolower($sidebar), '.php') ) {
335
+ return self::get_sidebar_from_php($sidebar, $data);
336
  }
337
+ return self::compile($sidebar, $data);
338
  }
339
 
340
  /**
346
  */
347
  public static function get_sidebar_from_php( $sidebar = '', $data ) {
348
  $caller = self::get_calling_script_dir();
349
+ $loader = new Loader();
350
+ $uris = $loader->get_locations($caller);
351
  ob_start();
352
  $found = false;
353
  foreach ( $uris as $uri ) {
354
+ if ( file_exists(trailingslashit($uri).$sidebar) ) {
355
+ include trailingslashit($uri).$sidebar;
356
  $found = true;
357
  break;
358
  }
359
  }
360
  if ( !$found ) {
361
+ Helper::error_log('error loading your sidebar, check to make sure the file exists');
362
  }
363
  $ret = ob_get_contents();
364
  ob_end_clean();
375
  * @return TimberFunctionWrapper
376
  */
377
  public static function get_widgets( $widget_id ) {
378
+ return trim(Helper::function_wrapper('dynamic_sidebar', array($widget_id), true));
379
  }
380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  /* Pagination
382
  ================================ */
383
 
392
  global $paged;
393
  global $wp_rewrite;
394
  $args = array();
395
+ $args['total'] = ceil($wp_query->found_posts / $wp_query->query_vars['posts_per_page']);
396
  if ( $wp_rewrite->using_permalinks() ) {
397
+ $url = explode('?', get_pagenum_link(0));
398
+ if ( isset($url[1]) ) {
399
+ parse_str($url[1], $query);
400
  $args['add_args'] = $query;
401
  }
402
+ $args['format'] = $wp_rewrite->pagination_base . '/%#%';
403
+ $args['base'] = trailingslashit($url[0]).'%_%';
404
  } else {
405
  $big = 999999999;
406
+ $args['base'] = str_replace($big, '%#%', esc_url(get_pagenum_link($big)));
407
  }
408
  $args['type'] = 'array';
409
+ $args['current'] = max(1, get_query_var('paged'));
410
+ $args['mid_size'] = max(9 - $args['current'], 3);
411
+ if ( is_int($prefs) ) {
412
  $args['mid_size'] = $prefs - 2;
413
  } else {
414
+ $args = array_merge($args, $prefs);
415
  }
416
  $data = array();
417
  $data['current'] = $args['current'];
418
  $data['total'] = $args['total'];
419
+ $data['pages'] = Helper::paginate_links($args);
420
+ $next = get_next_posts_page_link($args['total']);
421
  if ( $next ) {
422
+ $data['next'] = array('link' => untrailingslashit($next), 'class' => 'page-numbers next');
423
  }
424
+ $prev = previous_posts(false);
425
  if ( $prev ) {
426
+ $data['prev'] = array('link' => untrailingslashit($prev), 'class' => 'page-numbers prev');
427
  }
428
  if ( $paged < 2 ) {
429
  $data['prev'] = '';
430
  }
431
+ if ( $data['total'] === (double) 0 ) {
432
  $data['next'] = '';
433
  }
434
  return $data;
443
  * @return string
444
  */
445
  public static function get_calling_script_dir( $offset = 0 ) {
446
+ $caller = self::get_calling_script_file($offset);
447
+ if ( !is_null($caller) ) {
448
+ $pathinfo = pathinfo($caller);
449
  $dir = $pathinfo['dirname'];
450
  return $dir;
451
  }
452
  }
453
 
454
+
455
+ /**
456
+ * Add route.
457
+ *
458
+ * @param string $route
459
+ * @param callable $callback
460
+ * @param array $args
461
+ * @deprecated since 0.20.0 and will be removed in 1.1
462
+ */
463
+ public static function add_route( $route, $callback, $args = array() ) {
464
+ Helper::warn('Timber::add_route (and accompanying methods for load_view, etc. Have been deprecated and will soon be removed. Please update your theme with Route::map. You can read more in the 1.0 Upgrade Guide: https://github.com/timber/timber/wiki/1.0-Upgrade-Guide');
465
+ \Routes::map($route, $callback, $args);
466
+ }
467
+
468
  /**
469
  * Get calling script file.
470
  *
488
  }
489
  return $caller;
490
  }
491
+ }
 
 
lib/Twig.php ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Timber;
4
+
5
+ use Timber\URLHelper;
6
+ use Timber\Helper;
7
+
8
+ use Timber\Post;
9
+ use Timber\Term;
10
+ use Timber\Image;
11
+ use Timber\User;
12
+
13
+ class Twig {
14
+
15
+ public static $dir_name;
16
+
17
+ /**
18
+ * @codeCoverageIgnore
19
+ */
20
+ public static function init() {
21
+ new self();
22
+ }
23
+
24
+ /**
25
+ * @codeCoverageIgnore
26
+ */
27
+ function __construct() {
28
+ add_action('timber/twig/filters', array($this, 'add_timber_filters'));
29
+ }
30
+
31
+ /**
32
+ *
33
+ *
34
+ * @param Twig_Environment $twig
35
+ * @return Twig_Environment
36
+ */
37
+ function add_timber_filters( $twig ) {
38
+ /* image filters */
39
+ $twig->addFilter(new \Twig_SimpleFilter('resize', array('TimberImageHelper', 'resize')));
40
+ $twig->addFilter(new \Twig_SimpleFilter('retina', array('TimberImageHelper', 'retina_resize')));
41
+ $twig->addFilter(new \Twig_SimpleFilter('letterbox', array('TimberImageHelper', 'letterbox')));
42
+ $twig->addFilter(new \Twig_SimpleFilter('tojpg', array('TimberImageHelper', 'img_to_jpg')));
43
+
44
+ /* debugging filters */
45
+ $twig->addFilter(new \Twig_SimpleFilter('get_class', 'get_class'));
46
+ $twig->addFilter(new \Twig_SimpleFilter('get_type', 'get_type'));
47
+ $twig->addFilter(new \Twig_SimpleFilter('print_r', function( $arr ) {
48
+ return print_r($arr, true);
49
+ } ));
50
+
51
+ /* other filters */
52
+ $twig->addFilter(new \Twig_SimpleFilter('stripshortcodes', 'strip_shortcodes'));
53
+ $twig->addFilter(new \Twig_SimpleFilter('array', array($this, 'to_array')));
54
+ $twig->addFilter(new \Twig_SimpleFilter('excerpt', 'wp_trim_words'));
55
+ $twig->addFilter(new \Twig_SimpleFilter('function', array($this, 'exec_function')));
56
+ $twig->addFilter(new \Twig_SimpleFilter('pretags', array($this, 'twig_pretags')));
57
+ $twig->addFilter(new \Twig_SimpleFilter('sanitize', 'sanitize_title'));
58
+ $twig->addFilter(new \Twig_SimpleFilter('shortcodes', 'do_shortcode'));
59
+ $twig->addFilter(new \Twig_SimpleFilter('time_ago', array($this, 'time_ago')));
60
+ $twig->addFilter(new \Twig_SimpleFilter('wpautop', 'wpautop'));
61
+ $twig->addFilter(new \Twig_SimpleFilter('list', array($this, 'add_list_separators')));
62
+
63
+ $twig->addFilter(new \Twig_SimpleFilter('relative', function( $link ) {
64
+ return URLHelper::get_rel_url($link, true);
65
+ } ));
66
+
67
+ $twig->addFilter(new \Twig_SimpleFilter('date', array($this, 'intl_date')));
68
+
69
+ $twig->addFilter(new \Twig_SimpleFilter('truncate', function( $text, $len ) {
70
+ return Helper::trim_words($text, $len);
71
+ } ));
72
+
73
+ /* actions and filters */
74
+ $twig->addFunction(new \Twig_SimpleFunction('action', function( $context ) {
75
+ $args = func_get_args();
76
+ array_shift($args);
77
+ $args[] = $context;
78
+ call_user_func_array('do_action', $args);
79
+ }, array('needs_context' => true)));
80
+
81
+ $twig->addFilter(new \Twig_SimpleFilter('apply_filters', function() {
82
+ $args = func_get_args();
83
+ $tag = current(array_splice($args, 1, 1));
84
+
85
+ return apply_filters_ref_array($tag, $args);
86
+ } ));
87
+ $twig->addFunction(new \Twig_SimpleFunction('function', array(&$this, 'exec_function')));
88
+ $twig->addFunction(new \Twig_SimpleFunction('fn', array(&$this, 'exec_function')));
89
+
90
+ $twig->addFunction(new \Twig_SimpleFunction('shortcode', 'do_shortcode'));
91
+
92
+ /* TimberObjects */
93
+ $twig->addFunction(new \Twig_SimpleFunction('TimberPost', function( $pid, $PostClass = 'Timber\Post' ) {
94
+ if ( is_array($pid) && !Helper::is_array_assoc($pid) ) {
95
+ foreach ( $pid as &$p ) {
96
+ $p = new $PostClass($p);
97
+ }
98
+ return $pid;
99
+ }
100
+ return new $PostClass($pid);
101
+ } ));
102
+ $twig->addFunction(new \Twig_SimpleFunction('TimberImage', function( $pid = false, $ImageClass = 'Timber\Image' ) {
103
+ if ( is_array($pid) && !Helper::is_array_assoc($pid) ) {
104
+ foreach ( $pid as &$p ) {
105
+ $p = new $ImageClass($p);
106
+ }
107
+ return $pid;
108
+ }
109
+ return new $ImageClass($pid);
110
+ } ));
111
+
112
+ $twig->addFunction(new \Twig_SimpleFunction('TimberTerm', function( $pid, $TermClass = 'Timber\Term' ) {
113
+ if ( is_array($pid) && !Helper::is_array_assoc($pid) ) {
114
+ foreach ( $pid as &$p ) {
115
+ $p = new $TermClass($p);
116
+ }
117
+ return $pid;
118
+ }
119
+ return new $TermClass($pid);
120
+ } ));
121
+ $twig->addFunction(new \Twig_SimpleFunction('TimberUser', function( $pid, $UserClass = 'Timber\User' ) {
122
+ if ( is_array($pid) && !Helper::is_array_assoc($pid) ) {
123
+ foreach ( $pid as &$p ) {
124
+ $p = new $UserClass($p);
125
+ }
126
+ return $pid;
127
+ }
128
+ return new $UserClass($pid);
129
+ } ));
130
+
131
+ /* TimberObjects Alias */
132
+ $twig->addFunction(new \Twig_SimpleFunction('Post', function( $pid, $PostClass = 'Timber\Post' ) {
133
+ if ( is_array($pid) && !Helper::is_array_assoc($pid) ) {
134
+ foreach ( $pid as &$p ) {
135
+ $p = new $PostClass($p);
136
+ }
137
+ return $pid;
138
+ }
139
+ return new $PostClass($pid);
140
+ } ));
141
+ $twig->addFunction(new \Twig_SimpleFunction('Image', function( $pid, $ImageClass = 'Timber\Image' ) {
142
+ if ( is_array($pid) && !Helper::is_array_assoc($pid) ) {
143
+ foreach ( $pid as &$p ) {
144
+ $p = new $ImageClass($p);
145
+ }
146
+ return $pid;
147
+ }
148
+ return new $ImageClass($pid);
149
+ } ));
150
+ $twig->addFunction(new \Twig_SimpleFunction('Term', function( $pid, $TermClass = 'Timber\Term' ) {
151
+ if ( is_array($pid) && !Helper::is_array_assoc($pid) ) {
152
+ foreach ( $pid as &$p ) {
153
+ $p = new $TermClass($p);
154
+ }
155
+ return $pid;
156
+ }
157
+ return new $TermClass($pid);
158
+ } ));
159
+ $twig->addFunction(new \Twig_SimpleFunction('User', function( $pid, $UserClass = 'Timber\User' ) {
160
+ if ( is_array($pid) && !Helper::is_array_assoc($pid) ) {
161
+ foreach ( $pid as &$p ) {
162
+ $p = new $UserClass($p);
163
+ }
164
+ return $pid;
165
+ }
166
+ return new $UserClass($pid);
167
+ } ));
168
+
169
+ /* bloginfo and translate */
170
+ $twig->addFunction('bloginfo', new \Twig_SimpleFunction('bloginfo', function( $show = '', $filter = 'raw' ) {
171
+ return get_bloginfo($show, $filter);
172
+ } ));
173
+ $twig->addFunction('__', new \Twig_SimpleFunction('__', function( $text, $domain = 'default' ) {
174
+ return __($text, $domain);
175
+ } ));
176
+ $twig->addFunction('translate', new \Twig_SimpleFunction('translate', function( $text, $domain = 'default' ) {
177
+ return translate($text, $domain);
178
+ } ));
179
+ $twig->addFunction('_e', new \Twig_SimpleFunction('_e', function( $text, $domain = 'default' ) {
180
+ return _e($text, $domain);
181
+ } ));
182
+ $twig->addFunction('_n', new \Twig_SimpleFunction('_n', function( $single, $plural, $number, $domain = 'default' ) {
183
+ return _n($single, $plural, $number, $domain);
184
+ } ));
185
+ $twig->addFunction('_x', new \Twig_SimpleFunction('_x', function( $text, $context, $domain = 'default' ) {
186
+ return _x($text, $context, $domain);
187
+ } ));
188
+ $twig->addFunction('_ex', new \Twig_SimpleFunction('_ex', function( $text, $context, $domain = 'default' ) {
189
+ return _ex($text, $context, $domain);
190
+ } ));
191
+ $twig->addFunction('_nx', new \Twig_SimpleFunction('_nx', function( $single, $plural, $number, $context, $domain = 'default' ) {
192
+ return _nx($single, $plural, $number, $context, $domain);
193
+ } ));
194
+ $twig->addFunction('_n_noop', new \Twig_SimpleFunction('_n_noop', function( $singular, $plural, $domain = 'default' ) {
195
+ return _n_noop($singular, $plural, $domain);
196
+ } ));
197
+ $twig->addFunction('_nx_noop', new \Twig_SimpleFunction('_nx_noop', function( $singular, $plural, $context, $domain = 'default' ) {
198
+ return _nx_noop($singular, $plural, $context, $domain);
199
+ } ));
200
+ $twig->addFunction('translate_nooped_plural', new \Twig_SimpleFunction('translate_nooped_plural', function( $nooped_plural, $count, $domain = 'default' ) {
201
+ return translate_nooped_plural($nooped_plural, $count, $domain);
202
+ } ));
203
+ $twig = apply_filters('timber/twig', $twig);
204
+ /**
205
+ * get_twig is deprecated, use timber/twig
206
+ */
207
+ $twig = apply_filters('get_twig', $twig);
208
+ return $twig;
209
+ }
210
+
211
+ /**
212
+ *
213
+ *
214
+ * @param mixed $arr
215
+ * @return array
216
+ */
217
+ function to_array( $arr ) {
218
+ if ( is_array($arr) ) {
219
+ return $arr;
220
+ }
221
+ $arr = array($arr);
222
+ return $arr;
223
+ }
224
+
225
+ /**
226
+ *
227
+ *
228
+ * @param string $function_name
229
+ * @return mixed
230
+ */
231
+ function exec_function( $function_name ) {
232
+ $args = func_get_args();
233
+ array_shift($args);
234
+ if ( is_string($function_name) ) {
235
+ $function_name = trim($function_name);
236
+ }
237
+ return call_user_func_array($function_name, ($args));
238
+ }
239
+
240
+ /**
241
+ *
242
+ *
243
+ * @param string $content
244
+ * @return string
245
+ */
246
+ function twig_pretags( $content ) {
247
+ return preg_replace_callback('|<pre.*>(.*)</pre|isU', array(&$this, 'convert_pre_entities'), $content);
248
+ }
249
+
250
+ /**
251
+ *
252
+ *
253
+ * @param array $matches
254
+ * @return string
255
+ */
256
+ function convert_pre_entities( $matches ) {
257
+ return str_replace($matches[1], htmlentities($matches[1]), $matches[0]);
258
+ }
259
+
260
+ /**
261
+ *
262
+ *
263
+ * @param string $date
264
+ * @param string $format (optional)
265
+ * @return string
266
+ */
267
+ function intl_date( $date, $format = null ) {
268
+ if ( $format === null ) {
269
+ $format = get_option('date_format');
270
+ }
271
+
272
+ if ( $date instanceof \DateTime ) {
273
+ $timestamp = $date->getTimestamp() + $date->getOffset();
274
+ } else if ( is_numeric($date) && strtotime($date) === false ) {
275
+ $timestamp = intval($date);
276
+ } else {
277
+ $timestamp = strtotime($date);
278
+ }
279
+
280
+ return date_i18n($format, $timestamp);
281
+ }
282
+
283
+ /**
284
+ * @param int|string $from
285
+ * @param int|string $to
286
+ * @param string $format_past
287
+ * @param string $format_future
288
+ * @return string
289
+ */
290
+ function time_ago( $from, $to = null, $format_past = '%s ago', $format_future = '%s from now' ) {
291
+ $to = $to === null ? time() : $to;
292
+ $to = is_int($to) ? $to : strtotime($to);
293
+ $from = is_int($from) ? $from : strtotime($from);
294
+
295
+ if ( $from < $to ) {
296
+ return sprintf($format_past, human_time_diff($from, $to));
297
+ } else {
298
+ return sprintf($format_future, human_time_diff($to, $from));
299
+ }
300
+ }
301
+
302
+ /**
303
+ * @param array $arr
304
+ * @param string $first_delimiter
305
+ * @param string $second_delimiter
306
+ * @return string
307
+ */
308
+ function add_list_separators( $arr, $first_delimiter = ',', $second_delimiter = 'and' ) {
309
+ $length = count($arr);
310
+ $list = '';
311
+ foreach ( $arr as $index => $item ) {
312
+ if ( $index < $length - 2 ) {
313
+ $delimiter = $first_delimiter.' ';
314
+ } elseif ( $index == $length - 2 ) {
315
+ $delimiter = ' '.$second_delimiter.' ';
316
+ } else {
317
+ $delimiter = '';
318
+ }
319
+ $list = $list.$item.$delimiter;
320
+ }
321
+ return $list;
322
+ }
323
+
324
+ }
lib/{timber-url-helper.php → URLHelper.php} RENAMED
@@ -1,6 +1,8 @@
1
  <?php
2
 
3
- class TimberURLHelper {
 
 
4
 
5
  /**
6
  *
@@ -9,13 +11,13 @@ class TimberURLHelper {
9
  */
10
  public static function get_current_url() {
11
  $pageURL = "http://";
12
- if ( isset( $_SERVER['HTTPS'] ) && $_SERVER["HTTPS"] == "on" ) {
13
- $pageURL = "https://";;
14
  }
15
  if ( isset($_SERVER["SERVER_PORT"]) && $_SERVER["SERVER_PORT"] && $_SERVER["SERVER_PORT"] != "80" ) {
16
- $pageURL .= self::get_host() . ":" . $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
17
  } else {
18
- $pageURL .= self::get_host() . $_SERVER["REQUEST_URI"];
19
  }
20
  return $pageURL;
21
  }
@@ -27,11 +29,11 @@ class TimberURLHelper {
27
  * @return bool
28
  */
29
  public static function is_url( $url ) {
30
- if ( !is_string( $url ) ) {
31
  return false;
32
  }
33
- $url = strtolower( $url );
34
- if ( strstr( $url, '://' ) ) {
35
  return true;
36
  }
37
  return false;
@@ -43,12 +45,12 @@ class TimberURLHelper {
43
  * @return string
44
  */
45
  public static function get_path_base() {
46
- $struc = get_option( 'permalink_structure' );
47
- $struc = explode( '/', $struc );
48
  $p = '/';
49
  foreach ( $struc as $s ) {
50
- if ( !strstr( $s, '%' ) && strlen( $s ) ) {
51
- $p .= $s . '/';
52
  }
53
  }
54
  return $p;
@@ -62,21 +64,21 @@ class TimberURLHelper {
62
  * @return string
63
  */
64
  public static function get_rel_url( $url, $force = false ) {
65
- $url_info = parse_url( $url );
66
- if ( isset( $url_info['host'] ) && $url_info['host'] != self::get_host() && !$force ) {
67
  return $url;
68
  }
69
  $link = '';
70
- if ( isset( $url_info['path'] ) ) {
71
  $link = $url_info['path'];
72
  }
73
- if ( isset( $url_info['query'] ) && strlen( $url_info['query'] ) ) {
74
- $link .= '?' . $url_info['query'];
75
  }
76
- if ( isset( $url_info['fragment'] ) && strlen( $url_info['fragment'] ) ) {
77
- $link .= '#' . $url_info['fragment'];
78
  }
79
- $link = TimberURLHelper::remove_double_slashes( $link );
80
  return $link;
81
  }
82
 
@@ -86,10 +88,10 @@ class TimberURLHelper {
86
  * @return string the HTTP_HOST or SERVER_NAME
87
  */
88
  public static function get_host() {
89
- if (isset($_SERVER['HTTP_HOST'])) {
90
  return $_SERVER['HTTP_HOST'];
91
  }
92
- if (isset($_SERVER['SERVER_NAME'])) {
93
  return $_SERVER['SERVER_NAME'];
94
  }
95
  return '';
@@ -102,7 +104,7 @@ class TimberURLHelper {
102
  * @return bool
103
  */
104
  public static function is_local( $url ) {
105
- if ( strstr( $url, self::get_host() ) ) {
106
  return true;
107
  }
108
  return false;
@@ -116,8 +118,8 @@ class TimberURLHelper {
116
  */
117
  public static function get_full_path( $src ) {
118
  $root = ABSPATH;
119
- $old_root_path = $root . $src;
120
- $old_root_path = str_replace( '//', '/', $old_root_path );
121
  return $old_root_path;
122
  }
123
 
@@ -130,15 +132,15 @@ class TimberURLHelper {
130
  * @return string
131
  */
132
  public static function url_to_file_system( $url ) {
133
- $url_parts = parse_url( $url );
134
- $path = ABSPATH . $url_parts['path'];
135
- $path = str_replace( '//', '/', $path );
136
  return $path;
137
  }
138
 
139
  public static function file_system_to_url( $fs ) {
140
- $relative_path = self::get_rel_path( $fs );
141
- $home = home_url( '/'.$relative_path );
142
  return $home;
143
  }
144
 
@@ -149,12 +151,12 @@ class TimberURLHelper {
149
  * @return string
150
  */
151
  public static function get_rel_path( $src ) {
152
- if ( strstr( $src, ABSPATH ) ) {
153
- return str_replace( ABSPATH, '', $src );
154
  }
155
  //its outside the wordpress directory, alternate setups:
156
- $src = str_replace( WP_CONTENT_DIR, '', $src );
157
- return WP_CONTENT_SUBDIR . $src;
158
  }
159
 
160
  /**
@@ -164,9 +166,9 @@ class TimberURLHelper {
164
  * @return string
165
  */
166
  public static function remove_double_slashes( $url ) {
167
- $url = str_replace( '//', '/', $url );
168
- if ( strstr( $url, 'http:' ) && !strstr( $url, 'http://' ) ) {
169
- $url = str_replace( 'http:/', 'http://', $url );
170
  }
171
  return $url;
172
  }
@@ -179,19 +181,19 @@ class TimberURLHelper {
179
  * @return string
180
  */
181
  public static function prepend_to_url( $url, $path ) {
182
- if ( strstr( strtolower( $url ), 'http' ) ) {
183
- $url_parts = parse_url( $url );
184
- $url = $url_parts['scheme'] . '://' . $url_parts['host'] . $path . $url_parts['path'];
185
- if ( isset( $url_parts['query'] ) ) {
186
  $url .= $url_parts['query'];
187
  }
188
- if ( isset( $url_parts['fragment'] ) ) {
189
  $url .= $url_parts['fragment'];
190
  }
191
  } else {
192
- $url = $url . $path;
193
  }
194
- return self::remove_double_slashes( $url );
195
  }
196
 
197
  /**
@@ -201,8 +203,8 @@ class TimberURLHelper {
201
  * @return string
202
  */
203
  public static function preslashit( $path ) {
204
- if ( strpos( $path, '/' ) != 0 ) {
205
- $path = '/' . $path;
206
  }
207
  return $path;
208
  }
@@ -213,7 +215,7 @@ class TimberURLHelper {
213
  * @return boolean true if $path is an absolute url, false if relative.
214
  */
215
  public static function is_absolute( $path ) {
216
- return (boolean) ( strstr( $path, 'http' ) );
217
  }
218
 
219
  /**
@@ -225,21 +227,21 @@ class TimberURLHelper {
225
  * @return boolean if $url points to an external location returns true
226
  */
227
  public static function is_external_content( $url ) {
228
- $is_external = TimberURLHelper::is_absolute( $url ) && ! TimberURLHelper::is_internal_content( $url );
229
 
230
  return $is_external;
231
  }
232
 
233
- private static function is_internal_content($url) {
234
  // using content_url() instead of site_url or home_url is IMPORTANT
235
  // otherwise you run into errors with sites that:
236
  // 1. use WPML plugin
237
  // 2. or redefine content directory
238
- $is_content_url = strstr( $url, content_url() );
239
 
240
  // this case covers when the upload directory has been redefined
241
  $upload_dir = wp_upload_dir();
242
- $is_upload_url = strstr( $url, $upload_dir['baseurl'] );
243
 
244
  return $is_content_url || $is_upload_url;
245
  }
@@ -252,8 +254,8 @@ class TimberURLHelper {
252
  * true if it's a subdomain (http://cdn.example.org = true)
253
  */
254
  public static function is_external( $url ) {
255
- $has_http = strstr( strtolower( $url ), 'http' );
256
- $on_domain = strstr( $url, self::get_host() );
257
  if ( $has_http && !$on_domain ) {
258
  return true;
259
  }
@@ -268,41 +270,10 @@ class TimberURLHelper {
268
  */
269
  public static function remove_trailing_slash( $link ) {
270
  if ( $link != "/" )
271
- $link = untrailingslashit( $link );
272
  return $link;
273
  }
274
 
275
- /**
276
- * Download an external file via a URL
277
- *
278
- * @param string $url
279
- * @param int $timeout
280
- * @return string|WP_Error the location of the temporay file name or an error
281
- * @deprecated since 0.20.0
282
- */
283
- static function download_url( $url, $timeout = 300 ) {
284
- if ( !$url ) {
285
- return new WP_Error( 'http_no_url', __( 'Invalid URL Provided.' ) );
286
- }
287
-
288
- $tmpfname = wp_tempnam( $url );
289
- if ( !$tmpfname ) {
290
- return new WP_Error( 'http_no_file', __( 'Could not create Temporary file.' ) );
291
- }
292
-
293
- $response = wp_remote_get( $url, array( 'timeout' => $timeout, 'stream' => true, 'filename' => $tmpfname ) );
294
-
295
- if ( is_wp_error( $response ) ) {
296
- unlink( $tmpfname );
297
- return $response;
298
- }
299
- if ( 200 != wp_remote_retrieve_response_code( $response ) ) {
300
- unlink( $tmpfname );
301
- return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ) );
302
- }
303
- return $tmpfname;
304
- }
305
-
306
  /**
307
  * Returns the url parameters, for example for url http://example.org/blog/post/news/2014/whatever
308
  * this will return array('blog', 'post', 'news', '2014', 'whatever');
@@ -312,10 +283,10 @@ class TimberURLHelper {
312
  * @return array|string
313
  */
314
  public static function get_params( $i = false ) {
315
- $args = explode( '/', trim( strtolower( $_SERVER['REQUEST_URI'] ) ) );
316
  $newargs = array();
317
  foreach ( $args as $arg ) {
318
- if ( strlen( $arg ) ) {
319
  $newargs[] = $arg;
320
  }
321
  }
@@ -324,9 +295,9 @@ class TimberURLHelper {
324
  }
325
  if ( $i < 0 ) {
326
  //count from end
327
- $i = count( $newargs ) + $i;
328
  }
329
- if ( isset( $newargs[$i] ) ) {
330
  return $newargs[$i];
331
  }
332
  }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ class URLHelper {
6
 
7
  /**
8
  *
11
  */
12
  public static function get_current_url() {
13
  $pageURL = "http://";
14
+ if ( isset($_SERVER['HTTPS']) && $_SERVER["HTTPS"] == "on" ) {
15
+ $pageURL = "https://"; ;
16
  }
17
  if ( isset($_SERVER["SERVER_PORT"]) && $_SERVER["SERVER_PORT"] && $_SERVER["SERVER_PORT"] != "80" ) {
18
+ $pageURL .= self::get_host().":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
19
  } else {
20
+ $pageURL .= self::get_host().$_SERVER["REQUEST_URI"];
21
  }
22
  return $pageURL;
23
  }
29
  * @return bool
30
  */
31
  public static function is_url( $url ) {
32
+ if ( !is_string($url) ) {
33
  return false;
34
  }
35
+ $url = strtolower($url);
36
+ if ( strstr($url, '://') ) {
37
  return true;
38
  }
39
  return false;
45
  * @return string
46
  */
47
  public static function get_path_base() {
48
+ $struc = get_option('permalink_structure');
49
+ $struc = explode('/', $struc);
50
  $p = '/';
51
  foreach ( $struc as $s ) {
52
+ if ( !strstr($s, '%') && strlen($s) ) {
53
+ $p .= $s.'/';
54
  }
55
  }
56
  return $p;
64
  * @return string
65
  */
66
  public static function get_rel_url( $url, $force = false ) {
67
+ $url_info = parse_url($url);
68
+ if ( isset($url_info['host']) && $url_info['host'] != self::get_host() && !$force ) {
69
  return $url;
70
  }
71
  $link = '';
72
+ if ( isset($url_info['path']) ) {
73
  $link = $url_info['path'];
74
  }
75
+ if ( isset($url_info['query']) && strlen($url_info['query']) ) {
76
+ $link .= '?'.$url_info['query'];
77
  }
78
+ if ( isset($url_info['fragment']) && strlen($url_info['fragment']) ) {
79
+ $link .= '#'.$url_info['fragment'];
80
  }
81
+ $link = self::remove_double_slashes($link);
82
  return $link;
83
  }
84
 
88
  * @return string the HTTP_HOST or SERVER_NAME
89
  */
90
  public static function get_host() {
91
+ if ( isset($_SERVER['HTTP_HOST']) ) {
92
  return $_SERVER['HTTP_HOST'];
93
  }
94
+ if ( isset($_SERVER['SERVER_NAME']) ) {
95
  return $_SERVER['SERVER_NAME'];
96
  }
97
  return '';
104
  * @return bool
105
  */
106
  public static function is_local( $url ) {
107
+ if ( strstr($url, self::get_host()) ) {
108
  return true;
109
  }
110
  return false;
118
  */
119
  public static function get_full_path( $src ) {
120
  $root = ABSPATH;
121
+ $old_root_path = $root.$src;
122
+ $old_root_path = str_replace('//', '/', $old_root_path);
123
  return $old_root_path;
124
  }
125
 
132
  * @return string
133
  */
134
  public static function url_to_file_system( $url ) {
135
+ $url_parts = parse_url($url);
136
+ $path = ABSPATH.$url_parts['path'];
137
+ $path = str_replace('//', '/', $path);
138
  return $path;
139
  }
140
 
141
  public static function file_system_to_url( $fs ) {
142
+ $relative_path = self::get_rel_path($fs);
143
+ $home = home_url('/'.$relative_path);
144
  return $home;
145
  }
146
 
151
  * @return string
152
  */
153
  public static function get_rel_path( $src ) {
154
+ if ( strstr($src, ABSPATH) ) {
155
+ return str_replace(ABSPATH, '', $src);
156
  }
157
  //its outside the wordpress directory, alternate setups:
158
+ $src = str_replace(WP_CONTENT_DIR, '', $src);
159
+ return WP_CONTENT_SUBDIR.$src;
160
  }
161
 
162
  /**
166
  * @return string
167
  */
168
  public static function remove_double_slashes( $url ) {
169
+ $url = str_replace('//', '/', $url);
170
+ if ( strstr($url, 'http:') && !strstr($url, 'http://') ) {
171
+ $url = str_replace('http:/', 'http://', $url);
172
  }
173
  return $url;
174
  }
181
  * @return string
182
  */
183
  public static function prepend_to_url( $url, $path ) {
184
+ if ( strstr(strtolower($url), 'http') ) {
185
+ $url_parts = parse_url($url);
186
+ $url = $url_parts['scheme'].'://'.$url_parts['host'].$path.$url_parts['path'];
187
+ if ( isset($url_parts['query']) ) {
188
  $url .= $url_parts['query'];
189
  }
190
+ if ( isset($url_parts['fragment']) ) {
191
  $url .= $url_parts['fragment'];
192
  }
193
  } else {
194
+ $url = $url.$path;
195
  }
196
+ return self::remove_double_slashes($url);
197
  }
198
 
199
  /**
203
  * @return string
204
  */
205
  public static function preslashit( $path ) {
206
+ if ( strpos($path, '/') != 0 ) {
207
+ $path = '/'.$path;
208
  }
209
  return $path;
210
  }
215
  * @return boolean true if $path is an absolute url, false if relative.
216
  */
217
  public static function is_absolute( $path ) {
218
+ return (boolean) (strstr($path, 'http'));
219
  }
220
 
221
  /**
227
  * @return boolean if $url points to an external location returns true
228
  */
229
  public static function is_external_content( $url ) {
230
+ $is_external = self::is_absolute($url) && !self::is_internal_content($url);
231
 
232
  return $is_external;
233
  }
234
 
235
+ private static function is_internal_content( $url ) {
236
  // using content_url() instead of site_url or home_url is IMPORTANT
237
  // otherwise you run into errors with sites that:
238
  // 1. use WPML plugin
239
  // 2. or redefine content directory
240
+ $is_content_url = strstr($url, content_url());
241
 
242
  // this case covers when the upload directory has been redefined
243
  $upload_dir = wp_upload_dir();
244
+ $is_upload_url = strstr($url, $upload_dir['baseurl']);
245
 
246
  return $is_content_url || $is_upload_url;
247
  }
254
  * true if it's a subdomain (http://cdn.example.org = true)
255
  */
256
  public static function is_external( $url ) {
257
+ $has_http = strstr(strtolower($url), 'http');
258
+ $on_domain = strstr($url, self::get_host());
259
  if ( $has_http && !$on_domain ) {
260
  return true;
261
  }
270
  */
271
  public static function remove_trailing_slash( $link ) {
272
  if ( $link != "/" )
273
+ $link = untrailingslashit($link);
274
  return $link;
275
  }
276
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  /**
278
  * Returns the url parameters, for example for url http://example.org/blog/post/news/2014/whatever
279
  * this will return array('blog', 'post', 'news', '2014', 'whatever');
283
  * @return array|string
284
  */
285
  public static function get_params( $i = false ) {
286
+ $args = explode('/', trim(strtolower($_SERVER['REQUEST_URI'])));
287
  $newargs = array();
288
  foreach ( $args as $arg ) {
289
+ if ( strlen($arg) ) {
290
  $newargs[] = $arg;
291
  }
292
  }
295
  }
296
  if ( $i < 0 ) {
297
  //count from end
298
+ $i = count($newargs) + $i;
299
  }
300
+ if ( isset($newargs[$i]) ) {
301
  return $newargs[$i];
302
  }
303
  }
lib/{timber-user.php → User.php} RENAMED
@@ -1,5 +1,14 @@
1
  <?php
2
 
 
 
 
 
 
 
 
 
 
3
  /**
4
  * This is used in Timber to represent users retrived from WordPress. You can call `$my_user = new TimberUser(123);` directly, or access it through the `{{ post.author }}` method.
5
  * @example
@@ -17,7 +26,7 @@
17
  * <p class="article-info">This article is called "Consider the Lobster" and it's by David Foster Wallace
18
  * ```
19
  */
20
- class TimberUser extends TimberCore implements TimberCoreInterface {
21
 
22
  public $object_type = 'user';
23
  public static $representation = 'user';
@@ -31,6 +40,12 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
31
  public $description;
32
  public $display_name;
33
 
 
 
 
 
 
 
34
  /**
35
  * @api
36
  * @var string The first name of the user
@@ -53,7 +68,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
53
  /**
54
  * @param int|bool $uid
55
  */
56
- function __construct($uid = false) {
57
  $this->init($uid);
58
  }
59
 
@@ -84,7 +99,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
84
  * @param string $field_name
85
  * @return null
86
  */
87
- function get_meta($field_name) {
88
  return $this->get_meta_field($field_name);
89
  }
90
 
@@ -93,7 +108,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
93
  * @param string $field
94
  * @param mixed $value
95
  */
96
- function __set($field, $value) {
97
  if ( $field == 'name' ) {
98
  $this->display_name = $value;
99
  }
@@ -104,7 +119,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
104
  * @internal
105
  * @param int|bool $uid The user ID to use
106
  */
107
- protected function init($uid = false) {
108
  if ( $uid === false ) {
109
  $uid = get_current_user_id();
110
  }
@@ -129,6 +144,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
129
  }
130
  $this->id = $this->ID;
131
  $this->name = $this->name();
 
132
  $custom = $this->get_custom();
133
  $this->import($custom);
134
  }
@@ -137,7 +153,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
137
  * @param string $field_name
138
  * @return mixed
139
  */
140
- function get_meta_field($field_name) {
141
  $value = null;
142
  $value = apply_filters('timber_user_get_meta_field_pre', $value, $this->ID, $field_name, $this);
143
  if ( $value === null ) {
@@ -158,7 +174,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
158
  $um = get_user_meta($this->ID);
159
  }
160
  $custom = array();
161
- foreach ($um as $key => $value) {
162
  if ( is_array($value) && count($value) == 1 ) {
163
  $value = $value[0];
164
  }
@@ -193,7 +209,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
193
  * @param string $field_name
194
  * @return mixed
195
  */
196
- function meta($field_name) {
197
  return $this->get_meta_field($field_name);
198
  }
199
 
@@ -202,7 +218,7 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
202
  * @return string ex: /author/lincoln
203
  */
204
  public function path() {
205
- return TimberURLHelper::get_rel_url($this->get_link());
206
  }
207
 
208
  /**
@@ -212,37 +228,4 @@ class TimberUser extends TimberCore implements TimberCoreInterface {
212
  public function slug() {
213
  return $this->user_nicename;
214
  }
215
-
216
- /**
217
- * @deprecated 0.21.9
218
- * @return string The link to a user's profile page
219
- */
220
- function get_link() {
221
- return $this->link();
222
- }
223
-
224
- /**
225
- * @deprecated 0.21.8
226
- * @return string ex: /author/lincoln
227
- */
228
- function get_path() {
229
- return $this->path();
230
- }
231
-
232
- /**
233
- * @deprecated 0.21.8
234
- * @return string
235
- */
236
- function get_permalink() {
237
- return $this->get_link();
238
- }
239
-
240
- /**
241
- * @deprecated 0.21.8
242
- * @return string
243
- */
244
- function permalink() {
245
- return $this->get_permalink();
246
- }
247
-
248
- }
1
  <?php
2
 
3
+ namespace Timber;
4
+
5
+ use Timber\Core;
6
+ use Timber\CoreInterface;
7
+
8
+ use Timber\URLHelper;
9
+
10
+ use Timber\Image;
11
+
12
  /**
13
  * This is used in Timber to represent users retrived from WordPress. You can call `$my_user = new TimberUser(123);` directly, or access it through the `{{ post.author }}` method.
14
  * @example
26
  * <p class="article-info">This article is called "Consider the Lobster" and it's by David Foster Wallace
27
  * ```
28
  */
29
+ class User extends Core implements CoreInterface {
30
 
31
  public $object_type = 'user';
32
  public static $representation = 'user';
40
  public $description;
41
  public $display_name;
42
 
43
+ /**
44
+ * @api
45
+ * @var string|Image The URL of the author's avatar
46
+ */
47
+ public $avatar;
48
+
49
  /**
50
  * @api
51
  * @var string The first name of the user
68
  /**
69
  * @param int|bool $uid
70
  */
71
+ function __construct( $uid = false ) {
72
  $this->init($uid);
73
  }
74
 
99
  * @param string $field_name
100
  * @return null
101
  */
102
+ function get_meta( $field_name ) {
103
  return $this->get_meta_field($field_name);
104
  }
105
 
108
  * @param string $field
109
  * @param mixed $value
110
  */
111
+ function __set( $field, $value ) {
112
  if ( $field == 'name' ) {
113
  $this->display_name = $value;
114
  }
119
  * @internal
120
  * @param int|bool $uid The user ID to use
121
  */
122
+ protected function init( $uid = false ) {
123
  if ( $uid === false ) {
124
  $uid = get_current_user_id();
125
  }
144
  }
145
  $this->id = $this->ID;
146
  $this->name = $this->name();
147
+ $this->avatar = new Image(get_avatar_url($this->id));
148
  $custom = $this->get_custom();
149
  $this->import($custom);
150
  }
153
  * @param string $field_name
154
  * @return mixed
155
  */
156
+ function get_meta_field( $field_name ) {
157
  $value = null;
158
  $value = apply_filters('timber_user_get_meta_field_pre', $value, $this->ID, $field_name, $this);
159
  if ( $value === null ) {
174
  $um = get_user_meta($this->ID);
175
  }
176
  $custom = array();
177
+ foreach ( $um as $key => $value ) {
178
  if ( is_array($value) && count($value) == 1 ) {
179
  $value = $value[0];
180
  }
209
  * @param string $field_name
210
  * @return mixed
211
  */
212
+ function meta( $field_name ) {
213
  return $this->get_meta_field($field_name);
214
  }
215
 
218
  * @return string ex: /author/lincoln
219
  */
220
  public function path() {
221
+ return URLHelper::get_rel_url($this->link());
222
  }
223
 
224
  /**
228
  public function slug() {
229
  return $this->user_nicename;
230
  }
231
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/image/timber-image-operation-retina.php DELETED
@@ -1,74 +0,0 @@
1
- <?php
2
- /**
3
- * Contains the class for running image retina-izing operations
4
- */
5
-
6
- /**
7
- * Increases image size by a given factor
8
- * Arguments:
9
- * - factor by which to multiply image dimensions
10
- * @property float $factor the factor (ex: 2, 1.5, 1.75) to multiply dimension by
11
- */
12
- class TimberImageOperationRetina extends TimberImageOperation {
13
-
14
- private $factor;
15
-
16
- /**
17
- * Construct our operation
18
- * @param float $factor to multiply original dimensions by
19
- */
20
- function __construct($factor) {
21
- $this->factor = $factor;
22
- }
23
-
24
- /**
25
- * Generates the final filename based on the source's name and extension
26
- *
27
- * @param string $src_filename the basename of the file (ex: my-awesome-pic)
28
- * @param string $src_extension the extension (ex: .jpg)
29
- * @return string the final filename to be used (ex: my-awesome-pic@2x.jpg)
30
- */
31
- function filename($src_filename, $src_extension) {
32
- $newbase = $src_filename . '@' . $this->factor . 'x'; // add @2x, @3x, @1.5x, etc.
33
- $new_name = $newbase . '.' . $src_extension;
34
- return $new_name;
35
- }
36
-
37
- /**
38
- * Performs the actual image manipulation,
39
- * including saving the target file.
40
- *
41
- * @param string $load_filename filepath (not URL) to source file
42
- * (ex: /src/var/www/wp-content/uploads/my-pic.jpg)
43
- * @param string $save_filename filepath (not URL) where result file should be saved
44
- * (ex: /src/var/www/wp-content/uploads/my-pic@2x.jpg)
45
- * @return bool true if everything went fine, false otherwise
46
- */
47
- function run($load_filename, $save_filename){
48
- $image = wp_get_image_editor( $load_filename );
49
- if ( !is_wp_error( $image ) ) {
50
- $current_size = $image->get_size();
51
- $src_w = $current_size['width'];
52
- $src_h = $current_size['height'];
53
- // Get ratios
54
- $w = $src_w * $this->factor;
55
- $h = $src_h * $this->factor;
56
- $image->crop( 0, 0, $src_w, $src_h, $w, $h );
57
- $result = $image->save( $save_filename );
58
- if ( is_wp_error( $result ) ) {
59
- // @codeCoverageIgnoreStart
60
- TimberHelper::error_log( 'Error resizing image' );
61
- TimberHelper::error_log( $result );
62
- return false;
63
- // @codeCoverageIgnoreEnd
64
- } else {
65
- return true;
66
- }
67
- } else if ( isset( $image->error_data['error_loading_image'] ) ) {
68
- TimberHelper::error_log( 'Error loading ' . $image->error_data['error_loading_image'] );
69
- } else {
70
- TimberHelper::error_log( $image );
71
- }
72
- return false;
73
- }
74
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/integrations/acf-timber.php DELETED
@@ -1,58 +0,0 @@
1
- <?php
2
-
3
- class ACFTimber {
4
-
5
- function __construct() {
6
- add_filter( 'timber_post_get_meta', array( $this, 'post_get_meta' ), 10, 2 );
7
- add_filter( 'timber_post_get_meta_field', array( $this, 'post_get_meta_field' ), 10, 3 );
8
- add_filter( 'timber_term_get_meta', array( $this, 'term_get_meta' ), 10, 3 );
9
- add_filter( 'timber_term_get_meta_field', array( $this, 'term_get_meta_field' ), 10, 4 );
10
- add_filter( 'timber_user_get_meta_field_pre', array( $this, 'user_get_meta_field' ), 10, 3 );
11
- add_filter( 'timber_term_set_meta', array( $this, 'term_set_meta'), 10, 4 );
12
- }
13
-
14
- function post_get_meta( $customs, $post_id ) {
15
- return $customs;
16
- }
17
-
18
- function post_get_meta_field( $value, $post_id, $field_name ) {
19
- return get_field( $field_name, $post_id );
20
- }
21
-
22
- function term_get_meta_field( $value, $term_id, $field_name, $term ) {
23
- $searcher = $term->taxonomy . "_" . $term->ID;
24
- return get_field( $field_name, $searcher );
25
- }
26
-
27
- function term_set_meta( $value, $field, $term_id, $term ) {
28
- $searcher = $term->taxonomy . "_" . $term->ID;
29
- update_field( $field, $value, $searcher );
30
- return $value;
31
- }
32
-
33
- function term_get_meta( $fields, $term_id, $term ) {
34
- $searcher = $term->taxonomy . "_" . $term->ID; // save to a specific category
35
- $fds = get_fields( $searcher );
36
- if ( is_array( $fds ) ) {
37
- foreach ( $fds as $key => $value ) {
38
- $key = preg_replace( '/_/', '', $key, 1 );
39
- $key = str_replace( $searcher, '', $key );
40
- $key = preg_replace( '/_/', '', $key, 1 );
41
- $field = get_field( $key, $searcher );
42
- $fields[$key] = $field;
43
- }
44
- $fields = array_merge( $fields, $fds );
45
- }
46
- return $fields;
47
- }
48
-
49
- function user_get_meta( $fields, $user_id ) {
50
- return $fields;
51
- }
52
-
53
- function user_get_meta_field( $value, $uid, $field ) {
54
- return get_field( $field, 'user_' . $uid );
55
- }
56
- }
57
-
58
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/integrations/timber-command.php DELETED
@@ -1,36 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * These are methods that can be executed by WPCLI, other CLI mechanism or other external controllers
5
- * @package timber
6
- */
7
- class TimberCommand {
8
-
9
- public static function clear_cache($mode = 'all'){
10
- if (is_array($mode)){
11
- $mode = reset($mode);
12
- }
13
- if ($mode == 'all') {
14
- $twig_cache = self::clear_cache_twig();
15
- $timber_cache = self::clear_cache_timber();
16
- if ($twig_cache && $timber_cache){
17
- return true;
18
- }
19
- } else if ($mode == 'twig') {
20
- return self::clear_cache_twig();
21
- } else if ($mode == 'timber') {
22
- return self::clear_cache_timber();
23
- }
24
- }
25
-
26
- static function clear_cache_timber(){
27
- $loader = new TimberLoader();
28
- return $loader->clear_cache_timber();
29
- }
30
-
31
- static function clear_cache_twig(){
32
- $loader = new TimberLoader();
33
- return $loader->clear_cache_twig();
34
- }
35
-
36
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/integrations/wpcli-timber.php DELETED
@@ -1,57 +0,0 @@
1
- <?php
2
- if (!class_exists('WP_CLI_Command')) {
3
- return;
4
- }
5
-
6
- class Timber_WP_CLI_Command extends WP_CLI_Command {
7
-
8
- /**
9
- * Clears Timber and Twig's Cache
10
- *
11
- * ## EXAMPLES
12
- *
13
- * wp timber clear_cache
14
- *
15
- */
16
- public function clear_cache($mode = 'all') {
17
- TimberCommand::clear_cache($mode);
18
- }
19
-
20
- /**
21
- * Clears Twig's Cache
22
- *
23
- * ## EXAMPLES
24
- *
25
- * wp timber clear_cache_twig
26
- *
27
- */
28
- function clear_cache_twig(){
29
- $clear = TimberCommand::clear_cache_twig();
30
- if ($clear){
31
- WP_CLI::success('Cleared contents of twig cache');
32
- } else {
33
- WP_CLI::warning('Failed to clear twig cache');
34
- }
35
- }
36
-
37
- /**
38
- * Clears Timber's Cache
39
- *
40
- * ## EXAMPLES
41
- *
42
- * wp timber clear_cache_timber
43
- *
44
- */
45
- function clear_cache_timber() {
46
- $clear = TimberCommand::clear_cache_timber();
47
- $message = 'Failed to clear timber cache';
48
- if ($clear){
49
- $message = "Cleared contents of Timber's Cache";
50
- WP_CLI::success($message);
51
- } else {
52
- WP_CLI::warning($message);
53
- }
54
- return $message;
55
- }
56
-
57
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-admin.php DELETED
@@ -1,67 +0,0 @@
1
- <?php
2
-
3
- class TimberAdmin {
4
-
5
- public static function init() {
6
- $action = add_action('in_plugin_update_message-timber-library/timber.php', array(__CLASS__, 'in_plugin_update_message'), 10, 2);
7
-
8
- return add_filter( 'plugin_row_meta', array( __CLASS__, 'meta_links' ), 10, 2 );
9
- }
10
-
11
- /**
12
- * @param array $links
13
- * @param string $file
14
- * @return array
15
- */
16
- public static function meta_links( $links, $file ) {
17
- if ( strstr( $file, '/timber.php' ) ) {
18
- unset($links[2]);
19
- $links[] = '<a href="/wp-admin/plugin-install.php?tab=plugin-information&amp;plugin=timber-library&amp;TB_iframe=true&amp;width=600&amp;height=550" class="thickbox" aria-label="More information about Timber" data-title="Timber">View details</a>';
20
- $links[] = '<a href="http://upstatement.com/timber" target="_blank">Homepage</a>';
21
- $links[] = '<a href="https://github.com/jarednova/timber/wiki" target="_blank">Documentation</a>';
22
- $links[] = '<a href="https://github.com/jarednova/timber/wiki/getting-started" target="_blank">Starter Guide</a>';
23
- return $links;
24
- }
25
- return $links;
26
- }
27
-
28
- /**
29
- * in_plugin_update_message
30
- *
31
- * Displays an update message for plugin list screens.
32
- * Shows only the version updates from the current until the newest version
33
- *
34
- * @type function
35
- * @date 4/22/16
36
- *
37
- * @param {array} $plugin_data
38
- * @param {object} $r
39
- */
40
- public static function in_plugin_update_message( $plugin_data, $r ) {
41
- $m = '<hr />';
42
- if ( version_compare("1.0.0", $plugin_data['new_version'], "<=") ) {
43
- $m .= '<p><b>Warning:</b> Timber 1.0 removed a number of features and methods. Before upgrading please test your theme on a local or staging site to ensure that your theme will work with the newest version.</p>';
44
-
45
- } else if ( version_compare("1.0.0", $plugin_data['Version'], ">") ) {
46
- $m .= '<p><b>Warning:</b> Timber 1.0 will remove a number of features and methods. Before upgrading please test your theme on a local or staging site to ensure that your theme will work with the newest version.</p>';
47
- }
48
-
49
-
50
- $m .= '<p><strong>Is your theme in active development?</strong> That is, is someone actively in PHP files writing new code? If you answered "no", then <i>do not upgrade to version 1.0</i>. You will not benefit from Timber 1.0</p>';
51
-
52
- $m .= '<p>Read the <strong><a href="https://github.com/timber/timber/wiki/1.0-Upgrade-Guide">Upgrade Guide</a></strong> for more information</p>';
53
-
54
- if ( version_compare("0.22.6", $plugin_data['Version']) > 0 ) {
55
- $m .= "<p>You can <b><a href='https://downloads.wordpress.org/plugin/timber-library.0.22.6.zip'>upgrade to version 0.22.6</a></b> if you want to upgrade, but are unsure if you're ready for 1.0";
56
- }
57
-
58
-
59
-
60
-
61
-
62
- // show message
63
- echo '<br />'.sprintf($m, admin_url('edit.php?post_type=acf-field-group&page=acf-settings-updates'), 'http://www.advancedcustomfields.com/pro');
64
-
65
- }
66
-
67
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-core.php DELETED
@@ -1,117 +0,0 @@
1
- <?php
2
-
3
- abstract class TimberCore {
4
-
5
- public $id;
6
- public $ID;
7
- public $object_type;
8
-
9
- /**
10
- *
11
- *
12
- * @return boolean
13
- */
14
- function __isset( $field ) {
15
- if ( isset( $this->$field ) ) {
16
- return $this->$field;
17
- }
18
- return false;
19
- }
20
-
21
- /**
22
- * This is helpful for twig to return properties and methods see: https://github.com/fabpot/Twig/issues/2
23
- * @return mixed
24
- */
25
- function __call( $field, $args ) {
26
- return $this->__get( $field );
27
- }
28
-
29
- /**
30
- * This is helpful for twig to return properties and methods see: https://github.com/fabpot/Twig/issues/2
31
- *
32
- * @return mixed
33
- */
34
- function __get( $field ) {
35
- if ( property_exists($this, $field) ) {
36
- return $this->$field;
37
- }
38
- if ( method_exists($this, 'meta') && $meta_value = $this->meta( $field ) ) {
39
- return $this->$field = $meta_value;
40
- }
41
- if ( method_exists($this, $field) ) {
42
- return $this->$field = $this->$field();
43
- }
44
- return $this->$field = false;
45
- }
46
-
47
- /**
48
- * Takes an array or object and adds the properties to the parent object
49
- * @example
50
- * ```php
51
- * $data = array('airplane' => '757-200', 'flight' => '5316');
52
- * $post = new TimberPost()
53
- * $post->import(data);
54
- * echo $post->airplane; //757-200
55
- * ```
56
- * @param array|object $info an object or array you want to grab data from to attach to the Timber object
57
- */
58
- function import( $info, $force = false ) {
59
- if ( is_object( $info ) ) {
60
- $info = get_object_vars( $info );
61
- }
62
- if ( is_array( $info ) ) {
63
- foreach ( $info as $key => $value ) {
64
- if ( !empty( $key ) && $force ) {
65
- $this->$key = $value;
66
- } else if ( !empty( $key ) && !method_exists($this, $key) ){
67
- $this->$key = $value;
68
- }
69
- }
70
- }
71
- }
72
-
73
-
74
- /**
75
- * @ignore
76
- * @param string $key
77
- * @param mixed $value
78
- */
79
- function update( $key, $value ) {
80
- update_metadata( $this->object_type, $this->ID, $key, $value );
81
- }
82
-
83
- /**
84
- * Can you edit this post/term/user? Well good for you. You're no better than me.
85
- * @example
86
- * ```twig
87
- * {% if post.can_edit %}
88
- * <a href="{{ post.edit_link }}">Edit</a>
89
- * {% endif %}
90
- * ```
91
- * ```html
92
- * <a href="http://example.org/wp-admin/edit.php?p=242">Edit</a>
93
- * ```
94
- * @return bool
95
- */
96
- function can_edit() {
97
- if ( !function_exists( 'current_user_can' ) ) {
98
- return false;
99
- }
100
- if ( current_user_can( 'edit_post', $this->ID ) ) {
101
- return true;
102
- }
103
- return false;
104
- }
105
-
106
- /**
107
- *
108
- *
109
- * @return array
110
- */
111
- function get_method_values() {
112
- $ret = array();
113
- $ret['can_edit'] = $this->can_edit();
114
- return $ret;
115
- }
116
-
117
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-function-wrapper.php DELETED
@@ -1,92 +0,0 @@
1
- <?php
2
-
3
- class TimberFunctionWrapper {
4
-
5
- private $_class;
6
- private $_function;
7
- private $_args;
8
- private $_use_ob;
9
-
10
- public function __toString() {
11
- try {
12
- return (string)$this->call();
13
- } catch (Exception $e) {
14
- return 'Caught exception: ' . $e->getMessage() . "\n";
15
- }
16
- }
17
-
18
- /**
19
- *
20
- *
21
- * @param callable $function
22
- * @param array $args
23
- * @param bool $return_output_buffer
24
- */
25
- public function __construct( $function, $args = array(), $return_output_buffer = false ) {
26
- if( is_array( $function ) ) {
27
- if( (is_string( $function[0] ) && class_exists( $function[0] ) ) || gettype( $function[0] ) === 'object' ) {
28
- $this->_class = $function[0];
29
- }
30
-
31
- if( is_string( $function[1] ) ) $this->_function = $function[1];
32
- } else {
33
- $this->_function = $function;
34
- }
35
-
36
- $this->_args = $args;
37
- $this->_use_ob = $return_output_buffer;
38
-
39
- add_filter( 'get_twig', array( &$this, 'add_to_twig' ) );
40
- }
41
-
42
- /**
43
- *
44
- *
45
- * @param Twig_Environment $twig
46
- * @return Twig_Environment
47
- */
48
- public function add_to_twig( $twig ) {
49
- $wrapper = $this;
50
-
51
- $twig->addFunction( new Twig_SimpleFunction( $this->_function, function () use ( $wrapper ) {
52
- return call_user_func_array( array( $wrapper, 'call' ), func_get_args() );
53
- } ) );
54
-
55
- return $twig;
56
- }
57
-
58
- /**
59
- *
60
- *
61
- * @return string
62
- */
63
- public function call() {
64
- $args = $this->_parse_args( func_get_args(), $this->_args );
65
- $callable = ( isset( $this->_class ) ) ? array( $this->_class, $this->_function ) : $this->_function;
66
-
67
- if ( $this->_use_ob ) {
68
- return TimberHelper::ob_function( $callable, $args );
69
- } else {
70
- return call_user_func_array( $callable, $args );
71
- }
72
- }
73
-
74
- /**
75
- *
76
- *
77
- * @param array $args
78
- * @param array $defaults
79
- * @return array
80
- */
81
- private function _parse_args( $args, $defaults ) {
82
- $_arg = reset( $defaults );
83
-
84
- foreach ( $args as $index => $arg ) {
85
- $defaults[$index] = is_null( $arg ) ? $_arg : $arg;
86
- $_arg = next( $defaults );
87
- }
88
-
89
- return $defaults;
90
- }
91
-
92
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-integrations.php DELETED
@@ -1,24 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * This is for integrating external plugins into timber
5
- * @package timber
6
- */
7
- class TimberIntegrations {
8
- public static function init() {
9
-
10
- add_action( 'init', array( __CLASS__, 'maybe_init_acftimber' ) );
11
-
12
- if ( class_exists( 'WP_CLI_Command' ) ) {
13
- WP_CLI::add_command( 'timber', 'Timber_WP_CLI_Command' );
14
- }
15
- }
16
-
17
- public static function maybe_init_acftimber() {
18
-
19
- if ( class_exists( 'ACF' ) ) {
20
- new ACFTimber();
21
- }
22
-
23
- }
24
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-page.php DELETED
@@ -1,9 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * @ignore
5
- * @deprecated 0.21.9
6
- */
7
- class TimberPage extends TimberPost {
8
-
9
- }
 
 
 
 
 
 
 
 
 
lib/timber-post-getter.php DELETED
@@ -1,104 +0,0 @@
1
- <?php
2
-
3
- class TimberPostGetter {
4
-
5
- /**
6
- * @param mixed $query
7
- * @param string $PostClass
8
- * @return array|bool|null
9
- */
10
- static function get_post($query = false, $PostClass = 'TimberPost') {
11
- $posts = self::get_posts( $query, $PostClass );
12
- if ( $post = reset($posts ) ) {
13
- return $post;
14
- }
15
- }
16
-
17
- static function get_posts( $query = false, $PostClass = 'TimberPost', $return_collection = false ) {
18
- $posts = self::query_posts( $query, $PostClass );
19
- return apply_filters('timber_post_getter_get_posts', $posts->get_posts( $return_collection ));
20
- }
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
- }
28
-
29
- /**
30
- * @param mixed $query
31
- * @param string $PostClass
32
- * @return array|bool|null
33
- */
34
- static function query_posts($query = false, $PostClass = 'TimberPost' ) {
35
- if (self::is_post_class_or_class_map($query)) {
36
- $PostClass = $query;
37
- $query = false;
38
- }
39
-
40
- if (is_object($query) && !is_a($query, 'WP_Query') ){
41
- // The only object other than a query is a type of post object
42
- $query = array( $query );
43
- }
44
-
45
- if ( is_array( $query ) && count( $query ) && isset( $query[0] ) && is_object( $query[0] ) ) {
46
- // We have an array of post objects that already have data
47
- return new TimberPostsCollection( $query, $PostClass );
48
- } else {
49
- // We have a query (of sorts) to work with
50
- $tqi = new TimberQueryIterator( $query, $PostClass );
51
- return $tqi;
52
- }
53
- }
54
-
55
- static function get_pids($query){
56
- $posts = self::get_posts($query);
57
- $pids = array();
58
- foreach($posts as $post){
59
- if (isset($post->ID)){
60
- $pids[] = $post->ID;
61
- }
62
- }
63
- return $pids;
64
- }
65
-
66
- static function loop_to_id() {
67
- if (!self::wp_query_has_posts()) { return false; }
68
-
69
- global $wp_query;
70
- $post_num = property_exists($wp_query, 'current_post')
71
- ? $wp_query->current_post + 1
72
- : 0
73
- ;
74
-
75
- if (!isset($wp_query->posts[$post_num])) { return false; }
76
-
77
- return $wp_query->posts[$post_num]->ID;
78
- }
79
-
80
- /**
81
- * @return bool
82
- */
83
- static function wp_query_has_posts() {
84
- global $wp_query;
85
- return ($wp_query && property_exists($wp_query, 'posts') && $wp_query->posts);
86
- }
87
-
88
- /**
89
- * @param string|array $arg
90
- * @return bool
91
- */
92
- static function is_post_class_or_class_map($arg){
93
- if (is_string($arg) && class_exists($arg)) {
94
- return true;
95
- }
96
- if (is_array($arg)) {
97
- foreach ($arg as $item) {
98
- if (is_string($item) && (class_exists($item) && is_subclass_of($item, 'TimberPost'))) {
99
- return true;
100
- }
101
- }
102
- }
103
- }
104
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-posts-collection.php DELETED
@@ -1,97 +0,0 @@
1
- <?php
2
-
3
- // Exit if accessed directly
4
- if ( !defined( 'ABSPATH' ) )
5
- exit;
6
-
7
- class TimberPostsCollection extends ArrayObject {
8
-
9
- public function __construct( $posts = array(), $post_class = 'TimberPost' ) {
10
- $returned_posts = array();
11
- if ( is_null( $posts ) ){
12
- $posts = array();
13
- }
14
- foreach ( $posts as $post_object ) {
15
- $post_class_use = $post_class;
16
-
17
- if ( is_array( $post_class ) ) {
18
- $post_type = get_post_type( $post_object );
19
- $post_class_use = 'TimberPost';
20
-
21
- if ( isset( $post_class[$post_type] ) ) {
22
- $post_class_use = $post_class[$post_type];
23
-
24
- } else {
25
- if ( is_array( $post_class ) ) {
26
- TimberHelper::error_log( $post_type . ' of ' . $post_object->ID . ' not found in ' . print_r( $post_class, true ) );
27
- } else {
28
- TimberHelper::error_log( $post_type . ' not found in ' . $post_class );
29
- }
30
- }
31
- }
32
-
33
- // Don't create yet another object if $post_object is already of the right type
34
- if ( is_a( $post_object, $post_class_use ) ) {
35
- $post = $post_object;
36
- } else {
37
- $post = new $post_class_use( $post_object );
38
- }
39
-
40
- if ( isset( $post->ID ) ) {
41
- $returned_posts[] = $post;
42
- }
43
- }
44
-
45
- $returned_posts = self::maybe_set_preview($returned_posts);
46
-
47
- parent::__construct( $returned_posts, $flags = 0, 'TimberPostsIterator' );
48
- }
49
-
50
- public function get_posts() {
51
- return $this->getArrayCopy();
52
- }
53
-
54
- /**
55
- * @param array $posts
56
- * @return array
57
- */
58
- static function maybe_set_preview( $posts ) {
59
- if ( is_array( $posts ) && isset( $_GET['preview'] ) && $_GET['preview']
60
- && isset( $_GET['preview_id'] ) && $_GET['preview_id']
61
- && current_user_can( 'edit_post', $_GET['preview_id'] ) ) {
62
- // No need to check the nonce, that already happened in _show_post_preview on init
63
-
64
- $preview_id = $_GET['preview_id'];
65
- foreach( $posts as &$post ) {
66
- if ( is_object( $post ) && $post->ID == $preview_id ) {
67
- // Based on _set_preview( $post ), but adds import_custom
68
- $preview = wp_get_post_autosave( $preview_id );
69
- if ( is_object($preview) ) {
70
-
71
- $preview = sanitize_post($preview);
72
-
73
- $post->post_content = $preview->post_content;
74
- $post->post_title = $preview->post_title;
75
- $post->post_excerpt = $preview->post_excerpt;
76
- $post->import_custom( $preview_id );
77
-
78
- add_filter( 'get_the_terms', '_wp_preview_terms_filter', 10, 3 );
79
- }
80
- }
81
- }
82
-
83
- }
84
-
85
- return $posts;
86
- }
87
-
88
- }
89
-
90
- class TimberPostsIterator extends ArrayIterator {
91
-
92
- public function current() {
93
- global $post;
94
- $post = parent::current();
95
- return $post;
96
- }
97
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-query-iterator.php DELETED
@@ -1,156 +0,0 @@
1
- <?php
2
- // Exit if accessed directly
3
- if ( !defined( 'ABSPATH' ) )
4
- exit;
5
-
6
- class TimberQueryIterator implements Iterator {
7
-
8
- /**
9
- *
10
- *
11
- * @var WP_Query
12
- */
13
- private $_query = null;
14
- private $_posts_class = 'TimberPost';
15
-
16
- public function __construct( $query = false, $posts_class = 'TimberPost' ) {
17
- add_action( 'pre_get_posts', array($this, 'fix_number_posts_wp_quirk' ));
18
- if ( $posts_class )
19
- $this->_posts_class = $posts_class;
20
-
21
- if ( is_a( $query, 'WP_Query' ) ) {
22
- // We got a full-fledged WP Query, look no further!
23
- $the_query = $query;
24
-
25
- } elseif ( false === $query ) {
26
- // If query is explicitly set to false, use the main loop
27
- global $wp_query;
28
- $the_query =& $wp_query;
29
- //if we're on a custom posts page?
30
- $the_query = self::handle_maybe_custom_posts_page($the_query);
31
- } elseif ( TimberHelper::is_array_assoc( $query ) || ( is_string( $query ) && strstr( $query, '=' ) ) ) {
32
- // We have a regularly formed WP query string or array to use
33
- $the_query = new WP_Query( $query );
34
-
35
- } elseif ( is_numeric( $query ) || is_string( $query ) ) {
36
- // We have what could be a post name or post ID to pull out
37
- $the_query = self::get_query_from_string( $query );
38
-
39
- } elseif ( is_array( $query ) && count( $query ) && ( is_integer( $query[0] ) || is_string( $query[0] ) ) ) {
40
- // We have a list of pids (post IDs) to extract from
41
- $the_query = self::get_query_from_array_of_ids( $query );
42
- } elseif ( is_array($query) && empty($query)) {
43
- // it's an empty array
44
- $the_query = array();
45
- } else {
46
- TimberHelper::error_log( 'I have failed you! in ' . basename( __FILE__ ) . '::' . __LINE__ );
47
- TimberHelper::error_log( $query );
48
-
49
- // We have failed hard, at least let get something.
50
- $the_query = new WP_Query();
51
- }
52
-
53
- $this->_query = $the_query;
54
-
55
- }
56
-
57
- public function get_posts( $return_collection = false ) {
58
- if (isset($this->_query->posts)){
59
- $posts = new TimberPostsCollection( $this->_query->posts, $this->_posts_class );
60
- return ( $return_collection ) ? $posts : $posts->get_posts();
61
- }
62
- }
63
-
64
- //
65
- // GET POSTS
66
- //
67
- public static function get_query_from_array_of_ids( $query = array() ) {
68
- if ( !is_array( $query ) || !count( $query ) )
69
- return null;
70
-
71
- return new WP_Query( array(
72
- 'post_type'=> 'any',
73
- 'ignore_sticky_posts' => true,
74
- 'post__in' => $query,
75
- 'orderby' => 'post__in',
76
- 'nopaging' => true
77
- ) );
78
- }
79
-
80
- public static function get_query_from_string( $string = '' ) {
81
- $post_type = false;
82
-
83
- if ( is_string( $string ) && strstr( $string, '#' ) ) {
84
- //we have a post_type directive here
85
- list( $post_type, $string ) = explode( '#', $string );
86
- }
87
-
88
- $query = array(
89
- 'post_type' => ( $post_type ) ? $post_type : 'any'
90
- );
91
-
92
- if ( is_numeric( $string ) ) {
93
- $query['p'] = $string;
94
-
95
- } else {
96
- $query['name'] = $string;
97
- }
98
-
99
- return new WP_Query( $query );
100
- }
101
-
102
- //
103
- // Iterator Interface
104
- //
105
-
106
- public function valid() {
107
- return $this->_query->have_posts();
108
- }
109
-
110
- public function current() {
111
- global $post;
112
-
113
- $this->_query->the_post();
114
-
115
- // Sets up the global post, but also return the post, for use in Twig template
116
- $posts_class = $this->_posts_class;
117
- return new $posts_class( $post );
118
- }
119
-
120
- /**
121
- * Don't implement next, because current already advances the loop
122
- */
123
- final public function next() {}
124
-
125
- public function rewind() {
126
- $this->_query->rewind_posts();
127
- }
128
-
129
- public function key() {
130
- $this->_query->current_post;
131
- }
132
-
133
- //get_posts users numberposts
134
- static function fix_number_posts_wp_quirk( $query ) {
135
- if (isset($query->query) && isset($query->query['numberposts'])
136
- && !isset($query->query['posts_per_page'])) {
137
- $query->set( 'posts_per_page', $query->query['numberposts'] );
138
- }
139
- return $query;
140
- }
141
-
142
- /**
143
- * this will test for whether a custom page to display posts is active, and if so, set the query to the default
144
- * @param WP_Query $query the original query recived from WordPress
145
- * @return WP_Query
146
- */
147
- static function handle_maybe_custom_posts_page( $query ) {
148
- if ($custom_posts_page = get_option('page_for_posts')) {
149
- if ( isset($query->query['p']) && $query->query['p'] == $custom_posts_page ) {
150
- return new WP_Query(array('post_type' => 'post'));
151
- }
152
- }
153
- return $query;
154
- }
155
-
156
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-routes.php DELETED
@@ -1,33 +0,0 @@
1
- <?php
2
-
3
- class TimberRoutes {
4
-
5
- /**
6
- * @deprecated since 0.21.1 use Upstatement/routes instead
7
- */
8
- public static function init( $timber ) {
9
- // Install ourselves in Timber
10
- $timber->routes = new TimberRoutes();
11
- }
12
-
13
- /**
14
- * @param string $route
15
- * @param callable $callback
16
- * @deprecated since 0.21.1 use Upstatement/routes instead
17
- */
18
- public static function add_route($route, $callback, $args = array()) {
19
- Routes::map($route, $callback, $args);
20
- }
21
-
22
- /**
23
- * @param array $template
24
- * @param mixed $query
25
- * @param int $status_code
26
- * @param bool $tparams
27
- * @return bool
28
- * @deprecated since 0.21.1 use Upstatement/routes instead
29
- */
30
- public static function load_view($template, $query = false, $status_code = 200, $tparams = false) {
31
- Routes::load($template, $tparams, $query, $status_code);
32
- }
33
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/timber-twig.php DELETED
@@ -1,347 +0,0 @@
1
- <?php
2
-
3
- class TimberTwig {
4
-
5
- public static $dir_name;
6
-
7
- /**
8
- * @codeCoverageIgnore
9
- */
10
- public static function init() {
11
- new TimberTwig();
12
- }
13
-
14
- /**
15
- * @codeCoverageIgnore
16
- */
17
- function __construct() {
18
- add_action( 'timber/twig/filters', array( $this, 'add_timber_filters_deprecated' ) );
19
- add_action( 'timber/twig/filters', array( $this, 'add_timber_filters' ) );
20
- }
21
-
22
- /**
23
- * These are all deprecated and will be removed in 0.21.0
24
- *
25
- * @param Twig_Environment $twig
26
- * @deprecated since 0.20.7
27
- * @return Twig_Environment
28
- */
29
- function add_timber_filters_deprecated( $twig ) {
30
- $twig->addFilter( new Twig_SimpleFilter( 'get_src_from_attachment_id', 'twig_get_src_from_attachment_id' ) );
31
- $twig->addFilter( new Twig_SimpleFilter( 'wp_body_class', array( $this, 'body_class' ) ) );
32
- $twig->addFilter( new Twig_SimpleFilter( 'twitterify', array( 'TimberHelper', 'twitterify' ) ) );
33
- $twig->addFilter( new Twig_SimpleFilter( 'twitterfy', array( 'TimberHelper', 'twitterify' ) ) );
34
- $twig->addFilter( new Twig_SimpleFilter( 'string', function($arr, $glue = ' '){
35
- return twig_join_filter($arr, $glue);
36
- } ) );
37
- return $twig;
38
- }
39
-
40
- /**
41
- *
42
- *
43
- * @param Twig_Environment $twig
44
- * @return Twig_Environment
45
- */
46
- function add_timber_filters( $twig ) {
47
- /* image filters */
48
- $twig->addFilter( new Twig_SimpleFilter( 'resize', array( 'TimberImageHelper', 'resize' ) ) );
49
- $twig->addFilter( new Twig_SimpleFilter( 'retina', array( 'TimberImageHelper', 'retina_resize' ) ) );
50
- $twig->addFilter( new Twig_SimpleFilter( 'letterbox', array( 'TimberImageHelper', 'letterbox' ) ) );
51
- $twig->addFilter( new Twig_SimpleFilter( 'tojpg', array( 'TimberImageHelper', 'img_to_jpg' ) ) );
52
-
53
- /* debugging filters */
54
- $twig->addFilter( new Twig_SimpleFilter( 'docs', 'twig_object_docs' ) );
55
- $twig->addFilter( new Twig_SimpleFilter( 'get_class', 'get_class' ) );
56
- $twig->addFilter( new Twig_SimpleFilter( 'get_type', 'get_type' ) );
57
- $twig->addFilter( new Twig_SimpleFilter( 'print_r', function( $arr ) {
58
- return print_r( $arr, true );
59
- } ) );
60
- $twig->addFilter( new Twig_SimpleFilter( 'print_a', function( $arr ) {
61
- return '<pre>' . self::object_docs( $arr, true ) . '</pre>';
62
- } ) );
63
-
64
- /* other filters */
65
- $twig->addFilter( new Twig_SimpleFilter( 'stripshortcodes', 'strip_shortcodes' ) );
66
- $twig->addFilter( new Twig_SimpleFilter( 'array', array( $this, 'to_array' ) ) );
67
- $twig->addFilter( new Twig_SimpleFilter( 'excerpt', 'wp_trim_words' ) );
68
- $twig->addFilter( new Twig_SimpleFilter( 'function', array( $this, 'exec_function' ) ) );
69
- $twig->addFilter( new Twig_SimpleFilter( 'pretags', array( $this, 'twig_pretags' ) ) );
70
- $twig->addFilter( new Twig_SimpleFilter( 'sanitize', 'sanitize_title' ) );
71
- $twig->addFilter( new Twig_SimpleFilter( 'shortcodes', 'do_shortcode' ) );
72
- $twig->addFilter( new Twig_SimpleFilter( 'time_ago', array( $this, 'time_ago' ) ) );
73
- $twig->addFilter( new Twig_SimpleFilter( 'wpautop', 'wpautop' ) );
74
- $twig->addFilter( new Twig_SimpleFilter( 'list', array( $this, 'add_list_separators' ) ) );
75
-
76
- $twig->addFilter( new Twig_SimpleFilter( 'relative', function ( $link ) {
77
- return TimberURLHelper::get_rel_url( $link, true );
78
- } ) );
79
-
80
- $twig->addFilter( new Twig_SimpleFilter( 'date', array( $this, 'intl_date' ) ) );
81
-
82
- $twig->addFilter( new Twig_SimpleFilter( 'truncate', function ( $text, $len ) {
83
- return TimberHelper::trim_words( $text, $len );
84
- } ) );
85
-
86
- /* actions and filters */
87
- $twig->addFunction( new Twig_SimpleFunction( 'action', function ( $context ) {
88
- $args = func_get_args();
89
- array_shift( $args );
90
- $args[] = $context;
91
- call_user_func_array( 'do_action', $args );
92
- }, array( 'needs_context' => true ) ) );
93
-
94
- $twig->addFilter( new Twig_SimpleFilter( 'apply_filters', function () {
95
- $args = func_get_args();
96
- $tag = current( array_splice( $args, 1, 1 ) );
97
-
98
- return apply_filters_ref_array( $tag, $args );
99
- } ) );
100
- $twig->addFunction( new Twig_SimpleFunction( 'function', array( &$this, 'exec_function' ) ) );
101
- $twig->addFunction( new Twig_SimpleFunction( 'fn', array( &$this, 'exec_function' ) ) );
102
-
103
- $twig->addFunction( new Twig_SimpleFunction( 'shortcode', 'do_shortcode' ) );
104
-
105
- /* TimberObjects */
106
- $twig->addFunction( new Twig_SimpleFunction( 'TimberPost', function ( $pid, $PostClass = 'TimberPost' ) {
107
- if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
108
- foreach ( $pid as &$p ) {
109
- $p = new $PostClass( $p );
110
- }
111
- return $pid;
112
- }
113
- return new $PostClass( $pid );
114
- } ) );
115
- $twig->addFunction( new Twig_SimpleFunction( 'TimberImage', function ( $pid, $ImageClass = 'TimberImage' ) {
116
- if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
117
- foreach ( $pid as &$p ) {
118
- $p = new $ImageClass( $p );
119
- }
120
- return $pid;
121
- }
122
- return new $ImageClass( $pid );
123
- } ) );
124
- $twig->addFunction( new Twig_SimpleFunction( 'TimberTerm', function ( $pid, $TermClass = 'TimberTerm' ) {
125
- if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
126
- foreach ( $pid as &$p ) {
127
- $p = new $TermClass( $p );
128
- }
129
- return $pid;
130
- }
131
- return new $TermClass( $pid );
132
- } ) );
133
- $twig->addFunction( new Twig_SimpleFunction( 'TimberUser', function ( $pid, $UserClass = 'TimberUser' ) {
134
- if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
135
- foreach ( $pid as &$p ) {
136
- $p = new $UserClass( $p );
137
- }
138
- return $pid;
139
- }
140
- return new $UserClass( $pid );
141
- } ) );
142
-
143
- /* TimberObjects Alias */
144
- $twig->addFunction( new Twig_SimpleFunction( 'Post', function ( $pid, $PostClass = 'TimberPost' ) {
145
- if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
146
- foreach ( $pid as &$p ) {
147
- $p = new $PostClass( $p );
148
- }
149
- return $pid;
150
- }
151
- return new $PostClass( $pid );
152
- } ) );
153
- $twig->addFunction( new Twig_SimpleFunction( 'Image', function ( $pid, $ImageClass = 'TimberImage' ) {
154
- if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
155
- foreach ( $pid as &$p ) {
156
- $p = new $ImageClass( $p );
157
- }
158
- return $pid;
159
- }
160
- return new $ImageClass( $pid );
161
- } ) );
162
- $twig->addFunction( new Twig_SimpleFunction( 'Term', function ( $pid, $TermClass = 'TimberTerm' ) {
163
- if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
164
- foreach ( $pid as &$p ) {
165
- $p = new $TermClass( $p );
166
- }
167
- return $pid;
168
- }
169
- return new $TermClass( $pid );
170
- } ) );
171
- $twig->addFunction( new Twig_SimpleFunction( 'User', function ( $pid, $UserClass = 'TimberUser' ) {
172
- if ( is_array( $pid ) && !TimberHelper::is_array_assoc( $pid ) ) {
173
- foreach ( $pid as &$p ) {
174
- $p = new $UserClass( $p );
175
- }
176
- return $pid;
177
- }
178
- return new $UserClass( $pid );
179
- } ) );
180
-
181
- /* bloginfo and translate */
182
- $twig->addFunction( 'bloginfo', new Twig_SimpleFunction( 'bloginfo', function ( $show = '', $filter = 'raw' ) {
183
- return get_bloginfo( $show, $filter );
184
- } ) );
185
- $twig->addFunction( '__', new Twig_SimpleFunction( '__', function ( $text, $domain = 'default' ) {
186
- return __( $text, $domain );
187
- } ) );
188
- /* get_twig is deprecated, use timber/twig */
189
- $twig = apply_filters( 'get_twig', $twig );
190
- $twig = apply_filters( 'timber/twig', $twig );
191
- return $twig;
192
- }
193
-
194
- /**
195
- *
196
- *
197
- * @param mixed $arr
198
- * @return array
199
- */
200
- function to_array( $arr ) {
201
- if ( is_array( $arr ) ) {
202
- return $arr;
203
- }
204
- $arr = array( $arr );
205
- return $arr;
206
- }
207
-
208
- /**
209
- *
210
- *
211
- * @param string $function_name
212
- * @return mixed
213
- */
214
- function exec_function( $function_name ) {
215
- $args = func_get_args();
216
- array_shift( $args );
217
- if ( is_string($function_name) ) {
218
- $function_name = trim( $function_name );
219
- }
220
- return call_user_func_array( $function_name, ( $args ) );
221
- }
222
-
223
- /**
224
- *
225
- *
226
- * @param string $content
227
- * @return string
228
- */
229
- function twig_pretags( $content ) {
230
- return preg_replace_callback( '|<pre.*>(.*)</pre|isU', array( &$this, 'convert_pre_entities' ), $content );
231
- }
232
-
233
- /**
234
- *
235
- *
236
- * @param array $matches
237
- * @return string
238
- */
239
- function convert_pre_entities( $matches ) {
240
- return str_replace( $matches[1], htmlentities( $matches[1] ), $matches[0] );
241
- }
242
-
243
- /**
244
- * @param mixed $body_classes
245
- * @deprecated 0.20.7
246
- * @return string
247
- */
248
- function body_class( $body_classes ) {
249
- ob_start();
250
- if ( is_array( $body_classes ) ) {
251
- $body_classes = explode( ' ', $body_classes );
252
- }
253
- body_class( $body_classes );
254
- $return = ob_get_contents();
255
- ob_end_clean();
256
- return $return;
257
- }
258
-
259
- /**
260
- *
261
- *
262
- * @param string $date
263
- * @param string $format (optional)
264
- * @return string
265
- */
266
- function intl_date( $date, $format = null ) {
267
- if ( $format === null ) {
268
- $format = get_option( 'date_format' );
269
- }
270
-
271
- if ( $date instanceof DateTime ) {
272
- $timestamp = $date->getTimestamp() + $date->getOffset();
273
- } else if (is_numeric( $date ) && strtotime( $date ) === false ) {
274
- $timestamp = intval( $date );
275
- } else {
276
- $timestamp = strtotime( $date );
277
- }
278
-
279
- return date_i18n( $format, $timestamp );
280
- }
281
-
282
- //debug
283
-
284
- /**
285
- *
286
- *
287
- * @param mixed $obj
288
- * @param bool $methods
289
- * @deprecated since 0.20.7
290
- * @return string
291
- */
292
- function object_docs( $obj, $methods = true ) {
293
- $class = get_class( $obj );
294
- $properties = (array)$obj;
295
- if ( $methods ) {
296
- /** @var array $methods */
297
- $methods = $obj->get_method_values();
298
- }
299
- $rets = array_merge( $properties, $methods );
300
- ksort( $rets );
301
- $str = print_r( $rets, true );
302
- $str = str_replace( 'Array', $class . ' Object', $str );
303
- return $str;
304
- }
305
-
306
- /**
307
- * @param int|string $from
308
- * @param int|string $to
309
- * @param string $format_past
310
- * @param string $format_future
311
- * @return string
312
- */
313
- function time_ago( $from, $to = null, $format_past = '%s ago', $format_future = '%s from now' ) {
314
- $to = $to === null ? time() : $to;
315
- $to = is_int( $to ) ? $to : strtotime( $to );
316
- $from = is_int( $from ) ? $from : strtotime( $from );
317
-
318
- if ( $from < $to ) {
319
- return sprintf( $format_past, human_time_diff( $from, $to ) );
320
- } else {
321
- return sprintf( $format_future, human_time_diff( $to, $from ) );
322
- }
323
- }
324
-
325
- /**
326
- * @param array $arr
327
- * @param string $first_delimiter
328
- * @param string $second_delimiter
329
- * @return string
330
- */
331
- function add_list_separators( $arr, $first_delimiter = ',', $second_delimiter = 'and' ) {
332
- $length = count( $arr );
333
- $list = '';
334
- foreach( $arr as $index => $item ) {
335
- if ( $index < $length - 2 ) {
336
- $delimiter = $first_delimiter.' ';
337
- } elseif ( $index == $length - 2 ) {
338
- $delimiter = ' '.$second_delimiter.' ';
339
- } else {
340
- $delimiter = '';
341
- }
342
- $list = $list.$item.$delimiter;
343
- }
344
- return $list;
345
- }
346
-
347
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
readme.txt CHANGED
@@ -1,8 +1,8 @@
1
  === Timber ===
2
- Contributors: jarednova, lggorman
3
  Tags: template engine, templates, twig
4
  Requires at least: 3.7
5
- Stable tag: 0.22.6
6
  Tested up to: 4.5.1
7
  PHP version: 5.3.0 or greater
8
  License: GPLv2 or later
@@ -17,11 +17,11 @@ Once Timber is installed and activated in your plugin directory, it gives any Wo
17
 
18
  ### Looking for docs?
19
  * **[Project Page](http://upstatement.com/timber)**
20
- * [Timber Documentation](https://github.com/jarednova/timber/wiki/)
21
  * [Twig Reference (from SensioLabs)](http://twig.sensiolabs.org/doc/templates.html)
22
  _Twig is the template language powering Timber; if you need a little background on what a template language is, [Twig's homepage has an overview](http://twig.sensiolabs.org/)_
23
- * **[Video Tutorials](https://github.com/jarednova/timber/wiki/Video-Tutorials)**
24
- * [Overview / Getting Started Guide](https://github.com/jarednova/timber/wiki/getting-started)
25
 
26
  #### Related Projects
27
  * [**Timber Debug Bar**](http://wordpress.org/plugins/debug-bar-timber/) Adds a debug bar panel that will show you want template is in-use and the data sent to your twig file.
@@ -33,7 +33,7 @@ Nothing. Timber is meant for you to build a theme on. Like the [Starkers](https:
33
  Timber is great for any WordPress developer who cares about writing good, maintainable code. It helps teams of designers and developers working together. At [Upstatement](http://upstatement.com) we made Timber because not everyone knows the ins-and-outs of the_loop(), WordPress codex and PHP (nor should they). With Timber your best WordPress dev can focus on building the .php files with requests from WordPress and pass the data into .twig files. Once there, designers can easily mark-up data and build out a site's look-and-feel.
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
 
@@ -41,12 +41,43 @@ Timber is great for any WordPress developer who cares about writing good, mainta
41
 
42
  == Changelog ==
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  = 0.22.5 =
45
  * Fixed errors in tests (thanks @lggorman)
46
  * Fixed error in comments_link (thanks @tehlivi)
47
 
48
  = 0.22.4 =
49
- * Fixed [bug](https://github.com/jarednova/timber/issues/785) in get_calling_script file (thanks @gwagroves)
50
  * Added tons of new tests and docs (thanks @lggorman and @jarednova)
51
 
52
  = 0.22.3 =
@@ -61,7 +92,7 @@ Timber is great for any WordPress developer who cares about writing good, mainta
61
  * Added support for animated gifs
62
 
63
  = 0.22.1 =
64
- * Added better support for [post.get_terms](https://github.com/jarednova/timber/pull/737) (thanks @aaemnnosttv)
65
  * Fix for issue with ACF date field (thanks @rpkoller)
66
  * Fix for resizing jpEgs (thanks @eaton)
67
 
@@ -228,7 +259,7 @@ Timber is great for any WordPress developer who cares about writing good, mainta
228
  * Hotfix on timber-admin error
229
 
230
  = 0.17.0 =
231
- * Now you can resize/crop images with positional preferences, thanks @mmikkel. Here are the docs: https://github.com/jarednova/timber/wiki/TimberImage#resize
232
  * Removed the Timber Starter Guide from the admin, a link to the GitHub wiki suffices.
233
 
234
  = 0.16.8 =
@@ -323,7 +354,7 @@ Timber is great for any WordPress developer who cares about writing good, mainta
323
  * New Timber::compile method which _doesn't_ automatically echo. (Same args as Timber::render)
324
  * Added post.get_next / post.get_prev for TimberPosts
325
  * Fixed a thing to make get_preview easier when you want to omit the 'Read More' link
326
- * Read the [Full Release Notes](https://github.com/jarednova/timber/releases/tag/0.15.0)
327
 
328
  = 0.14.1 =
329
  * Added hooks to play nicely with Timber Debug Bar
@@ -446,7 +477,7 @@ Please post on [StackOverflow under the "Timber" tag](http://stackoverflow.com/q
446
  == Frequently Asked Questions ==
447
 
448
  = Can it be used in an existing theme? =
449
- You bet! Watch these **[video tutorials](https://github.com/jarednova/timber/wiki/Video-Tutorials)** to see how.
450
 
451
  = Is it used in production? =
452
  At Upstatement we've now used it on more than a dozen client sites. Hundreds of other sites use it too. You can check some of them out in the **[showcase](http://upstatement.com/timber/#showcase)**.
@@ -461,4 +492,4 @@ Whatever. It simplifies the silly stuff so that you can focus on building more c
461
  As stated above, we're using it in dozens of sites (and dozens more planned) -- dozens of other developers are using it too. This isn't going anywhere. Twig is the chosen language for other PHP platforms like Symfony, Drupal 8 and Craft. WordPress will eventually adopt Twig too, I promise you that.
462
 
463
  = Support? =
464
- Leave a [GitHub issue](https://github.com/jarednova/timber/issues?state=open) and I'll holler back.
1
  === Timber ===
2
+ Contributors: jarednova, connorjburton, lggorman
3
  Tags: template engine, templates, twig
4
  Requires at least: 3.7
5
+ Stable tag: 1.0.3
6
  Tested up to: 4.5.1
7
  PHP version: 5.3.0 or greater
8
  License: GPLv2 or later
17
 
18
  ### Looking for docs?
19
  * **[Project Page](http://upstatement.com/timber)**
20
+ * [Timber Documentation](https://github.com/timber/timber/wiki/)
21
  * [Twig Reference (from SensioLabs)](http://twig.sensiolabs.org/doc/templates.html)
22
  _Twig is the template language powering Timber; if you need a little background on what a template language is, [Twig's homepage has an overview](http://twig.sensiolabs.org/)_
23
+ * **[Video Tutorials](https://github.com/timber/timber/wiki/Video-Tutorials)**
24
+ * [Overview / Getting Started Guide](https://github.com/timber/timber/wiki/getting-started)
25
 
26
  #### Related Projects
27
  * [**Timber Debug Bar**](http://wordpress.org/plugins/debug-bar-timber/) Adds a debug bar panel that will show you want template is in-use and the data sent to your twig file.
33
  Timber is great for any WordPress developer who cares about writing good, maintainable code. It helps teams of designers and developers working together. At [Upstatement](http://upstatement.com) we made Timber because not everyone knows the ins-and-outs of the_loop(), WordPress codex and PHP (nor should they). With Timber your best WordPress dev can focus on building the .php files with requests from WordPress and pass the data into .twig files. Once there, designers can easily mark-up data and build out a site's look-and-feel.
34
 
35
  #### Want to read more?
36
+ * [Timber on GitHub](http://github.com/timber/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
 
41
 
42
  == Changelog ==
43
 
44
+ = 1.0.2 =
45
+ * Hot fix for PHP 5.3 support
46
+
47
+ = 1.0.1 =
48
+ * {{ user.avatar }} property is now available (thanks @connorjburton) d21eb85
49
+ * #947: Fix to pagination base (thanks @matsrietdijk) 270d7c2
50
+ * Fix to some namespacing issues (thanks @connorjburton) 0a8346a
51
+ * #958: Call the_post action to help other plugins (thanks @felthy) 4442703
52
+ * #976: Fixed problem with static declaration (@jarednova) c888606
53
+ * #978: Bug with arrays for post types (thanks @connorjburton) 571f6f8
54
+
55
+ = 1.0.0 =
56
+ * Added `{{ user }}` object to context
57
+ * Exposed translation functions
58
+ * Added better error reporting/warnings
59
+ * Fixed some things with function wrapper
60
+ * Timber is now namespaced, big big thanks to @connorjburton
61
+ * Cleanup of spacing
62
+ * Removed deprecated functions, added warning for key functions
63
+ * Updated version numbers and build script (@jarednova) 81a281e
64
+ * Corrected Routes -> /Routes which threw a fatal error (@jarednova) 26b6585
65
+
66
+ = 0.22.6 =
67
+ * New {{request}} object for post/get variables (thanks @connorjburton) #856
68
+ * New crop positions (thanks @salaros) #861
69
+ * Bug Fixes
70
+
71
+ * Fix to "next" in pagination (thanks @connorjburton) #900
72
+ * Fix to issue with tojpg filter's images not being deleted (thanks @connorjburton) #897
73
+ * `{{post.parent.children}}` used to return unpublished posts due to underlying behavior in WordPress, it now only returns published posts (thanks @connorjburton) #883
74
+
75
  = 0.22.5 =
76
  * Fixed errors in tests (thanks @lggorman)
77
  * Fixed error in comments_link (thanks @tehlivi)
78
 
79
  = 0.22.4 =
80
+ * Fixed [bug](https://github.com/timber/timber/issues/785) in get_calling_script file (thanks @gwagroves)
81
  * Added tons of new tests and docs (thanks @lggorman and @jarednova)
82
 
83
  = 0.22.3 =
92
  * Added support for animated gifs
93
 
94
  = 0.22.1 =
95
+ * Added better support for [post.get_terms](https://github.com/timber/timber/pull/737) (thanks @aaemnnosttv)
96
  * Fix for issue with ACF date field (thanks @rpkoller)
97
  * Fix for resizing jpEgs (thanks @eaton)
98
 
259
  * Hotfix on timber-admin error
260
 
261
  = 0.17.0 =
262
+ * Now you can resize/crop images with positional preferences, thanks @mmikkel. Here are the docs: https://github.com/timber/timber/wiki/TimberImage#resize
263
  * Removed the Timber Starter Guide from the admin, a link to the GitHub wiki suffices.
264
 
265
  = 0.16.8 =
354
  * New Timber::compile method which _doesn't_ automatically echo. (Same args as Timber::render)
355
  * Added post.get_next / post.get_prev for TimberPosts
356
  * Fixed a thing to make get_preview easier when you want to omit the 'Read More' link
357
+ * Read the [Full Release Notes](https://github.com/timber/timber/releases/tag/0.15.0)
358
 
359
  = 0.14.1 =
360
  * Added hooks to play nicely with Timber Debug Bar
477
  == Frequently Asked Questions ==
478
 
479
  = Can it be used in an existing theme? =
480
+ You bet! Watch these **[video tutorials](https://github.com/timber/timber/wiki/Video-Tutorials)** to see how.
481
 
482
  = Is it used in production? =
483
  At Upstatement we've now used it on more than a dozen client sites. Hundreds of other sites use it too. You can check some of them out in the **[showcase](http://upstatement.com/timber/#showcase)**.
492
  As stated above, we're using it in dozens of sites (and dozens more planned) -- dozens of other developers are using it too. This isn't going anywhere. Twig is the chosen language for other PHP platforms like Symfony, Drupal 8 and Craft. WordPress will eventually adopt Twig too, I promise you that.
493
 
494
  = Support? =
495
+ Leave a [GitHub issue](https://github.com/timber/timber/issues?state=open) and I'll holler back.
timber-starter-theme/single.php CHANGED
@@ -12,7 +12,6 @@
12
  $context = Timber::get_context();
13
  $post = Timber::query_post();
14
  $context['post'] = $post;
15
- $context['comment_form'] = TimberHelper::get_comment_form();
16
 
17
  if ( post_password_required( $post->ID ) ) {
18
  Timber::render( 'single-password.twig', $context );
12
  $context = Timber::get_context();
13
  $post = Timber::query_post();
14
  $context['post'] = $post;
 
15
 
16
  if ( post_password_required( $post->ID ) ) {
17
  Timber::render( 'single-password.twig', $context );
timber-starter-theme/templates/base.twig CHANGED
@@ -11,7 +11,7 @@
11
  {% block header %}
12
  <div class="wrapper">
13
  <h1 class="hdr-logo" role="banner">
14
- <a class="hdr-logo-link" href="/" rel="home">{{site.name}}</a>
15
  </h1>
16
  <nav id="nav-main" class="nav-main" role="navigation">
17
  {% include "menu.twig" with {'menu': menu.get_items} %}
11
  {% block header %}
12
  <div class="wrapper">
13
  <h1 class="hdr-logo" role="banner">
14
+ <a class="hdr-logo-link" href="{{site.siteurl}}" rel="home">{{site.name}}</a>
15
  </h1>
16
  <nav id="nav-main" class="nav-main" role="navigation">
17
  {% include "menu.twig" with {'menu': menu.get_items} %}
timber-starter-theme/templates/single.twig CHANGED
@@ -12,10 +12,11 @@
12
  {{post.content}}
13
  </div>
14
  </section>
 
15
  <section class="comments">
16
  <div class="respond">
17
  <h3 class="h2">Comments</h3>
18
- {{ comment_form }}
19
  </div>
20
  <div class="responses">
21
  {% for cmt in post.get_comments() %}
@@ -23,6 +24,7 @@
23
  {% endfor %}
24
  </div>
25
  </section>
 
26
  </article>
27
  </div><!-- /content-wrapper -->
28
  {% endblock %}
12
  {{post.content}}
13
  </div>
14
  </section>
15
+ {% if post.comment_status != 'closed' %}
16
  <section class="comments">
17
  <div class="respond">
18
  <h3 class="h2">Comments</h3>
19
+ {{ fn('comment_form') }}
20
  </div>
21
  <div class="responses">
22
  {% for cmt in post.get_comments() %}
24
  {% endfor %}
25
  </div>
26
  </section>
27
+ {% endif %}
28
  </article>
29
  </div><!-- /content-wrapper -->
30
  {% endblock %}
timber.php CHANGED
@@ -4,24 +4,17 @@ Plugin Name: Timber
4
  Description: The WordPress Timber Library allows you to write themes using the power Twig templates.
5
  Plugin URI: http://timber.upstatement.com
6
  Author: Jared Novack + Upstatement
7
- Version: 0.22.6
8
  Author URI: http://upstatement.com/
9
  */
10
-
11
- global $wp_version;
12
- global $timber;
13
-
14
  // we look for Composer files first in the plugins dir.
15
  // then in the wp-content dir (site install).
16
  // and finally in the current themes directories.
17
- if ( file_exists( $composer_autoload = __DIR__ . '/vendor/autoload.php' ) /* check in self */
18
- || file_exists( $composer_autoload = WP_CONTENT_DIR.'/vendor/autoload.php') /* check in wp-content */
19
- || file_exists( $composer_autoload = plugin_dir_path( __FILE__ ).'vendor/autoload.php') /* check in plugin directory */
20
- || file_exists( $composer_autoload = get_stylesheet_directory().'/vendor/autoload.php') /* check in child theme */
21
- || file_exists( $composer_autoload = get_template_directory().'/vendor/autoload.php') /* check in parent theme */
22
  ) {
23
- require_once $composer_autoload;
24
  }
25
-
26
- $timber = new Timber();
27
- Timber::$dirname = 'views';
4
  Description: The WordPress Timber Library allows you to write themes using the power Twig templates.
5
  Plugin URI: http://timber.upstatement.com
6
  Author: Jared Novack + Upstatement
7
+ Version: 1.0.3
8
  Author URI: http://upstatement.com/
9
  */
 
 
 
 
10
  // we look for Composer files first in the plugins dir.
11
  // then in the wp-content dir (site install).
12
  // and finally in the current themes directories.
13
+ if ( file_exists( $composer_autoload = __DIR__ . '/vendor/autoload.php' ) /* check in self */
14
+ || file_exists( $composer_autoload = WP_CONTENT_DIR.'/vendor/autoload.php') /* check in wp-content */
15
+ || file_exists( $composer_autoload = plugin_dir_path( __FILE__ ).'vendor/autoload.php') /* check in plugin directory */
16
+ || file_exists( $composer_autoload = get_stylesheet_directory().'/vendor/autoload.php') /* check in child theme */
17
+ || file_exists( $composer_autoload = get_template_directory().'/vendor/autoload.php') /* check in parent theme */
18
  ) {
19
+ require_once $composer_autoload;
20
  }
 
 
 
vendor/autoload.php CHANGED
@@ -4,4 +4,4 @@
4
 
5
  require_once __DIR__ . '/composer' . '/autoload_real.php';
6
 
7
- return ComposerAutoloaderInitea51aada03cf312d1b81bbbcbceabfc8::getLoader();
4
 
5
  require_once __DIR__ . '/composer' . '/autoload_real.php';
6
 
7
+ return ComposerAutoloaderInit7abff383b4787c8ca40e89b5be93c957::getLoader();
vendor/composer/autoload_classmap.php CHANGED
@@ -6,45 +6,5 @@ $vendorDir = dirname(dirname(__FILE__));
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
9
- 'ACFTimber' => $baseDir . '/lib/integrations/acf-timber.php',
10
  'AltoRouter' => $vendorDir . '/altorouter/altorouter/AltoRouter.php',
11
- 'Timber' => $baseDir . '/lib/timber.php',
12
- 'TimberAdmin' => $baseDir . '/lib/timber-admin.php',
13
- 'TimberArchives' => $baseDir . '/lib/timber-archives.php',
14
- 'TimberCommand' => $baseDir . '/lib/integrations/timber-command.php',
15
- 'TimberComment' => $baseDir . '/lib/timber-comment.php',
16
- 'TimberCore' => $baseDir . '/lib/timber-core.php',
17
- 'TimberCoreInterface' => $baseDir . '/lib/timber-core-interface.php',
18
- 'TimberFunctionWrapper' => $baseDir . '/lib/timber-function-wrapper.php',
19
- 'TimberHelper' => $baseDir . '/lib/timber-helper.php',
20
- 'TimberImage' => $baseDir . '/lib/timber-image.php',
21
- 'TimberImageHelper' => $baseDir . '/lib/timber-image-helper.php',
22
- 'TimberImageOperation' => $baseDir . '/lib/image/timber-image-operation.php',
23
- 'TimberImageOperationLetterbox' => $baseDir . '/lib/image/timber-image-operation-letterbox.php',
24
- 'TimberImageOperationResize' => $baseDir . '/lib/image/timber-image-operation-resize.php',
25
- 'TimberImageOperationRetina' => $baseDir . '/lib/image/timber-image-operation-retina.php',
26
- 'TimberImageOperationToJpg' => $baseDir . '/lib/image/timber-image-operation-tojpg.php',
27
- 'TimberIntegrations' => $baseDir . '/lib/timber-integrations.php',
28
- 'TimberLoader' => $baseDir . '/lib/timber-loader.php',
29
- 'TimberMenu' => $baseDir . '/lib/timber-menu.php',
30
- 'TimberMenuItem' => $baseDir . '/lib/timber-menu-item.php',
31
- 'TimberPage' => $baseDir . '/lib/timber-page.php',
32
- 'TimberPost' => $baseDir . '/lib/timber-post.php',
33
- 'TimberPostGetter' => $baseDir . '/lib/timber-post-getter.php',
34
- 'TimberPostsCollection' => $baseDir . '/lib/timber-posts-collection.php',
35
- 'TimberPostsIterator' => $baseDir . '/lib/timber-posts-collection.php',
36
- 'TimberQueryIterator' => $baseDir . '/lib/timber-query-iterator.php',
37
- 'TimberRequest' => $baseDir . '/lib/timber-request.php',
38
- 'TimberRoutes' => $baseDir . '/lib/timber-routes.php',
39
- 'TimberSite' => $baseDir . '/lib/timber-site.php',
40
- 'TimberTerm' => $baseDir . '/lib/timber-term.php',
41
- 'TimberTermGetter' => $baseDir . '/lib/timber-term-getter.php',
42
- 'TimberTheme' => $baseDir . '/lib/timber-theme.php',
43
- 'TimberTwig' => $baseDir . '/lib/timber-twig.php',
44
- 'TimberURLHelper' => $baseDir . '/lib/timber-url-helper.php',
45
- 'TimberUser' => $baseDir . '/lib/timber-user.php',
46
- 'Timber\\Cache\\KeyGenerator' => $baseDir . '/lib/cache/KeyGenerator.php',
47
- 'Timber\\Cache\\TimberKeyGeneratorInterface' => $baseDir . '/lib/cache/TimberKeyGeneratorInterface.php',
48
- 'Timber\\Cache\\WPObjectCacheAdapter' => $baseDir . '/lib/cache/WPObjectCacheAdapter.php',
49
- 'Timber_WP_CLI_Command' => $baseDir . '/lib/integrations/wpcli-timber.php',
50
  );
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
 
9
  'AltoRouter' => $vendorDir . '/altorouter/altorouter/AltoRouter.php',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  );
vendor/composer/autoload_files.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_files.php @generated by Composer, it's nice
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'cb78221880aa21d756cc44a8539bb425' => $baseDir . '/init.php',
10
+ );
vendor/composer/autoload_psr4.php CHANGED
@@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
 
9
  'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
10
  '' => array($vendorDir . '/asm89/twig-cache-extension/lib'),
11
  );
6
  $baseDir = dirname($vendorDir);
7
 
8
  return array(
9
+ 'Timber\\' => array($baseDir . '/lib'),
10
  'Composer\\Installers\\' => array($vendorDir . '/composer/installers/src/Composer/Installers'),
11
  '' => array($vendorDir . '/asm89/twig-cache-extension/lib'),
12
  );
vendor/composer/autoload_real.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
- class ComposerAutoloaderInitea51aada03cf312d1b81bbbcbceabfc8
6
  {
7
  private static $loader;
8
 
@@ -19,9 +19,9 @@ class ComposerAutoloaderInitea51aada03cf312d1b81bbbcbceabfc8
19
  return self::$loader;
20
  }
21
 
22
- spl_autoload_register(array('ComposerAutoloaderInitea51aada03cf312d1b81bbbcbceabfc8', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
- spl_autoload_unregister(array('ComposerAutoloaderInitea51aada03cf312d1b81bbbcbceabfc8', 'loadClassLoader'));
25
 
26
  $map = require __DIR__ . '/autoload_namespaces.php';
27
  foreach ($map as $namespace => $path) {
@@ -40,6 +40,20 @@ class ComposerAutoloaderInitea51aada03cf312d1b81bbbcbceabfc8
40
 
41
  $loader->register(true);
42
 
 
 
 
 
 
43
  return $loader;
44
  }
45
  }
 
 
 
 
 
 
 
 
 
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
+ class ComposerAutoloaderInit7abff383b4787c8ca40e89b5be93c957
6
  {
7
  private static $loader;
8
 
19
  return self::$loader;
20
  }
21
 
22
+ spl_autoload_register(array('ComposerAutoloaderInit7abff383b4787c8ca40e89b5be93c957', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit7abff383b4787c8ca40e89b5be93c957', 'loadClassLoader'));
25
 
26
  $map = require __DIR__ . '/autoload_namespaces.php';
27
  foreach ($map as $namespace => $path) {
40
 
41
  $loader->register(true);
42
 
43
+ $includeFiles = require __DIR__ . '/autoload_files.php';
44
+ foreach ($includeFiles as $fileIdentifier => $file) {
45
+ composerRequire7abff383b4787c8ca40e89b5be93c957($fileIdentifier, $file);
46
+ }
47
+
48
  return $loader;
49
  }
50
  }
51
+
52
+ function composerRequire7abff383b4787c8ca40e89b5be93c957($fileIdentifier, $file)
53
+ {
54
+ if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
55
+ require $file;
56
+
57
+ $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
58
+ }
59
+ }
vendor/composer/installed.json CHANGED
@@ -1,17 +1,17 @@
1
  [
2
  {
3
  "name": "composer/installers",
4
- "version": "v1.0.24",
5
- "version_normalized": "1.0.24.0",
6
  "source": {
7
  "type": "git",
8
  "url": "https://github.com/composer/installers.git",
9
- "reference": "1cf7cc4b89d5e8549bbb7d6ab1de75da13d27988"
10
  },
11
  "dist": {
12
  "type": "zip",
13
- "url": "https://api.github.com/repos/composer/installers/zipball/1cf7cc4b89d5e8549bbb7d6ab1de75da13d27988",
14
- "reference": "1cf7cc4b89d5e8549bbb7d6ab1de75da13d27988",
15
  "shasum": ""
16
  },
17
  "require": {
@@ -25,7 +25,7 @@
25
  "composer/composer": "1.0.*@dev",
26
  "phpunit/phpunit": "4.1.*"
27
  },
28
- "time": "2016-04-05 11:42:46",
29
  "type": "composer-plugin",
30
  "extra": {
31
  "class": "Composer\\Installers\\Plugin",
1
  [
2
  {
3
  "name": "composer/installers",
4
+ "version": "v1.0.25",
5
+ "version_normalized": "1.0.25.0",
6
  "source": {
7
  "type": "git",
8
  "url": "https://github.com/composer/installers.git",
9
+ "reference": "36e5b5843203d7f1cf6ffb0305a97e014387bd8e"
10
  },
11
  "dist": {
12
  "type": "zip",
13
+ "url": "https://api.github.com/repos/composer/installers/zipball/36e5b5843203d7f1cf6ffb0305a97e014387bd8e",
14
+ "reference": "36e5b5843203d7f1cf6ffb0305a97e014387bd8e",
15
  "shasum": ""
16
  },
17
  "require": {
25
  "composer/composer": "1.0.*@dev",
26
  "phpunit/phpunit": "4.1.*"
27
  },
28
+ "time": "2016-04-13 19:46:30",
29
  "type": "composer-plugin",
30
  "extra": {
31
  "class": "Composer\\Installers\\Plugin",
vendor/composer/installers/README.md CHANGED
@@ -83,6 +83,7 @@ is not needed to install packages with these frameworks:
83
  | symfony1 | **`symfony1-plugin`**
84
  | Tusk | `tusk-task`<br>`tusk-command`<br>`tusk-asset`
85
  | TYPO3 Flow | `typo3-flow-package`<br>`typo3-flow-framework`<br>`typo3-flow-plugin`<br>`typo3-flow-site`<br>`typo3-flow-boilerplate`<br>`typo3-flow-build`
 
86
  | Wolf CMS | `wolfcms-plugin`
87
  | WordPress | <b>`wordpress-plugin`<br>`wordpress-theme`</b><br>`wordpress-muplugin`
88
  | Zend | `zend-library`<br>`zend-extra`<br>`zend-module`
83
  | symfony1 | **`symfony1-plugin`**
84
  | Tusk | `tusk-task`<br>`tusk-command`<br>`tusk-asset`
85
  | TYPO3 Flow | `typo3-flow-package`<br>`typo3-flow-framework`<br>`typo3-flow-plugin`<br>`typo3-flow-site`<br>`typo3-flow-boilerplate`<br>`typo3-flow-build`
86
+ | TYPO3 CMS | `typo3-cms-extension` (Deprecated in this package, use the [TYPO3 CMS Installers](https://packagist.org/packages/typo3/cms-composer-installers) instead)
87
  | Wolf CMS | `wolfcms-plugin`
88
  | WordPress | <b>`wordpress-plugin`<br>`wordpress-theme`</b><br>`wordpress-muplugin`
89
  | Zend | `zend-library`<br>`zend-extra`<br>`zend-module`
vendor/composer/installers/src/Composer/Installers/Installer.php CHANGED
@@ -65,6 +65,7 @@ class Installer extends LibraryInstaller
65
  'symfony1' => 'Symfony1Installer',
66
  'thelia' => 'TheliaInstaller',
67
  'tusk' => 'TuskInstaller',
 
68
  'typo3-flow' => 'TYPO3FlowInstaller',
69
  'whmcs' => 'WHMCSInstaller',
70
  'wolfcms' => 'WolfCMSInstaller',
65
  'symfony1' => 'Symfony1Installer',
66
  'thelia' => 'TheliaInstaller',
67
  'tusk' => 'TuskInstaller',
68
+ 'typo3-cms' => 'TYPO3CmsInstaller',
69
  'typo3-flow' => 'TYPO3FlowInstaller',
70
  'whmcs' => 'WHMCSInstaller',
71
  'wolfcms' => 'WolfCMSInstaller',
vendor/composer/installers/src/Composer/Installers/TYPO3CmsInstaller.php ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Composer\Installers;
3
+
4
+ /**
5
+ * Extension installer for TYPO3 CMS
6
+ *
7
+ * @deprecated since 1.0.25, use https://packagist.org/packages/typo3/cms-composer-installers instead
8
+ *
9
+ * @author Sascha Egerer <sascha.egerer@dkd.de>
10
+ */
11
+ class TYPO3CmsInstaller extends BaseInstaller
12
+ {
13
+ protected $locations = array(
14
+ 'extension' => 'typo3conf/ext/{$name}/',
15
+ );
16
+ }
vendor/composer/installers/tests/Composer/Installers/Test/InstallerTest.php CHANGED
@@ -169,6 +169,7 @@ class InstallerTest extends TestCase
169
  array('tusk-task', true),
170
  array('tusk-asset', true),
171
  array('typo3-flow-plugin', true),
 
172
  array('whmcs-gateway', true),
173
  array('wolfcms-plugin', true),
174
  array('wordpress-plugin', true),
@@ -298,6 +299,7 @@ class InstallerTest extends TestCase
298
  array('tusk-task', '.tusk/tasks/my_task/', 'shama/my_task'),
299
  array('typo3-flow-package', 'Packages/Application/my_package/', 'shama/my_package'),
300
  array('typo3-flow-build', 'Build/my_package/', 'shama/my_package'),
 
301
  array('whmcs-gateway', 'modules/gateways/gateway_name/', 'vendor/gateway_name'),
302
  array('wolfcms-plugin', 'wolf/plugins/my_plugin/', 'shama/my_plugin'),
303
  array('wordpress-plugin', 'wp-content/plugins/my_plugin/', 'shama/my_plugin'),
169
  array('tusk-task', true),
170
  array('tusk-asset', true),
171
  array('typo3-flow-plugin', true),
172
+ array('typo3-cms-extension', true),
173
  array('whmcs-gateway', true),
174
  array('wolfcms-plugin', true),
175
  array('wordpress-plugin', true),
299
  array('tusk-task', '.tusk/tasks/my_task/', 'shama/my_task'),
300
  array('typo3-flow-package', 'Packages/Application/my_package/', 'shama/my_package'),
301
  array('typo3-flow-build', 'Build/my_package/', 'shama/my_package'),
302
+ array('typo3-cms-extension', 'typo3conf/ext/my_extension/', 'shama/my_extension'),
303
  array('whmcs-gateway', 'modules/gateways/gateway_name/', 'vendor/gateway_name'),
304
  array('wolfcms-plugin', 'wolf/plugins/my_plugin/', 'shama/my_plugin'),
305
  array('wordpress-plugin', 'wp-content/plugins/my_plugin/', 'shama/my_plugin'),