├── .coveralls.yml
├── .gitignore
├── .travis.yml
├── README.md
├── README.txt
├── assets
├── banner-1544x500.jpg
├── banner-1544x500.png
├── banner-772x250-02.jpg
├── banner-772x250.png
├── screenshot-1.png
├── screenshot-2.png
├── screenshot-add.png
├── screenshot-pin.png
├── screenshot-remove.png
├── screenshot-zones.png
├── src
│ ├── js
│ │ └── script.js
│ └── scss
│ │ └── style.scss
├── stream_manager-icon.svg
└── stream_manager-readme_banner.png
├── bin
├── install-wp-tests.sh
└── phpunit-no-cover.xml
├── composer.json
├── composer.lock
├── gulpfile.js
├── includes
├── class-stream-manager-admin.php
├── class-stream-manager-ajax-helper.php
├── class-stream-manager-api.php
├── class-stream-manager-manager.php
├── class-stream-manager-utilities.php
├── class-stream-manager.php
├── timber-stream.php
└── views
│ ├── add.twig
│ ├── meta.twig
│ ├── rules.twig
│ ├── stream.twig
│ ├── stub.twig
│ └── zones.twig
├── package.json
├── phpunit.xml
├── stream-manager.php
└── tests
├── StreamManager_UnitTestCase.php
├── bootstrap.php
├── test-stream-manager-admin.php
├── test-stream-manager-ajax-helper.php
├── test-stream-manager-api.php
├── test-stream-manager-hooks.php
└── test-stream-manager-integration.php
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 |
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .sass-cache
3 | vendor
4 | wp-content
5 | assets/build
6 | node_modules
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | dist: xenial
4 |
5 | services: mysql
6 |
7 | language: php
8 |
9 | php:
10 | - 5.6.30
11 | - 7.1
12 |
13 | env:
14 | - WP_VERSION=latest WP_MULTISITE=0
15 | - WP_VERSION=4.7.3 WP_MULTISITE=0
16 |
17 | before_script:
18 | - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
19 | - composer install
20 |
21 | script:
22 | - if [ "$TRAVIS_BRANCH" == "master" ]; then mkdir -p build/logs; vendor/bin/phpunit --coverage-clover build/logs/clover.xml; fi
23 | - if [ "$TRAVIS_BRANCH" != "master" ]; then vendor/bin/phpunit -c bin/phpunit-no-cover.xml; fi
24 |
25 | after_script:
26 | - if [ "$TRAVIS_BRANCH" == "master" ]; then php vendor/bin/coveralls -v; fi
27 |
28 | after_success:
29 | - if [ "$TRAVIS_BRANCH" == "master" ]; then coveralls; fi
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/Upstatement/stream-manager)
4 | [](https://coveralls.io/r/Upstatement/stream-manager?branch=master)
5 |
6 |
7 | Curate streams of WordPress posts.
8 |
9 | ## Setup
10 |
11 | Install and activate [Timber](https://github.com/jarednova/timber), then install and activate this plugin. Create your first stream in the WordPress admin, and then use this in your template file, replacing the ID with the stream ID:
12 |
13 | ```php
14 | $context['stream'] = new TimberStream(5);
15 | ```
16 |
17 | And add this to your twig file:
18 |
19 | ```twig
20 | {% for post in stream.get_posts %}
21 |
22 | {{ post.title }}
23 |
24 | {% endfor %}
25 | ```
26 |
27 | ## User Guide
28 |
29 | [Walkthrough Screencast](https://vimeo.com/160133857/025e8af0ae)
30 |
31 | ### Adding posts to a stream
32 |
33 | To add a post to the stream, start typing the title of the post in the 'Add Post' box. When the post you want to add appears, click it. The post should automatically be added to the top of the stream.
34 |
35 | 
36 |
37 | ### Removing posts from a stream
38 |
39 | To remove a post from the stream, hover over the post and click the x in the upper right. Note that the post won't be deleted entirely -- instead, it will be removed from its current position and appended to the bottom of the stream.
40 |
41 | 
42 |
43 | ### Pinning posts
44 |
45 | Pinning a post will fix in in its current spot in the stream, even if new posts are added. For example, if you were to pin a post in the top slot, the next new post to be published will go to the second slot and the original post will remain at the top. Pin a post to its current spot by clicking on the thumbtack icon to the left of the post title. If the thumbtack is red, the post is pinned. Unpin a post by clicking the thumbtack a second time.
46 |
47 | 
48 |
49 | ### Reordering a stream
50 |
51 | Posts in the stream can be reordered via drag and drop. Make sure to click 'Update Post' after making changes to the stream.
52 |
53 | ### Using zones
54 |
55 | Zones are a useful tool for visualizing where posts are going to display on the page. For example, if the first post in the stream appears in a special featured slot, you might demarcate that using a zone title 'Featured Post.' To add a zone, type the name of the zone in the 'Zones' box on the right and click 'Add Zone.' The zone will be added to the top of the stream, after which it can be dragged and dropped to the desired location.
56 |
57 | 
58 |
59 | ## Filter Hooks
60 |
61 | Stream Manager includes several filter hooks that can be used to modify the options array attached to a stream. A common use case is to modify the query that populates the stream.
62 |
63 | ### Default Options
64 |
65 | ```php
66 | $default = array(
67 | 'query' => array(
68 | 'post_type' => 'post',
69 | 'post_status' => 'publish',
70 | 'has_password' => false,
71 | 'ignore_sticky_posts' => true,
72 | 'posts_per_page' => 100,
73 | 'orderby' => 'post__in'
74 | ),
75 |
76 | 'stream' => array(),
77 | 'layouts' => array(
78 | 'active' => 'default',
79 | 'layouts' => array(
80 | 'default' => array(
81 | 'name' => 'Default',
82 | 'zones' => array()
83 | )
84 | )
85 | )
86 | );
87 | ```
88 | * * *
89 |
90 | ### stream-manager/options/id={stream-id}
91 |
92 | Restrict stream #3 to posts of the 'event' post type.
93 |
94 | ```php
95 | add_filter('stream-manager/options/id=3', function($defaults) {
96 | $defaults['query'] = array_merge($defaults['query'], array('post_type' => array('event')));
97 | return $defaults;
98 | });
99 | ```
100 |
101 | * * *
102 |
103 | ### stream-manager/options/{stream-slug}
104 |
105 | Restrict the 'homepage' stream to posts in the 'local-news' category.
106 |
107 | ```php
108 | add_filter('stream-manager/options/homepage', function($defaults) {
109 | $defaults['query'] = array_merge($defaults['query'], array('category_name' => 'local-news'));
110 | return $defaults;
111 | });
112 | ```
113 |
114 | * * *
115 |
116 | ### stream-manager/taxonomy/{stream-slug}
117 |
118 | Restrict the 'classifieds' stream to the posts with the tags with term ids of 12 and 13
119 |
120 | ```php
121 | add_filter('stream-manager/taxonomy/classifieds', function($defaults) {
122 | $defaults['relation'] = "OR";
123 | $defaults['post_tag'] = array( 12,13 );
124 | return $defaults;
125 | });
126 | ```
127 |
128 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | === Stream Manager ===
2 | Contributors: chrisvoll, lggorman, jarednova
3 | Tags: posts
4 | Requires at least: 3.8
5 | Tested up to: 4.7.3
6 | Stable tag: 1.3.4
7 | License: GPLv2 or later
8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html
9 |
10 | Easily curate streams of recent posts. Pin, remove, or add posts to a stream via a drag and drop interface.
11 |
12 | == Description ==
13 |
14 | We created Stream Manager with news editors in mind. Admins wanted the latest headlines to show up on the front page automatically, but didn’t want to give up the flexibility of pinning a major story in a featured spot or pushing a smaller item down below the fold.
15 |
16 | Stream Manager provides a simple interface for curating feeds of new posts from the WordPress Admin. New posts show up automatically at the top of a stream, but content can easily be added, removed, or repositioned on the page via the stream editor. Admins also have the option of pinning a post, which will lock it in its current position regardless of new content.
17 |
18 | Stream Manager is designed to work with Twig templating plugin [Timber](https://wordpress.org/plugins/timber-library/), as detailed in the installation instructions. Check out the [Timber project page](http://upstatement.com/timber/) for more info.
19 |
20 | = Links =
21 | * [Github repo](http://github.com/Upstatement/stream-manager) (includes user guide)
22 | * [Walkthough Screencast](https://vimeo.com/160133857/025e8af0ae)
23 | * [Developer docs](https://upstatement.github.io/stream-manager/)
24 | * [Timber docs](http://jarednova.github.io/timber/)
25 |
26 | == Installation ==
27 |
28 | 1. Install and activate Timber, then install and activate this plugin.
29 | 2. Create a new stream from the WordPress admin.
30 | 3. Add the following to your template file, replacing 'new-stream' with the slug of your stream.
31 | `
32 | $context['stream'] = new TimberStream('new-stream');
33 | `
34 | 4. Finally, add this to your twig file.
35 | `
36 | {% for post in stream.get_posts %}
37 |
38 | {{ post.title }}
39 |
40 | {% endfor %}
41 | `
42 |
43 | == Frequently Asked Questions ==
44 |
45 | = Can streams be filtered by post type or category? =
46 |
47 | Yes! Streams can be filtered by post type, taxonomy, or just about anything else that can be passed into a wp_query array. Check out the [github readme](http://github.com/Upstatement/stream-manager) for details on filter hooks.
48 |
49 |
50 | == Screenshots ==
51 |
52 | 1. Adding a new stream.
53 | 2. Pinning a stream to the top of
54 |
55 |
--------------------------------------------------------------------------------
/assets/banner-1544x500.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/banner-1544x500.jpg
--------------------------------------------------------------------------------
/assets/banner-1544x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/banner-1544x500.png
--------------------------------------------------------------------------------
/assets/banner-772x250-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/banner-772x250-02.jpg
--------------------------------------------------------------------------------
/assets/banner-772x250.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/banner-772x250.png
--------------------------------------------------------------------------------
/assets/screenshot-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/screenshot-1.png
--------------------------------------------------------------------------------
/assets/screenshot-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/screenshot-2.png
--------------------------------------------------------------------------------
/assets/screenshot-add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/screenshot-add.png
--------------------------------------------------------------------------------
/assets/screenshot-pin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/screenshot-pin.png
--------------------------------------------------------------------------------
/assets/screenshot-remove.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/screenshot-remove.png
--------------------------------------------------------------------------------
/assets/screenshot-zones.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/screenshot-zones.png
--------------------------------------------------------------------------------
/assets/src/js/script.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Stream Manager Admin JavaScript
3 | *
4 | * @package StreamManager
5 | * @author Chris Voll + Upstatement
6 | * @license GPL-2.0+
7 | * @link http://upstatement.com
8 | * @copyright 2014 Upstatement
9 | */
10 |
11 | jQuery(function($) {
12 |
13 | var stream = {
14 |
15 | ////////////////////////////////////////////
16 | //
17 | // Setup
18 | //
19 | ////////////////////////////////////////////
20 |
21 | $stream: $('.sm-posts'),
22 | $queue: $('.sm-alert'),
23 | $search: $('.sm-search'),
24 | $results: $('.sm-results'),
25 | $form: $('form#post'),
26 |
27 | init: function () {
28 |
29 | // stream Events
30 | $(document).on('heartbeat-tick.sm', $.proxy(this.on_heartbeat, this));
31 | this.$stream
32 | .on('click', '.pin-unpin', $.proxy(this.on_stub_pin, this))
33 | .on('dblclick', '.stub', $.proxy(this.on_stub_pin, this))
34 | .on('click', '.remove', $.proxy(this.on_stub_remove, this))
35 | .sortable({
36 | start : $.proxy(this.on_sortable_start, this),
37 | stop : $.proxy(this.on_sortable_stop, this),
38 | change : $.proxy(this.on_sortable_change, this),
39 | revert : 150,
40 | axis : 'y'
41 | });
42 | $('.reload-stream').on('click', $.proxy(this.on_stream_reload, this));
43 |
44 | // stream Update Notifications
45 | $(document).on('sm/stream_update', $.proxy(this.on_stream_update, this));
46 | this.$queue.on('click', $.proxy(this.on_apply_queue, this));
47 | this.$form.on('submit.sm', $.proxy(this.on_form_submit, this));
48 |
49 | // Search
50 | this.$search.on({
51 | input: $.proxy(this.on_search_input, this),
52 | keydown: $.proxy(this.on_search_keydown, this),
53 | focus: $.proxy(this.on_show_results, this)
54 | });
55 | this.$results.on({
56 | mouseover: $.proxy(this.on_result_hover, this),
57 | 'click sm/select': $.proxy(this.on_result_select, this)
58 | }, '.sm-result');
59 | $('body').on('mousedown', $.proxy(this.on_hide_results, this));
60 |
61 | },
62 |
63 |
64 | ////////////////////////////////////////////
65 | //
66 | // Heartbeat
67 | //
68 | // Avoid stream collisions by loading stream
69 | // updates from the database. For the purpose
70 | // of more accurate placements, pinned posts
71 | // are excluded from the list of IDs that
72 | // are passed around.
73 | //
74 | // Note that the purpose of this isn't to keep
75 | // the stream in sync among multiple editors;
76 | // instead, it's meant to ensure that no
77 | // published posts are left behind, in addition
78 | // to making sure that removed posts do not
79 | // interfere with the stream's sorting.
80 | //
81 | ////////////////////////////////////////////
82 |
83 | on_heartbeat: function(e, data) {
84 | var that = this;
85 |
86 | var front = this.$stream.attr('data-ids').split(',')
87 | back = data.sm_ids.split(',');
88 |
89 | // Published posts
90 | _.each( _.difference(back, front), function(id) {
91 | that.add_to_queue( 'insert', id, _.indexOf( back, id ) );
92 | });
93 |
94 | // Deleted posts
95 | _.each( _.difference(front, back), function(id) {
96 | that.add_to_queue( 'remove', id );
97 | });
98 |
99 | // Deleted pinned posts
100 | var front_pinned = this.$stream.attr('data-pinned').split(','),
101 | back_pinned = data.sm_pinned.split(',');
102 |
103 | _.each( _.difference(front_pinned, back_pinned), function(id) {
104 | that.add_to_queue( 'remove', id );
105 | });
106 |
107 | this.$stream.prop({
108 | 'data-ids' : data.sm_ids,
109 | 'data-pinned' : data.sm_pinned
110 | });
111 | },
112 |
113 |
114 | ////////////////////////////////////////////
115 | //
116 | // Stream Manipulation
117 | //
118 | ////////////////////////////////////////////
119 |
120 | on_stub_pin: function(e) {
121 | e.preventDefault();
122 |
123 | var $stub = $(e.target);
124 | if ( !$stub.is('.stub') ) $stub = $stub.closest('.stub');
125 |
126 | if ( $stub.hasClass('zone') ) return;
127 |
128 | if ( $stub.hasClass('pinned') ) {
129 | $stub.removeClass('pinned');
130 | $stub.find('.sm-pin-checkbox').prop('checked', false);
131 | $stub.find('.pin-unpin').prop('title', 'Pin this post')
132 | } else {
133 | $stub.addClass('pinned');
134 | $stub.find('.sm-pin-checkbox').prop('checked', true);
135 | $stub.find('.pin-unpin')
136 | .prop('title', 'Unpin this post')
137 | .addClass('animating')
138 | .one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
139 | $(this).removeClass('animating');
140 | });
141 | }
142 | },
143 |
144 | on_stub_remove: function(e) {
145 | e.preventDefault();
146 | var $stub = $(e.target).closest('.stub'),
147 | that = this;
148 |
149 | if ( $stub.hasClass('zone') ) {
150 | $stub.remove();
151 | $(document).trigger('sm/zone_update');
152 | return;
153 | }
154 | if ( $stub.hasClass('removed') ) return;
155 |
156 | $stub.addClass('removed');
157 |
158 | setTimeout(function() {
159 | that.remove_single( $stub.attr('data-id') );
160 | }, 500);
161 | },
162 |
163 | on_sortable_start: function (event, ui) {
164 | $(document).trigger('sm/sortable_start', ui.item);
165 | $(ui.placeholder).height($(ui.item).height());
166 | if ( ui.item.hasClass('pinned') ) {
167 | if ( !ui.item.hasClass('zone') ) {
168 | this.inventory_pinned('zone');
169 | } else {
170 | this.pinned_inventory = [];
171 | }
172 | } else {
173 | this.inventory_pinned();
174 | }
175 | },
176 |
177 | on_sortable_stop: function (event, ui) {
178 | if ( ui.item.hasClass('zone') ) {
179 | $(document).trigger('sm/zone_update');
180 | }
181 | },
182 |
183 | on_sortable_change: function (event, ui) {
184 | this.remove_pinned();
185 | this.insert_pinned();
186 | },
187 |
188 |
189 | // Remove all unpinned items, get new posts
190 | // from the database based on categories and tags
191 | // selected under Rules
192 | on_stream_reload: function (e) {
193 | e.preventDefault();
194 | var that = this;
195 |
196 | // Clear queues
197 | this.insert_queue = [];
198 | this.remove_queue = [];
199 | $(document).trigger('sm/stream_update');
200 |
201 | // Disable heartbeat checking
202 | $(document).off('heartbeat-tick.sm');
203 |
204 |
205 | // Setup the ajax request
206 | var categories = [];
207 | $('#categorychecklist input:checked').each(function() {
208 | categories.push( $(this).val() );
209 | })
210 | var exclude = [];
211 | $('.stub.pinned').each(function() {
212 | exclude.push( $(this).attr('data-id') );
213 | });
214 | var request = {
215 | action: 'sm_reload',
216 | stream_id: $('#post_ID').val(),
217 | taxonomies: {
218 | category: categories,
219 | post_tag: $('#tax-input-post_tag').val(),
220 | },
221 | exclude: exclude
222 | };
223 |
224 | $.post(ajaxurl, request, function(response) {
225 | response = JSON.parse(response);
226 | if ( !response.status || response.status == 'error' ) return;
227 |
228 | that.inventory_pinned();
229 | that.remove_pinned();
230 | that.$stream.empty();
231 | $(response.data).each(function() {
232 | that.$stream.append(this);
233 | });
234 | that.insert_pinned();
235 | });
236 | },
237 |
238 |
239 |
240 | ////////////////////////////////////////////
241 | //
242 | // Post queue UI
243 | //
244 | // Listens for the stream events to update
245 | // the user interface, letting the end user
246 | // know when there are changes.
247 | //
248 | ////////////////////////////////////////////
249 |
250 | on_stream_update: function (e) {
251 | var insert = this.insert_queue,
252 | remove = this.remove_queue;
253 |
254 | if ( (insert.length + remove.length) > 0 ) {
255 | this.$queue.show();
256 | var text = [' '];
257 |
258 | if ( insert.length == 1 ) {
259 | text.push('There is 1 new post. ');
260 | } else if ( insert.length > 1 ) {
261 | text.push('There are ' + insert.length + ' new posts. ');
262 | }
263 |
264 | if ( remove.length == 1 ) {
265 | text.push('There is 1 removed post.');
266 | } else if ( remove.length > 1 ) {
267 | text.push('There are ' + remove.length + ' removed posts.');
268 | }
269 |
270 | this.$queue.html(text.join(""));
271 | this.allow_submit = false;
272 | } else {
273 | this.$queue.hide();
274 | this.allow_submit = true;
275 | }
276 | },
277 |
278 | on_apply_queue: function(e) {
279 | this.apply_queues();
280 | this.allow_submit = true;
281 | },
282 |
283 | // @TODO: force one more heartbeat
284 | on_form_submit: function(e) {
285 | this.submit_flag = !this.submit_flag;
286 | if ( !this.submit_flag && !this.allow_submit ) return;
287 |
288 | if ( !this.allow_submit ) {
289 | if ( ! window.confirm('New posts have been published or removed since you began editing the stream. \n\nPress Cancel to go back, or OK to save the stream without them.') ) {
290 | e.preventDefault();
291 | } else {
292 | this.allow_submit = true;
293 | }
294 | }
295 | },
296 |
297 |
298 | ////////////////////////////////////////////
299 | //
300 | // Post Queue
301 | //
302 | // API for modifying the posts in the stream
303 | // user interface, including adding and
304 | // removing by post IDs. Used by the collision
305 | // management system (with the WordPress
306 | // heartbeat API) and the post search.
307 | //
308 | // -----------------------------------------
309 | //
310 | // Usage: (where queue_name is 'insert' or 'remove')
311 | // > stream.add_to_queue( queue_name, post_id, position );
312 | // > stream.remove_from_queue( queue_name, post_id );
313 | //
314 | // Apply changes:
315 | // > stream.apply_insert( queue_override );
316 | // > stream.apply_remove( queue_override );
317 | //
318 | // Add a single post without invoking the queues:
319 | // > stream.insert_single( id, position );
320 | // > stream.remove_single( id );
321 | //
322 | ////////////////////////////////////////////
323 |
324 | insert_queue: [],
325 | remove_queue: [],
326 |
327 | /**
328 | * Add a post to a queue
329 | */
330 | add_to_queue: function ( queue, id, position ) {
331 | if ( queue == 'insert' ) {
332 | if ( this.post_exists(id) || this.is_in_queue('insert', id) !== -1 ) return;
333 | this.insert_queue.push({
334 | id: id,
335 | position: position
336 | });
337 | } else if ( queue == 'remove' ) {
338 | if ( !this.post_exists(id) || this.is_in_queue('remove', id) !== -1 ) return;
339 | this.remove_queue.push({
340 | id: id
341 | });
342 | }
343 | $(document).trigger('sm/stream_update');
344 | },
345 |
346 | /**
347 | * Remove a post from a queue
348 | */
349 | remove_from_queue: function ( queue, id ) {
350 | var queue_name = queue + '_queue';
351 |
352 | for ( i in this[queue_name] ) {
353 | if ( this[queue_name][i].id == id ) {
354 | this[queue_name].splice(i, 1);
355 | }
356 | }
357 | $(document).trigger('sm/stream_update');
358 | },
359 |
360 | /**
361 | * Checks if a post is in a queue
362 | * @return -1 if not found, position if found
363 | */
364 | is_in_queue: function ( queue, id ) {
365 | var queue_name = queue + '_queue';
366 |
367 | for ( i in this[queue_name] ) {
368 | if ( this[queue_name][i].id == id ) {
369 | return i;
370 | }
371 | }
372 | return -1;
373 | },
374 |
375 |
376 | /**
377 | * Apply queue changes
378 | */
379 | apply_insert: function ( queue_override, animate ) {
380 | var queue = queue_override ? queue_override : this.insert_queue;
381 | if (queue.length < 1) return;
382 | var that = this;
383 |
384 | $.post(ajaxurl, {
385 | action: 'sm_request',
386 | queue: queue
387 | }, function (response) {
388 | response = JSON.parse(response);
389 | if ( response.status && response.status == 'error' ) return;
390 | that.ui_insert( response.data, animate );
391 | $(document).trigger( 'sm/stream_update' );
392 | }
393 | );
394 | },
395 | apply_remove: function ( queue_override ) {
396 | var queue = queue_override ? queue_override : this.remove_queue;
397 | if (queue.length < 1) return;
398 | this.ui_remove( queue );
399 | $(document).trigger( 'sm/stream_update' );
400 | },
401 | apply_queues: function () {
402 | this.apply_remove();
403 | this.apply_insert(null, true);
404 | },
405 |
406 |
407 |
408 | /**
409 | * Insert/remove post(s) in the UI
410 | */
411 | ui_insert: function ( insert_data, animate ) {
412 | if ( !insert_data ) return;
413 |
414 | this.inventory_pinned();
415 | this.remove_pinned();
416 |
417 | for ( id in insert_data ) {
418 | if ( insert_data[id]['object'] ) {
419 | this.inject( insert_data[id]['position'], insert_data[id]['object'], animate );
420 | }
421 | this.remove_from_queue( 'insert', id );
422 | }
423 |
424 | this.insert_pinned();
425 | },
426 | ui_remove: function ( remove_queue ) {
427 | if ( !remove_queue ) return;
428 |
429 | this.inventory_pinned();
430 | this.remove_pinned();
431 |
432 | this.delete_pinned( remove_queue );
433 |
434 | for ( i in remove_queue ) {
435 | var id = remove_queue[i].id;
436 | this.$stream.find('#post-' + id).remove();
437 | this.remove_from_queue( 'remove', id );
438 | }
439 |
440 | this.insert_pinned();
441 | },
442 |
443 |
444 |
445 | /**
446 | * Insert or remove just one post without invoking the queues
447 | */
448 | insert_single: function ( id, position ) {
449 | if ( this.post_exists(id) ) return;
450 | this.apply_insert([{
451 | id: id,
452 | position: position
453 | }], true);
454 | },
455 | remove_single: function ( id ) {
456 | if ( !this.post_exists(id) ) return;
457 | this.apply_remove([{
458 | id: id
459 | }]);
460 | },
461 |
462 |
463 | /**
464 | * Inserts one post object into the stream
465 | */
466 | inject: function (position, object, animate) {
467 | object = $( object );
468 | if ( position == 0 ) {
469 | this.$stream.prepend( object );
470 | } else {
471 | var $object_before = this.$stream.find( '.stub:nth-child(' + position + ')' );
472 | if ( $object_before.length ) {
473 | $object_before.after( object );
474 | } else {
475 | this.$stream.append( object );
476 | }
477 | }
478 |
479 | if ( animate ) {
480 | object.addClass('inserted');
481 | setTimeout(function() {
482 | object.removeClass('inserted');
483 | }, 2000);
484 | }
485 | },
486 |
487 |
488 | /**
489 | * Check if a post exists in the stream
490 | */
491 | find_post: function (id) {
492 | return this.$stream.find('#post-' + id);
493 | },
494 | post_exists: function (id) {
495 | return this.find_post(id).length;
496 | },
497 |
498 |
499 | /**
500 | * Helpers for keeping pinned stubs in place
501 | */
502 | pinned_inventory: [],
503 | inventory_pinned: function (className) {
504 | if ( !className ) className = 'pinned';
505 | var that = this;
506 | this.pinned_inventory = [];
507 | this.$stream.find('.stub').each( function (i) {
508 | if ( $(this).hasClass( className ) ) {
509 | that.pinned_inventory.push({
510 | id: $(this).attr('data-id'),
511 | obj: this,
512 | position: i
513 | });
514 | }
515 | });
516 | },
517 | remove_pinned: function () {
518 | for (i in this.pinned_inventory) {
519 | this.pinned_inventory[i].obj.remove();
520 | }
521 | },
522 | delete_pinned: function ( remove_queue ) {
523 | for ( i in this.pinned_inventory ) {
524 | var id = this.pinned_inventory[i].id;
525 | for ( j in remove_queue ) {
526 | if ( remove_queue[j].id == id ) {
527 | this.remove_from_queue( 'remove', id );
528 | this.pinned_inventory.splice(i, 1);
529 | }
530 | }
531 | }
532 | },
533 | insert_pinned: function () {
534 | for (i in this.pinned_inventory) {
535 | this.inject(
536 | this.pinned_inventory[i].position,
537 | this.pinned_inventory[i].obj
538 | );
539 | }
540 | },
541 |
542 |
543 | ////////////////////////////////////////////
544 | //
545 | // Search
546 | //
547 | ////////////////////////////////////////////
548 |
549 | search_query: '',
550 | search_timer: null,
551 |
552 | allow_submit: true,
553 | submit_flag: true, // because `submit` event gets called twice
554 |
555 | on_search_input: function(e) {
556 | var that = this;
557 |
558 | clearTimeout(this.search_timer);
559 | this.search_timer = setTimeout(function() {
560 | if ( $(e.target).val() !== that.search_query ) {
561 | that.search_query = $(e.target).val();
562 |
563 | if ( that.search_query.length > 2 ) {
564 |
565 | var request = {
566 | action: 'sm_search',
567 | query: that.search_query,
568 | stream_id: $('#post_ID').val()
569 | };
570 |
571 | $.post(ajaxurl, request, function(results) {
572 | var data = JSON.parse(results);
573 |
574 | that.$results.empty();
575 | that.$results.show();
576 |
577 | for (i in data.data) {
578 | var post = data.data[i];
579 | that.$results.append([
580 | '
',
581 | '',
582 | post.title,
583 | ' ',
584 | post.human_date + ' ago',
585 | that.post_exists( post.id ) ? ' - Already in stream' : '',
586 | ' ',
587 | ' ',
588 | ' '].join('')
589 | );
590 |
591 | that.$results.find('li:nth-child(1) .sm-result').addClass('active');
592 | }
593 | });
594 | } else {
595 | that.$results.empty();
596 | that.$results.hide();
597 | }
598 | }
599 | }, 200);
600 | },
601 |
602 | on_search_keydown: function (e) {
603 | if (e.keyCode == 38) {
604 | // up
605 | e.preventDefault();
606 | this.$results.show();
607 | var $active = this.$results.find('.active');
608 | var $prev = $active.parent().prev().find('.sm-result');
609 |
610 | if (!$prev.length) return;
611 |
612 | $active.removeClass('active');
613 | $prev.addClass('active');
614 | } else if (e.keyCode == 40) {
615 | // down
616 | e.preventDefault();
617 | this.$results.show();
618 | var $active = this.$results.find('.active');
619 | var $next = $active.parent().next().find('.sm-result');
620 |
621 | if (!$next.length) return;
622 |
623 | $active.removeClass('active');
624 | $next.addClass('active');
625 | } else if (e.keyCode == 13) {
626 | // enter
627 | e.preventDefault();
628 | if ( !this.$results.is(':visible') ) return;
629 | this.$results.find('.active').trigger('sm/select');
630 | }
631 | },
632 |
633 | on_show_results: function(e) {
634 | if ( !this.$results.is(':empty') ) {
635 | this.$results.show();
636 | }
637 | },
638 |
639 | on_result_hover: function (e) {
640 | if ( $(e.currentTarget).hasClass('active') ) return;
641 | this.$results.find('.active').removeClass('active');
642 | $(e.currentTarget).addClass('active');
643 | },
644 |
645 | on_result_select: function (e) {
646 | e.preventDefault();
647 | var id = $(e.currentTarget).attr('data-id');
648 | var current = this.find_post( id );
649 | if ( current && current.length ) {
650 | // only move non-pinned item
651 | // @TODO: revisit this in the future
652 | if ( current.hasClass('pinned') ) {
653 | setTimeout(function() { alert('This post is already pinned in the stream. To move it, please unpin it first.'); }, 0);
654 | return false;
655 | }
656 | this.remove_single( id );
657 | }
658 | this.insert_single( id, 0 );
659 | this.$results.hide();
660 | },
661 |
662 | on_hide_results: function(e) {
663 | if ( !$(e.target).closest('.sm-search-container').length ) {
664 | this.$results.hide();
665 | }
666 | },
667 |
668 | };
669 |
670 |
671 |
672 |
673 | stream.layouts = {
674 |
675 | data: {},
676 |
677 | init: function() {
678 | this.$data_field = $('.layouts-data');
679 | this.$container = $('#stream_box_zones');
680 | this.data = JSON.parse( this.$data_field.val() );
681 | $(document).on('sm/zone_update', $.proxy( this.on_zone_update, this ));
682 |
683 | // this.$container.find('.add-zone, .add-layout').on('click', this.on_toggle_add );
684 | this.$container.find('.add-zone-input').on('keydown', $.proxy( this.on_add_keydown, this ));
685 | this.$container.find('.add-zone-button').on('click', $.proxy( this.on_click_add_button, this ));
686 | // this.$container.find('.active-layout').on('change', $.proxy( this.on_change_layout, this ));
687 | stream.$stream.on({
688 | input : $.proxy( this.on_zone_update, this ),
689 | keydown : this.on_zone_keydown
690 | }, '.zone .zone-header');
691 | },
692 |
693 | on_toggle_add: function(e) {
694 | e.preventDefault();
695 | // if ( $(this).hasClass('add-zone') ) {
696 | $(this).siblings('.add-zone-container').toggle();
697 | // }
698 | // if ( $(this).hasClass('add-layout') ) {
699 | // $(this).siblings('.add-layout-container').toggle();
700 | // }
701 | },
702 |
703 | on_zone_update: function() {
704 | var that = this;
705 | // Update the internal data
706 | var active = this.data.active;
707 | this.data.layouts[active].zones = [];
708 |
709 | stream.$stream.find('.zone').each(function(index, el) {
710 | that.data.layouts[active].zones.push({
711 | position: $(this).index(),
712 | title: $(this).find('.zone-header').val()
713 | });
714 | });
715 |
716 | var $select = this.$container.find('.active-layout');
717 | $select.empty();
718 | for ( i in this.data.layouts ) {
719 | $select.append([
720 | '',
721 | this.data.layouts[i].name,
722 | ' '
723 | ].join(""));
724 | }
725 |
726 | this.$data_field.val( JSON.stringify(this.data) );
727 | },
728 |
729 | on_zone_keydown: function (e) {
730 | // disable enter
731 | if ( e.keyCode == '13' ) {
732 | e.preventDefault();
733 | this.blur();
734 | }
735 | },
736 |
737 | on_click_add_button: function (e) {
738 | e.preventDefault();
739 | //if ( $(e.currentTarget).hasClass('add-zone-button') ) {
740 | var $input = $(e.currentTarget).siblings('.layouts-input');
741 | this.insert_zones([{
742 | position: 0,
743 | title: $input.val()
744 | }]);
745 | $input.val('');
746 | //}
747 |
748 | // if ( $(e.currentTarget).hasClass('add-layout-button') ) {
749 | // var $input = $(e.currentTarget).siblings('.layouts-input');
750 | // var slug = this.slugify( $input.val() );
751 | // this.data.layouts[ slug ] = {
752 | // name: $input.val(),
753 | // zones: {}
754 | // }
755 | // this.data.active = slug;
756 | // $(document).trigger('sm/zone_update');
757 | // $input.val('');
758 | // }
759 | },
760 | on_add_keydown: function (e) {
761 | if ( e.keyCode == '13' ) {
762 | e.preventDefault();
763 | $(e.currentTarget).siblings('.button').trigger('click');
764 | }
765 | },
766 |
767 | // on_change_layout: function (e) {
768 | // this.data.active = $(e.currentTarget).val();
769 | // this.remove_zones();
770 | // this.insert_zones( this.data.layouts[ this.data.active ].zones );
771 | // },
772 |
773 | insert_zones: function( zones ) {
774 | for ( i in zones ) {
775 | stream.inject( zones[i].position, $([
776 | '',
777 | '
',
778 | '',
779 | '
'
780 | ].join("")) );
781 | }
782 | $(document).trigger('sm/zone_update');
783 | },
784 |
785 | remove_zones: function() {
786 | stream.$stream.find('.zone').remove();
787 | },
788 |
789 | // slugify: function(name) {
790 | // return name.toLowerCase().replace(/ /g,'-').replace(/[-]+/g, '-').replace(/[^\w-]+/g,'');
791 | // }
792 | };
793 |
794 |
795 |
796 |
797 | stream.init();
798 | stream.layouts.init();
799 |
800 | window.stream = stream;
801 | });
802 |
--------------------------------------------------------------------------------
/assets/src/scss/style.scss:
--------------------------------------------------------------------------------
1 |
2 | ////////////////////////////////////////////
3 | //
4 | // Configuration
5 | //
6 | ////////////////////////////////////////////
7 |
8 | $stub-hover: #FAFAFA;
9 | $stub-pinned: #F1F1F1;
10 |
11 |
12 | ////////////////////////////////////////////
13 | //
14 | // Mixins & Animations
15 | //
16 | ////////////////////////////////////////////
17 |
18 | @mixin prefixer($prefix, $content) {
19 | -webkit-#{$prefix}: #{$content};
20 | -moz-#{$prefix}: #{$content};
21 | #{$prefix}: #{$content};
22 | }
23 | @mixin clear {
24 | &::after {
25 | clear: both;
26 | content: "";
27 | display: table;
28 | }
29 | }
30 | @-webkit-keyframes pulse {
31 | 0% {
32 | -webkit-transform: scale3d(1, 1, 1);
33 | transform: scale3d(1, 1, 1);
34 | }
35 | 30% {
36 | -webkit-transform: scale3d(1.4, 1.4, 1.4);
37 | transform: scale3d(1.4, 1.4, 1.4);
38 | }
39 | 60% {
40 | -webkit-transform: scale3d(.8, .8, .8);
41 | transform: scale3d(.8, .8, .8);
42 | }
43 | 100% {
44 | -webkit-transform: scale3d(1, 1, 1);
45 | transform: scale3d(1, 1, 1);
46 | }
47 | }
48 | @keyframes pulse {
49 | 0% {
50 | -webkit-transform: scale3d(1, 1, 1);
51 | -ms-transform: scale3d(1, 1, 1);
52 | transform: scale3d(1, 1, 1);
53 | }
54 | 30% {
55 | -webkit-transform: scale3d(1.4, 1.4, 1.4);
56 | -ms-transform: scale3d(1.4, 1.4, 1.4);
57 | transform: scale3d(1.4, 1.4, 1.4);
58 | }
59 | 60% {
60 | -webkit-transform: scale3d(.8, .8, .8);
61 | -ms-transform: scale3d(.8, .8, .8);
62 | transform: scale3d(.8, .8, .8);
63 | }
64 | 100% {
65 | -webkit-transform: scale3d(1, 1, 1);
66 | -ms-transform: scale3d(1, 1, 1);
67 | transform: scale3d(1, 1, 1);
68 | }
69 | }
70 |
71 |
72 | ////////////////////////////////////////////
73 | //
74 | // Post Stub
75 | //
76 | ////////////////////////////////////////////
77 |
78 | .stub {
79 | cursor: move;
80 | padding: 10px;
81 | background: #FFF;
82 | margin: 0 1px 1px;
83 | box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
84 | @include prefixer(transition, .2s background);
85 | @include clear;
86 | min-height: 72px;
87 | a {
88 | text-decoration: none;
89 | }
90 |
91 | .post-thumb {
92 | float: right;
93 | margin-right: 10px;
94 | margin-left:10px;
95 | }
96 |
97 | .stub-action {
98 | display: inline-block;
99 | padding: 10px 10px 10px 5px;
100 | opacity: .1;
101 | margin-bottom: 25px;
102 | @include prefixer(transition, .2s opacity);
103 |
104 |
105 | &.pin-unpin {
106 | float: left;
107 | }
108 | &.remove {
109 | float: right;
110 | opacity: 0;
111 | }
112 | }
113 |
114 | .date {
115 | color: #AAA;
116 | border: 0;
117 | }
118 | .row-actions {
119 | opacity: 0;
120 | display: inline;
121 | visibility: visible;
122 | @include prefixer(transition, .2s opacity);
123 | }
124 |
125 | &:hover {
126 | background: $stub-hover;
127 | .row-actions {
128 | opacity: 1;
129 | }
130 | .stub-action {
131 | opacity: .5;
132 | }
133 | }
134 |
135 | &.pinned {
136 | box-shadow: none;
137 |
138 | &:hover {
139 | background: darken($stub-pinned, 3%);
140 | }
141 | .pin-unpin {
142 | opacity: 1;
143 | color: #D54E21;
144 | &.animating {
145 | -webkit-animation: pulse .5s ease-out;
146 | animation: pulse .5s ease-out;
147 | }
148 | }
149 | }
150 | &.ui-sortable-helper {
151 | box-shadow: 0 2px 10px rgba(0, 0, 0, .1);
152 | opacity: .9;
153 | }
154 | &.inserted {
155 | background: #FBF0C0;
156 | }
157 | &.removed {
158 | background: #FBCFCA !important;
159 | }
160 | }
161 |
162 |
163 | ////////////////////////////////////////////
164 | //
165 | // Stream Queue UI
166 | //
167 | ////////////////////////////////////////////
168 |
169 | .sm-alert {
170 | display: none;
171 | cursor: pointer;
172 | padding: 8px 12px;
173 | background: #1E8CBE;
174 | color: #FFF;
175 | font-weight: bold;
176 | }
177 |
178 |
179 | ////////////////////////////////////////////
180 | //
181 | // Search
182 | //
183 | ////////////////////////////////////////////
184 |
185 | .sm-add {
186 |
187 | .sm-search-container {
188 | position: relative;
189 | }
190 |
191 | .sm-search {
192 | display: block;
193 | width: 100%;
194 | padding: 10px 14px;
195 | margin: 0;
196 | }
197 |
198 | .sm-results {
199 | padding: 5px 0;
200 | margin: 0;
201 | position: absolute;
202 | top: 100%;
203 | left: 0;
204 | z-index: 100;
205 | background: #FFF;
206 | border-radius: 0 0 4px 4px;
207 | box-shadow: 0 3px 7px rgba(0, 0, 0, .1);
208 |
209 | li {
210 | margin: 0;
211 | }
212 |
213 | .sm-result {
214 | text-decoration: none;
215 | display: block;
216 | padding: 6px 15px;
217 |
218 | &.active {
219 | background: #0074A2;
220 | color: #FFF;
221 | }
222 | }
223 |
224 | .sm-result-date {
225 | font-size: 11px;
226 | opacity: .5;
227 | margin-left: 5px;
228 | }
229 | }
230 | }
231 |
232 |
233 | ////////////////////////////////////////////
234 | //
235 | // Layouts & Zones
236 | //
237 | ////////////////////////////////////////////
238 |
239 | .zone {
240 | margin: 0;
241 | box-shadow: none;
242 | min-height: 0;
243 | display:block;
244 | background: transparent;
245 | .stub-action {
246 | margin-bottom: 0px;
247 | }
248 | .zone-header {
249 | border-width: 0;
250 | background: transparent;
251 | box-shadow: none;
252 | margin: 6px 1px;
253 |
254 | &:focus {
255 | background: #FFF;
256 | border-width: 1px;
257 | margin: 5px 0;
258 | }
259 | }
260 |
261 | h3 {
262 | margin: 0;
263 | }
264 | }
265 |
266 | .add-layout-container,
267 | .add-zone-container {
268 | display: none;
269 | }
270 |
271 |
272 |
273 | // Override WordPress edit page styles
274 | #stream_box_stream {
275 | background: transparent;
276 | border: 0;
277 | @include prefixer(box-shadow, none);
278 |
279 | > .handlediv,
280 | > .hndle {
281 | display: none;
282 | }
283 |
284 | > .inside {
285 | margin: 0;
286 | padding: 0;
287 |
288 | > .sm-posts {
289 | border: 0;
290 | }
291 | }
292 | }
293 | .post-type-sm_stream {
294 | #title {
295 | position: relative;
296 | z-index: 1;
297 | }
298 | }
299 | .misc-pub-visibility,
300 | .misc-pub-curtime {
301 | display: none;
302 | }
303 | #misc-publishing-actions {
304 | padding-bottom: 15px;
305 | }
306 |
--------------------------------------------------------------------------------
/assets/stream_manager-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/stream_manager-readme_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Upstatement/stream-manager/3996657d7127146d11376418bf0cb7e279a03891/assets/stream_manager-readme_banner.png
--------------------------------------------------------------------------------
/bin/install-wp-tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [ $# -lt 3 ]; then
4 | echo "usage: $0 [db-host] [wp-version]"
5 | exit 1
6 | fi
7 |
8 | DB_NAME=$1
9 | DB_USER=$2
10 | DB_PASS=$3
11 | DB_HOST=${4-localhost}
12 | WP_VERSION=${5-latest}
13 |
14 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib}
15 | WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/}
16 |
17 | echo $WP_TESTS_DIR;
18 |
19 | download() {
20 | if [ `which curl` ]; then
21 | curl -s "$1" > "$2";
22 | elif [ `which wget` ]; then
23 | wget -nv -O "$2" "$1"
24 | fi
25 | }
26 |
27 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then
28 | WP_TESTS_TAG="tags/$WP_VERSION"
29 | else
30 | # http serves a single offer, whereas https serves multiple. we only want one
31 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
32 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
33 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
34 | if [[ -z "$LATEST_VERSION" ]]; then
35 | echo "Latest WordPress version could not be found"
36 | exit 1
37 | fi
38 | WP_TESTS_TAG="tags/$LATEST_VERSION"
39 | fi
40 |
41 | set -ex
42 |
43 | install_wp() {
44 |
45 | if [ -d $WP_CORE_DIR ]; then
46 | return;
47 | fi
48 |
49 | mkdir -p $WP_CORE_DIR
50 |
51 | if [ $WP_VERSION == 'latest' ]; then
52 | local ARCHIVE_NAME='latest'
53 | else
54 | local ARCHIVE_NAME="wordpress-$WP_VERSION"
55 | fi
56 |
57 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz
58 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR
59 |
60 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
61 | }
62 |
63 | install_test_suite() {
64 | # portable in-place argument for both GNU sed and Mac OSX sed
65 | if [[ $(uname -s) == 'Darwin' ]]; then
66 | local ioption='-i .bak'
67 | else
68 | local ioption='-i'
69 | fi
70 |
71 | # set up testing suite if it doesn't yet exist
72 | if [ ! -d $WP_TESTS_DIR ]; then
73 | # set up testing suite
74 | mkdir -p $WP_TESTS_DIR
75 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
76 | fi
77 |
78 | cd $WP_TESTS_DIR
79 |
80 | if [ ! -f wp-tests-config.php ]; then
81 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
82 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" "$WP_TESTS_DIR"/wp-tests-config.php
83 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
84 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
85 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
86 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
87 | fi
88 |
89 | }
90 |
91 | install_db() {
92 | # parse DB_HOST for port or socket references
93 | local PARTS=(${DB_HOST//\:/ })
94 | local DB_HOSTNAME=${PARTS[0]};
95 | local DB_SOCK_OR_PORT=${PARTS[1]};
96 | local EXTRA=""
97 |
98 | if ! [ -z $DB_HOSTNAME ] ; then
99 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
100 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
101 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then
102 | EXTRA=" --socket=$DB_SOCK_OR_PORT"
103 | elif ! [ -z $DB_HOSTNAME ] ; then
104 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
105 | fi
106 | fi
107 |
108 | # create database
109 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
110 | }
111 |
112 | install_wp
113 | install_test_suite
114 | install_db
--------------------------------------------------------------------------------
/bin/phpunit-no-cover.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | ../tests/
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "upstatement/stream-manager",
3 | "type": "wordpress-plugin",
4 | "description": "Plugin to manage streams of content in WordPress",
5 | "keywords": [
6 | "wordpress",
7 | "timber",
8 | "content management",
9 | "cms"
10 | ],
11 | "homepage": "http://github.com/upstatement/stream-manager",
12 | "license": "MIT",
13 | "authors": [
14 | {
15 | "name": "Jared Novack",
16 | "email": "jared@upstatement.com",
17 | "homepage": "http://upstatement.com"
18 | },
19 | {
20 | "name": "Chris Voll",
21 | "email": "chirs.voll@upstatement.com",
22 | "homepage": "http://upstatement.com"
23 | },
24 | {
25 | "name": "Gus Wrezerek",
26 | "email": "gus@upstatement.com",
27 | "homepage": "http://upstatement.com"
28 | },
29 | {
30 | "name": "Rowan Krishnan",
31 | "email": "rowan@upstatement.com",
32 | "homepage": "http://upstatement.com"
33 | },
34 | {
35 | "name": "Mike Swartz",
36 | "email": "mike@upstatement.com",
37 | "homepage": "http://upstatement.com"
38 | }
39 | ],
40 | "support": {
41 | "issues": "https://github.com/upstataement/stream-manager/issues",
42 | "wiki": "https://github.com/upstataement/stream-manager/wiki",
43 | "source": "https://github.com/upstataement/stream-manager"
44 | },
45 | "require": {
46 | "php": ">=5.3.0"
47 | },
48 | "require-dev": {
49 | "phpunit/phpunit": "5.7.*",
50 | "wpackagist-plugin/timber-library": "*",
51 | "composer/installers": "~1.0",
52 | "satooshi/php-coveralls": "dev-master"
53 | },
54 | "repositories": [
55 | {
56 | "type": "composer",
57 | "url": "http://wpackagist.org"
58 | }
59 | ],
60 | "config": {
61 | "secure-http": false
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "08e90ff5bd032f81abb3fdb54958af8c",
8 | "packages": [],
9 | "packages-dev": [
10 | {
11 | "name": "composer/installers",
12 | "version": "v1.2.0",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/composer/installers.git",
16 | "reference": "d78064c68299743e0161004f2de3a0204e33b804"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/composer/installers/zipball/d78064c68299743e0161004f2de3a0204e33b804",
21 | "reference": "d78064c68299743e0161004f2de3a0204e33b804",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "composer-plugin-api": "^1.0"
26 | },
27 | "replace": {
28 | "roundcube/plugin-installer": "*",
29 | "shama/baton": "*"
30 | },
31 | "require-dev": {
32 | "composer/composer": "1.0.*@dev",
33 | "phpunit/phpunit": "4.1.*"
34 | },
35 | "type": "composer-plugin",
36 | "extra": {
37 | "class": "Composer\\Installers\\Plugin",
38 | "branch-alias": {
39 | "dev-master": "1.0-dev"
40 | }
41 | },
42 | "autoload": {
43 | "psr-4": {
44 | "Composer\\Installers\\": "src/Composer/Installers"
45 | }
46 | },
47 | "notification-url": "https://packagist.org/downloads/",
48 | "license": [
49 | "MIT"
50 | ],
51 | "authors": [
52 | {
53 | "name": "Kyle Robinson Young",
54 | "email": "kyle@dontkry.com",
55 | "homepage": "https://github.com/shama"
56 | }
57 | ],
58 | "description": "A multi-framework Composer library installer",
59 | "homepage": "https://composer.github.io/installers/",
60 | "keywords": [
61 | "Craft",
62 | "Dolibarr",
63 | "Hurad",
64 | "ImageCMS",
65 | "MODX Evo",
66 | "Mautic",
67 | "OXID",
68 | "Plentymarkets",
69 | "RadPHP",
70 | "SMF",
71 | "Thelia",
72 | "WolfCMS",
73 | "agl",
74 | "aimeos",
75 | "annotatecms",
76 | "attogram",
77 | "bitrix",
78 | "cakephp",
79 | "chef",
80 | "cockpit",
81 | "codeigniter",
82 | "concrete5",
83 | "croogo",
84 | "dokuwiki",
85 | "drupal",
86 | "elgg",
87 | "expressionengine",
88 | "fuelphp",
89 | "grav",
90 | "installer",
91 | "joomla",
92 | "kohana",
93 | "laravel",
94 | "lithium",
95 | "magento",
96 | "mako",
97 | "mediawiki",
98 | "modulework",
99 | "moodle",
100 | "phpbb",
101 | "piwik",
102 | "ppi",
103 | "puppet",
104 | "reindex",
105 | "roundcube",
106 | "shopware",
107 | "silverstripe",
108 | "symfony",
109 | "typo3",
110 | "wordpress",
111 | "yawik",
112 | "zend",
113 | "zikula"
114 | ],
115 | "time": "2016-08-13T20:53:52+00:00"
116 | },
117 | {
118 | "name": "doctrine/instantiator",
119 | "version": "1.0.5",
120 | "source": {
121 | "type": "git",
122 | "url": "https://github.com/doctrine/instantiator.git",
123 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
124 | },
125 | "dist": {
126 | "type": "zip",
127 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
128 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
129 | "shasum": ""
130 | },
131 | "require": {
132 | "php": ">=5.3,<8.0-DEV"
133 | },
134 | "require-dev": {
135 | "athletic/athletic": "~0.1.8",
136 | "ext-pdo": "*",
137 | "ext-phar": "*",
138 | "phpunit/phpunit": "~4.0",
139 | "squizlabs/php_codesniffer": "~2.0"
140 | },
141 | "type": "library",
142 | "extra": {
143 | "branch-alias": {
144 | "dev-master": "1.0.x-dev"
145 | }
146 | },
147 | "autoload": {
148 | "psr-4": {
149 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
150 | }
151 | },
152 | "notification-url": "https://packagist.org/downloads/",
153 | "license": [
154 | "MIT"
155 | ],
156 | "authors": [
157 | {
158 | "name": "Marco Pivetta",
159 | "email": "ocramius@gmail.com",
160 | "homepage": "http://ocramius.github.com/"
161 | }
162 | ],
163 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
164 | "homepage": "https://github.com/doctrine/instantiator",
165 | "keywords": [
166 | "constructor",
167 | "instantiate"
168 | ],
169 | "time": "2015-06-14T21:17:01+00:00"
170 | },
171 | {
172 | "name": "guzzlehttp/guzzle",
173 | "version": "6.2.3",
174 | "source": {
175 | "type": "git",
176 | "url": "https://github.com/guzzle/guzzle.git",
177 | "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006"
178 | },
179 | "dist": {
180 | "type": "zip",
181 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8d6c6cc55186db87b7dc5009827429ba4e9dc006",
182 | "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006",
183 | "shasum": ""
184 | },
185 | "require": {
186 | "guzzlehttp/promises": "^1.0",
187 | "guzzlehttp/psr7": "^1.4",
188 | "php": ">=5.5"
189 | },
190 | "require-dev": {
191 | "ext-curl": "*",
192 | "phpunit/phpunit": "^4.0",
193 | "psr/log": "^1.0"
194 | },
195 | "type": "library",
196 | "extra": {
197 | "branch-alias": {
198 | "dev-master": "6.2-dev"
199 | }
200 | },
201 | "autoload": {
202 | "files": [
203 | "src/functions_include.php"
204 | ],
205 | "psr-4": {
206 | "GuzzleHttp\\": "src/"
207 | }
208 | },
209 | "notification-url": "https://packagist.org/downloads/",
210 | "license": [
211 | "MIT"
212 | ],
213 | "authors": [
214 | {
215 | "name": "Michael Dowling",
216 | "email": "mtdowling@gmail.com",
217 | "homepage": "https://github.com/mtdowling"
218 | }
219 | ],
220 | "description": "Guzzle is a PHP HTTP client library",
221 | "homepage": "http://guzzlephp.org/",
222 | "keywords": [
223 | "client",
224 | "curl",
225 | "framework",
226 | "http",
227 | "http client",
228 | "rest",
229 | "web service"
230 | ],
231 | "time": "2017-02-28T22:50:30+00:00"
232 | },
233 | {
234 | "name": "guzzlehttp/promises",
235 | "version": "v1.3.1",
236 | "source": {
237 | "type": "git",
238 | "url": "https://github.com/guzzle/promises.git",
239 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
240 | },
241 | "dist": {
242 | "type": "zip",
243 | "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
244 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
245 | "shasum": ""
246 | },
247 | "require": {
248 | "php": ">=5.5.0"
249 | },
250 | "require-dev": {
251 | "phpunit/phpunit": "^4.0"
252 | },
253 | "type": "library",
254 | "extra": {
255 | "branch-alias": {
256 | "dev-master": "1.4-dev"
257 | }
258 | },
259 | "autoload": {
260 | "psr-4": {
261 | "GuzzleHttp\\Promise\\": "src/"
262 | },
263 | "files": [
264 | "src/functions_include.php"
265 | ]
266 | },
267 | "notification-url": "https://packagist.org/downloads/",
268 | "license": [
269 | "MIT"
270 | ],
271 | "authors": [
272 | {
273 | "name": "Michael Dowling",
274 | "email": "mtdowling@gmail.com",
275 | "homepage": "https://github.com/mtdowling"
276 | }
277 | ],
278 | "description": "Guzzle promises library",
279 | "keywords": [
280 | "promise"
281 | ],
282 | "time": "2016-12-20T10:07:11+00:00"
283 | },
284 | {
285 | "name": "guzzlehttp/psr7",
286 | "version": "1.4.2",
287 | "source": {
288 | "type": "git",
289 | "url": "https://github.com/guzzle/psr7.git",
290 | "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
291 | },
292 | "dist": {
293 | "type": "zip",
294 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
295 | "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
296 | "shasum": ""
297 | },
298 | "require": {
299 | "php": ">=5.4.0",
300 | "psr/http-message": "~1.0"
301 | },
302 | "provide": {
303 | "psr/http-message-implementation": "1.0"
304 | },
305 | "require-dev": {
306 | "phpunit/phpunit": "~4.0"
307 | },
308 | "type": "library",
309 | "extra": {
310 | "branch-alias": {
311 | "dev-master": "1.4-dev"
312 | }
313 | },
314 | "autoload": {
315 | "psr-4": {
316 | "GuzzleHttp\\Psr7\\": "src/"
317 | },
318 | "files": [
319 | "src/functions_include.php"
320 | ]
321 | },
322 | "notification-url": "https://packagist.org/downloads/",
323 | "license": [
324 | "MIT"
325 | ],
326 | "authors": [
327 | {
328 | "name": "Michael Dowling",
329 | "email": "mtdowling@gmail.com",
330 | "homepage": "https://github.com/mtdowling"
331 | },
332 | {
333 | "name": "Tobias Schultze",
334 | "homepage": "https://github.com/Tobion"
335 | }
336 | ],
337 | "description": "PSR-7 message implementation that also provides common utility methods",
338 | "keywords": [
339 | "http",
340 | "message",
341 | "request",
342 | "response",
343 | "stream",
344 | "uri",
345 | "url"
346 | ],
347 | "time": "2017-03-20T17:10:46+00:00"
348 | },
349 | {
350 | "name": "myclabs/deep-copy",
351 | "version": "1.6.0",
352 | "source": {
353 | "type": "git",
354 | "url": "https://github.com/myclabs/DeepCopy.git",
355 | "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe"
356 | },
357 | "dist": {
358 | "type": "zip",
359 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5a5a9fc8025a08d8919be87d6884d5a92520cefe",
360 | "reference": "5a5a9fc8025a08d8919be87d6884d5a92520cefe",
361 | "shasum": ""
362 | },
363 | "require": {
364 | "php": ">=5.4.0"
365 | },
366 | "require-dev": {
367 | "doctrine/collections": "1.*",
368 | "phpunit/phpunit": "~4.1"
369 | },
370 | "type": "library",
371 | "autoload": {
372 | "psr-4": {
373 | "DeepCopy\\": "src/DeepCopy/"
374 | }
375 | },
376 | "notification-url": "https://packagist.org/downloads/",
377 | "license": [
378 | "MIT"
379 | ],
380 | "description": "Create deep copies (clones) of your objects",
381 | "homepage": "https://github.com/myclabs/DeepCopy",
382 | "keywords": [
383 | "clone",
384 | "copy",
385 | "duplicate",
386 | "object",
387 | "object graph"
388 | ],
389 | "time": "2017-01-26T22:05:40+00:00"
390 | },
391 | {
392 | "name": "phpdocumentor/reflection-common",
393 | "version": "1.0",
394 | "source": {
395 | "type": "git",
396 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
397 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c"
398 | },
399 | "dist": {
400 | "type": "zip",
401 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
402 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c",
403 | "shasum": ""
404 | },
405 | "require": {
406 | "php": ">=5.5"
407 | },
408 | "require-dev": {
409 | "phpunit/phpunit": "^4.6"
410 | },
411 | "type": "library",
412 | "extra": {
413 | "branch-alias": {
414 | "dev-master": "1.0.x-dev"
415 | }
416 | },
417 | "autoload": {
418 | "psr-4": {
419 | "phpDocumentor\\Reflection\\": [
420 | "src"
421 | ]
422 | }
423 | },
424 | "notification-url": "https://packagist.org/downloads/",
425 | "license": [
426 | "MIT"
427 | ],
428 | "authors": [
429 | {
430 | "name": "Jaap van Otterdijk",
431 | "email": "opensource@ijaap.nl"
432 | }
433 | ],
434 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
435 | "homepage": "http://www.phpdoc.org",
436 | "keywords": [
437 | "FQSEN",
438 | "phpDocumentor",
439 | "phpdoc",
440 | "reflection",
441 | "static analysis"
442 | ],
443 | "time": "2015-12-27T11:43:31+00:00"
444 | },
445 | {
446 | "name": "phpdocumentor/reflection-docblock",
447 | "version": "3.1.1",
448 | "source": {
449 | "type": "git",
450 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
451 | "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e"
452 | },
453 | "dist": {
454 | "type": "zip",
455 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e",
456 | "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e",
457 | "shasum": ""
458 | },
459 | "require": {
460 | "php": ">=5.5",
461 | "phpdocumentor/reflection-common": "^1.0@dev",
462 | "phpdocumentor/type-resolver": "^0.2.0",
463 | "webmozart/assert": "^1.0"
464 | },
465 | "require-dev": {
466 | "mockery/mockery": "^0.9.4",
467 | "phpunit/phpunit": "^4.4"
468 | },
469 | "type": "library",
470 | "autoload": {
471 | "psr-4": {
472 | "phpDocumentor\\Reflection\\": [
473 | "src/"
474 | ]
475 | }
476 | },
477 | "notification-url": "https://packagist.org/downloads/",
478 | "license": [
479 | "MIT"
480 | ],
481 | "authors": [
482 | {
483 | "name": "Mike van Riel",
484 | "email": "me@mikevanriel.com"
485 | }
486 | ],
487 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
488 | "time": "2016-09-30T07:12:33+00:00"
489 | },
490 | {
491 | "name": "phpdocumentor/type-resolver",
492 | "version": "0.2.1",
493 | "source": {
494 | "type": "git",
495 | "url": "https://github.com/phpDocumentor/TypeResolver.git",
496 | "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb"
497 | },
498 | "dist": {
499 | "type": "zip",
500 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
501 | "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb",
502 | "shasum": ""
503 | },
504 | "require": {
505 | "php": ">=5.5",
506 | "phpdocumentor/reflection-common": "^1.0"
507 | },
508 | "require-dev": {
509 | "mockery/mockery": "^0.9.4",
510 | "phpunit/phpunit": "^5.2||^4.8.24"
511 | },
512 | "type": "library",
513 | "extra": {
514 | "branch-alias": {
515 | "dev-master": "1.0.x-dev"
516 | }
517 | },
518 | "autoload": {
519 | "psr-4": {
520 | "phpDocumentor\\Reflection\\": [
521 | "src/"
522 | ]
523 | }
524 | },
525 | "notification-url": "https://packagist.org/downloads/",
526 | "license": [
527 | "MIT"
528 | ],
529 | "authors": [
530 | {
531 | "name": "Mike van Riel",
532 | "email": "me@mikevanriel.com"
533 | }
534 | ],
535 | "time": "2016-11-25T06:54:22+00:00"
536 | },
537 | {
538 | "name": "phpspec/prophecy",
539 | "version": "v1.7.0",
540 | "source": {
541 | "type": "git",
542 | "url": "https://github.com/phpspec/prophecy.git",
543 | "reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
544 | },
545 | "dist": {
546 | "type": "zip",
547 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
548 | "reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
549 | "shasum": ""
550 | },
551 | "require": {
552 | "doctrine/instantiator": "^1.0.2",
553 | "php": "^5.3|^7.0",
554 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
555 | "sebastian/comparator": "^1.1|^2.0",
556 | "sebastian/recursion-context": "^1.0|^2.0|^3.0"
557 | },
558 | "require-dev": {
559 | "phpspec/phpspec": "^2.5|^3.2",
560 | "phpunit/phpunit": "^4.8 || ^5.6.5"
561 | },
562 | "type": "library",
563 | "extra": {
564 | "branch-alias": {
565 | "dev-master": "1.6.x-dev"
566 | }
567 | },
568 | "autoload": {
569 | "psr-0": {
570 | "Prophecy\\": "src/"
571 | }
572 | },
573 | "notification-url": "https://packagist.org/downloads/",
574 | "license": [
575 | "MIT"
576 | ],
577 | "authors": [
578 | {
579 | "name": "Konstantin Kudryashov",
580 | "email": "ever.zet@gmail.com",
581 | "homepage": "http://everzet.com"
582 | },
583 | {
584 | "name": "Marcello Duarte",
585 | "email": "marcello.duarte@gmail.com"
586 | }
587 | ],
588 | "description": "Highly opinionated mocking framework for PHP 5.3+",
589 | "homepage": "https://github.com/phpspec/prophecy",
590 | "keywords": [
591 | "Double",
592 | "Dummy",
593 | "fake",
594 | "mock",
595 | "spy",
596 | "stub"
597 | ],
598 | "time": "2017-03-02T20:05:34+00:00"
599 | },
600 | {
601 | "name": "phpunit/php-code-coverage",
602 | "version": "4.0.8",
603 | "source": {
604 | "type": "git",
605 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
606 | "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d"
607 | },
608 | "dist": {
609 | "type": "zip",
610 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
611 | "reference": "ef7b2f56815df854e66ceaee8ebe9393ae36a40d",
612 | "shasum": ""
613 | },
614 | "require": {
615 | "ext-dom": "*",
616 | "ext-xmlwriter": "*",
617 | "php": "^5.6 || ^7.0",
618 | "phpunit/php-file-iterator": "^1.3",
619 | "phpunit/php-text-template": "^1.2",
620 | "phpunit/php-token-stream": "^1.4.2 || ^2.0",
621 | "sebastian/code-unit-reverse-lookup": "^1.0",
622 | "sebastian/environment": "^1.3.2 || ^2.0",
623 | "sebastian/version": "^1.0 || ^2.0"
624 | },
625 | "require-dev": {
626 | "ext-xdebug": "^2.1.4",
627 | "phpunit/phpunit": "^5.7"
628 | },
629 | "suggest": {
630 | "ext-xdebug": "^2.5.1"
631 | },
632 | "type": "library",
633 | "extra": {
634 | "branch-alias": {
635 | "dev-master": "4.0.x-dev"
636 | }
637 | },
638 | "autoload": {
639 | "classmap": [
640 | "src/"
641 | ]
642 | },
643 | "notification-url": "https://packagist.org/downloads/",
644 | "license": [
645 | "BSD-3-Clause"
646 | ],
647 | "authors": [
648 | {
649 | "name": "Sebastian Bergmann",
650 | "email": "sb@sebastian-bergmann.de",
651 | "role": "lead"
652 | }
653 | ],
654 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
655 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
656 | "keywords": [
657 | "coverage",
658 | "testing",
659 | "xunit"
660 | ],
661 | "time": "2017-04-02T07:44:40+00:00"
662 | },
663 | {
664 | "name": "phpunit/php-file-iterator",
665 | "version": "1.4.2",
666 | "source": {
667 | "type": "git",
668 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
669 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
670 | },
671 | "dist": {
672 | "type": "zip",
673 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
674 | "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
675 | "shasum": ""
676 | },
677 | "require": {
678 | "php": ">=5.3.3"
679 | },
680 | "type": "library",
681 | "extra": {
682 | "branch-alias": {
683 | "dev-master": "1.4.x-dev"
684 | }
685 | },
686 | "autoload": {
687 | "classmap": [
688 | "src/"
689 | ]
690 | },
691 | "notification-url": "https://packagist.org/downloads/",
692 | "license": [
693 | "BSD-3-Clause"
694 | ],
695 | "authors": [
696 | {
697 | "name": "Sebastian Bergmann",
698 | "email": "sb@sebastian-bergmann.de",
699 | "role": "lead"
700 | }
701 | ],
702 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
703 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
704 | "keywords": [
705 | "filesystem",
706 | "iterator"
707 | ],
708 | "time": "2016-10-03T07:40:28+00:00"
709 | },
710 | {
711 | "name": "phpunit/php-text-template",
712 | "version": "1.2.1",
713 | "source": {
714 | "type": "git",
715 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
716 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
717 | },
718 | "dist": {
719 | "type": "zip",
720 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
721 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
722 | "shasum": ""
723 | },
724 | "require": {
725 | "php": ">=5.3.3"
726 | },
727 | "type": "library",
728 | "autoload": {
729 | "classmap": [
730 | "src/"
731 | ]
732 | },
733 | "notification-url": "https://packagist.org/downloads/",
734 | "license": [
735 | "BSD-3-Clause"
736 | ],
737 | "authors": [
738 | {
739 | "name": "Sebastian Bergmann",
740 | "email": "sebastian@phpunit.de",
741 | "role": "lead"
742 | }
743 | ],
744 | "description": "Simple template engine.",
745 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
746 | "keywords": [
747 | "template"
748 | ],
749 | "time": "2015-06-21T13:50:34+00:00"
750 | },
751 | {
752 | "name": "phpunit/php-timer",
753 | "version": "1.0.9",
754 | "source": {
755 | "type": "git",
756 | "url": "https://github.com/sebastianbergmann/php-timer.git",
757 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
758 | },
759 | "dist": {
760 | "type": "zip",
761 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
762 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
763 | "shasum": ""
764 | },
765 | "require": {
766 | "php": "^5.3.3 || ^7.0"
767 | },
768 | "require-dev": {
769 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
770 | },
771 | "type": "library",
772 | "extra": {
773 | "branch-alias": {
774 | "dev-master": "1.0-dev"
775 | }
776 | },
777 | "autoload": {
778 | "classmap": [
779 | "src/"
780 | ]
781 | },
782 | "notification-url": "https://packagist.org/downloads/",
783 | "license": [
784 | "BSD-3-Clause"
785 | ],
786 | "authors": [
787 | {
788 | "name": "Sebastian Bergmann",
789 | "email": "sb@sebastian-bergmann.de",
790 | "role": "lead"
791 | }
792 | ],
793 | "description": "Utility class for timing",
794 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
795 | "keywords": [
796 | "timer"
797 | ],
798 | "time": "2017-02-26T11:10:40+00:00"
799 | },
800 | {
801 | "name": "phpunit/php-token-stream",
802 | "version": "1.4.11",
803 | "source": {
804 | "type": "git",
805 | "url": "https://github.com/sebastianbergmann/php-token-stream.git",
806 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7"
807 | },
808 | "dist": {
809 | "type": "zip",
810 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7",
811 | "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7",
812 | "shasum": ""
813 | },
814 | "require": {
815 | "ext-tokenizer": "*",
816 | "php": ">=5.3.3"
817 | },
818 | "require-dev": {
819 | "phpunit/phpunit": "~4.2"
820 | },
821 | "type": "library",
822 | "extra": {
823 | "branch-alias": {
824 | "dev-master": "1.4-dev"
825 | }
826 | },
827 | "autoload": {
828 | "classmap": [
829 | "src/"
830 | ]
831 | },
832 | "notification-url": "https://packagist.org/downloads/",
833 | "license": [
834 | "BSD-3-Clause"
835 | ],
836 | "authors": [
837 | {
838 | "name": "Sebastian Bergmann",
839 | "email": "sebastian@phpunit.de"
840 | }
841 | ],
842 | "description": "Wrapper around PHP's tokenizer extension.",
843 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
844 | "keywords": [
845 | "tokenizer"
846 | ],
847 | "time": "2017-02-27T10:12:30+00:00"
848 | },
849 | {
850 | "name": "phpunit/phpunit",
851 | "version": "5.7.13",
852 | "source": {
853 | "type": "git",
854 | "url": "https://github.com/sebastianbergmann/phpunit.git",
855 | "reference": "60ebeed87a35ea46fd7f7d8029df2d6f013ebb34"
856 | },
857 | "dist": {
858 | "type": "zip",
859 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/60ebeed87a35ea46fd7f7d8029df2d6f013ebb34",
860 | "reference": "60ebeed87a35ea46fd7f7d8029df2d6f013ebb34",
861 | "shasum": ""
862 | },
863 | "require": {
864 | "ext-dom": "*",
865 | "ext-json": "*",
866 | "ext-libxml": "*",
867 | "ext-mbstring": "*",
868 | "ext-xml": "*",
869 | "myclabs/deep-copy": "~1.3",
870 | "php": "^5.6 || ^7.0",
871 | "phpspec/prophecy": "^1.6.2",
872 | "phpunit/php-code-coverage": "^4.0.4",
873 | "phpunit/php-file-iterator": "~1.4",
874 | "phpunit/php-text-template": "~1.2",
875 | "phpunit/php-timer": "^1.0.6",
876 | "phpunit/phpunit-mock-objects": "^3.2",
877 | "sebastian/comparator": "^1.2.4",
878 | "sebastian/diff": "~1.2",
879 | "sebastian/environment": "^1.3.4 || ^2.0",
880 | "sebastian/exporter": "~2.0",
881 | "sebastian/global-state": "^1.1",
882 | "sebastian/object-enumerator": "~2.0",
883 | "sebastian/resource-operations": "~1.0",
884 | "sebastian/version": "~1.0|~2.0",
885 | "symfony/yaml": "~2.1|~3.0"
886 | },
887 | "conflict": {
888 | "phpdocumentor/reflection-docblock": "3.0.2"
889 | },
890 | "require-dev": {
891 | "ext-pdo": "*"
892 | },
893 | "suggest": {
894 | "ext-xdebug": "*",
895 | "phpunit/php-invoker": "~1.1"
896 | },
897 | "bin": [
898 | "phpunit"
899 | ],
900 | "type": "library",
901 | "extra": {
902 | "branch-alias": {
903 | "dev-master": "5.7.x-dev"
904 | }
905 | },
906 | "autoload": {
907 | "classmap": [
908 | "src/"
909 | ]
910 | },
911 | "notification-url": "https://packagist.org/downloads/",
912 | "license": [
913 | "BSD-3-Clause"
914 | ],
915 | "authors": [
916 | {
917 | "name": "Sebastian Bergmann",
918 | "email": "sebastian@phpunit.de",
919 | "role": "lead"
920 | }
921 | ],
922 | "description": "The PHP Unit Testing framework.",
923 | "homepage": "https://phpunit.de/",
924 | "keywords": [
925 | "phpunit",
926 | "testing",
927 | "xunit"
928 | ],
929 | "time": "2017-02-10T09:05:10+00:00"
930 | },
931 | {
932 | "name": "phpunit/phpunit-mock-objects",
933 | "version": "3.4.3",
934 | "source": {
935 | "type": "git",
936 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
937 | "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24"
938 | },
939 | "dist": {
940 | "type": "zip",
941 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
942 | "reference": "3ab72b65b39b491e0c011e2e09bb2206c2aa8e24",
943 | "shasum": ""
944 | },
945 | "require": {
946 | "doctrine/instantiator": "^1.0.2",
947 | "php": "^5.6 || ^7.0",
948 | "phpunit/php-text-template": "^1.2",
949 | "sebastian/exporter": "^1.2 || ^2.0"
950 | },
951 | "conflict": {
952 | "phpunit/phpunit": "<5.4.0"
953 | },
954 | "require-dev": {
955 | "phpunit/phpunit": "^5.4"
956 | },
957 | "suggest": {
958 | "ext-soap": "*"
959 | },
960 | "type": "library",
961 | "extra": {
962 | "branch-alias": {
963 | "dev-master": "3.2.x-dev"
964 | }
965 | },
966 | "autoload": {
967 | "classmap": [
968 | "src/"
969 | ]
970 | },
971 | "notification-url": "https://packagist.org/downloads/",
972 | "license": [
973 | "BSD-3-Clause"
974 | ],
975 | "authors": [
976 | {
977 | "name": "Sebastian Bergmann",
978 | "email": "sb@sebastian-bergmann.de",
979 | "role": "lead"
980 | }
981 | ],
982 | "description": "Mock Object library for PHPUnit",
983 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
984 | "keywords": [
985 | "mock",
986 | "xunit"
987 | ],
988 | "time": "2016-12-08T20:27:08+00:00"
989 | },
990 | {
991 | "name": "psr/http-message",
992 | "version": "1.0.1",
993 | "source": {
994 | "type": "git",
995 | "url": "https://github.com/php-fig/http-message.git",
996 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
997 | },
998 | "dist": {
999 | "type": "zip",
1000 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
1001 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
1002 | "shasum": ""
1003 | },
1004 | "require": {
1005 | "php": ">=5.3.0"
1006 | },
1007 | "type": "library",
1008 | "extra": {
1009 | "branch-alias": {
1010 | "dev-master": "1.0.x-dev"
1011 | }
1012 | },
1013 | "autoload": {
1014 | "psr-4": {
1015 | "Psr\\Http\\Message\\": "src/"
1016 | }
1017 | },
1018 | "notification-url": "https://packagist.org/downloads/",
1019 | "license": [
1020 | "MIT"
1021 | ],
1022 | "authors": [
1023 | {
1024 | "name": "PHP-FIG",
1025 | "homepage": "http://www.php-fig.org/"
1026 | }
1027 | ],
1028 | "description": "Common interface for HTTP messages",
1029 | "homepage": "https://github.com/php-fig/http-message",
1030 | "keywords": [
1031 | "http",
1032 | "http-message",
1033 | "psr",
1034 | "psr-7",
1035 | "request",
1036 | "response"
1037 | ],
1038 | "time": "2016-08-06T14:39:51+00:00"
1039 | },
1040 | {
1041 | "name": "psr/log",
1042 | "version": "1.0.2",
1043 | "source": {
1044 | "type": "git",
1045 | "url": "https://github.com/php-fig/log.git",
1046 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
1047 | },
1048 | "dist": {
1049 | "type": "zip",
1050 | "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
1051 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
1052 | "shasum": ""
1053 | },
1054 | "require": {
1055 | "php": ">=5.3.0"
1056 | },
1057 | "type": "library",
1058 | "extra": {
1059 | "branch-alias": {
1060 | "dev-master": "1.0.x-dev"
1061 | }
1062 | },
1063 | "autoload": {
1064 | "psr-4": {
1065 | "Psr\\Log\\": "Psr/Log/"
1066 | }
1067 | },
1068 | "notification-url": "https://packagist.org/downloads/",
1069 | "license": [
1070 | "MIT"
1071 | ],
1072 | "authors": [
1073 | {
1074 | "name": "PHP-FIG",
1075 | "homepage": "http://www.php-fig.org/"
1076 | }
1077 | ],
1078 | "description": "Common interface for logging libraries",
1079 | "homepage": "https://github.com/php-fig/log",
1080 | "keywords": [
1081 | "log",
1082 | "psr",
1083 | "psr-3"
1084 | ],
1085 | "time": "2016-10-10T12:19:37+00:00"
1086 | },
1087 | {
1088 | "name": "satooshi/php-coveralls",
1089 | "version": "dev-master",
1090 | "source": {
1091 | "type": "git",
1092 | "url": "https://github.com/satooshi/php-coveralls.git",
1093 | "reference": "d7285decc88dff59c5ff02c4b1052ab424ba7fa5"
1094 | },
1095 | "dist": {
1096 | "type": "zip",
1097 | "url": "https://api.github.com/repos/satooshi/php-coveralls/zipball/d7285decc88dff59c5ff02c4b1052ab424ba7fa5",
1098 | "reference": "d7285decc88dff59c5ff02c4b1052ab424ba7fa5",
1099 | "shasum": ""
1100 | },
1101 | "require": {
1102 | "ext-json": "*",
1103 | "ext-simplexml": "*",
1104 | "guzzlehttp/guzzle": "^6.0",
1105 | "php": "^5.5 || ^7.0",
1106 | "psr/log": "^1.0",
1107 | "symfony/config": "^2.1 || ^3.0",
1108 | "symfony/console": "^2.1 || ^3.0",
1109 | "symfony/stopwatch": "^2.0 || ^3.0",
1110 | "symfony/yaml": "^2.0 || ^3.0"
1111 | },
1112 | "suggest": {
1113 | "symfony/http-kernel": "Allows Symfony integration"
1114 | },
1115 | "bin": [
1116 | "bin/coveralls"
1117 | ],
1118 | "type": "library",
1119 | "extra": {
1120 | "branch-alias": {
1121 | "dev-master": "2.0-dev"
1122 | }
1123 | },
1124 | "autoload": {
1125 | "psr-4": {
1126 | "Satooshi\\": "src/Satooshi/"
1127 | }
1128 | },
1129 | "notification-url": "https://packagist.org/downloads/",
1130 | "license": [
1131 | "MIT"
1132 | ],
1133 | "authors": [
1134 | {
1135 | "name": "Kitamura Satoshi",
1136 | "email": "with.no.parachute@gmail.com",
1137 | "homepage": "https://www.facebook.com/satooshi.jp"
1138 | }
1139 | ],
1140 | "description": "PHP client library for Coveralls API",
1141 | "homepage": "https://github.com/satooshi/php-coveralls",
1142 | "keywords": [
1143 | "ci",
1144 | "coverage",
1145 | "github",
1146 | "test"
1147 | ],
1148 | "time": "2017-03-31 10:12:47"
1149 | },
1150 | {
1151 | "name": "sebastian/code-unit-reverse-lookup",
1152 | "version": "1.0.1",
1153 | "source": {
1154 | "type": "git",
1155 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
1156 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
1157 | },
1158 | "dist": {
1159 | "type": "zip",
1160 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
1161 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
1162 | "shasum": ""
1163 | },
1164 | "require": {
1165 | "php": "^5.6 || ^7.0"
1166 | },
1167 | "require-dev": {
1168 | "phpunit/phpunit": "^5.7 || ^6.0"
1169 | },
1170 | "type": "library",
1171 | "extra": {
1172 | "branch-alias": {
1173 | "dev-master": "1.0.x-dev"
1174 | }
1175 | },
1176 | "autoload": {
1177 | "classmap": [
1178 | "src/"
1179 | ]
1180 | },
1181 | "notification-url": "https://packagist.org/downloads/",
1182 | "license": [
1183 | "BSD-3-Clause"
1184 | ],
1185 | "authors": [
1186 | {
1187 | "name": "Sebastian Bergmann",
1188 | "email": "sebastian@phpunit.de"
1189 | }
1190 | ],
1191 | "description": "Looks up which function or method a line of code belongs to",
1192 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
1193 | "time": "2017-03-04T06:30:41+00:00"
1194 | },
1195 | {
1196 | "name": "sebastian/comparator",
1197 | "version": "1.2.4",
1198 | "source": {
1199 | "type": "git",
1200 | "url": "https://github.com/sebastianbergmann/comparator.git",
1201 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
1202 | },
1203 | "dist": {
1204 | "type": "zip",
1205 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
1206 | "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
1207 | "shasum": ""
1208 | },
1209 | "require": {
1210 | "php": ">=5.3.3",
1211 | "sebastian/diff": "~1.2",
1212 | "sebastian/exporter": "~1.2 || ~2.0"
1213 | },
1214 | "require-dev": {
1215 | "phpunit/phpunit": "~4.4"
1216 | },
1217 | "type": "library",
1218 | "extra": {
1219 | "branch-alias": {
1220 | "dev-master": "1.2.x-dev"
1221 | }
1222 | },
1223 | "autoload": {
1224 | "classmap": [
1225 | "src/"
1226 | ]
1227 | },
1228 | "notification-url": "https://packagist.org/downloads/",
1229 | "license": [
1230 | "BSD-3-Clause"
1231 | ],
1232 | "authors": [
1233 | {
1234 | "name": "Jeff Welch",
1235 | "email": "whatthejeff@gmail.com"
1236 | },
1237 | {
1238 | "name": "Volker Dusch",
1239 | "email": "github@wallbash.com"
1240 | },
1241 | {
1242 | "name": "Bernhard Schussek",
1243 | "email": "bschussek@2bepublished.at"
1244 | },
1245 | {
1246 | "name": "Sebastian Bergmann",
1247 | "email": "sebastian@phpunit.de"
1248 | }
1249 | ],
1250 | "description": "Provides the functionality to compare PHP values for equality",
1251 | "homepage": "http://www.github.com/sebastianbergmann/comparator",
1252 | "keywords": [
1253 | "comparator",
1254 | "compare",
1255 | "equality"
1256 | ],
1257 | "time": "2017-01-29T09:50:25+00:00"
1258 | },
1259 | {
1260 | "name": "sebastian/diff",
1261 | "version": "1.4.1",
1262 | "source": {
1263 | "type": "git",
1264 | "url": "https://github.com/sebastianbergmann/diff.git",
1265 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
1266 | },
1267 | "dist": {
1268 | "type": "zip",
1269 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
1270 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
1271 | "shasum": ""
1272 | },
1273 | "require": {
1274 | "php": ">=5.3.3"
1275 | },
1276 | "require-dev": {
1277 | "phpunit/phpunit": "~4.8"
1278 | },
1279 | "type": "library",
1280 | "extra": {
1281 | "branch-alias": {
1282 | "dev-master": "1.4-dev"
1283 | }
1284 | },
1285 | "autoload": {
1286 | "classmap": [
1287 | "src/"
1288 | ]
1289 | },
1290 | "notification-url": "https://packagist.org/downloads/",
1291 | "license": [
1292 | "BSD-3-Clause"
1293 | ],
1294 | "authors": [
1295 | {
1296 | "name": "Kore Nordmann",
1297 | "email": "mail@kore-nordmann.de"
1298 | },
1299 | {
1300 | "name": "Sebastian Bergmann",
1301 | "email": "sebastian@phpunit.de"
1302 | }
1303 | ],
1304 | "description": "Diff implementation",
1305 | "homepage": "https://github.com/sebastianbergmann/diff",
1306 | "keywords": [
1307 | "diff"
1308 | ],
1309 | "time": "2015-12-08T07:14:41+00:00"
1310 | },
1311 | {
1312 | "name": "sebastian/environment",
1313 | "version": "2.0.0",
1314 | "source": {
1315 | "type": "git",
1316 | "url": "https://github.com/sebastianbergmann/environment.git",
1317 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac"
1318 | },
1319 | "dist": {
1320 | "type": "zip",
1321 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
1322 | "reference": "5795ffe5dc5b02460c3e34222fee8cbe245d8fac",
1323 | "shasum": ""
1324 | },
1325 | "require": {
1326 | "php": "^5.6 || ^7.0"
1327 | },
1328 | "require-dev": {
1329 | "phpunit/phpunit": "^5.0"
1330 | },
1331 | "type": "library",
1332 | "extra": {
1333 | "branch-alias": {
1334 | "dev-master": "2.0.x-dev"
1335 | }
1336 | },
1337 | "autoload": {
1338 | "classmap": [
1339 | "src/"
1340 | ]
1341 | },
1342 | "notification-url": "https://packagist.org/downloads/",
1343 | "license": [
1344 | "BSD-3-Clause"
1345 | ],
1346 | "authors": [
1347 | {
1348 | "name": "Sebastian Bergmann",
1349 | "email": "sebastian@phpunit.de"
1350 | }
1351 | ],
1352 | "description": "Provides functionality to handle HHVM/PHP environments",
1353 | "homepage": "http://www.github.com/sebastianbergmann/environment",
1354 | "keywords": [
1355 | "Xdebug",
1356 | "environment",
1357 | "hhvm"
1358 | ],
1359 | "time": "2016-11-26T07:53:53+00:00"
1360 | },
1361 | {
1362 | "name": "sebastian/exporter",
1363 | "version": "2.0.0",
1364 | "source": {
1365 | "type": "git",
1366 | "url": "https://github.com/sebastianbergmann/exporter.git",
1367 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4"
1368 | },
1369 | "dist": {
1370 | "type": "zip",
1371 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
1372 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4",
1373 | "shasum": ""
1374 | },
1375 | "require": {
1376 | "php": ">=5.3.3",
1377 | "sebastian/recursion-context": "~2.0"
1378 | },
1379 | "require-dev": {
1380 | "ext-mbstring": "*",
1381 | "phpunit/phpunit": "~4.4"
1382 | },
1383 | "type": "library",
1384 | "extra": {
1385 | "branch-alias": {
1386 | "dev-master": "2.0.x-dev"
1387 | }
1388 | },
1389 | "autoload": {
1390 | "classmap": [
1391 | "src/"
1392 | ]
1393 | },
1394 | "notification-url": "https://packagist.org/downloads/",
1395 | "license": [
1396 | "BSD-3-Clause"
1397 | ],
1398 | "authors": [
1399 | {
1400 | "name": "Jeff Welch",
1401 | "email": "whatthejeff@gmail.com"
1402 | },
1403 | {
1404 | "name": "Volker Dusch",
1405 | "email": "github@wallbash.com"
1406 | },
1407 | {
1408 | "name": "Bernhard Schussek",
1409 | "email": "bschussek@2bepublished.at"
1410 | },
1411 | {
1412 | "name": "Sebastian Bergmann",
1413 | "email": "sebastian@phpunit.de"
1414 | },
1415 | {
1416 | "name": "Adam Harvey",
1417 | "email": "aharvey@php.net"
1418 | }
1419 | ],
1420 | "description": "Provides the functionality to export PHP variables for visualization",
1421 | "homepage": "http://www.github.com/sebastianbergmann/exporter",
1422 | "keywords": [
1423 | "export",
1424 | "exporter"
1425 | ],
1426 | "time": "2016-11-19T08:54:04+00:00"
1427 | },
1428 | {
1429 | "name": "sebastian/global-state",
1430 | "version": "1.1.1",
1431 | "source": {
1432 | "type": "git",
1433 | "url": "https://github.com/sebastianbergmann/global-state.git",
1434 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
1435 | },
1436 | "dist": {
1437 | "type": "zip",
1438 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
1439 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
1440 | "shasum": ""
1441 | },
1442 | "require": {
1443 | "php": ">=5.3.3"
1444 | },
1445 | "require-dev": {
1446 | "phpunit/phpunit": "~4.2"
1447 | },
1448 | "suggest": {
1449 | "ext-uopz": "*"
1450 | },
1451 | "type": "library",
1452 | "extra": {
1453 | "branch-alias": {
1454 | "dev-master": "1.0-dev"
1455 | }
1456 | },
1457 | "autoload": {
1458 | "classmap": [
1459 | "src/"
1460 | ]
1461 | },
1462 | "notification-url": "https://packagist.org/downloads/",
1463 | "license": [
1464 | "BSD-3-Clause"
1465 | ],
1466 | "authors": [
1467 | {
1468 | "name": "Sebastian Bergmann",
1469 | "email": "sebastian@phpunit.de"
1470 | }
1471 | ],
1472 | "description": "Snapshotting of global state",
1473 | "homepage": "http://www.github.com/sebastianbergmann/global-state",
1474 | "keywords": [
1475 | "global state"
1476 | ],
1477 | "time": "2015-10-12T03:26:01+00:00"
1478 | },
1479 | {
1480 | "name": "sebastian/object-enumerator",
1481 | "version": "2.0.1",
1482 | "source": {
1483 | "type": "git",
1484 | "url": "https://github.com/sebastianbergmann/object-enumerator.git",
1485 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7"
1486 | },
1487 | "dist": {
1488 | "type": "zip",
1489 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1311872ac850040a79c3c058bea3e22d0f09cbb7",
1490 | "reference": "1311872ac850040a79c3c058bea3e22d0f09cbb7",
1491 | "shasum": ""
1492 | },
1493 | "require": {
1494 | "php": ">=5.6",
1495 | "sebastian/recursion-context": "~2.0"
1496 | },
1497 | "require-dev": {
1498 | "phpunit/phpunit": "~5"
1499 | },
1500 | "type": "library",
1501 | "extra": {
1502 | "branch-alias": {
1503 | "dev-master": "2.0.x-dev"
1504 | }
1505 | },
1506 | "autoload": {
1507 | "classmap": [
1508 | "src/"
1509 | ]
1510 | },
1511 | "notification-url": "https://packagist.org/downloads/",
1512 | "license": [
1513 | "BSD-3-Clause"
1514 | ],
1515 | "authors": [
1516 | {
1517 | "name": "Sebastian Bergmann",
1518 | "email": "sebastian@phpunit.de"
1519 | }
1520 | ],
1521 | "description": "Traverses array structures and object graphs to enumerate all referenced objects",
1522 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
1523 | "time": "2017-02-18T15:18:39+00:00"
1524 | },
1525 | {
1526 | "name": "sebastian/recursion-context",
1527 | "version": "2.0.0",
1528 | "source": {
1529 | "type": "git",
1530 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1531 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a"
1532 | },
1533 | "dist": {
1534 | "type": "zip",
1535 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a",
1536 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a",
1537 | "shasum": ""
1538 | },
1539 | "require": {
1540 | "php": ">=5.3.3"
1541 | },
1542 | "require-dev": {
1543 | "phpunit/phpunit": "~4.4"
1544 | },
1545 | "type": "library",
1546 | "extra": {
1547 | "branch-alias": {
1548 | "dev-master": "2.0.x-dev"
1549 | }
1550 | },
1551 | "autoload": {
1552 | "classmap": [
1553 | "src/"
1554 | ]
1555 | },
1556 | "notification-url": "https://packagist.org/downloads/",
1557 | "license": [
1558 | "BSD-3-Clause"
1559 | ],
1560 | "authors": [
1561 | {
1562 | "name": "Jeff Welch",
1563 | "email": "whatthejeff@gmail.com"
1564 | },
1565 | {
1566 | "name": "Sebastian Bergmann",
1567 | "email": "sebastian@phpunit.de"
1568 | },
1569 | {
1570 | "name": "Adam Harvey",
1571 | "email": "aharvey@php.net"
1572 | }
1573 | ],
1574 | "description": "Provides functionality to recursively process PHP variables",
1575 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
1576 | "time": "2016-11-19T07:33:16+00:00"
1577 | },
1578 | {
1579 | "name": "sebastian/resource-operations",
1580 | "version": "1.0.0",
1581 | "source": {
1582 | "type": "git",
1583 | "url": "https://github.com/sebastianbergmann/resource-operations.git",
1584 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52"
1585 | },
1586 | "dist": {
1587 | "type": "zip",
1588 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
1589 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52",
1590 | "shasum": ""
1591 | },
1592 | "require": {
1593 | "php": ">=5.6.0"
1594 | },
1595 | "type": "library",
1596 | "extra": {
1597 | "branch-alias": {
1598 | "dev-master": "1.0.x-dev"
1599 | }
1600 | },
1601 | "autoload": {
1602 | "classmap": [
1603 | "src/"
1604 | ]
1605 | },
1606 | "notification-url": "https://packagist.org/downloads/",
1607 | "license": [
1608 | "BSD-3-Clause"
1609 | ],
1610 | "authors": [
1611 | {
1612 | "name": "Sebastian Bergmann",
1613 | "email": "sebastian@phpunit.de"
1614 | }
1615 | ],
1616 | "description": "Provides a list of PHP built-in functions that operate on resources",
1617 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
1618 | "time": "2015-07-28T20:34:47+00:00"
1619 | },
1620 | {
1621 | "name": "sebastian/version",
1622 | "version": "2.0.1",
1623 | "source": {
1624 | "type": "git",
1625 | "url": "https://github.com/sebastianbergmann/version.git",
1626 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
1627 | },
1628 | "dist": {
1629 | "type": "zip",
1630 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
1631 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
1632 | "shasum": ""
1633 | },
1634 | "require": {
1635 | "php": ">=5.6"
1636 | },
1637 | "type": "library",
1638 | "extra": {
1639 | "branch-alias": {
1640 | "dev-master": "2.0.x-dev"
1641 | }
1642 | },
1643 | "autoload": {
1644 | "classmap": [
1645 | "src/"
1646 | ]
1647 | },
1648 | "notification-url": "https://packagist.org/downloads/",
1649 | "license": [
1650 | "BSD-3-Clause"
1651 | ],
1652 | "authors": [
1653 | {
1654 | "name": "Sebastian Bergmann",
1655 | "email": "sebastian@phpunit.de",
1656 | "role": "lead"
1657 | }
1658 | ],
1659 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1660 | "homepage": "https://github.com/sebastianbergmann/version",
1661 | "time": "2016-10-03T07:35:21+00:00"
1662 | },
1663 | {
1664 | "name": "symfony/config",
1665 | "version": "v3.2.7",
1666 | "source": {
1667 | "type": "git",
1668 | "url": "https://github.com/symfony/config.git",
1669 | "reference": "8444bde28e3c2a33e571e6f180c2d78bfdc4480d"
1670 | },
1671 | "dist": {
1672 | "type": "zip",
1673 | "url": "https://api.github.com/repos/symfony/config/zipball/8444bde28e3c2a33e571e6f180c2d78bfdc4480d",
1674 | "reference": "8444bde28e3c2a33e571e6f180c2d78bfdc4480d",
1675 | "shasum": ""
1676 | },
1677 | "require": {
1678 | "php": ">=5.5.9",
1679 | "symfony/filesystem": "~2.8|~3.0"
1680 | },
1681 | "require-dev": {
1682 | "symfony/yaml": "~3.0"
1683 | },
1684 | "suggest": {
1685 | "symfony/yaml": "To use the yaml reference dumper"
1686 | },
1687 | "type": "library",
1688 | "extra": {
1689 | "branch-alias": {
1690 | "dev-master": "3.2-dev"
1691 | }
1692 | },
1693 | "autoload": {
1694 | "psr-4": {
1695 | "Symfony\\Component\\Config\\": ""
1696 | },
1697 | "exclude-from-classmap": [
1698 | "/Tests/"
1699 | ]
1700 | },
1701 | "notification-url": "https://packagist.org/downloads/",
1702 | "license": [
1703 | "MIT"
1704 | ],
1705 | "authors": [
1706 | {
1707 | "name": "Fabien Potencier",
1708 | "email": "fabien@symfony.com"
1709 | },
1710 | {
1711 | "name": "Symfony Community",
1712 | "homepage": "https://symfony.com/contributors"
1713 | }
1714 | ],
1715 | "description": "Symfony Config Component",
1716 | "homepage": "https://symfony.com",
1717 | "time": "2017-04-04T15:30:56+00:00"
1718 | },
1719 | {
1720 | "name": "symfony/console",
1721 | "version": "v3.2.7",
1722 | "source": {
1723 | "type": "git",
1724 | "url": "https://github.com/symfony/console.git",
1725 | "reference": "c30243cc51f726812be3551316b109a2f5deaf8d"
1726 | },
1727 | "dist": {
1728 | "type": "zip",
1729 | "url": "https://api.github.com/repos/symfony/console/zipball/c30243cc51f726812be3551316b109a2f5deaf8d",
1730 | "reference": "c30243cc51f726812be3551316b109a2f5deaf8d",
1731 | "shasum": ""
1732 | },
1733 | "require": {
1734 | "php": ">=5.5.9",
1735 | "symfony/debug": "~2.8|~3.0",
1736 | "symfony/polyfill-mbstring": "~1.0"
1737 | },
1738 | "require-dev": {
1739 | "psr/log": "~1.0",
1740 | "symfony/event-dispatcher": "~2.8|~3.0",
1741 | "symfony/filesystem": "~2.8|~3.0",
1742 | "symfony/process": "~2.8|~3.0"
1743 | },
1744 | "suggest": {
1745 | "psr/log": "For using the console logger",
1746 | "symfony/event-dispatcher": "",
1747 | "symfony/filesystem": "",
1748 | "symfony/process": ""
1749 | },
1750 | "type": "library",
1751 | "extra": {
1752 | "branch-alias": {
1753 | "dev-master": "3.2-dev"
1754 | }
1755 | },
1756 | "autoload": {
1757 | "psr-4": {
1758 | "Symfony\\Component\\Console\\": ""
1759 | },
1760 | "exclude-from-classmap": [
1761 | "/Tests/"
1762 | ]
1763 | },
1764 | "notification-url": "https://packagist.org/downloads/",
1765 | "license": [
1766 | "MIT"
1767 | ],
1768 | "authors": [
1769 | {
1770 | "name": "Fabien Potencier",
1771 | "email": "fabien@symfony.com"
1772 | },
1773 | {
1774 | "name": "Symfony Community",
1775 | "homepage": "https://symfony.com/contributors"
1776 | }
1777 | ],
1778 | "description": "Symfony Console Component",
1779 | "homepage": "https://symfony.com",
1780 | "time": "2017-04-04T14:33:42+00:00"
1781 | },
1782 | {
1783 | "name": "symfony/debug",
1784 | "version": "v3.2.7",
1785 | "source": {
1786 | "type": "git",
1787 | "url": "https://github.com/symfony/debug.git",
1788 | "reference": "56f613406446a4a0a031475cfd0a01751de22659"
1789 | },
1790 | "dist": {
1791 | "type": "zip",
1792 | "url": "https://api.github.com/repos/symfony/debug/zipball/56f613406446a4a0a031475cfd0a01751de22659",
1793 | "reference": "56f613406446a4a0a031475cfd0a01751de22659",
1794 | "shasum": ""
1795 | },
1796 | "require": {
1797 | "php": ">=5.5.9",
1798 | "psr/log": "~1.0"
1799 | },
1800 | "conflict": {
1801 | "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
1802 | },
1803 | "require-dev": {
1804 | "symfony/class-loader": "~2.8|~3.0",
1805 | "symfony/http-kernel": "~2.8|~3.0"
1806 | },
1807 | "type": "library",
1808 | "extra": {
1809 | "branch-alias": {
1810 | "dev-master": "3.2-dev"
1811 | }
1812 | },
1813 | "autoload": {
1814 | "psr-4": {
1815 | "Symfony\\Component\\Debug\\": ""
1816 | },
1817 | "exclude-from-classmap": [
1818 | "/Tests/"
1819 | ]
1820 | },
1821 | "notification-url": "https://packagist.org/downloads/",
1822 | "license": [
1823 | "MIT"
1824 | ],
1825 | "authors": [
1826 | {
1827 | "name": "Fabien Potencier",
1828 | "email": "fabien@symfony.com"
1829 | },
1830 | {
1831 | "name": "Symfony Community",
1832 | "homepage": "https://symfony.com/contributors"
1833 | }
1834 | ],
1835 | "description": "Symfony Debug Component",
1836 | "homepage": "https://symfony.com",
1837 | "time": "2017-03-28T21:38:24+00:00"
1838 | },
1839 | {
1840 | "name": "symfony/filesystem",
1841 | "version": "v3.2.7",
1842 | "source": {
1843 | "type": "git",
1844 | "url": "https://github.com/symfony/filesystem.git",
1845 | "reference": "64421e6479c4a8e60d790fb666bd520992861b66"
1846 | },
1847 | "dist": {
1848 | "type": "zip",
1849 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/64421e6479c4a8e60d790fb666bd520992861b66",
1850 | "reference": "64421e6479c4a8e60d790fb666bd520992861b66",
1851 | "shasum": ""
1852 | },
1853 | "require": {
1854 | "php": ">=5.5.9"
1855 | },
1856 | "type": "library",
1857 | "extra": {
1858 | "branch-alias": {
1859 | "dev-master": "3.2-dev"
1860 | }
1861 | },
1862 | "autoload": {
1863 | "psr-4": {
1864 | "Symfony\\Component\\Filesystem\\": ""
1865 | },
1866 | "exclude-from-classmap": [
1867 | "/Tests/"
1868 | ]
1869 | },
1870 | "notification-url": "https://packagist.org/downloads/",
1871 | "license": [
1872 | "MIT"
1873 | ],
1874 | "authors": [
1875 | {
1876 | "name": "Fabien Potencier",
1877 | "email": "fabien@symfony.com"
1878 | },
1879 | {
1880 | "name": "Symfony Community",
1881 | "homepage": "https://symfony.com/contributors"
1882 | }
1883 | ],
1884 | "description": "Symfony Filesystem Component",
1885 | "homepage": "https://symfony.com",
1886 | "time": "2017-03-26T15:47:15+00:00"
1887 | },
1888 | {
1889 | "name": "symfony/polyfill-mbstring",
1890 | "version": "v1.3.0",
1891 | "source": {
1892 | "type": "git",
1893 | "url": "https://github.com/symfony/polyfill-mbstring.git",
1894 | "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
1895 | },
1896 | "dist": {
1897 | "type": "zip",
1898 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
1899 | "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
1900 | "shasum": ""
1901 | },
1902 | "require": {
1903 | "php": ">=5.3.3"
1904 | },
1905 | "suggest": {
1906 | "ext-mbstring": "For best performance"
1907 | },
1908 | "type": "library",
1909 | "extra": {
1910 | "branch-alias": {
1911 | "dev-master": "1.3-dev"
1912 | }
1913 | },
1914 | "autoload": {
1915 | "psr-4": {
1916 | "Symfony\\Polyfill\\Mbstring\\": ""
1917 | },
1918 | "files": [
1919 | "bootstrap.php"
1920 | ]
1921 | },
1922 | "notification-url": "https://packagist.org/downloads/",
1923 | "license": [
1924 | "MIT"
1925 | ],
1926 | "authors": [
1927 | {
1928 | "name": "Nicolas Grekas",
1929 | "email": "p@tchwork.com"
1930 | },
1931 | {
1932 | "name": "Symfony Community",
1933 | "homepage": "https://symfony.com/contributors"
1934 | }
1935 | ],
1936 | "description": "Symfony polyfill for the Mbstring extension",
1937 | "homepage": "https://symfony.com",
1938 | "keywords": [
1939 | "compatibility",
1940 | "mbstring",
1941 | "polyfill",
1942 | "portable",
1943 | "shim"
1944 | ],
1945 | "time": "2016-11-14T01:06:16+00:00"
1946 | },
1947 | {
1948 | "name": "symfony/stopwatch",
1949 | "version": "v3.2.7",
1950 | "source": {
1951 | "type": "git",
1952 | "url": "https://github.com/symfony/stopwatch.git",
1953 | "reference": "c5ee0f8650c84b4d36a5f76b3b504233feaabf75"
1954 | },
1955 | "dist": {
1956 | "type": "zip",
1957 | "url": "https://api.github.com/repos/symfony/stopwatch/zipball/c5ee0f8650c84b4d36a5f76b3b504233feaabf75",
1958 | "reference": "c5ee0f8650c84b4d36a5f76b3b504233feaabf75",
1959 | "shasum": ""
1960 | },
1961 | "require": {
1962 | "php": ">=5.5.9"
1963 | },
1964 | "type": "library",
1965 | "extra": {
1966 | "branch-alias": {
1967 | "dev-master": "3.2-dev"
1968 | }
1969 | },
1970 | "autoload": {
1971 | "psr-4": {
1972 | "Symfony\\Component\\Stopwatch\\": ""
1973 | },
1974 | "exclude-from-classmap": [
1975 | "/Tests/"
1976 | ]
1977 | },
1978 | "notification-url": "https://packagist.org/downloads/",
1979 | "license": [
1980 | "MIT"
1981 | ],
1982 | "authors": [
1983 | {
1984 | "name": "Fabien Potencier",
1985 | "email": "fabien@symfony.com"
1986 | },
1987 | {
1988 | "name": "Symfony Community",
1989 | "homepage": "https://symfony.com/contributors"
1990 | }
1991 | ],
1992 | "description": "Symfony Stopwatch Component",
1993 | "homepage": "https://symfony.com",
1994 | "time": "2017-02-18T17:28:00+00:00"
1995 | },
1996 | {
1997 | "name": "symfony/yaml",
1998 | "version": "v3.2.7",
1999 | "source": {
2000 | "type": "git",
2001 | "url": "https://github.com/symfony/yaml.git",
2002 | "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621"
2003 | },
2004 | "dist": {
2005 | "type": "zip",
2006 | "url": "https://api.github.com/repos/symfony/yaml/zipball/62b4cdb99d52cb1ff253c465eb1532a80cebb621",
2007 | "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621",
2008 | "shasum": ""
2009 | },
2010 | "require": {
2011 | "php": ">=5.5.9"
2012 | },
2013 | "require-dev": {
2014 | "symfony/console": "~2.8|~3.0"
2015 | },
2016 | "suggest": {
2017 | "symfony/console": "For validating YAML files using the lint command"
2018 | },
2019 | "type": "library",
2020 | "extra": {
2021 | "branch-alias": {
2022 | "dev-master": "3.2-dev"
2023 | }
2024 | },
2025 | "autoload": {
2026 | "psr-4": {
2027 | "Symfony\\Component\\Yaml\\": ""
2028 | },
2029 | "exclude-from-classmap": [
2030 | "/Tests/"
2031 | ]
2032 | },
2033 | "notification-url": "https://packagist.org/downloads/",
2034 | "license": [
2035 | "MIT"
2036 | ],
2037 | "authors": [
2038 | {
2039 | "name": "Fabien Potencier",
2040 | "email": "fabien@symfony.com"
2041 | },
2042 | {
2043 | "name": "Symfony Community",
2044 | "homepage": "https://symfony.com/contributors"
2045 | }
2046 | ],
2047 | "description": "Symfony Yaml Component",
2048 | "homepage": "https://symfony.com",
2049 | "time": "2017-03-20T09:45:15+00:00"
2050 | },
2051 | {
2052 | "name": "webmozart/assert",
2053 | "version": "1.2.0",
2054 | "source": {
2055 | "type": "git",
2056 | "url": "https://github.com/webmozart/assert.git",
2057 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f"
2058 | },
2059 | "dist": {
2060 | "type": "zip",
2061 | "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f",
2062 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f",
2063 | "shasum": ""
2064 | },
2065 | "require": {
2066 | "php": "^5.3.3 || ^7.0"
2067 | },
2068 | "require-dev": {
2069 | "phpunit/phpunit": "^4.6",
2070 | "sebastian/version": "^1.0.1"
2071 | },
2072 | "type": "library",
2073 | "extra": {
2074 | "branch-alias": {
2075 | "dev-master": "1.3-dev"
2076 | }
2077 | },
2078 | "autoload": {
2079 | "psr-4": {
2080 | "Webmozart\\Assert\\": "src/"
2081 | }
2082 | },
2083 | "notification-url": "https://packagist.org/downloads/",
2084 | "license": [
2085 | "MIT"
2086 | ],
2087 | "authors": [
2088 | {
2089 | "name": "Bernhard Schussek",
2090 | "email": "bschussek@gmail.com"
2091 | }
2092 | ],
2093 | "description": "Assertions to validate method input/output with nice error messages.",
2094 | "keywords": [
2095 | "assert",
2096 | "check",
2097 | "validate"
2098 | ],
2099 | "time": "2016-11-23T20:04:58+00:00"
2100 | },
2101 | {
2102 | "name": "wpackagist-plugin/timber-library",
2103 | "version": "1.2.4",
2104 | "source": {
2105 | "type": "svn",
2106 | "url": "https://plugins.svn.wordpress.org/timber-library/",
2107 | "reference": "tags/1.2.4"
2108 | },
2109 | "dist": {
2110 | "type": "zip",
2111 | "url": "https://downloads.wordpress.org/plugin/timber-library.1.2.4.zip",
2112 | "reference": null,
2113 | "shasum": null
2114 | },
2115 | "require": {
2116 | "composer/installers": "~1.0"
2117 | },
2118 | "type": "wordpress-plugin",
2119 | "homepage": "https://wordpress.org/plugins/timber-library/"
2120 | }
2121 | ],
2122 | "aliases": [],
2123 | "minimum-stability": "stable",
2124 | "stability-flags": {
2125 | "satooshi/php-coveralls": 20
2126 | },
2127 | "prefer-stable": false,
2128 | "prefer-lowest": false,
2129 | "platform": {
2130 | "php": ">=5.3.0"
2131 | },
2132 | "platform-dev": []
2133 | }
2134 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const $ = require('gulp-load-plugins')();
3 | const config = require('./package.json');
4 |
5 | //////////////////////////////////////////////
6 | //
7 | // Front-end build tasks
8 | //
9 | ////////////////////////////////////////////////
10 |
11 | // ----------------------------------------
12 | //
13 | // Styles: Compile scss to css and minify.
14 | //
15 | // ----------------------------------------
16 |
17 | gulp.task('styles', function() {
18 | gulp.src('./assets/src/scss/*.scss')
19 | .pipe($.sass())
20 | .pipe($.cleanCss())
21 | .on('error', $.sass.logError)
22 | .pipe( gulp.dest('./assets/build/css/') );
23 | });
24 |
25 | // ----------------------------------------
26 | //
27 | // Scripts: Minify js.
28 | //
29 | // ----------------------------------------
30 | gulp.task('scripts', function() {
31 | return gulp.src('./assets/src/js/*.js')
32 | .pipe($.uglify())
33 | .pipe(gulp.dest('./assets/build/js/'));
34 | });
35 |
36 | // ----------------------------------------
37 | //
38 | // Watch/Default: Watch asset folders for
39 | // changes and rerun tasks as needed.
40 | //
41 | // ----------------------------------------
42 | gulp.task('watch', function() {
43 | gulp.watch('./assets/src/scss/**/*.scss', ['styles']);
44 | gulp.watch('./assets/src/js/**/*.js', ['scripts']);
45 | });
46 |
47 | gulp.task('default', ['styles', 'scripts', 'watch']);
48 |
49 | gulp.task('build', ['styles', 'scripts']);
50 |
51 | //////////////////////////////////////////////
52 | //
53 | // Automated deployment to wp.org plugin repo
54 | //
55 | //////////////////////////////////////////////
56 |
57 | const WP_REPO = 'http://plugins.svn.wordpress.org/stream-manager/';
58 | const ENTRY_FILE = 'stream-manager.php';
59 | const BUILD_FILES = [
60 | './assets/**/*',
61 | './includes/**/*',
62 | './README.txt',
63 | './README.md',
64 | './stream-manager.php'
65 | ];
66 |
67 | // ----------------------------------------
68 | //
69 | // Git: Checkout the master branch from git
70 | // and pull down the latest.
71 | //
72 | // ----------------------------------------
73 |
74 | gulp.task('git:checkout', function(done) {
75 | return $.git.checkout('master', function (err) {
76 | if (err) throw err;
77 | done();
78 | });
79 | });
80 |
81 | gulp.task('git:pull', function(done) {
82 | return $.git.pull('origin', 'master', function (err) {
83 | if (err) throw err;
84 | done();
85 | });
86 | });
87 |
88 | gulp.task('git', $.sequence('git:checkout', 'git:pull'));
89 |
90 | // ----------------------------------------
91 | //
92 | // Version: Update the version number and
93 | // commit and push the update
94 | //
95 | // ----------------------------------------
96 |
97 | gulp.task('version:plugin', function() {
98 | return gulp.src([ENTRY_FILE], { base: './' })
99 | .pipe($.replace(/(Version:\s+)([\d|.]+)/, '$1' + config.version))
100 | .pipe(gulp.dest('.'));
101 | });
102 |
103 | gulp.task('version:readme', function() {
104 | return gulp.src(['README.txt'], { base: './' })
105 | .pipe($.replace(/(Stable tag:\s+)([\d|.]+)/, '$1' + config.version))
106 | .pipe(gulp.dest('.'));
107 | });
108 |
109 | gulp.task('version:commit', function() {
110 | return gulp.src([ENTRY_FILE, 'README.txt'])
111 | .pipe($.git.commit('Updated version #'));
112 | });
113 |
114 | gulp.task('version:push', function() {
115 | return $.git.push('origin', 'master', function (err) {
116 | if (err) throw err;
117 | });
118 | })
119 |
120 | gulp.task('version', $.sequence(
121 | 'version:plugin',
122 | 'version:readme',
123 | 'version:commit',
124 | 'version:push'
125 | ));
126 |
127 | // ----------------------------------------
128 | //
129 | // Svn: Checkout the SVN repository from wp.org.
130 | // Clean out what's already in the tag
131 | // and trunk folders, copy our files into
132 | // the svn repo, and commit the updates.
133 | //
134 | // ----------------------------------------
135 |
136 | gulp.task('svn:checkout', function(done) {
137 | return $.svn.checkout(WP_REPO, 'svn', function(err){
138 | if(err) throw err;
139 | done();
140 | });
141 | });
142 |
143 | gulp.task('svn:delete', function() {
144 | return gulp.src(['./svn/tags/' + config.version + '/*', './svn/trunk/*'], {read:false})
145 | .pipe($.clean());
146 | });
147 |
148 | gulp.task('svn:copy', ['build'], function() {
149 | return gulp.src(BUILD_FILES, { base: './' })
150 | .pipe(gulp.dest('./svn/tags/' + config.version))
151 | .pipe(gulp.dest('./svn/trunk'));
152 | });
153 |
154 | gulp.task('svn:add', function(done){
155 | return $.svn.add('svn/*', {args: '--force'}, function(err){
156 | if(err) throw err;
157 | done();
158 | });
159 | });
160 |
161 | gulp.task('svn:commit', function(done){
162 | return $.svn.commit('Releasing tag ' + config.version, {cwd: './svn'}, function(err){
163 | if(err) throw err;
164 | done();
165 | });
166 | });
167 |
168 | gulp.task('svn:cleanup', function() {
169 | return gulp.src('./svn', {read:false})
170 | .pipe($.clean());
171 | });
172 |
173 | gulp.task('svn', $.sequence(
174 | 'svn:checkout',
175 | 'svn:delete',
176 | 'svn:copy',
177 | 'svn:add',
178 | 'svn:commit',
179 | 'svn:cleanup'
180 | ));
181 |
182 | // -------------------------------------
183 | //
184 | // Everything! Release the plugin to the wp plugin repo.
185 | //
186 | // -------------------------------------
187 |
188 | gulp.task('release', $.sequence('git', 'version', 'svn'));
189 |
--------------------------------------------------------------------------------
/includes/class-stream-manager-admin.php:
--------------------------------------------------------------------------------
1 | 'post',
39 | 'post_status' => 'publish',
40 | 'has_password' => false,
41 | 'ignore_sticky_posts' => true,
42 |
43 | 'posts_per_page' => 100,
44 | 'orderby' => 'post__in'
45 | );
46 |
47 | /**
48 | * Initialize the plugin
49 | *
50 | * @since 1.0.0
51 | */
52 | private function __construct() {
53 | $this->plugin = StreamManager::get_instance();
54 | $this->plugin_slug = $this->plugin->get_plugin_slug();
55 | $this->post_type_slug = $this->plugin->get_post_type_slug();
56 |
57 |
58 | // Admin Page Helpers
59 | // ------------------
60 |
61 | // Load admin styles and scripts
62 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
63 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );
64 |
65 | // Stream edit page metaboxes
66 | add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
67 |
68 | // Help text
69 | add_action( 'admin_head', array( $this, 'add_help_text' ), 10, 3 );
70 |
71 |
72 | // Stream Manipulation
73 | // -----------------
74 |
75 | // Saving Streams
76 | add_action( 'save_post', array( $this, 'save_stream' ) );
77 |
78 | // AJAX Helpers
79 | // ------------
80 |
81 | // Heartbeat
82 | add_filter( 'heartbeat_received', array( $this, 'ajax_heartbeat' ), 10, 3 );
83 |
84 | // Retrieve rendered post stubs AJAX
85 | add_filter( 'wp_ajax_sm_request', array( $this, 'ajax_retrieve_posts' ) );
86 |
87 | // Search posts AJAX
88 | add_filter( 'wp_ajax_sm_search', array( $this, 'ajax_search_posts' ) );
89 |
90 | // Retrieve posts for stream reload
91 | add_filter( 'wp_ajax_sm_reload', array( $this, 'ajax_retrieve_reload_posts' ) );
92 |
93 | add_filter( 'wp_terms_checklist_args', array( $this, 'stream_categories_helper' ), 10, 2 );
94 | }
95 |
96 | public static function is_active() {
97 | if ( function_exists( 'get_current_screen' ) ) {
98 | $current_screen = get_current_screen();
99 | if ( isset($current_screen) ) {
100 | return $current_screen->id == "sm_stream";
101 | }
102 | }
103 | }
104 |
105 | /**
106 | * Return an instance of this class.
107 | *
108 | * @since 1.0.0
109 | *
110 | * @return object A single instance of this class.
111 | */
112 | public static function get_instance() {
113 |
114 | // If the single instance hasn't been set, set it now.
115 | if ( null == self::$instance ) {
116 | self::$instance = new self;
117 | }
118 |
119 | return self::$instance;
120 | }
121 |
122 | /**
123 | * Register and enqueue admin-specific style sheet.
124 | *
125 | * @since 1.0.0
126 | *
127 | * @return null Return early if no settings page is registered.
128 | */
129 | public function enqueue_admin_styles() {
130 | if ( !$this->is_active() ) return;
131 |
132 | wp_enqueue_style(
133 | $this->plugin_slug .'-admin-styles',
134 | plugins_url( '../assets/build/css/style.css', __FILE__ ),
135 | array(),
136 | StreamManager::VERSION
137 | );
138 |
139 | /*
140 | * this limits the number of visible stream items via css
141 | * when one is deleted, the stubs "move up", so they one at a time become visible
142 | * https://github.com/Upstatement/stream-manager/issues/28
143 | */
144 | //+ 1 because css is greedy! (it encompasses the number, so we want the *next* element)
145 | $display_limit = (int) apply_filters( $this->plugin_slug . '/stub_display_limit', 15 ) + 1;
146 | $display_limit_css = ".sm-posts .content:nth-of-type(n+{$display_limit}){
147 | opacity:0.4;
148 | }";
149 | wp_add_inline_style( $this->plugin_slug .'-admin-styles', $display_limit_css );
150 | }
151 |
152 | /**
153 | * Register and enqueue admin-specific JavaScript.
154 | *
155 | * @since 1.0.0
156 | *
157 | * @return null Return early if no settings page is registered.
158 | */
159 | public function enqueue_admin_scripts() {
160 | if ( !$this->is_active() ) return;
161 |
162 | wp_enqueue_script(
163 | $this->plugin_slug . '-admin-script',
164 | plugins_url( '../assets/build/js/script.js', __FILE__ ),
165 | array( 'jquery', 'underscore' ),
166 | StreamManager::VERSION
167 | );
168 | }
169 |
170 |
171 | /**
172 | * Add meta boxes to Stream edit page
173 | *
174 | * @since 1.0.0
175 | */
176 | public function add_meta_boxes() {
177 | add_meta_box(
178 | 'stream_box_stream',
179 | 'Stream',
180 | array( $this, 'meta_box_stream' ),
181 | $this->post_type_slug,
182 | 'normal'
183 | );
184 | add_meta_box(
185 | 'stream_box_add',
186 | 'Add Post',
187 | array( $this, 'meta_box_add' ),
188 | $this->post_type_slug,
189 | 'side'
190 | );
191 | add_meta_box(
192 | 'stream_box_zones',
193 | 'Zones',
194 | array( $this, 'meta_box_zones' ),
195 | $this->post_type_slug,
196 | 'side'
197 | );
198 | }
199 |
200 |
201 | /**
202 | * Render Stream metabox
203 | *
204 | * @since 1.0.0
205 | *
206 | * @param object $post WordPress post object
207 | */
208 | public function meta_box_stream( $post ) {
209 | $stream_post = new TimberStream( $post->ID );
210 | $ids = array_keys( $stream_post->filter_stream('pinned', false) );
211 | $pinned = array_keys( $stream_post->filter_stream('pinned', true ) );
212 | $layouts = $stream_post->get('layouts');
213 | $layout = $layouts['layouts'][ $layouts['active'] ];
214 |
215 | Timber::render('views/stream.twig', array(
216 | 'posts' => $stream_post->get_posts( array( 'show_hidden' => true ) ),
217 | 'post_ids' => implode( ',', $ids ),
218 | 'post_pinned' => implode( ',', $pinned ),
219 | 'nonce' => wp_nonce_field('sm_nonce', 'sm_meta_box_nonce', true, false),
220 | 'layout' => $layout
221 | ));
222 | }
223 |
224 |
225 | /**
226 | * Render Post Add metabox
227 | *
228 | * @since 1.0.0
229 | *
230 | * @param object $post WordPress post object
231 | */
232 | public function meta_box_add( $post ) {
233 | Timber::render('views/add.twig');
234 | }
235 |
236 |
237 | /**
238 | * Render Layout metabox
239 | *
240 | * @since 1.0.0
241 | *
242 | * @param object $post WordPress post object
243 | */
244 | public function meta_box_zones( $post ) {
245 | $stream_post = new TimberStream( $post->ID );
246 | $layouts = $stream_post->get('layouts');
247 |
248 | $context = array(
249 | 'post' => $stream_post,
250 | 'layouts' => $layouts,
251 | 'layouts_json' => JSON_encode( $layouts )
252 | );
253 |
254 | Timber::render('views/zones.twig', array_merge(Timber::get_context(), $context));
255 | }
256 |
257 | /**
258 | * Save the stream metadata
259 | *
260 | * @since 1.0.0
261 | * @param integer $stream_id Stream Post ID
262 | * @todo Move Rules update to TimberStream::save_stream
263 | */
264 | public function save_stream( $stream_id, $apply_security_checks = true ) {
265 | // Bail if we're doing an auto save
266 | if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
267 |
268 | if ( $apply_security_checks ) {
269 |
270 | // if our nonce isn't there, or we can't verify it, bail
271 | if( !isset( $_POST['sm_meta_box_nonce'] ) || !wp_verify_nonce( $_POST['sm_meta_box_nonce'], 'sm_nonce' ) ) return;
272 |
273 | // if our current user can't edit this post, bail
274 | if( !current_user_can( 'edit_post', $stream_id ) ) return;
275 | }
276 |
277 | $stream = new TimberStream( $stream_id );
278 |
279 | // Sorting
280 | if ( isset( $_POST['sm_sort'] ) ) {
281 | $data = array();
282 |
283 | foreach ( $_POST['sm_sort'] as $i => $post_id ) {
284 | $data[] = array(
285 | 'id' => $post_id,
286 | 'pinned' => isset($_POST['sm_pin'][$post_id])
287 | );
288 | }
289 |
290 | $stream->set('stream', $data);
291 | $stream->repopulate_stream();
292 | }
293 |
294 | // Layouts
295 | if ( isset( $_POST['sm_layouts'] ) ) {
296 | $stream->set('layouts', JSON_decode( stripslashes($_POST['sm_layouts']), true ) );
297 | }
298 |
299 | // Save the stream, and prevent and infinite loop
300 | remove_action( 'save_post', array( $this, 'save_stream' ) );
301 | $stream->save_stream();
302 | add_action( 'save_post', array( $this, 'save_stream' ) );
303 | }
304 |
305 |
306 | /**
307 | * Add help text to stream edit page
308 | *
309 | * @since 1.0.0
310 | */
311 | function add_help_text() {
312 | $screen = get_current_screen();
313 |
314 | // Return early if we're not on the book post type.
315 | if ( 'sm_stream' != $screen->id ) return;
316 |
317 | // Setup help tab args.
318 | $tabs = array(
319 | array(
320 | 'id' => 'sm_stream_1',
321 | 'title' => 'About Streams',
322 | 'content' => implode("\n", array(
323 | 'About Streams ',
324 | 'Streams are feeds of content, updated automatically as new posts are created.',
325 | 'Any changes made to the stream from this page will be reflected where the stream is output in the theme, on the homepage or elsewhere.
'
326 | ))
327 | ),
328 | array(
329 | 'id' => 'sm_stream_2',
330 | 'title' => 'How to Use',
331 | 'content' => implode("\n", array(
332 | 'How to Use ',
333 | 'Reordering: ',
334 | 'Drag and drop posts into the desired order, then click the "Update" button.
',
335 | 'Adding a Post: ',
336 | 'Type the name of the post in the "Add Post" box and select the intended post when it appears in the dropdown.
',
337 | 'Removing a Post: ',
338 | 'To remove a post from the stream, hover over the post and click the x in the upper right. Note that the post will not be deleted entirely; instead, it will be removed from its current position and appended to the bottom of the stream.',
339 | '
For more detailed instructions, consult the User Guide in the Github project readme.
'
340 | ))
341 | ),
342 | array(
343 | 'id' => 'sm_stream_3',
344 | 'title' => 'Use in Theme',
345 | 'content' => implode("\n", array(
346 | 'Use in Theme ',
347 | '',
348 | '
$context[\'stream\'] = new TimberStream(' . get_the_ID() . '); ',
349 | '',
350 | 'In your view file (twig):
',
351 | '
{% for post in stream.get_posts %}',
352 | ' {{ post.title }}',
353 | '{% endfor %} '
354 | ))
355 | )
356 | );
357 |
358 | // Add the help tab.
359 | foreach ( $tabs as $tab ) {
360 | $screen->add_help_tab( $tab );
361 | }
362 | }
363 |
364 | public function stream_categories_helper( $args, $post_id ) {
365 | if ( $this->is_active() ) {
366 | $stream = new TimberStream( $post_id );
367 | if ( isset($stream->sm_rules['category']) ) {
368 | $args['selected_cats'] = $stream->sm_rules['category'];
369 | }
370 | }
371 | return $args;
372 | }
373 |
374 |
375 | /**
376 | * Respond to admin heartbeat with stream IDs
377 | *
378 | * @since 1.0.0
379 | *
380 | * @param array $response default WordPress heartbeat response
381 | * @param array $data data included with WordPress heartbeat request
382 | * @param string $screen_id admin screen slug
383 | *
384 | * @return array WordPress heartbeat response
385 | */
386 | public function ajax_heartbeat( $response, $data, $screen_id ) {
387 |
388 | if ( $screen_id == 'sm_stream' && isset( $data['wp-refresh-post-lock'] ) ) {
389 | $stream_post = new TimberStream( $data['wp-refresh-post-lock']['post_id'] );
390 | $ids = array_keys( $stream_post->filter_stream('pinned', false) );
391 | $response['sm_ids'] = implode( ',', $ids );
392 |
393 | $pinned = array_keys( $stream_post->filter_stream('pinned', true) );
394 | $response['sm_pinned'] = implode( ',', $pinned );
395 | }
396 |
397 | return $response;
398 | }
399 |
400 |
401 | /**
402 | * Retrieve rendered post stubs
403 | *
404 | * @since 1.0.0
405 | *
406 | * @param array $request AJAX request (uses $_POST instead)
407 | */
408 | public function ajax_retrieve_posts( $request ) {
409 | if ( !isset( $_POST['queue'] ) ) $this->ajax_respond( 'error' );
410 | $output = StreamManagerAjaxHelper::retrieve_posts($_POST['queue']);
411 |
412 | $this->ajax_respond( 'success', $output );
413 | }
414 |
415 | /**
416 | * Retrieve search results
417 | *
418 | * @since 1.0.0
419 | *
420 | * @param array $request AJAX request (uses $_POST instead)
421 | */
422 | public function ajax_search_posts( $request ) {
423 | if ( !isset( $_POST['query'] ) || !isset( $_POST['stream_id'] ) ) $this->ajax_respond( 'error' );
424 | $output = StreamManagerAjaxHelper::search_posts($_POST['query'], $_POST['stream_id']);
425 |
426 | $this->ajax_respond( 'success', $output );
427 | }
428 |
429 | /**
430 | * Send AJAX response
431 | *
432 | * @since 1.0.0
433 | *
434 | * @param string $status AJAX status (error|success)
435 | * @param array $data data with which to respond
436 | */
437 | public function ajax_respond( $status = 'error', $data = array() ) {
438 | echo( json_encode( array(
439 | 'status' => $status,
440 | 'data' => $data
441 | )));
442 | die();
443 | }
444 |
445 |
446 | }
447 |
--------------------------------------------------------------------------------
/includes/class-stream-manager-ajax-helper.php:
--------------------------------------------------------------------------------
1 | $item) {
32 | $post = new TimberPost( $item['id'] );
33 | if ( !$post ) continue;
34 | $post->pinned = false;
35 | $output[ $item['id'] ] = array(
36 | 'position' => $item['position'],
37 | 'object' => Timber::compile('views/stub.twig', array(
38 | 'post' => $post
39 | ))
40 | );
41 | }
42 | return $output;
43 | }
44 |
45 | /**
46 | * Searches posts for 'Add New' autocomplete
47 | *
48 | * @since 1.0.0
49 | *
50 | * @param string $query search term
51 | * @param int $stream_id post id of current stream
52 | *
53 | * @return array $output posts w/ ids, date, title, human time diff
54 | */
55 | public static function search_posts( $query, $stream_id ) {
56 | $defaults = array(
57 | 's' => $query,
58 | 'post_type' => 'post',
59 | 'post_status' => 'publish',
60 | 'posts_per_page' => 10
61 | );
62 | $stream = new TimberStream($stream_id);
63 | $args = array_merge( $defaults, $stream->get( 'query' ) );
64 |
65 | $posts = Timber::get_posts( $args );
66 |
67 | $output = array();
68 |
69 | foreach ( $posts as $post ) {
70 | $output[] = array(
71 | 'id' => $post->ID,
72 | 'title' => $post->title,
73 | 'date' => $post->post_date,
74 | 'human_date' => human_time_diff( strtotime( $post->post_date ) )
75 | );
76 | }
77 | return $output;
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/includes/class-stream-manager-api.php:
--------------------------------------------------------------------------------
1 | 'sm_stream', 'name' => $slug ) );
27 | if ( count( $posts ) ) {
28 | return true;
29 | }
30 | return false;
31 | }
32 |
33 | /**
34 | * Insert a new stream, with the option to pass a wp_query array to filter the stream.
35 | * Returns false if the stream already exists.
36 | *
37 | * @param string $slug
38 | * @param string $title
39 | * @param array $query_array wp_query object
40 | *
41 | * @return int $pid ID of new stream
42 | */
43 | static function insert_stream( $slug, $title = NULL, $query_array = NULL ) {
44 |
45 | if( self::stream_exists( $slug ) ) {
46 | return false;
47 | }
48 |
49 | $post_title = $title?: $slug;
50 | $args = array(
51 | 'post_type' => 'sm_stream',
52 | 'post_name' => $slug,
53 | 'post_title' => $post_title,
54 | 'post_status' => 'publish'
55 | );
56 | $pid = wp_insert_post($args);
57 |
58 | if ( $query_array ) {
59 | add_filter('stream-manager/options/'.$slug, function($defaults) use ($query_array) {
60 | $defaults['query'] = array_merge( $defaults['query'], $query_array );
61 | return $defaults;
62 | });
63 | }
64 |
65 | return $pid;
66 | }
67 |
68 | /**
69 | * Delete a stream by slug
70 | *
71 | * @param string $slug
72 | * @param bool $force_delete bypass trash and force deletion
73 | *
74 | * @return int $deleted ID of deleted stream
75 | */
76 | static function delete_stream( $slug, $force_delete = true ) {
77 | $posts = get_posts( array( 'post_type' => 'sm_stream', 'name' => $slug ) );
78 | if( $posts ) {
79 | $post = $posts[0];
80 | $deleted = wp_delete_post( $post->ID, $force_delete );
81 | return $deleted->ID;
82 | } else {
83 | return false;
84 | }
85 | }
86 | }
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/includes/class-stream-manager-manager.php:
--------------------------------------------------------------------------------
1 | plugin = StreamManager::get_instance();
40 | $this->plugin_slug = $this->plugin->get_plugin_slug();
41 | $this->post_type_slug = $this->plugin->get_post_type_slug();
42 |
43 | // Saving Posts (= updating streams)
44 | add_action( 'transition_post_status', array( $this, 'on_save_post' ), 10, 3 );
45 | add_action( 'publish_future_post', function($post_id) {
46 | $post = get_post($post_id);
47 | $this->on_save_post('publish', 'future', $post);
48 | }, 10, 1);
49 |
50 | }
51 |
52 | /**
53 | * Return an instance of this class.
54 | *
55 | * @since 1.1.0
56 | *
57 | * @return object A single instance of this class.
58 | */
59 | public static function get_instance() {
60 |
61 | // If the single instance hasn't been set, set it now.
62 | if ( null == self::$instance ) {
63 | self::$instance = new self;
64 | }
65 |
66 | return self::$instance;
67 | }
68 |
69 |
70 | /**
71 | * Update streams whenever any post status is changed
72 | *
73 | * @since 1.1.0
74 | *
75 | * @param string $new new post status
76 | * @param string $old old post status
77 | * @param object $post WordPress post object
78 | */
79 | public function on_save_post( $new, $old, $post ) {
80 | if ( $post->post_type == 'sm_stream' ) return;
81 |
82 | if ( $old == 'publish' && $new != 'publish' ) {
83 | // Remove from streams
84 | $streams = $this->plugin->get_streams();
85 | foreach ( $streams as $stream ) {
86 | $stream->remove_post( $post->ID );
87 | }
88 | }
89 |
90 | if ( $old != 'publish' && $new == 'publish' ) {
91 | //seems weird, but it's necessary for ACF
92 | //and potentially other plugins
93 | //we can't be sure what actions have been added
94 | //so checking for infinite loop-type bugs isn't possible
95 | do_action('save_post', $post->ID, $post, true );
96 | remove_all_actions('save_post');
97 | // Add to streams
98 | $streams = $this->plugin->get_streams();
99 | foreach ( $streams as $stream ) {
100 | $stream->insert_post( $post->ID );
101 | }
102 | }
103 | }
104 |
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/includes/class-stream-manager-utilities.php:
--------------------------------------------------------------------------------
1 | term_id;
42 | }
43 |
44 | return $return_objects ? $terms : $output;
45 | }
46 |
47 | public static function build_tax_query( $taxonomies ) {
48 | $output = array('relation' => 'OR');
49 | foreach ( $taxonomies as $taxonomy => $terms ) {
50 | if ( !$terms ) continue;
51 |
52 | $terms = is_array($terms) ? $terms : self::parse_terms( $taxonomy, $terms );
53 | foreach ( $terms as $i => $term ) {
54 | if ( empty( $term ) ) unset( $terms[$i] );
55 | }
56 |
57 | if ( !empty($terms) ) {
58 | $output[] = array(
59 | 'taxonomy' => $taxonomy,
60 | 'field' => 'term_id',
61 | 'terms' => $terms
62 | );
63 | }
64 | }
65 | return $output;
66 | }
67 |
68 | }
--------------------------------------------------------------------------------
/includes/class-stream-manager.php:
--------------------------------------------------------------------------------
1 | plugin_slug;
90 | }
91 |
92 | /**
93 | * Return the post type slug.
94 | *
95 | * @since 1.0.0
96 | *
97 | * @return Post type slug variable.
98 | */
99 | public function get_post_type_slug() {
100 | return $this->post_type_slug;
101 | }
102 |
103 | /**
104 | * Return an instance of this class.
105 | *
106 | * @since 1.0.0
107 | *
108 | * @return object A single instance of this class.
109 | */
110 | public static function get_instance() {
111 |
112 | // If the single instance hasn't been set, set it now.
113 | if ( null == self::$instance ) {
114 | self::$instance = new self;
115 | }
116 |
117 | return self::$instance;
118 | }
119 |
120 | /**
121 | * Ensure that Timber is loaded. Depending on the order that the
122 | * plugins are activated, Timber may be loaded after the Stream
123 | * Manager and needs to be loaded manually.
124 | *
125 | * @since 1.0.0
126 | *
127 | * @return boolean True if dependencies are met, false if not
128 | */
129 | public function check_dependencies() {
130 | return class_exists('Timber');
131 | }
132 |
133 | /**
134 | * Create the Stream post type, add to admin
135 | *
136 | * @since 1.0.0
137 | */
138 | public function define_post_types() {
139 | $labels = array(
140 | 'name' => 'Streams',
141 | 'singular_name' => 'Stream',
142 | 'menu_name' => 'Streams',
143 | 'parent_item_colon' => 'Parent Stream',
144 | 'all_items' => 'Streams',
145 | 'view_item' => 'View Stream',
146 | 'add_new_item' => 'Add New Stream',
147 | 'add_new' => 'Add New',
148 | 'edit_item' => 'Edit Stream',
149 | 'update_item' => 'Update Stream',
150 | 'search_items' => 'Search Stream',
151 | 'not_found' => 'Not found',
152 | 'not_found_in_trash' => 'Not found in Trash',
153 | );
154 | $args = array(
155 | 'label' => $this->post_type_slug,
156 | 'description' => 'Stream',
157 | 'labels' => $labels,
158 | 'supports' => array( 'title' ),
159 | 'hierarchical' => false,
160 | 'public' => false,
161 | 'show_ui' => true,
162 | 'show_in_menu' => true,
163 | 'show_in_nav_menus' => false,
164 | 'show_in_admin_bar' => false,
165 | 'menu_position' => 5,
166 | 'menu_icon' => 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiI+PGRlZnM+PHN0eWxlPi5he2ZpbGw6I2ZmZjt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPnN0cmVhbV9tYW5hZ2VyLWljb25fMTZ4MTYtd2hpdGU8L3RpdGxlPjxwYXRoIGNsYXNzPSJhIiBkPSJNOSwxMGExLDEsMCwxLDEtMiwwQTMuMjUsMy4yNSwwLDAsMSw4LDguMzMsMy4zLDMuMywwLDAsMSw5LDEwWiIvPjxwYXRoIGNsYXNzPSJhIiBkPSJNOCw0LjE3UzQuNSw3LjQ1LDQuNSwxMGEzLjUsMy41LDAsMSwwLDcsMEMxMS41LDcuNDYsOCw0LjE3LDgsNC4xN1pNOCwxMmEyLDIsMCwwLDEtMi0yQzYsOC41NSw4LDYuNjcsOCw2LjY3UzEwLDguNTUsMTAsMTBBMiwyLDAsMCwxLDgsMTJaIi8+PHBhdGggY2xhc3M9ImEiIGQ9Ik04LDBTMiw1LjYzLDIsMTBhNiw2LDAsMSwwLDEyLDBDMTQsNS42NSw4LDAsOCwwWk04LDE0LjVBNC41LDQuNSwwLDAsMSwzLjUsMTBDMy41LDYuNzMsOCwyLjUsOCwyLjVzNC41LDQuMjQsNC41LDcuNUE0LjQ5LDQuNDksMCwwLDEsOCwxNC41WiIvPjwvc3ZnPg==',
167 | 'can_export' => true,
168 | 'has_archive' => false,
169 | 'exclude_from_search' => true,
170 | 'publicly_queryable' => true,
171 | 'capability_type' => 'post',
172 | );
173 | register_post_type( $this->post_type_slug, $args );
174 | }
175 |
176 | /**
177 | * Add Stream post type messages.
178 | *
179 | * @since 1.0.0
180 | */
181 | function define_post_type_messages($messages) {
182 | global $post, $post_ID;
183 | $post_type = get_post_type( $post_ID );
184 |
185 | $obj = get_post_type_object($post_type);
186 | $singular = $obj->labels->singular_name;
187 |
188 | $messages[$this->post_type_slug] = array(
189 | 0 => '',
190 | 1 => __($singular . ' updated.'),
191 | 2 => __('Custom field updated.'),
192 | 3 => __('Custom field deleted.'),
193 | 4 => __($singular . ' updated.'),
194 | 5 => isset($_GET['revision']) ? sprintf( __($singular.' restored to revision from %s'), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
195 | 6 => __($singular . ' published.'),
196 | 7 => __('Page saved.'),
197 | 8 => __($singular . ' submitted.'),
198 | 9 => sprintf( __($singular.' scheduled for: %1$s .'), date_i18n( __( 'M j, Y @ G:i' ) ) ),
199 | 10 => __($singular . ' draft updated.'),
200 | );
201 | return $messages;
202 | }
203 |
204 | /**
205 | * Retrieve all streams from the database.
206 | *
207 | * @since 1.0.0
208 | *
209 | * @return array Collection of TimberStream objects
210 | */
211 | public function get_streams( $query = array(), $PostClass = 'TimberStream' ) {
212 | if ($this->streams) return $this->streams;
213 | $query = array_merge( $query, array(
214 | 'post_type' => $this->post_type_slug,
215 | 'nopaging' => true
216 | ));
217 | return $this->streams = Timber::get_posts( $query, $PostClass );
218 | }
219 |
220 | function add_timber_filters_functions($twig) {
221 | $twig->addFunction(new Twig_SimpleFunction('TimberStream', function ($pid, $StreamClass = 'TimberStream') {
222 | if (is_array($pid) && !TimberHelper::is_array_assoc($pid)) {
223 | foreach ($pid as &$p) {
224 | $p = new $StreamClass($p);
225 | }
226 | return $pid;
227 | }
228 | return new $StreamClass($pid);
229 | }));
230 | return $twig;
231 | }
232 |
233 | }
234 |
--------------------------------------------------------------------------------
/includes/timber-stream.php:
--------------------------------------------------------------------------------
1 | $stream = new TimberStream( $pid );
13 | * > foreach ( $stream->get_posts() as $post ) {
14 | * > echo ( $post->title );
15 | * > }
16 | */
17 |
18 | class TimberStream extends TimberPost {
19 |
20 | /**
21 | * Stream post cache.
22 | *
23 | * This will only be populated when TimberStream::get_posts
24 | * is run without a $query argument.
25 | *
26 | * @since 1.0.0
27 | *
28 | * @var array
29 | */
30 | public $posts;
31 |
32 | /**
33 | * @since 1.0.0
34 | * @var array
35 | */
36 | public $sm_query = array();
37 |
38 | /**
39 | * Default stream options, used when creating a
40 | * new stream.
41 | *
42 | * @since 1.0.0
43 | * @var array
44 | */
45 | public $default_options = array(
46 | 'query' => array(
47 | 'post_type' => 'post',
48 | 'post_status' => 'publish',
49 | 'has_password' => false,
50 | 'ignore_sticky_posts' => true,
51 | 'posts_per_page' => 100,
52 | 'orderby' => 'post__in'
53 | ),
54 |
55 | 'stream' => array(),
56 | 'layouts' => array(
57 | 'active' => 'default',
58 | 'layouts' => array(
59 | 'default' => array(
60 | 'name' => 'Default',
61 | 'zones' => array()
62 | )
63 | )
64 | )
65 | );
66 |
67 | /**
68 | * Stream options.
69 | * This is set by __construct based on what is stored
70 | * in the database.
71 | *
72 | * @since 1.0.0
73 | *
74 | * @var array
75 | */
76 | public $options = null;
77 |
78 | /**
79 | * Construct Timber\Post, kick off stream init
80 | *
81 | * @param integer|boolean|string $pid Post ID or slug
82 | *
83 | * @todo allow creating a TimberStream w/out database
84 | */
85 | public function __construct($pid = null) {
86 | parent::__construct($pid);
87 | $this->init_stream($pid);
88 | }
89 |
90 | /**
91 | * Init Stream object
92 | *
93 | * @param integer|boolean|string $pid Post ID or slug
94 | *
95 | */
96 | public function init_stream($pid) {
97 | if ($this->post_type === 'sm_stream') {
98 | if ( !$this->post_content ) $this->post_content = serialize(array());
99 | $this->options = array_merge( $this->default_options, unserialize($this->post_content) );
100 | $this->options['query'] = apply_filters('stream-manager/query', $this->options['query']);
101 | $this->options = apply_filters( 'stream-manager/options/id=' . $this->ID, $this->options, $this );
102 | $this->options = apply_filters( 'stream-manager/options/'.$this->slug, $this->options, $this );
103 |
104 | $taxes = apply_filters( 'stream-manager/taxonomy/'.$this->slug, array(), $this );
105 | if (is_array($taxes) && !empty($taxes)) {
106 | $taxes = StreamManagerUtilities::build_tax_query($taxes);
107 | if (isset($this->options['query']['tax_query'])) {
108 | $this->options['query']['tax_query'] = array_merge($this->options['query']['tax_query'], $taxes);
109 | } else {
110 | $this->options['query']['tax_query'] = $taxes;
111 | }
112 | }
113 | }
114 |
115 | }
116 |
117 | /**
118 | * Get filtered & sorted collection of posts in the stream
119 | *
120 | * @since 1.0.0
121 | *
122 | * @param array $query WP_Query query argument
123 | * @param string $PostClass Timber post class
124 | *
125 | * @return array collection of TimberPost objects
126 | */
127 | public function get_posts($query = array(), $PostClass = 'TimberPost') {
128 | $cache = ( empty($query) || !is_array($query) ) ? true : false;
129 |
130 | if ( $cache && !empty($this->posts) ) return $this->posts;
131 |
132 | // Create an array of just post IDs
133 | $query = array_merge( $this->get('query'), $query );
134 | $query['post__in'] = array();
135 | foreach ( $this->get('stream') as $item ) {
136 | $query['post__in'][] = $item['id'];
137 | }
138 | if( isset( $query['post__not_in'] ) && is_array( $query['post__not_in'] ) ){
139 | $query['post__in'] = array_diff( $query['post__in'], $query['post__not_in'] );
140 | unset( $query['post__not_in'] );
141 | }
142 |
143 | $posts_orig = Timber::get_posts($query, $PostClass);
144 | $post_ids = array_map(function($post) {
145 | return $post->ID;
146 | }, $posts_orig);
147 |
148 | //get posts that have been added via search that fall outside the tax rules
149 | $saved_posts = $this->get_posts_without_tax_query($query);
150 | $extra = array_diff($saved_posts, $post_ids);
151 | $all_ids = array_merge($extra,$post_ids);
152 |
153 | //use the stream to put posts back in order
154 | foreach($this->get('stream') as $item) {
155 | if(in_array($item['id'], $all_ids)) {
156 | $posts[] = new $PostClass($item['id']);
157 | }
158 | }
159 |
160 | if (empty($posts)) {
161 | // if the user has re-configured the feed we might need to blow out the saved items to make way for the fresh query;
162 | unset($query['post__in']);
163 | $posts = Timber::get_posts($query, $PostClass);
164 | }
165 | $pinned = array_keys($this->filter_stream('pinned', true));
166 |
167 | foreach ($posts as &$post) {
168 | $post->pinned = in_array( $post->ID, $pinned );
169 | }
170 |
171 | if ( $cache ) $this->posts = $posts;
172 |
173 | return $posts;
174 | }
175 |
176 | /**
177 | * Get the ids of all saved posts, including any removed by the taxonomy query
178 | *
179 | */
180 | public function get_posts_without_tax_query($query, $PostClass = 'TimberPost') {
181 |
182 | // Remove any taxonomy limitations, since those would remove any
183 | // posts from the stream that were added by searching in the UI.
184 | unset($query['tax_query']);
185 |
186 | $all_posts = Timber::get_posts($query, $PostClass);
187 | $postids = array_map(function($post) {
188 | return $post->ID;
189 | }, $all_posts);
190 |
191 | return $postids;
192 |
193 | }
194 |
195 | /**
196 | * Filter posts in the stream, returning only the filtered
197 | * posts (including their position).
198 | *
199 | * @since 1.0.0
200 | *
201 | * @return array filtered posts
202 | */
203 | public function filter_stream($attribute, $value) {
204 | $items = array();
205 |
206 | foreach ( $this->get('stream') as $position => $item ) {
207 | $item['position'] = $position;
208 | if ( $item[$attribute] == $value ) $items[$item['id']] = $item;
209 | }
210 |
211 | return $items;
212 | }
213 |
214 |
215 | /**
216 | * Enforce the stream length.
217 | *
218 | * If there are fewer posts than allowed, add some from the base query.
219 | * If there are more, remove them.
220 | *
221 | * @since 1.0.0
222 | *
223 | * @todo it's possible for a pinned item to go above the limit
224 | */
225 | public function repopulate_stream() {
226 |
227 | // Determine how many over/under we are
228 | $query = $this->get('query');
229 | $difference = count( $this->get('stream') ) - $query['posts_per_page'];
230 |
231 | if ( $difference < 0 ) {
232 |
233 | // Under -- add pinned posts to the end
234 | $query = $this->get('query');
235 | $ids = array();
236 | foreach ( $this->get('stream') as $post ) {
237 | $ids[] = $post['id'];
238 | }
239 | $query['post__not_in'] = $ids;
240 | $query['posts_per_page'] = $difference * -1;
241 | $posts = Timber::get_posts($query);
242 |
243 | $this->remove_pinned();
244 |
245 | foreach ( $posts as $post ) {
246 | $this->options['stream'][] = array(
247 | 'id' => $post->ID,
248 | 'pinned' => false
249 | );
250 | }
251 |
252 | $this->reinsert_pinned();
253 |
254 | } else if ( $difference > 0 ) {
255 |
256 | // Over -- remove non-pinned posts at the end
257 | $this->remove_pinned();
258 | for ( $i = 1; $i <= $difference; $i++ ) {
259 | array_pop( $this->options['stream'] );
260 | }
261 | $this->reinsert_pinned();
262 |
263 | }
264 | }
265 |
266 |
267 | /**
268 | * Checks if a post exists in a stream
269 | *
270 | * @since 1.0.0
271 | *
272 | * @param integer $post_id Post ID
273 | *
274 | * @return array returns the data saved in the stream, plus its position
275 | */
276 | public function check_post ( $post_id ) {
277 | foreach ( $this->get('stream') as $position => $item ) {
278 | if ( $item['id'] == $post_id ) {
279 | $item['position'] = $position;
280 | return $item;
281 | }
282 | }
283 | return false;
284 | }
285 |
286 | /**
287 | * Removes a post from a stream and, by default, fills
288 | * in the empty space at the end.
289 | *
290 | * @since 1.0.0
291 | *
292 | * @param integer $post_id Post ID
293 | * @param boolean $repopulate add/remove posts to enforce stream length
294 | */
295 | public function remove_post ( $post_id, $repopulate = true ) {
296 | $post = $this->check_post( $post_id );
297 | if ( $post ) {
298 | $this->remove_pinned();
299 |
300 | // Remove non-pinned
301 | unset($this->options['stream'][ $post['position'] ]);
302 |
303 | // Remove pinned
304 | foreach ( $this->pinned as $i => $pinned ) {
305 | if ( $pinned['id'] == $post_id ) {
306 | unset( $this->pinned[$i] );
307 | }
308 | }
309 | $this->reinsert_pinned();
310 | if ( $repopulate ) $this->repopulate_stream();
311 | $this->save_stream();
312 | }
313 | }
314 |
315 | /**
316 | * Inserts a post in the stream
317 | *
318 | * @since 1.0.0
319 | *
320 | * @param integer $post_id Post ID
321 | */
322 | public function insert_post ( $post_id ) {
323 | // Does it already exist? If so, remove it, and we'll reinsert it
324 | if ( $this->check_post( $post_id ) ) {
325 | $this->remove_post( $post_id, false );
326 | }
327 |
328 | // Determine where it is in the original query (if at all),
329 | // minus any pinned items
330 | $query = array_merge( $this->get('query'), array(
331 | 'post__not_in' => array_keys($this->filter_stream('pinned', true))
332 | ));
333 | $posts = Timber::get_posts( $query );
334 |
335 | $in_stream = false;
336 |
337 | foreach ( $posts as $i => $post ) {
338 | if ( $post->ID == $post_id ) $in_stream = $i;
339 | }
340 |
341 | // If it's not in the stream, bail
342 | if ( $in_stream === false ) return;
343 |
344 | // Remove pinned items from the stream...
345 | $this->remove_pinned();
346 |
347 | // ... then insert this post ...
348 | array_splice( $this->options['stream'], $in_stream, 0, array( array (
349 | 'id' => $post_id,
350 | 'pinned' => false
351 | ) ) );
352 |
353 | // ... and then reinsert the pinned items
354 | $this->reinsert_pinned();
355 | $this->repopulate_stream();
356 | $this->save_stream();
357 | }
358 |
359 | /**
360 | * Temporarily removes pinned items from the stream, for the
361 | * purpose of modifying the auto-flowing stream.
362 | *
363 | * @since 1.0.0
364 | */
365 | public function remove_pinned() {
366 | $this->pinned = $this->filter_stream('pinned', true);
367 | foreach ( $this->pinned as $pin ) {
368 | unset ( $this->options['stream'][ $pin['position'] ] );
369 | }
370 | }
371 |
372 | /**
373 | * Place the pinned items back in the stream in their appropriate
374 | * locations
375 | *
376 | * @since 1.0.0
377 | */
378 | public function reinsert_pinned() {
379 | foreach ( $this->pinned as $pin ) {
380 | $position = $pin['position'];
381 | unset( $pin['position'] );
382 | array_splice( $this->options['stream'], $position, 0, array( $pin ) );
383 | }
384 | }
385 |
386 |
387 | public function get( $key ) {
388 | return apply_filters( 'stream-manager/get_option/id=' . $this->ID, $this->options[$key], $key, $this );
389 | }
390 |
391 | public function set( $key, $value ) {
392 | $this->options[$key] = apply_filters( 'stream-manager/set_option/id=' . $this->ID, $value, $key, $this );
393 | }
394 |
395 |
396 |
397 | /**
398 | * Save the stream metadata
399 | *
400 | * @since 1.0.0
401 | */
402 |
403 |
404 | public function save_stream() {
405 | $save_data = apply_filters( 'stream-manager/save/id=' . $this->ID, array(
406 | 'ID' => $this->ID,
407 | 'post_content' => serialize($this->options)
408 | ), $this);
409 |
410 | // Fix conflict with yoast premium
411 | add_filter('wpseo_premium_post_redirect_slug_change', '__return_true');
412 |
413 | wp_update_post( $save_data );
414 |
415 | remove_filter('wpseo_premium_post_redirect_slug_change', '__return_true');
416 |
417 | }
418 |
419 | }
420 |
--------------------------------------------------------------------------------
/includes/views/add.twig:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/includes/views/meta.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/includes/views/rules.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ function('post_categories_meta_box', post, null ) }}
4 |
5 |
6 |
7 | {{ function('post_tags_meta_box', post, null) }}
8 |
9 |
10 | Reload Stream
--------------------------------------------------------------------------------
/includes/views/stream.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% set j = 0 %}
5 |
6 | {% for i, post in posts %}
7 |
8 | {% for zone in layout.zones %}
9 | {% if zone.position == j %}
10 | {% include ["meta.twig", "views/meta.twig"] %}
11 | {% set j = j + 1 %}
12 | {% endif %}
13 | {% endfor %}
14 |
15 | {% include ["stub.twig", "views/stub.twig"] %}
16 |
17 | {% set j = j + 1 %}
18 |
19 | {% endfor %}
20 |
21 |
22 |
23 | {{ nonce }}
24 |
--------------------------------------------------------------------------------
/includes/views/stub.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% if post.thumbnail and post.thumbnail.src %}
9 |
10 | {% endif %}
11 |
12 |
{{ post.title }}
13 | {#
14 |
15 | {{post.get_preview(10, true, '')}}
16 |
17 | #}
18 |
19 |
20 | {{ function('human_time_diff', function('strtotime', post.post_date)) }} ago
21 |
22 |
23 |
24 | Edit
25 |
26 | View
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/includes/views/zones.twig:
--------------------------------------------------------------------------------
1 |
2 | {#
3 | {% for slug, layout in layouts.layouts %}
4 | {{ layout.name }}
5 | {% endfor %}
6 |
7 |
8 | + Add Layout
9 |
10 |
11 |
12 |
13 |
14 | + Add Zone
15 | #}
16 |
17 |
18 | {#
#}
19 |
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stream-manager",
3 | "version": "1.3.4",
4 | "description": "WordPress plugin to curate streams of the latests posts.",
5 | "main": "index.js",
6 | "directories": {
7 | "test": "tests"
8 | },
9 | "devDependencies": {
10 | "gulp": "^3.9.1",
11 | "gulp-clean": "^0.3.2",
12 | "gulp-clean-css": "^3.3.1",
13 | "gulp-git": "^2.2.0",
14 | "gulp-load-plugins": "^1.5.0",
15 | "gulp-replace": "^0.5.4",
16 | "gulp-sass": "^3.1.0",
17 | "gulp-sequence": "^0.4.6",
18 | "gulp-svn": "^1.0.7",
19 | "gulp-uglify": "^2.1.2"
20 | },
21 | "scripts": {
22 | "test": "echo \"Error: no test specified\" && exit 1"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/Upstatement/stream-manager.git"
27 | },
28 | "author": "Upstatement",
29 | "license": "ISC",
30 | "bugs": {
31 | "url": "https://github.com/Upstatement/stream-manager/issues"
32 | },
33 | "homepage": "https://github.com/Upstatement/stream-manager#readme"
34 | }
35 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 | ./tests/
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | includes
21 | stream-manager.php
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/stream-manager.php:
--------------------------------------------------------------------------------
1 | Please install Timber to use Stream Manager.
');
59 | });
60 | return;
61 | }
62 | }
63 |
64 |
65 |
66 | ////////////////////////////////////////////
67 | //
68 | // Public-Facing Functionality
69 | //
70 | ////////////////////////////////////////////
71 |
72 | require_once( plugin_dir_path( __FILE__ ) . 'includes/class-stream-manager-utilities.php' );
73 | require_once( plugin_dir_path( __FILE__ ) . 'includes/class-stream-manager-ajax-helper.php' );
74 | require_once( plugin_dir_path( __FILE__ ) . 'includes/class-stream-manager.php' );
75 | require_once( plugin_dir_path( __FILE__ ) . 'includes/timber-stream.php' );
76 | require_once( plugin_dir_path( __FILE__ ) . 'includes/class-stream-manager-manager.php');
77 | require_once( plugin_dir_path( __FILE__ ) . 'includes/class-stream-manager-api.php');
78 |
79 |
80 |
81 |
82 | add_action( 'plugins_loaded', array( 'StreamManager', 'get_instance' ) );
83 | add_action( 'plugins_loaded', array( 'StreamManagerManager', 'get_instance' ) );
84 |
85 |
86 | ////////////////////////////////////////////
87 | //
88 | // Dashboard & Administrative Functionality
89 | //
90 | ////////////////////////////////////////////
91 |
92 | if ( is_admin() ) {
93 | require_once( plugin_dir_path( __FILE__ ) . 'includes/class-stream-manager-admin.php' );
94 |
95 | add_action( 'plugins_loaded', array( 'StreamManagerAdmin', 'get_instance' ) );
96 | }
97 |
98 |
99 |
--------------------------------------------------------------------------------
/tests/StreamManager_UnitTestCase.php:
--------------------------------------------------------------------------------
1 | factory->post->create(array('post_date' => $date));
10 | }
11 | return $post_ids;
12 | }
13 |
14 | function buildStream( $name = 'Sample Stream', $options = array() ) {
15 | $pid = $this->factory->post->create(array('post_type' => 'sm_stream', 'post_content' => '', 'post_title' => $name));
16 | add_filter('stream-manager/options/id='.$pid, function($defaults, $stream) use ($options) {
17 | $defaults['query'] = array_merge($defaults['query'], $options);
18 | return $defaults;
19 | }, 10, 2);
20 | $stream = new TimberStream($pid);
21 | return $stream;
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | assertObjectHasAttribute('plugin', $admin);
8 | $this->assertObjectHasAttribute('default_query', $admin);
9 | }
10 |
11 | function testSaveStream() {
12 | $admin = StreamManagerAdmin::get_instance();
13 | $stream = $this->buildStream();
14 | $postids = $this->buildPosts(5);
15 | foreach($postids as $id) {
16 | $_POST['sm_sort'][] = $id;
17 | }
18 | $admin->save_stream($stream->ID, false);
19 | $stream = new TimberStream($stream->ID);
20 | $this->assertEquals(5, count($stream->options['stream']));
21 | }
22 |
23 | function testAddHelpText() {
24 | set_current_screen( 'sm_stream' );
25 | $admin = StreamManagerAdmin::get_instance();
26 | $admin->add_help_text();
27 | $screen = get_current_screen();
28 | $tabs = $screen->get_help_tabs();
29 | $this->assertEquals(3, count($tabs));
30 | }
31 |
32 | function testDefinePostTypes() {
33 | $manager = StreamManager::get_instance();
34 | $manager->define_post_types();
35 | $post_types = get_post_types();
36 | $this->assertTrue(in_array($manager->get_post_type_slug(), $post_types));
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/tests/test-stream-manager-ajax-helper.php:
--------------------------------------------------------------------------------
1 | factory->post->create();
7 | $queue = array(array('id' => $postid, 'position' => 0));
8 | $output = StreamManagerAjaxHelper::retrieve_posts($queue);
9 | $this->assertEquals($output[$postid]['position'], 0);
10 | $html = $output[$postid]['object'];
11 | if (strpos($html, 'id="post-'.$postid) !== false) {
12 | $contains_id = true;
13 | } else {
14 | $contains_id = false;
15 | }
16 | $this->assertTrue($contains_id);
17 | }
18 |
19 | function testRetrievePostsCheckTitle() {
20 | $postid = $this->factory->post->create(array('post_title' => 'The Wrong Trousers'));
21 | $queue = array(array('id' => $postid, 'position' => 0));
22 | $output = StreamManagerAjaxHelper::retrieve_posts($queue);
23 | $html = $output[$postid]['object'];
24 | if (strpos($html, 'The Wrong Trousers') !== false) {
25 | $contains_title = true;
26 | } else {
27 | $contains_title = false;
28 | }
29 | $this->assertTrue($contains_title);
30 | }
31 |
32 | function testSearchPosts() {
33 | $pid = $this->buildStream();
34 | $bagel_post = $this->factory->post->create(array('post_title' => 'Bagels'));
35 | $croissant_post = $this->factory->post->create(array('post_title' => 'Croissants'));
36 | $pastries_post = $this->factory->post->create(array('post_title' => 'Croissants and Bagels'));
37 | $output = StreamManagerAjaxHelper::search_posts('bagel', $pid);
38 | $this->assertEquals(2, count($output));
39 | $this->assertEquals('1 min', $output[0]['human_date']);
40 | }
41 |
42 | function testSearchPostsAppliesFilter() {
43 | $pid = $this->buildStream('Test Stream', array('post_type' => 'pastry'));
44 | $this->factory->post->create(array('post_title' => 'bagel1'));
45 | $this->factory->post->create(array('post_title' => 'bagel2'));
46 | $this->factory->post->create(array('post_title' => 'bagel3', 'post_type' => 'pastry'));
47 | $output = StreamManagerAjaxHelper::search_posts('bagel', $pid);
48 | $this->assertEquals(1, count($output));
49 | }
50 |
51 | function testSearchPostsNoMatches() {
52 | $pid = $this->buildStream();
53 | $bagel_post = $this->factory->post->create(array('post_title' => 'Bagels'));
54 | $output = StreamManagerAjaxHelper::search_posts('muffin', $pid);
55 | $this->assertEquals(0, count($output));
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/tests/test-stream-manager-api.php:
--------------------------------------------------------------------------------
1 | assertTrue($exists);
9 | }
10 |
11 | function testStreamExistsFalse() {
12 | $exists = StreamManagerApi::stream_exists( 'test_stream' );
13 | $this->assertFalse($exists);
14 | }
15 |
16 | function testInsertStream() {
17 | $sid = StreamManagerApi::insert_stream( 'test_stream' );
18 | $streams = get_posts( array( 'post_type' => 'sm_stream' ) );
19 | $this->assertEquals( 1, count( $streams ) );
20 | $this->assertEquals( $streams[0]->post_name, 'test_stream');
21 | }
22 |
23 | function testInsertStreamIfExists() {
24 | $sid = StreamManagerApi::insert_stream( 'test_stream' );
25 | $sid = StreamManagerApi::insert_stream( 'test_stream' );
26 | $streams = get_posts( array( 'post_type' => 'sm_stream' ) );
27 | $this->assertEquals( 1, count( $streams ) );
28 | $this->assertEquals( $streams[0]->post_name, 'test_stream');
29 | }
30 |
31 | function testInsertStreamWithFilter() {
32 | $cid = wp_create_category('local');
33 | $cat = get_category( $cid );
34 | $sid = StreamManagerApi::insert_stream('local', null, array('category_name' => $cat->name ) );
35 | $postid = $this->factory->post->create( array( 'post_category' => array( $cid ) ) );
36 | $postid2 = $this->factory->post->create();
37 | $all_posts = get_posts();
38 | $this->assertEquals( 2, count( $all_posts ) );
39 | $stream = new TimberStream( $sid );
40 | $posts = $stream->get_posts();
41 | $this->assertEquals( 1, count( $posts ) );
42 | }
43 |
44 | function testDeleteStream() {
45 | $sid = StreamManagerApi::insert_stream('test_stream');
46 | $streams = get_posts( array( 'post_type' => 'sm_stream' ) );
47 | $stream = $streams[0];
48 | $this->assertEquals( 1, count( $streams ) );
49 | $deleted = StreamManagerApi::delete_stream( $stream->post_name );
50 | $streams = get_posts( array( 'post_type' => 'sm_stream' ) );
51 | $this->assertEquals( 0, count( $streams ) );
52 | $this->assertEquals($sid, $deleted);
53 | }
54 |
55 | function testDeleteStreamIfDoesntExist() {
56 | $deleted = StreamManagerApi::delete_stream( 'test' );
57 | $this->assertFalse($deleted);
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/tests/test-stream-manager-hooks.php:
--------------------------------------------------------------------------------
1 | buildStream('Sample Stream', array('post_type' => 'article'));
7 | $this->buildPosts(5);
8 | $posts = $stream->get_posts();
9 | $this->assertEquals( 0, count($posts) );
10 | }
11 |
12 | function testEmptyTaxonomyQueryHook() {
13 | $cooking_id = $this->factory->term->create(array('name' => 'Cooking'.rand(0, 1000)));
14 | $stream = $this->buildStream('Sample Stream', array('post_type' => 'post', 'tax_query' => array('relation' => 'OR', array('taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => array($cooking_id)))));
15 | $this->buildPosts(5);
16 | $posts = $stream->get_posts();
17 | $this->assertEquals( 0, count($posts) );
18 | }
19 |
20 | function testSinglePostTaxonomyQueryHook() {
21 | $cooking_id = $this->factory->term->create(array('name' => 'Cooking'));
22 | $stream = $this->buildStream('Sample Stream', array('post_type' => 'post', 'tax_query' => array('relation' => 'OR', array('taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => array($cooking_id)))));
23 | $this->buildPosts(5);
24 | $cooking_post = $this->factory->post->create(array('tags_input' => 'cooking', 'post_title' => 'All About Cooking'));
25 | $posts = $stream->get_posts();
26 | $this->assertEquals( 1, count($posts) );
27 | $this->assertEquals( $cooking_post, $posts[0]->ID );
28 | }
29 |
30 | function testDoublePostTaxonomyQueryHook() {
31 | $jump_term_id = $this->factory->term->create(array('name' => 'Jumping'));
32 | $jive_term_id = $this->factory->term->create(array('name' => 'Jiveing'));
33 | $stream = $this->buildStream('Sample Stream', array('post_type' => 'post', 'tax_query' => array('relation' => 'OR', array('taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => array($jump_term_id, $jive_term_id)))));
34 | $this->buildPosts(5);
35 | $jumping_post = $this->factory->post->create(array('tags_input' => 'jumping', 'post_title' => 'Jumping & Jiving', 'post_date' => '2014-12-15 12:00:00'));
36 | $jiving_post = $this->factory->post->create(array('tags_input' => 'jumping', 'post_title' => 'Jiving n Jumping', 'post_date' => '2014-12-25 12:00:00'));
37 | $posts = $stream->get_posts();
38 | $this->assertEquals( 2, count($posts) );
39 | $this->assertEquals( $jiving_post, $posts[0]->ID );
40 | }
41 |
42 | function testSinglePostTaxonomyHook() {
43 | //build some stuff
44 | $baking_id = $this->factory->term->create(array('name' => 'Baking'));
45 | $sid = $this->factory->post->create(array('post_type' => 'sm_stream', 'post_content' => '', 'post_title' => 'Sample Stream'));
46 | $stream = new TimberPost($sid);
47 |
48 | //add a filter
49 | add_filter('stream-manager/taxonomy/'.$stream->slug, function($defaults) use ($baking_id) {
50 | $defaults['relation'] = "OR";
51 | $defaults['post_tag'] = array( $baking_id );
52 | return $defaults;
53 | });
54 | $stream = new TimberStream($sid);
55 |
56 | //now make some posts
57 | $baking_post = $this->factory->post->create(array('tags_input' => 'baking', 'post_title' => 'Cookies'));
58 | $this->buildPosts(5);
59 |
60 | //and get them
61 | $posts = $stream->get_posts();
62 | $this->assertEquals( 1, count($posts) );
63 | }
64 |
65 | function testSinglePostDoubleHooks() {
66 | $handstand_id = $this->factory->term->create( array('name' => 'Handstands') );
67 | add_filter('stream-manager/taxonomy/fitness-stream', function($defaults) use ($handstand_id) {
68 | $defaults['relation'] = "OR";
69 | $defaults['post_tag'] = array( $handstand_id );
70 | return $defaults;
71 | });
72 |
73 | $parkour_id = $this->factory->term->create( array('name' => 'Parkour') );
74 |
75 | $stream = $this->buildStream('Fitness Stream', array('post_type' => 'post', 'tax_query' => array('relation' => 'OR', array('taxonomy' => 'post_tag', 'field' => 'term_id', 'terms' => array($parkour_id)))));
76 |
77 |
78 | $parkour_post = $this->factory->post->create(array('tags_input' => 'parkour', 'post_title' => 'Parkour!'));
79 | $handstand_post = $this->factory->post->create(array('tags_input' => 'handstands', 'post_title' => 'Handstands'));
80 | $combo_post = $this->factory->post->create(array('tags_input' => 'parkour, handstands', 'post_title' => 'Parkour & Handstands'));
81 | $combo = new TimberPost($combo_post);
82 | $this->buildPosts(5);
83 | $posts = $stream->get_posts();
84 | $this->assertEquals( 3, count($posts) );
85 | }
86 |
87 |
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/tests/test-stream-manager-integration.php:
--------------------------------------------------------------------------------
1 | buildPosts(10);
7 | $this->assertEquals(10, count($pids));
8 | $pids = $this->buildPosts(7);
9 | $this->assertEquals(7, count($pids));
10 | }
11 |
12 | function testBasicStream() {
13 | $count = rand(0, 25);
14 | $this->buildPosts($count);
15 | $stream = $this->buildStream();
16 | $posts = $stream->get_posts();
17 | $this->assertEquals($count, count($posts));
18 | }
19 |
20 | function testPostPublish() {
21 | $stream = $this->buildStream();
22 | $this->buildPosts(4);
23 | $posts = $stream->get_posts();
24 | $this->assertEquals(4, count($posts));
25 | $post_id = $this->factory->post->create(array('post_status' => 'draft'));
26 | wp_publish_post($post_id);
27 | $posts = $stream->get_posts(array('post_type' => 'post'));
28 | $this->assertEquals(5, count($posts));
29 | }
30 |
31 | function testPostUnpublish() {
32 | $stream = $this->buildStream();
33 | $post_id = $this->factory->post->create();
34 | $posts = $stream->get_posts();
35 | $this->assertEquals(1, count($posts));
36 | wp_update_post(array( 'ID' => $post_id, 'post_status' => 'draft' ));
37 | $stream = new TimberStream( $stream->ID );
38 | $posts = $stream->get_posts();
39 | $this->assertEquals(0, count($posts));
40 | }
41 |
42 | function testRemovePost() {
43 | $stream = $this->buildStream();
44 | $postids = $this->buildPosts(5);
45 | foreach($postids as $id) {
46 | $data[] = array('id' => $id, 'pinned' => '');
47 | }
48 | $stream->set('stream', $data);
49 | $posts = $stream->get_posts();
50 | $first = $posts[0]->ID;
51 | $stream->remove_post($first);
52 | $stream = new TimberStream($stream->ID);
53 | $posts = $stream->get_posts();
54 | $this->assertEquals($first, $posts[4]->ID);
55 | }
56 |
57 | function testRemovePinnedPost() {
58 | $stream = $this->buildStream();
59 | $postids = $this->buildPosts(5);
60 | foreach($postids as $id) {
61 | $data[] = array('id' => $id, 'pinned' => 1);
62 | }
63 | $stream->set('stream', $data);
64 | $posts = $stream->get_posts();
65 | $first = $posts[0]->ID;
66 | $stream->remove_post($first);
67 | $stream = new TimberStream($stream->ID);
68 | $posts = $stream->get_posts();
69 | $this->assertEquals($first, $posts[4]->ID);
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------