• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

timber / timber / 21904947821

11 Feb 2026 12:24PM UTC coverage: 89.673% (+0.07%) from 89.608%
21904947821

Pull #3023

travis-ci

web-flow
Merge 70be7c33f into d6c4d6191
Pull Request #3023: Add render_twig_block method

65 of 67 new or added lines in 4 files covered. (97.01%)

38 existing lines in 6 files now uncovered.

4637 of 5171 relevant lines covered (89.67%)

65.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.67
/src/Factory/PostFactory.php
1
<?php
2

3
namespace Timber\Factory;
4

5
use InvalidArgumentException;
6
use Timber\Attachment;
7
use Timber\CoreInterface;
8
use Timber\Helper;
9
use Timber\Image;
10
use Timber\PathHelper;
11
use Timber\Post;
12
use Timber\PostArrayObject;
13
use Timber\PostQuery;
14
use WP_Post;
15
use WP_Query;
16

17
/**
18
 * Internal API class for instantiating posts
19
 */
20
class PostFactory
21
{
22
    public function from($params)
480✔
23
    {
24

25
        if (\is_int($params) || \is_string($params) && \is_numeric($params)) {
480✔
26
            return $this->from_id((int) $params);
375✔
27
        }
28

29
        if ($params instanceof WP_Query) {
152✔
30
            return $this->from_wp_query($params);
31✔
31
        }
32

33
        if (\is_object($params)) {
135✔
34
            return $this->from_post_object($params);
92✔
35
        }
36

37
        if ($this->is_numeric_array($params)) {
79✔
38
            return new PostArrayObject(\array_map([$this, 'from'], $params));
27✔
39
        }
40

41
        if (\is_array($params) && !empty($params['ID'])) {
52✔
42
            return $this->from_id($params['ID']);
2✔
43
        }
44

45
        if (\is_array($params) && [] !== $params) {
50✔
46
            return $this->from_wp_query(new WP_Query($params));
47✔
47
        }
48

49
        return null;
3✔
50
    }
51

52
    protected function from_id(int $id): ?Post
377✔
53
    {
54
        $wp_post = \get_post($id);
377✔
55

56
        if (!$wp_post) {
377✔
57
            return null;
2✔
58
        }
59

60
        return $this->build($wp_post);
376✔
61
    }
62

63
    protected function from_post_object(object $obj): CoreInterface
92✔
64
    {
65
        if ($obj instanceof CoreInterface) {
92✔
66
            return $obj;
13✔
67
        }
68

69
        if ($obj instanceof WP_Post) {
81✔
70
            return $this->build($obj);
81✔
71
        }
72

73
        throw new InvalidArgumentException(\sprintf(
×
74
            'Expected an instance of Timber\CoreInterface or WP_Post, got %s',
×
75
            $obj::class
×
UNCOV
76
        ));
×
77
    }
78

79
    protected function from_wp_query(WP_Query $query): iterable
77✔
80
    {
81
        return new PostQuery($query);
77✔
82
    }
83

84
    protected function get_post_class(WP_Post $post): string
436✔
85
    {
86
        /**
87
         * Pseudo filter that checks whether the non-usable filter was used.
88
         *
89
         * @deprecated 2.0.0, use `timber/post/classmap`
90
         */
91
        if ('deprecated' !== \apply_filters('Timber\PostClassMap', 'deprecated')) {
436✔
92
            Helper::doing_it_wrong(
1✔
93
                'The `Timber\PostClassMap` filter',
1✔
94
                'Use the `timber/post/classmap` filter instead.',
1✔
95
                '2.0.0'
1✔
96
            );
1✔
97
        }
98

99
        /**
100
         * Filters the class(es) used for different post types.
101
         *
102
         * Read more about this in the documentation for [Post Class Maps](https://timber.github.io/docs/v2/guides/class-maps/#the-post-class-map).
103
         *
104
         * The default Post Class Map will contain class names for posts, pages that map to
105
         * `Timber\Post` and a callback that will map attachments to `Timber\Attachment` and
106
         * attachments that are images to `Timber\Image`.
107
         *
108
         * Make sure to merge in your additional classes instead of overwriting the whole Class Map.
109
         *
110
         * @since 2.0.0
111
         * @example
112
         * ```
113
         * use Book;
114
         * use Page;
115
         *
116
         * add_filter( 'timber/post/classmap', function( $classmap ) {
117
         *     $custom_classmap = [
118
         *         'page' => Page::class,
119
         *         'book' => Book::class,
120
         *     ];
121
         *
122
         *     return array_merge( $classmap, $custom_classmap );
123
         * } );
124
         * ```
125
         *
126
         * @param array $classmap The post class(es) to use. An associative array where the key is
127
         *                        the post type and the value the name of the class to use for this
128
         *                        post type or a callback that determines the class to use.
129
         */
130
        $classmap = \apply_filters('timber/post/classmap', [
436✔
131
            'post' => Post::class,
436✔
132
            'page' => Post::class,
436✔
133
            // Apply special logic for attachments.
134
            'attachment' => fn (WP_Post $attachment) => $this->is_image($attachment) ? Image::class : Attachment::class,
436✔
135
        ]);
436✔
136

137
        $class = $classmap[$post->post_type] ?? null;
436✔
138

139
        // If class is a callable, call it to get the actual class name
140
        if (\is_callable($class)) {
436✔
141
            $class = $class($post);
81✔
142
        }
143

144
        $class ??= Post::class;
436✔
145

146
        /**
147
         * Filters the post class based on your custom criteria.
148
         *
149
         * Maybe you want to set a custom class based upon how blocks are used?
150
         * This allows you to filter the PHP class, utilizing data from the WP_Post object.
151
         *
152
         * @since 2.0.0
153
         * @example
154
         * ```
155
         * add_filter( 'timber/post/class', function( $class, $post ) {
156
         *     if ( has_blocks($post) ) {
157
         *         return GutenbergPost::class;
158
         *     }
159
         *
160
         *     return $class;
161
         * }, 10, 2 );
162
         * ```
163
         *
164
         * @param string $class The class to use.
165
         * @param WP_Post $post The post object.
166
         */
167
        $class = \apply_filters('timber/post/class', $class, $post);
436✔
168

169
        return $class;
436✔
170
    }
171

172
    protected function is_image(WP_Post $post)
76✔
173
    {
174
        $src = \get_attached_file($post->ID);
76✔
175
        $mimes = \wp_get_mime_types();
76✔
176
        // Add mime types that Timber recognizes as images, regardless of config
177
        $mimes['svg'] = 'image/svg+xml';
76✔
178
        $mimes['webp'] = 'image/webp';
76✔
179
        $check = \wp_check_filetype(PathHelper::basename($src), $mimes);
76✔
180

181
        /**
182
         * Filters the list of image extensions that will be used to determine if an attachment is an image.
183
         *
184
         * You can use this filter to add or remove image extensions to the list of extensions that will be
185
         * used to determine if an attachment is an image.
186
         *
187
         * @param array $extensions An array of image extensions.
188
         * @since 2.0.0
189
         */
190
        $extensions = \apply_filters('timber/post/image_extensions', [
76✔
191
            'jpg',
76✔
192
            'jpeg',
76✔
193
            'jpe',
76✔
194
            'gif',
76✔
195
            'png',
76✔
196
            'svg',
76✔
197
            'webp',
76✔
198
            'avif',
76✔
199
        ]);
76✔
200

201
        return \in_array($check['ext'], $extensions);
76✔
202
    }
203

204
    protected function build(WP_Post $post): CoreInterface
436✔
205
    {
206
        $class = $this->get_post_class($post);
436✔
207

208
        return $class::build($post);
436✔
209
    }
210

211
    protected function is_numeric_array($arr)
79✔
212
    {
213
        if (!\is_array($arr) || [] === $arr) {
79✔
214
            return false;
3✔
215
        }
216
        foreach (\array_keys($arr) as $k) {
76✔
217
            if (!\is_int($k)) {
76✔
218
                return false;
49✔
219
            }
220
        }
221
        return true;
27✔
222
    }
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc