Nginx Helper - Version 2.0.0

Version Description

  • Fix typo causing failure to purge on trashed comment. #159 - by jinschoi
  • Refactor Plugin structure and remove unused code. Initial code by chandrapatel, #153 - by jinschoi,
  • Run phpcs and fix warning. #158
  • Make compatible with EasyEngine v4.
Download this release

Release Info

Developer rahulsprajapati
Plugin Icon 128x128 Nginx Helper
Version 2.0.0
Comparing to
See all releases

Code changes from version 1.9.12 to 2.0.0

admin/class-fastcgi-purger.php ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The admin-specific functionality of the plugin.
4
+ *
5
+ * @link https://rtcamp.com/nginx-helper/
6
+ * @since 2.0.0
7
+ *
8
+ * @package nginx-helper
9
+ * @subpackage nginx-helper/admin
10
+ */
11
+
12
+ /**
13
+ * Description of FastCGI_Purger
14
+ *
15
+ * @package nginx-helper
16
+ * @subpackage nginx-helper/admin
17
+ * @author rtCamp
18
+ */
19
+ class FastCGI_Purger extends Purger {
20
+
21
+ /**
22
+ * Function to purge url.
23
+ *
24
+ * @param string $url URL.
25
+ * @param bool $feed Weather it is feed or not.
26
+ */
27
+ public function purge_url( $url, $feed = true ) {
28
+
29
+ global $nginx_helper_admin;
30
+
31
+ $this->log( '- Purging URL | ' . $url );
32
+
33
+ $parse = wp_parse_url( $url );
34
+
35
+ if ( ! isset( $parse['path'] ) ) {
36
+ $parse['path'] = '';
37
+ }
38
+
39
+ switch ( $nginx_helper_admin->options['purge_method'] ) {
40
+
41
+ case 'unlink_files':
42
+ $_url_purge_base = $parse['scheme'] . '://' . $parse['host'] . $parse['path'];
43
+ $_url_purge = $_url_purge_base;
44
+
45
+ if ( isset( $parse['query'] ) && $parse['query'] !== '' ) {
46
+ $_url_purge .= '?' . $parse['query'];
47
+ }
48
+
49
+ $this->delete_cache_file_for( $_url_purge );
50
+
51
+ if ( $feed ) {
52
+
53
+ $feed_url = rtrim( $_url_purge_base, '/' ) . '/feed/';
54
+ $this->delete_cache_file_for( $feed_url );
55
+ $this->delete_cache_file_for( $feed_url . 'atom/' );
56
+ $this->delete_cache_file_for( $feed_url . 'rdf/' );
57
+
58
+ }
59
+ break;
60
+
61
+ case 'get_request':
62
+ // Go to default case.
63
+ default:
64
+ $_url_purge_base = $parse['scheme'] . '://' . $parse['host'] . '/purge' . $parse['path'];
65
+ $_url_purge = $_url_purge_base;
66
+
67
+ if ( isset( $parse['query'] ) && '' !== $parse['query'] ) {
68
+ $_url_purge .= '?' . $parse['query'];
69
+ }
70
+
71
+ $this->do_remote_get( $_url_purge );
72
+
73
+ if ( $feed ) {
74
+
75
+ $feed_url = rtrim( $_url_purge_base, '/' ) . '/feed/';
76
+ $this->do_remote_get( $feed_url );
77
+ $this->do_remote_get( $feed_url . 'atom/' );
78
+ $this->do_remote_get( $feed_url . 'rdf/' );
79
+
80
+ }
81
+ break;
82
+
83
+ }
84
+
85
+ }
86
+
87
+ /**
88
+ * Function to custom purge urls.
89
+ */
90
+ public function custom_purge_urls() {
91
+
92
+ global $nginx_helper_admin;
93
+
94
+ $parse = wp_parse_url( site_url() );
95
+
96
+ $purge_urls = isset( $nginx_helper_admin->options['purge_url'] ) && ! empty( $nginx_helper_admin->options['purge_url'] ) ?
97
+ explode( "\r\n", $nginx_helper_admin->options['purge_url'] ) : array();
98
+
99
+ /**
100
+ * Allow plugins/themes to modify/extend urls.
101
+ *
102
+ * @param array $purge_urls URLs which needs to be purged.
103
+ * @param bool $wildcard If wildcard in url is allowed or not. default false.
104
+ */
105
+ $purge_urls = apply_filters( 'rt_nginx_helper_purge_urls', $purge_urls, false );
106
+
107
+ switch ( $nginx_helper_admin->options['purge_method'] ) {
108
+
109
+ case 'unlink_files':
110
+ $_url_purge_base = $parse['scheme'] . '://' . $parse['host'];
111
+
112
+ if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
113
+
114
+ foreach ( $purge_urls as $purge_url ) {
115
+
116
+ $purge_url = trim( $purge_url );
117
+
118
+ if ( strpos( $purge_url, '*' ) === false ) {
119
+
120
+ $purge_url = $_url_purge_base . $purge_url;
121
+ $this->log( '- Purging URL | ' . $purge_url );
122
+ $this->delete_cache_file_for( $purge_url );
123
+
124
+ }
125
+
126
+ }
127
+
128
+ }
129
+ break;
130
+
131
+ case 'get_request':
132
+ // Go to default case.
133
+ default:
134
+ $_url_purge_base = $parse['scheme'] . '://' . $parse['host'] . '/purge';
135
+
136
+ if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
137
+
138
+ foreach ( $purge_urls as $purge_url ) {
139
+
140
+ $purge_url = trim( $purge_url );
141
+
142
+ if ( strpos( $purge_url, '*' ) === false ) {
143
+
144
+ $purge_url = $_url_purge_base . $purge_url;
145
+ $this->log( '- Purging URL | ' . $purge_url );
146
+ $this->do_remote_get( $purge_url );
147
+
148
+ }
149
+
150
+ }
151
+
152
+ }
153
+ break;
154
+
155
+ }
156
+
157
+ }
158
+
159
+ /**
160
+ * Purge everything.
161
+ */
162
+ public function purge_all() {
163
+
164
+ $this->unlink_recursive( RT_WP_NGINX_HELPER_CACHE_PATH, false );
165
+ $this->log( '* * * * *' );
166
+ $this->log( '* Purged Everything!' );
167
+ $this->log( '* * * * *' );
168
+
169
+ }
170
+
171
+ }
admin/class-nginx-helper-admin.php ADDED
@@ -0,0 +1,694 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The admin-specific functionality of the plugin.
4
+ *
5
+ * @link https://rtcamp.com/nginx-helper/
6
+ * @since 2.0.0
7
+ *
8
+ * @package nginx-helper
9
+ * @subpackage nginx-helper/admin
10
+ */
11
+
12
+ /**
13
+ * The admin-specific functionality of the plugin.
14
+ *
15
+ * Defines the plugin name, version, and two examples hooks for how to
16
+ * enqueue the admin-specific stylesheet and JavaScript.
17
+ *
18
+ * @package nginx-helper
19
+ * @subpackage nginx-helper/admin
20
+ * @author rtCamp
21
+ */
22
+ class Nginx_Helper_Admin {
23
+
24
+ /**
25
+ * The ID of this plugin.
26
+ *
27
+ * @since 2.0.0
28
+ * @access private
29
+ * @var string $plugin_name The ID of this plugin.
30
+ */
31
+ private $plugin_name;
32
+
33
+ /**
34
+ * The version of this plugin.
35
+ *
36
+ * @since 2.0.0
37
+ * @access private
38
+ * @var string $version The current version of this plugin.
39
+ */
40
+ private $version;
41
+
42
+ /**
43
+ * Various settings tabs.
44
+ *
45
+ * @since 2.0.0
46
+ * @access private
47
+ * @var string $settings_tabs Various settings tabs.
48
+ */
49
+ private $settings_tabs;
50
+
51
+ /**
52
+ * Purge options.
53
+ *
54
+ * @since 2.0.0
55
+ * @access public
56
+ * @var string $options Purge options.
57
+ */
58
+ public $options;
59
+
60
+ /**
61
+ * WP-CLI Command.
62
+ *
63
+ * @since 2.0.0
64
+ * @access public
65
+ * @var string $options WP-CLI Command.
66
+ */
67
+ const WP_CLI_COMMAND = 'nginx-helper';
68
+
69
+ /**
70
+ * Initialize the class and set its properties.
71
+ *
72
+ * @since 2.0.0
73
+ * @param string $plugin_name The name of this plugin.
74
+ * @param string $version The version of this plugin.
75
+ */
76
+ public function __construct( $plugin_name, $version ) {
77
+
78
+ $this->plugin_name = $plugin_name;
79
+ $this->version = $version;
80
+
81
+ /**
82
+ * Define settings tabs
83
+ */
84
+ $this->settings_tabs = apply_filters(
85
+ 'rt_nginx_helper_settings_tabs', array(
86
+ 'general' => array(
87
+ 'menu_title' => __( 'General', 'nginx-helper' ),
88
+ 'menu_slug' => 'general',
89
+ ),
90
+ 'support' => array(
91
+ 'menu_title' => __( 'Support', 'nginx-helper' ),
92
+ 'menu_slug' => 'support',
93
+ ),
94
+ )
95
+ );
96
+
97
+ $this->options = $this->nginx_helper_settings();
98
+
99
+ }
100
+
101
+ /**
102
+ * Register the stylesheets for the admin area.
103
+ *
104
+ * @since 2.0.0
105
+ *
106
+ * @param string $hook The current admin page.
107
+ */
108
+ public function enqueue_styles( $hook ) {
109
+
110
+ /**
111
+ * This function is provided for demonstration purposes only.
112
+ *
113
+ * An instance of this class should be passed to the run() function
114
+ * defined in Nginx_Helper_Loader as all of the hooks are defined
115
+ * in that particular class.
116
+ *
117
+ * The Nginx_Helper_Loader will then create the relationship
118
+ * between the defined hooks and the functions defined in this
119
+ * class.
120
+ */
121
+
122
+ if ( 'settings_page_nginx' !== $hook ) {
123
+ return;
124
+ }
125
+
126
+ wp_enqueue_style( $this->plugin_name . '-icons', plugin_dir_url( __FILE__ ) . 'icons/css/nginx-fontello.css', array(), $this->version, 'all' );
127
+ wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/nginx-helper-admin.css', array(), $this->version, 'all' );
128
+
129
+ }
130
+
131
+ /**
132
+ * Register the JavaScript for the admin area.
133
+ *
134
+ * @since 2.0.0
135
+ *
136
+ * @param string $hook The current admin page.
137
+ */
138
+ public function enqueue_scripts( $hook ) {
139
+
140
+ /**
141
+ * This function is provided for demonstration purposes only.
142
+ *
143
+ * An instance of this class should be passed to the run() function
144
+ * defined in Nginx_Helper_Loader as all of the hooks are defined
145
+ * in that particular class.
146
+ *
147
+ * The Nginx_Helper_Loader will then create the relationship
148
+ * between the defined hooks and the functions defined in this
149
+ * class.
150
+ */
151
+
152
+ if ( 'settings_page_nginx' !== $hook ) {
153
+ return;
154
+ }
155
+
156
+ wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/nginx-helper-admin.js', array( 'jquery' ), $this->version, false );
157
+
158
+ }
159
+
160
+ /**
161
+ * Add admin menu.
162
+ *
163
+ * @since 2.0.0
164
+ */
165
+ public function nginx_helper_admin_menu() {
166
+
167
+ if ( is_multisite() ) {
168
+
169
+ add_submenu_page(
170
+ 'settings.php',
171
+ __( 'Nginx Helper', 'nginx-helper' ),
172
+ __( 'Nginx Helper', 'nginx-helper' ),
173
+ 'manage_options',
174
+ 'nginx',
175
+ array( &$this, 'nginx_helper_setting_page' )
176
+ );
177
+
178
+ } else {
179
+
180
+ add_submenu_page(
181
+ 'options-general.php',
182
+ __( 'Nginx Helper', 'nginx-helper' ),
183
+ __( 'Nginx Helper', 'nginx-helper' ),
184
+ 'manage_options',
185
+ 'nginx',
186
+ array( &$this, 'nginx_helper_setting_page' )
187
+ );
188
+
189
+ }
190
+
191
+ }
192
+
193
+ /**
194
+ * Function to add toolbar purge link.
195
+ *
196
+ * @param object $wp_admin_bar Admin bar object.
197
+ */
198
+ public function nginx_helper_toolbar_purge_link( $wp_admin_bar ) {
199
+
200
+ if ( ! current_user_can( 'manage_options' ) ) {
201
+ return;
202
+ }
203
+
204
+ $purge_url = add_query_arg(
205
+ array(
206
+ 'nginx_helper_action' => 'purge',
207
+ 'nginx_helper_urls' => 'all',
208
+ )
209
+ );
210
+
211
+ $nonced_url = wp_nonce_url( $purge_url, 'nginx_helper-purge_all' );
212
+
213
+ $wp_admin_bar->add_menu(
214
+ array(
215
+ 'id' => 'nginx-helper-purge-all',
216
+ 'title' => __( 'Purge Cache', 'nginx-helper' ),
217
+ 'href' => $nonced_url,
218
+ 'meta' => array( 'title' => __( 'Purge Cache', 'nginx-helper' ) ),
219
+ )
220
+ );
221
+
222
+ }
223
+
224
+ /**
225
+ * Display settings.
226
+ *
227
+ * @global $string $pagenow Contain current admin page.
228
+ *
229
+ * @since 2.0.0
230
+ */
231
+ public function nginx_helper_setting_page() {
232
+ include plugin_dir_path(__FILE__ ) . 'partials/nginx-helper-admin-display.php';
233
+ }
234
+
235
+ /**
236
+ * Default settings.
237
+ *
238
+ * @since 2.0.0
239
+ * @return array
240
+ */
241
+ public function nginx_helper_default_settings() {
242
+
243
+ return array(
244
+ 'enable_purge' => 0,
245
+ 'cache_method' => 'enable_fastcgi',
246
+ 'purge_method' => 'get_request',
247
+ 'enable_map' => 0,
248
+ 'enable_log' => 0,
249
+ 'log_level' => 'INFO',
250
+ 'log_filesize' => '5',
251
+ 'enable_stamp' => 0,
252
+ 'purge_homepage_on_edit' => 1,
253
+ 'purge_homepage_on_del' => 1,
254
+ 'purge_archive_on_edit' => 1,
255
+ 'purge_archive_on_del' => 1,
256
+ 'purge_archive_on_new_comment' => 0,
257
+ 'purge_archive_on_deleted_comment' => 0,
258
+ 'purge_page_on_mod' => 1,
259
+ 'purge_page_on_new_comment' => 1,
260
+ 'purge_page_on_deleted_comment' => 1,
261
+ 'redis_hostname' => '127.0.0.1',
262
+ 'redis_port' => '6379',
263
+ 'redis_prefix' => 'nginx-cache:',
264
+ 'purge_url' => '',
265
+ );
266
+
267
+ }
268
+
269
+ /**
270
+ * Get settings.
271
+ *
272
+ * @since 2.0.0
273
+ */
274
+ public function nginx_helper_settings() {
275
+
276
+ $options = get_site_option( 'rt_wp_nginx_helper_options', array( 'redis_hostname' => '127.0.0.1', 'redis_port' => '6379', 'redis_prefix' => 'nginx-cache:' ) );
277
+
278
+ $data = wp_parse_args(
279
+ $options,
280
+ $this->nginx_helper_default_settings()
281
+ );
282
+
283
+ $is_redis_enabled = (
284
+ defined( 'RT_WP_NGINX_HELPER_REDIS_HOSTNAME' ) &&
285
+ defined( 'RT_WP_NGINX_HELPER_REDIS_PORT' ) &&
286
+ defined( 'RT_WP_NGINX_HELPER_REDIS_PREFIX' )
287
+ );
288
+
289
+ if ( ! $is_redis_enabled ) {
290
+ return $data;
291
+ }
292
+
293
+ $data['redis_enabled_by_constant'] = $is_redis_enabled;
294
+ $data['enable_purge'] = $is_redis_enabled;
295
+ $data['cache_method'] = 'enable_redis';
296
+ $data['redis_hostname'] = RT_WP_NGINX_HELPER_REDIS_HOSTNAME;
297
+ $data['redis_port'] = RT_WP_NGINX_HELPER_REDIS_PORT;
298
+ $data['redis_prefix'] = RT_WP_NGINX_HELPER_REDIS_PREFIX;
299
+
300
+ return $data;
301
+
302
+ }
303
+
304
+ /**
305
+ * Nginx helper setting link function.
306
+ *
307
+ * @param array $links links.
308
+ *
309
+ * @return mixed
310
+ */
311
+ public function nginx_helper_settings_link( $links ) {
312
+
313
+ if ( is_network_admin() ) {
314
+ $setting_page = 'settings.php';
315
+ } else {
316
+ $setting_page = 'options-general.php';
317
+ }
318
+
319
+ $settings_link = '<a href="' . admin_url( $setting_page . '?page=nginx' ) . '">' . __( 'Settings', 'nginx-helper' ) . '</a>';
320
+ array_unshift( $links, $settings_link );
321
+
322
+ return $links;
323
+
324
+ }
325
+
326
+ /**
327
+ * Retrieve the asset path.
328
+ *
329
+ * @since 2.0.0
330
+ * @return string asset path of the plugin.
331
+ */
332
+ public function functional_asset_path() {
333
+
334
+ $log_path = WP_CONTENT_DIR . '/uploads/nginx-helper/';
335
+
336
+ return apply_filters( 'nginx_asset_path', $log_path );
337
+
338
+ }
339
+
340
+ /**
341
+ * Retrieve the asset url.
342
+ *
343
+ * @since 2.0.0
344
+ * @return string asset url of the plugin.
345
+ */
346
+ public function functional_asset_url() {
347
+
348
+ $log_url = WP_CONTENT_URL . '/uploads/nginx-helper/';
349
+
350
+ return apply_filters( 'nginx_asset_url', $log_url );
351
+
352
+ }
353
+
354
+ /**
355
+ * Get latest news.
356
+ *
357
+ * @since 2.0.0
358
+ */
359
+ public function nginx_helper_get_feeds() {
360
+
361
+ // Get RSS Feed(s).
362
+ require_once ABSPATH . WPINC . '/feed.php';
363
+
364
+ $maxitems = 0;
365
+ $rss_items = array();
366
+
367
+ // Get a SimplePie feed object from the specified feed source.
368
+ $rss = fetch_feed( 'https://rtcamp.com/blog/feed/' );
369
+
370
+ if ( ! is_wp_error( $rss ) ) { // Checks that the object is created correctly.
371
+
372
+ // Figure out how many total items there are, but limit it to 5.
373
+ $maxitems = $rss->get_item_quantity( 5 );
374
+ // Build an array of all the items, starting with element 0 (first element).
375
+ $rss_items = $rss->get_items( 0, $maxitems );
376
+
377
+ }
378
+ ?>
379
+ <ul role="list">
380
+ <?php
381
+ if ( 0 === $maxitems ) {
382
+ echo '<li role="listitem">' . esc_html_e( 'No items', 'nginx-helper' ) . '.</li>';
383
+ } else {
384
+
385
+ // Loop through each feed item and display each item as a hyperlink.
386
+ foreach ( $rss_items as $item ) {
387
+ ?>
388
+ <li role="listitem">
389
+ <?php
390
+ echo wp_kses(
391
+ sprintf(
392
+ '<a href="%1$s" title="%2$s">%3$s</a>',
393
+ esc_url( $item->get_permalink() ), esc_attr__( 'Posted ', 'nginx-helper' ) . esc_attr( $item->get_date( 'j F Y | g:i a' ) ), esc_html( $item->get_title() )
394
+ ),
395
+ array( 'strong' => array(), 'a' => array( 'href' => array(), 'title' => array() ) )
396
+ );
397
+ ?>
398
+ </li>
399
+ <?php
400
+ }
401
+
402
+ }
403
+ ?>
404
+ </ul>
405
+ <?php
406
+ die();
407
+
408
+ }
409
+
410
+ /**
411
+ * Add time stamps in html.
412
+ */
413
+ public function add_timestamps() {
414
+
415
+ if ( is_admin() || (int) $this->options['enable_purge'] !== 1 || (int) $this->options['enable_stamp'] !== 1 ) {
416
+ return;
417
+ }
418
+
419
+ foreach ( headers_list() as $header ) {
420
+ list( $key, $value ) = explode( ':', $header, 2 );
421
+ if ( 'Content-Type' === $key && strpos( trim( $value ), 'text/html' ) !== 0 ) {
422
+ return;
423
+ }
424
+ if ( 'Content-Type' === $key ) {
425
+ break;
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Don't add timestamp if run from ajax, cron or wpcli.
431
+ */
432
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
433
+ return;
434
+ }
435
+
436
+ if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
437
+ return;
438
+ }
439
+
440
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
441
+ return;
442
+ }
443
+
444
+ $timestamps = "\n<!--" .
445
+ 'Cached using Nginx-Helper on ' . current_time( 'mysql' ) . '. ' .
446
+ 'It took ' . get_num_queries() . ' queries executed in ' . timer_stop() . ' seconds.' .
447
+ "-->\n" .
448
+ '<!--Visit http://wordpress.org/extend/plugins/nginx-helper/faq/ for more details-->';
449
+
450
+ echo wp_kses( $timestamps, array() );
451
+
452
+ }
453
+
454
+ /**
455
+ * Get map
456
+ *
457
+ * @global object $wpdb
458
+ *
459
+ * @return string
460
+ */
461
+ public function get_map() {
462
+
463
+ if ( ! $this->options['enable_map'] ) {
464
+ return;
465
+ }
466
+
467
+ if ( is_multisite() ) {
468
+
469
+ global $wpdb;
470
+
471
+ $rt_all_blogs = $wpdb->get_results(
472
+ $wpdb->prepare(
473
+ 'SELECT blog_id, domain, path FROM ' . $wpdb->blogs . " WHERE site_id = %d AND archived = '0' AND mature = '0' AND spam = '0' AND deleted = '0'",
474
+ $wpdb->siteid
475
+ )
476
+ );
477
+
478
+ $wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
479
+
480
+ $rt_domain_map_sites = '';
481
+
482
+ if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->dmtable}'" ) === $wpdb->dmtable ) { // phpcs:ignore
483
+ $rt_domain_map_sites = $wpdb->get_results( "SELECT blog_id, domain FROM {$wpdb->dmtable} ORDER BY id DESC" );
484
+ }
485
+
486
+ $rt_nginx_map = '';
487
+ $rt_nginx_map_array = array();
488
+
489
+ if ( $rt_all_blogs ) {
490
+
491
+ foreach ( $rt_all_blogs as $blog ) {
492
+
493
+ if ( 'yes' === SUBDOMAIN_INSTALL ) {
494
+ $rt_nginx_map_array[ $blog->domain ] = $blog->blog_id;
495
+ } else {
496
+
497
+ if ( 1 !== $blog->blog_id ) {
498
+ $rt_nginx_map_array[ $blog->path ] = $blog->blog_id;
499
+ }
500
+
501
+ }
502
+
503
+ }
504
+
505
+ }
506
+
507
+ if ( $rt_domain_map_sites ) {
508
+
509
+ foreach ( $rt_domain_map_sites as $site ) {
510
+ $rt_nginx_map_array[ $site->domain ] = $site->blog_id;
511
+ }
512
+
513
+ }
514
+
515
+ foreach ( $rt_nginx_map_array as $domain => $domain_id ) {
516
+ $rt_nginx_map .= "\t" . $domain . "\t" . $domain_id . ";\n";
517
+ }
518
+
519
+ return $rt_nginx_map;
520
+
521
+ }
522
+
523
+ }
524
+
525
+ /**
526
+ * Update map
527
+ */
528
+ public function update_map() {
529
+
530
+ if ( is_multisite() ) {
531
+
532
+ $rt_nginx_map = $this->get_map();
533
+
534
+ if ( $fp = fopen( $this->functional_asset_path() . 'map.conf', 'w+' ) ) {
535
+ fwrite( $fp, $rt_nginx_map );
536
+ fclose( $fp );
537
+ }
538
+
539
+ }
540
+
541
+ }
542
+
543
+ /**
544
+ * Purge url when post status is changed.
545
+ *
546
+ * @global string $blog_id Blog id.
547
+ * @global object $nginx_purger Nginx purger variable.
548
+ *
549
+ * @param string $new_status New status.
550
+ * @param string $old_status Old status.
551
+ * @param object $post Post object.
552
+ */
553
+ public function set_future_post_option_on_future_status( $new_status, $old_status, $post ) {
554
+
555
+ global $blog_id, $nginx_purger;
556
+
557
+ if ( ! $this->options['enable_purge'] ) {
558
+ return;
559
+ }
560
+
561
+ $purge_status = array( 'publish', 'future' );
562
+
563
+ if ( in_array( $old_status, $purge_status, true ) || in_array( $new_status, $purge_status, true ) ) {
564
+
565
+ $nginx_purger->log( 'Purge post on transition post STATUS from ' . $old_status . ' to ' . $new_status );
566
+ $nginx_purger->purge_post( $post->ID );
567
+
568
+ }
569
+
570
+ if (
571
+ 'future' === $new_status && $post && 'future' === $post->post_status &&
572
+ (
573
+ ( 'post' === $post->post_type || 'page' === $post->post_type ) ||
574
+ (
575
+ isset( $this->options['custom_post_types_recognized'] ) &&
576
+ in_array( $post->post_type, $this->options['custom_post_types_recognized'], true )
577
+ )
578
+ )
579
+ ) {
580
+
581
+ $nginx_purger->log( 'Set/update future_posts option ( post id = ' . $post->ID . ' and blog id = ' . $blog_id . ' )' );
582
+ $this->options['future_posts'][ $blog_id ][ $post->ID ] = strtotime( $post->post_date_gmt ) + 60;
583
+ update_site_option( 'rt_wp_nginx_helper_options', $this->options );
584
+
585
+ }
586
+
587
+ }
588
+
589
+ /**
590
+ * Unset future post option on delete
591
+ *
592
+ * @global string $blog_id Blog id.
593
+ * @global object $nginx_purger Nginx helper object.
594
+ *
595
+ * @param int $post_id Post id.
596
+ */
597
+ public function unset_future_post_option_on_delete( $post_id ) {
598
+
599
+ global $blog_id, $nginx_purger;
600
+
601
+ if (
602
+ ! $this->options['enable_purge'] ||
603
+ empty( $this->options['future_posts'] ) ||
604
+ empty( $this->options['future_posts'][ $blog_id ] ) ||
605
+ empty( isset( $this->options['future_posts'][ $blog_id ][ $post_id ] ) ) ||
606
+ wp_is_post_revision( $post_id )
607
+ ) {
608
+ return;
609
+ }
610
+
611
+ $nginx_purger->log( 'Unset future_posts option ( post id = ' . $post_id . ' and blog id = ' . $blog_id . ' )' );
612
+
613
+ unset( $this->options['future_posts'][ $blog_id ][ $post_id ] );
614
+
615
+ if ( ! count( $this->options['future_posts'][ $blog_id ] ) ) {
616
+ unset( $this->options['future_posts'][ $blog_id ] );
617
+ }
618
+
619
+ update_site_option( 'rt_wp_nginx_helper_options', $this->options );
620
+ }
621
+
622
+ /**
623
+ * Update map when new blog added in multisite.
624
+ *
625
+ * @global object $nginx_purger Nginx purger class object.
626
+ *
627
+ * @param string $blog_id blog id.
628
+ */
629
+ public function update_new_blog_options( $blog_id ) {
630
+
631
+ global $nginx_purger;
632
+
633
+ $nginx_purger->log( "New site added ( id $blog_id )" );
634
+ $this->update_map();
635
+ $nginx_purger->log( "New site added to nginx map ( id $blog_id )" );
636
+ $helper_options = $this->nginx_helper_default_settings();
637
+ update_blog_option( $blog_id, 'rt_wp_nginx_helper_options', $helper_options );
638
+ $nginx_purger->log( "Default options updated for the new blog ( id $blog_id )" );
639
+
640
+ }
641
+
642
+ /**
643
+ * Purge all urls.
644
+ *
645
+ * @global object $nginx_purger
646
+ */
647
+ public function purge_all() {
648
+
649
+ global $nginx_purger;
650
+
651
+ $method = filter_input( INPUT_SERVER, 'REQUEST_METHOD', FILTER_SANITIZE_STRING );
652
+
653
+ if ( 'POST' === $method ) {
654
+ $action = filter_input( INPUT_POST, 'nginx_helper_action', FILTER_SANITIZE_STRING );
655
+ } else {
656
+ $action = filter_input( INPUT_GET, 'nginx_helper_action', FILTER_SANITIZE_STRING );
657
+ }
658
+
659
+ if ( empty( $action ) ) {
660
+ return;
661
+ }
662
+
663
+ if ( ! current_user_can( 'manage_options' ) ) {
664
+ wp_die( 'Sorry, you do not have the necessary privileges to edit these options.' );
665
+ }
666
+
667
+ if ( 'done' === $action ) {
668
+
669
+ add_action( 'admin_notices', array( &$this, 'display_notices' ) );
670
+ add_action( 'network_admin_notices', array( &$this, 'display_notices' ) );
671
+ return;
672
+
673
+ }
674
+
675
+ check_admin_referer( 'nginx_helper-purge_all' );
676
+ switch ( $action ) {
677
+ case 'purge':
678
+ $nginx_purger->purge_all();
679
+ break;
680
+ }
681
+
682
+ wp_redirect( esc_url_raw( add_query_arg( array( 'nginx_helper_action' => 'done' ) ) ) );
683
+ exit();
684
+
685
+ }
686
+
687
+ /**
688
+ * Dispay plugin notices.
689
+ */
690
+ public function display_notices() {
691
+ echo '<div class="updated"><p>' . esc_html__( 'Purge initiated', 'nginx-helper' ) . '</p></div>';
692
+ }
693
+
694
+ }
admin/class-phpredis-purger.php ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The admin-specific functionality of the plugin.
4
+ *
5
+ * @link https://rtcamp.com/nginx-helper/
6
+ * @since 2.0.0
7
+ *
8
+ * @package nginx-helper
9
+ * @subpackage nginx-helper/admin
10
+ */
11
+
12
+ /**
13
+ * Description of PhpRedis_Purger
14
+ *
15
+ * @package nginx-helper
16
+ * @subpackage nginx-helper/admin
17
+ * @author rtCamp
18
+ */
19
+ class PhpRedis_Purger extends Purger {
20
+
21
+ /**
22
+ * PHP Redis api object.
23
+ *
24
+ * @since 2.0.0
25
+ * @access public
26
+ * @var string $redis_object PHP Redis api object.
27
+ */
28
+ public $redis_object;
29
+
30
+ /**
31
+ * Initialize the class and set its properties.
32
+ *
33
+ * @since 2.0.0
34
+ */
35
+ public function __construct() {
36
+
37
+ global $nginx_helper_admin;
38
+
39
+ try {
40
+
41
+ $this->redis_object = new Redis();
42
+ $this->redis_object->connect(
43
+ $nginx_helper_admin->options['redis_hostname'],
44
+ $nginx_helper_admin->options['redis_port'],
45
+ 5
46
+ );
47
+
48
+ } catch ( Exception $e ) {
49
+ $this->log( $e->getMessage(), 'ERROR' );
50
+ }
51
+
52
+ }
53
+
54
+ /**
55
+ * Purge all cache.
56
+ */
57
+ public function purge_all() {
58
+
59
+ global $nginx_helper_admin;
60
+
61
+ $prefix = trim( $nginx_helper_admin->options['redis_prefix'] );
62
+
63
+ $this->log( '* * * * *' );
64
+
65
+ // If Purge Cache link click from network admin then purge all.
66
+ if ( is_network_admin() ) {
67
+
68
+ $total_keys_purged = $this->delete_keys_by_wildcard( $prefix . '*' );
69
+ $this->log( '* Purged Everything! * ' );
70
+
71
+ } else { // Else purge only site specific cache.
72
+
73
+ $parse = wp_parse_url( get_home_url() );
74
+ $parse['path'] = empty( $parse['path'] ) ? '/' : $parse['path'];
75
+ $total_keys_purged = $this->delete_keys_by_wildcard( $prefix . $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'] . '*' );
76
+ $this->log( '* ' . get_home_url() . ' Purged! * ' );
77
+
78
+ }
79
+
80
+ if ( $total_keys_purged ) {
81
+ $this->log( "Total {$total_keys_purged} urls purged." );
82
+ } else {
83
+ $this->log( 'No Cache found.' );
84
+ }
85
+
86
+ $this->log( '* * * * *' );
87
+
88
+ }
89
+
90
+ /**
91
+ * Purge url.
92
+ *
93
+ * @param string $url URL to purge.
94
+ * @param bool $feed Feed or not.
95
+ */
96
+ public function purge_url( $url, $feed = true ) {
97
+
98
+ global $nginx_helper_admin;
99
+
100
+ $parse = wp_parse_url( $url );
101
+
102
+ if ( ! isset( $parse['path'] ) ) {
103
+ $parse['path'] = '';
104
+ }
105
+
106
+ $prefix = $nginx_helper_admin->options['redis_prefix'];
107
+ $_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'];
108
+ $is_purged = $this->delete_single_key( $_url_purge_base );
109
+
110
+ if ( $is_purged ) {
111
+ $this->log( '- Purged URL | ' . $url );
112
+ } else {
113
+ $this->log( '- Cache Not Found | ' . $url, 'ERROR' );
114
+ }
115
+
116
+ $this->log( '* * * * *' );
117
+
118
+ }
119
+
120
+ /**
121
+ * Custom purge urls.
122
+ */
123
+ public function custom_purge_urls() {
124
+
125
+ global $nginx_helper_admin;
126
+
127
+ $parse = wp_parse_url( site_url() );
128
+ $prefix = $nginx_helper_admin->options['redis_prefix'];
129
+ $_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $parse['host'];
130
+
131
+ $purge_urls = isset( $nginx_helper_admin->options['purge_url'] ) && ! empty( $nginx_helper_admin->options['purge_url'] ) ?
132
+ explode( "\r\n", $nginx_helper_admin->options['purge_url'] ) : array();
133
+
134
+ /**
135
+ * Allow plugins/themes to modify/extend urls.
136
+ *
137
+ * @param array $purge_urls URLs which needs to be purged.
138
+ * @param bool $wildcard If wildcard in url is allowed or not. default true.
139
+ */
140
+ $purge_urls = apply_filters( 'rt_nginx_helper_purge_urls', $purge_urls, true );
141
+
142
+ if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
143
+
144
+ foreach ( $purge_urls as $purge_url ) {
145
+
146
+ $purge_url = trim( $purge_url );
147
+
148
+ if ( strpos( $purge_url, '*' ) === false ) {
149
+
150
+ $purge_url = $_url_purge_base . $purge_url;
151
+ $status = $this->delete_single_key( $purge_url );
152
+
153
+ if ( $status ) {
154
+ $this->log( '- Purge URL | ' . $purge_url );
155
+ } else {
156
+ $this->log( '- Cache Not Found | ' . $purge_url, 'ERROR' );
157
+ }
158
+
159
+ } else {
160
+
161
+ $purge_url = $_url_purge_base . $purge_url;
162
+ $status = $this->delete_keys_by_wildcard( $purge_url );
163
+
164
+ if ( $status ) {
165
+ $this->log( '- Purge Wild Card URL | ' . $purge_url . ' | ' . $status . ' url purged' );
166
+ } else {
167
+ $this->log( '- Cache Not Found | ' . $purge_url, 'ERROR' );
168
+ }
169
+
170
+ }
171
+
172
+ }
173
+
174
+ }
175
+
176
+ }
177
+
178
+ /**
179
+ * Single Key Delete Example
180
+ * e.g. $key can be nginx-cache:httpGETexample.com/
181
+ *
182
+ * @param string $key Key.
183
+ *
184
+ * @return int
185
+ */
186
+ public function delete_single_key( $key ) {
187
+
188
+ try {
189
+ return $this->redis_object->del( $key );
190
+ } catch ( Exception $e ) {
191
+ $this->log( $e->getMessage(), 'ERROR' );
192
+ }
193
+
194
+ }
195
+
196
+ /**
197
+ * Delete Keys by wildcard.
198
+ * e.g. $key can be nginx-cache:httpGETexample.com*
199
+ *
200
+ * Lua Script block to delete multiple keys using wildcard
201
+ * Script will return count i.e. number of keys deleted
202
+ * if return value is 0, that means no matches were found
203
+ *
204
+ * Call redis eval and return value from lua script
205
+ *
206
+ * @param string $pattern pattern.
207
+ *
208
+ * @return mixed
209
+ */
210
+ public function delete_keys_by_wildcard( $pattern ) {
211
+
212
+ // Lua Script.
213
+ $lua = <<<LUA
214
+ local k = 0
215
+ for i, name in ipairs(redis.call('KEYS', KEYS[1]))
216
+ do
217
+ redis.call('DEL', name)
218
+ k = k+1
219
+ end
220
+ return k
221
+ LUA;
222
+
223
+ try {
224
+ return $this->redis_object->eval( $lua, array( $pattern ), 1 );
225
+ } catch ( Exception $e ) {
226
+ $this->log( $e->getMessage(), 'ERROR' );
227
+ }
228
+
229
+ }
230
+
231
+ }
admin/class-predis-purger.php ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The admin-specific functionality of the plugin.
4
+ *
5
+ * @package nginx-helper
6
+ */
7
+
8
+ /**
9
+ * Description of Predis_Purger
10
+ *
11
+ * @package nginx-helper
12
+ * @subpackage nginx-helper/admin
13
+ * @author rtCamp
14
+ */
15
+ class Predis_Purger extends Purger {
16
+
17
+ /**
18
+ * Predis api object.
19
+ *
20
+ * @since 2.0.0
21
+ * @access public
22
+ * @var string $redis_object Predis api object.
23
+ */
24
+ public $redis_object;
25
+
26
+ /**
27
+ * Initialize the class and set its properties.
28
+ *
29
+ * @since 2.0.0
30
+ */
31
+ public function __construct() {
32
+
33
+ global $nginx_helper_admin;
34
+
35
+ if ( ! class_exists( 'Predis\Autoloader' ) ) {
36
+ require_once NGINX_HELPER_BASEPATH . 'admin/predis.php';
37
+ }
38
+
39
+ Predis\Autoloader::register();
40
+
41
+ // redis server parameter.
42
+ $this->redis_object = new Predis\Client(
43
+ array(
44
+ 'host' => $nginx_helper_admin->options['redis_hostname'],
45
+ 'port' => $nginx_helper_admin->options['redis_port'],
46
+ )
47
+ );
48
+
49
+ try {
50
+ $this->redis_object->connect();
51
+ } catch ( Exception $e ) {
52
+ $this->log( $e->getMessage(), 'ERROR' );
53
+ }
54
+
55
+ }
56
+
57
+ /**
58
+ * Purge all.
59
+ */
60
+ public function purge_all() {
61
+
62
+ global $nginx_helper_admin;
63
+
64
+ $prefix = trim( $nginx_helper_admin->options['redis_prefix'] );
65
+
66
+ $this->log( '* * * * *' );
67
+
68
+ // If Purge Cache link click from network admin then purge all.
69
+ if ( is_network_admin() ) {
70
+
71
+ $this->delete_keys_by_wildcard( $prefix . '*' );
72
+ $this->log( '* Purged Everything! * ' );
73
+
74
+ } else { // Else purge only site specific cache.
75
+
76
+ $parse = wp_parse_url( get_home_url() );
77
+ $parse['path'] = empty( $parse['path'] ) ? '/' : $parse['path'];
78
+ $this->delete_keys_by_wildcard( $prefix . $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'] . '*' );
79
+ $this->log( '* ' . get_home_url() . ' Purged! * ' );
80
+
81
+ }
82
+
83
+ $this->log( '* * * * *' );
84
+
85
+ }
86
+
87
+ /**
88
+ * Purge url.
89
+ *
90
+ * @param string $url URL.
91
+ * @param bool $feed Feed or not.
92
+ */
93
+ public function purge_url( $url, $feed = true ) {
94
+
95
+ global $nginx_helper_admin;
96
+
97
+ $this->log( '- Purging URL | ' . $url );
98
+
99
+ $parse = wp_parse_url( $url );
100
+
101
+ if ( ! isset( $parse['path'] ) ) {
102
+ $parse['path'] = '';
103
+ }
104
+
105
+ $prefix = $nginx_helper_admin->options['redis_prefix'];
106
+ $_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $parse['host'] . $parse['path'];
107
+ $this->delete_single_key( $_url_purge_base );
108
+
109
+ }
110
+
111
+ /**
112
+ * Custom purge urls.
113
+ */
114
+ public function custom_purge_urls() {
115
+
116
+ global $nginx_helper_admin;
117
+
118
+ $parse = wp_parse_url( site_url() );
119
+ $prefix = $nginx_helper_admin->options['redis_prefix'];
120
+ $_url_purge_base = $prefix . $parse['scheme'] . 'GET' . $parse['host'];
121
+
122
+ $purge_urls = isset( $nginx_helper_admin->options['purge_url'] ) && ! empty( $nginx_helper_admin->options['purge_url'] ) ?
123
+ explode( "\r\n", $nginx_helper_admin->options['purge_url'] ) : array();
124
+
125
+ /**
126
+ * Allow plugins/themes to modify/extend urls.
127
+ *
128
+ * @param array $purge_urls URLs which needs to be purged.
129
+ * @param bool $wildcard If wildcard in url is allowed or not. default true.
130
+ */
131
+ $purge_urls = apply_filters( 'rt_nginx_helper_purge_urls', $purge_urls, true );
132
+
133
+ if ( is_array( $purge_urls ) && ! empty( $purge_urls ) ) {
134
+
135
+ foreach ( $purge_urls as $purge_url ) {
136
+
137
+ $purge_url = trim( $purge_url );
138
+
139
+ if ( strpos( $purge_url, '*' ) === false ) {
140
+
141
+ $purge_url = $_url_purge_base . $purge_url;
142
+ $status = $this->delete_single_key( $purge_url );
143
+ if ( $status ) {
144
+ $this->log( '- Purge URL | ' . $purge_url );
145
+ } else {
146
+ $this->log( '- Not Found | ' . $purge_url, 'ERROR' );
147
+ }
148
+
149
+ } else {
150
+
151
+ $purge_url = $_url_purge_base . $purge_url;
152
+ $status = $this->delete_keys_by_wildcard( $purge_url );
153
+
154
+ if ( $status ) {
155
+ $this->log( '- Purge Wild Card URL | ' . $purge_url . ' | ' . $status . ' url purged' );
156
+ } else {
157
+ $this->log( '- Not Found | ' . $purge_url, 'ERROR' );
158
+ }
159
+
160
+ }
161
+
162
+ }
163
+
164
+ }
165
+
166
+ }
167
+
168
+ /**
169
+ * Single Key Delete Example
170
+ * e.g. $key can be nginx-cache:httpGETexample.com/
171
+ *
172
+ * @param string $key Key to delete cache.
173
+ *
174
+ * @return mixed
175
+ */
176
+ public function delete_single_key( $key ) {
177
+
178
+ try {
179
+ return $this->redis_object->executeRaw( array( 'DEL', $key ) );
180
+ } catch ( Exception $e ) {
181
+ $this->log( $e->getMessage(), 'ERROR' );
182
+ }
183
+
184
+ }
185
+
186
+ /**
187
+ * Delete Keys by wildcard.
188
+ * e.g. $key can be nginx-cache:httpGETexample.com*
189
+ *
190
+ * Lua Script block to delete multiple keys using wildcard
191
+ * Script will return count i.e. number of keys deleted
192
+ * if return value is 0, that means no matches were found
193
+ *
194
+ * Call redis eval and return value from lua script
195
+ *
196
+ * @param string $pattern Pattern.
197
+ *
198
+ * @return mixed
199
+ */
200
+ public function delete_keys_by_wildcard( $pattern ) {
201
+
202
+ // Lua Script.
203
+ $lua = <<<LUA
204
+ local k = 0
205
+ for i, name in ipairs(redis.call('KEYS', KEYS[1]))
206
+ do
207
+ redis.call('DEL', name)
208
+ k = k+1
209
+ end
210
+ return k
211
+ LUA;
212
+
213
+ try {
214
+ return $this->redis_object->eval( $lua, 1, $pattern );
215
+ } catch ( Exception $e ) {
216
+ $this->log( $e->getMessage(), 'ERROR' );
217
+ }
218
+
219
+ }
220
+
221
+ }
admin/class-purger.php ADDED
@@ -0,0 +1,1164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The admin-specific functionality of the plugin.
4
+ *
5
+ * @package nginx-helper
6
+ */
7
+
8
+ /**
9
+ * Description of purger
10
+ *
11
+ * @package nginx-helper
12
+ *
13
+ * @subpackage nginx-helper/admin
14
+ *
15
+ * @author rtCamp
16
+ */
17
+ abstract class Purger {
18
+
19
+ /**
20
+ * Purge cache for url.
21
+ *
22
+ * @param string $url URL.
23
+ * @param bool $feed Is feed or not.
24
+ *
25
+ * @return mixed
26
+ */
27
+ abstract public function purge_url( $url, $feed = true );
28
+
29
+ /**
30
+ * Purge cache for custom url.
31
+ *
32
+ * @return mixed
33
+ */
34
+ abstract public function custom_purge_urls();
35
+
36
+ /**
37
+ * Purge all cache
38
+ *
39
+ * @return mixed
40
+ */
41
+ abstract public function purge_all();
42
+
43
+ /**
44
+ * Purge cache on comment.
45
+ *
46
+ * @param int $comment_id Comment id.
47
+ * @param object $comment Comment data.
48
+ */
49
+ public function purge_post_on_comment( $comment_id, $comment ) {
50
+
51
+ $oldstatus = '';
52
+ $approved = $comment->comment_approved;
53
+
54
+ if ( $approved === null ) {
55
+ $newstatus = false;
56
+ } elseif ( $approved === '1' ) {
57
+ $newstatus = 'approved';
58
+ } elseif ( $approved === '0' ) {
59
+ $newstatus = 'unapproved';
60
+ } elseif ( $approved === 'spam' ) {
61
+ $newstatus = 'spam';
62
+ } elseif ( $approved === 'trash' ) {
63
+ $newstatus = 'trash';
64
+ } else {
65
+ $newstatus = false;
66
+ }
67
+
68
+ $this->purge_post_on_comment_change( $newstatus, $oldstatus, $comment );
69
+
70
+ }
71
+
72
+ /**
73
+ * Purge post cache on comment change.
74
+ *
75
+ * @param string $newstatus New status.
76
+ * @param string $oldstatus Old status.
77
+ * @param object $comment Comment data.
78
+ */
79
+ public function purge_post_on_comment_change( $newstatus, $oldstatus, $comment ) {
80
+
81
+ global $nginx_helper_admin, $blog_id;
82
+
83
+ if ( ! $nginx_helper_admin->options['enable_purge'] ) {
84
+ return;
85
+ }
86
+
87
+ $_post_id = $comment->comment_post_ID;
88
+ $_comment_id = $comment->comment_ID;
89
+
90
+ $this->log( '* * * * *' );
91
+ $this->log( '* Blog :: ' . addslashes( get_bloginfo( 'name' ) ) . ' ( ' . $blog_id . ' ). ' );
92
+ $this->log( '* Post :: ' . get_the_title( $_post_id ) . ' ( ' . $_post_id . ' ) ' );
93
+ $this->log( "* Comment :: $_comment_id." );
94
+ $this->log( "* Status Changed from $oldstatus to $newstatus" );
95
+
96
+ switch ( $newstatus ) {
97
+
98
+ case 'approved':
99
+ if ( 1 === $nginx_helper_admin->options['purge_page_on_new_comment'] ) {
100
+
101
+ $this->log( '* Comment ( ' . $_comment_id . ' ) approved. Post ( ' . $_post_id . ' ) purging...' );
102
+ $this->log( '* * * * *' );
103
+ $this->purge_post( $_post_id );
104
+
105
+ }
106
+ break;
107
+
108
+ case 'spam':
109
+ case 'unapproved':
110
+ case 'trash':
111
+ if ( 'approved' === $oldstatus && 1 === (int)$nginx_helper_admin->options['purge_page_on_deleted_comment'] ) {
112
+
113
+ $this->log( '* Comment ( ' . $_comment_id . ' ) removed as ( ' . $newstatus . ' ). Post ( ' . $_post_id . ' ) purging...' );
114
+ $this->log( '* * * * *' );
115
+ $this->purge_post( $_post_id );
116
+
117
+ }
118
+ break;
119
+
120
+ }
121
+
122
+ }
123
+
124
+ /**
125
+ * Purge post cache.
126
+ *
127
+ * @param int $_ID Post id.
128
+ */
129
+ public function purge_post( $_ID ) {
130
+
131
+ global $nginx_helper_admin, $blog_id;
132
+
133
+ if ( ! $nginx_helper_admin->options['enable_purge'] ) {
134
+ return;
135
+ }
136
+
137
+ switch ( current_filter() ) {
138
+
139
+ case 'publish_post':
140
+ $this->log( '* * * * *' );
141
+ $this->log( '* Blog :: ' . addslashes( get_bloginfo( 'name' ) ) . ' ( ' . $blog_id . ' ).' );
142
+ $this->log( '* Post :: ' . get_the_title( $_ID ) . ' ( ' . $_ID . ' ).' );
143
+ $this->log( '* Post ( ' . $_ID . ' ) published or edited and its status is published' );
144
+ $this->log( '* * * * *' );
145
+ break;
146
+
147
+ case 'publish_page':
148
+ $this->log( '* * * * *' );
149
+ $this->log( '* Blog :: ' . addslashes( get_bloginfo( 'name' ) ) . ' ( ' . $blog_id . ' ).' );
150
+ $this->log( '* Page :: ' . get_the_title( $_ID ) . ' ( ' . $_ID . ' ).' );
151
+ $this->log( '* Page ( ' . $_ID . ' ) published or edited and its status is published' );
152
+ $this->log( '* * * * *' );
153
+ break;
154
+
155
+ case 'comment_post':
156
+ case 'wp_set_comment_status':
157
+ break;
158
+
159
+ default:
160
+ $_post_type = get_post_type( $_ID );
161
+ $this->log( '* * * * *' );
162
+ $this->log( '* Blog :: ' . addslashes( get_bloginfo( 'name' ) ) . ' ( ' . $blog_id . ' ).' );
163
+ $this->log( "* Custom post type '" . $_post_type . "' :: " . get_the_title( $_ID ) . ' ( ' . $_ID . ' ).' );
164
+ $this->log( "* CPT '" . $_post_type . "' ( " . $_ID . ' ) published or edited and its status is published' );
165
+ $this->log( '* * * * *' );
166
+ break;
167
+
168
+ }
169
+
170
+ $this->log( 'Function purge_post BEGIN ===' );
171
+
172
+ if ( 1 === (int) $nginx_helper_admin->options['purge_homepage_on_edit'] ) {
173
+ $this->_purge_homepage();
174
+ }
175
+
176
+ if ( 'comment_post' === current_filter() || 'wp_set_comment_status' === current_filter() ) {
177
+
178
+ $this->_purge_by_options(
179
+ $_ID, $blog_id, $nginx_helper_admin->options['purge_page_on_new_comment'], $nginx_helper_admin->options['purge_archive_on_new_comment'], $nginx_helper_admin->options['purge_archive_on_new_comment']
180
+ );
181
+
182
+ } else {
183
+
184
+ $this->_purge_by_options(
185
+ $_ID, $blog_id, $nginx_helper_admin->options['purge_page_on_mod'], $nginx_helper_admin->options['purge_archive_on_edit'], $nginx_helper_admin->options['purge_archive_on_edit']
186
+ );
187
+
188
+ }
189
+
190
+ $this->custom_purge_urls();
191
+
192
+ $this->log( 'Function purge_post END ^^^' );
193
+ }
194
+
195
+ /**
196
+ * Purge cache by options.
197
+ *
198
+ * @param int $post_id Post id.
199
+ * @param int $blog_id Blog id.
200
+ * @param bool $_purge_page Purge page or not.
201
+ * @param bool $_purge_archive Purge archive or not.
202
+ * @param bool $_purge_custom_taxa Purge taxonomy or not.
203
+ */
204
+ private function _purge_by_options( $post_id, $blog_id, $_purge_page, $_purge_archive, $_purge_custom_taxa ) {
205
+
206
+ $_post_type = get_post_type( $post_id );
207
+
208
+ if ( $_purge_page ) {
209
+
210
+ if ( 'post' === $_post_type || 'page' === $_post_type ) {
211
+ $this->log( 'Purging ' . $_post_type . ' ( id ' . $post_id . ', blog id ' . $blog_id . ' ) ' );
212
+ } else {
213
+ $this->log( "Purging custom post type '" . $_post_type . "' ( id " . $post_id . ', blog id ' . $blog_id . ' )' );
214
+ }
215
+
216
+ $url = get_permalink( $post_id );
217
+
218
+ if ( 'trash' === get_post_status( $post_id ) ) {
219
+ $url = str_replace( '__trashed', '', $url );
220
+ }
221
+
222
+ $this->purge_url( $url );
223
+
224
+ }
225
+
226
+ if ( $_purge_archive ) {
227
+
228
+ $_post_type_archive_link = get_post_type_archive_link( $_post_type );
229
+
230
+ if ( function_exists( 'get_post_type_archive_link' ) && $_post_type_archive_link ) {
231
+
232
+ $this->log( 'Purging post type archive ( ' . $_post_type . ' )' );
233
+ $this->purge_url( $_post_type_archive_link );
234
+
235
+ }
236
+
237
+ if ( 'post' === $_post_type ) {
238
+
239
+ $this->log( 'Purging date' );
240
+
241
+ $day = get_the_time( 'd', $post_id );
242
+ $month = get_the_time( 'm', $post_id );
243
+ $year = get_the_time( 'Y', $post_id );
244
+
245
+ if ( $year ) {
246
+
247
+ $this->purge_url( get_year_link( $year ) );
248
+
249
+ if ( $month ) {
250
+
251
+ $this->purge_url( get_month_link( $year, $month ) );
252
+
253
+ if ( $day ) {
254
+ $this->purge_url( get_day_link( $year, $month, $day ) );
255
+ }
256
+
257
+ }
258
+
259
+ }
260
+
261
+ }
262
+
263
+ $categories = wp_get_post_categories( $post_id );
264
+
265
+ if ( ! is_wp_error( $categories ) ) {
266
+
267
+ $this->log( 'Purging category archives' );
268
+
269
+ foreach ( $categories as $category_id ) {
270
+
271
+ $this->log( 'Purging category ' . $category_id );
272
+ $this->purge_url( get_category_link( $category_id ) );
273
+
274
+ }
275
+
276
+ }
277
+
278
+ $tags = get_the_tags( $post_id );
279
+
280
+ if ( ! is_wp_error( $tags ) && ! empty( $tags ) ) {
281
+
282
+ $this->log( 'Purging tag archives' );
283
+
284
+ foreach ( $tags as $tag ) {
285
+
286
+ $this->log( 'Purging tag ' . $tag->term_id );
287
+ $this->purge_url( get_tag_link( $tag->term_id ) );
288
+
289
+ }
290
+
291
+ }
292
+
293
+ $author_id = get_post( $post_id )->post_author;
294
+
295
+ if ( ! empty( $author_id ) ) {
296
+
297
+ $this->log( 'Purging author archive' );
298
+ $this->purge_url( get_author_posts_url( $author_id ) );
299
+
300
+ }
301
+
302
+ }
303
+
304
+ if ( $_purge_custom_taxa ) {
305
+
306
+ $custom_taxonomies = get_taxonomies(
307
+ array(
308
+ 'public' => true,
309
+ '_builtin' => false,
310
+ )
311
+ );
312
+
313
+ if ( ! empty( $custom_taxonomies ) ) {
314
+
315
+ $this->log( 'Purging custom taxonomies related' );
316
+
317
+ foreach ( $custom_taxonomies as $taxon ) {
318
+
319
+ if ( ! in_array( $taxon, array( 'category', 'post_tag', 'link_category' ), true ) ) {
320
+
321
+ $terms = get_the_terms( $post_id, $taxon );
322
+
323
+ if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
324
+
325
+ foreach ( $terms as $term ) {
326
+ $this->purge_url( get_term_link( $term, $taxon ) );
327
+ }
328
+
329
+ }
330
+
331
+ } else {
332
+ $this->log( "Your built-in taxonomy '" . $taxon . "' has param '_builtin' set to false.", 'WARNING' );
333
+ }
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Deletes local cache files without a 3rd party nginx module.
341
+ * Does not require any other modules. Requires that the cache be stored on the same server as WordPress. You must also be using the default nginx cache options (levels=1:2) and (fastcgi_cache_key "$scheme$request_method$host$request_uri").
342
+ * Read more on how this works here:
343
+ * https://www.digitalocean.com/community/tutorials/how-to-setup-fastcgi-caching-with-nginx-on-your-vps#purging-the-cache
344
+ *
345
+ * @param string $url URL to purge.
346
+ *
347
+ * @return bool
348
+ */
349
+ protected function delete_cache_file_for( $url ) {
350
+
351
+ // Verify cache path is set.
352
+ if ( ! defined( 'RT_WP_NGINX_HELPER_CACHE_PATH' ) ) {
353
+
354
+ $this->log( 'Error purging because RT_WP_NGINX_HELPER_CACHE_PATH was not defined. URL: ' . $url, 'ERROR' );
355
+ return false;
356
+
357
+ }
358
+
359
+ // Verify URL is valid.
360
+ $url_data = wp_parse_url( $url );
361
+ if ( ! $url_data ) {
362
+
363
+ $this->log( 'Error purging because specified URL did not appear to be valid. URL: ' . $url, 'ERROR' );
364
+ return false;
365
+
366
+ }
367
+
368
+ // Build a hash of the URL.
369
+ $hash = md5( $url_data['scheme'] . 'GET' . $url_data['host'] . $url_data['path'] );
370
+
371
+ // Ensure trailing slash.
372
+ $cache_path = RT_WP_NGINX_HELPER_CACHE_PATH;
373
+ $cache_path = ( '/' === substr( $cache_path, -1 ) ) ? $cache_path : $cache_path . '/';
374
+
375
+ // Set path to cached file.
376
+ $cached_file = $cache_path . substr( $hash, -1 ) . '/' . substr( $hash, -3, 2 ) . '/' . $hash;
377
+
378
+ // Verify cached file exists.
379
+ if ( ! file_exists( $cached_file ) ) {
380
+
381
+ $this->log( '- - ' . $url . ' is currently not cached ( checked for file: ' . $cached_file . ' )' );
382
+ return false;
383
+
384
+ }
385
+
386
+ // Delete the cached file.
387
+ if ( unlink( $cached_file ) ) {
388
+ $this->log( '- - ' . $url . ' *** PURGED ***' );
389
+ } else {
390
+ $this->log( '- - An error occurred deleting the cache file. Check the server logs for a PHP warning.', 'ERROR' );
391
+ }
392
+
393
+ }
394
+
395
+ /**
396
+ * Remote get data from url.
397
+ *
398
+ * @param string $url URL to do remote request.
399
+ */
400
+ protected function do_remote_get( $url ) {
401
+
402
+ $response = wp_remote_get( $url );
403
+
404
+ if ( is_wp_error( $response ) ) {
405
+
406
+ $_errors_str = implode( ' - ', $response->get_error_messages() );
407
+ $this->log( 'Error while purging URL. ' . $_errors_str, 'ERROR' );
408
+
409
+ } else {
410
+
411
+ if ( $response['response']['code'] ) {
412
+
413
+ switch ( $response['response']['code'] ) {
414
+
415
+ case 200:
416
+ $this->log( '- - ' . $url . ' *** PURGED ***' );
417
+ break;
418
+ case 404:
419
+ $this->log( '- - ' . $url . ' is currently not cached' );
420
+ break;
421
+ default:
422
+ $this->log( '- - ' . $url . ' not found ( ' . $response['response']['code'] . ' )', 'WARNING' );
423
+
424
+ }
425
+
426
+ }
427
+
428
+ }
429
+
430
+ }
431
+
432
+ /**
433
+ * Check http connection.
434
+ *
435
+ * @return string
436
+ */
437
+ public function check_http_connection() {
438
+
439
+ $purge_url = plugins_url( 'nginx-manager/check-proxy.php' );
440
+ $response = wp_remote_get( $purge_url );
441
+
442
+ if ( ! is_wp_error( $response ) && ( 'HTTP Connection OK' === $response['body'] ) ) {
443
+ return 'OK';
444
+ }
445
+
446
+ return 'KO';
447
+
448
+ }
449
+
450
+ /**
451
+ * Log file.
452
+ *
453
+ * @param string $msg Message to log.
454
+ * @param string $level Level.
455
+ *
456
+ * @return bool|void
457
+ */
458
+ public function log( $msg, $level = 'INFO' ) {
459
+
460
+ global $nginx_helper_admin;
461
+
462
+ if ( ! $nginx_helper_admin->options['enable_log'] ) {
463
+ return;
464
+ }
465
+
466
+ $log_levels = array(
467
+ 'INFO' => 0,
468
+ 'WARNING' => 1,
469
+ 'ERROR' => 2,
470
+ 'NONE' => 3,
471
+ );
472
+
473
+ if ( $log_levels[ $level ] >= $log_levels[ $nginx_helper_admin->options['log_level'] ] ) {
474
+
475
+ if ( $fp = fopen( $nginx_helper_admin->functional_asset_path() . 'nginx.log', 'a+' ) ) {
476
+
477
+ fwrite( $fp, "\n" . gmdate( 'Y-m-d H:i:s ' ) . ' | ' . $level . ' | ' . $msg );
478
+ fclose( $fp );
479
+
480
+ }
481
+
482
+ }
483
+
484
+ return true;
485
+
486
+ }
487
+
488
+ /**
489
+ * Check and truncate log file.
490
+ */
491
+ public function check_and_truncate_log_file() {
492
+
493
+ global $nginx_helper_admin;
494
+
495
+ $nginx_asset_path = $nginx_helper_admin->functional_asset_path() . 'nginx.log';
496
+
497
+ $max_size_allowed = ( is_numeric( $nginx_helper_admin->options['log_filesize'] ) ) ? $nginx_helper_admin->options['log_filesize'] * 1048576 : 5242880;
498
+
499
+ $fileSize = filesize( $nginx_asset_path );
500
+
501
+ if ( $fileSize > $max_size_allowed ) {
502
+
503
+ $offset = $fileSize - $max_size_allowed;
504
+ $file_content = file_get_contents( $nginx_asset_path, null, null, $offset );
505
+ $file_content = empty( $file_content ) ? '' : strstr( $file_content, "\n" );
506
+
507
+ if ( $file_content && $fp = fopen( $nginx_asset_path, 'w+' ) ) {
508
+
509
+ fwrite( $fp, $file_content );
510
+ fclose( $fp );
511
+ }
512
+ }
513
+ }
514
+
515
+ /**
516
+ * Purge image on edit.
517
+ *
518
+ * @param int $attachment_id Attachment id.
519
+ */
520
+ public function purge_image_on_edit( $attachment_id ) {
521
+
522
+ $this->log( 'Purging media on edit BEGIN ===' );
523
+
524
+ if ( wp_attachment_is_image( $attachment_id ) ) {
525
+
526
+ $this->purge_url( wp_get_attachment_url( $attachment_id ), false );
527
+ $attachment = wp_get_attachment_metadata( $attachment_id );
528
+
529
+ if ( ! empty( $attachment['sizes'] ) && is_array( $attachment['sizes'] ) ) {
530
+
531
+ foreach ( array_keys( $attachment['sizes'] ) as $size_name ) {
532
+
533
+ $resize_image = wp_get_attachment_image_src( $attachment_id, $size_name );
534
+
535
+ if ( $resize_image ) {
536
+ $this->purge_url( $resize_image[0], false );
537
+ }
538
+
539
+ }
540
+
541
+ }
542
+
543
+ $this->purge_url( get_attachment_link( $attachment_id ) );
544
+
545
+ } else {
546
+ $this->log( 'Media ( id ' . $attachment_id . ') edited: no image', 'WARNING' );
547
+ }
548
+
549
+ $this->log( 'Purging media on edit END ^^^' );
550
+
551
+ }
552
+
553
+ /**
554
+ * Purge cache on post moved to trash.
555
+ *
556
+ * @param string $new_status New post status.
557
+ * @param string $old_status Old post status.
558
+ * @param WP_Post $post Post object.
559
+ *
560
+ * @return bool|void
561
+ */
562
+ public function purge_on_post_moved_to_trash( $new_status, $old_status, $post ) {
563
+
564
+ global $nginx_helper_admin, $blog_id;
565
+
566
+ if ( ! $nginx_helper_admin->options['enable_purge'] ) {
567
+ return;
568
+ }
569
+
570
+ if ( 'trash' === $new_status ) {
571
+
572
+ $this->log( '# # # # #' );
573
+ $this->log( "# Post '$post->post_title' ( id " . $post->ID . ' ) moved to the trash.' );
574
+ $this->log( '# # # # #' );
575
+ $this->log( 'Function purge_on_post_moved_to_trash ( post id ' . $post->ID . ' ) BEGIN ===' );
576
+
577
+ if ( 1 === (int) $nginx_helper_admin->options['purge_homepage_on_del'] ) {
578
+ $this->_purge_homepage();
579
+ }
580
+
581
+ $this->_purge_by_options(
582
+ $post->ID,
583
+ $blog_id,
584
+ true,
585
+ $nginx_helper_admin->options['purge_archive_on_del'],
586
+ $nginx_helper_admin->options['purge_archive_on_del']
587
+ );
588
+
589
+ $this->log( 'Function purge_on_post_moved_to_trash ( post id ' . $post->ID . ' ) END ===' );
590
+ }
591
+
592
+ return true;
593
+
594
+ }
595
+
596
+ /**
597
+ * Purge cache of homepage.
598
+ *
599
+ * @return bool
600
+ */
601
+ private function _purge_homepage() {
602
+
603
+ // WPML installetd?.
604
+ if ( function_exists( 'icl_get_home_url' ) ) {
605
+
606
+ $homepage_url = trailingslashit( icl_get_home_url() );
607
+ $this->log( sprintf( __( 'Purging homepage (WPML) ', 'nginx-helper' ) . '%s', $homepage_url ) );
608
+
609
+ } else {
610
+
611
+ $homepage_url = trailingslashit( home_url() );
612
+ $this->log( sprintf( __( 'Purging homepage ', 'nginx-helper' ) . '%s', $homepage_url ) );
613
+
614
+ }
615
+
616
+ $this->purge_url( $homepage_url );
617
+
618
+ return true;
619
+
620
+ }
621
+
622
+ /**
623
+ * Purge personal urls.
624
+ *
625
+ * @return bool
626
+ */
627
+ private function _purge_personal_urls() {
628
+
629
+ global $nginx_helper_admin;
630
+
631
+ $this->log( __( 'Purging personal urls', 'nginx-helper' ) );
632
+
633
+ if ( isset( $nginx_helper_admin->options['purgeable_url']['urls'] ) ) {
634
+
635
+ foreach ( $nginx_helper_admin->options['purgeable_url']['urls'] as $url ) {
636
+ $this->purge_url( $url, false );
637
+ }
638
+
639
+ } else {
640
+ $this->log( '- ' . __( 'No personal urls available', 'nginx-helper' ) );
641
+ }
642
+
643
+ return true;
644
+
645
+ }
646
+
647
+ /**
648
+ * Purge post categories.
649
+ *
650
+ * @param int $_post_id Post id.
651
+ *
652
+ * @return bool
653
+ */
654
+ private function _purge_post_categories( $_post_id ) {
655
+
656
+ $this->log( __( 'Purging category archives', 'nginx-helper' ) );
657
+
658
+ $categories = wp_get_post_categories( $_post_id );
659
+
660
+ if ( ! is_wp_error( $categories ) && ! empty( $categories ) ) {
661
+
662
+ foreach ( $categories as $category_id ) {
663
+
664
+ $this->log( sprintf( __( "Purging category '%d'", 'nginx-helper' ), $category_id ) );
665
+ $this->purge_url( get_category_link( $category_id ) );
666
+
667
+ }
668
+
669
+ }
670
+
671
+ return true;
672
+ }
673
+
674
+ /**
675
+ * Purge post tags.
676
+ *
677
+ * @param int $_post_id Post id.
678
+ *
679
+ * @return bool
680
+ */
681
+ private function _purge_post_tags( $_post_id ) {
682
+
683
+ $this->log( __( 'Purging tags archives', 'nginx-helper' ) );
684
+
685
+ $tags = get_the_tags( $_post_id );
686
+
687
+ if ( ! is_wp_error( $tags ) && ! empty( $tags ) ) {
688
+
689
+ foreach ( $tags as $tag ) {
690
+
691
+ $this->log( sprintf( __( "Purging tag '%1\$s' ( id %2\$d )", 'nginx-helper' ), $tag->name, $tag->term_id ) );
692
+ $this->purge_url( get_tag_link( $tag->term_id ) );
693
+
694
+ }
695
+
696
+ }
697
+
698
+ return true;
699
+
700
+ }
701
+
702
+ /**
703
+ * Purge post custom taxonomy data.
704
+ *
705
+ * @param int $_post_id Post id.
706
+ *
707
+ * @return bool
708
+ */
709
+ private function _purge_post_custom_taxa( $_post_id ) {
710
+
711
+ $this->log( __( 'Purging post custom taxonomies related', 'nginx-helper' ) );
712
+
713
+ $custom_taxonomies = get_taxonomies(
714
+ array(
715
+ 'public' => true,
716
+ '_builtin' => false,
717
+ )
718
+ );
719
+
720
+ if ( ! empty( $custom_taxonomies ) ) {
721
+
722
+ foreach ( $custom_taxonomies as $taxon ) {
723
+
724
+ $this->log( sprintf( '+ ' . __( "Purging custom taxonomy '%s'", 'nginx-helper' ), $taxon ) );
725
+
726
+ if ( ! in_array( $taxon, array( 'category', 'post_tag', 'link_category' ), true ) ) {
727
+
728
+ $terms = get_the_terms( $_post_id, $taxon );
729
+
730
+ if ( ! is_wp_error( $terms ) && ! empty( $terms ) && is_array( $terms ) ) {
731
+
732
+ foreach ( $terms as $term ) {
733
+ $this->purge_url( get_term_link( $term, $taxon ) );
734
+ }
735
+
736
+ }
737
+
738
+ } else {
739
+ $this->log( sprintf( '- ' . __( "Your built-in taxonomy '%s' has param '_builtin' set to false.", 'nginx-helper' ), $taxon ), 'WARNING' );
740
+ }
741
+ }
742
+
743
+ } else {
744
+ $this->log( '- ' . __( 'No custom taxonomies', 'nginx-helper' ) );
745
+ }
746
+
747
+ return true;
748
+ }
749
+
750
+ /**
751
+ * Purge all categories.
752
+ *
753
+ * @return bool
754
+ */
755
+ private function _purge_all_categories() {
756
+
757
+ $this->log( __( 'Purging all categories', 'nginx-helper' ) );
758
+
759
+ $_categories = get_categories();
760
+
761
+ if ( ! empty( $_categories ) ) {
762
+
763
+ foreach ( $_categories as $c ) {
764
+
765
+ $this->log( sprintf( __( "Purging category '%1\$s' ( id %2\$d )", 'nginx-helper' ), $c->name, $c->term_id ) );
766
+ $this->purge_url( get_category_link( $c->term_id ) );
767
+
768
+ }
769
+
770
+ } else {
771
+
772
+ $this->log( __( 'No categories archives', 'nginx-helper' ) );
773
+
774
+ }
775
+
776
+ return true;
777
+ }
778
+
779
+ /**
780
+ * Purge all posttags cache.
781
+ *
782
+ * @return bool
783
+ */
784
+ private function _purge_all_posttags() {
785
+
786
+ $this->log( __( 'Purging all tags', 'nginx-helper' ) );
787
+
788
+ $_posttags = get_tags();
789
+
790
+ if ( ! empty( $_posttags ) ) {
791
+
792
+ foreach ( $_posttags as $t ) {
793
+
794
+ $this->log( sprintf( __( "Purging tag '%1\$s' ( id %2\$d )", 'nginx-helper' ), $t->name, $t->term_id ) );
795
+ $this->purge_url( get_tag_link( $t->term_id ) );
796
+
797
+ }
798
+
799
+ } else {
800
+ $this->log( __( 'No tags archives', 'nginx-helper' ) );
801
+ }
802
+
803
+ return true;
804
+
805
+ }
806
+
807
+ /**
808
+ * Purge all custom taxonomy data.
809
+ *
810
+ * @return bool
811
+ */
812
+ private function _purge_all_customtaxa() {
813
+
814
+ $this->log( __( 'Purging all custom taxonomies', 'nginx-helper' ) );
815
+
816
+ $custom_taxonomies = get_taxonomies(
817
+ array(
818
+ 'public' => true,
819
+ '_builtin' => false,
820
+ )
821
+ );
822
+
823
+ if ( ! empty( $custom_taxonomies ) ) {
824
+
825
+ foreach ( $custom_taxonomies as $taxon ) {
826
+
827
+ $this->log( sprintf( '+ ' . __( "Purging custom taxonomy '%s'", 'nginx-helper' ), $taxon ) );
828
+
829
+ if ( ! in_array( $taxon, array( 'category', 'post_tag', 'link_category' ), true ) ) {
830
+
831
+ $terms = get_terms( $taxon );
832
+
833
+ if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
834
+
835
+ foreach ( $terms as $term ) {
836
+
837
+ $this->purge_url( get_term_link( $term, $taxon ) );
838
+
839
+ }
840
+
841
+ }
842
+
843
+ } else {
844
+ $this->log( sprintf( '- ' . __( "Your built-in taxonomy '%s' has param '_builtin' set to false.", 'nginx-helper' ), $taxon ), 'WARNING' );
845
+ }
846
+
847
+ }
848
+
849
+ } else {
850
+ $this->log( '- ' . __( 'No custom taxonomies', 'nginx-helper' ) );
851
+ }
852
+
853
+ return true;
854
+ }
855
+
856
+ /**
857
+ * Purge all taxonomies.
858
+ *
859
+ * @return bool
860
+ */
861
+ private function _purge_all_taxonomies() {
862
+
863
+ $this->_purge_all_categories();
864
+ $this->_purge_all_posttags();
865
+ $this->_purge_all_customtaxa();
866
+
867
+ return true;
868
+ }
869
+
870
+ /**
871
+ * Purge all posts cache.
872
+ *
873
+ * @return bool
874
+ */
875
+ private function _purge_all_posts() {
876
+
877
+ $this->log( __( 'Purging all posts, pages and custom post types.', 'nginx-helper' ) );
878
+
879
+ $args = array(
880
+ 'posts_per_page' => 0,
881
+ 'post_type' => 'any',
882
+ 'post_status' => 'publish',
883
+ );
884
+
885
+ $get_posts = new WP_Query;
886
+ $_posts = $get_posts->query( $args );
887
+
888
+ if ( ! empty( $_posts ) ) {
889
+
890
+ foreach ( $_posts as $p ) {
891
+
892
+ $this->log( sprintf( '+ ' . __( "Purging post id '%1\$d' ( post type '%2\$s' )", 'nginx-helper' ), $p->ID, $p->post_type ) );
893
+ $this->purge_url( get_permalink( $p->ID ) );
894
+
895
+ }
896
+
897
+ } else {
898
+ $this->log( '- ' . __( 'No posts', 'nginx-helper' ) );
899
+ }
900
+
901
+ return true;
902
+
903
+ }
904
+
905
+ /**
906
+ * Purge all archives.
907
+ *
908
+ * @return bool
909
+ */
910
+ private function _purge_all_date_archives() {
911
+
912
+ $this->log( __( 'Purging all date-based archives.', 'nginx-helper' ) );
913
+
914
+ $this->_purge_all_daily_archives();
915
+ $this->_purge_all_monthly_archives();
916
+ $this->_purge_all_yearly_archives();
917
+
918
+ return true;
919
+
920
+ }
921
+
922
+ /**
923
+ * Purge daily archives cache.
924
+ */
925
+ private function _purge_all_daily_archives() {
926
+
927
+ global $wpdb;
928
+
929
+ $this->log( __( 'Purging all daily archives.', 'nginx-helper' ) );
930
+
931
+ $_query_daily_archives = $wpdb->prepare(
932
+ "SELECT YEAR(post_date) AS %s, MONTH(post_date) AS %s, DAYOFMONTH(post_date) AS %s, count(ID) as posts
933
+ FROM $wpdb->posts
934
+ WHERE post_type = %s AND post_status = %s
935
+ GROUP BY YEAR(post_date), MONTH(post_date), DAYOFMONTH(post_date)
936
+ ORDER BY post_date DESC",
937
+ 'year', 'month', 'dayofmonth', 'post', 'publish'
938
+ );
939
+
940
+ $_daily_archives = $wpdb->get_results( $_query_daily_archives ); // phpcs:ignore
941
+
942
+ if ( ! empty( $_daily_archives ) ) {
943
+
944
+ foreach ( $_daily_archives as $_da ) {
945
+
946
+ $this->log(
947
+ sprintf(
948
+ '+ ' . __( "Purging daily archive '%1\$s/%2\$s/%3\$s'", 'nginx-helper' ), $_da->year, $_da->month, $_da->dayofmonth
949
+ )
950
+ );
951
+
952
+ $this->purge_url( get_day_link( $_da->year, $_da->month, $_da->dayofmonth ) );
953
+
954
+ }
955
+
956
+ } else {
957
+ $this->log( '- ' . __( 'No daily archives', 'nginx-helper' ) );
958
+ }
959
+
960
+ }
961
+
962
+ /**
963
+ * Purge all monthly archives.
964
+ */
965
+ private function _purge_all_monthly_archives() {
966
+
967
+ global $wpdb;
968
+
969
+ $this->log( __( 'Purging all monthly archives.', 'nginx-helper' ) );
970
+
971
+ $_monthly_archives = wp_cache_get( 'nginx_helper_monthly_archives', 'nginx_helper' );
972
+
973
+ if ( empty( $_monthly_archives ) ) {
974
+
975
+ $_query_monthly_archives = $wpdb->prepare(
976
+ "SELECT YEAR(post_date) AS %s, MONTH(post_date) AS %s, count(ID) as posts
977
+ FROM $wpdb->posts
978
+ WHERE post_type = %s AND post_status = %s
979
+ GROUP BY YEAR(post_date), MONTH(post_date)
980
+ ORDER BY post_date DESC",
981
+ 'year', 'month', 'post', 'publish'
982
+ );
983
+
984
+ $_monthly_archives = $wpdb->get_results( $_query_monthly_archives ); // phpcs:ignore
985
+
986
+ wp_cache_set( 'nginx_helper_monthly_archives', $_monthly_archives, 'nginx_helper', 24*60*60 );
987
+
988
+ }
989
+
990
+
991
+ if ( ! empty( $_monthly_archives ) ) {
992
+
993
+ foreach ( $_monthly_archives as $_ma ) {
994
+
995
+ $this->log( sprintf( '+ ' . __( "Purging monthly archive '%1\$s/%2\$s'", 'nginx-helper' ), $_ma->year, $_ma->month ) );
996
+ $this->purge_url( get_month_link( $_ma->year, $_ma->month ) );
997
+
998
+ }
999
+
1000
+ } else {
1001
+ $this->log( '- ' . __( 'No monthly archives', 'nginx-helper' ) );
1002
+ }
1003
+
1004
+ }
1005
+
1006
+ /**
1007
+ * Purge all yearly archive cache.
1008
+ */
1009
+ private function _purge_all_yearly_archives() {
1010
+
1011
+ global $wpdb;
1012
+
1013
+ $this->log( __( 'Purging all yearly archives.', 'nginx-helper' ) );
1014
+
1015
+ $_yearly_archives = wp_cache_get( 'nginx_helper_yearly_archives', 'nginx_helper' );
1016
+
1017
+ if ( empty( $_yearly_archives ) ) {
1018
+
1019
+ $_query_yearly_archives = $wpdb->prepare(
1020
+ "SELECT YEAR(post_date) AS %s, count(ID) as posts
1021
+ FROM $wpdb->posts
1022
+ WHERE post_type = %s AND post_status = %s
1023
+ GROUP BY YEAR(post_date)
1024
+ ORDER BY post_date DESC",
1025
+ 'year', 'post', 'publish'
1026
+ );
1027
+
1028
+ $_yearly_archives = $wpdb->get_results( $_query_yearly_archives ); // phpcs:ignore
1029
+
1030
+ wp_cache_set( 'nginx_helper_yearly_archives', $_yearly_archives, 'nginx_helper', 24*60*60 );
1031
+
1032
+ }
1033
+
1034
+ if ( ! empty( $_yearly_archives ) ) {
1035
+
1036
+ foreach ( $_yearly_archives as $_ya ) {
1037
+
1038
+ $this->log( sprintf( '+ ' . __( "Purging yearly archive '%s'", 'nginx-helper' ), $_ya->year ) );
1039
+ $this->purge_url( get_year_link( $_ya->year ) );
1040
+
1041
+ }
1042
+
1043
+ } else {
1044
+ $this->log( '- ' . __( 'No yearly archives', 'nginx-helper' ) );
1045
+ }
1046
+
1047
+ }
1048
+
1049
+ /**
1050
+ * Purge all cache.
1051
+ *
1052
+ * @return bool
1053
+ */
1054
+ public function purge_them_all() {
1055
+
1056
+ $this->log( __( "Let's purge everything!", 'nginx-helper' ) );
1057
+ $this->_purge_homepage();
1058
+ $this->_purge_personal_urls();
1059
+ $this->_purge_all_posts();
1060
+ $this->_purge_all_taxonomies();
1061
+ $this->_purge_all_date_archives();
1062
+ $this->log( __( 'Everything purged!', 'nginx-helper' ) );
1063
+
1064
+ return true;
1065
+
1066
+ }
1067
+
1068
+ /**
1069
+ * Purge cache on term edited.
1070
+ *
1071
+ * @param int $term_id Term id.
1072
+ * @param int $tt_id Taxonomy id.
1073
+ * @param string $taxon Taxonomy.
1074
+ *
1075
+ * @return bool
1076
+ */
1077
+ public function purge_on_term_taxonomy_edited( $term_id, $tt_id, $taxon ) {
1078
+
1079
+ $this->log( __( 'Term taxonomy edited or deleted', 'nginx-helper' ) );
1080
+
1081
+ $term = get_term( $term_id, $taxon );
1082
+ $current_filter = current_filter();
1083
+
1084
+ if ( 'edit_term' === $current_filter && ! is_wp_error( $term ) && ! empty( $term ) ) {
1085
+
1086
+ $this->log( sprintf( __( "Term taxonomy '%1\$s' edited, (tt_id '%2\$d', term_id '%3\$d', taxonomy '%4\$s')", 'nginx-helper' ), $term->name, $tt_id, $term_id, $taxon ) );
1087
+
1088
+ } elseif ( 'delete_term' === $current_filter ) {
1089
+
1090
+ $this->log( sprintf( __( "A term taxonomy has been deleted from taxonomy '%1\$s', (tt_id '%2\$d', term_id '%3\$d')", 'nginx-helper' ), $taxon, $term_id, $tt_id ) );
1091
+
1092
+ }
1093
+
1094
+ $this->_purge_homepage();
1095
+
1096
+ return true;
1097
+
1098
+ }
1099
+
1100
+ /**
1101
+ * Check ajax referrer on purge.
1102
+ *
1103
+ * @param string $action The Ajax nonce action.
1104
+ *
1105
+ * @return bool
1106
+ */
1107
+ public function purge_on_check_ajax_referer( $action ) {
1108
+
1109
+ switch ( $action ) {
1110
+
1111
+ case 'save-sidebar-widgets':
1112
+ $this->log( __( 'Widget saved, moved or removed in a sidebar', 'nginx-helper' ) );
1113
+ $this->_purge_homepage();
1114
+ break;
1115
+
1116
+ default:
1117
+ break;
1118
+
1119
+ }
1120
+
1121
+ return true;
1122
+
1123
+ }
1124
+
1125
+ /**
1126
+ * Unlink file recursively.
1127
+ * Source - http://stackoverflow.com/a/1360437/156336
1128
+ *
1129
+ * @param string $dir Directory.
1130
+ * @param bool $deleteRootToo Delete root or not.
1131
+ */
1132
+ public function unlink_recursive( $dir, $deleteRootToo ) {
1133
+
1134
+ if ( ! is_dir( $dir ) ) {
1135
+ return;
1136
+ }
1137
+
1138
+ if ( ! $dh = opendir( $dir ) ) {
1139
+ return;
1140
+ }
1141
+
1142
+ while ( false !== ( $obj = readdir( $dh ) ) ) {
1143
+
1144
+ if ( $obj == '.' || $obj == '..' ) {
1145
+ continue;
1146
+ }
1147
+
1148
+ if ( ! @unlink( $dir . '/' . $obj ) ) {
1149
+ $this->unlink_recursive( $dir . '/' . $obj, false );
1150
+ }
1151
+
1152
+ }
1153
+
1154
+ if ( $deleteRootToo ) {
1155
+ rmdir( $dir );
1156
+ }
1157
+
1158
+ closedir( $dh );
1159
+
1160
+ return;
1161
+
1162
+ }
1163
+
1164
+ }
admin/css/nginx-helper-admin.css ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * All of the CSS for your admin-specific functionality should be
3
+ * included in this file.
4
+ */
5
+
6
+ .clearfix {
7
+ *zoom: 1;
8
+ }
9
+ .clearfix:before,
10
+ .clearfix:after {
11
+ content: " ";
12
+ display: table;
13
+ }
14
+ .clearfix:after {
15
+ clear: both;
16
+ }
17
+ h4 {
18
+ margin: 0;
19
+ }
20
+ .form-table th,
21
+ .form-wrap label {
22
+ vertical-align: middle;
23
+ }
24
+ table.rtnginx-table {
25
+ border-bottom: 1px solid #EEE;
26
+ }
27
+ table.rtnginx-table:last-child {
28
+ border-bottom: 0;
29
+ }
30
+ .rtnginx-table p.error {
31
+ color: red;
32
+ }
33
+ pre#map {
34
+ background: #e5e5e5 none;
35
+ border-radius: 10px;
36
+ padding: 10px;
37
+ }
38
+ .wrap h2.rt_option_title {
39
+ background: url(../icons/nginx-icon-32x32.png) 0 6px no-repeat rgba(0, 0, 0, 0);
40
+ padding-left: 40px;
41
+ }
42
+ #poststuff h2 {
43
+ padding: 0 0 0 10px;
44
+ margin-top: 0;
45
+ }
46
+ form#purgeall .button-primary {
47
+ margin-bottom: 20px;
48
+ box-shadow: inset 0 -2px rgba(0, 0, 0, 0.14);
49
+ padding: 15px 30px;
50
+ font-size: 1rem;
51
+ border: 0;
52
+ border-radius: 5px;
53
+ color: #FFF;
54
+ background: #DD3D36;
55
+ height: auto;
56
+ }
57
+ form#purgeall .button-primary:hover,
58
+ form#purgeall .button-primary:focus {
59
+ background: #d52c24;
60
+ }
61
+ .nh-aligncenter {
62
+ display: block;
63
+ text-align: center;
64
+ line-height: 2;
65
+ }
66
+ #latest_news .inside ul,
67
+ #useful-links .inside ul {
68
+ margin: 0 0 0 12px;
69
+ }
70
+ #latest_news .inside ul li,
71
+ #useful-links .inside ul li {
72
+ list-style: square;
73
+ padding: 0 0 7px;
74
+ }
75
+ #social .inside a {
76
+ background-color: #666;
77
+ color: #FFF;
78
+ display: inline-block;
79
+ height: 30px;
80
+ font-size: 1.25rem;
81
+ line-height: 30px;
82
+ margin: 10px 20px 0 0;
83
+ overflow: hidden;
84
+ padding: 0;
85
+ text-align: center;
86
+ text-decoration: none;
87
+ width: 30px;
88
+ -webkit-border-radius: 1000px;
89
+ -moz-border-radius: 1000px;
90
+ border-radius: 1000px;
91
+ }
92
+ #social .inside .nginx-helper-rss:hover {
93
+ background-color: #FAA33D;
94
+ }
95
+ #social .inside .nginx-helper-facebook:hover {
96
+ background-color: #537BBD;
97
+ }
98
+ #social .inside .nginx-helper-twitter:hover {
99
+ background-color: #40BFF5;
100
+ }
101
+ #social .inside .nginx-helper-gplus:hover {
102
+ background-color: #DD4B39;
103
+ }
104
+ .rt-purge_url { width: 100%; }
admin/icons/config.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "nginx-fontello",
3
+ "css_prefix_text": "nginx-helper-",
4
+ "css_use_suffix": false,
5
+ "hinting": true,
6
+ "units_per_em": 1000,
7
+ "ascent": 850,
8
+ "glyphs": [
9
+ {
10
+ "uid": "72b1277834cba5b7944b0a6cac7ddb0d",
11
+ "css": "rss",
12
+ "code": 59395,
13
+ "src": "fontawesome"
14
+ },
15
+ {
16
+ "uid": "627abcdb627cb1789e009c08e2678ef9",
17
+ "css": "twitter",
18
+ "code": 59394,
19
+ "src": "fontawesome"
20
+ },
21
+ {
22
+ "uid": "bc50457410acf467b8b5721240768742",
23
+ "css": "facebook",
24
+ "code": 59393,
25
+ "src": "entypo"
26
+ },
27
+ {
28
+ "uid": "b945f4ac2439565661e8e4878e35d379",
29
+ "css": "gplus",
30
+ "code": 59392,
31
+ "src": "entypo"
32
+ }
33
+ ]
34
+ }
admin/icons/css/nginx-fontello.css ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @font-face {
2
+ font-family: 'nginx-fontello';
3
+ src: url('../font/nginx-fontello.eot?7388141');
4
+ src: url('../font/nginx-fontello.eot?7388141#iefix') format('embedded-opentype'),
5
+ url('../font/nginx-fontello.woff?7388141') format('woff'),
6
+ url('../font/nginx-fontello.ttf?7388141') format('truetype'),
7
+ url('../font/nginx-fontello.svg?7388141#nginx-fontello') format('svg');
8
+ font-weight: normal;
9
+ font-style: normal;
10
+ }
11
+ /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
12
+ /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
13
+ /*
14
+ @media screen and (-webkit-min-device-pixel-ratio:0) {
15
+ @font-face {
16
+ font-family: 'nginx-fontello';
17
+ src: url('../font/nginx-fontello.svg?7388141#nginx-fontello') format('svg');
18
+ }
19
+ }
20
+ */
21
+
22
+ [class^="nginx-helper-"]:before, [class*=" nginx-helper-"]:before {
23
+ font-family: "nginx-fontello";
24
+ font-style: normal;
25
+ font-weight: normal;
26
+ speak: none;
27
+
28
+ display: inline-block;
29
+ text-decoration: inherit;
30
+ width: 1em;
31
+ margin-right: .2em;
32
+ text-align: center;
33
+ /* opacity: .8; */
34
+
35
+ /* For safety - reset parent styles, that can break glyph codes*/
36
+ font-variant: normal;
37
+ text-transform: none;
38
+
39
+ /* fix buttons height, for twitter bootstrap */
40
+ line-height: 1em;
41
+
42
+ /* Animation center compensation - margins should be symmetric */
43
+ /* remove if not needed */
44
+ margin-left: .2em;
45
+
46
+ /* you can be more comfortable with increased icons size */
47
+ /* font-size: 120%; */
48
+
49
+ /* Uncomment for 3D effect */
50
+ /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
51
+ }
52
+
53
+ .nginx-helper-rss:before { content: '\e803'; } /* '' */
54
+ .nginx-helper-twitter:before { content: '\e802'; } /* '' */
55
+ .nginx-helper-facebook:before { content: '\e801'; } /* '' */
56
+ .nginx-helper-gplus:before { content: '\e800'; } /* '' */
admin/icons/font/nginx-fontello.eot ADDED
Binary file
admin/icons/font/nginx-fontello.svg ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg xmlns="http://www.w3.org/2000/svg">
4
+ <metadata>Copyright (C) 2013 by original authors @ fontello.com</metadata>
5
+ <defs>
6
+ <font id="nginx-fontello" horiz-adv-x="1000" >
7
+ <font-face font-family="nginx-fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
8
+ <missing-glyph horiz-adv-x="1000" />
9
+ <glyph glyph-name="rss" unicode="&#xe803;" d="m214 100q0-45-31-76t-76-31t-76 31t-31 76t31 76t76 31t76-31t31-76z m286-69q1-15-9-26q-11-12-27-12h-75q-14 0-24 9t-11 23q-12 128-103 219t-219 103q-14 1-23 11t-9 24v75q0 16 12 26q9 10 24 10h3q89-7 170-45t145-101q63-63 101-145t45-171z m286-1q1-15-10-26q-10-11-26-11h-80q-14 0-25 10t-11 23q-6 120-56 228t-129 188t-188 129t-227 57q-14 0-24 11t-10 24v80q0 15 11 26q10 10 25 10h1q147-8 280-67t238-164q104-104 164-238t67-280z" horiz-adv-x="785.7" />
10
+ <glyph glyph-name="twitter" unicode="&#xe802;" d="m904 622q-37-54-90-93q0-8 0-23q0-73-21-145t-64-139t-103-117t-144-82t-181-30q-151 0-276 81q19-3 43-3q126 0 224 77q-59 2-105 36t-64 89q19-2 34-2q24 0 48 6q-63 13-104 62t-41 115v2q38-21 82-23q-37 25-59 64t-22 86q0 49 25 91q68-83 164-133t208-55q-5 21-5 41q0 75 53 127t127 53q79 0 132-57q61 12 114 44q-20-64-79-100q52 6 104 28z" horiz-adv-x="928.6" />
11
+ <glyph glyph-name="facebook" unicode="&#xe801;" d="m500 644l-142 0q-14 0-25-15t-11-37l0-102l178 0l0-148l-178 0l0-442l-170 0l0 442l-152 0l0 148l152 0l0 86q0 94 59 159t147 65l142 0l0-156z" horiz-adv-x="500" />
12
+ <glyph glyph-name="gplus" unicode="&#xe800;" d="m48 572q0 58 25 102t56 65t69 34t56 15t26 2l230 0l0-4q0-22-78-36q-28 0-38-6q40-20 54-56t14-96q0-102-68-158q-38-38-38-54q0-18 50-64q104-90 104-178q0-140-116-194q-68-34-150-34l-4 0l-4 2q-2-2-4-2q-24 0-54 5t-75 21t-74 57t-29 103q0 60 32 101t83 57t88 22t71 6l2 0q-16 22-24 47t-8 39l2 14l-14 0q-64 0-110 30q-74 44-74 160z m370-452q-4 52-43 84t-103 32l-16 0q-64-2-114-46q-46-42-42-94t53-80t119-24q68 4 109 40t37 88z m-60 500q-30 108-122 108q-12 0-20-2q-40-12-58-62q-16-50-2-106q14-52 47-85t71-33q12 0 18 2q42 12 63 65t3 113z m388-174l150 0l0-94l-150 0l0-150l-94 0l0 150l-150 0l0 94l150 0l0 150l94 0l0-150z" horiz-adv-x="896" />
13
+ </font>
14
+ </defs>
15
+ </svg>
admin/icons/font/nginx-fontello.ttf ADDED
Binary file
admin/icons/font/nginx-fontello.woff ADDED
Binary file
admin/icons/nginx-icon-32x32.png ADDED
Binary file
admin/index.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Silence is golden.
4
+ *
5
+ * @package nginx-helper
6
+ */
7
+
8
+ // Silence.
admin/js/nginx-helper-admin.js ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function ($) {
2
+ 'use strict';
3
+
4
+ /**
5
+ * All of the code for your admin-specific JavaScript source
6
+ * should reside in this file.
7
+ *
8
+ * Note that this assume you're going to use jQuery, so it prepares
9
+ * the $ function reference to be used within the scope of this
10
+ * function.
11
+ *
12
+ * From here, you're able to define handlers for when the DOM is
13
+ * ready:
14
+ *
15
+ * $(function() {
16
+ *
17
+ * });
18
+ *
19
+ * Or when the window is loaded:
20
+ *
21
+ * $( window ).load(function() {
22
+ *
23
+ * });
24
+ *
25
+ * ...and so on.
26
+ *
27
+ * Remember that ideally, we should not attach any more than a single DOM-ready or window-load handler
28
+ * for any particular page. Though other scripts in WordPress core, other plugins, and other themes may
29
+ * be doing this, we should try to minimize doing that in our own work.
30
+ */
31
+ $(function () {
32
+
33
+ var news_section = jQuery( '#latest_news' );
34
+
35
+ if ( news_section.length > 0 ) {
36
+
37
+ var args = {
38
+ 'action': 'rt_get_feeds'
39
+ };
40
+
41
+ jQuery.get( ajaxurl, args, function (data) {
42
+ news_section.find( '.inside' ).html( data );
43
+ console.log(data);
44
+ });
45
+
46
+ }
47
+
48
+ jQuery( "form#purgeall a" ).click( function (e) {
49
+
50
+ if ( confirm( "Purging entire cache is not recommended. Would you like to continue ?" ) == true ) {
51
+ // continue submitting form
52
+ } else {
53
+ e.preventDefault();
54
+ }
55
+
56
+ });
57
+
58
+ /**
59
+ * Show OR Hide options on option checkbox
60
+ * @param {type} selector Selector of Checkbox and PostBox
61
+ */
62
+ function nginx_show_option( selector ) {
63
+
64
+ jQuery( '#' + selector ).on( 'change', function () {
65
+
66
+ if ( jQuery( this ).is( ':checked' ) ) {
67
+
68
+ jQuery( '.' + selector ).show();
69
+
70
+ if ( selector == "cache_method_redis" ) {
71
+ jQuery( '.cache_method_fastcgi' ).hide();
72
+ } else if ( selector == "cache_method_fastcgi" ) {
73
+ jQuery( '.cache_method_redis' ).hide();
74
+ }
75
+
76
+ } else {
77
+ jQuery( '.' + selector ).hide();
78
+ }
79
+
80
+ });
81
+
82
+ }
83
+
84
+ /* Function call with parameter */
85
+ nginx_show_option( 'cache_method_fastcgi' );
86
+ nginx_show_option( 'cache_method_redis' );
87
+ nginx_show_option( 'enable_map' );
88
+ nginx_show_option( 'enable_log' );
89
+ nginx_show_option( 'enable_purge' );
90
+
91
+ });
92
+ })(jQuery);
admin/partials/nginx-helper-admin-display.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Provide a admin area view for the plugin
4
+ *
5
+ * This file is used to markup the admin-facing aspects of the plugin.
6
+ *
7
+ * @link http://example.com
8
+ * @since 2.0.0
9
+ *
10
+ * @package nginx-helper
11
+ * @subpackage nginx-helper/admin/partials
12
+ */
13
+
14
+ global $pagenow;
15
+ ?>
16
+
17
+ <!-- This file should primarily consist of HTML with a little bit of PHP. -->
18
+
19
+ <div class="wrap rt-nginx-wrapper">
20
+ <h2 class="rt_option_title">
21
+ <?php esc_html_e( 'Nginx Settings', 'nginx-helper' ); ?>
22
+ </h2>
23
+ <div id="poststuff">
24
+ <div id="post-body" class="metabox-holder columns-2">
25
+ <div id="post-body-content">
26
+ <?php
27
+ /* Show settinhs tabs */
28
+ $current_tab = filter_input( INPUT_GET, 'tab', FILTER_SANITIZE_STRING );
29
+ $current_setting_tab = ( ! empty( $current_tab ) ) ? $current_tab : 'general';
30
+
31
+ echo '<h2 class="nav-tab-wrapper">';
32
+ foreach ( $this->settings_tabs as $setting_tab => $setting_name ) {
33
+
34
+ $class = ( $setting_tab === $current_setting_tab ) ? ' nav-tab-active' : '';
35
+ echo wp_kses(
36
+ sprintf(
37
+ '<a class="nav-tab%1$s" href="?page=nginx&tab=%2$s">%3$s</a>',
38
+ esc_attr( $class ), esc_attr( $setting_name['menu_slug'] ), esc_html( $setting_name['menu_title'] )
39
+ ),
40
+ array( 'a' => array( 'href' => array(), 'class' => array(), ) )
41
+ );
42
+ }
43
+ echo '</h2>';
44
+
45
+ switch ( $current_setting_tab ) {
46
+
47
+ case 'general':
48
+ include plugin_dir_path(__FILE__ ) . 'nginx-helper-general-options.php';
49
+ break;
50
+ case 'support':
51
+ include plugin_dir_path(__FILE__ ) . 'nginx-helper-support-options.php';
52
+ break;
53
+
54
+ }
55
+ ?>
56
+ </div> <!-- End of #post-body-content -->
57
+ <div id="postbox-container-1" class="postbox-container">
58
+ <?php
59
+ require plugin_dir_path(__FILE__ ) . 'nginx-helper-sidebar-display.php';
60
+ ?>
61
+ </div> <!-- End of #postbox-container-1 -->
62
+ </div> <!-- End of #post-body -->
63
+ </div> <!-- End of #poststuff -->
64
+ </div> <!-- End of .wrap .rt-nginx-wrapper -->
admin/partials/nginx-helper-general-options.php ADDED
@@ -0,0 +1,743 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Display general options of the plugin.
4
+ *
5
+ * This file is used to markup the admin-facing aspects of the plugin.
6
+ *
7
+ * @since 2.0.0
8
+ *
9
+ * @package nginx-helper
10
+ * @subpackage nginx-helper/admin/partials
11
+ */
12
+
13
+ global $nginx_helper_admin;
14
+
15
+ $error_log_filesize = false;
16
+
17
+ $args = array(
18
+ 'enable_purge' => FILTER_SANITIZE_STRING,
19
+ 'enable_stamp' => FILTER_SANITIZE_STRING,
20
+ 'purge_method' => FILTER_SANITIZE_STRING,
21
+ 'is_submit' => FILTER_SANITIZE_STRING,
22
+ 'redis_hostname' => FILTER_SANITIZE_STRING,
23
+ 'redis_port' => FILTER_SANITIZE_STRING,
24
+ 'redis_prefix' => FILTER_SANITIZE_STRING,
25
+ 'purge_homepage_on_edit' => FILTER_SANITIZE_STRING,
26
+ 'purge_homepage_on_del' => FILTER_SANITIZE_STRING,
27
+ 'purge_url' => FILTER_SANITIZE_STRING,
28
+ 'log_level' => FILTER_SANITIZE_STRING,
29
+ 'log_filesize' => FILTER_SANITIZE_STRING,
30
+ 'smart_http_expire_save' => FILTER_SANITIZE_STRING,
31
+ 'cache_method' => FILTER_SANITIZE_STRING,
32
+ 'enable_map' => FILTER_SANITIZE_STRING,
33
+ 'enable_log' => FILTER_SANITIZE_STRING,
34
+ 'purge_archive_on_edit' => FILTER_SANITIZE_STRING,
35
+ 'purge_archive_on_del' => FILTER_SANITIZE_STRING,
36
+ 'purge_archive_on_new_comment' => FILTER_SANITIZE_STRING,
37
+ 'purge_archive_on_deleted_comment' => FILTER_SANITIZE_STRING,
38
+ 'purge_page_on_mod' => FILTER_SANITIZE_STRING,
39
+ 'purge_page_on_new_comment' => FILTER_SANITIZE_STRING,
40
+ 'purge_page_on_deleted_comment' => FILTER_SANITIZE_STRING,
41
+ );
42
+
43
+ $all_inputs = filter_input_array(INPUT_POST, $args);
44
+
45
+ if ( isset( $all_inputs['smart_http_expire_save'] ) && 'Save All Changes' === $all_inputs['smart_http_expire_save'] ) {
46
+
47
+ unset( $all_inputs['smart_http_expire_save'] );
48
+ unset( $all_inputs['is_submit'] );
49
+
50
+ $nginx_settings = wp_parse_args(
51
+ $all_inputs,
52
+ $nginx_helper_admin->nginx_helper_default_settings()
53
+ );
54
+
55
+ if ( ( ! is_numeric( $nginx_settings['log_filesize'] ) ) || ( empty( $nginx_settings['log_filesize'] ) ) ) {
56
+ $error_log_filesize = __( 'Log file size must be a number.', 'nginx-helper' );
57
+ unset( $nginx_settings['log_filesize'] );
58
+ }
59
+
60
+ if ( $nginx_settings['enable_map'] ) {
61
+ $nginx_helper_admin->update_map();
62
+ }
63
+
64
+ update_site_option( 'rt_wp_nginx_helper_options', $nginx_settings );
65
+
66
+ echo '<div class="updated"><p>' . esc_html__( 'Settings saved.', 'nginx-helper' ) . '</p></div>';
67
+
68
+ }
69
+
70
+ $nginx_helper_settings = $nginx_helper_admin->nginx_helper_settings();
71
+ $log_path = $nginx_helper_admin->functional_asset_path();
72
+ $log_url = $nginx_helper_admin->functional_asset_url();
73
+
74
+ /**
75
+ * Get setting url for single multiple with subdomain OR multiple with subdirectory site.
76
+ */
77
+ $nginx_setting_link = '#';
78
+ if ( is_multisite() ) {
79
+ if ( SUBDOMAIN_INSTALL === false ) {
80
+ $nginx_setting_link = 'https://rtcamp.com/wordpress-nginx/tutorials/multisite/subdirectories/fastcgi-cache-with-purging/';
81
+ } else {
82
+ $nginx_setting_link = 'https://rtcamp.com/wordpress-nginx/tutorials/multisite/subdomains/fastcgi-cache-with-purging/';
83
+ }
84
+ } else {
85
+ $nginx_setting_link = 'https://rtcamp.com/wordpress-nginx/tutorials/single-site/fastcgi-cache-with-purging/';
86
+ }
87
+ ?>
88
+
89
+ <!-- Forms containing nginx helper settings options. -->
90
+ <form id="post_form" method="post" action="#" name="smart_http_expire_form" class="clearfix">
91
+ <div class="postbox">
92
+ <h3 class="hndle">
93
+ <span><?php esc_html_e( 'Purging Options', 'nginx-helper' ); ?></span>
94
+ </h3>
95
+ <div class="inside">
96
+ <table class="form-table">
97
+ <tr valign="top">
98
+ <td>
99
+ <input type="checkbox" value="1" id="enable_purge" name="enable_purge" <?php checked( $nginx_helper_settings['enable_purge'], 1 ); ?> />
100
+ <label for="enable_purge"><?php esc_html_e( 'Enable Purge', 'nginx-helper' ); ?></label>
101
+ </td>
102
+ </tr>
103
+ </table>
104
+ </div> <!-- End of .inside -->
105
+ </div>
106
+
107
+ <?php if ( ! ( ! is_network_admin() && is_multisite() ) ) { ?>
108
+ <div class="postbox enable_purge"<?php echo ( empty( $nginx_helper_settings['enable_purge'] ) ) ? ' style="display: none;"' : ''; ?>>
109
+ <h3 class="hndle">
110
+ <span><?php esc_html_e( 'Caching Method', 'nginx-helper' ); ?></span>
111
+ </h3>
112
+ <div class="inside">
113
+ <input type="hidden" name="is_submit" value="1" />
114
+ <table class="form-table">
115
+ <tr valign="top">
116
+ <td>
117
+ <input type="radio" value="enable_fastcgi" id="cache_method_fastcgi" name="cache_method" <?php echo checked( $nginx_helper_settings['cache_method'], 'enable_fastcgi' ); ?> />
118
+ <label for="cache_method_fastcgi">
119
+ <?php
120
+ echo wp_kses(
121
+ sprintf(
122
+ '%1$s (<a target="_blank" href="%2$s" title="%3$s">%4$s</a>)',
123
+ esc_html__( 'nginx Fastcgi cache', 'nginx-helper' ), esc_url( $nginx_setting_link ), esc_attr__( 'External settings for nginx', 'nginx-helper' ), esc_html__( 'requires external settings for nginx', 'nginx-helper' )
124
+ ),
125
+ array( 'strong' => array(), 'a' => array( 'href' => array(), 'title' => array() ) )
126
+ );
127
+ ?>
128
+ </label>
129
+ </td>
130
+ </tr>
131
+ <tr valign="top">
132
+ <td>
133
+ <input type="radio" value="enable_redis" id="cache_method_redis" name="cache_method" <?php echo checked( $nginx_helper_settings['cache_method'], 'enable_redis' ); ?> />
134
+ <label for="cache_method_redis">
135
+ <?php printf( esc_html__( 'Redis cache', 'nginx-helper' ) ); ?>
136
+ </label>
137
+ </td>
138
+ </tr>
139
+ </table>
140
+ </div> <!-- End of .inside -->
141
+ </div>
142
+ <div class="enable_purge">
143
+ <div class="postbox cache_method_fastcgi" <?php echo ( ! empty( $nginx_helper_settings['enable_purge'] ) && 'enable_fastcgi' === $nginx_helper_settings['cache_method'] ) ? '' : 'style="display: none;"'; ?> >
144
+ <h3 class="hndle">
145
+ <span><?php esc_html_e( 'Purge Method', 'nginx-helper' ); ?></span>
146
+ </h3>
147
+ <div class="inside">
148
+ <table class="form-table rtnginx-table">
149
+ <tr valign="top">
150
+ <td>
151
+ <fieldset>
152
+ <legend class="screen-reader-text">
153
+ <span>
154
+ &nbsp;
155
+ <?php esc_html_e( 'when a post/page/custom post is published.', 'nginx-helper' ); ?>
156
+ </span>
157
+ </legend>
158
+ <label for="purge_method_get_request">
159
+ <input type="radio" value="get_request" id="purge_method_get_request" name="purge_method" <?php checked( $nginx_helper_settings['purge_method'], 'get_request' ); ?>>
160
+ &nbsp;
161
+ <?php
162
+ echo wp_kses(
163
+ sprintf(
164
+ '%1$s <strong>PURGE/url</strong> %2$s',
165
+ esc_html__( 'Using a GET request to', 'nginx-helper' ), esc_html__( '(Default option)', 'nginx-helper' )
166
+ ),
167
+ array( 'strong' => array() )
168
+ );
169
+ ?>
170
+ <br />
171
+ <small>
172
+ <?php
173
+ echo wp_kses(
174
+ sprintf(
175
+ '%1$s <strong><a href="https://github.com/FRiCKLE/ngx_cache_purge">ngx_cache_purge</a></strong> %2$s.',
176
+ esc_html__( 'Uses the', 'nginx-helper' ), esc_html__( 'module', 'nginx-helper' )
177
+ ),
178
+ array( 'strong' => array(), 'a' => array( 'href' => array() ) )
179
+ );
180
+ ?>
181
+ </small>
182
+ </label>
183
+ <br />
184
+ <label for="purge_method_unlink_files">
185
+ <input type="radio" value="unlink_files" id="purge_method_unlink_files" name="purge_method" <?php checked( $nginx_helper_settings['purge_method'], 'unlink_files' ); ?>>
186
+ &nbsp;
187
+ <?php
188
+ esc_html_e( 'Delete local server cache files', 'nginx-helper' );
189
+ ?>
190
+ <br />
191
+ <small>
192
+ <?php
193
+ echo wp_kses(
194
+ sprintf(
195
+ '%1$s<strong>RT_WP_NGINX_HELPER_CACHE_PATH</strong>. %2$s',
196
+ esc_html__( 'Checks for matching cache file in ', 'nginx-helper' ), esc_html__( 'Does not require any other modules. Requires that the cache be stored on the same server as WordPress. You must also be using the default nginx cache options (levels=1:2) and (fastcgi_cache_key "$scheme$request_method$host$request_uri").', 'nginx-helper' )
197
+ ),
198
+ array( 'strong' => array() )
199
+ );
200
+ ?>
201
+ </small>
202
+ </label>
203
+ <br />
204
+ </fieldset>
205
+ </td>
206
+ </tr>
207
+ </table>
208
+ </div> <!-- End of .inside -->
209
+ </div>
210
+ <div class="postbox cache_method_redis"<?php echo ( ! empty( $nginx_helper_settings['enable_purge'] ) && 'enable_redis' === $nginx_helper_settings['cache_method'] ) ? '' : ' style="display: none;"'; ?>>
211
+ <h3 class="hndle">
212
+ <span><?php esc_html_e( 'Redis Settings', 'nginx-helper' ); ?></span>
213
+ </h3>
214
+ <div class="inside">
215
+ <table class="form-table rtnginx-table">
216
+ <tr>
217
+ <th><label for="redis_hostname"><?php esc_html_e( 'Hostname', 'nginx-helper' ); ?></label></th>
218
+ <td>
219
+ <input id="redis_hostname" class="medium-text" type="text" name="redis_hostname" value="<?php echo esc_attr( $nginx_helper_settings['redis_hostname'] ); ?>" <?php echo ( $nginx_helper_settings['redis_enabled_by_constant'] ) ? 'readonly="readonly"' : ''; ?> />
220
+ <?php
221
+ if ( $nginx_helper_settings['redis_enabled_by_constant'] ) {
222
+
223
+ echo '<p class="description">';
224
+ esc_html_e( 'Overridden by constant variables.', 'nginx-helper' );
225
+ echo '</p>';
226
+
227
+ }
228
+ ?>
229
+ </td>
230
+ </tr>
231
+ <tr>
232
+ <th><label for="redis_port"><?php esc_html_e( 'Port', 'nginx-helper' ); ?></label></th>
233
+ <td>
234
+ <input id="redis_port" class="medium-text" type="text" name="redis_port" value="<?php echo esc_attr( $nginx_helper_settings['redis_port'] ); ?>" <?php echo ( $nginx_helper_settings['redis_enabled_by_constant'] ) ? 'readonly="readonly"' : ''; ?> />
235
+ <?php
236
+ if ( $nginx_helper_settings['redis_enabled_by_constant'] ) {
237
+
238
+ echo '<p class="description">';
239
+ esc_html_e( 'Overridden by constant variables.', 'nginx-helper' );
240
+ echo '</p>';
241
+
242
+ }
243
+ ?>
244
+ </td>
245
+ </tr>
246
+ <tr>
247
+ <th><label for="redis_prefix"><?php esc_html_e( 'Prefix', 'nginx-helper' ); ?></label></th>
248
+ <td>
249
+ <input id="redis_prefix" class="medium-text" type="text" name="redis_prefix" value="<?php echo esc_attr( $nginx_helper_settings['redis_prefix'] ); ?>" <?php echo ( $nginx_helper_settings['redis_enabled_by_constant'] ) ? 'readonly="readonly"' : ''; ?> />
250
+ <?php
251
+ if ( $nginx_helper_settings['redis_enabled_by_constant'] ) {
252
+
253
+ echo '<p class="description">';
254
+ esc_html_e( 'Overridden by constant variables.', 'nginx-helper' );
255
+ echo '</p>';
256
+
257
+ }
258
+ ?>
259
+ </td>
260
+ </tr>
261
+ </table>
262
+ </div> <!-- End of .inside -->
263
+ </div>
264
+ </div>
265
+ <div class="postbox enable_purge"<?php echo ( empty( $nginx_helper_settings['enable_purge'] ) ) ? ' style="display: none;"' : ''; ?>>
266
+ <h3 class="hndle">
267
+ <span><?php esc_html_e( 'Purging Conditions', 'nginx-helper' ); ?></span>
268
+ </h3>
269
+ <div class="inside">
270
+ <table class="form-table rtnginx-table">
271
+ <tr valign="top">
272
+ <th scope="row"><h4><?php esc_html_e( 'Purge Homepage:', 'nginx-helper' ); ?></h4></th>
273
+ <td>
274
+ <fieldset>
275
+ <legend class="screen-reader-text">
276
+ <span>
277
+ &nbsp;
278
+ <?php
279
+ esc_html_e( 'when a post/page/custom post is modified or added.', 'nginx-helper' );
280
+ ?>
281
+ </span>
282
+ </legend>
283
+ <label for="purge_homepage_on_edit">
284
+ <input type="checkbox" value="1" id="purge_homepage_on_edit" name="purge_homepage_on_edit" <?php checked( $nginx_helper_settings['purge_homepage_on_edit'], 1 ); ?> />
285
+ &nbsp;
286
+ <?php
287
+ echo wp_kses(
288
+ sprintf(
289
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>%5$s<strong>%6$s</strong>.',
290
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'post', 'nginx-helper' ), esc_html__( ' (or page/custom post) is ', 'nginx-helper' ), esc_html__( 'modified', 'nginx-helper' ), esc_html__( ' or ', 'nginx-helper' ), esc_html__( 'added', 'nginx-helper' )
291
+ ),
292
+ array( 'strong' => array(), )
293
+ );
294
+ ?>
295
+ </label>
296
+ <br />
297
+ </fieldset>
298
+ <fieldset>
299
+ <legend class="screen-reader-text">
300
+ <span>
301
+ &nbsp;
302
+ <?php
303
+ esc_html_e( 'when an existing post/page/custom post is modified.', 'nginx-helper' );
304
+ ?>
305
+ </span>
306
+ </legend>
307
+ <label for="purge_homepage_on_del">
308
+ <input type="checkbox" value="1" id="purge_homepage_on_del" name="purge_homepage_on_del" <?php checked( $nginx_helper_settings['purge_homepage_on_del'], 1 ); ?> />
309
+ &nbsp;
310
+ <?php
311
+ echo wp_kses(
312
+ sprintf(
313
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>.',
314
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'published post', 'nginx-helper' ), esc_html__( ' (or page/custom post) is ', 'nginx-helper' ), esc_html__( 'trashed', 'nginx-helper' )
315
+ ),
316
+ array( 'strong' => array(), )
317
+ );
318
+ ?>
319
+ </label>
320
+ <br />
321
+ </fieldset>
322
+ </td>
323
+ </tr>
324
+ </table>
325
+ <table class="form-table rtnginx-table">
326
+ <tr valign="top">
327
+ <th scope="row">
328
+ <h4>
329
+ <?php esc_html_e( 'Purge Post/Page/Custom Post Type:', 'nginx-helper' ); ?>
330
+ </h4>
331
+ </th>
332
+ <td>
333
+ <fieldset>
334
+ <legend class="screen-reader-text">
335
+ <span>&nbsp;
336
+ <?php
337
+ esc_html_e( 'when a post/page/custom post is published.', 'nginx-helper' );
338
+ ?>
339
+ </span>
340
+ </legend>
341
+ <label for="purge_page_on_mod">
342
+ <input type="checkbox" value="1" id="purge_page_on_mod" name="purge_page_on_mod" <?php checked( $nginx_helper_settings['purge_page_on_mod'], 1 ); ?>>
343
+ &nbsp;
344
+ <?php
345
+ echo wp_kses(
346
+ sprintf(
347
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>.',
348
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'post', 'nginx-helper' ), esc_html__( ' is ', 'nginx-helper' ), esc_html__( 'published', 'nginx-helper' )
349
+ ),
350
+ array( 'strong' => array(), )
351
+ );
352
+ ?>
353
+ </label>
354
+ <br />
355
+ </fieldset>
356
+ <fieldset>
357
+ <legend class="screen-reader-text">
358
+ <span>
359
+ &nbsp;
360
+ <?php
361
+ esc_html_e( 'when a comment is approved/published.', 'nginx-helper' );
362
+ ?>
363
+ </span>
364
+ </legend>
365
+ <label for="purge_page_on_new_comment">
366
+ <input type="checkbox" value="1" id="purge_page_on_new_comment" name="purge_page_on_new_comment" <?php checked( $nginx_helper_settings['purge_page_on_new_comment'], 1 ); ?>>
367
+ &nbsp;
368
+ <?php
369
+ echo wp_kses(
370
+ sprintf(
371
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>.',
372
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'comment', 'nginx-helper' ), esc_html__( ' is ', 'nginx-helper' ), esc_html__( 'approved/published', 'nginx-helper' )
373
+ ),
374
+ array( 'strong' => array(), )
375
+ );
376
+ ?>
377
+ </label>
378
+ <br />
379
+ </fieldset>
380
+ <fieldset>
381
+ <legend class="screen-reader-text">
382
+ <span>
383
+ &nbsp;
384
+ <?php
385
+ esc_html_e( 'when a comment is unapproved/deleted.', 'nginx-helper' );
386
+ ?>
387
+ </span>
388
+ </legend>
389
+ <label for="purge_page_on_deleted_comment">
390
+ <input type="checkbox" value="1" id="purge_page_on_deleted_comment" name="purge_page_on_deleted_comment" <?php checked( $nginx_helper_settings['purge_page_on_deleted_comment'], 1 ); ?>>
391
+ &nbsp;
392
+ <?php
393
+ echo wp_kses(
394
+ sprintf(
395
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>.',
396
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'comment', 'nginx-helper' ), esc_html__( ' is ', 'nginx-helper' ), esc_html__( 'unapproved/deleted', 'nginx-helper' )
397
+ ),
398
+ array( 'strong' => array(), )
399
+ );
400
+ ?>
401
+ </label>
402
+ <br />
403
+ </fieldset>
404
+ </td>
405
+ </tr>
406
+ </table>
407
+ <table class="form-table rtnginx-table">
408
+ <tr valign="top">
409
+ <th scope="row">
410
+ <h4>
411
+ <?php esc_html_e( 'Purge Archives:', 'nginx-helper' ); ?>
412
+ </h4>
413
+ <small><?php esc_html_e( '(date, category, tag, author, custom taxonomies)', 'nginx-helper' ); ?></small>
414
+ </th>
415
+ <td>
416
+ <fieldset>
417
+ <legend class="screen-reader-text">
418
+ <span>
419
+ &nbsp;
420
+ <?php
421
+ esc_html_e( 'when an post/page/custom post is modified or added', 'nginx-helper' );
422
+ ?>
423
+ </span>
424
+ </legend>
425
+ <label for="purge_archive_on_edit">
426
+ <input type="checkbox" value="1" id="purge_archive_on_edit" name="purge_archive_on_edit" <?php checked( $nginx_helper_settings['purge_archive_on_edit'], 1 ); ?> />
427
+ &nbsp;
428
+ <?php
429
+ echo wp_kses(
430
+ sprintf(
431
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>%5$s<strong>%6$s</strong>.',
432
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'post', 'nginx-helper' ), esc_html__( ' (or page/custom post) is ', 'nginx-helper' ), esc_html__( 'modified', 'nginx-helper' ), esc_html__( ' or ', 'nginx-helper' ), esc_html__( 'added', 'nginx-helper' )
433
+ ),
434
+ array( 'strong' => array(), )
435
+ );
436
+ ?>
437
+ </label>
438
+ <br />
439
+ </fieldset>
440
+ <fieldset>
441
+ <legend class="screen-reader-text">
442
+ <span>
443
+ &nbsp;
444
+ <?php
445
+ esc_html_e( 'when an existing post/page/custom post is trashed.', 'nginx-helper' );
446
+ ?>
447
+ </span>
448
+ </legend>
449
+ <label for="purge_archive_on_del">
450
+ <input type="checkbox" value="1" id="purge_archive_on_del" name="purge_archive_on_del"<?php checked( $nginx_helper_settings['purge_archive_on_del'], 1 ); ?> />
451
+ &nbsp;
452
+ <?php
453
+ echo wp_kses(
454
+ sprintf(
455
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>.',
456
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'published post', 'nginx-helper' ), esc_html__( ' (or page/custom post) is ', 'nginx-helper' ), esc_html__( 'trashed', 'nginx-helper' )
457
+ ),
458
+ array( 'strong' => array(), )
459
+ );
460
+ ?>
461
+ </label>
462
+ <br />
463
+ </fieldset>
464
+ <br />
465
+ <fieldset>
466
+ <legend class="screen-reader-text">
467
+ <span>
468
+ &nbsp;
469
+ <?php
470
+ esc_html_e( 'when a comment is approved/published.', 'nginx-helper' );
471
+ ?>
472
+ </span>
473
+ </legend>
474
+ <label for="purge_archive_on_new_comment">
475
+ <input type="checkbox" value="1" id="purge_archive_on_new_comment" name="purge_archive_on_new_comment" <?php checked( $nginx_helper_settings['purge_archive_on_new_comment'], 1 ); ?> />
476
+ &nbsp;
477
+ <?php
478
+ echo wp_kses(
479
+ sprintf(
480
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>.',
481
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'comment', 'nginx-helper' ), esc_html__( ' is ', 'nginx-helper' ), esc_html__( 'approved/published', 'nginx-helper' )
482
+ ),
483
+ array( 'strong' => array(), )
484
+ );
485
+ ?>
486
+ </label>
487
+ <br />
488
+ </fieldset>
489
+ <fieldset>
490
+ <legend class="screen-reader-text">
491
+ <span>
492
+ &nbsp;
493
+ <?php
494
+ esc_html_e( 'when a comment is unapproved/deleted.', 'nginx-helper' );
495
+ ?>
496
+ </span>
497
+ </legend>
498
+ <label for="purge_archive_on_deleted_comment">
499
+ <input type="checkbox" value="1" id="purge_archive_on_deleted_comment" name="purge_archive_on_deleted_comment" <?php checked( $nginx_helper_settings['purge_archive_on_deleted_comment'], 1 ); ?> />
500
+ &nbsp;
501
+ <?php
502
+ echo wp_kses(
503
+ sprintf(
504
+ '%1$s<strong>%2$s</strong>%3$s<strong>%4$s</strong>.',
505
+ esc_html__( 'when a ', 'nginx-helper' ), esc_html__( 'comment', 'nginx-helper' ), esc_html__( ' is ', 'nginx-helper' ), esc_html__( 'unapproved/deleted', 'nginx-helper' )
506
+ ),
507
+ array( 'strong' => array(), )
508
+ );
509
+ ?>
510
+ </label>
511
+ <br />
512
+ </fieldset>
513
+ </td>
514
+ </tr>
515
+ </table>
516
+ <table class="form-table rtnginx-table">
517
+ <tr valign="top">
518
+ <th scope="row">
519
+ <h4><?php esc_html_e( 'Custom Purge URL:', 'nginx-helper' ); ?></h4>
520
+ </th>
521
+ <td>
522
+ <textarea rows="5"class="rt-purge_url" id="purge_url" name="purge_url"><?php echo esc_url( $nginx_helper_admin->options['purge_url'] ); ?></textarea>
523
+ <p class="description">
524
+ Add one URL per line. URL should not contain domain name.
525
+ <br>
526
+ Eg: To purge http://example.com/sample-page/ add <strong>/sample-page/</strong> in above textarea.
527
+ <br>
528
+ '*' will only work with redis cache server.
529
+ </p>
530
+ </td>
531
+ </tr>
532
+ </table>
533
+ </div> <!-- End of .inside -->
534
+ </div>
535
+ <div class="postbox">
536
+ <h3 class="hndle">
537
+ <span><?php esc_html_e( 'Debug Options', 'nginx-helper' ); ?></span>
538
+ </h3>
539
+ <div class="inside">
540
+ <input type="hidden" name="is_submit" value="1" />
541
+ <table class="form-table">
542
+ <?php if ( is_network_admin() ) { ?>
543
+ <tr valign="top">
544
+ <td>
545
+ <input type="checkbox" value="1" id="enable_map" name="enable_map" <?php checked( $nginx_helper_settings['enable_map'], 1 ); ?> />
546
+ <label for="enable_map">
547
+ <?php esc_html_e( 'Enable Nginx Map.', 'nginx-helper' ); ?>
548
+ </label>
549
+ </td>
550
+ </tr>
551
+ <?php } ?>
552
+ <tr valign="top">
553
+ <td>
554
+ <input type="checkbox" value="1" id="enable_log" name="enable_log"<?php checked( $nginx_helper_settings['enable_log'], 1 ); ?> />
555
+ <label for="enable_log">
556
+ <?php esc_html_e( 'Enable Logging', 'nginx-helper' ); ?>
557
+ </label>
558
+ </td>
559
+ </tr>
560
+ <tr valign="top">
561
+ <td>
562
+ <input type="checkbox" value="1" id="enable_stamp" name="enable_stamp" <?php checked( $nginx_helper_settings['enable_stamp'], 1 ); ?> />
563
+ <label for="enable_stamp">
564
+ <?php esc_html_e( 'Enable Nginx Timestamp in HTML', 'nginx-helper' ); ?>
565
+ </label>
566
+ </td>
567
+ </tr>
568
+ </table>
569
+ </div> <!-- End of .inside -->
570
+ </div>
571
+ <?php
572
+ } // End of if ( ! ( ! is_network_admin() && is_multisite() ) )
573
+
574
+ if ( is_network_admin() ) {
575
+ ?>
576
+ <div class="postbox enable_map"<?php echo ( empty( $nginx_helper_settings['enable_map'] ) ) ? ' style="display: none;"' : ''; ?>>
577
+ <h3 class="hndle">
578
+ <span><?php esc_html_e( 'Nginx Map', 'nginx-helper' ); ?></span>
579
+ </h3>
580
+ <div class="inside">
581
+ <?php
582
+ if ( ! is_writable( $log_path . 'map.conf' ) ) {
583
+ ?>
584
+ <span class="error fade" style="display: block">
585
+ <p>
586
+ <?php
587
+ echo wp_kses(
588
+ sprintf(
589
+ '%1$s<br /><br />%2$s<strong>%3$s</strong>',
590
+ esc_html__( 'Can\'t write on map file.', 'nginx-helper' ), esc_html__( 'Check you have write permission on ', 'nginx-helper' ), esc_url( $log_path . 'map.conf' )
591
+ ),
592
+ array( 'br' => array(), 'strong' => array(), )
593
+ );
594
+ ?>
595
+ </p>
596
+ </span>
597
+ <?php
598
+ }
599
+ ?>
600
+ <table class="form-table rtnginx-table">
601
+ <tr>
602
+ <th>
603
+ <?php
604
+ echo wp_kses(
605
+ sprintf(
606
+ '%1$s<br /><small>%2$s</small>',
607
+ esc_html__( 'Nginx Map path to include in nginx settings', 'nginx-helper' ), esc_html__( '(recommended)', 'nginx-helper' )
608
+ ),
609
+ array( 'br' => array(), 'small' => array(), )
610
+ );
611
+ ?>
612
+ </th>
613
+ <td>
614
+ <pre>
615
+ <?php echo esc_url( $log_path . 'map.conf' ); ?>
616
+ </pre>
617
+ </td>
618
+ </tr>
619
+ <tr>
620
+ <th>
621
+ <?php
622
+ echo wp_kses(
623
+ sprintf(
624
+ '%1$s<br />%2$s<br /><small>%3$s</small>',
625
+ esc_html__( 'Or,', 'nginx-helper' ), esc_html__( 'Text to manually copy and paste in nginx settings', 'nginx-helper' ), esc_html__( '(if your network is small and new sites are not added frequently)', 'nginx-helper' )
626
+ ),
627
+ array( 'br' => array(), 'small' => array(), )
628
+ );
629
+ ?>
630
+ </th>
631
+ <td>
632
+ <pre id="map">
633
+ <?php echo esc_html( $nginx_helper_admin->get_map() ); ?>
634
+ </pre>
635
+ </td>
636
+ </tr>
637
+ </table>
638
+ </div> <!-- End of .inside -->
639
+ </div>
640
+ <?php
641
+ }
642
+ ?>
643
+ <div class="postbox enable_log"<?php echo ( empty( $nginx_helper_settings['enable_log'] ) ) ? ' style="display: none;"' : ''; ?>>
644
+ <h3 class="hndle">
645
+ <span><?php esc_html_e( 'Logging Options', 'nginx-helper' ); ?></span>
646
+ </h3>
647
+ <div class="inside">
648
+ <?php
649
+ if ( ! is_dir( $log_path ) ) {
650
+ mkdir( $log_path );
651
+ }
652
+ if ( ! file_exists( $log_path . 'nginx.log' ) ) {
653
+ $log = fopen( $log_path . 'nginx.log', 'w' );
654
+ fclose( $log );
655
+ }
656
+ if ( ! is_writable( $log_path . 'nginx.log' ) ) {
657
+ ?>
658
+ <span class="error fade" style="display : block">
659
+ <p>
660
+ <?php
661
+ echo wp_kses(
662
+ sprintf(
663
+ '%1$s<br /><br />%2$s<strong>%3$s</strong>',
664
+ esc_html__( 'Can\'t write on log file.', 'nginx-helper' ), esc_html__( 'Check you have write permission on ', 'nginx-helper' ), esc_url( $log_path . 'nginx.log' )
665
+ ),
666
+ array( 'br' => array(), 'strong' => array(), )
667
+ );
668
+ ?>
669
+ </p>
670
+ </span>
671
+ <?php
672
+ }
673
+ ?>
674
+
675
+ <table class="form-table rtnginx-table">
676
+ <tbody>
677
+ <tr>
678
+ <th>
679
+ <label for="rt_wp_nginx_helper_logs_path">
680
+ <?php esc_html_e( 'Logs path', 'nginx-helper' ); ?>
681
+ </label>
682
+ </th>
683
+ <td>
684
+ <code>
685
+ <?php echo esc_url( $log_path . 'nginx.log' ); ?>
686
+ </code>
687
+ </td>
688
+ </tr>
689
+ <tr>
690
+ <th>
691
+ <label for="rt_wp_nginx_helper_logs_link">
692
+ <?php esc_html_e( 'View Log', 'nginx-helper' ); ?>
693
+ </label>
694
+ </th>
695
+ <td>
696
+ <a target="_blank" href="<?php echo esc_url( $log_url . 'nginx.log' ); ?>">
697
+ <?php esc_html_e( 'Log', 'nginx-helper' ); ?>
698
+ </a>
699
+ </td>
700
+ </tr>
701
+ <tr>
702
+ <th>
703
+ <label for="rt_wp_nginx_helper_log_level">
704
+ <?php esc_html_e( 'Log level', 'nginx-helper' ); ?>
705
+ </label>
706
+ </th>
707
+ <td>
708
+ <select name="log_level">
709
+ <option value="NONE" <?php selected( $nginx_helper_settings['log_level'], 'NONE' ); ?>> <?php esc_html_e( 'None', 'nginx-helper' ); ?> </option>
710
+ <option value="INFO" <?php selected( $nginx_helper_settings['log_level'], 'INFO' ); ?>> <?php esc_html_e( 'Info', 'nginx-helper' ); ?> </option>
711
+ <option value="WARNING" <?php selected( $nginx_helper_settings['log_level'], 'WARNING' ); ?>> <?php esc_html_e( 'Warning', 'nginx-helper' ); ?> </option>
712
+ <option value="ERROR" <?php selected( $nginx_helper_settings['log_level'], 'ERROR' ); ?>> <?php esc_html_e( 'Error', 'nginx-helper' ); ?> </option>
713
+ </select>
714
+ </td>
715
+ </tr>
716
+ <tr>
717
+ <th>
718
+ <label for="log_filesize">
719
+ <?php esc_html_e( 'Max log file size', 'nginx-helper' ); ?>
720
+ </label>
721
+ </th>
722
+ <td>
723
+ <input id="log_filesize" class="small-text" type="text" name="log_filesize" value="<?php echo esc_attr( $nginx_helper_settings['log_filesize'] ); ?>" />
724
+ <?php
725
+ esc_html_e( 'Mb', 'nginx-helper' );
726
+ if ( $error_log_filesize ) {
727
+ ?>
728
+ <p class="error fade" style="display: block;">
729
+ <?php echo esc_html( $error_log_filesize ); ?>
730
+ </p>
731
+ <?php
732
+ }
733
+ ?>
734
+ </td>
735
+ </tr>
736
+ </tbody>
737
+ </table>
738
+ </div> <!-- End of .inside -->
739
+ </div>
740
+ <?php
741
+ submit_button( __( 'Save All Changes', 'nginx-helper' ), 'primary large', 'smart_http_expire_save', true );
742
+ ?>
743
+ </form><!-- End of #post_form -->
admin/partials/nginx-helper-sidebar-display.php ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Display sidebar.
4
+ *
5
+ * This file is used to markup the admin-facing aspects of the plugin.
6
+ *
7
+ * @since 2.0.0
8
+ *
9
+ * @package nginx-helper
10
+ * @subpackage nginx-helper/admin/partials
11
+ */
12
+
13
+ $purge_url = add_query_arg(
14
+ array(
15
+ 'nginx_helper_action' => 'purge',
16
+ 'nginx_helper_urls' => 'all',
17
+ )
18
+ );
19
+ $nonced_url = wp_nonce_url( $purge_url, 'nginx_helper-purge_all' );
20
+ ?>
21
+
22
+ <!-- This file should primarily consist of HTML with a little bit of PHP. -->
23
+
24
+ <form id="purgeall" action="" method="post" class="clearfix">
25
+ <a href="<?php echo esc_url( $nonced_url ); ?>" class="button-primary">
26
+ <?php esc_html_e( 'Purge Entire Cache', 'nginx-helper' ); ?>
27
+ </a>
28
+ </form>
29
+ <div class="postbox" id="support">
30
+ <h3 class="hndle">
31
+ <span><?php esc_html_e( 'Need Help?', 'nginx-helper' ); ?></span>
32
+ </h3>
33
+ <div class="inside">
34
+ <p>
35
+ <?php
36
+ echo wp_kses(
37
+ sprintf(
38
+ '%1$s <a href=\'%2$s\'>%3$s</a>.',
39
+ esc_html__( 'Please use our', 'nginx-helper' ), esc_url( 'http://rtcamp.com/support/forum/wordpress-nginx/' ), esc_html__( 'free support forum', 'nginx-helper' )
40
+ ),
41
+ array( 'a' => array( 'href' => array() ) )
42
+ );
43
+ ?>
44
+ </p>
45
+ </div>
46
+ </div>
47
+
48
+ <div class="postbox" id="social">
49
+ <h3 class="hndle">
50
+ <span>
51
+ <?php esc_html_e( 'Getting Social is Good', 'nginx-helper' ); ?>
52
+ </span>
53
+ </h3>
54
+ <div style="text-align:center;" class="inside">
55
+ <a class="nginx-helper-facebook" title="<?php esc_attr_e( 'Become a fan on Facebook', 'nginx-helper' ); ?>" target="_blank" href="http://www.facebook.com/rtCamp.solutions/"></a>
56
+ <a class="nginx-helper-twitter" title="<?php esc_attr_e( 'Follow us on Twitter', 'nginx-helper' ); ?>" target="_blank" href="https://twitter.com/rtcamp/"></a>
57
+ <a class="nginx-helper-gplus" title="<?php esc_attr_e( 'Add to Circle', 'nginx-helper' ); ?>" target="_blank" href="https://plus.google.com/110214156830549460974/posts"></a>
58
+ <a class="nginx-helper-rss" title="<?php esc_attr_e( 'Subscribe to our feeds', 'nginx-helper' ); ?>" target="_blank" href="http://feeds.feedburner.com/rtcamp/"></a>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="postbox" id="useful-links">
63
+ <h3 class="hndle">
64
+ <span><?php esc_html_e( 'Useful Links', 'nginx-helper' ); ?></span>
65
+ </h3>
66
+ <div class="inside">
67
+ <ul role="list">
68
+ <li role="listitem">
69
+ <a href="https://rtcamp.com/wordpress-nginx/" title="<?php esc_attr_e( 'WordPress-Nginx Solutions', 'nginx-helper' ); ?>"><?php esc_html_e( 'WordPress-Nginx Solutions', 'nginx-helper' ); ?></a>
70
+ </li>
71
+ <li role="listitem">
72
+ <a href="https://rtcamp.com/services/wordPress-themes-design-development/" title="<?php esc_attr_e( 'WordPress Theme Devleopment', 'nginx-helper' ); ?>"><?php esc_html_e( 'WordPress Theme Devleopment', 'nginx-helper' ); ?></a>
73
+ </li>
74
+ <li role="listitem">
75
+ <a href="http://rtcamp.com/services/wordpress-plugins/" title="<?php esc_attr_e( 'WordPress Plugin Development', 'nginx-helper' ); ?>"><?php esc_html_e( 'WordPress Plugin Development', 'nginx-helper' ); ?></a>
76
+ </li>
77
+ <li role="listitem">
78
+ <a href="http://rtcamp.com/services/custom-wordpress-solutions/" title="<?php esc_attr_e( 'WordPress Consultancy', 'nginx-helper' ); ?>"><?php esc_html_e( 'WordPress Consultancy', 'nginx-helper' ); ?></a>
79
+ </li>
80
+ <li role="listitem">
81
+ <a href="https://rtcamp.com/easyengine/" title="<?php esc_attr_e( 'easyengine (ee)', 'nginx-helper' ); ?>"><?php esc_html_e( 'easyengine (ee)', 'nginx-helper' ); ?></a>
82
+ </li>
83
+ </ul>
84
+ </div>
85
+ </div>
86
+
87
+ <div class="postbox" id="latest_news">
88
+ <div title="<?php esc_attr_e( 'Click to toggle', 'nginx-helper' ); ?>" class="handlediv"><br /></div>
89
+ <h3 class="hndle"><span><?php esc_html_e( 'Latest News', 'nginx-helper' ); ?></span></h3>
90
+ <div class="inside"><img src ="<?php echo esc_url( admin_url() ); ?>/images/wpspin_light.gif" /><?php esc_html_e( 'Loading...', 'nginx-helper' ); ?></div>
91
+ </div>
admin/partials/nginx-helper-support-options.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Display support options of the plugin.
4
+ *
5
+ * This file is used to markup the admin-facing aspects of the plugin.
6
+ *
7
+ * @since 2.0.0
8
+ *
9
+ * @package nginx-helper
10
+ * @subpackage nginx-helper/admin/partials
11
+ */
12
+
13
+ ?>
14
+
15
+ <!-- This file should primarily consist of HTML with a little bit of PHP. -->
16
+ <div class="postbox">
17
+ <h3 class="hndle">
18
+ <span><?php esc_html_e( 'Support Forums', 'nginx-helper' ); ?></span>
19
+ </h3>
20
+ <div class="inside">
21
+ <table class="form-table">
22
+ <tr valign="top">
23
+ <th>
24
+ <?php esc_html_e( 'Free Support', 'nginx-helper' ); ?>
25
+ </th>
26
+ <td>
27
+ <a href="https://rtcamp.com/support/forum/wordpress-nginx/" title="<?php esc_attr_e( 'Free Support Forum', 'nginx-helper' ); ?>" target="_blank">
28
+ <?php esc_html_e( 'Link to forum', 'nginx-helper' ); ?>
29
+ </a>
30
+ </td>
31
+ </tr>
32
+ <tr valign="top">
33
+ <th>
34
+ <?php esc_html_e( 'Premium Support', 'nginx-helper' ); ?>
35
+ </th>
36
+ <td>
37
+ <a href="https://rtcamp.com/wordpress-nginx/pricing/" title="<?php esc_attr_e( 'Premium Support Forum', 'nginx-helper' ); ?>" target="_blank">
38
+ <?php esc_html_e( 'Link to forum', 'nginx-helper' ); ?>
39
+ </a>
40
+ </td>
41
+ </tr>
42
+ </table>
43
+ </div>
44
+ </div>
admin/predis.php ADDED
@@ -0,0 +1,15202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of the Predis package.
5
+ *
6
+ * (c) Daniele Alessandri <suppakilla@gmail.com>
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ namespace Predis\Command;
13
+
14
+ use InvalidArgumentException;
15
+
16
+ /**
17
+ * Defines an abstraction representing a Redis command.
18
+ *
19
+ * @author Daniele Alessandri <suppakilla@gmail.com>
20
+ */
21
+ interface CommandInterface
22
+ {
23
+ /**
24
+ * Returns the ID of the Redis command. By convention, command identifiers
25
+ * must always be uppercase.
26
+ *
27
+ * @return string
28
+ */
29
+ public function getId();
30
+
31
+ /**
32
+ * Assign the specified slot to the command for clustering distribution.
33
+ *
34
+ * @param int $slot Slot ID.
35
+ */
36
+ public function setSlot($slot);
37
+
38
+ /**
39
+ * Returns the assigned slot of the command for clustering distribution.
40
+ *
41
+ * @return int|null
42
+ */
43
+ public function getSlot();
44
+
45
+ /**
46
+ * Sets the arguments for the command.
47
+ *
48
+ * @param array $arguments List of arguments.
49
+ */
50
+ public function setArguments(array $arguments);
51
+
52
+ /**
53
+ * Sets the raw arguments for the command without processing them.
54
+ *
55
+ * @param array $arguments List of arguments.
56
+ */
57
+ public function setRawArguments(array $arguments);
58
+
59
+ /**
60
+ * Gets the arguments of the command.
61
+ *
62
+ * @return array
63
+ */
64
+ public function getArguments();
65
+
66
+ /**
67
+ * Gets the argument of the command at the specified index.
68
+ *
69
+ * @param int $index Index of the desired argument.
70
+ *
71
+ * @return mixed|null
72
+ */
73
+ public function getArgument($index);
74
+
75
+ /**
76
+ * Parses a raw response and returns a PHP object.
77
+ *
78
+ * @param string $data Binary string containing the whole response.
79
+ *
80
+ * @return mixed
81
+ */
82
+ public function parseResponse($data);
83
+ }
84
+
85
+ /**
86
+ * Base class for Redis commands.
87
+ *
88
+ * @author Daniele Alessandri <suppakilla@gmail.com>
89
+ */
90
+ abstract class Command implements CommandInterface
91
+ {
92
+ private $slot;
93
+ private $arguments = array();
94
+
95
+ /**
96
+ * Returns a filtered array of the arguments.
97
+ *
98
+ * @param array $arguments List of arguments.
99
+ *
100
+ * @return array
101
+ */
102
+ protected function filterArguments(array $arguments)
103
+ {
104
+ return $arguments;
105
+ }
106
+
107
+ /**
108
+ * {@inheritdoc}
109
+ */
110
+ public function setArguments(array $arguments)
111
+ {
112
+ $this->arguments = $this->filterArguments($arguments);
113
+ unset($this->slot);
114
+ }
115
+
116
+ /**
117
+ * {@inheritdoc}
118
+ */
119
+ public function setRawArguments(array $arguments)
120
+ {
121
+ $this->arguments = $arguments;
122
+ unset($this->slot);
123
+ }
124
+
125
+ /**
126
+ * {@inheritdoc}
127
+ */
128
+ public function getArguments()
129
+ {
130
+ return $this->arguments;
131
+ }
132
+
133
+ /**
134
+ * {@inheritdoc}
135
+ */
136
+ public function getArgument($index)
137
+ {
138
+ if (isset($this->arguments[$index])) {
139
+ return $this->arguments[$index];
140
+ }
141
+ }
142
+
143
+ /**
144
+ * {@inheritdoc}
145
+ */
146
+ public function setSlot($slot)
147
+ {
148
+ $this->slot = $slot;
149
+ }
150
+
151
+ /**
152
+ * {@inheritdoc}
153
+ */
154
+ public function getSlot()
155
+ {
156
+ if (isset($this->slot)) {
157
+ return $this->slot;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * {@inheritdoc}
163
+ */
164
+ public function parseResponse($data)
165
+ {
166
+ return $data;
167
+ }
168
+
169
+ /**
170
+ * Normalizes the arguments array passed to a Redis command.
171
+ *
172
+ * @param array $arguments Arguments for a command.
173
+ *
174
+ * @return array
175
+ */
176
+ public static function normalizeArguments(array $arguments)
177
+ {
178
+ if (count($arguments) === 1 && is_array($arguments[0])) {
179
+ return $arguments[0];
180
+ }
181
+
182
+ return $arguments;
183
+ }
184
+
185
+ /**
186
+ * Normalizes the arguments array passed to a variadic Redis command.
187
+ *
188
+ * @param array $arguments Arguments for a command.
189
+ *
190
+ * @return array
191
+ */
192
+ public static function normalizeVariadic(array $arguments)
193
+ {
194
+ if (count($arguments) === 2 && is_array($arguments[1])) {
195
+ return array_merge(array($arguments[0]), $arguments[1]);
196
+ }
197
+
198
+ return $arguments;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * @link http://redis.io/commands/zrange
204
+ * @author Daniele Alessandri <suppakilla@gmail.com>
205
+ */
206
+ class ZSetRange extends Command
207
+ {
208
+ /**
209
+ * {@inheritdoc}
210
+ */
211
+ public function getId()
212
+ {
213
+ return 'ZRANGE';
214
+ }
215
+
216
+ /**
217
+ * {@inheritdoc}
218
+ */
219
+ protected function filterArguments(array $arguments)
220
+ {
221
+ if (count($arguments) === 4) {
222
+ $lastType = gettype($arguments[3]);
223
+
224
+ if ($lastType === 'string' && strtoupper($arguments[3]) === 'WITHSCORES') {
225
+ // Used for compatibility with older versions
226
+ $arguments[3] = array('WITHSCORES' => true);
227
+ $lastType = 'array';
228
+ }
229
+
230
+ if ($lastType === 'array') {
231
+ $options = $this->prepareOptions(array_pop($arguments));
232
+
233
+ return array_merge($arguments, $options);
234
+ }
235
+ }
236
+
237
+ return $arguments;
238
+ }
239
+
240
+ /**
241
+ * Returns a list of options and modifiers compatible with Redis.
242
+ *
243
+ * @param array $options List of options.
244
+ *
245
+ * @return array
246
+ */
247
+ protected function prepareOptions($options)
248
+ {
249
+ $opts = array_change_key_case($options, CASE_UPPER);
250
+ $finalizedOpts = array();
251
+
252
+ if (!empty($opts['WITHSCORES'])) {
253
+ $finalizedOpts[] = 'WITHSCORES';
254
+ }
255
+
256
+ return $finalizedOpts;
257
+ }
258
+
259
+ /**
260
+ * Checks for the presence of the WITHSCORES modifier.
261
+ *
262
+ * @return bool
263
+ */
264
+ protected function withScores()
265
+ {
266
+ $arguments = $this->getArguments();
267
+
268
+ if (count($arguments) < 4) {
269
+ return false;
270
+ }
271
+
272
+ return strtoupper($arguments[3]) === 'WITHSCORES';
273
+ }
274
+
275
+ /**
276
+ * {@inheritdoc}
277
+ */
278
+ public function parseResponse($data)
279
+ {
280
+ if ($this->withScores()) {
281
+ $result = array();
282
+
283
+ for ($i = 0; $i < count($data); $i++) {
284
+ $result[$data[$i]] = $data[++$i];
285
+ }
286
+
287
+ return $result;
288
+ }
289
+
290
+ return $data;
291
+ }
292
+ }
293
+
294
+ /**
295
+ * @link http://redis.io/commands/sinterstore
296
+ * @author Daniele Alessandri <suppakilla@gmail.com>
297
+ */
298
+ class SetIntersectionStore extends Command
299
+ {
300
+ /**
301
+ * {@inheritdoc}
302
+ */
303
+ public function getId()
304
+ {
305
+ return 'SINTERSTORE';
306
+ }
307
+
308
+ /**
309
+ * {@inheritdoc}
310
+ */
311
+ protected function filterArguments(array $arguments)
312
+ {
313
+ if (count($arguments) === 2 && is_array($arguments[1])) {
314
+ return array_merge(array($arguments[0]), $arguments[1]);
315
+ }
316
+
317
+ return $arguments;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * @link http://redis.io/commands/sinter
323
+ * @author Daniele Alessandri <suppakilla@gmail.com>
324
+ */
325
+ class SetIntersection extends Command
326
+ {
327
+ /**
328
+ * {@inheritdoc}
329
+ */
330
+ public function getId()
331
+ {
332
+ return 'SINTER';
333
+ }
334
+
335
+ /**
336
+ * {@inheritdoc}
337
+ */
338
+ protected function filterArguments(array $arguments)
339
+ {
340
+ return self::normalizeArguments($arguments);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * @link http://redis.io/commands/eval
346
+ * @author Daniele Alessandri <suppakilla@gmail.com>
347
+ */
348
+ class ServerEval extends Command
349
+ {
350
+ /**
351
+ * {@inheritdoc}
352
+ */
353
+ public function getId()
354
+ {
355
+ return 'EVAL';
356
+ }
357
+
358
+ /**
359
+ * Calculates the SHA1 hash of the body of the script.
360
+ *
361
+ * @return string SHA1 hash.
362
+ */
363
+ public function getScriptHash()
364
+ {
365
+ return sha1($this->getArgument(0));
366
+ }
367
+ }
368
+
369
+ /**
370
+ * @link http://redis.io/commands/rename
371
+ * @author Daniele Alessandri <suppakilla@gmail.com>
372
+ */
373
+ class KeyRename extends Command
374
+ {
375
+ /**
376
+ * {@inheritdoc}
377
+ */
378
+ public function getId()
379
+ {
380
+ return 'RENAME';
381
+ }
382
+ }
383
+
384
+ /**
385
+ * @link http://redis.io/commands/setex
386
+ * @author Daniele Alessandri <suppakilla@gmail.com>
387
+ */
388
+ class StringSetExpire extends Command
389
+ {
390
+ /**
391
+ * {@inheritdoc}
392
+ */
393
+ public function getId()
394
+ {
395
+ return 'SETEX';
396
+ }
397
+ }
398
+
399
+ /**
400
+ * @link http://redis.io/commands/mset
401
+ * @author Daniele Alessandri <suppakilla@gmail.com>
402
+ */
403
+ class StringSetMultiple extends Command
404
+ {
405
+ /**
406
+ * {@inheritdoc}
407
+ */
408
+ public function getId()
409
+ {
410
+ return 'MSET';
411
+ }
412
+
413
+ /**
414
+ * {@inheritdoc}
415
+ */
416
+ protected function filterArguments(array $arguments)
417
+ {
418
+ if (count($arguments) === 1 && is_array($arguments[0])) {
419
+ $flattenedKVs = array();
420
+ $args = $arguments[0];
421
+
422
+ foreach ($args as $k => $v) {
423
+ $flattenedKVs[] = $k;
424
+ $flattenedKVs[] = $v;
425
+ }
426
+
427
+ return $flattenedKVs;
428
+ }
429
+
430
+ return $arguments;
431
+ }
432
+ }
433
+
434
+ /**
435
+ * @link http://redis.io/commands/expireat
436
+ * @author Daniele Alessandri <suppakilla@gmail.com>
437
+ */
438
+ class KeyExpireAt extends Command
439
+ {
440
+ /**
441
+ * {@inheritdoc}
442
+ */
443
+ public function getId()
444
+ {
445
+ return 'EXPIREAT';
446
+ }
447
+
448
+ /**
449
+ * {@inheritdoc}
450
+ */
451
+ public function parseResponse($data)
452
+ {
453
+ return (bool) $data;
454
+ }
455
+ }
456
+
457
+ /**
458
+ * @link http://redis.io/commands/blpop
459
+ * @author Daniele Alessandri <suppakilla@gmail.com>
460
+ */
461
+ class ListPopFirstBlocking extends Command
462
+ {
463
+ /**
464
+ * {@inheritdoc}
465
+ */
466
+ public function getId()
467
+ {
468
+ return 'BLPOP';
469
+ }
470
+
471
+ /**
472
+ * {@inheritdoc}
473
+ */
474
+ protected function filterArguments(array $arguments)
475
+ {
476
+ if (count($arguments) === 2 && is_array($arguments[0])) {
477
+ list($arguments, $timeout) = $arguments;
478
+ array_push($arguments, $timeout);
479
+ }
480
+
481
+ return $arguments;
482
+ }
483
+ }
484
+
485
+ /**
486
+ * @link http://redis.io/commands/unsubscribe
487
+ * @author Daniele Alessandri <suppakilla@gmail.com>
488
+ */
489
+ class PubSubUnsubscribe extends Command
490
+ {
491
+ /**
492
+ * {@inheritdoc}
493
+ */
494
+ public function getId()
495
+ {
496
+ return 'UNSUBSCRIBE';
497
+ }
498
+
499
+ /**
500
+ * {@inheritdoc}
501
+ */
502
+ protected function filterArguments(array $arguments)
503
+ {
504
+ return self::normalizeArguments($arguments);
505
+ }
506
+ }
507
+
508
+ /**
509
+ * @link http://redis.io/commands/info
510
+ * @author Daniele Alessandri <suppakilla@gmail.com>
511
+ */
512
+ class ServerInfo extends Command
513
+ {
514
+ /**
515
+ * {@inheritdoc}
516
+ */
517
+ public function getId()
518
+ {
519
+ return 'INFO';
520
+ }
521
+
522
+ /**
523
+ * {@inheritdoc}
524
+ */
525
+ public function parseResponse($data)
526
+ {
527
+ $info = array();
528
+ $infoLines = preg_split('/\r?\n/', $data);
529
+
530
+ foreach ($infoLines as $row) {
531
+ if (strpos($row, ':') === false) {
532
+ continue;
533
+ }
534
+
535
+ list($k, $v) = $this->parseRow($row);
536
+ $info[$k] = $v;
537
+ }
538
+
539
+ return $info;
540
+ }
541
+
542
+ /**
543
+ * Parses a single row of the response and returns the key-value pair.
544
+ *
545
+ * @param string $row Single row of the response.
546
+ *
547
+ * @return array
548
+ */
549
+ protected function parseRow($row)
550
+ {
551
+ list($k, $v) = explode(':', $row, 2);
552
+
553
+ if (preg_match('/^db\d+$/', $k)) {
554
+ $v = $this->parseDatabaseStats($v);
555
+ }
556
+
557
+ return array($k, $v);
558
+ }
559
+
560
+ /**
561
+ * Extracts the statistics of each logical DB from the string buffer.
562
+ *
563
+ * @param string $str Response buffer.
564
+ *
565
+ * @return array
566
+ */
567
+ protected function parseDatabaseStats($str)
568
+ {
569
+ $db = array();
570
+
571
+ foreach (explode(',', $str) as $dbvar) {
572
+ list($dbvk, $dbvv) = explode('=', $dbvar);
573
+ $db[trim($dbvk)] = $dbvv;
574
+ }
575
+
576
+ return $db;
577
+ }
578
+
579
+ /**
580
+ * Parses the response and extracts the allocation statistics.
581
+ *
582
+ * @param string $str Response buffer.
583
+ *
584
+ * @return array
585
+ */
586
+ protected function parseAllocationStats($str)
587
+ {
588
+ $stats = array();
589
+
590
+ foreach (explode(',', $str) as $kv) {
591
+ @list($size, $objects, $extra) = explode('=', $kv);
592
+
593
+ // hack to prevent incorrect values when parsing the >=256 key
594
+ if (isset($extra)) {
595
+ $size = ">=$objects";
596
+ $objects = $extra;
597
+ }
598
+
599
+ $stats[$size] = $objects;
600
+ }
601
+
602
+ return $stats;
603
+ }
604
+ }
605
+
606
+ /**
607
+ * @link http://redis.io/commands/evalsha
608
+ * @author Daniele Alessandri <suppakilla@gmail.com>
609
+ */
610
+ class ServerEvalSHA extends ServerEval
611
+ {
612
+ /**
613
+ * {@inheritdoc}
614
+ */
615
+ public function getId()
616
+ {
617
+ return 'EVALSHA';
618
+ }
619
+
620
+ /**
621
+ * Returns the SHA1 hash of the body of the script.
622
+ *
623
+ * @return string SHA1 hash.
624
+ */
625
+ public function getScriptHash()
626
+ {
627
+ return $this->getArgument(0);
628
+ }
629
+ }
630
+
631
+ /**
632
+ * @link http://redis.io/commands/expire
633
+ * @author Daniele Alessandri <suppakilla@gmail.com>
634
+ */
635
+ class KeyExpire extends Command
636
+ {
637
+ /**
638
+ * {@inheritdoc}
639
+ */
640
+ public function getId()
641
+ {
642
+ return 'EXPIRE';
643
+ }
644
+
645
+ /**
646
+ * {@inheritdoc}
647
+ */
648
+ public function parseResponse($data)
649
+ {
650
+ return (bool) $data;
651
+ }
652
+ }
653
+
654
+ /**
655
+ * @link http://redis.io/commands/subscribe
656
+ * @author Daniele Alessandri <suppakilla@gmail.com>
657
+ */
658
+ class PubSubSubscribe extends Command
659
+ {
660
+ /**
661
+ * {@inheritdoc}
662
+ */
663
+ public function getId()
664
+ {
665
+ return 'SUBSCRIBE';
666
+ }
667
+
668
+ /**
669
+ * {@inheritdoc}
670
+ */
671
+ protected function filterArguments(array $arguments)
672
+ {
673
+ return self::normalizeArguments($arguments);
674
+ }
675
+ }
676
+
677
+ /**
678
+ * @link http://redis.io/commands/rpush
679
+ * @author Daniele Alessandri <suppakilla@gmail.com>
680
+ */
681
+ class ListPushTail extends Command
682
+ {
683
+ /**
684
+ * {@inheritdoc}
685
+ */
686
+ public function getId()
687
+ {
688
+ return 'RPUSH';
689
+ }
690
+
691
+ /**
692
+ * {@inheritdoc}
693
+ */
694
+ protected function filterArguments(array $arguments)
695
+ {
696
+ return self::normalizeVariadic($arguments);
697
+ }
698
+ }
699
+
700
+ /**
701
+ * @link http://redis.io/commands/ttl
702
+ * @author Daniele Alessandri <suppakilla@gmail.com>
703
+ */
704
+ class KeyTimeToLive extends Command
705
+ {
706
+ /**
707
+ * {@inheritdoc}
708
+ */
709
+ public function getId()
710
+ {
711
+ return 'TTL';
712
+ }
713
+ }
714
+
715
+ /**
716
+ * @link http://redis.io/commands/zunionstore
717
+ * @author Daniele Alessandri <suppakilla@gmail.com>
718
+ */
719
+ class ZSetUnionStore extends Command
720
+ {
721
+ /**
722
+ * {@inheritdoc}
723
+ */
724
+ public function getId()
725
+ {
726
+ return 'ZUNIONSTORE';
727
+ }
728
+
729
+ /**
730
+ * {@inheritdoc}
731
+ */
732
+ protected function filterArguments(array $arguments)
733
+ {
734
+ $options = array();
735
+ $argc = count($arguments);
736
+
737
+ if ($argc > 2 && is_array($arguments[$argc - 1])) {
738
+ $options = $this->prepareOptions(array_pop($arguments));
739
+ }
740
+
741
+ if (is_array($arguments[1])) {
742
+ $arguments = array_merge(
743
+ array($arguments[0], count($arguments[1])),
744
+ $arguments[1]
745
+ );
746
+ }
747
+
748
+ return array_merge($arguments, $options);
749
+ }
750
+
751
+ /**
752
+ * Returns a list of options and modifiers compatible with Redis.
753
+ *
754
+ * @param array $options List of options.
755
+ *
756
+ * @return array
757
+ */
758
+ private function prepareOptions($options)
759
+ {
760
+ $opts = array_change_key_case($options, CASE_UPPER);
761
+ $finalizedOpts = array();
762
+
763
+ if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) {
764
+ $finalizedOpts[] = 'WEIGHTS';
765
+
766
+ foreach ($opts['WEIGHTS'] as $weight) {
767
+ $finalizedOpts[] = $weight;
768
+ }
769
+ }
770
+
771
+ if (isset($opts['AGGREGATE'])) {
772
+ $finalizedOpts[] = 'AGGREGATE';
773
+ $finalizedOpts[] = $opts['AGGREGATE'];
774
+ }
775
+
776
+ return $finalizedOpts;
777
+ }
778
+ }
779
+
780
+ /**
781
+ * @link http://redis.io/commands/zrangebyscore
782
+ * @author Daniele Alessandri <suppakilla@gmail.com>
783
+ */
784
+ class ZSetRangeByScore extends ZSetRange
785
+ {
786
+ /**
787
+ * {@inheritdoc}
788
+ */
789
+ public function getId()
790
+ {
791
+ return 'ZRANGEBYSCORE';
792
+ }
793
+
794
+ /**
795
+ * {@inheritdoc}
796
+ */
797
+ protected function prepareOptions($options)
798
+ {
799
+ $opts = array_change_key_case($options, CASE_UPPER);
800
+ $finalizedOpts = array();
801
+
802
+ if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
803
+ $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
804
+
805
+ $finalizedOpts[] = 'LIMIT';
806
+ $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
807
+ $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
808
+ }
809
+
810
+ return array_merge($finalizedOpts, parent::prepareOptions($options));
811
+ }
812
+
813
+ /**
814
+ * {@inheritdoc}
815
+ */
816
+ protected function withScores()
817
+ {
818
+ $arguments = $this->getArguments();
819
+
820
+ for ($i = 3; $i < count($arguments); $i++) {
821
+ switch (strtoupper($arguments[$i])) {
822
+ case 'WITHSCORES':
823
+ return true;
824
+
825
+ case 'LIMIT':
826
+ $i += 2;
827
+ break;
828
+ }
829
+ }
830
+
831
+ return false;
832
+ }
833
+ }
834
+
835
+ /**
836
+ * @link http://redis.io/commands/zremrangebyrank
837
+ * @author Daniele Alessandri <suppakilla@gmail.com>
838
+ */
839
+ class ZSetRemoveRangeByRank extends Command
840
+ {
841
+ /**
842
+ * {@inheritdoc}
843
+ */
844
+ public function getId()
845
+ {
846
+ return 'ZREMRANGEBYRANK';
847
+ }
848
+ }
849
+
850
+ /**
851
+ * @link http://redis.io/commands/spop
852
+ * @author Daniele Alessandri <suppakilla@gmail.com>
853
+ */
854
+ class SetPop extends Command
855
+ {
856
+ /**
857
+ * {@inheritdoc}
858
+ */
859
+ public function getId()
860
+ {
861
+ return 'SPOP';
862
+ }
863
+ }
864
+
865
+ /**
866
+ * @link http://redis.io/commands/smove
867
+ * @author Daniele Alessandri <suppakilla@gmail.com>
868
+ */
869
+ class SetMove extends Command
870
+ {
871
+ /**
872
+ * {@inheritdoc}
873
+ */
874
+ public function getId()
875
+ {
876
+ return 'SMOVE';
877
+ }
878
+
879
+ /**
880
+ * {@inheritdoc}
881
+ */
882
+ public function parseResponse($data)
883
+ {
884
+ return (bool) $data;
885
+ }
886
+ }
887
+
888
+ /**
889
+ * @link http://redis.io/commands/sismember
890
+ * @author Daniele Alessandri <suppakilla@gmail.com>
891
+ */
892
+ class SetIsMember extends Command
893
+ {
894
+ /**
895
+ * {@inheritdoc}
896
+ */
897
+ public function getId()
898
+ {
899
+ return 'SISMEMBER';
900
+ }
901
+
902
+ /**
903
+ * {@inheritdoc}
904
+ */
905
+ public function parseResponse($data)
906
+ {
907
+ return (bool) $data;
908
+ }
909
+ }
910
+
911
+ /**
912
+ * @link http://redis.io/commands/smembers
913
+ * @author Daniele Alessandri <suppakilla@gmail.com>
914
+ */
915
+ class SetMembers extends Command
916
+ {
917
+ /**
918
+ * {@inheritdoc}
919
+ */
920
+ public function getId()
921
+ {
922
+ return 'SMEMBERS';
923
+ }
924
+ }
925
+
926
+ /**
927
+ * @link http://redis.io/commands/zremrangebyscore
928
+ * @author Daniele Alessandri <suppakilla@gmail.com>
929
+ */
930
+ class ZSetRemoveRangeByScore extends Command
931
+ {
932
+ /**
933
+ * {@inheritdoc}
934
+ */
935
+ public function getId()
936
+ {
937
+ return 'ZREMRANGEBYSCORE';
938
+ }
939
+ }
940
+
941
+ /**
942
+ * @link http://redis.io/commands/srandmember
943
+ * @author Daniele Alessandri <suppakilla@gmail.com>
944
+ */
945
+ class SetRandomMember extends Command
946
+ {
947
+ /**
948
+ * {@inheritdoc}
949
+ */
950
+ public function getId()
951
+ {
952
+ return 'SRANDMEMBER';
953
+ }
954
+ }
955
+
956
+ /**
957
+ * @link http://redis.io/commands/sscan
958
+ * @author Daniele Alessandri <suppakilla@gmail.com>
959
+ */
960
+ class SetScan extends Command
961
+ {
962
+ /**
963
+ * {@inheritdoc}
964
+ */
965
+ public function getId()
966
+ {
967
+ return 'SSCAN';
968
+ }
969
+
970
+ /**
971
+ * {@inheritdoc}
972
+ */
973
+ protected function filterArguments(array $arguments)
974
+ {
975
+ if (count($arguments) === 3 && is_array($arguments[2])) {
976
+ $options = $this->prepareOptions(array_pop($arguments));
977
+ $arguments = array_merge($arguments, $options);
978
+ }
979
+
980
+ return $arguments;
981
+ }
982
+
983
+ /**
984
+ * Returns a list of options and modifiers compatible with Redis.
985
+ *
986
+ * @param array $options List of options.
987
+ *
988
+ * @return array
989
+ */
990
+ protected function prepareOptions($options)
991
+ {
992
+ $options = array_change_key_case($options, CASE_UPPER);
993
+ $normalized = array();
994
+
995
+ if (!empty($options['MATCH'])) {
996
+ $normalized[] = 'MATCH';
997
+ $normalized[] = $options['MATCH'];
998
+ }
999
+
1000
+ if (!empty($options['COUNT'])) {
1001
+ $normalized[] = 'COUNT';
1002
+ $normalized[] = $options['COUNT'];
1003
+ }
1004
+
1005
+ return $normalized;
1006
+ }
1007
+ }
1008
+
1009
+ /**
1010
+ * @link http://redis.io/commands/zremrangebylex
1011
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1012
+ */
1013
+ class ZSetRemoveRangeByLex extends Command
1014
+ {
1015
+ /**
1016
+ * {@inheritdoc}
1017
+ */
1018
+ public function getId()
1019
+ {
1020
+ return 'ZREMRANGEBYLEX';
1021
+ }
1022
+ }
1023
+
1024
+ /**
1025
+ * @link http://redis.io/commands/bitop
1026
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1027
+ */
1028
+ class StringBitOp extends Command
1029
+ {
1030
+ /**
1031
+ * {@inheritdoc}
1032
+ */
1033
+ public function getId()
1034
+ {
1035
+ return 'BITOP';
1036
+ }
1037
+
1038
+ /**
1039
+ * {@inheritdoc}
1040
+ */
1041
+ protected function filterArguments(array $arguments)
1042
+ {
1043
+ if (count($arguments) === 3 && is_array($arguments[2])) {
1044
+ list($operation, $destination, ) = $arguments;
1045
+ $arguments = $arguments[2];
1046
+ array_unshift($arguments, $operation, $destination);
1047
+ }
1048
+
1049
+ return $arguments;
1050
+ }
1051
+ }
1052
+
1053
+ /**
1054
+ * @link http://redis.io/commands/bitcount
1055
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1056
+ */
1057
+ class StringBitCount extends Command
1058
+ {
1059
+ /**
1060
+ * {@inheritdoc}
1061
+ */
1062
+ public function getId()
1063
+ {
1064
+ return 'BITCOUNT';
1065
+ }
1066
+ }
1067
+
1068
+ /**
1069
+ * @link http://redis.io/commands/append
1070
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1071
+ */
1072
+ class StringAppend extends Command
1073
+ {
1074
+ /**
1075
+ * {@inheritdoc}
1076
+ */
1077
+ public function getId()
1078
+ {
1079
+ return 'APPEND';
1080
+ }
1081
+ }
1082
+
1083
+ /**
1084
+ * @link http://redis.io/commands/sunion
1085
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1086
+ */
1087
+ class SetUnion extends SetIntersection
1088
+ {
1089
+ /**
1090
+ * {@inheritdoc}
1091
+ */
1092
+ public function getId()
1093
+ {
1094
+ return 'SUNION';
1095
+ }
1096
+ }
1097
+
1098
+ /**
1099
+ * @link http://redis.io/commands/sunionstore
1100
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1101
+ */
1102
+ class SetUnionStore extends SetIntersectionStore
1103
+ {
1104
+ /**
1105
+ * {@inheritdoc}
1106
+ */
1107
+ public function getId()
1108
+ {
1109
+ return 'SUNIONSTORE';
1110
+ }
1111
+ }
1112
+
1113
+ /**
1114
+ * @link http://redis.io/commands/srem
1115
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1116
+ */
1117
+ class SetRemove extends Command
1118
+ {
1119
+ /**
1120
+ * {@inheritdoc}
1121
+ */
1122
+ public function getId()
1123
+ {
1124
+ return 'SREM';
1125
+ }
1126
+
1127
+ /**
1128
+ * {@inheritdoc}
1129
+ */
1130
+ protected function filterArguments(array $arguments)
1131
+ {
1132
+ return self::normalizeVariadic($arguments);
1133
+ }
1134
+ }
1135
+
1136
+ /**
1137
+ * @link http://redis.io/commands/zrevrange
1138
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1139
+ */
1140
+ class ZSetReverseRange extends ZSetRange
1141
+ {
1142
+ /**
1143
+ * {@inheritdoc}
1144
+ */
1145
+ public function getId()
1146
+ {
1147
+ return 'ZREVRANGE';
1148
+ }
1149
+ }
1150
+
1151
+ /**
1152
+ * @link http://redis.io/commands/slowlog
1153
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1154
+ */
1155
+ class ServerSlowlog extends Command
1156
+ {
1157
+ /**
1158
+ * {@inheritdoc}
1159
+ */
1160
+ public function getId()
1161
+ {
1162
+ return 'SLOWLOG';
1163
+ }
1164
+
1165
+ /**
1166
+ * {@inheritdoc}
1167
+ */
1168
+ public function parseResponse($data)
1169
+ {
1170
+ if (is_array($data)) {
1171
+ $log = array();
1172
+
1173
+ foreach ($data as $index => $entry) {
1174
+ $log[$index] = array(
1175
+ 'id' => $entry[0],
1176
+ 'timestamp' => $entry[1],
1177
+ 'duration' => $entry[2],
1178
+ 'command' => $entry[3],
1179
+ );
1180
+ }
1181
+
1182
+ return $log;
1183
+ }
1184
+
1185
+ return $data;
1186
+ }
1187
+ }
1188
+
1189
+ /**
1190
+ * @link http://redis.io/commands/zscore
1191
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1192
+ */
1193
+ class ZSetScore extends Command
1194
+ {
1195
+ /**
1196
+ * {@inheritdoc}
1197
+ */
1198
+ public function getId()
1199
+ {
1200
+ return 'ZSCORE';
1201
+ }
1202
+ }
1203
+
1204
+ /**
1205
+ * @link http://redis.io/commands/slaveof
1206
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1207
+ */
1208
+ class ServerSlaveOf extends Command
1209
+ {
1210
+ /**
1211
+ * {@inheritdoc}
1212
+ */
1213
+ public function getId()
1214
+ {
1215
+ return 'SLAVEOF';
1216
+ }
1217
+
1218
+ /**
1219
+ * {@inheritdoc}
1220
+ */
1221
+ protected function filterArguments(array $arguments)
1222
+ {
1223
+ if (count($arguments) === 0 || $arguments[0] === 'NO ONE') {
1224
+ return array('NO', 'ONE');
1225
+ }
1226
+
1227
+ return $arguments;
1228
+ }
1229
+ }
1230
+
1231
+ /**
1232
+ * @link http://redis.io/commands/shutdown
1233
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1234
+ */
1235
+ class ServerShutdown extends Command
1236
+ {
1237
+ /**
1238
+ * {@inheritdoc}
1239
+ */
1240
+ public function getId()
1241
+ {
1242
+ return 'SHUTDOWN';
1243
+ }
1244
+ }
1245
+
1246
+ /**
1247
+ * @link http://redis.io/commands/script
1248
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1249
+ */
1250
+ class ServerScript extends Command
1251
+ {
1252
+ /**
1253
+ * {@inheritdoc}
1254
+ */
1255
+ public function getId()
1256
+ {
1257
+ return 'SCRIPT';
1258
+ }
1259
+ }
1260
+
1261
+ /**
1262
+ * @link http://redis.io/topics/sentinel
1263
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1264
+ */
1265
+ class ServerSentinel extends Command
1266
+ {
1267
+ /**
1268
+ * {@inheritdoc}
1269
+ */
1270
+ public function getId()
1271
+ {
1272
+ return 'SENTINEL';
1273
+ }
1274
+
1275
+ /**
1276
+ * {@inheritdoc}
1277
+ */
1278
+ public function parseResponse($data)
1279
+ {
1280
+ switch (strtolower($this->getArgument(0))) {
1281
+ case 'masters':
1282
+ case 'slaves':
1283
+ return self::processMastersOrSlaves($data);
1284
+
1285
+ default:
1286
+ return $data;
1287
+ }
1288
+ }
1289
+
1290
+ /**
1291
+ * Returns a processed response to SENTINEL MASTERS or SENTINEL SLAVES.
1292
+ *
1293
+ * @param array $servers List of Redis servers.
1294
+ *
1295
+ * @return array
1296
+ */
1297
+ protected static function processMastersOrSlaves(array $servers)
1298
+ {
1299
+ foreach ($servers as $idx => $node) {
1300
+ $processed = array();
1301
+ $count = count($node);
1302
+
1303
+ for ($i = 0; $i < $count; $i++) {
1304
+ $processed[$node[$i]] = $node[++$i];
1305
+ }
1306
+
1307
+ $servers[$idx] = $processed;
1308
+ }
1309
+
1310
+ return $servers;
1311
+ }
1312
+ }
1313
+
1314
+ /**
1315
+ * @link http://redis.io/commands/zscan
1316
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1317
+ */
1318
+ class ZSetScan extends Command
1319
+ {
1320
+ /**
1321
+ * {@inheritdoc}
1322
+ */
1323
+ public function getId()
1324
+ {
1325
+ return 'ZSCAN';
1326
+ }
1327
+
1328
+ /**
1329
+ * {@inheritdoc}
1330
+ */
1331
+ protected function filterArguments(array $arguments)
1332
+ {
1333
+ if (count($arguments) === 3 && is_array($arguments[2])) {
1334
+ $options = $this->prepareOptions(array_pop($arguments));
1335
+ $arguments = array_merge($arguments, $options);
1336
+ }
1337
+
1338
+ return $arguments;
1339
+ }
1340
+
1341
+ /**
1342
+ * Returns a list of options and modifiers compatible with Redis.
1343
+ *
1344
+ * @param array $options List of options.
1345
+ *
1346
+ * @return array
1347
+ */
1348
+ protected function prepareOptions($options)
1349
+ {
1350
+ $options = array_change_key_case($options, CASE_UPPER);
1351
+ $normalized = array();
1352
+
1353
+ if (!empty($options['MATCH'])) {
1354
+ $normalized[] = 'MATCH';
1355
+ $normalized[] = $options['MATCH'];
1356
+ }
1357
+
1358
+ if (!empty($options['COUNT'])) {
1359
+ $normalized[] = 'COUNT';
1360
+ $normalized[] = $options['COUNT'];
1361
+ }
1362
+
1363
+ return $normalized;
1364
+ }
1365
+
1366
+ /**
1367
+ * {@inheritdoc}
1368
+ */
1369
+ public function parseResponse($data)
1370
+ {
1371
+ if (is_array($data)) {
1372
+ $members = $data[1];
1373
+ $result = array();
1374
+
1375
+ for ($i = 0; $i < count($members); $i++) {
1376
+ $result[$members[$i]] = (float) $members[++$i];
1377
+ }
1378
+
1379
+ $data[1] = $result;
1380
+ }
1381
+
1382
+ return $data;
1383
+ }
1384
+ }
1385
+
1386
+ /**
1387
+ * @link http://redis.io/commands/zrevrank
1388
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1389
+ */
1390
+ class ZSetReverseRank extends Command
1391
+ {
1392
+ /**
1393
+ * {@inheritdoc}
1394
+ */
1395
+ public function getId()
1396
+ {
1397
+ return 'ZREVRANK';
1398
+ }
1399
+ }
1400
+
1401
+ /**
1402
+ * @link http://redis.io/commands/sdiffstore
1403
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1404
+ */
1405
+ class SetDifferenceStore extends SetIntersectionStore
1406
+ {
1407
+ /**
1408
+ * {@inheritdoc}
1409
+ */
1410
+ public function getId()
1411
+ {
1412
+ return 'SDIFFSTORE';
1413
+ }
1414
+ }
1415
+
1416
+ /**
1417
+ * @link http://redis.io/commands/zrevrangebyscore
1418
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1419
+ */
1420
+ class ZSetReverseRangeByScore extends ZSetRangeByScore
1421
+ {
1422
+ /**
1423
+ * {@inheritdoc}
1424
+ */
1425
+ public function getId()
1426
+ {
1427
+ return 'ZREVRANGEBYSCORE';
1428
+ }
1429
+ }
1430
+
1431
+ /**
1432
+ * @link http://redis.io/commands/sdiff
1433
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1434
+ */
1435
+ class SetDifference extends SetIntersection
1436
+ {
1437
+ /**
1438
+ * {@inheritdoc}
1439
+ */
1440
+ public function getId()
1441
+ {
1442
+ return 'SDIFF';
1443
+ }
1444
+ }
1445
+
1446
+ /**
1447
+ * @link http://redis.io/commands/scard
1448
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1449
+ */
1450
+ class SetCardinality extends Command
1451
+ {
1452
+ /**
1453
+ * {@inheritdoc}
1454
+ */
1455
+ public function getId()
1456
+ {
1457
+ return 'SCARD';
1458
+ }
1459
+ }
1460
+
1461
+ /**
1462
+ * @link http://redis.io/commands/time
1463
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1464
+ */
1465
+ class ServerTime extends Command
1466
+ {
1467
+ /**
1468
+ * {@inheritdoc}
1469
+ */
1470
+ public function getId()
1471
+ {
1472
+ return 'TIME';
1473
+ }
1474
+ }
1475
+
1476
+ /**
1477
+ * @link http://redis.io/commands/sadd
1478
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1479
+ */
1480
+ class SetAdd extends Command
1481
+ {
1482
+ /**
1483
+ * {@inheritdoc}
1484
+ */
1485
+ public function getId()
1486
+ {
1487
+ return 'SADD';
1488
+ }
1489
+
1490
+ /**
1491
+ * {@inheritdoc}
1492
+ */
1493
+ protected function filterArguments(array $arguments)
1494
+ {
1495
+ return self::normalizeVariadic($arguments);
1496
+ }
1497
+ }
1498
+
1499
+ /**
1500
+ * @link http://redis.io/commands/bitpos
1501
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1502
+ */
1503
+ class StringBitPos extends Command
1504
+ {
1505
+ /**
1506
+ * {@inheritdoc}
1507
+ */
1508
+ public function getId()
1509
+ {
1510
+ return 'BITPOS';
1511
+ }
1512
+ }
1513
+
1514
+ /**
1515
+ * @link http://redis.io/commands/decrby
1516
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1517
+ */
1518
+ class StringDecrementBy extends Command
1519
+ {
1520
+ /**
1521
+ * {@inheritdoc}
1522
+ */
1523
+ public function getId()
1524
+ {
1525
+ return 'DECRBY';
1526
+ }
1527
+ }
1528
+
1529
+ /**
1530
+ * @link http://redis.io/commands/substr
1531
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1532
+ */
1533
+ class StringSubstr extends Command
1534
+ {
1535
+ /**
1536
+ * {@inheritdoc}
1537
+ */
1538
+ public function getId()
1539
+ {
1540
+ return 'SUBSTR';
1541
+ }
1542
+ }
1543
+
1544
+ /**
1545
+ * @link http://redis.io/commands/zlexcount
1546
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1547
+ */
1548
+ class ZSetLexCount extends Command
1549
+ {
1550
+ /**
1551
+ * {@inheritdoc}
1552
+ */
1553
+ public function getId()
1554
+ {
1555
+ return 'ZLEXCOUNT';
1556
+ }
1557
+ }
1558
+
1559
+ /**
1560
+ * @link http://redis.io/commands/zinterstore
1561
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1562
+ */
1563
+ class ZSetIntersectionStore extends ZSetUnionStore
1564
+ {
1565
+ /**
1566
+ * {@inheritdoc}
1567
+ */
1568
+ public function getId()
1569
+ {
1570
+ return 'ZINTERSTORE';
1571
+ }
1572
+ }
1573
+
1574
+ /**
1575
+ * @link http://redis.io/commands/strlen
1576
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1577
+ */
1578
+ class StringStrlen extends Command
1579
+ {
1580
+ /**
1581
+ * {@inheritdoc}
1582
+ */
1583
+ public function getId()
1584
+ {
1585
+ return 'STRLEN';
1586
+ }
1587
+ }
1588
+
1589
+ /**
1590
+ * @link http://redis.io/commands/setrange
1591
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1592
+ */
1593
+ class StringSetRange extends Command
1594
+ {
1595
+ /**
1596
+ * {@inheritdoc}
1597
+ */
1598
+ public function getId()
1599
+ {
1600
+ return 'SETRANGE';
1601
+ }
1602
+ }
1603
+
1604
+ /**
1605
+ * @link http://redis.io/commands/msetnx
1606
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1607
+ */
1608
+ class StringSetMultiplePreserve extends StringSetMultiple
1609
+ {
1610
+ /**
1611
+ * {@inheritdoc}
1612
+ */
1613
+ public function getId()
1614
+ {
1615
+ return 'MSETNX';
1616
+ }
1617
+
1618
+ /**
1619
+ * {@inheritdoc}
1620
+ */
1621
+ public function parseResponse($data)
1622
+ {
1623
+ return (bool) $data;
1624
+ }
1625
+ }
1626
+
1627
+ /**
1628
+ * @link http://redis.io/commands/setnx
1629
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1630
+ */
1631
+ class StringSetPreserve extends Command
1632
+ {
1633
+ /**
1634
+ * {@inheritdoc}
1635
+ */
1636
+ public function getId()
1637
+ {
1638
+ return 'SETNX';
1639
+ }
1640
+
1641
+ /**
1642
+ * {@inheritdoc}
1643
+ */
1644
+ public function parseResponse($data)
1645
+ {
1646
+ return (bool) $data;
1647
+ }
1648
+ }
1649
+
1650
+ /**
1651
+ * @link http://redis.io/commands/discard
1652
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1653
+ */
1654
+ class TransactionDiscard extends Command
1655
+ {
1656
+ /**
1657
+ * {@inheritdoc}
1658
+ */
1659
+ public function getId()
1660
+ {
1661
+ return 'DISCARD';
1662
+ }
1663
+ }
1664
+
1665
+ /**
1666
+ * @link http://redis.io/commands/exec
1667
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1668
+ */
1669
+ class TransactionExec extends Command
1670
+ {
1671
+ /**
1672
+ * {@inheritdoc}
1673
+ */
1674
+ public function getId()
1675
+ {
1676
+ return 'EXEC';
1677
+ }
1678
+ }
1679
+
1680
+ /**
1681
+ * @link http://redis.io/commands/zcard
1682
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1683
+ */
1684
+ class ZSetCardinality extends Command
1685
+ {
1686
+ /**
1687
+ * {@inheritdoc}
1688
+ */
1689
+ public function getId()
1690
+ {
1691
+ return 'ZCARD';
1692
+ }
1693
+ }
1694
+
1695
+ /**
1696
+ * @link http://redis.io/commands/zcount
1697
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1698
+ */
1699
+ class ZSetCount extends Command
1700
+ {
1701
+ /**
1702
+ * {@inheritdoc}
1703
+ */
1704
+ public function getId()
1705
+ {
1706
+ return 'ZCOUNT';
1707
+ }
1708
+ }
1709
+
1710
+ /**
1711
+ * @link http://redis.io/commands/zadd
1712
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1713
+ */
1714
+ class ZSetAdd extends Command
1715
+ {
1716
+ /**
1717
+ * {@inheritdoc}
1718
+ */
1719
+ public function getId()
1720
+ {
1721
+ return 'ZADD';
1722
+ }
1723
+
1724
+ /**
1725
+ * {@inheritdoc}
1726
+ */
1727
+ protected function filterArguments(array $arguments)
1728
+ {
1729
+ if (count($arguments) === 2 && is_array($arguments[1])) {
1730
+ $flattened = array($arguments[0]);
1731
+
1732
+ foreach ($arguments[1] as $member => $score) {
1733
+ $flattened[] = $score;
1734
+ $flattened[] = $member;
1735
+ }
1736
+
1737
+ return $flattened;
1738
+ }
1739
+
1740
+ return $arguments;
1741
+ }
1742
+ }
1743
+
1744
+ /**
1745
+ * @link http://redis.io/commands/watch
1746
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1747
+ */
1748
+ class TransactionWatch extends Command
1749
+ {
1750
+ /**
1751
+ * {@inheritdoc}
1752
+ */
1753
+ public function getId()
1754
+ {
1755
+ return 'WATCH';
1756
+ }
1757
+
1758
+ /**
1759
+ * {@inheritdoc}
1760
+ */
1761
+ protected function filterArguments(array $arguments)
1762
+ {
1763
+ if (isset($arguments[0]) && is_array($arguments[0])) {
1764
+ return $arguments[0];
1765
+ }
1766
+
1767
+ return $arguments;
1768
+ }
1769
+ }
1770
+
1771
+ /**
1772
+ * @link http://redis.io/commands/multi
1773
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1774
+ */
1775
+ class TransactionMulti extends Command
1776
+ {
1777
+ /**
1778
+ * {@inheritdoc}
1779
+ */
1780
+ public function getId()
1781
+ {
1782
+ return 'MULTI';
1783
+ }
1784
+ }
1785
+
1786
+ /**
1787
+ * @link http://redis.io/commands/unwatch
1788
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1789
+ */
1790
+ class TransactionUnwatch extends Command
1791
+ {
1792
+ /**
1793
+ * {@inheritdoc}
1794
+ */
1795
+ public function getId()
1796
+ {
1797
+ return 'UNWATCH';
1798
+ }
1799
+ }
1800
+
1801
+ /**
1802
+ * @link http://redis.io/commands/zrangebylex
1803
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1804
+ */
1805
+ class ZSetRangeByLex extends ZSetRange
1806
+ {
1807
+ /**
1808
+ * {@inheritdoc}
1809
+ */
1810
+ public function getId()
1811
+ {
1812
+ return 'ZRANGEBYLEX';
1813
+ }
1814
+
1815
+ /**
1816
+ * {@inheritdoc}
1817
+ */
1818
+ protected function prepareOptions($options)
1819
+ {
1820
+ $opts = array_change_key_case($options, CASE_UPPER);
1821
+ $finalizedOpts = array();
1822
+
1823
+ if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
1824
+ $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
1825
+
1826
+ $finalizedOpts[] = 'LIMIT';
1827
+ $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
1828
+ $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
1829
+ }
1830
+
1831
+ return $finalizedOpts;
1832
+ }
1833
+
1834
+ /**
1835
+ * {@inheritdoc}
1836
+ */
1837
+ protected function withScores()
1838
+ {
1839
+ return false;
1840
+ }
1841
+ }
1842
+
1843
+ /**
1844
+ * @link http://redis.io/commands/zrank
1845
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1846
+ */
1847
+ class ZSetRank extends Command
1848
+ {
1849
+ /**
1850
+ * {@inheritdoc}
1851
+ */
1852
+ public function getId()
1853
+ {
1854
+ return 'ZRANK';
1855
+ }
1856
+ }
1857
+
1858
+ /**
1859
+ * @link http://redis.io/commands/mget
1860
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1861
+ */
1862
+ class StringGetMultiple extends Command
1863
+ {
1864
+ /**
1865
+ * {@inheritdoc}
1866
+ */
1867
+ public function getId()
1868
+ {
1869
+ return 'MGET';
1870
+ }
1871
+
1872
+ /**
1873
+ * {@inheritdoc}
1874
+ */
1875
+ protected function filterArguments(array $arguments)
1876
+ {
1877
+ return self::normalizeArguments($arguments);
1878
+ }
1879
+ }
1880
+
1881
+ /**
1882
+ * @link http://redis.io/commands/getrange
1883
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1884
+ */
1885
+ class StringGetRange extends Command
1886
+ {
1887
+ /**
1888
+ * {@inheritdoc}
1889
+ */
1890
+ public function getId()
1891
+ {
1892
+ return 'GETRANGE';
1893
+ }
1894
+ }
1895
+
1896
+ /**
1897
+ * @link http://redis.io/commands/zrem
1898
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1899
+ */
1900
+ class ZSetRemove extends Command
1901
+ {
1902
+ /**
1903
+ * {@inheritdoc}
1904
+ */
1905
+ public function getId()
1906
+ {
1907
+ return 'ZREM';
1908
+ }
1909
+
1910
+ /**
1911
+ * {@inheritdoc}
1912
+ */
1913
+ protected function filterArguments(array $arguments)
1914
+ {
1915
+ return self::normalizeVariadic($arguments);
1916
+ }
1917
+ }
1918
+
1919
+ /**
1920
+ * @link http://redis.io/commands/getbit
1921
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1922
+ */
1923
+ class StringGetBit extends Command
1924
+ {
1925
+ /**
1926
+ * {@inheritdoc}
1927
+ */
1928
+ public function getId()
1929
+ {
1930
+ return 'GETBIT';
1931
+ }
1932
+ }
1933
+
1934
+ /**
1935
+ * @link http://redis.io/commands/zincrby
1936
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1937
+ */
1938
+ class ZSetIncrementBy extends Command
1939
+ {
1940
+ /**
1941
+ * {@inheritdoc}
1942
+ */
1943
+ public function getId()
1944
+ {
1945
+ return 'ZINCRBY';
1946
+ }
1947
+ }
1948
+
1949
+ /**
1950
+ * @link http://redis.io/commands/get
1951
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1952
+ */
1953
+ class StringGet extends Command
1954
+ {
1955
+ /**
1956
+ * {@inheritdoc}
1957
+ */
1958
+ public function getId()
1959
+ {
1960
+ return 'GET';
1961
+ }
1962
+ }
1963
+
1964
+ /**
1965
+ * @link http://redis.io/commands/getset
1966
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1967
+ */
1968
+ class StringGetSet extends Command
1969
+ {
1970
+ /**
1971
+ * {@inheritdoc}
1972
+ */
1973
+ public function getId()
1974
+ {
1975
+ return 'GETSET';
1976
+ }
1977
+ }
1978
+
1979
+ /**
1980
+ * @link http://redis.io/commands/incr
1981
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1982
+ */
1983
+ class StringIncrement extends Command
1984
+ {
1985
+ /**
1986
+ * {@inheritdoc}
1987
+ */
1988
+ public function getId()
1989
+ {
1990
+ return 'INCR';
1991
+ }
1992
+ }
1993
+
1994
+ /**
1995
+ * @link http://redis.io/commands/set
1996
+ * @author Daniele Alessandri <suppakilla@gmail.com>
1997
+ */
1998
+ class StringSet extends Command
1999
+ {
2000
+ /**
2001
+ * {@inheritdoc}
2002
+ */
2003
+ public function getId()
2004
+ {
2005
+ return 'SET';
2006
+ }
2007
+ }
2008
+
2009
+ /**
2010
+ * @link http://redis.io/commands/setbit
2011
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2012
+ */
2013
+ class StringSetBit extends Command
2014
+ {
2015
+ /**
2016
+ * {@inheritdoc}
2017
+ */
2018
+ public function getId()
2019
+ {
2020
+ return 'SETBIT';
2021
+ }
2022
+ }
2023
+
2024
+ /**
2025
+ * @link http://redis.io/commands/psetex
2026
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2027
+ */
2028
+ class StringPreciseSetExpire extends StringSetExpire
2029
+ {
2030
+ /**
2031
+ * {@inheritdoc}
2032
+ */
2033
+ public function getId()
2034
+ {
2035
+ return 'PSETEX';
2036
+ }
2037
+ }
2038
+
2039
+ /**
2040
+ * @link http://redis.io/commands/incrbyfloat
2041
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2042
+ */
2043
+ class StringIncrementByFloat extends Command
2044
+ {
2045
+ /**
2046
+ * {@inheritdoc}
2047
+ */
2048
+ public function getId()
2049
+ {
2050
+ return 'INCRBYFLOAT';
2051
+ }
2052
+ }
2053
+
2054
+ /**
2055
+ * @link http://redis.io/commands/incrby
2056
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2057
+ */
2058
+ class StringIncrementBy extends Command
2059
+ {
2060
+ /**
2061
+ * {@inheritdoc}
2062
+ */
2063
+ public function getId()
2064
+ {
2065
+ return 'INCRBY';
2066
+ }
2067
+ }
2068
+
2069
+ /**
2070
+ * @link http://redis.io/commands/save
2071
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2072
+ */
2073
+ class ServerSave extends Command
2074
+ {
2075
+ /**
2076
+ * {@inheritdoc}
2077
+ */
2078
+ public function getId()
2079
+ {
2080
+ return 'SAVE';
2081
+ }
2082
+ }
2083
+
2084
+ /**
2085
+ * @link http://redis.io/commands/decr
2086
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2087
+ */
2088
+ class StringDecrement extends Command
2089
+ {
2090
+ /**
2091
+ * {@inheritdoc}
2092
+ */
2093
+ public function getId()
2094
+ {
2095
+ return 'DECR';
2096
+ }
2097
+ }
2098
+
2099
+ /**
2100
+ * @link http://redis.io/commands/flushall
2101
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2102
+ */
2103
+ class ServerFlushAll extends Command
2104
+ {
2105
+ /**
2106
+ * {@inheritdoc}
2107
+ */
2108
+ public function getId()
2109
+ {
2110
+ return 'FLUSHALL';
2111
+ }
2112
+ }
2113
+
2114
+ /**
2115
+ * @link http://redis.io/commands/del
2116
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2117
+ */
2118
+ class KeyDelete extends Command
2119
+ {
2120
+ /**
2121
+ * {@inheritdoc}
2122
+ */
2123
+ public function getId()
2124
+ {
2125
+ return 'DEL';
2126
+ }
2127
+
2128
+ /**
2129
+ * {@inheritdoc}
2130
+ */
2131
+ protected function filterArguments(array $arguments)
2132
+ {
2133
+ return self::normalizeArguments($arguments);
2134
+ }
2135
+ }
2136
+
2137
+ /**
2138
+ * @link http://redis.io/commands/dump
2139
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2140
+ */
2141
+ class KeyDump extends Command
2142
+ {
2143
+ /**
2144
+ * {@inheritdoc}
2145
+ */
2146
+ public function getId()
2147
+ {
2148
+ return 'DUMP';
2149
+ }
2150
+ }
2151
+
2152
+ /**
2153
+ * @link http://redis.io/commands/exists
2154
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2155
+ */
2156
+ class KeyExists extends Command
2157
+ {
2158
+ /**
2159
+ * {@inheritdoc}
2160
+ */
2161
+ public function getId()
2162
+ {
2163
+ return 'EXISTS';
2164
+ }
2165
+
2166
+ /**
2167
+ * {@inheritdoc}
2168
+ */
2169
+ public function parseResponse($data)
2170
+ {
2171
+ return (bool) $data;
2172
+ }
2173
+ }
2174
+
2175
+ /**
2176
+ * @link http://redis.io/commands/pfmerge
2177
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2178
+ */
2179
+ class HyperLogLogMerge extends Command
2180
+ {
2181
+ /**
2182
+ * {@inheritdoc}
2183
+ */
2184
+ public function getId()
2185
+ {
2186
+ return 'PFMERGE';
2187
+ }
2188
+
2189
+ /**
2190
+ * {@inheritdoc}
2191
+ */
2192
+ protected function filterArguments(array $arguments)
2193
+ {
2194
+ return self::normalizeArguments($arguments);
2195
+ }
2196
+ }
2197
+
2198
+ /**
2199
+ * @link http://redis.io/commands/pfcount
2200
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2201
+ */
2202
+ class HyperLogLogCount extends Command
2203
+ {
2204
+ /**
2205
+ * {@inheritdoc}
2206
+ */
2207
+ public function getId()
2208
+ {
2209
+ return 'PFCOUNT';
2210
+ }
2211
+
2212
+ /**
2213
+ * {@inheritdoc}
2214
+ */
2215
+ protected function filterArguments(array $arguments)
2216
+ {
2217
+ return self::normalizeArguments($arguments);
2218
+ }
2219
+ }
2220
+
2221
+ /**
2222
+ * @link http://redis.io/commands/hvals
2223
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2224
+ */
2225
+ class HashValues extends Command
2226
+ {
2227
+ /**
2228
+ * {@inheritdoc}
2229
+ */
2230
+ public function getId()
2231
+ {
2232
+ return 'HVALS';
2233
+ }
2234
+ }
2235
+
2236
+ /**
2237
+ * @link http://redis.io/commands/pfadd
2238
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2239
+ */
2240
+ class HyperLogLogAdd extends Command
2241
+ {
2242
+ /**
2243
+ * {@inheritdoc}
2244
+ */
2245
+ public function getId()
2246
+ {
2247
+ return 'PFADD';
2248
+ }
2249
+
2250
+ /**
2251
+ * {@inheritdoc}
2252
+ */
2253
+ protected function filterArguments(array $arguments)
2254
+ {
2255
+ return self::normalizeVariadic($arguments);
2256
+ }
2257
+
2258
+ /**
2259
+ * {@inheritdoc}
2260
+ */
2261
+ public function parseResponse($data)
2262
+ {
2263
+ return (bool) $data;
2264
+ }
2265
+ }
2266
+
2267
+ /**
2268
+ * @link http://redis.io/commands/keys
2269
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2270
+ */
2271
+ class KeyKeys extends Command
2272
+ {
2273
+ /**
2274
+ * {@inheritdoc}
2275
+ */
2276
+ public function getId()
2277
+ {
2278
+ return 'KEYS';
2279
+ }
2280
+ }
2281
+
2282
+ /**
2283
+ * @link http://redis.io/commands/move
2284
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2285
+ */
2286
+ class KeyMove extends Command
2287
+ {
2288
+ /**
2289
+ * {@inheritdoc}
2290
+ */
2291
+ public function getId()
2292
+ {
2293
+ return 'MOVE';
2294
+ }
2295
+
2296
+ /**
2297
+ * {@inheritdoc}
2298
+ */
2299
+ public function parseResponse($data)
2300
+ {
2301
+ return (bool) $data;
2302
+ }
2303
+ }
2304
+
2305
+ /**
2306
+ * @link http://redis.io/commands/randomkey
2307
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2308
+ */
2309
+ class KeyRandom extends Command
2310
+ {
2311
+ /**
2312
+ * {@inheritdoc}
2313
+ */
2314
+ public function getId()
2315
+ {
2316
+ return 'RANDOMKEY';
2317
+ }
2318
+
2319
+ /**
2320
+ * {@inheritdoc}
2321
+ */
2322
+ public function parseResponse($data)
2323
+ {
2324
+ return $data !== '' ? $data : null;
2325
+ }
2326
+ }
2327
+
2328
+ /**
2329
+ * @link http://redis.io/commands/renamenx
2330
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2331
+ */
2332
+ class KeyRenamePreserve extends KeyRename
2333
+ {
2334
+ /**
2335
+ * {@inheritdoc}
2336
+ */
2337
+ public function getId()
2338
+ {
2339
+ return 'RENAMENX';
2340
+ }
2341
+
2342
+ /**
2343
+ * {@inheritdoc}
2344
+ */
2345
+ public function parseResponse($data)
2346
+ {
2347
+ return (bool) $data;
2348
+ }
2349
+ }
2350
+
2351
+ /**
2352
+ * @link http://redis.io/commands/restore
2353
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2354
+ */
2355
+ class KeyRestore extends Command
2356
+ {
2357
+ /**
2358
+ * {@inheritdoc}
2359
+ */
2360
+ public function getId()
2361
+ {
2362
+ return 'RESTORE';
2363
+ }
2364
+ }
2365
+
2366
+ /**
2367
+ * @link http://redis.io/commands/pttl
2368
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2369
+ */
2370
+ class KeyPreciseTimeToLive extends KeyTimeToLive
2371
+ {
2372
+ /**
2373
+ * {@inheritdoc}
2374
+ */
2375
+ public function getId()
2376
+ {
2377
+ return 'PTTL';
2378
+ }
2379
+ }
2380
+
2381
+ /**
2382
+ * @link http://redis.io/commands/pexpireat
2383
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2384
+ */
2385
+ class KeyPreciseExpireAt extends KeyExpireAt
2386
+ {
2387
+ /**
2388
+ * {@inheritdoc}
2389
+ */
2390
+ public function getId()
2391
+ {
2392
+ return 'PEXPIREAT';
2393
+ }
2394
+ }
2395
+
2396
+ /**
2397
+ * @link http://redis.io/commands/persist
2398
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2399
+ */
2400
+ class KeyPersist extends Command
2401
+ {
2402
+ /**
2403
+ * {@inheritdoc}
2404
+ */
2405
+ public function getId()
2406
+ {
2407
+ return 'PERSIST';
2408
+ }
2409
+
2410
+ /**
2411
+ * {@inheritdoc}
2412
+ */
2413
+ public function parseResponse($data)
2414
+ {
2415
+ return (bool) $data;
2416
+ }
2417
+ }
2418
+
2419
+ /**
2420
+ * @link http://redis.io/commands/pexpire
2421
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2422
+ */
2423
+ class KeyPreciseExpire extends KeyExpire
2424
+ {
2425
+ /**
2426
+ * {@inheritdoc}
2427
+ */
2428
+ public function getId()
2429
+ {
2430
+ return 'PEXPIRE';
2431
+ }
2432
+ }
2433
+
2434
+ /**
2435
+ * @link http://redis.io/commands/hsetnx
2436
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2437
+ */
2438
+ class HashSetPreserve extends Command
2439
+ {
2440
+ /**
2441
+ * {@inheritdoc}
2442
+ */
2443
+ public function getId()
2444
+ {
2445
+ return 'HSETNX';
2446
+ }
2447
+
2448
+ /**
2449
+ * {@inheritdoc}
2450
+ */
2451
+ public function parseResponse($data)
2452
+ {
2453
+ return (bool) $data;
2454
+ }
2455
+ }
2456
+
2457
+ /**
2458
+ * @link http://redis.io/commands/hmset
2459
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2460
+ */
2461
+ class HashSetMultiple extends Command
2462
+ {
2463
+ /**
2464
+ * {@inheritdoc}
2465
+ */
2466
+ public function getId()
2467
+ {
2468
+ return 'HMSET';
2469
+ }
2470
+
2471
+ /**
2472
+ * {@inheritdoc}
2473
+ */
2474
+ protected function filterArguments(array $arguments)
2475
+ {
2476
+ if (count($arguments) === 2 && is_array($arguments[1])) {
2477
+ $flattenedKVs = array($arguments[0]);
2478
+ $args = $arguments[1];
2479
+
2480
+ foreach ($args as $k => $v) {
2481
+ $flattenedKVs[] = $k;
2482
+ $flattenedKVs[] = $v;
2483
+ }
2484
+
2485
+ return $flattenedKVs;
2486
+ }
2487
+
2488
+ return $arguments;
2489
+ }
2490
+ }
2491
+
2492
+ /**
2493
+ * @link http://redis.io/commands/select
2494
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2495
+ */
2496
+ class ConnectionSelect extends Command
2497
+ {
2498
+ /**
2499
+ * {@inheritdoc}
2500
+ */
2501
+ public function getId()
2502
+ {
2503
+ return 'SELECT';
2504
+ }
2505
+ }
2506
+
2507
+ /**
2508
+ * @link http://redis.io/commands/hdel
2509
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2510
+ */
2511
+ class HashDelete extends Command
2512
+ {
2513
+ /**
2514
+ * {@inheritdoc}
2515
+ */
2516
+ public function getId()
2517
+ {
2518
+ return 'HDEL';
2519
+ }
2520
+
2521
+ /**
2522
+ * {@inheritdoc}
2523
+ */
2524
+ protected function filterArguments(array $arguments)
2525
+ {
2526
+ return self::normalizeVariadic($arguments);
2527
+ }
2528
+ }
2529
+
2530
+ /**
2531
+ * @link http://redis.io/commands/hexists
2532
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2533
+ */
2534
+ class HashExists extends Command
2535
+ {
2536
+ /**
2537
+ * {@inheritdoc}
2538
+ */
2539
+ public function getId()
2540
+ {
2541
+ return 'HEXISTS';
2542
+ }
2543
+
2544
+ /**
2545
+ * {@inheritdoc}
2546
+ */
2547
+ public function parseResponse($data)
2548
+ {
2549
+ return (bool) $data;
2550
+ }
2551
+ }
2552
+
2553
+ /**
2554
+ * @link http://redis.io/commands/quit
2555
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2556
+ */
2557
+ class ConnectionQuit extends Command
2558
+ {
2559
+ /**
2560
+ * {@inheritdoc}
2561
+ */
2562
+ public function getId()
2563
+ {
2564
+ return 'QUIT';
2565
+ }
2566
+ }
2567
+
2568
+ /**
2569
+ * @link http://redis.io/commands/ping
2570
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2571
+ */
2572
+ class ConnectionPing extends Command
2573
+ {
2574
+ /**
2575
+ * {@inheritdoc}
2576
+ */
2577
+ public function getId()
2578
+ {
2579
+ return 'PING';
2580
+ }
2581
+ }
2582
+
2583
+ /**
2584
+ * @link http://redis.io/commands/auth
2585
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2586
+ */
2587
+ class ConnectionAuth extends Command
2588
+ {
2589
+ /**
2590
+ * {@inheritdoc}
2591
+ */
2592
+ public function getId()
2593
+ {
2594
+ return 'AUTH';
2595
+ }
2596
+ }
2597
+
2598
+ /**
2599
+ * @link http://redis.io/commands/echo
2600
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2601
+ */
2602
+ class ConnectionEcho extends Command
2603
+ {
2604
+ /**
2605
+ * {@inheritdoc}
2606
+ */
2607
+ public function getId()
2608
+ {
2609
+ return 'ECHO';
2610
+ }
2611
+ }
2612
+
2613
+ /**
2614
+ * @link http://redis.io/commands/hget
2615
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2616
+ */
2617
+ class HashGet extends Command
2618
+ {
2619
+ /**
2620
+ * {@inheritdoc}
2621
+ */
2622
+ public function getId()
2623
+ {
2624
+ return 'HGET';
2625
+ }
2626
+ }
2627
+
2628
+ /**
2629
+ * @link http://redis.io/commands/hgetall
2630
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2631
+ */
2632
+ class HashGetAll extends Command
2633
+ {
2634
+ /**
2635
+ * {@inheritdoc}
2636
+ */
2637
+ public function getId()
2638
+ {
2639
+ return 'HGETALL';
2640
+ }
2641
+
2642
+ /**
2643
+ * {@inheritdoc}
2644
+ */
2645
+ public function parseResponse($data)
2646
+ {
2647
+ $result = array();
2648
+
2649
+ for ($i = 0; $i < count($data); $i++) {
2650
+ $result[$data[$i]] = $data[++$i];
2651
+ }
2652
+
2653
+ return $result;
2654
+ }
2655
+ }
2656
+
2657
+ /**
2658
+ * @link http://redis.io/commands/hlen
2659
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2660
+ */
2661
+ class HashLength extends Command
2662
+ {
2663
+ /**
2664
+ * {@inheritdoc}
2665
+ */
2666
+ public function getId()
2667
+ {
2668
+ return 'HLEN';
2669
+ }
2670
+ }
2671
+
2672
+ /**
2673
+ * @link http://redis.io/commands/hscan
2674
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2675
+ */
2676
+ class HashScan extends Command
2677
+ {
2678
+ /**
2679
+ * {@inheritdoc}
2680
+ */
2681
+ public function getId()
2682
+ {
2683
+ return 'HSCAN';
2684
+ }
2685
+
2686
+ /**
2687
+ * {@inheritdoc}
2688
+ */
2689
+ protected function filterArguments(array $arguments)
2690
+ {
2691
+ if (count($arguments) === 3 && is_array($arguments[2])) {
2692
+ $options = $this->prepareOptions(array_pop($arguments));
2693
+ $arguments = array_merge($arguments, $options);
2694
+ }
2695
+
2696
+ return $arguments;
2697
+ }
2698
+
2699
+ /**
2700
+ * Returns a list of options and modifiers compatible with Redis.
2701
+ *
2702
+ * @param array $options List of options.
2703
+ *
2704
+ * @return array
2705
+ */
2706
+ protected function prepareOptions($options)
2707
+ {
2708
+ $options = array_change_key_case($options, CASE_UPPER);
2709
+ $normalized = array();
2710
+
2711
+ if (!empty($options['MATCH'])) {
2712
+ $normalized[] = 'MATCH';
2713
+ $normalized[] = $options['MATCH'];
2714
+ }
2715
+
2716
+ if (!empty($options['COUNT'])) {
2717
+ $normalized[] = 'COUNT';
2718
+ $normalized[] = $options['COUNT'];
2719
+ }
2720
+
2721
+ return $normalized;
2722
+ }
2723
+
2724
+ /**
2725
+ * {@inheritdoc}
2726
+ */
2727
+ public function parseResponse($data)
2728
+ {
2729
+ if (is_array($data)) {
2730
+ $fields = $data[1];
2731
+ $result = array();
2732
+
2733
+ for ($i = 0; $i < count($fields); $i++) {
2734
+ $result[$fields[$i]] = $fields[++$i];
2735
+ }
2736
+
2737
+ $data[1] = $result;
2738
+ }
2739
+
2740
+ return $data;
2741
+ }
2742
+ }
2743
+
2744
+ /**
2745
+ * @link http://redis.io/commands/hset
2746
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2747
+ */
2748
+ class HashSet extends Command
2749
+ {
2750
+ /**
2751
+ * {@inheritdoc}
2752
+ */
2753
+ public function getId()
2754
+ {
2755
+ return 'HSET';
2756
+ }
2757
+
2758
+ /**
2759
+ * {@inheritdoc}
2760
+ */
2761
+ public function parseResponse($data)
2762
+ {
2763
+ return (bool) $data;
2764
+ }
2765
+ }
2766
+
2767
+ /**
2768
+ * @link http://redis.io/commands/hkeys
2769
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2770
+ */
2771
+ class HashKeys extends Command
2772
+ {
2773
+ /**
2774
+ * {@inheritdoc}
2775
+ */
2776
+ public function getId()
2777
+ {
2778
+ return 'HKEYS';
2779
+ }
2780
+ }
2781
+
2782
+ /**
2783
+ * @link http://redis.io/commands/hincrbyfloat
2784
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2785
+ */
2786
+ class HashIncrementByFloat extends Command
2787
+ {
2788
+ /**
2789
+ * {@inheritdoc}
2790
+ */
2791
+ public function getId()
2792
+ {
2793
+ return 'HINCRBYFLOAT';
2794
+ }
2795
+ }
2796
+
2797
+ /**
2798
+ * @link http://redis.io/commands/hmget
2799
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2800
+ */
2801
+ class HashGetMultiple extends Command
2802
+ {
2803
+ /**
2804
+ * {@inheritdoc}
2805
+ */
2806
+ public function getId()
2807
+ {
2808
+ return 'HMGET';
2809
+ }
2810
+
2811
+ /**
2812
+ * {@inheritdoc}
2813
+ */
2814
+ protected function filterArguments(array $arguments)
2815
+ {
2816
+ return self::normalizeVariadic($arguments);
2817
+ }
2818
+ }
2819
+
2820
+ /**
2821
+ * @link http://redis.io/commands/hincrby
2822
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2823
+ */
2824
+ class HashIncrementBy extends Command
2825
+ {
2826
+ /**
2827
+ * {@inheritdoc}
2828
+ */
2829
+ public function getId()
2830
+ {
2831
+ return 'HINCRBY';
2832
+ }
2833
+ }
2834
+
2835
+ /**
2836
+ * @link http://redis.io/commands/scan
2837
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2838
+ */
2839
+ class KeyScan extends Command
2840
+ {
2841
+ /**
2842
+ * {@inheritdoc}
2843
+ */
2844
+ public function getId()
2845
+ {
2846
+ return 'SCAN';
2847
+ }
2848
+
2849
+ /**
2850
+ * {@inheritdoc}
2851
+ */
2852
+ protected function filterArguments(array $arguments)
2853
+ {
2854
+ if (count($arguments) === 2 && is_array($arguments[1])) {
2855
+ $options = $this->prepareOptions(array_pop($arguments));
2856
+ $arguments = array_merge($arguments, $options);
2857
+ }
2858
+
2859
+ return $arguments;
2860
+ }
2861
+
2862
+ /**
2863
+ * Returns a list of options and modifiers compatible with Redis.
2864
+ *
2865
+ * @param array $options List of options.
2866
+ *
2867
+ * @return array
2868
+ */
2869
+ protected function prepareOptions($options)
2870
+ {
2871
+ $options = array_change_key_case($options, CASE_UPPER);
2872
+ $normalized = array();
2873
+
2874
+ if (!empty($options['MATCH'])) {
2875
+ $normalized[] = 'MATCH';
2876
+ $normalized[] = $options['MATCH'];
2877
+ }
2878
+
2879
+ if (!empty($options['COUNT'])) {
2880
+ $normalized[] = 'COUNT';
2881
+ $normalized[] = $options['COUNT'];
2882
+ }
2883
+
2884
+ return $normalized;
2885
+ }
2886
+ }
2887
+
2888
+ /**
2889
+ * @link http://redis.io/commands/sort
2890
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2891
+ */
2892
+ class KeySort extends Command
2893
+ {
2894
+ /**
2895
+ * {@inheritdoc}
2896
+ */
2897
+ public function getId()
2898
+ {
2899
+ return 'SORT';
2900
+ }
2901
+
2902
+ /**
2903
+ * {@inheritdoc}
2904
+ */
2905
+ protected function filterArguments(array $arguments)
2906
+ {
2907
+ if (count($arguments) === 1) {
2908
+ return $arguments;
2909
+ }
2910
+
2911
+ $query = array($arguments[0]);
2912
+ $sortParams = array_change_key_case($arguments[1], CASE_UPPER);
2913
+
2914
+ if (isset($sortParams['BY'])) {
2915
+ $query[] = 'BY';
2916
+ $query[] = $sortParams['BY'];
2917
+ }
2918
+
2919
+ if (isset($sortParams['GET'])) {
2920
+ $getargs = $sortParams['GET'];
2921
+
2922
+ if (is_array($getargs)) {
2923
+ foreach ($getargs as $getarg) {
2924
+ $query[] = 'GET';
2925
+ $query[] = $getarg;
2926
+ }
2927
+ } else {
2928
+ $query[] = 'GET';
2929
+ $query[] = $getargs;
2930
+ }
2931
+ }
2932
+
2933
+ if (isset($sortParams['LIMIT']) &&
2934
+ is_array($sortParams['LIMIT']) &&
2935
+ count($sortParams['LIMIT']) == 2) {
2936
+
2937
+ $query[] = 'LIMIT';
2938
+ $query[] = $sortParams['LIMIT'][0];
2939
+ $query[] = $sortParams['LIMIT'][1];
2940
+ }
2941
+
2942
+ if (isset($sortParams['SORT'])) {
2943
+ $query[] = strtoupper($sortParams['SORT']);
2944
+ }
2945
+
2946
+ if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) {
2947
+ $query[] = 'ALPHA';
2948
+ }
2949
+
2950
+ if (isset($sortParams['STORE'])) {
2951
+ $query[] = 'STORE';
2952
+ $query[] = $sortParams['STORE'];
2953
+ }
2954
+
2955
+ return $query;
2956
+ }
2957
+ }
2958
+
2959
+ /**
2960
+ * Class for generic "anonymous" Redis commands.
2961
+ *
2962
+ * This command class does not filter input arguments or parse responses, but
2963
+ * can be used to leverage the standard Predis API to execute any command simply
2964
+ * by providing the needed arguments following the command signature as defined
2965
+ * by Redis in its documentation.
2966
+ *
2967
+ * @author Daniele Alessandri <suppakilla@gmail.com>
2968
+ */
2969
+ class RawCommand implements CommandInterface
2970
+ {
2971
+ private $slot;
2972
+ private $commandID;
2973
+ private $arguments;
2974
+
2975
+ /**
2976
+ * @param array $arguments Command ID and its arguments.
2977
+ *
2978
+ * @throws \InvalidArgumentException
2979
+ */
2980
+ public function __construct(array $arguments)
2981
+ {
2982
+ if (!$arguments) {
2983
+ throw new InvalidArgumentException(
2984
+ 'The arguments array must contain at least the command ID.'
2985
+ );
2986
+ }
2987
+
2988
+ $this->commandID = strtoupper(array_shift($arguments));
2989
+ $this->arguments = $arguments;
2990
+ }
2991
+
2992
+ /**
2993
+ * Creates a new raw command using a variadic method.
2994
+ *
2995
+ * @param string $commandID Redis command ID.
2996
+ * @param string ... Arguments list for the command.
2997
+ *
2998
+ * @return CommandInterface
2999
+ */
3000
+ public static function create($commandID /* [ $arg, ... */)
3001
+ {
3002
+ $arguments = func_get_args();
3003
+ $command = new self($arguments);
3004
+
3005
+ return $command;
3006
+ }
3007
+
3008
+ /**
3009
+ * {@inheritdoc}
3010
+ */
3011
+ public function getId()
3012
+ {
3013
+ return $this->commandID;
3014
+ }
3015
+
3016
+ /**
3017
+ * {@inheritdoc}
3018
+ */
3019
+ public function setArguments(array $arguments)
3020
+ {
3021
+ $this->arguments = $arguments;
3022
+ unset($this->slot);
3023
+ }
3024
+
3025
+ /**
3026
+ * {@inheritdoc}
3027
+ */
3028
+ public function setRawArguments(array $arguments)
3029
+ {
3030
+ $this->setArguments($arguments);
3031
+ }
3032
+
3033
+ /**
3034
+ * {@inheritdoc}
3035
+ */
3036
+ public function getArguments()
3037
+ {
3038
+ return $this->arguments;
3039
+ }
3040
+
3041
+ /**
3042
+ * {@inheritdoc}
3043
+ */
3044
+ public function getArgument($index)
3045
+ {
3046
+ if (isset($this->arguments[$index])) {
3047
+ return $this->arguments[$index];
3048
+ }
3049
+ }
3050
+
3051
+ /**
3052
+ * {@inheritdoc}
3053
+ */
3054
+ public function setSlot($slot)
3055
+ {
3056
+ $this->slot = $slot;
3057
+ }
3058
+
3059
+ /**
3060
+ * {@inheritdoc}
3061
+ */
3062
+ public function getSlot()
3063
+ {
3064
+ if (isset($this->slot)) {
3065
+ return $this->slot;
3066
+ }
3067
+ }
3068
+
3069
+ /**
3070
+ * {@inheritdoc}
3071
+ */
3072
+ public function parseResponse($data)
3073
+ {
3074
+ return $data;
3075
+ }
3076
+ }
3077
+
3078
+ /**
3079
+ * Base class used to implement an higher level abstraction for commands based
3080
+ * on Lua scripting with EVAL and EVALSHA.
3081
+ *
3082
+ * @link http://redis.io/commands/eval
3083
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3084
+ */
3085
+ abstract class ScriptCommand extends ServerEvalSHA
3086
+ {
3087
+ /**
3088
+ * Gets the body of a Lua script.
3089
+ *
3090
+ * @return string
3091
+ */
3092
+ abstract public function getScript();
3093
+
3094
+ /**
3095
+ * Specifies the number of arguments that should be considered as keys.
3096
+ *
3097
+ * The default behaviour for the base class is to return 0 to indicate that
3098
+ * all the elements of the arguments array should be considered as keys, but
3099
+ * subclasses can enforce a static number of keys.
3100
+ *
3101
+ * @return int
3102
+ */
3103
+ protected function getKeysCount()
3104
+ {
3105
+ return 0;
3106
+ }
3107
+
3108
+ /**
3109
+ * Returns the elements from the arguments that are identified as keys.
3110
+ *
3111
+ * @return array
3112
+ */
3113
+ public function getKeys()
3114
+ {
3115
+ return array_slice($this->getArguments(), 2, $this->getKeysCount());
3116
+ }
3117
+
3118
+ /**
3119
+ * {@inheritdoc}
3120
+ */
3121
+ protected function filterArguments(array $arguments)
3122
+ {
3123
+ if (($numkeys = $this->getKeysCount()) && $numkeys < 0) {
3124
+ $numkeys = count($arguments) + $numkeys;
3125
+ }
3126
+
3127
+ return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments);
3128
+ }
3129
+
3130
+ /**
3131
+ * @return array
3132
+ */
3133
+ public function getEvalArguments()
3134
+ {
3135
+ $arguments = $this->getArguments();
3136
+ $arguments[0] = $this->getScript();
3137
+
3138
+ return $arguments;
3139
+ }
3140
+ }
3141
+
3142
+ /**
3143
+ * @link http://redis.io/commands/bgrewriteaof
3144
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3145
+ */
3146
+ class ServerBackgroundRewriteAOF extends Command
3147
+ {
3148
+ /**
3149
+ * {@inheritdoc}
3150
+ */
3151
+ public function getId()
3152
+ {
3153
+ return 'BGREWRITEAOF';
3154
+ }
3155
+
3156
+ /**
3157
+ * {@inheritdoc}
3158
+ */
3159
+ public function parseResponse($data)
3160
+ {
3161
+ return $data == 'Background append only file rewriting started';
3162
+ }
3163
+ }
3164
+
3165
+ /**
3166
+ * @link http://redis.io/commands/punsubscribe
3167
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3168
+ */
3169
+ class PubSubUnsubscribeByPattern extends PubSubUnsubscribe
3170
+ {
3171
+ /**
3172
+ * {@inheritdoc}
3173
+ */
3174
+ public function getId()
3175
+ {
3176
+ return 'PUNSUBSCRIBE';
3177
+ }
3178
+ }
3179
+
3180
+ /**
3181
+ * @link http://redis.io/commands/psubscribe
3182
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3183
+ */
3184
+ class PubSubSubscribeByPattern extends PubSubSubscribe
3185
+ {
3186
+ /**
3187
+ * {@inheritdoc}
3188
+ */
3189
+ public function getId()
3190
+ {
3191
+ return 'PSUBSCRIBE';
3192
+ }
3193
+ }
3194
+
3195
+ /**
3196
+ * @link http://redis.io/commands/publish
3197
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3198
+ */
3199
+ class PubSubPublish extends Command
3200
+ {
3201
+ /**
3202
+ * {@inheritdoc}
3203
+ */
3204
+ public function getId()
3205
+ {
3206
+ return 'PUBLISH';
3207
+ }
3208
+ }
3209
+
3210
+ /**
3211
+ * @link http://redis.io/commands/pubsub
3212
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3213
+ */
3214
+ class PubSubPubsub extends Command
3215
+ {
3216
+ /**
3217
+ * {@inheritdoc}
3218
+ */
3219
+ public function getId()
3220
+ {
3221
+ return 'PUBSUB';
3222
+ }
3223
+
3224
+ /**
3225
+ * {@inheritdoc}
3226
+ */
3227
+ public function parseResponse($data)
3228
+ {
3229
+ switch (strtolower($this->getArgument(0))) {
3230
+ case 'numsub':
3231
+ return self::processNumsub($data);
3232
+
3233
+ default:
3234
+ return $data;
3235
+ }
3236
+ }
3237
+
3238
+ /**
3239
+ * Returns the processed response to PUBSUB NUMSUB.
3240
+ *
3241
+ * @param array $channels List of channels
3242
+ *
3243
+ * @return array
3244
+ */
3245
+ protected static function processNumsub(array $channels)
3246
+ {
3247
+ $processed = array();
3248
+ $count = count($channels);
3249
+
3250
+ for ($i = 0; $i < $count; $i++) {
3251
+ $processed[$channels[$i]] = $channels[++$i];
3252
+ }
3253
+
3254
+ return $processed;
3255
+ }
3256
+ }
3257
+
3258
+ /**
3259
+ * @link http://redis.io/commands/bgsave
3260
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3261
+ */
3262
+ class ServerBackgroundSave extends Command
3263
+ {
3264
+ /**
3265
+ * {@inheritdoc}
3266
+ */
3267
+ public function getId()
3268
+ {
3269
+ return 'BGSAVE';
3270
+ }
3271
+
3272
+ /**
3273
+ * {@inheritdoc}
3274
+ */
3275
+ public function parseResponse($data)
3276
+ {
3277
+ return $data === 'Background saving started' ? true : $data;
3278
+ }
3279
+ }
3280
+
3281
+ /**
3282
+ * @link http://redis.io/commands/client-list
3283
+ * @link http://redis.io/commands/client-kill
3284
+ * @link http://redis.io/commands/client-getname
3285
+ * @link http://redis.io/commands/client-setname
3286
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3287
+ */
3288
+ class ServerClient extends Command
3289
+ {
3290
+ /**
3291
+ * {@inheritdoc}
3292
+ */
3293
+ public function getId()
3294
+ {
3295
+ return 'CLIENT';
3296
+ }
3297
+
3298
+ /**
3299
+ * {@inheritdoc}
3300
+ */
3301
+ public function parseResponse($data)
3302
+ {
3303
+ $args = array_change_key_case($this->getArguments(), CASE_UPPER);
3304
+
3305
+ switch (strtoupper($args[0])) {
3306
+ case 'LIST':
3307
+ return $this->parseClientList($data);
3308
+ case 'KILL':
3309
+ case 'GETNAME':
3310
+ case 'SETNAME':
3311
+ default:
3312
+ return $data;
3313
+ }
3314
+ }
3315
+
3316
+ /**
3317
+ * Parses the response to CLIENT LIST and returns a structured list.
3318
+ *
3319
+ * @param string $data Response buffer.
3320
+ *
3321
+ * @return array
3322
+ */
3323
+ protected function parseClientList($data)
3324
+ {
3325
+ $clients = array();
3326
+
3327
+ foreach (explode("\n", $data, -1) as $clientData) {
3328
+ $client = array();
3329
+
3330
+ foreach (explode(' ', $clientData) as $kv) {
3331
+ @list($k, $v) = explode('=', $kv);
3332
+ $client[$k] = $v;
3333
+ }
3334
+
3335
+ $clients[] = $client;
3336
+ }
3337
+
3338
+ return $clients;
3339
+ }
3340
+ }
3341
+
3342
+ /**
3343
+ * @link http://redis.io/commands/info
3344
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3345
+ */
3346
+ class ServerInfoV26x extends ServerInfo
3347
+ {
3348
+ /**
3349
+ * {@inheritdoc}
3350
+ */
3351
+ public function parseResponse($data)
3352
+ {
3353
+ if ($data === '') {
3354
+ return array();
3355
+ }
3356
+
3357
+ $info = array();
3358
+
3359
+ $current = null;
3360
+ $infoLines = preg_split('/\r?\n/', $data);
3361
+
3362
+ if (isset($infoLines[0]) && $infoLines[0][0] !== '#') {
3363
+ return parent::parseResponse($data);
3364
+ }
3365
+
3366
+ foreach ($infoLines as $row) {
3367
+ if ($row === '') {
3368
+ continue;
3369
+ }
3370
+
3371
+ if (preg_match('/^# (\w+)$/', $row, $matches)) {
3372
+ $info[$matches[1]] = array();
3373
+ $current = &$info[$matches[1]];
3374
+ continue;
3375
+ }
3376
+
3377
+ list($k, $v) = $this->parseRow($row);
3378
+ $current[$k] = $v;
3379
+ }
3380
+
3381
+ return $info;
3382
+ }
3383
+ }
3384
+
3385
+ /**
3386
+ * @link http://redis.io/commands/lastsave
3387
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3388
+ */
3389
+ class ServerLastSave extends Command
3390
+ {
3391
+ /**
3392
+ * {@inheritdoc}
3393
+ */
3394
+ public function getId()
3395
+ {
3396
+ return 'LASTSAVE';
3397
+ }
3398
+ }
3399
+
3400
+ /**
3401
+ * @link http://redis.io/commands/monitor
3402
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3403
+ */
3404
+ class ServerMonitor extends Command
3405
+ {
3406
+ /**
3407
+ * {@inheritdoc}
3408
+ */
3409
+ public function getId()
3410
+ {
3411
+ return 'MONITOR';
3412
+ }
3413
+ }
3414
+
3415
+ /**
3416
+ * @link http://redis.io/commands/flushdb
3417
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3418
+ */
3419
+ class ServerFlushDatabase extends Command
3420
+ {
3421
+ /**
3422
+ * {@inheritdoc}
3423
+ */
3424
+ public function getId()
3425
+ {
3426
+ return 'FLUSHDB';
3427
+ }
3428
+ }
3429
+
3430
+ /**
3431
+ * @link http://redis.io/commands/dbsize
3432
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3433
+ */
3434
+ class ServerDatabaseSize extends Command
3435
+ {
3436
+ /**
3437
+ * {@inheritdoc}
3438
+ */
3439
+ public function getId()
3440
+ {
3441
+ return 'DBSIZE';
3442
+ }
3443
+ }
3444
+
3445
+ /**
3446
+ * @link http://redis.io/commands/command
3447
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3448
+ */
3449
+ class ServerCommand extends Command
3450
+ {
3451
+ /**
3452
+ * {@inheritdoc}
3453
+ */
3454
+ public function getId()
3455
+ {
3456
+ return 'COMMAND';
3457
+ }
3458
+ }
3459
+
3460
+ /**
3461
+ * @link http://redis.io/commands/config-set
3462
+ * @link http://redis.io/commands/config-get
3463
+ * @link http://redis.io/commands/config-resetstat
3464
+ * @link http://redis.io/commands/config-rewrite
3465
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3466
+ */
3467
+ class ServerConfig extends Command
3468
+ {
3469
+ /**
3470
+ * {@inheritdoc}
3471
+ */
3472
+ public function getId()
3473
+ {
3474
+ return 'CONFIG';
3475
+ }
3476
+
3477
+ /**
3478
+ * {@inheritdoc}
3479
+ */
3480
+ public function parseResponse($data)
3481
+ {
3482
+ if (is_array($data)) {
3483
+ $result = array();
3484
+
3485
+ for ($i = 0; $i < count($data); $i++) {
3486
+ $result[$data[$i]] = $data[++$i];
3487
+ }
3488
+
3489
+ return $result;
3490
+ }
3491
+
3492
+ return $data;
3493
+ }
3494
+ }
3495
+
3496
+ /**
3497
+ * Defines a command whose keys can be prefixed.
3498
+ *
3499
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3500
+ */
3501
+ interface PrefixableCommandInterface extends CommandInterface
3502
+ {
3503
+ /**
3504
+ * Prefixes all the keys found in the arguments of the command.
3505
+ *
3506
+ * @param string $prefix String used to prefix the keys.
3507
+ */
3508
+ public function prefixKeys($prefix);
3509
+ }
3510
+
3511
+ /**
3512
+ * @link http://redis.io/commands/ltrim
3513
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3514
+ */
3515
+ class ListTrim extends Command
3516
+ {
3517
+ /**
3518
+ * {@inheritdoc}
3519
+ */
3520
+ public function getId()
3521
+ {
3522
+ return 'LTRIM';
3523
+ }
3524
+ }
3525
+
3526
+ /**
3527
+ * @link http://redis.io/commands/lpop
3528
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3529
+ */
3530
+ class ListPopFirst extends Command
3531
+ {
3532
+ /**
3533
+ * {@inheritdoc}
3534
+ */
3535
+ public function getId()
3536
+ {
3537
+ return 'LPOP';
3538
+ }
3539
+ }
3540
+
3541
+ /**
3542
+ * @link http://redis.io/commands/rpop
3543
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3544
+ */
3545
+ class ListPopLast extends Command
3546
+ {
3547
+ /**
3548
+ * {@inheritdoc}
3549
+ */
3550
+ public function getId()
3551
+ {
3552
+ return 'RPOP';
3553
+ }
3554
+ }
3555
+
3556
+ /**
3557
+ * @link http://redis.io/commands/brpop
3558
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3559
+ */
3560
+ class ListPopLastBlocking extends ListPopFirstBlocking
3561
+ {
3562
+ /**
3563
+ * {@inheritdoc}
3564
+ */
3565
+ public function getId()
3566
+ {
3567
+ return 'BRPOP';
3568
+ }
3569
+ }
3570
+
3571
+ /**
3572
+ * @link http://redis.io/commands/llen
3573
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3574
+ */
3575
+ class ListLength extends Command
3576
+ {
3577
+ /**
3578
+ * {@inheritdoc}
3579
+ */
3580
+ public function getId()
3581
+ {
3582
+ return 'LLEN';
3583
+ }
3584
+ }
3585
+
3586
+ /**
3587
+ * @link http://redis.io/commands/linsert
3588
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3589
+ */
3590
+ class ListInsert extends Command
3591
+ {
3592
+ /**
3593
+ * {@inheritdoc}
3594
+ */
3595
+ public function getId()
3596
+ {
3597
+ return 'LINSERT';
3598
+ }
3599
+ }
3600
+
3601
+ /**
3602
+ * @link http://redis.io/commands/type
3603
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3604
+ */
3605
+ class KeyType extends Command
3606
+ {
3607
+ /**
3608
+ * {@inheritdoc}
3609
+ */
3610
+ public function getId()
3611
+ {
3612
+ return 'TYPE';
3613
+ }
3614
+ }
3615
+
3616
+ /**
3617
+ * @link http://redis.io/commands/lindex
3618
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3619
+ */
3620
+ class ListIndex extends Command
3621
+ {
3622
+ /**
3623
+ * {@inheritdoc}
3624
+ */
3625
+ public function getId()
3626
+ {
3627
+ return 'LINDEX';
3628
+ }
3629
+ }
3630
+
3631
+ /**
3632
+ * @link http://redis.io/commands/rpoplpush
3633
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3634
+ */
3635
+ class ListPopLastPushHead extends Command
3636
+ {
3637
+ /**
3638
+ * {@inheritdoc}
3639
+ */
3640
+ public function getId()
3641
+ {
3642
+ return 'RPOPLPUSH';
3643
+ }
3644
+ }
3645
+
3646
+ /**
3647
+ * @link http://redis.io/commands/brpoplpush
3648
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3649
+ */
3650
+ class ListPopLastPushHeadBlocking extends Command
3651
+ {
3652
+ /**
3653
+ * {@inheritdoc}
3654
+ */
3655
+ public function getId()
3656
+ {
3657
+ return 'BRPOPLPUSH';
3658
+ }
3659
+ }
3660
+
3661
+ /**
3662
+ * @link http://redis.io/commands/lrem
3663
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3664
+ */
3665
+ class ListRemove extends Command
3666
+ {
3667
+ /**
3668
+ * {@inheritdoc}
3669
+ */
3670
+ public function getId()
3671
+ {
3672
+ return 'LREM';
3673
+ }
3674
+ }
3675
+
3676
+ /**
3677
+ * @link http://redis.io/commands/lset
3678
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3679
+ */
3680
+ class ListSet extends Command
3681
+ {
3682
+ /**
3683
+ * {@inheritdoc}
3684
+ */
3685
+ public function getId()
3686
+ {
3687
+ return 'LSET';
3688
+ }
3689
+ }
3690
+
3691
+ /**
3692
+ * @link http://redis.io/commands/lrange
3693
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3694
+ */
3695
+ class ListRange extends Command
3696
+ {
3697
+ /**
3698
+ * {@inheritdoc}
3699
+ */
3700
+ public function getId()
3701
+ {
3702
+ return 'LRANGE';
3703
+ }
3704
+ }
3705
+
3706
+ /**
3707
+ * @link http://redis.io/commands/rpushx
3708
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3709
+ */
3710
+ class ListPushTailX extends Command
3711
+ {
3712
+ /**
3713
+ * {@inheritdoc}
3714
+ */
3715
+ public function getId()
3716
+ {
3717
+ return 'RPUSHX';
3718
+ }
3719
+ }
3720
+
3721
+ /**
3722
+ * @link http://redis.io/commands/lpush
3723
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3724
+ */
3725
+ class ListPushHead extends ListPushTail
3726
+ {
3727
+ /**
3728
+ * {@inheritdoc}
3729
+ */
3730
+ public function getId()
3731
+ {
3732
+ return 'LPUSH';
3733
+ }
3734
+ }
3735
+
3736
+ /**
3737
+ * @link http://redis.io/commands/lpushx
3738
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3739
+ */
3740
+ class ListPushHeadX extends Command
3741
+ {
3742
+ /**
3743
+ * {@inheritdoc}
3744
+ */
3745
+ public function getId()
3746
+ {
3747
+ return 'LPUSHX';
3748
+ }
3749
+ }
3750
+
3751
+ /**
3752
+ * @link http://redis.io/commands/object
3753
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3754
+ */
3755
+ class ServerObject extends Command
3756
+ {
3757
+ /**
3758
+ * {@inheritdoc}
3759
+ */
3760
+ public function getId()
3761
+ {
3762
+ return 'OBJECT';
3763
+ }
3764
+ }
3765
+
3766
+ /* --------------------------------------------------------------------------- */
3767
+
3768
+ namespace Predis\Connection;
3769
+
3770
+ use InvalidArgumentException;
3771
+ use Predis\CommunicationException;
3772
+ use Predis\Command\CommandInterface;
3773
+ use Predis\Protocol\ProtocolException;
3774
+ use Predis\Protocol\ProtocolProcessorInterface;
3775
+ use Predis\Protocol\Text\ProtocolProcessor as TextProtocolProcessor;
3776
+ use UnexpectedValueException;
3777
+ use ReflectionClass;
3778
+ use Predis\Command\RawCommand;
3779
+ use Predis\NotSupportedException;
3780
+ use Predis\Response\Error as ErrorResponse;
3781
+ use Predis\Response\Status as StatusResponse;
3782
+
3783
+ /**
3784
+ * Defines a connection object used to communicate with one or multiple
3785
+ * Redis servers.
3786
+ *
3787
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3788
+ */
3789
+ interface ConnectionInterface
3790
+ {
3791
+ /**
3792
+ * Opens the connection to Redis.
3793
+ */
3794
+ public function connect();
3795
+
3796
+ /**
3797
+ * Closes the connection to Redis.
3798
+ */
3799
+ public function disconnect();
3800
+
3801
+ /**
3802
+ * Checks if the connection to Redis is considered open.
3803
+ *
3804
+ * @return bool
3805
+ */
3806
+ public function isConnected();
3807
+
3808
+ /**
3809
+ * Writes the request for the given command over the connection.
3810
+ *
3811
+ * @param CommandInterface $command Command instance.
3812
+ */
3813
+ public function writeRequest(CommandInterface $command);
3814
+
3815
+ /**
3816
+ * Reads the response to the given command from the connection.
3817
+ *
3818
+ * @param CommandInterface $command Command instance.
3819
+ *
3820
+ * @return mixed
3821
+ */
3822
+ public function readResponse(CommandInterface $command);
3823
+
3824
+ /**
3825
+ * Writes a request for the given command over the connection and reads back
3826
+ * the response returned by Redis.
3827
+ *
3828
+ * @param CommandInterface $command Command instance.
3829
+ *
3830
+ * @return mixed
3831
+ */
3832
+ public function executeCommand(CommandInterface $command);
3833
+ }
3834
+
3835
+ /**
3836
+ * Defines a connection used to communicate with a single Redis node.
3837
+ *
3838
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3839
+ */
3840
+ interface NodeConnectionInterface extends ConnectionInterface
3841
+ {
3842
+ /**
3843
+ * Returns a string representation of the connection.
3844
+ *
3845
+ * @return string
3846
+ */
3847
+ public function __toString();
3848
+
3849
+ /**
3850
+ * Returns the underlying resource used to communicate with Redis.
3851
+ *
3852
+ * @return mixed
3853
+ */
3854
+ public function getResource();
3855
+
3856
+ /**
3857
+ * Returns the parameters used to initialize the connection.
3858
+ *
3859
+ * @return ParametersInterface
3860
+ */
3861
+ public function getParameters();
3862
+
3863
+ /**
3864
+ * Pushes the given command into a queue of commands executed when
3865
+ * establishing the actual connection to Redis.
3866
+ *
3867
+ * @param CommandInterface $command Instance of a Redis command.
3868
+ */
3869
+ public function addConnectCommand(CommandInterface $command);
3870
+
3871
+ /**
3872
+ * Reads a response from the server.
3873
+ *
3874
+ * @return mixed
3875
+ */
3876
+ public function read();
3877
+ }
3878
+
3879
+ /**
3880
+ * Defines a virtual connection composed of multiple connection instances to
3881
+ * single Redis nodes.
3882
+ *
3883
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3884
+ */
3885
+ interface AggregateConnectionInterface extends ConnectionInterface
3886
+ {
3887
+ /**
3888
+ * Adds a connection instance to the aggregate connection.
3889
+ *
3890
+ * @param NodeConnectionInterface $connection Connection instance.
3891
+ */
3892
+ public function add(NodeConnectionInterface $connection);
3893
+
3894
+ /**
3895
+ * Removes the specified connection instance from the aggregate connection.
3896
+ *
3897
+ * @param NodeConnectionInterface $connection Connection instance.
3898
+ *
3899
+ * @return bool Returns true if the connection was in the pool.
3900
+ */
3901
+ public function remove(NodeConnectionInterface $connection);
3902
+
3903
+ /**
3904
+ * Returns the connection instance in charge for the given command.
3905
+ *
3906
+ * @param CommandInterface $command Command instance.
3907
+ *
3908
+ * @return NodeConnectionInterface
3909
+ */
3910
+ public function getConnection(CommandInterface $command);
3911
+
3912
+ /**
3913
+ * Returns a connection instance from the aggregate connection by its alias.
3914
+ *
3915
+ * @param string $connectionID Connection alias.
3916
+ *
3917
+ * @return NodeConnectionInterface|null
3918
+ */
3919
+ public function getConnectionById($connectionID);
3920
+ }
3921
+
3922
+ /**
3923
+ * Base class with the common logic used by connection classes to communicate
3924
+ * with Redis.
3925
+ *
3926
+ * @author Daniele Alessandri <suppakilla@gmail.com>
3927
+ */
3928
+ abstract class AbstractConnection implements NodeConnectionInterface
3929
+ {
3930
+ private $resource;
3931
+ private $cachedId;
3932
+
3933
+ protected $parameters;
3934
+ protected $initCommands = array();
3935
+
3936
+ /**
3937
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
3938
+ */
3939
+ public function __construct(ParametersInterface $parameters)
3940
+ {
3941
+ $this->parameters = $this->assertParameters($parameters);
3942
+ }
3943
+
3944
+ /**
3945
+ * Disconnects from the server and destroys the underlying resource when
3946
+ * PHP's garbage collector kicks in.
3947
+ */
3948
+ public function __destruct()
3949
+ {
3950
+ $this->disconnect();
3951
+ }
3952
+
3953
+ /**
3954
+ * Checks some of the parameters used to initialize the connection.
3955
+ *
3956
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
3957
+ *
3958
+ * @return ParametersInterface
3959
+ *
3960
+ * @throws \InvalidArgumentException
3961
+ */
3962
+ protected function assertParameters(ParametersInterface $parameters)
3963
+ {
3964
+ $scheme = $parameters->scheme;
3965
+
3966
+ if ($scheme !== 'tcp' && $scheme !== 'unix') {
3967
+ throw new InvalidArgumentException("Invalid scheme: '$scheme'.");
3968
+ }
3969
+
3970
+ if ($scheme === 'unix' && !isset($parameters->path)) {
3971
+ throw new InvalidArgumentException('Missing UNIX domain socket path.');
3972
+ }
3973
+
3974
+ return $parameters;
3975
+ }
3976
+
3977
+ /**
3978
+ * Creates the underlying resource used to communicate with Redis.
3979
+ *
3980
+ * @return mixed
3981
+ */
3982
+ abstract protected function createResource();
3983
+
3984
+ /**
3985
+ * {@inheritdoc}
3986
+ */
3987
+ public function isConnected()
3988
+ {
3989
+ return isset($this->resource);
3990
+ }
3991
+
3992
+ /**
3993
+ * {@inheritdoc}
3994
+ */
3995
+ public function connect()
3996
+ {
3997
+ if (!$this->isConnected()) {
3998
+ $this->resource = $this->createResource();
3999
+
4000
+ return true;
4001
+ }
4002
+
4003
+ return false;
4004
+ }
4005
+
4006
+ /**
4007
+ * {@inheritdoc}
4008
+ */
4009
+ public function disconnect()
4010
+ {
4011
+ unset($this->resource);
4012
+ }
4013
+
4014
+ /**
4015
+ * {@inheritdoc}
4016
+ */
4017
+ public function addConnectCommand(CommandInterface $command)
4018
+ {
4019
+ $this->initCommands[] = $command;
4020
+ }
4021
+
4022
+ /**
4023
+ * {@inheritdoc}
4024
+ */
4025
+ public function executeCommand(CommandInterface $command)
4026
+ {
4027
+ $this->writeRequest($command);
4028
+
4029
+ return $this->readResponse($command);
4030
+ }
4031
+
4032
+ /**
4033
+ * {@inheritdoc}
4034
+ */
4035
+ public function readResponse(CommandInterface $command)
4036
+ {
4037
+ return $this->read();
4038
+ }
4039
+
4040
+ /**
4041
+ * Helper method to handle connection errors.
4042
+ *
4043
+ * @param string $message Error message.
4044
+ * @param int $code Error code.
4045
+ */
4046
+ protected function onConnectionError($message, $code = null)
4047
+ {
4048
+ CommunicationException::handle(
4049
+ new ConnectionException(
4050
+ $this, "$message [{$this->parameters->scheme}://{$this->getIdentifier()}]", $code
4051
+ )
4052
+ );
4053
+ }
4054
+
4055
+ /**
4056
+ * Helper method to handle protocol errors.
4057
+ *
4058
+ * @param string $message Error message.
4059
+ */
4060
+ protected function onProtocolError($message)
4061
+ {
4062
+ CommunicationException::handle(
4063
+ new ProtocolException(
4064
+ $this, "$message [{$this->parameters->scheme}://{$this->getIdentifier()}]"
4065
+ )
4066
+ );
4067
+ }
4068
+
4069
+ /**
4070
+ * {@inheritdoc}
4071
+ */
4072
+ public function getResource()
4073
+ {
4074
+ if (isset($this->resource)) {
4075
+ return $this->resource;
4076
+ }
4077
+
4078
+ $this->connect();
4079
+
4080
+ return $this->resource;
4081
+ }
4082
+
4083
+ /**
4084
+ * {@inheritdoc}
4085
+ */
4086
+ public function getParameters()
4087
+ {
4088
+ return $this->parameters;
4089
+ }
4090
+
4091
+ /**
4092
+ * Gets an identifier for the connection.
4093
+ *
4094
+ * @return string
4095
+ */
4096
+ protected function getIdentifier()
4097
+ {
4098
+ if ($this->parameters->scheme === 'unix') {
4099
+ return $this->parameters->path;
4100
+ }
4101
+
4102
+ return "{$this->parameters->host}:{$this->parameters->port}";
4103
+ }
4104
+
4105
+ /**
4106
+ * {@inheritdoc}
4107
+ */
4108
+ public function __toString()
4109
+ {
4110
+ if (!isset($this->cachedId)) {
4111
+ $this->cachedId = $this->getIdentifier();
4112
+ }
4113
+
4114
+ return $this->cachedId;
4115
+ }
4116
+
4117
+ /**
4118
+ * {@inheritdoc}
4119
+ */
4120
+ public function __sleep()
4121
+ {
4122
+ return array('parameters', 'initCommands');
4123
+ }
4124
+ }
4125
+
4126
+ /**
4127
+ * Standard connection to Redis servers implemented on top of PHP's streams.
4128
+ * The connection parameters supported by this class are:
4129
+ *
4130
+ * - scheme: it can be either 'tcp' or 'unix'.
4131
+ * - host: hostname or IP address of the server.
4132
+ * - port: TCP port of the server.
4133
+ * - path: path of a UNIX domain socket when scheme is 'unix'.
4134
+ * - timeout: timeout to perform the connection.
4135
+ * - read_write_timeout: timeout of read / write operations.
4136
+ * - async_connect: performs the connection asynchronously.
4137
+ * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
4138
+ * - persistent: the connection is left intact after a GC collection.
4139
+ *
4140
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4141
+ */
4142
+ class StreamConnection extends AbstractConnection
4143
+ {
4144
+ /**
4145
+ * Disconnects from the server and destroys the underlying resource when the
4146
+ * garbage collector kicks in only if the connection has not been marked as
4147
+ * persistent.
4148
+ */
4149
+ public function __destruct()
4150
+ {
4151
+ if (isset($this->parameters->persistent) && $this->parameters->persistent) {
4152
+ return;
4153
+ }
4154
+
4155
+ $this->disconnect();
4156
+ }
4157
+
4158
+ /**
4159
+ * {@inheritdoc}
4160
+ */
4161
+ protected function createResource()
4162
+ {
4163
+ $initializer = "{$this->parameters->scheme}StreamInitializer";
4164
+ $resource = $this->$initializer($this->parameters);
4165
+
4166
+ return $resource;
4167
+ }
4168
+
4169
+ /**
4170
+ * Initializes a TCP stream resource.
4171
+ *
4172
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4173
+ *
4174
+ * @return resource
4175
+ */
4176
+ protected function tcpStreamInitializer(ParametersInterface $parameters)
4177
+ {
4178
+ $uri = "tcp://{$parameters->host}:{$parameters->port}";
4179
+ $flags = STREAM_CLIENT_CONNECT;
4180
+
4181
+ if (isset($parameters->async_connect) && (bool) $parameters->async_connect) {
4182
+ $flags |= STREAM_CLIENT_ASYNC_CONNECT;
4183
+ }
4184
+
4185
+ if (isset($parameters->persistent) && (bool) $parameters->persistent) {
4186
+ $flags |= STREAM_CLIENT_PERSISTENT;
4187
+ $uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
4188
+ }
4189
+
4190
+ $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
4191
+
4192
+ if (!$resource) {
4193
+ $this->onConnectionError(trim($errstr), $errno);
4194
+ }
4195
+
4196
+ if (isset($parameters->read_write_timeout)) {
4197
+ $rwtimeout = (float) $parameters->read_write_timeout;
4198
+ $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
4199
+ $timeoutSeconds = floor($rwtimeout);
4200
+ $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000;
4201
+ stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
4202
+ }
4203
+
4204
+ if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
4205
+ $socket = socket_import_stream($resource);
4206
+ socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
4207
+ }
4208
+
4209
+ return $resource;
4210
+ }
4211
+
4212
+ /**
4213
+ * Initializes a UNIX stream resource.
4214
+ *
4215
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4216
+ *
4217
+ * @return resource
4218
+ */
4219
+ protected function unixStreamInitializer(ParametersInterface $parameters)
4220
+ {
4221
+ $uri = "unix://{$parameters->path}";
4222
+ $flags = STREAM_CLIENT_CONNECT;
4223
+
4224
+ if ((bool) $parameters->persistent) {
4225
+ $flags |= STREAM_CLIENT_PERSISTENT;
4226
+ }
4227
+
4228
+ $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
4229
+
4230
+ if (!$resource) {
4231
+ $this->onConnectionError(trim($errstr), $errno);
4232
+ }
4233
+
4234
+ if (isset($parameters->read_write_timeout)) {
4235
+ $rwtimeout = (float) $parameters->read_write_timeout;
4236
+ $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
4237
+ $timeoutSeconds = floor($rwtimeout);
4238
+ $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000;
4239
+ stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
4240
+ }
4241
+
4242
+ return $resource;
4243
+ }
4244
+
4245
+ /**
4246
+ * {@inheritdoc}
4247
+ */
4248
+ public function connect()
4249
+ {
4250
+ if (parent::connect() && $this->initCommands) {
4251
+ foreach ($this->initCommands as $command) {
4252
+ $this->executeCommand($command);
4253
+ }
4254
+ }
4255
+ }
4256
+
4257
+ /**
4258
+ * {@inheritdoc}
4259
+ */
4260
+ public function disconnect()
4261
+ {
4262
+ if ($this->isConnected()) {
4263
+ fclose($this->getResource());
4264
+ parent::disconnect();
4265
+ }
4266
+ }
4267
+
4268
+ /**
4269
+ * Performs a write operation over the stream of the buffer containing a
4270
+ * command serialized with the Redis wire protocol.
4271
+ *
4272
+ * @param string $buffer Representation of a command in the Redis wire protocol.
4273
+ */
4274
+ protected function write($buffer)
4275
+ {
4276
+ $socket = $this->getResource();
4277
+
4278
+ while (($length = strlen($buffer)) > 0) {
4279
+ $written = @fwrite($socket, $buffer);
4280
+
4281
+ if ($length === $written) {
4282
+ return;
4283
+ }
4284
+
4285
+ if ($written === false || $written === 0) {
4286
+ $this->onConnectionError('Error while writing bytes to the server.');
4287
+ }
4288
+
4289
+ $buffer = substr($buffer, $written);
4290
+ }
4291
+ }
4292
+
4293
+ /**
4294
+ * {@inheritdoc}
4295
+ */
4296
+ public function read()
4297
+ {
4298
+ $socket = $this->getResource();
4299
+ $chunk = fgets($socket);
4300
+
4301
+ if ($chunk === false || $chunk === '') {
4302
+ $this->onConnectionError('Error while reading line from the server.');
4303
+ }
4304
+
4305
+ $prefix = $chunk[0];
4306
+ $payload = substr($chunk, 1, -2);
4307
+
4308
+ switch ($prefix) {
4309
+ case '+':
4310
+ return StatusResponse::get($payload);
4311
+
4312
+ case '$':
4313
+ $size = (int) $payload;
4314
+
4315
+ if ($size === -1) {
4316
+ return null;
4317
+ }
4318
+
4319
+ $bulkData = '';
4320
+ $bytesLeft = ($size += 2);
4321
+
4322
+ do {
4323
+ $chunk = fread($socket, min($bytesLeft, 4096));
4324
+
4325
+ if ($chunk === false || $chunk === '') {
4326
+ $this->onConnectionError('Error while reading bytes from the server.');
4327
+ }
4328
+
4329
+ $bulkData .= $chunk;
4330
+ $bytesLeft = $size - strlen($bulkData);
4331
+ } while ($bytesLeft > 0);
4332
+
4333
+ return substr($bulkData, 0, -2);
4334
+
4335
+ case '*':
4336
+ $count = (int) $payload;
4337
+
4338
+ if ($count === -1) {
4339
+ return null;
4340
+ }
4341
+
4342
+ $multibulk = array();
4343
+
4344
+ for ($i = 0; $i < $count; $i++) {
4345
+ $multibulk[$i] = $this->read();
4346
+ }
4347
+
4348
+ return $multibulk;
4349
+
4350
+ case ':':
4351
+ return (int) $payload;
4352
+
4353
+ case '-':
4354
+ return new ErrorResponse($payload);
4355
+
4356
+ default:
4357
+ $this->onProtocolError("Unknown response prefix: '$prefix'.");
4358
+
4359
+ return;
4360
+ }
4361
+ }
4362
+
4363
+ /**
4364
+ * {@inheritdoc}
4365
+ */
4366
+ public function writeRequest(CommandInterface $command)
4367
+ {
4368
+ $commandID = $command->getId();
4369
+ $arguments = $command->getArguments();
4370
+
4371
+ $cmdlen = strlen($commandID);
4372
+ $reqlen = count($arguments) + 1;
4373
+
4374
+ $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
4375
+
4376
+ for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
4377
+ $argument = $arguments[$i];
4378
+ $arglen = strlen($argument);
4379
+ $buffer .= "\${$arglen}\r\n{$argument}\r\n";
4380
+ }
4381
+
4382
+ $this->write($buffer);
4383
+ }
4384
+ }
4385
+
4386
+ /**
4387
+ * Interface defining a container for connection parameters.
4388
+ *
4389
+ * The actual list of connection parameters depends on the features supported by
4390
+ * each connection backend class (please refer to their specific documentation),
4391
+ * but the most common parameters used through the library are:
4392
+ *
4393
+ * @property-read string scheme Connection scheme, such as 'tcp' or 'unix'.
4394
+ * @property-read string host IP address or hostname of Redis.
4395
+ * @property-read int port TCP port on which Redis is listening to.
4396
+ * @property-read string path Path of a UNIX domain socket file.
4397
+ * @property-read string alias Alias for the connection.
4398
+ * @property-read float timeout Timeout for the connect() operation.
4399
+ * @property-read float read_write_timeout Timeout for read() and write() operations.
4400
+ * @property-read bool async_connect Performs the connect() operation asynchronously.
4401
+ * @property-read bool tcp_nodelay Toggles the Nagle's algorithm for coalescing.
4402
+ * @property-read bool persistent Leaves the connection open after a GC collection.
4403
+ * @property-read string password Password to access Redis (see the AUTH command).
4404
+ * @property-read string database Database index (see the SELECT command).
4405
+ *
4406
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4407
+ */
4408
+ interface ParametersInterface
4409
+ {
4410
+ /**
4411
+ * Checks if the specified parameters is set.
4412
+ *
4413
+ * @param string $parameter Name of the parameter.
4414
+ *
4415
+ * @return bool
4416
+ */
4417
+ public function __isset($parameter);
4418
+
4419
+ /**
4420
+ * Returns the value of the specified parameter.
4421
+ *
4422
+ * @param string $parameter Name of the parameter.
4423
+ *
4424
+ * @return mixed|null
4425
+ */
4426
+ public function __get($parameter);
4427
+
4428
+ /**
4429
+ * Returns an array representation of the connection parameters.
4430
+ *
4431
+ * @return array
4432
+ */
4433
+ public function toArray();
4434
+ }
4435
+
4436
+ /**
4437
+ * Interface for classes providing a factory of connections to Redis nodes.
4438
+ *
4439
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4440
+ */
4441
+ interface FactoryInterface
4442
+ {
4443
+ /**
4444
+ * Defines or overrides the connection class identified by a scheme prefix.
4445
+ *
4446
+ * @param string $scheme Target connection scheme.
4447
+ * @param mixed $initializer Fully-qualified name of a class or a callable for lazy initialization.
4448
+ */
4449
+ public function define($scheme, $initializer);
4450
+
4451
+ /**
4452
+ * Undefines the connection identified by a scheme prefix.
4453
+ *
4454
+ * @param string $scheme Target connection scheme.
4455
+ */
4456
+ public function undefine($scheme);
4457
+
4458
+ /**
4459
+ * Creates a new connection object.
4460
+ *
4461
+ * @param mixed $parameters Initialization parameters for the connection.
4462
+ *
4463
+ * @return NodeConnectionInterface
4464
+ */
4465
+ public function create($parameters);
4466
+
4467
+ /**
4468
+ * Aggregates single connections into an aggregate connection instance.
4469
+ *
4470
+ * @param AggregateConnectionInterface $aggregate Aggregate connection instance.
4471
+ * @param array $parameters List of parameters for each connection.
4472
+ */
4473
+ public function aggregate(AggregateConnectionInterface $aggregate, array $parameters);
4474
+ }
4475
+
4476
+ /**
4477
+ * Defines a connection to communicate with a single Redis server that leverages
4478
+ * an external protocol processor to handle pluggable protocol handlers.
4479
+ *
4480
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4481
+ */
4482
+ interface CompositeConnectionInterface extends NodeConnectionInterface
4483
+ {
4484
+ /**
4485
+ * Returns the protocol processor used by the connection.
4486
+ */
4487
+ public function getProtocol();
4488
+
4489
+ /**
4490
+ * Writes the buffer containing over the connection.
4491
+ *
4492
+ * @param string $buffer String buffer to be sent over the connection.
4493
+ */
4494
+ public function writeBuffer($buffer);
4495
+
4496
+ /**
4497
+ * Reads the given number of bytes from the connection.
4498
+ *
4499
+ * @param int $length Number of bytes to read from the connection.
4500
+ *
4501
+ * @return string
4502
+ */
4503
+ public function readBuffer($length);
4504
+
4505
+ /**
4506
+ * Reads a line from the connection.
4507
+ *
4508
+ * @param string
4509
+ */
4510
+ public function readLine();
4511
+ }
4512
+
4513
+ /**
4514
+ * This class provides the implementation of a Predis connection that uses PHP's
4515
+ * streams for network communication and wraps the phpiredis C extension (PHP
4516
+ * bindings for hiredis) to parse and serialize the Redis protocol.
4517
+ *
4518
+ * This class is intended to provide an optional low-overhead alternative for
4519
+ * processing responses from Redis compared to the standard pure-PHP classes.
4520
+ * Differences in speed when dealing with short inline responses are practically
4521
+ * nonexistent, the actual speed boost is for big multibulk responses when this
4522
+ * protocol processor can parse and return responses very fast.
4523
+ *
4524
+ * For instructions on how to build and install the phpiredis extension, please
4525
+ * consult the repository of the project.
4526
+ *
4527
+ * The connection parameters supported by this class are:
4528
+ *
4529
+ * - scheme: it can be either 'tcp' or 'unix'.
4530
+ * - host: hostname or IP address of the server.
4531
+ * - port: TCP port of the server.
4532
+ * - path: path of a UNIX domain socket when scheme is 'unix'.
4533
+ * - timeout: timeout to perform the connection.
4534
+ * - read_write_timeout: timeout of read / write operations.
4535
+ * - async_connect: performs the connection asynchronously.
4536
+ * - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
4537
+ * - persistent: the connection is left intact after a GC collection.
4538
+ *
4539
+ * @link https://github.com/nrk/phpiredis
4540
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4541
+ */
4542
+ class PhpiredisStreamConnection extends StreamConnection
4543
+ {
4544
+ private $reader;
4545
+
4546
+ /**
4547
+ * {@inheritdoc}
4548
+ */
4549
+ public function __construct(ParametersInterface $parameters)
4550
+ {
4551
+ $this->assertExtensions();
4552
+
4553
+ parent::__construct($parameters);
4554
+
4555
+ $this->reader = $this->createReader();
4556
+ }
4557
+
4558
+ /**
4559
+ * {@inheritdoc}
4560
+ */
4561
+ public function __destruct()
4562
+ {
4563
+ phpiredis_reader_destroy($this->reader);
4564
+
4565
+ parent::__destruct();
4566
+ }
4567
+
4568
+ /**
4569
+ * Checks if the phpiredis extension is loaded in PHP.
4570
+ */
4571
+ private function assertExtensions()
4572
+ {
4573
+ if (!extension_loaded('phpiredis')) {
4574
+ throw new NotSupportedException(
4575
+ 'The "phpiredis" extension is required by this connection backend.'
4576
+ );
4577
+ }
4578
+ }
4579
+
4580
+ /**
4581
+ * {@inheritdoc}
4582
+ */
4583
+ protected function tcpStreamInitializer(ParametersInterface $parameters)
4584
+ {
4585
+ $uri = "tcp://{$parameters->host}:{$parameters->port}";
4586
+ $flags = STREAM_CLIENT_CONNECT;
4587
+ $socket = null;
4588
+
4589
+ if (isset($parameters->async_connect) && (bool) $parameters->async_connect) {
4590
+ $flags |= STREAM_CLIENT_ASYNC_CONNECT;
4591
+ }
4592
+
4593
+ if (isset($parameters->persistent) && (bool) $parameters->persistent) {
4594
+ $flags |= STREAM_CLIENT_PERSISTENT;
4595
+ $uri .= strpos($path = $parameters->path, '/') === 0 ? $path : "/$path";
4596
+ }
4597
+
4598
+ $resource = @stream_socket_client($uri, $errno, $errstr, (float) $parameters->timeout, $flags);
4599
+
4600
+ if (!$resource) {
4601
+ $this->onConnectionError(trim($errstr), $errno);
4602
+ }
4603
+
4604
+ if (isset($parameters->read_write_timeout) && function_exists('socket_import_stream')) {
4605
+ $rwtimeout = (float) $parameters->read_write_timeout;
4606
+ $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
4607
+
4608
+ $timeout = array(
4609
+ 'sec' => $timeoutSeconds = floor($rwtimeout),
4610
+ 'usec' => ($rwtimeout - $timeoutSeconds) * 1000000,
4611
+ );
4612
+
4613
+ $socket = $socket ?: socket_import_stream($resource);
4614
+ @socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout);
4615
+ @socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
4616
+ }
4617
+
4618
+ if (isset($parameters->tcp_nodelay) && function_exists('socket_import_stream')) {
4619
+ $socket = $socket ?: socket_import_stream($resource);
4620
+ socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
4621
+ }
4622
+
4623
+ return $resource;
4624
+ }
4625
+
4626
+ /**
4627
+ * Creates a new instance of the protocol reader resource.
4628
+ *
4629
+ * @return resource
4630
+ */
4631
+ private function createReader()
4632
+ {
4633
+ $reader = phpiredis_reader_create();
4634
+
4635
+ phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
4636
+ phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
4637
+
4638
+ return $reader;
4639
+ }
4640
+
4641
+ /**
4642
+ * Returns the underlying protocol reader resource.
4643
+ *
4644
+ * @return resource
4645
+ */
4646
+ protected function getReader()
4647
+ {
4648
+ return $this->reader;
4649
+ }
4650
+
4651
+ /**
4652
+ * Returns the handler used by the protocol reader for inline responses.
4653
+ *
4654
+ * @return \Closure
4655
+ */
4656
+ protected function getStatusHandler()
4657
+ {
4658
+ return function ($payload) {
4659
+ return StatusResponse::get($payload);
4660
+ };
4661
+ }
4662
+
4663
+ /**
4664
+ * Returns the handler used by the protocol reader for error responses.
4665
+ *
4666
+ * @return \Closure
4667
+ */
4668
+ protected function getErrorHandler()
4669
+ {
4670
+ return function ($errorMessage) {
4671
+ return new ErrorResponse($errorMessage);
4672
+ };
4673
+ }
4674
+
4675
+ /**
4676
+ * {@inheritdoc}
4677
+ */
4678
+ public function read()
4679
+ {
4680
+ $socket = $this->getResource();
4681
+ $reader = $this->reader;
4682
+
4683
+ while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
4684
+ $buffer = stream_socket_recvfrom($socket, 4096);
4685
+
4686
+ if ($buffer === false || $buffer === '') {
4687
+ $this->onConnectionError('Error while reading bytes from the server.');
4688
+ }
4689
+
4690
+ phpiredis_reader_feed($reader, $buffer);
4691
+ }
4692
+
4693
+ if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
4694
+ return phpiredis_reader_get_reply($reader);
4695
+ } else {
4696
+ $this->onProtocolError(phpiredis_reader_get_error($reader));
4697
+
4698
+ return;
4699
+ }
4700
+ }
4701
+
4702
+ /**
4703
+ * {@inheritdoc}
4704
+ */
4705
+ public function writeRequest(CommandInterface $command)
4706
+ {
4707
+ $arguments = $command->getArguments();
4708
+ array_unshift($arguments, $command->getId());
4709
+
4710
+ $this->write(phpiredis_format_command($arguments));
4711
+ }
4712
+
4713
+ /**
4714
+ * {@inheritdoc}
4715
+ */
4716
+ public function __wakeup()
4717
+ {
4718
+ $this->assertExtensions();
4719
+ $this->reader = $this->createReader();
4720
+ }
4721
+ }
4722
+
4723
+ /**
4724
+ * This class implements a Predis connection that actually talks with Webdis
4725
+ * instead of connecting directly to Redis. It relies on the cURL extension to
4726
+ * communicate with the web server and the phpiredis extension to parse the
4727
+ * protocol for responses returned in the http response bodies.
4728
+ *
4729
+ * Some features are not yet available or they simply cannot be implemented:
4730
+ * - Pipelining commands.
4731
+ * - Publish / Subscribe.
4732
+ * - MULTI / EXEC transactions (not yet supported by Webdis).
4733
+ *
4734
+ * The connection parameters supported by this class are:
4735
+ *
4736
+ * - scheme: must be 'http'.
4737
+ * - host: hostname or IP address of the server.
4738
+ * - port: TCP port of the server.
4739
+ * - timeout: timeout to perform the connection.
4740
+ * - user: username for authentication.
4741
+ * - pass: password for authentication.
4742
+ *
4743
+ * @link http://webd.is
4744
+ * @link http://github.com/nicolasff/webdis
4745
+ * @link http://github.com/seppo0010/phpiredis
4746
+ * @author Daniele Alessandri <suppakilla@gmail.com>
4747
+ */
4748
+ class WebdisConnection implements NodeConnectionInterface
4749
+ {
4750
+ private $parameters;
4751
+ private $resource;
4752
+ private $reader;
4753
+
4754
+ /**
4755
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
4756
+ *
4757
+ * @throws \InvalidArgumentException
4758
+ */
4759
+ public function __construct(ParametersInterface $parameters)
4760
+ {
4761
+ $this->assertExtensions();
4762
+
4763
+ if ($parameters->scheme !== 'http') {
4764
+ throw new InvalidArgumentException("Invalid scheme: '{$parameters->scheme}'.");
4765
+ }
4766
+
4767
+ $this->parameters = $parameters;
4768
+
4769
+ $this->resource = $this->createCurl();
4770
+ $this->reader = $this->createReader();
4771
+ }
4772
+
4773
+ /**
4774
+ * Frees the underlying cURL and protocol reader resources when the garbage
4775
+ * collector kicks in.
4776
+ */
4777
+ public function __destruct()
4778
+ {
4779
+ curl_close($this->resource);
4780
+ phpiredis_reader_destroy($this->reader);
4781
+ }
4782
+
4783
+ /**
4784
+ * Helper method used to throw on unsupported methods.
4785
+ *
4786
+ * @param string $method Name of the unsupported method.
4787
+ *
4788
+ * @throws NotSupportedException
4789
+ */
4790
+ private function throwNotSupportedException($method)
4791
+ {
4792
+ $class = __CLASS__;
4793
+ throw new NotSupportedException("The method $class::$method() is not supported.");
4794
+ }
4795
+
4796
+ /**
4797
+ * Checks if the cURL and phpiredis extensions are loaded in PHP.
4798
+ */
4799
+ private function assertExtensions()
4800
+ {
4801
+ if (!extension_loaded('curl')) {
4802
+ throw new NotSupportedException(
4803
+ 'The "curl" extension is required by this connection backend.'
4804
+ );
4805
+ }
4806
+
4807
+ if (!extension_loaded('phpiredis')) {
4808
+ throw new NotSupportedException(
4809
+ 'The "phpiredis" extension is required by this connection backend.'
4810
+ );
4811
+ }
4812
+ }
4813
+
4814
+ /**
4815
+ * Initializes cURL.
4816
+ *
4817
+ * @return resource
4818
+ */
4819
+ private function createCurl()
4820
+ {
4821
+ $parameters = $this->getParameters();
4822
+
4823
+ $options = array(
4824
+ CURLOPT_FAILONERROR => true,
4825
+ CURLOPT_CONNECTTIMEOUT_MS => $parameters->timeout * 1000,
4826
+ CURLOPT_URL => "{$parameters->scheme}://{$parameters->host}:{$parameters->port}",
4827
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
4828
+ CURLOPT_POST => true,
4829
+ CURLOPT_WRITEFUNCTION => array($this, 'feedReader'),
4830
+ );
4831
+
4832
+ if (isset($parameters->user, $parameters->pass)) {
4833
+ $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}";
4834
+ }
4835
+
4836
+ curl_setopt_array($resource = curl_init(), $options);
4837
+
4838
+ return $resource;
4839
+ }
4840
+
4841
+ /**
4842
+ * Initializes the phpiredis protocol reader.
4843
+ *
4844
+ * @return resource
4845
+ */
4846
+ private function createReader()
4847
+ {
4848
+ $reader = phpiredis_reader_create();
4849
+
4850
+ phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
4851
+ phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
4852
+
4853
+ return $reader;
4854
+ }
4855
+
4856
+ /**
4857
+ * Returns the handler used by the protocol reader for inline responses.
4858
+ *
4859
+ * @return \Closure
4860
+ */
4861
+ protected function getStatusHandler()
4862
+ {
4863
+ return function ($payload) {
4864
+ return StatusResponse::get($payload);
4865
+ };
4866
+ }
4867
+
4868
+ /**
4869
+ * Returns the handler used by the protocol reader for error responses.
4870
+ *
4871
+ * @return \Closure
4872
+ */
4873
+ protected function getErrorHandler()
4874
+ {
4875
+ return function ($payload) {
4876
+ return new ErrorResponse($payload);
4877
+ };
4878
+ }
4879
+
4880
+ /**
4881
+ * Feeds the phpredis reader resource with the data read from the network.
4882
+ *
4883
+ * @param resource $resource Reader resource.
4884
+ * @param string $buffer Buffer of data read from a connection.
4885
+ *
4886
+ * @return int
4887
+ */
4888
+ protected function feedReader($resource, $buffer)
4889
+ {
4890
+ phpiredis_reader_feed($this->reader, $buffer);
4891
+
4892
+ return strlen($buffer);
4893
+ }
4894
+
4895
+ /**
4896
+ * {@inheritdoc}
4897
+ */
4898
+ public function connect()
4899
+ {
4900
+ // NOOP
4901
+ }
4902
+
4903
+ /**
4904
+ * {@inheritdoc}
4905
+ */
4906
+ public function disconnect()
4907
+ {
4908
+ // NOOP
4909
+ }
4910
+
4911
+ /**
4912
+ * {@inheritdoc}
4913
+ */
4914
+ public function isConnected()
4915
+ {
4916
+ return true;
4917
+ }
4918
+
4919
+ /**
4920
+ * Checks if the specified command is supported by this connection class.
4921
+ *
4922
+ * @param CommandInterface $command Command instance.
4923
+ *
4924
+ * @return string
4925
+ *
4926
+ * @throws NotSupportedException
4927
+ */
4928
+ protected function getCommandId(CommandInterface $command)
4929
+ {
4930
+ switch ($commandID = $command->getId()) {
4931
+ case 'AUTH':
4932
+ case 'SELECT':
4933
+ case 'MULTI':
4934
+ case 'EXEC':
4935
+ case 'WATCH':
4936
+ case 'UNWATCH':
4937
+ case 'DISCARD':
4938
+ case 'MONITOR':
4939
+ throw new NotSupportedException("Command '$commandID' is not allowed by Webdis.");
4940
+
4941
+ default:
4942
+ return $commandID;
4943
+ }
4944
+ }
4945
+
4946
+ /**
4947
+ * {@inheritdoc}
4948
+ */
4949
+ public function writeRequest(CommandInterface $command)
4950
+ {
4951
+ $this->throwNotSupportedException(__FUNCTION__);
4952
+ }
4953
+
4954
+ /**
4955
+ * {@inheritdoc}
4956
+ */
4957
+ public function readResponse(CommandInterface $command)
4958
+ {
4959
+ $this->throwNotSupportedException(__FUNCTION__);
4960
+ }
4961
+
4962
+ /**
4963
+ * {@inheritdoc}
4964
+ */
4965
+ public function executeCommand(CommandInterface $command)
4966
+ {
4967
+ $resource = $this->resource;
4968
+ $commandId = $this->getCommandId($command);
4969
+
4970
+ if ($arguments = $command->getArguments()) {
4971
+ $arguments = implode('/', array_map('urlencode', $arguments));
4972
+ $serializedCommand = "$commandId/$arguments.raw";
4973
+ } else {
4974
+ $serializedCommand = "$commandId.raw";
4975
+ }
4976
+
4977
+ curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand);
4978
+
4979
+ if (curl_exec($resource) === false) {
4980
+ $error = curl_error($resource);
4981
+ $errno = curl_errno($resource);
4982
+
4983
+ throw new ConnectionException($this, trim($error), $errno);
4984
+ }
4985
+
4986
+ if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) {
4987
+ throw new ProtocolException($this, phpiredis_reader_get_error($this->reader));
4988
+ }
4989
+
4990
+ return phpiredis_reader_get_reply($this->reader);
4991
+ }
4992
+
4993
+ /**
4994
+ * {@inheritdoc}
4995
+ */
4996
+ public function getResource()
4997
+ {
4998
+ return $this->resource;
4999
+ }
5000
+
5001
+ /**
5002
+ * {@inheritdoc}
5003
+ */
5004
+ public function getParameters()
5005
+ {
5006
+ return $this->parameters;
5007
+ }
5008
+
5009
+ /**
5010
+ * {@inheritdoc}
5011
+ */
5012
+ public function addConnectCommand(CommandInterface $command)
5013
+ {
5014
+ $this->throwNotSupportedException(__FUNCTION__);
5015
+ }
5016
+
5017
+ /**
5018
+ * {@inheritdoc}
5019
+ */
5020
+ public function read()
5021
+ {
5022
+ $this->throwNotSupportedException(__FUNCTION__);
5023
+ }
5024
+
5025
+ /**
5026
+ * {@inheritdoc}
5027
+ */
5028
+ public function __toString()
5029
+ {
5030
+ return "{$this->parameters->host}:{$this->parameters->port}";
5031
+ }
5032
+
5033
+ /**
5034
+ * {@inheritdoc}
5035
+ */
5036
+ public function __sleep()
5037
+ {
5038
+ return array('parameters');
5039
+ }
5040
+
5041
+ /**
5042
+ * {@inheritdoc}
5043
+ */
5044
+ public function __wakeup()
5045
+ {
5046
+ $this->assertExtensions();
5047
+
5048
+ $this->resource = $this->createCurl();
5049
+ $this->reader = $this->createReader();
5050
+ }
5051
+ }
5052
+
5053
+ /**
5054
+ * This class provides the implementation of a Predis connection that uses the
5055
+ * PHP socket extension for network communication and wraps the phpiredis C
5056
+ * extension (PHP bindings for hiredis) to parse the Redis protocol.
5057
+ *
5058
+ * This class is intended to provide an optional low-overhead alternative for
5059
+ * processing responses from Redis compared to the standard pure-PHP classes.
5060
+ * Differences in speed when dealing with short inline responses are practically
5061
+ * nonexistent, the actual speed boost is for big multibulk responses when this
5062
+ * protocol processor can parse and return responses very fast.
5063
+ *
5064
+ * For instructions on how to build and install the phpiredis extension, please
5065
+ * consult the repository of the project.
5066
+ *
5067
+ * The connection parameters supported by this class are:
5068
+ *
5069
+ * - scheme: it can be either 'tcp' or 'unix'.
5070
+ * - host: hostname or IP address of the server.
5071
+ * - port: TCP port of the server.
5072
+ * - path: path of a UNIX domain socket when scheme is 'unix'.
5073
+ * - timeout: timeout to perform the connection.
5074
+ * - read_write_timeout: timeout of read / write operations.
5075
+ *
5076
+ * @link http://github.com/nrk/phpiredis
5077
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5078
+ */
5079
+ class PhpiredisSocketConnection extends AbstractConnection
5080
+ {
5081
+ private $reader;
5082
+
5083
+ /**
5084
+ * {@inheritdoc}
5085
+ */
5086
+ public function __construct(ParametersInterface $parameters)
5087
+ {
5088
+ $this->assertExtensions();
5089
+
5090
+ parent::__construct($parameters);
5091
+
5092
+ $this->reader = $this->createReader();
5093
+ }
5094
+
5095
+ /**
5096
+ * Disconnects from the server and destroys the underlying resource and the
5097
+ * protocol reader resource when PHP's garbage collector kicks in.
5098
+ */
5099
+ public function __destruct()
5100
+ {
5101
+ phpiredis_reader_destroy($this->reader);
5102
+
5103
+ parent::__destruct();
5104
+ }
5105
+
5106
+ /**
5107
+ * Checks if the socket and phpiredis extensions are loaded in PHP.
5108
+ */
5109
+ protected function assertExtensions()
5110
+ {
5111
+ if (!extension_loaded('sockets')) {
5112
+ throw new NotSupportedException(
5113
+ 'The "sockets" extension is required by this connection backend.'
5114
+ );
5115
+ }
5116
+
5117
+ if (!extension_loaded('phpiredis')) {
5118
+ throw new NotSupportedException(
5119
+ 'The "phpiredis" extension is required by this connection backend.'
5120
+ );
5121
+ }
5122
+ }
5123
+
5124
+ /**
5125
+ * {@inheritdoc}
5126
+ */
5127
+ protected function assertParameters(ParametersInterface $parameters)
5128
+ {
5129
+ if (isset($parameters->persistent)) {
5130
+ throw new NotSupportedException(
5131
+ "Persistent connections are not supported by this connection backend."
5132
+ );
5133
+ }
5134
+
5135
+ return parent::assertParameters($parameters);
5136
+ }
5137
+
5138
+ /**
5139
+ * Creates a new instance of the protocol reader resource.
5140
+ *
5141
+ * @return resource
5142
+ */
5143
+ private function createReader()
5144
+ {
5145
+ $reader = phpiredis_reader_create();
5146
+
5147
+ phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
5148
+ phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
5149
+
5150
+ return $reader;
5151
+ }
5152
+
5153
+ /**
5154
+ * Returns the underlying protocol reader resource.
5155
+ *
5156
+ * @return resource
5157
+ */
5158
+ protected function getReader()
5159
+ {
5160
+ return $this->reader;
5161
+ }
5162
+
5163
+ /**
5164
+ * Returns the handler used by the protocol reader for inline responses.
5165
+ *
5166
+ * @return \Closure
5167
+ */
5168
+ private function getStatusHandler()
5169
+ {
5170
+ return function ($payload) {
5171
+ return StatusResponse::get($payload);
5172
+ };
5173
+ }
5174
+
5175
+ /**
5176
+ * Returns the handler used by the protocol reader for error responses.
5177
+ *
5178
+ * @return \Closure
5179
+ */
5180
+ protected function getErrorHandler()
5181
+ {
5182
+ return function ($payload) {
5183
+ return new ErrorResponse($payload);
5184
+ };
5185
+ }
5186
+
5187
+ /**
5188
+ * Helper method used to throw exceptions on socket errors.
5189
+ */
5190
+ private function emitSocketError()
5191
+ {
5192
+ $errno = socket_last_error();
5193
+ $errstr = socket_strerror($errno);
5194
+
5195
+ $this->disconnect();
5196
+
5197
+ $this->onConnectionError(trim($errstr), $errno);
5198
+ }
5199
+
5200
+ /**
5201
+ * {@inheritdoc}
5202
+ */
5203
+ protected function createResource()
5204
+ {
5205
+ $isUnix = $this->parameters->scheme === 'unix';
5206
+ $domain = $isUnix ? AF_UNIX : AF_INET;
5207
+ $protocol = $isUnix ? 0 : SOL_TCP;
5208
+
5209
+ $socket = @call_user_func('socket_create', $domain, SOCK_STREAM, $protocol);
5210
+
5211
+ if (!is_resource($socket)) {
5212
+ $this->emitSocketError();
5213
+ }
5214
+
5215
+ $this->setSocketOptions($socket, $this->parameters);
5216
+
5217
+ return $socket;
5218
+ }
5219
+
5220
+ /**
5221
+ * Sets options on the socket resource from the connection parameters.
5222
+ *
5223
+ * @param resource $socket Socket resource.
5224
+ * @param ParametersInterface $parameters Parameters used to initialize the connection.
5225
+ */
5226
+ private function setSocketOptions($socket, ParametersInterface $parameters)
5227
+ {
5228
+ if ($parameters->scheme !== 'tcp') {
5229
+ return;
5230
+ }
5231
+
5232
+ if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) {
5233
+ $this->emitSocketError();
5234
+ }
5235
+
5236
+ if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
5237
+ $this->emitSocketError();
5238
+ }
5239
+
5240
+ if (isset($parameters->read_write_timeout)) {
5241
+ $rwtimeout = (float) $parameters->read_write_timeout;
5242
+ $timeoutSec = floor($rwtimeout);
5243
+ $timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000;
5244
+
5245
+ $timeout = array(
5246
+ 'sec' => $timeoutSec,
5247
+ 'usec' => $timeoutUsec,
5248
+ );
5249
+
5250
+ if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) {
5251
+ $this->emitSocketError();
5252
+ }
5253
+
5254
+ if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) {
5255
+ $this->emitSocketError();
5256
+ }
5257
+ }
5258
+ }
5259
+
5260
+ /**
5261
+ * Gets the address from the connection parameters.
5262
+ *
5263
+ * @param ParametersInterface $parameters Parameters used to initialize the connection.
5264
+ *
5265
+ * @return string
5266
+ */
5267
+ protected static function getAddress(ParametersInterface $parameters)
5268
+ {
5269
+ if ($parameters->scheme === 'unix') {
5270
+ return $parameters->path;
5271
+ }
5272
+
5273
+ $host = $parameters->host;
5274
+
5275
+ if (ip2long($host) === false) {
5276
+ if (false === $addresses = gethostbynamel($host)) {
5277
+ return false;
5278
+ }
5279
+
5280
+ return $addresses[array_rand($addresses)];
5281
+ }
5282
+
5283
+ return $host;
5284
+ }
5285
+
5286
+ /**
5287
+ * Opens the actual connection to the server with a timeout.
5288
+ *
5289
+ * @param ParametersInterface $parameters Parameters used to initialize the connection.
5290
+ *
5291
+ * @return string
5292
+ */
5293
+ private function connectWithTimeout(ParametersInterface $parameters)
5294
+ {
5295
+ if (false === $host = self::getAddress($parameters)) {
5296
+ $this->onConnectionError("Cannot resolve the address of '$parameters->host'.");
5297
+ }
5298
+
5299
+ $socket = $this->getResource();
5300
+
5301
+ socket_set_nonblock($socket);
5302
+
5303
+ if (@socket_connect($socket, $host, (int) $parameters->port) === false) {
5304
+ $error = socket_last_error();
5305
+
5306
+ if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) {
5307
+ $this->emitSocketError();
5308
+ }
5309
+ }
5310
+
5311
+ socket_set_block($socket);
5312
+
5313
+ $null = null;
5314
+ $selectable = array($socket);
5315
+
5316
+ $timeout = (float) $parameters->timeout;
5317
+ $timeoutSecs = floor($timeout);
5318
+ $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000;
5319
+
5320
+ $selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs);
5321
+
5322
+ if ($selected === 2) {
5323
+ $this->onConnectionError('Connection refused.', SOCKET_ECONNREFUSED);
5324
+ }
5325
+ if ($selected === 0) {
5326
+ $this->onConnectionError('Connection timed out.', SOCKET_ETIMEDOUT);
5327
+ }
5328
+ if ($selected === false) {
5329
+ $this->emitSocketError();
5330
+ }
5331
+ }
5332
+
5333
+ /**
5334
+ * {@inheritdoc}
5335
+ */
5336
+ public function connect()
5337
+ {
5338
+ if (parent::connect()) {
5339
+ $this->connectWithTimeout($this->parameters);
5340
+
5341
+ if ($this->initCommands) {
5342
+ foreach ($this->initCommands as $command) {
5343
+ $this->executeCommand($command);
5344
+ }
5345
+ }
5346
+ }
5347
+ }
5348
+
5349
+ /**
5350
+ * {@inheritdoc}
5351
+ */
5352
+ public function disconnect()
5353
+ {
5354
+ if ($this->isConnected()) {
5355
+ socket_close($this->getResource());
5356
+ parent::disconnect();
5357
+ }
5358
+ }
5359
+
5360
+ /**
5361
+ * {@inheritdoc}
5362
+ */
5363
+ protected function write($buffer)
5364
+ {
5365
+ $socket = $this->getResource();
5366
+
5367
+ while (($length = strlen($buffer)) > 0) {
5368
+ $written = socket_write($socket, $buffer, $length);
5369
+
5370
+ if ($length === $written) {
5371
+ return;
5372
+ }
5373
+
5374
+ if ($written === false) {
5375
+ $this->onConnectionError('Error while writing bytes to the server.');
5376
+ }
5377
+
5378
+ $buffer = substr($buffer, $written);
5379
+ }
5380
+ }
5381
+
5382
+ /**
5383
+ * {@inheritdoc}
5384
+ */
5385
+ public function read()
5386
+ {
5387
+ $socket = $this->getResource();
5388
+ $reader = $this->reader;
5389
+
5390
+ while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
5391
+ if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '') {
5392
+ $this->emitSocketError();
5393
+ }
5394
+
5395
+ phpiredis_reader_feed($reader, $buffer);
5396
+ }
5397
+
5398
+ if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
5399
+ return phpiredis_reader_get_reply($reader);
5400
+ } else {
5401
+ $this->onProtocolError(phpiredis_reader_get_error($reader));
5402
+
5403
+ return;
5404
+ }
5405
+ }
5406
+
5407
+ /**
5408
+ * {@inheritdoc}
5409
+ */
5410
+ public function writeRequest(CommandInterface $command)
5411
+ {
5412
+ $arguments = $command->getArguments();
5413
+ array_unshift($arguments, $command->getId());
5414
+
5415
+ $this->write(phpiredis_format_command($arguments));
5416
+ }
5417
+
5418
+ /**
5419
+ * {@inheritdoc}
5420
+ */
5421
+ public function __wakeup()
5422
+ {
5423
+ $this->assertExtensions();
5424
+ $this->reader = $this->createReader();
5425
+ }
5426
+ }
5427
+
5428
+ /**
5429
+ * Connection abstraction to Redis servers based on PHP's stream that uses an
5430
+ * external protocol processor defining the protocol used for the communication.
5431
+ *
5432
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5433
+ */
5434
+ class CompositeStreamConnection extends StreamConnection implements CompositeConnectionInterface
5435
+ {
5436
+ protected $protocol;
5437
+
5438
+ /**
5439
+ * @param ParametersInterface $parameters Initialization parameters for the connection.
5440
+ * @param ProtocolProcessorInterface $protocol Protocol processor.
5441
+ */
5442
+ public function __construct(
5443
+ ParametersInterface $parameters,
5444
+ ProtocolProcessorInterface $protocol = null
5445
+ ) {
5446
+ $this->parameters = $this->assertParameters($parameters);
5447
+ $this->protocol = $protocol ?: new TextProtocolProcessor();
5448
+ }
5449
+
5450
+ /**
5451
+ * {@inheritdoc}
5452
+ */
5453
+ public function getProtocol()
5454
+ {
5455
+ return $this->protocol;
5456
+ }
5457
+
5458
+ /**
5459
+ * {@inheritdoc}
5460
+ */
5461
+ public function writeBuffer($buffer)
5462
+ {
5463
+ $this->write($buffer);
5464
+ }
5465
+
5466
+ /**
5467
+ * {@inheritdoc}
5468
+ */
5469
+ public function readBuffer($length)
5470
+ {
5471
+ if ($length <= 0) {
5472
+ throw new InvalidArgumentException('Length parameter must be greater than 0.');
5473
+ }
5474
+
5475
+ $value = '';
5476
+ $socket = $this->getResource();
5477
+
5478
+ do {
5479
+ $chunk = fread($socket, $length);
5480
+
5481
+ if ($chunk === false || $chunk === '') {
5482
+ $this->onConnectionError('Error while reading bytes from the server.');
5483
+ }
5484
+
5485
+ $value .= $chunk;
5486
+ } while (($length -= strlen($chunk)) > 0);
5487
+
5488
+ return $value;
5489
+ }
5490
+
5491
+ /**
5492
+ * {@inheritdoc}
5493
+ */
5494
+ public function readLine()
5495
+ {
5496
+ $value = '';
5497
+ $socket = $this->getResource();
5498
+
5499
+ do {
5500
+ $chunk = fgets($socket);
5501
+
5502
+ if ($chunk === false || $chunk === '') {
5503
+ $this->onConnectionError('Error while reading line from the server.');
5504
+ }
5505
+
5506
+ $value .= $chunk;
5507
+ } while (substr($value, -2) !== "\r\n");
5508
+
5509
+ return substr($value, 0, -2);
5510
+ }
5511
+
5512
+ /**
5513
+ * {@inheritdoc}
5514
+ */
5515
+ public function writeRequest(CommandInterface $command)
5516
+ {
5517
+ $this->protocol->write($this, $command);
5518
+ }
5519
+
5520
+ /**
5521
+ * {@inheritdoc}
5522
+ */
5523
+ public function read()
5524
+ {
5525
+ return $this->protocol->read($this);
5526
+ }
5527
+
5528
+ /**
5529
+ * {@inheritdoc}
5530
+ */
5531
+ public function __sleep()
5532
+ {
5533
+ return array_merge(parent::__sleep(), array('protocol'));
5534
+ }
5535
+ }
5536
+
5537
+ /**
5538
+ * Exception class that identifies connection-related errors.
5539
+ *
5540
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5541
+ */
5542
+ class ConnectionException extends CommunicationException
5543
+ {
5544
+ }
5545
+
5546
+ /**
5547
+ * Container for connection parameters used to initialize connections to Redis.
5548
+ *
5549
+ * {@inheritdoc}
5550
+ *
5551
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5552
+ */
5553
+ class Parameters implements ParametersInterface
5554
+ {
5555
+ private $parameters;
5556
+
5557
+ private static $defaults = array(
5558
+ 'scheme' => 'tcp',
5559
+ 'host' => '127.0.0.1',
5560
+ 'port' => 6379,
5561
+ 'timeout' => 5.0,
5562
+ );
5563
+
5564
+ /**
5565
+ * @param array $parameters Named array of connection parameters.
5566
+ */
5567
+ public function __construct(array $parameters = array())
5568
+ {
5569
+ $this->parameters = $this->filter($parameters) + $this->getDefaults();
5570
+ }
5571
+
5572
+ /**
5573
+ * Returns some default parameters with their values.
5574
+ *
5575
+ * @return array
5576
+ */
5577
+ protected function getDefaults()
5578
+ {
5579
+ return self::$defaults;
5580
+ }
5581
+
5582
+ /**
5583
+ * Creates a new instance by supplying the initial parameters either in the
5584
+ * form of an URI string or a named array.
5585
+ *
5586
+ * @param array|string $parameters Set of connection parameters.
5587
+ *
5588
+ * @return Parameters
5589
+ */
5590
+ public static function create($parameters)
5591
+ {
5592
+ if (is_string($parameters)) {
5593
+ $parameters = static::parse($parameters);
5594
+ }
5595
+
5596
+ return new static($parameters ?: array());
5597
+ }
5598
+
5599
+ /**
5600
+ * Parses an URI string returning an array of connection parameters.
5601
+ *
5602
+ * @param string $uri URI string.
5603
+ *
5604
+ * @return array
5605
+ *
5606
+ * @throws \InvalidArgumentException
5607
+ */
5608
+ public static function parse($uri)
5609
+ {
5610
+ if (stripos($uri, 'unix') === 0) {
5611
+ // Hack to support URIs for UNIX sockets with minimal effort.
5612
+ $uri = str_ireplace('unix:///', 'unix://localhost/', $uri);
5613
+ }
5614
+
5615
+ if (!($parsed = parse_url($uri)) || !isset($parsed['host'])) {
5616
+ throw new InvalidArgumentException("Invalid parameters URI: $uri");
5617
+ }
5618
+
5619
+ if (isset($parsed['query'])) {
5620
+ parse_str($parsed['query'], $queryarray);
5621
+ unset($parsed['query']);
5622
+
5623
+ $parsed = array_merge($parsed, $queryarray);
5624
+ }
5625
+
5626
+ return $parsed;
5627
+ }
5628
+
5629
+ /**
5630
+ * Validates and converts each value of the connection parameters array.
5631
+ *
5632
+ * @param array $parameters Connection parameters.
5633
+ *
5634
+ * @return array
5635
+ */
5636
+ protected function filter(array $parameters)
5637
+ {
5638
+ return $parameters ?: array();
5639
+ }
5640
+
5641
+ /**
5642
+ * {@inheritdoc}
5643
+ */
5644
+ public function __get($parameter)
5645
+ {
5646
+ if (isset($this->parameters[$parameter])) {
5647
+ return $this->parameters[$parameter];
5648
+ }
5649
+ }
5650
+
5651
+ /**
5652
+ * {@inheritdoc}
5653
+ */
5654
+ public function __isset($parameter)
5655
+ {
5656
+ return isset($this->parameters[$parameter]);
5657
+ }
5658
+
5659
+ /**
5660
+ * {@inheritdoc}
5661
+ */
5662
+ public function toArray()
5663
+ {
5664
+ return $this->parameters;
5665
+ }
5666
+
5667
+ /**
5668
+ * {@inheritdoc}
5669
+ */
5670
+ public function __sleep()
5671
+ {
5672
+ return array('parameters');
5673
+ }
5674
+ }
5675
+
5676
+ /**
5677
+ * Standard connection factory for creating connections to Redis nodes.
5678
+ *
5679
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5680
+ */
5681
+ class Factory implements FactoryInterface
5682
+ {
5683
+ protected $schemes = array(
5684
+ 'tcp' => 'Predis\Connection\StreamConnection',
5685
+ 'unix' => 'Predis\Connection\StreamConnection',
5686
+ 'http' => 'Predis\Connection\WebdisConnection',
5687
+ );
5688
+
5689
+ /**
5690
+ * Checks if the provided argument represents a valid connection class
5691
+ * implementing Predis\Connection\NodeConnectionInterface. Optionally,
5692
+ * callable objects are used for lazy initialization of connection objects.
5693
+ *
5694
+ * @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
5695
+ *
5696
+ * @return mixed
5697
+ *
5698
+ * @throws \InvalidArgumentException
5699
+ */
5700
+ protected function checkInitializer($initializer)
5701
+ {
5702
+ if (is_callable($initializer)) {
5703
+ return $initializer;
5704
+ }
5705
+
5706
+ $class = new ReflectionClass($initializer);
5707
+
5708
+ if (!$class->isSubclassOf('Predis\Connection\NodeConnectionInterface')) {
5709
+ throw new InvalidArgumentException(
5710
+ 'A connection initializer must be a valid connection class or a callable object.'
5711
+ );
5712
+ }
5713
+
5714
+ return $initializer;
5715
+ }
5716
+
5717
+ /**
5718
+ * {@inheritdoc}
5719
+ */
5720
+ public function define($scheme, $initializer)
5721
+ {
5722
+ $this->schemes[$scheme] = $this->checkInitializer($initializer);
5723
+ }
5724
+
5725
+ /**
5726
+ * {@inheritdoc}
5727
+ */
5728
+ public function undefine($scheme)
5729
+ {
5730
+ unset($this->schemes[$scheme]);
5731
+ }
5732
+
5733
+ /**
5734
+ * {@inheritdoc}
5735
+ */
5736
+ public function create($parameters)
5737
+ {
5738
+ if (!$parameters instanceof ParametersInterface) {
5739
+ $parameters = $this->createParameters($parameters);
5740
+ }
5741
+
5742
+ $scheme = $parameters->scheme;
5743
+
5744
+ if (!isset($this->schemes[$scheme])) {
5745
+ throw new InvalidArgumentException("Unknown connection scheme: '$scheme'.");
5746
+ }
5747
+
5748
+ $initializer = $this->schemes[$scheme];
5749
+
5750
+ if (is_callable($initializer)) {
5751
+ $connection = call_user_func($initializer, $parameters, $this);
5752
+ } else {
5753
+ $connection = new $initializer($parameters);
5754
+ $this->prepareConnection($connection);
5755
+ }
5756
+
5757
+ if (!$connection instanceof NodeConnectionInterface) {
5758
+ throw new UnexpectedValueException(
5759
+ "Objects returned by connection initializers must implement ".
5760
+ "'Predis\Connection\NodeConnectionInterface'."
5761
+ );
5762
+ }
5763
+
5764
+ return $connection;
5765
+ }
5766
+
5767
+ /**
5768
+ * {@inheritdoc}
5769
+ */
5770
+ public function aggregate(AggregateConnectionInterface $connection, array $parameters)
5771
+ {
5772
+ foreach ($parameters as $node) {
5773
+ $connection->add($node instanceof NodeConnectionInterface ? $node : $this->create($node));
5774
+ }
5775
+ }
5776
+
5777
+ /**
5778
+ * Creates a connection parameters instance from the supplied argument.
5779
+ *
5780
+ * @param mixed $parameters Original connection parameters.
5781
+ *
5782
+ * @return ParametersInterface
5783
+ */
5784
+ protected function createParameters($parameters)
5785
+ {
5786
+ return Parameters::create($parameters);
5787
+ }
5788
+
5789
+ /**
5790
+ * Prepares a connection instance after its initialization.
5791
+ *
5792
+ * @param NodeConnectionInterface $connection Connection instance.
5793
+ */
5794
+ protected function prepareConnection(NodeConnectionInterface $connection)
5795
+ {
5796
+ $parameters = $connection->getParameters();
5797
+
5798
+ if (isset($parameters->password)) {
5799
+ $connection->addConnectCommand(
5800
+ new RawCommand(array('AUTH', $parameters->password))
5801
+ );
5802
+ }
5803
+
5804
+ if (isset($parameters->database)) {
5805
+ $connection->addConnectCommand(
5806
+ new RawCommand(array('SELECT', $parameters->database))
5807
+ );
5808
+ }
5809
+ }
5810
+ }
5811
+
5812
+ /* --------------------------------------------------------------------------- */
5813
+
5814
+ namespace Predis\Profile;
5815
+
5816
+ use InvalidArgumentException;
5817
+ use ReflectionClass;
5818
+ use Predis\ClientException;
5819
+ use Predis\Command\CommandInterface;
5820
+ use Predis\Command\Processor\ProcessorInterface;
5821
+
5822
+ /**
5823
+ * A profile defines all the features and commands supported by certain versions
5824
+ * of Redis. Instances of Predis\Client should use a server profile matching the
5825
+ * version of Redis being used.
5826
+ *
5827
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5828
+ */
5829
+ interface ProfileInterface
5830
+ {
5831
+ /**
5832
+ * Returns the profile version corresponding to the Redis version.
5833
+ *
5834
+ * @return string
5835
+ */
5836
+ public function getVersion();
5837
+
5838
+ /**
5839
+ * Checks if the profile supports the specified command.
5840
+ *
5841
+ * @param string $commandID Command ID.
5842
+ *
5843
+ * @return bool
5844
+ */
5845
+ public function supportsCommand($commandID);
5846
+
5847
+ /**
5848
+ * Checks if the profile supports the specified list of commands.
5849
+ *
5850
+ * @param array $commandIDs List of command IDs.
5851
+ *
5852
+ * @return string
5853
+ */
5854
+ public function supportsCommands(array $commandIDs);
5855
+
5856
+ /**
5857
+ * Creates a new command instance.
5858
+ *
5859
+ * @param string $commandID Command ID.
5860
+ * @param array $arguments Arguments for the command.
5861
+ *
5862
+ * @return CommandInterface
5863
+ */
5864
+ public function createCommand($commandID, array $arguments = array());
5865
+ }
5866
+
5867
+ /**
5868
+ * Base class implementing common functionalities for Redis server profiles.
5869
+ *
5870
+ * @author Daniele Alessandri <suppakilla@gmail.com>
5871
+ */
5872
+ abstract class RedisProfile implements ProfileInterface
5873
+ {
5874
+ private $commands;
5875
+ private $processor;
5876
+
5877
+ /**
5878
+ *
5879
+ */
5880
+ public function __construct()
5881
+ {
5882
+ $this->commands = $this->getSupportedCommands();
5883
+ }
5884
+
5885
+ /**
5886
+ * Returns a map of all the commands supported by the profile and their
5887
+ * actual PHP classes.
5888
+ *
5889
+ * @return array
5890
+ */
5891
+ abstract protected function getSupportedCommands();
5892
+
5893
+ /**
5894
+ * {@inheritdoc}
5895
+ */
5896
+ public function supportsCommand($commandID)
5897
+ {
5898
+ return isset($this->commands[strtoupper($commandID)]);
5899
+ }
5900
+
5901
+ /**
5902
+ * {@inheritdoc}
5903
+ */
5904
+ public function supportsCommands(array $commandIDs)
5905
+ {
5906
+ foreach ($commandIDs as $commandID) {
5907
+ if (!$this->supportsCommand($commandID)) {
5908
+ return false;
5909
+ }
5910
+ }
5911
+
5912
+ return true;
5913
+ }
5914
+
5915
+ /**
5916
+ * Returns the fully-qualified name of a class representing the specified
5917
+ * command ID registered in the current server profile.
5918
+ *
5919
+ * @param string $commandID Command ID.
5920
+ *
5921
+ * @return string|null
5922
+ */
5923
+ public function getCommandClass($commandID)
5924
+ {
5925
+ if (isset($this->commands[$commandID = strtoupper($commandID)])) {
5926
+ return $this->commands[$commandID];
5927
+ }
5928
+ }
5929
+
5930
+ /**
5931
+ * {@inheritdoc}
5932
+ */
5933
+ public function createCommand($commandID, array $arguments = array())
5934
+ {
5935
+ $commandID = strtoupper($commandID);
5936
+
5937
+ if (!isset($this->commands[$commandID])) {
5938
+ throw new ClientException("Command '$commandID' is not a registered Redis command.");
5939
+ }
5940
+
5941
+ $commandClass = $this->commands[$commandID];
5942
+ $command = new $commandClass();
5943
+ $command->setArguments($arguments);
5944
+
5945
+ if (isset($this->processor)) {
5946
+ $this->processor->process($command);
5947
+ }
5948
+
5949
+ return $command;
5950
+ }
5951
+
5952
+ /**
5953
+ * Defines a new command in the server profile.
5954
+ *
5955
+ * @param string $commandID Command ID.
5956
+ * @param string $class Fully-qualified name of a Predis\Command\CommandInterface.
5957
+ *
5958
+ * @throws \InvalidArgumentException
5959
+ */
5960
+ public function defineCommand($commandID, $class)
5961
+ {
5962
+ $reflection = new ReflectionClass($class);
5963
+
5964
+ if (!$reflection->isSubclassOf('Predis\Command\CommandInterface')) {
5965
+ throw new InvalidArgumentException("The class '$class' is not a valid command class.");
5966
+ }
5967
+
5968
+ $this->commands[strtoupper($commandID)] = $class;
5969
+ }
5970
+
5971
+ /**
5972
+ * {@inheritdoc}
5973
+ */
5974
+ public function setProcessor(ProcessorInterface $processor = null)
5975
+ {
5976
+ $this->processor = $processor;
5977
+ }
5978
+
5979
+ /**
5980
+ * {@inheritdoc}
5981
+ */
5982
+ public function getProcessor()
5983
+ {
5984
+ return $this->processor;
5985
+ }
5986
+
5987
+ /**
5988
+ * Returns the version of server profile as its string representation.
5989
+ *
5990
+ * @return string
5991
+ */
5992
+ public function __toString()
5993
+ {
5994
+ return $this->getVersion();
5995
+ }
5996
+ }
5997
+
5998
+ /**
5999
+ * Server profile for Redis 3.0.
6000
+ *
6001
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6002
+ */
6003
+ class RedisVersion300 extends RedisProfile
6004
+ {
6005
+ /**
6006
+ * {@inheritdoc}
6007
+ */
6008
+ public function getVersion()
6009
+ {
6010
+ return '3.0';
6011
+ }
6012
+
6013
+ /**
6014
+ * {@inheritdoc}
6015
+ */
6016
+ public function getSupportedCommands()
6017
+ {
6018
+ return array(
6019
+ /* ---------------- Redis 1.2 ---------------- */
6020
+
6021
+ /* commands operating on the key space */
6022
+ 'EXISTS' => 'Predis\Command\KeyExists',
6023
+ 'DEL' => 'Predis\Command\KeyDelete',
6024
+ 'TYPE' => 'Predis\Command\KeyType',
6025
+ 'KEYS' => 'Predis\Command\KeyKeys',
6026
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6027
+ 'RENAME' => 'Predis\Command\KeyRename',
6028
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6029
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6030
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6031
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6032
+ 'MOVE' => 'Predis\Command\KeyMove',
6033
+ 'SORT' => 'Predis\Command\KeySort',
6034
+ 'DUMP' => 'Predis\Command\KeyDump',
6035
+ 'RESTORE' => 'Predis\Command\KeyRestore',
6036
+
6037
+ /* commands operating on string values */
6038
+ 'SET' => 'Predis\Command\StringSet',
6039
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6040
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6041
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6042
+ 'GET' => 'Predis\Command\StringGet',
6043
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6044
+ 'GETSET' => 'Predis\Command\StringGetSet',
6045
+ 'INCR' => 'Predis\Command\StringIncrement',
6046
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6047
+ 'DECR' => 'Predis\Command\StringDecrement',
6048
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6049
+
6050
+ /* commands operating on lists */
6051
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6052
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6053
+ 'LLEN' => 'Predis\Command\ListLength',
6054
+ 'LRANGE' => 'Predis\Command\ListRange',
6055
+ 'LTRIM' => 'Predis\Command\ListTrim',
6056
+ 'LINDEX' => 'Predis\Command\ListIndex',
6057
+ 'LSET' => 'Predis\Command\ListSet',
6058
+ 'LREM' => 'Predis\Command\ListRemove',
6059
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6060
+ 'RPOP' => 'Predis\Command\ListPopLast',
6061
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6062
+
6063
+ /* commands operating on sets */
6064
+ 'SADD' => 'Predis\Command\SetAdd',
6065
+ 'SREM' => 'Predis\Command\SetRemove',
6066
+ 'SPOP' => 'Predis\Command\SetPop',
6067
+ 'SMOVE' => 'Predis\Command\SetMove',
6068
+ 'SCARD' => 'Predis\Command\SetCardinality',
6069
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6070
+ 'SINTER' => 'Predis\Command\SetIntersection',
6071
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6072
+ 'SUNION' => 'Predis\Command\SetUnion',
6073
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6074
+ 'SDIFF' => 'Predis\Command\SetDifference',
6075
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6076
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6077
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6078
+
6079
+ /* commands operating on sorted sets */
6080
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6081
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6082
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6083
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6084
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6085
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6086
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6087
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6088
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6089
+
6090
+ /* connection related commands */
6091
+ 'PING' => 'Predis\Command\ConnectionPing',
6092
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6093
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6094
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6095
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6096
+
6097
+ /* remote server control commands */
6098
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
6099
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6100
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6101
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6102
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6103
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6104
+ 'SAVE' => 'Predis\Command\ServerSave',
6105
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6106
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6107
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6108
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6109
+
6110
+ /* ---------------- Redis 2.0 ---------------- */
6111
+
6112
+ /* commands operating on string values */
6113
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6114
+ 'APPEND' => 'Predis\Command\StringAppend',
6115
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6116
+
6117
+ /* commands operating on lists */
6118
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6119
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6120
+
6121
+ /* commands operating on sorted sets */
6122
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
6123
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
6124
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
6125
+ 'ZRANK' => 'Predis\Command\ZSetRank',
6126
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
6127
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
6128
+
6129
+ /* commands operating on hashes */
6130
+ 'HSET' => 'Predis\Command\HashSet',
6131
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
6132
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
6133
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
6134
+ 'HGET' => 'Predis\Command\HashGet',
6135
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
6136
+ 'HDEL' => 'Predis\Command\HashDelete',
6137
+ 'HEXISTS' => 'Predis\Command\HashExists',
6138
+ 'HLEN' => 'Predis\Command\HashLength',
6139
+ 'HKEYS' => 'Predis\Command\HashKeys',
6140
+ 'HVALS' => 'Predis\Command\HashValues',
6141
+ 'HGETALL' => 'Predis\Command\HashGetAll',
6142
+
6143
+ /* transactions */
6144
+ 'MULTI' => 'Predis\Command\TransactionMulti',
6145
+ 'EXEC' => 'Predis\Command\TransactionExec',
6146
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
6147
+
6148
+ /* publish - subscribe */
6149
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
6150
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
6151
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
6152
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
6153
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
6154
+
6155
+ /* remote server control commands */
6156
+ 'CONFIG' => 'Predis\Command\ServerConfig',
6157
+
6158
+ /* ---------------- Redis 2.2 ---------------- */
6159
+
6160
+ /* commands operating on the key space */
6161
+ 'PERSIST' => 'Predis\Command\KeyPersist',
6162
+
6163
+ /* commands operating on string values */
6164
+ 'STRLEN' => 'Predis\Command\StringStrlen',
6165
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
6166
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
6167
+ 'SETBIT' => 'Predis\Command\StringSetBit',
6168
+ 'GETBIT' => 'Predis\Command\StringGetBit',
6169
+
6170
+ /* commands operating on lists */
6171
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
6172
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6173
+ 'LINSERT' => 'Predis\Command\ListInsert',
6174
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6175
+
6176
+ /* commands operating on sorted sets */
6177
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6178
+
6179
+ /* transactions */
6180
+ 'WATCH' => 'Predis\Command\TransactionWatch',
6181
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6182
+
6183
+ /* remote server control commands */
6184
+ 'OBJECT' => 'Predis\Command\ServerObject',
6185
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6186
+
6187
+ /* ---------------- Redis 2.4 ---------------- */
6188
+
6189
+ /* remote server control commands */
6190
+ 'CLIENT' => 'Predis\Command\ServerClient',
6191
+
6192
+ /* ---------------- Redis 2.6 ---------------- */
6193
+
6194
+ /* commands operating on the key space */
6195
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
6196
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
6197
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
6198
+
6199
+ /* commands operating on string values */
6200
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
6201
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
6202
+ 'BITOP' => 'Predis\Command\StringBitOp',
6203
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
6204
+
6205
+ /* commands operating on hashes */
6206
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
6207
+
6208
+ /* scripting */
6209
+ 'EVAL' => 'Predis\Command\ServerEval',
6210
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
6211
+ 'SCRIPT' => 'Predis\Command\ServerScript',
6212
+
6213
+ /* remote server control commands */
6214
+ 'TIME' => 'Predis\Command\ServerTime',
6215
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
6216
+
6217
+ /* ---------------- Redis 2.8 ---------------- */
6218
+
6219
+ /* commands operating on the key space */
6220
+ 'SCAN' => 'Predis\Command\KeyScan',
6221
+
6222
+ /* commands operating on string values */
6223
+ 'BITPOS' => 'Predis\Command\StringBitPos',
6224
+
6225
+ /* commands operating on sets */
6226
+ 'SSCAN' => 'Predis\Command\SetScan',
6227
+
6228
+ /* commands operating on sorted sets */
6229
+ 'ZSCAN' => 'Predis\Command\ZSetScan',
6230
+ 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
6231
+ 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
6232
+ 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
6233
+
6234
+ /* commands operating on hashes */
6235
+ 'HSCAN' => 'Predis\Command\HashScan',
6236
+
6237
+ /* publish - subscribe */
6238
+ 'PUBSUB' => 'Predis\Command\PubSubPubsub',
6239
+
6240
+ /* commands operating on HyperLogLog */
6241
+ 'PFADD' => 'Predis\Command\HyperLogLogAdd',
6242
+ 'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
6243
+ 'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
6244
+
6245
+ /* remote server control commands */
6246
+ 'COMMAND' => 'Predis\Command\ServerCommand',
6247
+
6248
+ /* ---------------- Redis 3.0 ---------------- */
6249
+
6250
+ );
6251
+ }
6252
+ }
6253
+
6254
+ /**
6255
+ * Server profile for Redis 2.6.
6256
+ *
6257
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6258
+ */
6259
+ class RedisVersion260 extends RedisProfile
6260
+ {
6261
+ /**
6262
+ * {@inheritdoc}
6263
+ */
6264
+ public function getVersion()
6265
+ {
6266
+ return '2.6';
6267
+ }
6268
+
6269
+ /**
6270
+ * {@inheritdoc}
6271
+ */
6272
+ public function getSupportedCommands()
6273
+ {
6274
+ return array(
6275
+ /* ---------------- Redis 1.2 ---------------- */
6276
+
6277
+ /* commands operating on the key space */
6278
+ 'EXISTS' => 'Predis\Command\KeyExists',
6279
+ 'DEL' => 'Predis\Command\KeyDelete',
6280
+ 'TYPE' => 'Predis\Command\KeyType',
6281
+ 'KEYS' => 'Predis\Command\KeyKeys',
6282
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6283
+ 'RENAME' => 'Predis\Command\KeyRename',
6284
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6285
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6286
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6287
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6288
+ 'MOVE' => 'Predis\Command\KeyMove',
6289
+ 'SORT' => 'Predis\Command\KeySort',
6290
+ 'DUMP' => 'Predis\Command\KeyDump',
6291
+ 'RESTORE' => 'Predis\Command\KeyRestore',
6292
+
6293
+ /* commands operating on string values */
6294
+ 'SET' => 'Predis\Command\StringSet',
6295
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6296
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6297
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6298
+ 'GET' => 'Predis\Command\StringGet',
6299
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6300
+ 'GETSET' => 'Predis\Command\StringGetSet',
6301
+ 'INCR' => 'Predis\Command\StringIncrement',
6302
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6303
+ 'DECR' => 'Predis\Command\StringDecrement',
6304
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6305
+
6306
+ /* commands operating on lists */
6307
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6308
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6309
+ 'LLEN' => 'Predis\Command\ListLength',
6310
+ 'LRANGE' => 'Predis\Command\ListRange',
6311
+ 'LTRIM' => 'Predis\Command\ListTrim',
6312
+ 'LINDEX' => 'Predis\Command\ListIndex',
6313
+ 'LSET' => 'Predis\Command\ListSet',
6314
+ 'LREM' => 'Predis\Command\ListRemove',
6315
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6316
+ 'RPOP' => 'Predis\Command\ListPopLast',
6317
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6318
+
6319
+ /* commands operating on sets */
6320
+ 'SADD' => 'Predis\Command\SetAdd',
6321
+ 'SREM' => 'Predis\Command\SetRemove',
6322
+ 'SPOP' => 'Predis\Command\SetPop',
6323
+ 'SMOVE' => 'Predis\Command\SetMove',
6324
+ 'SCARD' => 'Predis\Command\SetCardinality',
6325
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6326
+ 'SINTER' => 'Predis\Command\SetIntersection',
6327
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6328
+ 'SUNION' => 'Predis\Command\SetUnion',
6329
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6330
+ 'SDIFF' => 'Predis\Command\SetDifference',
6331
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6332
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6333
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6334
+
6335
+ /* commands operating on sorted sets */
6336
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6337
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6338
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6339
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6340
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6341
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6342
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6343
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6344
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6345
+
6346
+ /* connection related commands */
6347
+ 'PING' => 'Predis\Command\ConnectionPing',
6348
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6349
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6350
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6351
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6352
+
6353
+ /* remote server control commands */
6354
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
6355
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6356
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6357
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6358
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6359
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6360
+ 'SAVE' => 'Predis\Command\ServerSave',
6361
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6362
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6363
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6364
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6365
+
6366
+ /* ---------------- Redis 2.0 ---------------- */
6367
+
6368
+ /* commands operating on string values */
6369
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6370
+ 'APPEND' => 'Predis\Command\StringAppend',
6371
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6372
+
6373
+ /* commands operating on lists */
6374
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6375
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6376
+
6377
+ /* commands operating on sorted sets */
6378
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
6379
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
6380
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
6381
+ 'ZRANK' => 'Predis\Command\ZSetRank',
6382
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
6383
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
6384
+
6385
+ /* commands operating on hashes */
6386
+ 'HSET' => 'Predis\Command\HashSet',
6387
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
6388
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
6389
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
6390
+ 'HGET' => 'Predis\Command\HashGet',
6391
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
6392
+ 'HDEL' => 'Predis\Command\HashDelete',
6393
+ 'HEXISTS' => 'Predis\Command\HashExists',
6394
+ 'HLEN' => 'Predis\Command\HashLength',
6395
+ 'HKEYS' => 'Predis\Command\HashKeys',
6396
+ 'HVALS' => 'Predis\Command\HashValues',
6397
+ 'HGETALL' => 'Predis\Command\HashGetAll',
6398
+
6399
+ /* transactions */
6400
+ 'MULTI' => 'Predis\Command\TransactionMulti',
6401
+ 'EXEC' => 'Predis\Command\TransactionExec',
6402
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
6403
+
6404
+ /* publish - subscribe */
6405
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
6406
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
6407
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
6408
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
6409
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
6410
+
6411
+ /* remote server control commands */
6412
+ 'CONFIG' => 'Predis\Command\ServerConfig',
6413
+
6414
+ /* ---------------- Redis 2.2 ---------------- */
6415
+
6416
+ /* commands operating on the key space */
6417
+ 'PERSIST' => 'Predis\Command\KeyPersist',
6418
+
6419
+ /* commands operating on string values */
6420
+ 'STRLEN' => 'Predis\Command\StringStrlen',
6421
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
6422
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
6423
+ 'SETBIT' => 'Predis\Command\StringSetBit',
6424
+ 'GETBIT' => 'Predis\Command\StringGetBit',
6425
+
6426
+ /* commands operating on lists */
6427
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
6428
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6429
+ 'LINSERT' => 'Predis\Command\ListInsert',
6430
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6431
+
6432
+ /* commands operating on sorted sets */
6433
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6434
+
6435
+ /* transactions */
6436
+ 'WATCH' => 'Predis\Command\TransactionWatch',
6437
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6438
+
6439
+ /* remote server control commands */
6440
+ 'OBJECT' => 'Predis\Command\ServerObject',
6441
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6442
+
6443
+ /* ---------------- Redis 2.4 ---------------- */
6444
+
6445
+ /* remote server control commands */
6446
+ 'CLIENT' => 'Predis\Command\ServerClient',
6447
+
6448
+ /* ---------------- Redis 2.6 ---------------- */
6449
+
6450
+ /* commands operating on the key space */
6451
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
6452
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
6453
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
6454
+
6455
+ /* commands operating on string values */
6456
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
6457
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
6458
+ 'BITOP' => 'Predis\Command\StringBitOp',
6459
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
6460
+
6461
+ /* commands operating on hashes */
6462
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
6463
+
6464
+ /* scripting */
6465
+ 'EVAL' => 'Predis\Command\ServerEval',
6466
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
6467
+ 'SCRIPT' => 'Predis\Command\ServerScript',
6468
+
6469
+ /* remote server control commands */
6470
+ 'TIME' => 'Predis\Command\ServerTime',
6471
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
6472
+ );
6473
+ }
6474
+ }
6475
+
6476
+ /**
6477
+ * Server profile for Redis 2.8.
6478
+ *
6479
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6480
+ */
6481
+ class RedisVersion280 extends RedisProfile
6482
+ {
6483
+ /**
6484
+ * {@inheritdoc}
6485
+ */
6486
+ public function getVersion()
6487
+ {
6488
+ return '2.8';
6489
+ }
6490
+
6491
+ /**
6492
+ * {@inheritdoc}
6493
+ */
6494
+ public function getSupportedCommands()
6495
+ {
6496
+ return array(
6497
+ /* ---------------- Redis 1.2 ---------------- */
6498
+
6499
+ /* commands operating on the key space */
6500
+ 'EXISTS' => 'Predis\Command\KeyExists',
6501
+ 'DEL' => 'Predis\Command\KeyDelete',
6502
+ 'TYPE' => 'Predis\Command\KeyType',
6503
+ 'KEYS' => 'Predis\Command\KeyKeys',
6504
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6505
+ 'RENAME' => 'Predis\Command\KeyRename',
6506
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6507
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6508
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6509
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6510
+ 'MOVE' => 'Predis\Command\KeyMove',
6511
+ 'SORT' => 'Predis\Command\KeySort',
6512
+ 'DUMP' => 'Predis\Command\KeyDump',
6513
+ 'RESTORE' => 'Predis\Command\KeyRestore',
6514
+
6515
+ /* commands operating on string values */
6516
+ 'SET' => 'Predis\Command\StringSet',
6517
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6518
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6519
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6520
+ 'GET' => 'Predis\Command\StringGet',
6521
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6522
+ 'GETSET' => 'Predis\Command\StringGetSet',
6523
+ 'INCR' => 'Predis\Command\StringIncrement',
6524
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6525
+ 'DECR' => 'Predis\Command\StringDecrement',
6526
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6527
+
6528
+ /* commands operating on lists */
6529
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6530
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6531
+ 'LLEN' => 'Predis\Command\ListLength',
6532
+ 'LRANGE' => 'Predis\Command\ListRange',
6533
+ 'LTRIM' => 'Predis\Command\ListTrim',
6534
+ 'LINDEX' => 'Predis\Command\ListIndex',
6535
+ 'LSET' => 'Predis\Command\ListSet',
6536
+ 'LREM' => 'Predis\Command\ListRemove',
6537
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6538
+ 'RPOP' => 'Predis\Command\ListPopLast',
6539
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6540
+
6541
+ /* commands operating on sets */
6542
+ 'SADD' => 'Predis\Command\SetAdd',
6543
+ 'SREM' => 'Predis\Command\SetRemove',
6544
+ 'SPOP' => 'Predis\Command\SetPop',
6545
+ 'SMOVE' => 'Predis\Command\SetMove',
6546
+ 'SCARD' => 'Predis\Command\SetCardinality',
6547
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6548
+ 'SINTER' => 'Predis\Command\SetIntersection',
6549
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6550
+ 'SUNION' => 'Predis\Command\SetUnion',
6551
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6552
+ 'SDIFF' => 'Predis\Command\SetDifference',
6553
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6554
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6555
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6556
+
6557
+ /* commands operating on sorted sets */
6558
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6559
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6560
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6561
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6562
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6563
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6564
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6565
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6566
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6567
+
6568
+ /* connection related commands */
6569
+ 'PING' => 'Predis\Command\ConnectionPing',
6570
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6571
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6572
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6573
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6574
+
6575
+ /* remote server control commands */
6576
+ 'INFO' => 'Predis\Command\ServerInfoV26x',
6577
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6578
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6579
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6580
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6581
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6582
+ 'SAVE' => 'Predis\Command\ServerSave',
6583
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6584
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6585
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6586
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6587
+
6588
+ /* ---------------- Redis 2.0 ---------------- */
6589
+
6590
+ /* commands operating on string values */
6591
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6592
+ 'APPEND' => 'Predis\Command\StringAppend',
6593
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6594
+
6595
+ /* commands operating on lists */
6596
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6597
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6598
+
6599
+ /* commands operating on sorted sets */
6600
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
6601
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
6602
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
6603
+ 'ZRANK' => 'Predis\Command\ZSetRank',
6604
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
6605
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
6606
+
6607
+ /* commands operating on hashes */
6608
+ 'HSET' => 'Predis\Command\HashSet',
6609
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
6610
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
6611
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
6612
+ 'HGET' => 'Predis\Command\HashGet',
6613
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
6614
+ 'HDEL' => 'Predis\Command\HashDelete',
6615
+ 'HEXISTS' => 'Predis\Command\HashExists',
6616
+ 'HLEN' => 'Predis\Command\HashLength',
6617
+ 'HKEYS' => 'Predis\Command\HashKeys',
6618
+ 'HVALS' => 'Predis\Command\HashValues',
6619
+ 'HGETALL' => 'Predis\Command\HashGetAll',
6620
+
6621
+ /* transactions */
6622
+ 'MULTI' => 'Predis\Command\TransactionMulti',
6623
+ 'EXEC' => 'Predis\Command\TransactionExec',
6624
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
6625
+
6626
+ /* publish - subscribe */
6627
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
6628
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
6629
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
6630
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
6631
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
6632
+
6633
+ /* remote server control commands */
6634
+ 'CONFIG' => 'Predis\Command\ServerConfig',
6635
+
6636
+ /* ---------------- Redis 2.2 ---------------- */
6637
+
6638
+ /* commands operating on the key space */
6639
+ 'PERSIST' => 'Predis\Command\KeyPersist',
6640
+
6641
+ /* commands operating on string values */
6642
+ 'STRLEN' => 'Predis\Command\StringStrlen',
6643
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
6644
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
6645
+ 'SETBIT' => 'Predis\Command\StringSetBit',
6646
+ 'GETBIT' => 'Predis\Command\StringGetBit',
6647
+
6648
+ /* commands operating on lists */
6649
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
6650
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6651
+ 'LINSERT' => 'Predis\Command\ListInsert',
6652
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6653
+
6654
+ /* commands operating on sorted sets */
6655
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6656
+
6657
+ /* transactions */
6658
+ 'WATCH' => 'Predis\Command\TransactionWatch',
6659
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6660
+
6661
+ /* remote server control commands */
6662
+ 'OBJECT' => 'Predis\Command\ServerObject',
6663
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6664
+
6665
+ /* ---------------- Redis 2.4 ---------------- */
6666
+
6667
+ /* remote server control commands */
6668
+ 'CLIENT' => 'Predis\Command\ServerClient',
6669
+
6670
+ /* ---------------- Redis 2.6 ---------------- */
6671
+
6672
+ /* commands operating on the key space */
6673
+ 'PTTL' => 'Predis\Command\KeyPreciseTimeToLive',
6674
+ 'PEXPIRE' => 'Predis\Command\KeyPreciseExpire',
6675
+ 'PEXPIREAT' => 'Predis\Command\KeyPreciseExpireAt',
6676
+
6677
+ /* commands operating on string values */
6678
+ 'PSETEX' => 'Predis\Command\StringPreciseSetExpire',
6679
+ 'INCRBYFLOAT' => 'Predis\Command\StringIncrementByFloat',
6680
+ 'BITOP' => 'Predis\Command\StringBitOp',
6681
+ 'BITCOUNT' => 'Predis\Command\StringBitCount',
6682
+
6683
+ /* commands operating on hashes */
6684
+ 'HINCRBYFLOAT' => 'Predis\Command\HashIncrementByFloat',
6685
+
6686
+ /* scripting */
6687
+ 'EVAL' => 'Predis\Command\ServerEval',
6688
+ 'EVALSHA' => 'Predis\Command\ServerEvalSHA',
6689
+ 'SCRIPT' => 'Predis\Command\ServerScript',
6690
+
6691
+ /* remote server control commands */
6692
+ 'TIME' => 'Predis\Command\ServerTime',
6693
+ 'SENTINEL' => 'Predis\Command\ServerSentinel',
6694
+
6695
+ /* ---------------- Redis 2.8 ---------------- */
6696
+
6697
+ /* commands operating on the key space */
6698
+ 'SCAN' => 'Predis\Command\KeyScan',
6699
+
6700
+ /* commands operating on string values */
6701
+ 'BITPOS' => 'Predis\Command\StringBitPos',
6702
+
6703
+ /* commands operating on sets */
6704
+ 'SSCAN' => 'Predis\Command\SetScan',
6705
+
6706
+ /* commands operating on sorted sets */
6707
+ 'ZSCAN' => 'Predis\Command\ZSetScan',
6708
+ 'ZLEXCOUNT' => 'Predis\Command\ZSetLexCount',
6709
+ 'ZRANGEBYLEX' => 'Predis\Command\ZSetRangeByLex',
6710
+ 'ZREMRANGEBYLEX' => 'Predis\Command\ZSetRemoveRangeByLex',
6711
+
6712
+ /* commands operating on hashes */
6713
+ 'HSCAN' => 'Predis\Command\HashScan',
6714
+
6715
+ /* publish - subscribe */
6716
+ 'PUBSUB' => 'Predis\Command\PubSubPubsub',
6717
+
6718
+ /* commands operating on HyperLogLog */
6719
+ 'PFADD' => 'Predis\Command\HyperLogLogAdd',
6720
+ 'PFCOUNT' => 'Predis\Command\HyperLogLogCount',
6721
+ 'PFMERGE' => 'Predis\Command\HyperLogLogMerge',
6722
+
6723
+ /* remote server control commands */
6724
+ 'COMMAND' => 'Predis\Command\ServerCommand',
6725
+ );
6726
+ }
6727
+ }
6728
+
6729
+ /**
6730
+ * Server profile for Redis 2.4.
6731
+ *
6732
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6733
+ */
6734
+ class RedisVersion240 extends RedisProfile
6735
+ {
6736
+ /**
6737
+ * {@inheritdoc}
6738
+ */
6739
+ public function getVersion()
6740
+ {
6741
+ return '2.4';
6742
+ }
6743
+
6744
+ /**
6745
+ * {@inheritdoc}
6746
+ */
6747
+ public function getSupportedCommands()
6748
+ {
6749
+ return array(
6750
+ /* ---------------- Redis 1.2 ---------------- */
6751
+
6752
+ /* commands operating on the key space */
6753
+ 'EXISTS' => 'Predis\Command\KeyExists',
6754
+ 'DEL' => 'Predis\Command\KeyDelete',
6755
+ 'TYPE' => 'Predis\Command\KeyType',
6756
+ 'KEYS' => 'Predis\Command\KeyKeys',
6757
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6758
+ 'RENAME' => 'Predis\Command\KeyRename',
6759
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6760
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6761
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6762
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6763
+ 'MOVE' => 'Predis\Command\KeyMove',
6764
+ 'SORT' => 'Predis\Command\KeySort',
6765
+
6766
+ /* commands operating on string values */
6767
+ 'SET' => 'Predis\Command\StringSet',
6768
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6769
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6770
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6771
+ 'GET' => 'Predis\Command\StringGet',
6772
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6773
+ 'GETSET' => 'Predis\Command\StringGetSet',
6774
+ 'INCR' => 'Predis\Command\StringIncrement',
6775
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6776
+ 'DECR' => 'Predis\Command\StringDecrement',
6777
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6778
+
6779
+ /* commands operating on lists */
6780
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6781
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6782
+ 'LLEN' => 'Predis\Command\ListLength',
6783
+ 'LRANGE' => 'Predis\Command\ListRange',
6784
+ 'LTRIM' => 'Predis\Command\ListTrim',
6785
+ 'LINDEX' => 'Predis\Command\ListIndex',
6786
+ 'LSET' => 'Predis\Command\ListSet',
6787
+ 'LREM' => 'Predis\Command\ListRemove',
6788
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6789
+ 'RPOP' => 'Predis\Command\ListPopLast',
6790
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6791
+
6792
+ /* commands operating on sets */
6793
+ 'SADD' => 'Predis\Command\SetAdd',
6794
+ 'SREM' => 'Predis\Command\SetRemove',
6795
+ 'SPOP' => 'Predis\Command\SetPop',
6796
+ 'SMOVE' => 'Predis\Command\SetMove',
6797
+ 'SCARD' => 'Predis\Command\SetCardinality',
6798
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6799
+ 'SINTER' => 'Predis\Command\SetIntersection',
6800
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6801
+ 'SUNION' => 'Predis\Command\SetUnion',
6802
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6803
+ 'SDIFF' => 'Predis\Command\SetDifference',
6804
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
6805
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
6806
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
6807
+
6808
+ /* commands operating on sorted sets */
6809
+ 'ZADD' => 'Predis\Command\ZSetAdd',
6810
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
6811
+ 'ZREM' => 'Predis\Command\ZSetRemove',
6812
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
6813
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
6814
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
6815
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
6816
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
6817
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
6818
+
6819
+ /* connection related commands */
6820
+ 'PING' => 'Predis\Command\ConnectionPing',
6821
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
6822
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
6823
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
6824
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
6825
+
6826
+ /* remote server control commands */
6827
+ 'INFO' => 'Predis\Command\ServerInfo',
6828
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
6829
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
6830
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
6831
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
6832
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
6833
+ 'SAVE' => 'Predis\Command\ServerSave',
6834
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
6835
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
6836
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
6837
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
6838
+
6839
+ /* ---------------- Redis 2.0 ---------------- */
6840
+
6841
+ /* commands operating on string values */
6842
+ 'SETEX' => 'Predis\Command\StringSetExpire',
6843
+ 'APPEND' => 'Predis\Command\StringAppend',
6844
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
6845
+
6846
+ /* commands operating on lists */
6847
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
6848
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
6849
+
6850
+ /* commands operating on sorted sets */
6851
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
6852
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
6853
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
6854
+ 'ZRANK' => 'Predis\Command\ZSetRank',
6855
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
6856
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
6857
+
6858
+ /* commands operating on hashes */
6859
+ 'HSET' => 'Predis\Command\HashSet',
6860
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
6861
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
6862
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
6863
+ 'HGET' => 'Predis\Command\HashGet',
6864
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
6865
+ 'HDEL' => 'Predis\Command\HashDelete',
6866
+ 'HEXISTS' => 'Predis\Command\HashExists',
6867
+ 'HLEN' => 'Predis\Command\HashLength',
6868
+ 'HKEYS' => 'Predis\Command\HashKeys',
6869
+ 'HVALS' => 'Predis\Command\HashValues',
6870
+ 'HGETALL' => 'Predis\Command\HashGetAll',
6871
+
6872
+ /* transactions */
6873
+ 'MULTI' => 'Predis\Command\TransactionMulti',
6874
+ 'EXEC' => 'Predis\Command\TransactionExec',
6875
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
6876
+
6877
+ /* publish - subscribe */
6878
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
6879
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
6880
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
6881
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
6882
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
6883
+
6884
+ /* remote server control commands */
6885
+ 'CONFIG' => 'Predis\Command\ServerConfig',
6886
+
6887
+ /* ---------------- Redis 2.2 ---------------- */
6888
+
6889
+ /* commands operating on the key space */
6890
+ 'PERSIST' => 'Predis\Command\KeyPersist',
6891
+
6892
+ /* commands operating on string values */
6893
+ 'STRLEN' => 'Predis\Command\StringStrlen',
6894
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
6895
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
6896
+ 'SETBIT' => 'Predis\Command\StringSetBit',
6897
+ 'GETBIT' => 'Predis\Command\StringGetBit',
6898
+
6899
+ /* commands operating on lists */
6900
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
6901
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
6902
+ 'LINSERT' => 'Predis\Command\ListInsert',
6903
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
6904
+
6905
+ /* commands operating on sorted sets */
6906
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
6907
+
6908
+ /* transactions */
6909
+ 'WATCH' => 'Predis\Command\TransactionWatch',
6910
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
6911
+
6912
+ /* remote server control commands */
6913
+ 'OBJECT' => 'Predis\Command\ServerObject',
6914
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
6915
+
6916
+ /* ---------------- Redis 2.4 ---------------- */
6917
+
6918
+ /* remote server control commands */
6919
+ 'CLIENT' => 'Predis\Command\ServerClient',
6920
+ );
6921
+ }
6922
+ }
6923
+
6924
+ /**
6925
+ * Server profile for Redis 2.0.
6926
+ *
6927
+ * @author Daniele Alessandri <suppakilla@gmail.com>
6928
+ */
6929
+ class RedisVersion200 extends RedisProfile
6930
+ {
6931
+ /**
6932
+ * {@inheritdoc}
6933
+ */
6934
+ public function getVersion()
6935
+ {
6936
+ return '2.0';
6937
+ }
6938
+
6939
+ /**
6940
+ * {@inheritdoc}
6941
+ */
6942
+ public function getSupportedCommands()
6943
+ {
6944
+ return array(
6945
+ /* ---------------- Redis 1.2 ---------------- */
6946
+
6947
+ /* commands operating on the key space */
6948
+ 'EXISTS' => 'Predis\Command\KeyExists',
6949
+ 'DEL' => 'Predis\Command\KeyDelete',
6950
+ 'TYPE' => 'Predis\Command\KeyType',
6951
+ 'KEYS' => 'Predis\Command\KeyKeys',
6952
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
6953
+ 'RENAME' => 'Predis\Command\KeyRename',
6954
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
6955
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
6956
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
6957
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
6958
+ 'MOVE' => 'Predis\Command\KeyMove',
6959
+ 'SORT' => 'Predis\Command\KeySort',
6960
+
6961
+ /* commands operating on string values */
6962
+ 'SET' => 'Predis\Command\StringSet',
6963
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
6964
+ 'MSET' => 'Predis\Command\StringSetMultiple',
6965
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
6966
+ 'GET' => 'Predis\Command\StringGet',
6967
+ 'MGET' => 'Predis\Command\StringGetMultiple',
6968
+ 'GETSET' => 'Predis\Command\StringGetSet',
6969
+ 'INCR' => 'Predis\Command\StringIncrement',
6970
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
6971
+ 'DECR' => 'Predis\Command\StringDecrement',
6972
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
6973
+
6974
+ /* commands operating on lists */
6975
+ 'RPUSH' => 'Predis\Command\ListPushTail',
6976
+ 'LPUSH' => 'Predis\Command\ListPushHead',
6977
+ 'LLEN' => 'Predis\Command\ListLength',
6978
+ 'LRANGE' => 'Predis\Command\ListRange',
6979
+ 'LTRIM' => 'Predis\Command\ListTrim',
6980
+ 'LINDEX' => 'Predis\Command\ListIndex',
6981
+ 'LSET' => 'Predis\Command\ListSet',
6982
+ 'LREM' => 'Predis\Command\ListRemove',
6983
+ 'LPOP' => 'Predis\Command\ListPopFirst',
6984
+ 'RPOP' => 'Predis\Command\ListPopLast',
6985
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
6986
+
6987
+ /* commands operating on sets */
6988
+ 'SADD' => 'Predis\Command\SetAdd',
6989
+ 'SREM' => 'Predis\Command\SetRemove',
6990
+ 'SPOP' => 'Predis\Command\SetPop',
6991
+ 'SMOVE' => 'Predis\Command\SetMove',
6992
+ 'SCARD' => 'Predis\Command\SetCardinality',
6993
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
6994
+ 'SINTER' => 'Predis\Command\SetIntersection',
6995
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
6996
+ 'SUNION' => 'Predis\Command\SetUnion',
6997
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
6998
+ 'SDIFF' => 'Predis\Command\SetDifference',
6999
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
7000
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
7001
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
7002
+
7003
+ /* commands operating on sorted sets */
7004
+ 'ZADD' => 'Predis\Command\ZSetAdd',
7005
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
7006
+ 'ZREM' => 'Predis\Command\ZSetRemove',
7007
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
7008
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
7009
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
7010
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
7011
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
7012
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
7013
+
7014
+ /* connection related commands */
7015
+ 'PING' => 'Predis\Command\ConnectionPing',
7016
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
7017
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
7018
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
7019
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
7020
+
7021
+ /* remote server control commands */
7022
+ 'INFO' => 'Predis\Command\ServerInfo',
7023
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7024
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
7025
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
7026
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
7027
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
7028
+ 'SAVE' => 'Predis\Command\ServerSave',
7029
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
7030
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
7031
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
7032
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
7033
+
7034
+ /* ---------------- Redis 2.0 ---------------- */
7035
+
7036
+ /* commands operating on string values */
7037
+ 'SETEX' => 'Predis\Command\StringSetExpire',
7038
+ 'APPEND' => 'Predis\Command\StringAppend',
7039
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
7040
+
7041
+ /* commands operating on lists */
7042
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
7043
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
7044
+
7045
+ /* commands operating on sorted sets */
7046
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
7047
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
7048
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
7049
+ 'ZRANK' => 'Predis\Command\ZSetRank',
7050
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
7051
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
7052
+
7053
+ /* commands operating on hashes */
7054
+ 'HSET' => 'Predis\Command\HashSet',
7055
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
7056
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
7057
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
7058
+ 'HGET' => 'Predis\Command\HashGet',
7059
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
7060
+ 'HDEL' => 'Predis\Command\HashDelete',
7061
+ 'HEXISTS' => 'Predis\Command\HashExists',
7062
+ 'HLEN' => 'Predis\Command\HashLength',
7063
+ 'HKEYS' => 'Predis\Command\HashKeys',
7064
+ 'HVALS' => 'Predis\Command\HashValues',
7065
+ 'HGETALL' => 'Predis\Command\HashGetAll',
7066
+
7067
+ /* transactions */
7068
+ 'MULTI' => 'Predis\Command\TransactionMulti',
7069
+ 'EXEC' => 'Predis\Command\TransactionExec',
7070
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
7071
+
7072
+ /* publish - subscribe */
7073
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
7074
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
7075
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
7076
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
7077
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
7078
+
7079
+ /* remote server control commands */
7080
+ 'CONFIG' => 'Predis\Command\ServerConfig',
7081
+ );
7082
+ }
7083
+ }
7084
+
7085
+ /**
7086
+ * Server profile for the current unstable version of Redis.
7087
+ *
7088
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7089
+ */
7090
+ class RedisUnstable extends RedisVersion300
7091
+ {
7092
+ /**
7093
+ * {@inheritdoc}
7094
+ */
7095
+ public function getVersion()
7096
+ {
7097
+ return '3.0';
7098
+ }
7099
+
7100
+ /**
7101
+ * {@inheritdoc}
7102
+ */
7103
+ public function getSupportedCommands()
7104
+ {
7105
+ return array_merge(parent::getSupportedCommands(), array());
7106
+ }
7107
+ }
7108
+
7109
+ /**
7110
+ * Factory class for creating profile instances from strings.
7111
+ *
7112
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7113
+ */
7114
+ final class Factory
7115
+ {
7116
+ private static $profiles = array(
7117
+ '2.0' => 'Predis\Profile\RedisVersion200',
7118
+ '2.2' => 'Predis\Profile\RedisVersion220',
7119
+ '2.4' => 'Predis\Profile\RedisVersion240',
7120
+ '2.6' => 'Predis\Profile\RedisVersion260',
7121
+ '2.8' => 'Predis\Profile\RedisVersion280',
7122
+ '3.0' => 'Predis\Profile\RedisVersion300',
7123
+ 'default' => 'Predis\Profile\RedisVersion300',
7124
+ 'dev' => 'Predis\Profile\RedisUnstable',
7125
+ );
7126
+
7127
+ /**
7128
+ *
7129
+ */
7130
+ private function __construct()
7131
+ {
7132
+ // NOOP
7133
+ }
7134
+
7135
+ /**
7136
+ * Returns the default server profile.
7137
+ *
7138
+ * @return ProfileInterface
7139
+ */
7140
+ public static function getDefault()
7141
+ {
7142
+ return self::get('default');
7143
+ }
7144
+
7145
+ /**
7146
+ * Returns the development server profile.
7147
+ *
7148
+ * @return ProfileInterface
7149
+ */
7150
+ public static function getDevelopment()
7151
+ {
7152
+ return self::get('dev');
7153
+ }
7154
+
7155
+ /**
7156
+ * Registers a new server profile.
7157
+ *
7158
+ * @param string $alias Profile version or alias.
7159
+ * @param string $class FQN of a class implementing Predis\Profile\ProfileInterface.
7160
+ *
7161
+ * @throws \InvalidArgumentException
7162
+ */
7163
+ public static function define($alias, $class)
7164
+ {
7165
+ $reflection = new ReflectionClass($class);
7166
+
7167
+ if (!$reflection->isSubclassOf('Predis\Profile\ProfileInterface')) {
7168
+ throw new InvalidArgumentException("The class '$class' is not a valid profile class.");
7169
+ }
7170
+
7171
+ self::$profiles[$alias] = $class;
7172
+ }
7173
+
7174
+ /**
7175
+ * Returns the specified server profile.
7176
+ *
7177
+ * @param string $version Profile version or alias.
7178
+ *
7179
+ * @return ProfileInterface
7180
+ *
7181
+ * @throws ClientException
7182
+ */
7183
+ public static function get($version)
7184
+ {
7185
+ if (!isset(self::$profiles[$version])) {
7186
+ throw new ClientException("Unknown server profile: '$version'.");
7187
+ }
7188
+
7189
+ $profile = self::$profiles[$version];
7190
+
7191
+ return new $profile();
7192
+ }
7193
+ }
7194
+
7195
+ /**
7196
+ * Server profile for Redis 2.2.
7197
+ *
7198
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7199
+ */
7200
+ class RedisVersion220 extends RedisProfile
7201
+ {
7202
+ /**
7203
+ * {@inheritdoc}
7204
+ */
7205
+ public function getVersion()
7206
+ {
7207
+ return '2.2';
7208
+ }
7209
+
7210
+ /**
7211
+ * {@inheritdoc}
7212
+ */
7213
+ public function getSupportedCommands()
7214
+ {
7215
+ return array(
7216
+ /* ---------------- Redis 1.2 ---------------- */
7217
+
7218
+ /* commands operating on the key space */
7219
+ 'EXISTS' => 'Predis\Command\KeyExists',
7220
+ 'DEL' => 'Predis\Command\KeyDelete',
7221
+ 'TYPE' => 'Predis\Command\KeyType',
7222
+ 'KEYS' => 'Predis\Command\KeyKeys',
7223
+ 'RANDOMKEY' => 'Predis\Command\KeyRandom',
7224
+ 'RENAME' => 'Predis\Command\KeyRename',
7225
+ 'RENAMENX' => 'Predis\Command\KeyRenamePreserve',
7226
+ 'EXPIRE' => 'Predis\Command\KeyExpire',
7227
+ 'EXPIREAT' => 'Predis\Command\KeyExpireAt',
7228
+ 'TTL' => 'Predis\Command\KeyTimeToLive',
7229
+ 'MOVE' => 'Predis\Command\KeyMove',
7230
+ 'SORT' => 'Predis\Command\KeySort',
7231
+
7232
+ /* commands operating on string values */
7233
+ 'SET' => 'Predis\Command\StringSet',
7234
+ 'SETNX' => 'Predis\Command\StringSetPreserve',
7235
+ 'MSET' => 'Predis\Command\StringSetMultiple',
7236
+ 'MSETNX' => 'Predis\Command\StringSetMultiplePreserve',
7237
+ 'GET' => 'Predis\Command\StringGet',
7238
+ 'MGET' => 'Predis\Command\StringGetMultiple',
7239
+ 'GETSET' => 'Predis\Command\StringGetSet',
7240
+ 'INCR' => 'Predis\Command\StringIncrement',
7241
+ 'INCRBY' => 'Predis\Command\StringIncrementBy',
7242
+ 'DECR' => 'Predis\Command\StringDecrement',
7243
+ 'DECRBY' => 'Predis\Command\StringDecrementBy',
7244
+
7245
+ /* commands operating on lists */
7246
+ 'RPUSH' => 'Predis\Command\ListPushTail',
7247
+ 'LPUSH' => 'Predis\Command\ListPushHead',
7248
+ 'LLEN' => 'Predis\Command\ListLength',
7249
+ 'LRANGE' => 'Predis\Command\ListRange',
7250
+ 'LTRIM' => 'Predis\Command\ListTrim',
7251
+ 'LINDEX' => 'Predis\Command\ListIndex',
7252
+ 'LSET' => 'Predis\Command\ListSet',
7253
+ 'LREM' => 'Predis\Command\ListRemove',
7254
+ 'LPOP' => 'Predis\Command\ListPopFirst',
7255
+ 'RPOP' => 'Predis\Command\ListPopLast',
7256
+ 'RPOPLPUSH' => 'Predis\Command\ListPopLastPushHead',
7257
+
7258
+ /* commands operating on sets */
7259
+ 'SADD' => 'Predis\Command\SetAdd',
7260
+ 'SREM' => 'Predis\Command\SetRemove',
7261
+ 'SPOP' => 'Predis\Command\SetPop',
7262
+ 'SMOVE' => 'Predis\Command\SetMove',
7263
+ 'SCARD' => 'Predis\Command\SetCardinality',
7264
+ 'SISMEMBER' => 'Predis\Command\SetIsMember',
7265
+ 'SINTER' => 'Predis\Command\SetIntersection',
7266
+ 'SINTERSTORE' => 'Predis\Command\SetIntersectionStore',
7267
+ 'SUNION' => 'Predis\Command\SetUnion',
7268
+ 'SUNIONSTORE' => 'Predis\Command\SetUnionStore',
7269
+ 'SDIFF' => 'Predis\Command\SetDifference',
7270
+ 'SDIFFSTORE' => 'Predis\Command\SetDifferenceStore',
7271
+ 'SMEMBERS' => 'Predis\Command\SetMembers',
7272
+ 'SRANDMEMBER' => 'Predis\Command\SetRandomMember',
7273
+
7274
+ /* commands operating on sorted sets */
7275
+ 'ZADD' => 'Predis\Command\ZSetAdd',
7276
+ 'ZINCRBY' => 'Predis\Command\ZSetIncrementBy',
7277
+ 'ZREM' => 'Predis\Command\ZSetRemove',
7278
+ 'ZRANGE' => 'Predis\Command\ZSetRange',
7279
+ 'ZREVRANGE' => 'Predis\Command\ZSetReverseRange',
7280
+ 'ZRANGEBYSCORE' => 'Predis\Command\ZSetRangeByScore',
7281
+ 'ZCARD' => 'Predis\Command\ZSetCardinality',
7282
+ 'ZSCORE' => 'Predis\Command\ZSetScore',
7283
+ 'ZREMRANGEBYSCORE' => 'Predis\Command\ZSetRemoveRangeByScore',
7284
+
7285
+ /* connection related commands */
7286
+ 'PING' => 'Predis\Command\ConnectionPing',
7287
+ 'AUTH' => 'Predis\Command\ConnectionAuth',
7288
+ 'SELECT' => 'Predis\Command\ConnectionSelect',
7289
+ 'ECHO' => 'Predis\Command\ConnectionEcho',
7290
+ 'QUIT' => 'Predis\Command\ConnectionQuit',
7291
+
7292
+ /* remote server control commands */
7293
+ 'INFO' => 'Predis\Command\ServerInfo',
7294
+ 'SLAVEOF' => 'Predis\Command\ServerSlaveOf',
7295
+ 'MONITOR' => 'Predis\Command\ServerMonitor',
7296
+ 'DBSIZE' => 'Predis\Command\ServerDatabaseSize',
7297
+ 'FLUSHDB' => 'Predis\Command\ServerFlushDatabase',
7298
+ 'FLUSHALL' => 'Predis\Command\ServerFlushAll',
7299
+ 'SAVE' => 'Predis\Command\ServerSave',
7300
+ 'BGSAVE' => 'Predis\Command\ServerBackgroundSave',
7301
+ 'LASTSAVE' => 'Predis\Command\ServerLastSave',
7302
+ 'SHUTDOWN' => 'Predis\Command\ServerShutdown',
7303
+ 'BGREWRITEAOF' => 'Predis\Command\ServerBackgroundRewriteAOF',
7304
+
7305
+ /* ---------------- Redis 2.0 ---------------- */
7306
+
7307
+ /* commands operating on string values */
7308
+ 'SETEX' => 'Predis\Command\StringSetExpire',
7309
+ 'APPEND' => 'Predis\Command\StringAppend',
7310
+ 'SUBSTR' => 'Predis\Command\StringSubstr',
7311
+
7312
+ /* commands operating on lists */
7313
+ 'BLPOP' => 'Predis\Command\ListPopFirstBlocking',
7314
+ 'BRPOP' => 'Predis\Command\ListPopLastBlocking',
7315
+
7316
+ /* commands operating on sorted sets */
7317
+ 'ZUNIONSTORE' => 'Predis\Command\ZSetUnionStore',
7318
+ 'ZINTERSTORE' => 'Predis\Command\ZSetIntersectionStore',
7319
+ 'ZCOUNT' => 'Predis\Command\ZSetCount',
7320
+ 'ZRANK' => 'Predis\Command\ZSetRank',
7321
+ 'ZREVRANK' => 'Predis\Command\ZSetReverseRank',
7322
+ 'ZREMRANGEBYRANK' => 'Predis\Command\ZSetRemoveRangeByRank',
7323
+
7324
+ /* commands operating on hashes */
7325
+ 'HSET' => 'Predis\Command\HashSet',
7326
+ 'HSETNX' => 'Predis\Command\HashSetPreserve',
7327
+ 'HMSET' => 'Predis\Command\HashSetMultiple',
7328
+ 'HINCRBY' => 'Predis\Command\HashIncrementBy',
7329
+ 'HGET' => 'Predis\Command\HashGet',
7330
+ 'HMGET' => 'Predis\Command\HashGetMultiple',
7331
+ 'HDEL' => 'Predis\Command\HashDelete',
7332
+ 'HEXISTS' => 'Predis\Command\HashExists',
7333
+ 'HLEN' => 'Predis\Command\HashLength',
7334
+ 'HKEYS' => 'Predis\Command\HashKeys',
7335
+ 'HVALS' => 'Predis\Command\HashValues',
7336
+ 'HGETALL' => 'Predis\Command\HashGetAll',
7337
+
7338
+ /* transactions */
7339
+ 'MULTI' => 'Predis\Command\TransactionMulti',
7340
+ 'EXEC' => 'Predis\Command\TransactionExec',
7341
+ 'DISCARD' => 'Predis\Command\TransactionDiscard',
7342
+
7343
+ /* publish - subscribe */
7344
+ 'SUBSCRIBE' => 'Predis\Command\PubSubSubscribe',
7345
+ 'UNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribe',
7346
+ 'PSUBSCRIBE' => 'Predis\Command\PubSubSubscribeByPattern',
7347
+ 'PUNSUBSCRIBE' => 'Predis\Command\PubSubUnsubscribeByPattern',
7348
+ 'PUBLISH' => 'Predis\Command\PubSubPublish',
7349
+
7350
+ /* remote server control commands */
7351
+ 'CONFIG' => 'Predis\Command\ServerConfig',
7352
+
7353
+ /* ---------------- Redis 2.2 ---------------- */
7354
+
7355
+ /* commands operating on the key space */
7356
+ 'PERSIST' => 'Predis\Command\KeyPersist',
7357
+
7358
+ /* commands operating on string values */
7359
+ 'STRLEN' => 'Predis\Command\StringStrlen',
7360
+ 'SETRANGE' => 'Predis\Command\StringSetRange',
7361
+ 'GETRANGE' => 'Predis\Command\StringGetRange',
7362
+ 'SETBIT' => 'Predis\Command\StringSetBit',
7363
+ 'GETBIT' => 'Predis\Command\StringGetBit',
7364
+
7365
+ /* commands operating on lists */
7366
+ 'RPUSHX' => 'Predis\Command\ListPushTailX',
7367
+ 'LPUSHX' => 'Predis\Command\ListPushHeadX',
7368
+ 'LINSERT' => 'Predis\Command\ListInsert',
7369
+ 'BRPOPLPUSH' => 'Predis\Command\ListPopLastPushHeadBlocking',
7370
+
7371
+ /* commands operating on sorted sets */
7372
+ 'ZREVRANGEBYSCORE' => 'Predis\Command\ZSetReverseRangeByScore',
7373
+
7374
+ /* transactions */
7375
+ 'WATCH' => 'Predis\Command\TransactionWatch',
7376
+ 'UNWATCH' => 'Predis\Command\TransactionUnwatch',
7377
+
7378
+ /* remote server control commands */
7379
+ 'OBJECT' => 'Predis\Command\ServerObject',
7380
+ 'SLOWLOG' => 'Predis\Command\ServerSlowlog',
7381
+ );
7382
+ }
7383
+ }
7384
+
7385
+ /* --------------------------------------------------------------------------- */
7386
+
7387
+ namespace Predis;
7388
+
7389
+ use InvalidArgumentException;
7390
+ use UnexpectedValueException;
7391
+ use Predis\Command\CommandInterface;
7392
+ use Predis\Command\RawCommand;
7393
+ use Predis\Command\ScriptCommand;
7394
+ use Predis\Configuration\Options;
7395
+ use Predis\Configuration\OptionsInterface;
7396
+ use Predis\Connection\ConnectionInterface;
7397
+ use Predis\Connection\AggregateConnectionInterface;
7398
+ use Predis\Connection\ParametersInterface;
7399
+ use Predis\Monitor\Consumer as MonitorConsumer;
7400
+ use Predis\Pipeline\Pipeline;
7401
+ use Predis\PubSub\Consumer as PubSubConsumer;
7402
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
7403
+ use Predis\Response\ResponseInterface;
7404
+ use Predis\Response\ServerException;
7405
+ use Predis\Transaction\MultiExec as MultiExecTransaction;
7406
+ use Predis\Profile\ProfileInterface;
7407
+ use Exception;
7408
+ use Predis\Connection\NodeConnectionInterface;
7409
+
7410
+ /**
7411
+ * Base exception class for Predis-related errors.
7412
+ *
7413
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7414
+ */
7415
+ abstract class PredisException extends Exception
7416
+ {
7417
+ }
7418
+
7419
+ /**
7420
+ * Interface defining a client-side context such as a pipeline or transaction.
7421
+ *
7422
+ * @method $this del(array $keys)
7423
+ * @method $this dump($key)
7424
+ * @method $this exists($key)
7425
+ * @method $this expire($key, $seconds)
7426
+ * @method $this expireat($key, $timestamp)
7427
+ * @method $this keys($pattern)
7428
+ * @method $this move($key, $db)
7429
+ * @method $this object($subcommand, $key)
7430
+ * @method $this persist($key)
7431
+ * @method $this pexpire($key, $milliseconds)
7432
+ * @method $this pexpireat($key, $timestamp)
7433
+ * @method $this pttl($key)
7434
+ * @method $this randomkey()
7435
+ * @method $this rename($key, $target)
7436
+ * @method $this renamenx($key, $target)
7437
+ * @method $this scan($cursor, array $options = null)
7438
+ * @method $this sort($key, array $options = null)
7439
+ * @method $this ttl($key)
7440
+ * @method $this type($key)
7441
+ * @method $this append($key, $value)
7442
+ * @method $this bitcount($key, $start = null, $end = null)
7443
+ * @method $this bitop($operation, $destkey, $key)
7444
+ * @method $this decr($key)
7445
+ * @method $this decrby($key, $decrement)
7446
+ * @method $this get($key)
7447
+ * @method $this getbit($key, $offset)
7448
+ * @method $this getrange($key, $start, $end)
7449
+ * @method $this getset($key, $value)
7450
+ * @method $this incr($key)
7451
+ * @method $this incrby($key, $increment)
7452
+ * @method $this incrbyfloat($key, $increment)
7453
+ * @method $this mget(array $keys)
7454
+ * @method $this mset(array $dictionary)
7455
+ * @method $this msetnx(array $dictionary)
7456
+ * @method $this psetex($key, $milliseconds, $value)
7457
+ * @method $this set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
7458
+ * @method $this setbit($key, $offset, $value)
7459
+ * @method $this setex($key, $seconds, $value)
7460
+ * @method $this setnx($key, $value)
7461
+ * @method $this setrange($key, $offset, $value)
7462
+ * @method $this strlen($key)
7463
+ * @method $this hdel($key, array $fields)
7464
+ * @method $this hexists($key, $field)
7465
+ * @method $this hget($key, $field)
7466
+ * @method $this hgetall($key)
7467
+ * @method $this hincrby($key, $field, $increment)
7468
+ * @method $this hincrbyfloat($key, $field, $increment)
7469
+ * @method $this hkeys($key)
7470
+ * @method $this hlen($key)
7471
+ * @method $this hmget($key, array $fields)
7472
+ * @method $this hmset($key, array $dictionary)
7473
+ * @method $this hscan($key, $cursor, array $options = null)
7474
+ * @method $this hset($key, $field, $value)
7475
+ * @method $this hsetnx($key, $field, $value)
7476
+ * @method $this hvals($key)
7477
+ * @method $this blpop(array $keys, $timeout)
7478
+ * @method $this brpop(array $keys, $timeout)
7479
+ * @method $this brpoplpush($source, $destination, $timeout)
7480
+ * @method $this lindex($key, $index)
7481
+ * @method $this linsert($key, $whence, $pivot, $value)
7482
+ * @method $this llen($key)
7483
+ * @method $this lpop($key)
7484
+ * @method $this lpush($key, array $values)
7485
+ * @method $this lpushx($key, $value)
7486
+ * @method $this lrange($key, $start, $stop)
7487
+ * @method $this lrem($key, $count, $value)
7488
+ * @method $this lset($key, $index, $value)
7489
+ * @method $this ltrim($key, $start, $stop)
7490
+ * @method $this rpop($key)
7491
+ * @method $this rpoplpush($source, $destination)
7492
+ * @method $this rpush($key, array $values)
7493
+ * @method $this rpushx($key, $value)
7494
+ * @method $this sadd($key, array $members)
7495
+ * @method $this scard($key)
7496
+ * @method $this sdiff(array $keys)
7497
+ * @method $this sdiffstore($destination, array $keys)
7498
+ * @method $this sinter(array $keys)
7499
+ * @method $this sinterstore($destination, array $keys)
7500
+ * @method $this sismember($key, $member)
7501
+ * @method $this smembers($key)
7502
+ * @method $this smove($source, $destination, $member)
7503
+ * @method $this spop($key)
7504
+ * @method $this srandmember($key, $count = null)
7505
+ * @method $this srem($key, $member)
7506
+ * @method $this sscan($key, $cursor, array $options = null)
7507
+ * @method $this sunion(array $keys)
7508
+ * @method $this sunionstore($destination, array $keys)
7509
+ * @method $this zadd($key, array $membersAndScoresDictionary)
7510
+ * @method $this zcard($key)
7511
+ * @method $this zcount($key, $min, $max)
7512
+ * @method $this zincrby($key, $increment, $member)
7513
+ * @method $this zinterstore($destination, array $keys, array $options = null)
7514
+ * @method $this zrange($key, $start, $stop, array $options = null)
7515
+ * @method $this zrangebyscore($key, $min, $max, array $options = null)
7516
+ * @method $this zrank($key, $member)
7517
+ * @method $this zrem($key, $member)
7518
+ * @method $this zremrangebyrank($key, $start, $stop)
7519
+ * @method $this zremrangebyscore($key, $min, $max)
7520
+ * @method $this zrevrange($key, $start, $stop, array $options = null)
7521
+ * @method $this zrevrangebyscore($key, $min, $max, array $options = null)
7522
+ * @method $this zrevrank($key, $member)
7523
+ * @method $this zunionstore($destination, array $keys, array $options = null)
7524
+ * @method $this zscore($key, $member)
7525
+ * @method $this zscan($key, $cursor, array $options = null)
7526
+ * @method $this zrangebylex($key, $start, $stop, array $options = null)
7527
+ * @method $this zremrangebylex($key, $min, $max)
7528
+ * @method $this zlexcount($key, $min, $max)
7529
+ * @method $this pfadd($key, array $elements)
7530
+ * @method $this pfmerge($destinationKey, array $sourceKeys)
7531
+ * @method $this pfcount(array $keys)
7532
+ * @method $this pubsub($subcommand, $argument)
7533
+ * @method $this publish($channel, $message)
7534
+ * @method $this discard()
7535
+ * @method $this exec()
7536
+ * @method $this multi()
7537
+ * @method $this unwatch()
7538
+ * @method $this watch($key)
7539
+ * @method $this eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7540
+ * @method $this evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7541
+ * @method $this script($subcommand, $argument = null)
7542
+ * @method $this auth($password)
7543
+ * @method $this echo($message)
7544
+ * @method $this ping($message = null)
7545
+ * @method $this select($database)
7546
+ * @method $this bgrewriteaof()
7547
+ * @method $this bgsave()
7548
+ * @method $this client($subcommand, $argument = null)
7549
+ * @method $this config($subcommand, $argument = null)
7550
+ * @method $this dbsize()
7551
+ * @method $this flushall()
7552
+ * @method $this flushdb()
7553
+ * @method $this info($section = null)
7554
+ * @method $this lastsave()
7555
+ * @method $this save()
7556
+ * @method $this slaveof($host, $port)
7557
+ * @method $this slowlog($subcommand, $argument = null)
7558
+ * @method $this time()
7559
+ * @method $this command()
7560
+ *
7561
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7562
+ */
7563
+ interface ClientContextInterface
7564
+ {
7565
+
7566
+ /**
7567
+ * Sends the specified command instance to Redis.
7568
+ *
7569
+ * @param CommandInterface $command Command instance.
7570
+ *
7571
+ * @return mixed
7572
+ */
7573
+ public function executeCommand(CommandInterface $command);
7574
+
7575
+ /**
7576
+ * Sends the specified command with its arguments to Redis.
7577
+ *
7578
+ * @param string $method Command ID.
7579
+ * @param array $arguments Arguments for the command.
7580
+ *
7581
+ * @return mixed
7582
+ */
7583
+ public function __call($method, $arguments);
7584
+
7585
+ /**
7586
+ * Starts the execution of the context.
7587
+ *
7588
+ * @param mixed $callable Optional callback for execution.
7589
+ *
7590
+ * @return array
7591
+ */
7592
+ public function execute($callable = null);
7593
+ }
7594
+
7595
+ /**
7596
+ * Base exception class for network-related errors.
7597
+ *
7598
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7599
+ */
7600
+ abstract class CommunicationException extends PredisException
7601
+ {
7602
+ private $connection;
7603
+
7604
+ /**
7605
+ * @param NodeConnectionInterface $connection Connection that generated the exception.
7606
+ * @param string $message Error message.
7607
+ * @param int $code Error code.
7608
+ * @param Exception $innerException Inner exception for wrapping the original error.
7609
+ */
7610
+ public function __construct(
7611
+ NodeConnectionInterface $connection,
7612
+ $message = null,
7613
+ $code = null,
7614
+ Exception $innerException = null
7615
+ ) {
7616
+ parent::__construct($message, $code, $innerException);
7617
+ $this->connection = $connection;
7618
+ }
7619
+
7620
+ /**
7621
+ * Gets the connection that generated the exception.
7622
+ *
7623
+ * @return NodeConnectionInterface
7624
+ */
7625
+ public function getConnection()
7626
+ {
7627
+ return $this->connection;
7628
+ }
7629
+
7630
+ /**
7631
+ * Indicates if the receiver should reset the underlying connection.
7632
+ *
7633
+ * @return bool
7634
+ */
7635
+ public function shouldResetConnection()
7636
+ {
7637
+ return true;
7638
+ }
7639
+
7640
+ /**
7641
+ * Helper method to handle exceptions generated by a connection object.
7642
+ *
7643
+ * @param CommunicationException $exception Exception.
7644
+ *
7645
+ * @throws CommunicationException
7646
+ */
7647
+ public static function handle(CommunicationException $exception)
7648
+ {
7649
+ if ($exception->shouldResetConnection()) {
7650
+ $connection = $exception->getConnection();
7651
+
7652
+ if ($connection->isConnected()) {
7653
+ $connection->disconnect();
7654
+ }
7655
+ }
7656
+
7657
+ throw $exception;
7658
+ }
7659
+ }
7660
+
7661
+ /**
7662
+ * Interface defining a client able to execute commands against Redis.
7663
+ *
7664
+ * All the commands exposed by the client generally have the same signature as
7665
+ * described by the Redis documentation, but some of them offer an additional
7666
+ * and more friendly interface to ease programming which is described in the
7667
+ * following list of methods:
7668
+ *
7669
+ * @method int del(array $keys)
7670
+ * @method string dump($key)
7671
+ * @method int exists($key)
7672
+ * @method int expire($key, $seconds)
7673
+ * @method int expireat($key, $timestamp)
7674
+ * @method array keys($pattern)
7675
+ * @method int move($key, $db)
7676
+ * @method mixed object($subcommand, $key)
7677
+ * @method int persist($key)
7678
+ * @method int pexpire($key, $milliseconds)
7679
+ * @method int pexpireat($key, $timestamp)
7680
+ * @method int pttl($key)
7681
+ * @method string randomkey()
7682
+ * @method mixed rename($key, $target)
7683
+ * @method int renamenx($key, $target)
7684
+ * @method array scan($cursor, array $options = null)
7685
+ * @method array sort($key, array $options = null)
7686
+ * @method int ttl($key)
7687
+ * @method mixed type($key)
7688
+ * @method int append($key, $value)
7689
+ * @method int bitcount($key, $start = null, $end = null)
7690
+ * @method int bitop($operation, $destkey, $key)
7691
+ * @method int decr($key)
7692
+ * @method int decrby($key, $decrement)
7693
+ * @method string get($key)
7694
+ * @method int getbit($key, $offset)
7695
+ * @method string getrange($key, $start, $end)
7696
+ * @method string getset($key, $value)
7697
+ * @method int incr($key)
7698
+ * @method int incrby($key, $increment)
7699
+ * @method string incrbyfloat($key, $increment)
7700
+ * @method array mget(array $keys)
7701
+ * @method mixed mset(array $dictionary)
7702
+ * @method int msetnx(array $dictionary)
7703
+ * @method mixed psetex($key, $milliseconds, $value)
7704
+ * @method mixed set($key, $value, $expireResolution = null, $expireTTL = null, $flag = null)
7705
+ * @method int setbit($key, $offset, $value)
7706
+ * @method int setex($key, $seconds, $value)
7707
+ * @method int setnx($key, $value)
7708
+ * @method int setrange($key, $offset, $value)
7709
+ * @method int strlen($key)
7710
+ * @method int hdel($key, array $fields)
7711
+ * @method int hexists($key, $field)
7712
+ * @method string hget($key, $field)
7713
+ * @method array hgetall($key)
7714
+ * @method int hincrby($key, $field, $increment)
7715
+ * @method string hincrbyfloat($key, $field, $increment)
7716
+ * @method array hkeys($key)
7717
+ * @method int hlen($key)
7718
+ * @method array hmget($key, array $fields)
7719
+ * @method mixed hmset($key, array $dictionary)
7720
+ * @method array hscan($key, $cursor, array $options = null)
7721
+ * @method int hset($key, $field, $value)
7722
+ * @method int hsetnx($key, $field, $value)
7723
+ * @method array hvals($key)
7724
+ * @method array blpop(array $keys, $timeout)
7725
+ * @method array brpop(array $keys, $timeout)
7726
+ * @method array brpoplpush($source, $destination, $timeout)
7727
+ * @method string lindex($key, $index)
7728
+ * @method int linsert($key, $whence, $pivot, $value)
7729
+ * @method int llen($key)
7730
+ * @method string lpop($key)
7731
+ * @method int lpush($key, array $values)
7732
+ * @method int lpushx($key, $value)
7733
+ * @method array lrange($key, $start, $stop)
7734
+ * @method int lrem($key, $count, $value)
7735
+ * @method mixed lset($key, $index, $value)
7736
+ * @method mixed ltrim($key, $start, $stop)
7737
+ * @method string rpop($key)
7738
+ * @method string rpoplpush($source, $destination)
7739
+ * @method int rpush($key, array $values)
7740
+ * @method int rpushx($key, $value)
7741
+ * @method int sadd($key, array $members)
7742
+ * @method int scard($key)
7743
+ * @method array sdiff(array $keys)
7744
+ * @method int sdiffstore($destination, array $keys)
7745
+ * @method array sinter(array $keys)
7746
+ * @method int sinterstore($destination, array $keys)
7747
+ * @method int sismember($key, $member)
7748
+ * @method array smembers($key)
7749
+ * @method int smove($source, $destination, $member)
7750
+ * @method string spop($key)
7751
+ * @method string srandmember($key, $count = null)
7752
+ * @method int srem($key, $member)
7753
+ * @method array sscan($key, $cursor, array $options = null)
7754
+ * @method array sunion(array $keys)
7755
+ * @method int sunionstore($destination, array $keys)
7756
+ * @method int zadd($key, array $membersAndScoresDictionary)
7757
+ * @method int zcard($key)
7758
+ * @method string zcount($key, $min, $max)
7759
+ * @method string zincrby($key, $increment, $member)
7760
+ * @method int zinterstore($destination, array $keys, array $options = null)
7761
+ * @method array zrange($key, $start, $stop, array $options = null)
7762
+ * @method array zrangebyscore($key, $min, $max, array $options = null)
7763
+ * @method int zrank($key, $member)
7764
+ * @method int zrem($key, $member)
7765
+ * @method int zremrangebyrank($key, $start, $stop)
7766
+ * @method int zremrangebyscore($key, $min, $max)
7767
+ * @method array zrevrange($key, $start, $stop, array $options = null)
7768
+ * @method array zrevrangebyscore($key, $min, $max, array $options = null)
7769
+ * @method int zrevrank($key, $member)
7770
+ * @method int zunionstore($destination, array $keys, array $options = null)
7771
+ * @method string zscore($key, $member)
7772
+ * @method array zscan($key, $cursor, array $options = null)
7773
+ * @method array zrangebylex($key, $start, $stop, array $options = null)
7774
+ * @method int zremrangebylex($key, $min, $max)
7775
+ * @method int zlexcount($key, $min, $max)
7776
+ * @method int pfadd($key, array $elements)
7777
+ * @method mixed pfmerge($destinationKey, array $sourceKeys)
7778
+ * @method int pfcount(array $keys)
7779
+ * @method mixed pubsub($subcommand, $argument)
7780
+ * @method int publish($channel, $message)
7781
+ * @method mixed discard()
7782
+ * @method array exec()
7783
+ * @method mixed multi()
7784
+ * @method mixed unwatch()
7785
+ * @method mixed watch($key)
7786
+ * @method mixed eval($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7787
+ * @method mixed evalsha($script, $numkeys, $keyOrArg1 = null, $keyOrArgN = null)
7788
+ * @method mixed script($subcommand, $argument = null)
7789
+ * @method mixed auth($password)
7790
+ * @method string echo($message)
7791
+ * @method mixed ping($message = null)
7792
+ * @method mixed select($database)
7793
+ * @method mixed bgrewriteaof()
7794
+ * @method mixed bgsave()
7795
+ * @method mixed client($subcommand, $argument = null)
7796
+ * @method mixed config($subcommand, $argument = null)
7797
+ * @method int dbsize()
7798
+ * @method mixed flushall()
7799
+ * @method mixed flushdb()
7800
+ * @method array info($section = null)
7801
+ * @method int lastsave()
7802
+ * @method mixed save()
7803
+ * @method mixed slaveof($host, $port)
7804
+ * @method mixed slowlog($subcommand, $argument = null)
7805
+ * @method array time()
7806
+ * @method array command()
7807
+ *
7808
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7809
+ */
7810
+ interface ClientInterface
7811
+ {
7812
+ /**
7813
+ * Returns the server profile used by the client.
7814
+ *
7815
+ * @return ProfileInterface
7816
+ */
7817
+ public function getProfile();
7818
+
7819
+ /**
7820
+ * Returns the client options specified upon initialization.
7821
+ *
7822
+ * @return OptionsInterface
7823
+ */
7824
+ public function getOptions();
7825
+
7826
+ /**
7827
+ * Opens the underlying connection to the server.
7828
+ */
7829
+ public function connect();
7830
+
7831
+ /**
7832
+ * Closes the underlying connection from the server.
7833
+ */
7834
+ public function disconnect();
7835
+
7836
+ /**
7837
+ * Returns the underlying connection instance.
7838
+ *
7839
+ * @return ConnectionInterface
7840
+ */
7841
+ public function getConnection();
7842
+
7843
+ /**
7844
+ * Creates a new instance of the specified Redis command.
7845
+ *
7846
+ * @param string $method Command ID.
7847
+ * @param array $arguments Arguments for the command.
7848
+ *
7849
+ * @return CommandInterface
7850
+ */
7851
+ public function createCommand($method, $arguments = array());
7852
+
7853
+ /**
7854
+ * Executes the specified Redis command.
7855
+ *
7856
+ * @param CommandInterface $command Command instance.
7857
+ *
7858
+ * @return mixed
7859
+ */
7860
+ public function executeCommand(CommandInterface $command);
7861
+
7862
+ /**
7863
+ * Creates a Redis command with the specified arguments and sends a request
7864
+ * to the server.
7865
+ *
7866
+ * @param string $method Command ID.
7867
+ * @param array $arguments Arguments for the command.
7868
+ *
7869
+ * @return mixed
7870
+ */
7871
+ public function __call($method, $arguments);
7872
+ }
7873
+
7874
+ /**
7875
+ * Exception class thrown when trying to use features not supported by certain
7876
+ * classes or abstractions of Predis.
7877
+ *
7878
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7879
+ */
7880
+ class NotSupportedException extends PredisException
7881
+ {
7882
+ }
7883
+
7884
+ /**
7885
+ * Exception class that identifies client-side errors.
7886
+ *
7887
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7888
+ */
7889
+ class ClientException extends PredisException
7890
+ {
7891
+ }
7892
+
7893
+ /**
7894
+ * Client class used for connecting and executing commands on Redis.
7895
+ *
7896
+ * This is the main high-level abstraction of Predis upon which various other
7897
+ * abstractions are built. Internally it aggregates various other classes each
7898
+ * one with its own responsibility and scope.
7899
+ *
7900
+ * {@inheritdoc}
7901
+ *
7902
+ * @author Daniele Alessandri <suppakilla@gmail.com>
7903
+ */
7904
+ class Client implements ClientInterface
7905
+ {
7906
+ const VERSION = '1.0.1';
7907
+
7908
+ protected $connection;
7909
+ protected $options;
7910
+ private $profile;
7911
+
7912
+ /**
7913
+ * @param mixed $parameters Connection parameters for one or more servers.
7914
+ * @param mixed $options Options to configure some behaviours of the client.
7915
+ */
7916
+ public function __construct($parameters = null, $options = null)
7917
+ {
7918
+ $this->options = $this->createOptions($options ?: array());
7919
+ $this->connection = $this->createConnection($parameters ?: array());
7920
+ $this->profile = $this->options->profile;
7921
+ }
7922
+
7923
+ /**
7924
+ * Creates a new instance of Predis\Configuration\Options from different
7925
+ * types of arguments or simply returns the passed argument if it is an
7926
+ * instance of Predis\Configuration\OptionsInterface.
7927
+ *
7928
+ * @param mixed $options Client options.
7929
+ *
7930
+ * @return OptionsInterface
7931
+ *
7932
+ * @throws \InvalidArgumentException
7933
+ */
7934
+ protected function createOptions($options)
7935
+ {
7936
+ if (is_array($options)) {
7937
+ return new Options($options);
7938
+ }
7939
+
7940
+ if ($options instanceof OptionsInterface) {
7941
+ return $options;
7942
+ }
7943
+
7944
+ throw new InvalidArgumentException("Invalid type for client options.");
7945
+ }
7946
+
7947
+ /**
7948
+ * Creates single or aggregate connections from different types of arguments
7949
+ * (string, array) or returns the passed argument if it is an instance of a
7950
+ * class implementing Predis\Connection\ConnectionInterface.
7951
+ *
7952
+ * Accepted types for connection parameters are:
7953
+ *
7954
+ * - Instance of Predis\Connection\ConnectionInterface.
7955
+ * - Instance of Predis\Connection\ParametersInterface.
7956
+ * - Array
7957
+ * - String
7958
+ * - Callable
7959
+ *
7960
+ * @param mixed $parameters Connection parameters or connection instance.
7961
+ *
7962
+ * @return ConnectionInterface
7963
+ *
7964
+ * @throws \InvalidArgumentException
7965
+ */
7966
+ protected function createConnection($parameters)
7967
+ {
7968
+ if ($parameters instanceof ConnectionInterface) {
7969
+ return $parameters;
7970
+ }
7971
+
7972
+ if ($parameters instanceof ParametersInterface || is_string($parameters)) {
7973
+ return $this->options->connections->create($parameters);
7974
+ }
7975
+
7976
+ if (is_array($parameters)) {
7977
+ if (!isset($parameters[0])) {
7978
+ return $this->options->connections->create($parameters);
7979
+ }
7980
+
7981
+ $options = $this->options;
7982
+
7983
+ if ($options->defined('aggregate')) {
7984
+ $initializer = $this->getConnectionInitializerWrapper($options->aggregate);
7985
+ $connection = $initializer($parameters, $options);
7986
+ } else {
7987
+ if ($options->defined('replication') && $replication = $options->replication) {
7988
+ $connection = $replication;
7989
+ } else {
7990
+ $connection = $options->cluster;
7991
+ }
7992
+
7993
+ $options->connections->aggregate($connection, $parameters);
7994
+ }
7995
+
7996
+ return $connection;
7997
+ }
7998
+
7999
+ if (is_callable($parameters)) {
8000
+ $initializer = $this->getConnectionInitializerWrapper($parameters);
8001
+ $connection = $initializer($this->options);
8002
+
8003
+ return $connection;
8004
+ }
8005
+
8006
+ throw new InvalidArgumentException('Invalid type for connection parameters.');
8007
+ }
8008
+
8009
+ /**
8010
+ * Wraps a callable to make sure that its returned value represents a valid
8011
+ * connection type.
8012
+ *
8013
+ * @param mixed $callable
8014
+ *
8015
+ * @return \Closure
8016
+ */
8017
+ protected function getConnectionInitializerWrapper($callable)
8018
+ {
8019
+ return function () use ($callable) {
8020
+ $connection = call_user_func_array($callable, func_get_args());
8021
+
8022
+ if (!$connection instanceof ConnectionInterface) {
8023
+ throw new UnexpectedValueException(
8024
+ 'The callable connection initializer returned an invalid type.'
8025
+ );
8026
+ }
8027
+
8028
+ return $connection;
8029
+ };
8030
+ }
8031
+
8032
+ /**
8033
+ * {@inheritdoc}
8034
+ */
8035
+ public function getProfile()
8036
+ {
8037
+ return $this->profile;
8038
+ }
8039
+
8040
+ /**
8041
+ * {@inheritdoc}
8042
+ */
8043
+ public function getOptions()
8044
+ {
8045
+ return $this->options;
8046
+ }
8047
+
8048
+ /**
8049
+ * Creates a new client instance for the specified connection ID or alias,
8050
+ * only when working with an aggregate connection (cluster, replication).
8051
+ * The new client instances uses the same options of the original one.
8052
+ *
8053
+ * @param string $connectionID Identifier of a connection.
8054
+ *
8055
+ * @return Client
8056
+ *
8057
+ * @throws \InvalidArgumentException
8058
+ */
8059
+ public function getClientFor($connectionID)
8060
+ {
8061
+ if (!$connection = $this->getConnectionById($connectionID)) {
8062
+ throw new InvalidArgumentException("Invalid connection ID: $connectionID.");
8063
+ }
8064
+
8065
+ return new static($connection, $this->options);
8066
+ }
8067
+
8068
+ /**
8069
+ * Opens the underlying connection and connects to the server.
8070
+ */
8071
+ public function connect()
8072
+ {
8073
+ $this->connection->connect();
8074
+ }
8075
+
8076
+ /**
8077
+ * Closes the underlying connection and disconnects from the server.
8078
+ */
8079
+ public function disconnect()
8080
+ {
8081
+ $this->connection->disconnect();
8082
+ }
8083
+
8084
+ /**
8085
+ * Closes the underlying connection and disconnects from the server.
8086
+ *
8087
+ * This is the same as `Client::disconnect()` as it does not actually send
8088
+ * the `QUIT` command to Redis, but simply closes the connection.
8089
+ */
8090
+ public function quit()
8091
+ {
8092
+ $this->disconnect();
8093
+ }
8094
+
8095
+ /**
8096
+ * Returns the current state of the underlying connection.
8097
+ *
8098
+ * @return bool
8099
+ */
8100
+ public function isConnected()
8101
+ {
8102
+ return $this->connection->isConnected();
8103
+ }
8104
+
8105
+ /**
8106
+ * {@inheritdoc}
8107
+ */
8108
+ public function getConnection()
8109
+ {
8110
+ return $this->connection;
8111
+ }
8112
+
8113
+ /**
8114
+ * Retrieves the specified connection from the aggregate connection when the
8115
+ * client is in cluster or replication mode.
8116
+ *
8117
+ * @param string $connectionID Index or alias of the single connection.
8118
+ *
8119
+ * @return Connection\NodeConnectionInterface
8120
+ *
8121
+ * @throws NotSupportedException
8122
+ */
8123
+ public function getConnectionById($connectionID)
8124
+ {
8125
+ if (!$this->connection instanceof AggregateConnectionInterface) {
8126
+ throw new NotSupportedException(
8127
+ 'Retrieving connections by ID is supported only by aggregate connections.'
8128
+ );
8129
+ }
8130
+
8131
+ return $this->connection->getConnectionById($connectionID);
8132
+ }
8133
+
8134
+ /**
8135
+ * Executes a command without filtering its arguments, parsing the response,
8136
+ * applying any prefix to keys or throwing exceptions on Redis errors even
8137
+ * regardless of client options.
8138
+ *
8139
+ * It is possibile to indentify Redis error responses from normal responses
8140
+ * using the second optional argument which is populated by reference.
8141
+ *
8142
+ * @param array $arguments Command arguments as defined by the command signature.
8143
+ * @param bool $error Set to TRUE when Redis returned an error response.
8144
+ *
8145
+ * @return mixed
8146
+ */
8147
+ public function executeRaw(array $arguments, &$error = null)
8148
+ {
8149
+ $error = false;
8150
+ $response = $this->connection->executeCommand(
8151
+ new RawCommand($arguments)
8152
+ );
8153
+
8154
+ if ($response instanceof ResponseInterface) {
8155
+ if ($response instanceof ErrorResponseInterface) {
8156
+ $error = true;
8157
+ }
8158
+
8159
+ return (string) $response;
8160
+ }
8161
+
8162
+ return $response;
8163
+ }
8164
+
8165
+ /**
8166
+ * {@inheritdoc}
8167
+ */
8168
+ public function __call($commandID, $arguments)
8169
+ {
8170
+ return $this->executeCommand(
8171
+ $this->createCommand($commandID, $arguments)
8172
+ );
8173
+ }
8174
+
8175
+ /**
8176
+ * {@inheritdoc}
8177
+ */
8178
+ public function createCommand($commandID, $arguments = array())
8179
+ {
8180
+ return $this->profile->createCommand($commandID, $arguments);
8181
+ }
8182
+
8183
+ /**
8184
+ * {@inheritdoc}
8185
+ */
8186
+ public function executeCommand(CommandInterface $command)
8187
+ {
8188
+ $response = $this->connection->executeCommand($command);
8189
+
8190
+ if ($response instanceof ResponseInterface) {
8191
+ if ($response instanceof ErrorResponseInterface) {
8192
+ $response = $this->onErrorResponse($command, $response);
8193
+ }
8194
+
8195
+ return $response;
8196
+ }
8197
+
8198
+ return $command->parseResponse($response);
8199
+ }
8200
+
8201
+ /**
8202
+ * Handles -ERR responses returned by Redis.
8203
+ *
8204
+ * @param CommandInterface $command Redis command that generated the error.
8205
+ * @param ErrorResponseInterface $response Instance of the error response.
8206
+ *
8207
+ * @return mixed
8208
+ *
8209
+ * @throws ServerException
8210
+ */
8211
+ protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $response)
8212
+ {
8213
+ if ($command instanceof ScriptCommand && $response->getErrorType() === 'NOSCRIPT') {
8214
+ $eval = $this->createCommand('EVAL');
8215
+ $eval->setRawArguments($command->getEvalArguments());
8216
+
8217
+ $response = $this->executeCommand($eval);
8218
+
8219
+ if (!$response instanceof ResponseInterface) {
8220
+ $response = $command->parseResponse($response);
8221
+ }
8222
+
8223
+ return $response;
8224
+ }
8225
+
8226
+ if ($this->options->exceptions) {
8227
+ throw new ServerException($response->getMessage());
8228
+ }
8229
+
8230
+ return $response;
8231
+ }
8232
+
8233
+ /**
8234
+ * Executes the specified initializer method on `$this` by adjusting the
8235
+ * actual invokation depending on the arity (0, 1 or 2 arguments). This is
8236
+ * simply an utility method to create Redis contexts instances since they
8237
+ * follow a common initialization path.
8238
+ *
8239
+ * @param string $initializer Method name.
8240
+ * @param array $argv Arguments for the method.
8241
+ *
8242
+ * @return mixed
8243
+ */
8244
+ private function sharedContextFactory($initializer, $argv = null)
8245
+ {
8246
+ switch (count($argv)) {
8247
+ case 0:
8248
+ return $this->$initializer();
8249
+
8250
+ case 1:
8251
+ return is_array($argv[0])
8252
+ ? $this->$initializer($argv[0])
8253
+ : $this->$initializer(null, $argv[0]);
8254
+
8255
+ case 2:
8256
+ list($arg0, $arg1) = $argv;
8257
+
8258
+ return $this->$initializer($arg0, $arg1);
8259
+
8260
+ default:
8261
+ return $this->$initializer($this, $argv);
8262
+ }
8263
+ }
8264
+
8265
+ /**
8266
+ * Creates a new pipeline context and returns it, or returns the results of
8267
+ * a pipeline executed inside the optionally provided callable object.
8268
+ *
8269
+ * @param mixed ... Array of options, a callable for execution, or both.
8270
+ *
8271
+ * @return Pipeline|array
8272
+ */
8273
+ public function pipeline(/* arguments */)
8274
+ {
8275
+ return $this->sharedContextFactory('createPipeline', func_get_args());
8276
+ }
8277
+
8278
+ /**
8279
+ * Actual pipeline context initializer method.
8280
+ *
8281
+ * @param array $options Options for the context.
8282
+ * @param mixed $callable Optional callable used to execute the context.
8283
+ *
8284
+ * @return Pipeline|array
8285
+ */
8286
+ protected function createPipeline(array $options = null, $callable = null)
8287
+ {
8288
+ if (isset($options['atomic']) && $options['atomic']) {
8289
+ $class = 'Predis\Pipeline\Atomic';
8290
+ } elseif (isset($options['fire-and-forget']) && $options['fire-and-forget']) {
8291
+ $class = 'Predis\Pipeline\FireAndForget';
8292
+ } else {
8293
+ $class = 'Predis\Pipeline\Pipeline';
8294
+ }
8295
+
8296
+ /*
8297
+ * @var ClientContextInterface
8298
+ */
8299
+ $pipeline = new $class($this);
8300
+
8301
+ if (isset($callable)) {
8302
+ return $pipeline->execute($callable);
8303
+ }
8304
+
8305
+ return $pipeline;
8306
+ }
8307
+
8308
+ /**
8309
+ * Creates a new transaction context and returns it, or returns the results
8310
+ * of a transaction executed inside the optionally provided callable object.
8311
+ *
8312
+ * @param mixed ... Array of options, a callable for execution, or both.
8313
+ *
8314
+ * @return MultiExecTransaction|array
8315
+ */
8316
+ public function transaction(/* arguments */)
8317
+ {
8318
+ return $this->sharedContextFactory('createTransaction', func_get_args());
8319
+ }
8320
+
8321
+ /**
8322
+ * Actual transaction context initializer method.
8323
+ *
8324
+ * @param array $options Options for the context.
8325
+ * @param mixed $callable Optional callable used to execute the context.
8326
+ *
8327
+ * @return MultiExecTransaction|array
8328
+ */
8329
+ protected function createTransaction(array $options = null, $callable = null)
8330
+ {
8331
+ $transaction = new MultiExecTransaction($this, $options);
8332
+
8333
+ if (isset($callable)) {
8334
+ return $transaction->execute($callable);
8335
+ }
8336
+
8337
+ return $transaction;
8338
+ }
8339
+
8340
+ /**
8341
+ * Creates a new publis/subscribe context and returns it, or starts its loop
8342
+ * inside the optionally provided callable object.
8343
+ *
8344
+ * @param mixed ... Array of options, a callable for execution, or both.
8345
+ *
8346
+ * @return PubSubConsumer|null
8347
+ */
8348
+ public function pubSubLoop(/* arguments */)
8349
+ {
8350
+ return $this->sharedContextFactory('createPubSub', func_get_args());
8351
+ }
8352
+
8353
+ /**
8354
+ * Actual publish/subscribe context initializer method.
8355
+ *
8356
+ * @param array $options Options for the context.
8357
+ * @param mixed $callable Optional callable used to execute the context.
8358
+ *
8359
+ * @return PubSubConsumer|null
8360
+ */
8361
+ protected function createPubSub(array $options = null, $callable = null)
8362
+ {
8363
+ $pubsub = new PubSubConsumer($this, $options);
8364
+
8365
+ if (!isset($callable)) {
8366
+ return $pubsub;
8367
+ }
8368
+
8369
+ foreach ($pubsub as $message) {
8370
+ if (call_user_func($callable, $pubsub, $message) === false) {
8371
+ $pubsub->stop();
8372
+ }
8373
+ }
8374
+ }
8375
+
8376
+ /**
8377
+ * Creates a new monitor consumer and returns it.
8378
+ *
8379
+ * @return MonitorConsumer
8380
+ */
8381
+ public function monitor()
8382
+ {
8383
+ return new MonitorConsumer($this);
8384
+ }
8385
+ }
8386
+
8387
+ /**
8388
+ * Implements a lightweight PSR-0 compliant autoloader for Predis.
8389
+ *
8390
+ * @author Eric Naeseth <eric@thumbtack.com>
8391
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8392
+ */
8393
+ class Autoloader
8394
+ {
8395
+ private $directory;
8396
+ private $prefix;
8397
+ private $prefixLength;
8398
+
8399
+ /**
8400
+ * @param string $baseDirectory Base directory where the source files are located.
8401
+ */
8402
+ public function __construct($baseDirectory = __DIR__)
8403
+ {
8404
+ $this->directory = $baseDirectory;
8405
+ $this->prefix = __NAMESPACE__ . '\\';
8406
+ $this->prefixLength = strlen($this->prefix);
8407
+ }
8408
+
8409
+ /**
8410
+ * Registers the autoloader class with the PHP SPL autoloader.
8411
+ *
8412
+ * @param bool $prepend Prepend the autoloader on the stack instead of appending it.
8413
+ */
8414
+ public static function register($prepend = false)
8415
+ {
8416
+ spl_autoload_register(array(new self, 'autoload'), true, $prepend);
8417
+ }
8418
+
8419
+ /**
8420
+ * Loads a class from a file using its fully qualified name.
8421
+ *
8422
+ * @param string $className Fully qualified name of a class.
8423
+ */
8424
+ public function autoload($className)
8425
+ {
8426
+ if (0 === strpos($className, $this->prefix)) {
8427
+ $parts = explode('\\', substr($className, $this->prefixLength));
8428
+ $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
8429
+
8430
+ if (is_file($filepath)) {
8431
+ require($filepath);
8432
+ }
8433
+ }
8434
+ }
8435
+ }
8436
+
8437
+ /* --------------------------------------------------------------------------- */
8438
+
8439
+ namespace Predis\Configuration;
8440
+
8441
+ use InvalidArgumentException;
8442
+ use Predis\Connection\Aggregate\ClusterInterface;
8443
+ use Predis\Connection\Aggregate\PredisCluster;
8444
+ use Predis\Connection\Aggregate\RedisCluster;
8445
+ use Predis\Connection\Factory;
8446
+ use Predis\Connection\FactoryInterface;
8447
+ use Predis\Command\Processor\KeyPrefixProcessor;
8448
+ use Predis\Command\Processor\ProcessorInterface;
8449
+ use Predis\Profile\Factory as Predis_Factory;
8450
+ use Predis\Profile\ProfileInterface;
8451
+ use Predis\Profile\RedisProfile;
8452
+ use Predis\Connection\Aggregate\MasterSlaveReplication;
8453
+ use Predis\Connection\Aggregate\ReplicationInterface;
8454
+
8455
+ /**
8456
+ * Defines an handler used by Predis\Configuration\Options to filter, validate
8457
+ * or return default values for a given option.
8458
+ *
8459
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8460
+ */
8461
+ interface OptionInterface
8462
+ {
8463
+ /**
8464
+ * Filters and validates the passed value.
8465
+ *
8466
+ * @param OptionsInterface $options Options container.
8467
+ * @param mixed $value Input value.
8468
+ *
8469
+ * @return mixed
8470
+ */
8471
+ public function filter(OptionsInterface $options, $value);
8472
+
8473
+ /**
8474
+ * Returns the default value for the option.
8475
+ *
8476
+ * @param OptionsInterface $options Options container.
8477
+ *
8478
+ * @return mixed
8479
+ */
8480
+ public function getDefault(OptionsInterface $options);
8481
+ }
8482
+
8483
+ /**
8484
+ * Interface defining a container for client options.
8485
+ *
8486
+ * @property-read mixed aggregate Custom connection aggregator.
8487
+ * @property-read mixed cluster Aggregate connection for clustering.
8488
+ * @property-read mixed connections Connection factory.
8489
+ * @property-read mixed exceptions Toggles exceptions in client for -ERR responses.
8490
+ * @property-read mixed prefix Key prefixing strategy using the given prefix.
8491
+ * @property-read mixed profile Server profile.
8492
+ * @property-read mixed replication Aggregate connection for replication.
8493
+ *
8494
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8495
+ */
8496
+ interface OptionsInterface
8497
+ {
8498
+ /**
8499
+ * Returns the default value for the given option.
8500
+ *
8501
+ * @param string $option Name of the option.
8502
+ *
8503
+ * @return mixed|null
8504
+ */
8505
+ public function getDefault($option);
8506
+
8507
+ /**
8508
+ * Checks if the given option has been set by the user upon initialization.
8509
+ *
8510
+ * @param string $option Name of the option.
8511
+ *
8512
+ * @return bool
8513
+ */
8514
+ public function defined($option);
8515
+
8516
+ /**
8517
+ * Checks if the given option has been set and does not evaluate to NULL.
8518
+ *
8519
+ * @param string $option Name of the option.
8520
+ *
8521
+ * @return bool
8522
+ */
8523
+ public function __isset($option);
8524
+
8525
+ /**
8526
+ * Returns the value of the given option.
8527
+ *
8528
+ * @param string $option Name of the option.
8529
+ *
8530
+ * @return mixed|null
8531
+ */
8532
+ public function __get($option);
8533
+ }
8534
+
8535
+ /**
8536
+ * Configures a command processor that apply the specified prefix string to a
8537
+ * series of Redis commands considered prefixable.
8538
+ *
8539
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8540
+ */
8541
+ class PrefixOption implements OptionInterface
8542
+ {
8543
+ /**
8544
+ * {@inheritdoc}
8545
+ */
8546
+ public function filter(OptionsInterface $options, $value)
8547
+ {
8548
+ if ($value instanceof ProcessorInterface) {
8549
+ return $value;
8550
+ }
8551
+
8552
+ return new KeyPrefixProcessor($value);
8553
+ }
8554
+
8555
+ /**
8556
+ * {@inheritdoc}
8557
+ */
8558
+ public function getDefault(OptionsInterface $options)
8559
+ {
8560
+ // NOOP
8561
+ }
8562
+ }
8563
+
8564
+ /**
8565
+ * Configures the server profile to be used by the client to create command
8566
+ * instances depending on the specified version of the Redis server.
8567
+ *
8568
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8569
+ */
8570
+ class ProfileOption implements OptionInterface
8571
+ {
8572
+ /**
8573
+ * Sets the commands processors that need to be applied to the profile.
8574
+ *
8575
+ * @param OptionsInterface $options Client options.
8576
+ * @param ProfileInterface $profile Server profile.
8577
+ */
8578
+ protected function setProcessors(OptionsInterface $options, ProfileInterface $profile)
8579
+ {
8580
+ if (isset($options->prefix) && $profile instanceof RedisProfile) {
8581
+ // NOTE: directly using __get('prefix') is actually a workaround for
8582
+ // HHVM 2.3.0. It's correct and respects the options interface, it's
8583
+ // just ugly. We will remove this hack when HHVM will fix re-entrant
8584
+ // calls to __get() once and for all.
8585
+
8586
+ $profile->setProcessor($options->__get('prefix'));
8587
+ }
8588
+ }
8589
+
8590
+ /**
8591
+ * {@inheritdoc}
8592
+ */
8593
+ public function filter(OptionsInterface $options, $value)
8594
+ {
8595
+ if (is_string($value)) {
8596
+ $value = Predis_Factory::get($value);
8597
+ $this->setProcessors($options, $value);
8598
+ } elseif (!$value instanceof ProfileInterface) {
8599
+ throw new InvalidArgumentException('Invalid value for the profile option.');
8600
+ }
8601
+
8602
+ return $value;
8603
+ }
8604
+
8605
+ /**
8606
+ * {@inheritdoc}
8607
+ */
8608
+ public function getDefault(OptionsInterface $options)
8609
+ {
8610
+ $profile = Predis_Factory::getDefault();
8611
+ $this->setProcessors($options, $profile);
8612
+
8613
+ return $profile;
8614
+ }
8615
+ }
8616
+
8617
+ /**
8618
+ * Configures an aggregate connection used for master/slave replication among
8619
+ * multiple Redis nodes.
8620
+ *
8621
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8622
+ */
8623
+ class ReplicationOption implements OptionInterface
8624
+ {
8625
+ /**
8626
+ * {@inheritdoc}
8627
+ *
8628
+ * @todo There's more code than needed due to a bug in filter_var() as
8629
+ * discussed here https://bugs.php.net/bug.php?id=49510 and different
8630
+ * behaviours when encountering NULL values on PHP 5.3.
8631
+ */
8632
+ public function filter(OptionsInterface $options, $value)
8633
+ {
8634
+ if ($value instanceof ReplicationInterface) {
8635
+ return $value;
8636
+ }
8637
+
8638
+ if (is_bool($value) || $value === null) {
8639
+ return $value ? $this->getDefault($options) : null;
8640
+ }
8641
+
8642
+ if (
8643
+ !is_object($value) &&
8644
+ null !== $asbool = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)
8645
+ ) {
8646
+ return $asbool ? $this->getDefault($options) : null;
8647
+ }
8648
+
8649
+ throw new InvalidArgumentException(
8650
+ "An instance of type 'Predis\Connection\Aggregate\ReplicationInterface' was expected."
8651
+ );
8652
+ }
8653
+
8654
+ /**
8655
+ * {@inheritdoc}
8656
+ */
8657
+ public function getDefault(OptionsInterface $options)
8658
+ {
8659
+ return new MasterSlaveReplication();
8660
+ }
8661
+ }
8662
+
8663
+ /**
8664
+ * Manages Predis options with filtering, conversion and lazy initialization of
8665
+ * values using a mini-DI container approach.
8666
+ *
8667
+ * {@inheritdoc}
8668
+ *
8669
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8670
+ */
8671
+ class Options implements OptionsInterface
8672
+ {
8673
+ protected $input;
8674
+ protected $options;
8675
+ protected $handlers;
8676
+
8677
+ /**
8678
+ * @param array $options Array of options with their values
8679
+ */
8680
+ public function __construct(array $options = array())
8681
+ {
8682
+ $this->input = $options;
8683
+ $this->options = array();
8684
+ $this->handlers = $this->getHandlers();
8685
+ }
8686
+
8687
+ /**
8688
+ * Ensures that the default options are initialized.
8689
+ *
8690
+ * @return array
8691
+ */
8692
+ protected function getHandlers()
8693
+ {
8694
+ return array(
8695
+ 'cluster' => 'Predis\Configuration\ClusterOption',
8696
+ 'connections' => 'Predis\Configuration\ConnectionFactoryOption',
8697
+ 'exceptions' => 'Predis\Configuration\ExceptionsOption',
8698
+ 'prefix' => 'Predis\Configuration\PrefixOption',
8699
+ 'profile' => 'Predis\Configuration\ProfileOption',
8700
+ 'replication' => 'Predis\Configuration\ReplicationOption',
8701
+ );
8702
+ }
8703
+
8704
+ /**
8705
+ * {@inheritdoc}
8706
+ */
8707
+ public function getDefault($option)
8708
+ {
8709
+ if (isset($this->handlers[$option])) {
8710
+ $handler = $this->handlers[$option];
8711
+ $handler = new $handler();
8712
+
8713
+ return $handler->getDefault($this);
8714
+ }
8715
+ }
8716
+
8717
+ /**
8718
+ * {@inheritdoc}
8719
+ */
8720
+ public function defined($option)
8721
+ {
8722
+ return (
8723
+ array_key_exists($option, $this->options) ||
8724
+ array_key_exists($option, $this->input)
8725
+ );
8726
+ }
8727
+
8728
+ /**
8729
+ * {@inheritdoc}
8730
+ */
8731
+ public function __isset($option)
8732
+ {
8733
+ return (
8734
+ array_key_exists($option, $this->options) ||
8735
+ array_key_exists($option, $this->input)
8736
+ ) && $this->__get($option) !== null;
8737
+ }
8738
+
8739
+ /**
8740
+ * {@inheritdoc}
8741
+ */
8742
+ public function __get($option)
8743
+ {
8744
+ if (isset($this->options[$option]) || array_key_exists($option, $this->options)) {
8745
+ return $this->options[$option];
8746
+ }
8747
+
8748
+ if (isset($this->input[$option]) || array_key_exists($option, $this->input)) {
8749
+ $value = $this->input[$option];
8750
+ unset($this->input[$option]);
8751
+
8752
+ if (method_exists($value, '__invoke')) {
8753
+ $value = $value($this, $option);
8754
+ }
8755
+
8756
+ if (isset($this->handlers[$option])) {
8757
+ $handler = $this->handlers[$option];
8758
+ $handler = new $handler();
8759
+ $value = $handler->filter($this, $value);
8760
+ }
8761
+
8762
+ return $this->options[$option] = $value;
8763
+ }
8764
+
8765
+ if (isset($this->handlers[$option])) {
8766
+ return $this->options[$option] = $this->getDefault($option);
8767
+ }
8768
+
8769
+ return null;
8770
+ }
8771
+ }
8772
+
8773
+ /**
8774
+ * Configures a connection factory used by the client to create new connection
8775
+ * instances for single Redis nodes.
8776
+ *
8777
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8778
+ */
8779
+ class ConnectionFactoryOption implements OptionInterface
8780
+ {
8781
+ /**
8782
+ * {@inheritdoc}
8783
+ */
8784
+ public function filter(OptionsInterface $options, $value)
8785
+ {
8786
+ if ($value instanceof FactoryInterface) {
8787
+ return $value;
8788
+ } elseif (is_array($value)) {
8789
+ $factory = $this->getDefault($options);
8790
+
8791
+ foreach ($value as $scheme => $initializer) {
8792
+ $factory->define($scheme, $initializer);
8793
+ }
8794
+
8795
+ return $factory;
8796
+ } else {
8797
+ throw new InvalidArgumentException(
8798
+ 'Invalid value provided for the connections option.'
8799
+ );
8800
+ }
8801
+ }
8802
+
8803
+ /**
8804
+ * {@inheritdoc}
8805
+ */
8806
+ public function getDefault(OptionsInterface $options)
8807
+ {
8808
+ return new Factory();
8809
+ }
8810
+ }
8811
+
8812
+ /**
8813
+ * Configures whether consumers (such as the client) should throw exceptions on
8814
+ * Redis errors (-ERR responses) or just return instances of error responses.
8815
+ *
8816
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8817
+ */
8818
+ class ExceptionsOption implements OptionInterface
8819
+ {
8820
+ /**
8821
+ * {@inheritdoc}
8822
+ */
8823
+ public function filter(OptionsInterface $options, $value)
8824
+ {
8825
+ return filter_var($value, FILTER_VALIDATE_BOOLEAN);
8826
+ }
8827
+
8828
+ /**
8829
+ * {@inheritdoc}
8830
+ */
8831
+ public function getDefault(OptionsInterface $options)
8832
+ {
8833
+ return true;
8834
+ }
8835
+ }
8836
+
8837
+ /**
8838
+ * Configures an aggregate connection used for clustering
8839
+ * multiple Redis nodes using various implementations with
8840
+ * different algorithms or strategies.
8841
+ *
8842
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8843
+ */
8844
+ class ClusterOption implements OptionInterface
8845
+ {
8846
+ /**
8847
+ * Creates a new cluster connection from on a known descriptive name.
8848
+ *
8849
+ * @param OptionsInterface $options Instance of the client options.
8850
+ * @param string $id Descriptive identifier of the cluster type (`predis`, `redis-cluster`)
8851
+ *
8852
+ * @return ClusterInterface|null
8853
+ */
8854
+ protected function createByDescription(OptionsInterface $options, $id)
8855
+ {
8856
+ switch ($id) {
8857
+ case 'predis':
8858
+ case 'predis-cluster':
8859
+ return new PredisCluster();
8860
+
8861
+ case 'redis':
8862
+ case 'redis-cluster':
8863
+ return new RedisCluster($options->connections);
8864
+
8865
+ default:
8866
+ return;
8867
+ }
8868
+ }
8869
+
8870
+ /**
8871
+ * {@inheritdoc}
8872
+ */
8873
+ public function filter(OptionsInterface $options, $value)
8874
+ {
8875
+ if (is_string($value)) {
8876
+ $value = $this->createByDescription($options, $value);
8877
+ }
8878
+
8879
+ if (!$value instanceof ClusterInterface) {
8880
+ throw new InvalidArgumentException(
8881
+ "An instance of type 'Predis\Connection\Aggregate\ClusterInterface' was expected."
8882
+ );
8883
+ }
8884
+
8885
+ return $value;
8886
+ }
8887
+
8888
+ /**
8889
+ * {@inheritdoc}
8890
+ */
8891
+ public function getDefault(OptionsInterface $options)
8892
+ {
8893
+ return new PredisCluster();
8894
+ }
8895
+ }
8896
+
8897
+ /* --------------------------------------------------------------------------- */
8898
+
8899
+ namespace Predis\Response;
8900
+
8901
+ use Predis\PredisException;
8902
+
8903
+ /**
8904
+ * Represents a complex response object from Redis.
8905
+ *
8906
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8907
+ */
8908
+ interface ResponseInterface
8909
+ {
8910
+ }
8911
+
8912
+ /**
8913
+ * Represents an error returned by Redis (responses identified by "-" in the
8914
+ * Redis protocol) during the execution of an operation on the server.
8915
+ *
8916
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8917
+ */
8918
+ interface ErrorInterface extends ResponseInterface
8919
+ {
8920
+ /**
8921
+ * Returns the error message
8922
+ *
8923
+ * @return string
8924
+ */
8925
+ public function getMessage();
8926
+
8927
+ /**
8928
+ * Returns the error type (e.g. ERR, ASK, MOVED)
8929
+ *
8930
+ * @return string
8931
+ */
8932
+ public function getErrorType();
8933
+ }
8934
+
8935
+ /**
8936
+ * Represents a status response returned by Redis.
8937
+ *
8938
+ * @author Daniele Alessandri <suppakilla@gmail.com>
8939
+ */
8940
+ class Status implements ResponseInterface
8941
+ {
8942
+ private static $OK;
8943
+ private static $QUEUED;
8944
+
8945
+ private $payload;
8946
+
8947
+ /**
8948
+ * @param string $payload Payload of the status response as returned by Redis.
8949
+ */
8950
+ public function __construct($payload)
8951
+ {
8952
+ $this->payload = $payload;
8953
+ }
8954
+
8955
+ /**
8956
+ * Converts the response object to its string representation.
8957
+ *
8958
+ * @return string
8959
+ */
8960
+ public function __toString()
8961
+ {
8962
+ return $this->payload;
8963
+ }
8964
+
8965
+ /**
8966
+ * Returns the payload of status response.
8967
+ *
8968
+ * @return string
8969
+ */
8970
+ public function getPayload()
8971
+ {
8972
+ return $this->payload;
8973
+ }
8974
+
8975
+ /**
8976
+ * Returns an instance of a status response object.
8977
+ *
8978
+ * Common status responses such as OK or QUEUED are cached in order to lower
8979
+ * the global memory usage especially when using pipelines.
8980
+ *
8981
+ * @param string $payload Status response payload.
8982
+ *
8983
+ * @return string
8984
+ */
8985
+ public static function get($payload)
8986
+ {
8987
+ switch ($payload) {
8988
+ case 'OK':
8989
+ case 'QUEUED':
8990
+ if (isset(self::$$payload)) {
8991
+ return self::$$payload;
8992
+ }
8993
+
8994
+ return self::$$payload = new self($payload);
8995
+
8996
+ default:
8997
+ return new self($payload);
8998
+ }
8999
+ }
9000
+ }
9001
+
9002
+ /**
9003
+ * Represents an error returned by Redis (-ERR responses) during the execution
9004
+ * of a command on the server.
9005
+ *
9006
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9007
+ */
9008
+ class Error implements ErrorInterface
9009
+ {
9010
+ private $message;
9011
+
9012
+ /**
9013
+ * @param string $message Error message returned by Redis
9014
+ */
9015
+ public function __construct($message)
9016
+ {
9017
+ $this->message = $message;
9018
+ }
9019
+
9020
+ /**
9021
+ * {@inheritdoc}
9022
+ */
9023
+ public function getMessage()
9024
+ {
9025
+ return $this->message;
9026
+ }
9027
+
9028
+ /**
9029
+ * {@inheritdoc}
9030
+ */
9031
+ public function getErrorType()
9032
+ {
9033
+ list($errorType, ) = explode(' ', $this->getMessage(), 2);
9034
+
9035
+ return $errorType;
9036
+ }
9037
+
9038
+ /**
9039
+ * Converts the object to its string representation.
9040
+ *
9041
+ * @return string
9042
+ */
9043
+ public function __toString()
9044
+ {
9045
+ return $this->getMessage();
9046
+ }
9047
+ }
9048
+
9049
+ /**
9050
+ * Exception class that identifies server-side Redis errors.
9051
+ *
9052
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9053
+ */
9054
+ class ServerException extends PredisException implements ErrorInterface
9055
+ {
9056
+ /**
9057
+ * Gets the type of the error returned by Redis.
9058
+ *
9059
+ * @return string
9060
+ */
9061
+ public function getErrorType()
9062
+ {
9063
+ list($errorType, ) = explode(' ', $this->getMessage(), 2);
9064
+
9065
+ return $errorType;
9066
+ }
9067
+
9068
+ /**
9069
+ * Converts the exception to an instance of Predis\Response\Error.
9070
+ *
9071
+ * @return Error
9072
+ */
9073
+ public function toErrorResponse()
9074
+ {
9075
+ return new Error($this->getMessage());
9076
+ }
9077
+ }
9078
+
9079
+ /* --------------------------------------------------------------------------- */
9080
+
9081
+ namespace Predis\Protocol\Text\Handler;
9082
+
9083
+ use Predis\CommunicationException;
9084
+ use Predis\Connection\CompositeConnectionInterface;
9085
+ use Predis\Protocol\ProtocolException;
9086
+ use Predis\Response\Error;
9087
+ use Predis\Response\Status;
9088
+ use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
9089
+
9090
+ /**
9091
+ * Defines a pluggable handler used to parse a particular type of response.
9092
+ *
9093
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9094
+ */
9095
+ interface ResponseHandlerInterface
9096
+ {
9097
+ /**
9098
+ * Deserializes a response returned by Redis and reads more data from the
9099
+ * connection if needed.
9100
+ *
9101
+ * @param CompositeConnectionInterface $connection Redis connection.
9102
+ * @param string $payload String payload.
9103
+ *
9104
+ * @return mixed
9105
+ */
9106
+ public function handle(CompositeConnectionInterface $connection, $payload);
9107
+ }
9108
+
9109
+ /**
9110
+ * Handler for the status response type in the standard Redis wire protocol. It
9111
+ * translates certain classes of status response to PHP objects or just returns
9112
+ * the payload as a string.
9113
+ *
9114
+ * @link http://redis.io/topics/protocol
9115
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9116
+ */
9117
+ class StatusResponse implements ResponseHandlerInterface
9118
+ {
9119
+ /**
9120
+ * {@inheritdoc}
9121
+ */
9122
+ public function handle(CompositeConnectionInterface $connection, $payload)
9123
+ {
9124
+ return Status::get($payload);
9125
+ }
9126
+ }
9127
+
9128
+ /**
9129
+ * Handler for the multibulk response type in the standard Redis wire protocol.
9130
+ * It returns multibulk responses as iterators that can stream bulk elements.
9131
+ *
9132
+ * Streamable multibulk responses are not globally supported by the abstractions
9133
+ * built-in into Predis, such as transactions or pipelines. Use them with care!
9134
+ *
9135
+ * @link http://redis.io/topics/protocol
9136
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9137
+ */
9138
+ class StreamableMultiBulkResponse implements ResponseHandlerInterface
9139
+ {
9140
+ /**
9141
+ * {@inheritdoc}
9142
+ */
9143
+ public function handle(CompositeConnectionInterface $connection, $payload)
9144
+ {
9145
+ $length = (int) $payload;
9146
+
9147
+ if ("$length" != $payload) {
9148
+ CommunicationException::handle(new ProtocolException(
9149
+ $connection, "Cannot parse '$payload' as a valid length for a multi-bulk response."
9150
+ ));
9151
+ }
9152
+
9153
+ return new MultiBulkIterator($connection, $length);
9154
+ }
9155
+ }
9156
+
9157
+ /**
9158
+ * Handler for the multibulk response type in the standard Redis wire protocol.
9159
+ * It returns multibulk responses as PHP arrays.
9160
+ *
9161
+ * @link http://redis.io/topics/protocol
9162
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9163
+ */
9164
+ class MultiBulkResponse implements ResponseHandlerInterface
9165
+ {
9166
+ /**
9167
+ * {@inheritdoc}
9168
+ */
9169
+ public function handle(CompositeConnectionInterface $connection, $payload)
9170
+ {
9171
+ $length = (int) $payload;
9172
+
9173
+ if ("$length" !== $payload) {
9174
+ CommunicationException::handle(new ProtocolException(
9175
+ $connection, "Cannot parse '$payload' as a valid length of a multi-bulk response."
9176
+ ));
9177
+ }
9178
+
9179
+ if ($length === -1) {
9180
+ return null;
9181
+ }
9182
+
9183
+ $list = array();
9184
+
9185
+ if ($length > 0) {
9186
+ $handlersCache = array();
9187
+ $reader = $connection->getProtocol()->getResponseReader();
9188
+
9189
+ for ($i = 0; $i < $length; $i++) {
9190
+ $header = $connection->readLine();
9191
+ $prefix = $header[0];
9192
+
9193
+ if (isset($handlersCache[$prefix])) {
9194
+ $handler = $handlersCache[$prefix];
9195
+ } else {
9196
+ $handler = $reader->getHandler($prefix);
9197
+ $handlersCache[$prefix] = $handler;
9198
+ }
9199
+
9200
+ $list[$i] = $handler->handle($connection, substr($header, 1));
9201
+ }
9202
+ }
9203
+
9204
+ return $list;
9205
+ }
9206
+ }
9207
+
9208
+ /**
9209
+ * Handler for the error response type in the standard Redis wire protocol.
9210
+ * It translates the payload to a complex response object for Predis.
9211
+ *
9212
+ * @link http://redis.io/topics/protocol
9213
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9214
+ */
9215
+ class ErrorResponse implements ResponseHandlerInterface
9216
+ {
9217
+ /**
9218
+ * {@inheritdoc}
9219
+ */
9220
+ public function handle(CompositeConnectionInterface $connection, $payload)
9221
+ {
9222
+ return new Error($payload);
9223
+ }
9224
+ }
9225
+
9226
+ /**
9227
+ * Handler for the integer response type in the standard Redis wire protocol.
9228
+ * It translates the payload an integer or NULL.
9229
+ *
9230
+ * @link http://redis.io/topics/protocol
9231
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9232
+ */
9233
+ class IntegerResponse implements ResponseHandlerInterface
9234
+ {
9235
+ /**
9236
+ * {@inheritdoc}
9237
+ */
9238
+ public function handle(CompositeConnectionInterface $connection, $payload)
9239
+ {
9240
+ if (is_numeric($payload)) {
9241
+ return (int) $payload;
9242
+ }
9243
+
9244
+ if ($payload !== 'nil') {
9245
+ CommunicationException::handle(new ProtocolException(
9246
+ $connection, "Cannot parse '$payload' as a valid numeric response."
9247
+ ));
9248
+ }
9249
+
9250
+ return null;
9251
+ }
9252
+ }
9253
+
9254
+ /**
9255
+ * Handler for the bulk response type in the standard Redis wire protocol.
9256
+ * It translates the payload to a string or a NULL.
9257
+ *
9258
+ * @link http://redis.io/topics/protocol
9259
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9260
+ */
9261
+ class BulkResponse implements ResponseHandlerInterface
9262
+ {
9263
+ /**
9264
+ * {@inheritdoc}
9265
+ */
9266
+ public function handle(CompositeConnectionInterface $connection, $payload)
9267
+ {
9268
+ $length = (int) $payload;
9269
+
9270
+ if ("$length" !== $payload) {
9271
+ CommunicationException::handle(new ProtocolException(
9272
+ $connection, "Cannot parse '$payload' as a valid length for a bulk response."
9273
+ ));
9274
+ }
9275
+
9276
+ if ($length >= 0) {
9277
+ return substr($connection->readBuffer($length + 2), 0, -2);
9278
+ }
9279
+
9280
+ if ($length == -1) {
9281
+ return null;
9282
+ }
9283
+
9284
+ CommunicationException::handle(new ProtocolException(
9285
+ $connection, "Value '$payload' is not a valid length for a bulk response."
9286
+ ));
9287
+
9288
+ return;
9289
+ }
9290
+ }
9291
+
9292
+ /* --------------------------------------------------------------------------- */
9293
+
9294
+ namespace Predis\Collection\Iterator;
9295
+
9296
+ use Iterator;
9297
+ use Predis\ClientInterface;
9298
+ use Predis\NotSupportedException;
9299
+ use InvalidArgumentException;
9300
+
9301
+ /**
9302
+ * Provides the base implementation for a fully-rewindable PHP iterator that can
9303
+ * incrementally iterate over cursor-based collections stored on Redis using the
9304
+ * commands in the `SCAN` family.
9305
+ *
9306
+ * Given their incremental nature with multiple fetches, these kind of iterators
9307
+ * offer limited guarantees about the returned elements because the collection
9308
+ * can change several times during the iteration process.
9309
+ *
9310
+ * @see http://redis.io/commands/scan
9311
+ *
9312
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9313
+ */
9314
+ abstract class CursorBasedIterator implements Iterator
9315
+ {
9316
+ protected $client;
9317
+ protected $match;
9318
+ protected $count;
9319
+
9320
+ protected $valid;
9321
+ protected $fetchmore;
9322
+ protected $elements;
9323
+ protected $cursor;
9324
+ protected $position;
9325
+ protected $current;
9326
+
9327
+ /**
9328
+ * @param ClientInterface $client Client connected to Redis.
9329
+ * @param string $match Pattern to match during the server-side iteration.
9330
+ * @param int $count Hint used by Redis to compute the number of results per iteration.
9331
+ */
9332
+ public function __construct(ClientInterface $client, $match = null, $count = null)
9333
+ {
9334
+ $this->client = $client;
9335
+ $this->match = $match;
9336
+ $this->count = $count;
9337
+
9338
+ $this->reset();
9339
+ }
9340
+
9341
+ /**
9342
+ * Ensures that the client supports the specified Redis command required to
9343
+ * fetch elements from the server to perform the iteration.
9344
+ *
9345
+ * @param ClientInterface $client Client connected to Redis.
9346
+ * @param string $commandID Command ID.
9347
+ *
9348
+ * @throws NotSupportedException
9349
+ */
9350
+ protected function requiredCommand(ClientInterface $client, $commandID)
9351
+ {
9352
+ if (!$client->getProfile()->supportsCommand($commandID)) {
9353
+ throw new NotSupportedException("The current profile does not support '$commandID'.");
9354
+ }
9355
+ }
9356
+
9357
+ /**
9358
+ * Resets the inner state of the iterator.
9359
+ */
9360
+ protected function reset()
9361
+ {
9362
+ $this->valid = true;
9363
+ $this->fetchmore = true;
9364
+ $this->elements = array();
9365
+ $this->cursor = 0;
9366
+ $this->position = -1;
9367
+ $this->current = null;
9368
+ }
9369
+
9370
+ /**
9371
+ * Returns an array of options for the `SCAN` command.
9372
+ *
9373
+ * @return array
9374
+ */
9375
+ protected function getScanOptions()
9376
+ {
9377
+ $options = array();
9378
+
9379
+ if (strlen($this->match) > 0) {
9380
+ $options['MATCH'] = $this->match;
9381
+ }
9382
+
9383
+ if ($this->count > 0) {
9384
+ $options['COUNT'] = $this->count;
9385
+ }
9386
+
9387
+ return $options;
9388
+ }
9389
+
9390
+ /**
9391
+ * Fetches a new set of elements from the remote collection, effectively
9392
+ * advancing the iteration process.
9393
+ *
9394
+ * @return array
9395
+ */
9396
+ abstract protected function executeCommand();
9397
+
9398
+ /**
9399
+ * Populates the local buffer of elements fetched from the server during
9400
+ * the iteration.
9401
+ */
9402
+ protected function fetch()
9403
+ {
9404
+ list($cursor, $elements) = $this->executeCommand();
9405
+
9406
+ if (!$cursor) {
9407
+ $this->fetchmore = false;
9408
+ }
9409
+
9410
+ $this->cursor = $cursor;
9411
+ $this->elements = $elements;
9412
+ }
9413
+
9414
+ /**
9415
+ * Extracts next values for key() and current().
9416
+ */
9417
+ protected function extractNext()
9418
+ {
9419
+ $this->position++;
9420
+ $this->current = array_shift($this->elements);
9421
+ }
9422
+
9423
+ /**
9424
+ * {@inheritdoc}
9425
+ */
9426
+ public function rewind()
9427
+ {
9428
+ $this->reset();
9429
+ $this->next();
9430
+ }
9431
+
9432
+ /**
9433
+ * {@inheritdoc}
9434
+ */
9435
+ public function current()
9436
+ {
9437
+ return $this->current;
9438
+ }
9439
+
9440
+ /**
9441
+ * {@inheritdoc}
9442
+ */
9443
+ public function key()
9444
+ {
9445
+ return $this->position;
9446
+ }
9447
+
9448
+ /**
9449
+ * {@inheritdoc}
9450
+ */
9451
+ public function next()
9452
+ {
9453
+ tryFetch: {
9454
+ if (!$this->elements && $this->fetchmore) {
9455
+ $this->fetch();
9456
+ }
9457
+
9458
+ if ($this->elements) {
9459
+ $this->extractNext();
9460
+ } elseif ($this->cursor) {
9461
+ goto tryFetch;
9462
+ } else {
9463
+ $this->valid = false;
9464
+ }
9465
+ }
9466
+ }
9467
+
9468
+ /**
9469
+ * {@inheritdoc}
9470
+ */
9471
+ public function valid()
9472
+ {
9473
+ return $this->valid;
9474
+ }
9475
+ }
9476
+
9477
+ /**
9478
+ * Abstracts the iteration of members stored in a sorted set by leveraging the
9479
+ * ZSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9480
+ *
9481
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9482
+ * @link http://redis.io/commands/scan
9483
+ */
9484
+ class SortedSetKey extends CursorBasedIterator
9485
+ {
9486
+ protected $key;
9487
+
9488
+ /**
9489
+ * {@inheritdoc}
9490
+ */
9491
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9492
+ {
9493
+ $this->requiredCommand($client, 'ZSCAN');
9494
+
9495
+ parent::__construct($client, $match, $count);
9496
+
9497
+ $this->key = $key;
9498
+ }
9499
+
9500
+ /**
9501
+ * {@inheritdoc}
9502
+ */
9503
+ protected function executeCommand()
9504
+ {
9505
+ return $this->client->zscan($this->key, $this->cursor, $this->getScanOptions());
9506
+ }
9507
+
9508
+ /**
9509
+ * {@inheritdoc}
9510
+ */
9511
+ protected function extractNext()
9512
+ {
9513
+ if ($kv = each($this->elements)) {
9514
+ $this->position = $kv[0];
9515
+ $this->current = $kv[1];
9516
+
9517
+ unset($this->elements[$this->position]);
9518
+ }
9519
+ }
9520
+ }
9521
+
9522
+ /**
9523
+ * Abstracts the iteration of members stored in a set by leveraging the SSCAN
9524
+ * command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9525
+ *
9526
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9527
+ * @link http://redis.io/commands/scan
9528
+ */
9529
+ class SetKey extends CursorBasedIterator
9530
+ {
9531
+ protected $key;
9532
+
9533
+ /**
9534
+ * {@inheritdoc}
9535
+ */
9536
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9537
+ {
9538
+ $this->requiredCommand($client, 'SSCAN');
9539
+
9540
+ parent::__construct($client, $match, $count);
9541
+
9542
+ $this->key = $key;
9543
+ }
9544
+
9545
+ /**
9546
+ * {@inheritdoc}
9547
+ */
9548
+ protected function executeCommand()
9549
+ {
9550
+ return $this->client->sscan($this->key, $this->cursor, $this->getScanOptions());
9551
+ }
9552
+ }
9553
+
9554
+ /**
9555
+ * Abstracts the iteration of the keyspace on a Redis instance by leveraging the
9556
+ * SCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9557
+ *
9558
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9559
+ * @link http://redis.io/commands/scan
9560
+ */
9561
+ class Keyspace extends CursorBasedIterator
9562
+ {
9563
+ /**
9564
+ * {@inheritdoc}
9565
+ */
9566
+ public function __construct(ClientInterface $client, $match = null, $count = null)
9567
+ {
9568
+ $this->requiredCommand($client, 'SCAN');
9569
+
9570
+ parent::__construct($client, $match, $count);
9571
+ }
9572
+
9573
+ /**
9574
+ * {@inheritdoc}
9575
+ */
9576
+ protected function executeCommand()
9577
+ {
9578
+ return $this->client->scan($this->cursor, $this->getScanOptions());
9579
+ }
9580
+ }
9581
+
9582
+ /**
9583
+ * Abstracts the iteration of fields and values of an hash by leveraging the
9584
+ * HSCAN command (Redis >= 2.8) wrapped in a fully-rewindable PHP iterator.
9585
+ *
9586
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9587
+ * @link http://redis.io/commands/scan
9588
+ */
9589
+ class HashKey extends CursorBasedIterator
9590
+ {
9591
+ protected $key;
9592
+
9593
+ /**
9594
+ * {@inheritdoc}
9595
+ */
9596
+ public function __construct(ClientInterface $client, $key, $match = null, $count = null)
9597
+ {
9598
+ $this->requiredCommand($client, 'HSCAN');
9599
+
9600
+ parent::__construct($client, $match, $count);
9601
+
9602
+ $this->key = $key;
9603
+ }
9604
+
9605
+ /**
9606
+ * {@inheritdoc}
9607
+ */
9608
+ protected function executeCommand()
9609
+ {
9610
+ return $this->client->hscan($this->key, $this->cursor, $this->getScanOptions());
9611
+ }
9612
+
9613
+ /**
9614
+ * {@inheritdoc}
9615
+ */
9616
+ protected function extractNext()
9617
+ {
9618
+ $this->position = key($this->elements);
9619
+ $this->current = array_shift($this->elements);
9620
+ }
9621
+ }
9622
+
9623
+ /**
9624
+ * Abstracts the iteration of items stored in a list by leveraging the LRANGE
9625
+ * command wrapped in a fully-rewindable PHP iterator.
9626
+ *
9627
+ * This iterator tries to emulate the behaviour of cursor-based iterators based
9628
+ * on the SCAN-family of commands introduced in Redis <= 2.8, meaning that due
9629
+ * to its incremental nature with multiple fetches it can only offer limited
9630
+ * guarantees on the returned elements because the collection can change several
9631
+ * times (trimmed, deleted, overwritten) during the iteration process.
9632
+ *
9633
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9634
+ * @link http://redis.io/commands/lrange
9635
+ */
9636
+ class ListKey implements Iterator
9637
+ {
9638
+ protected $client;
9639
+ protected $count;
9640
+ protected $key;
9641
+
9642
+ protected $valid;
9643
+ protected $fetchmore;
9644
+ protected $elements;
9645
+ protected $position;
9646
+ protected $current;
9647
+
9648
+ /**
9649
+ * @param ClientInterface $client Client connected to Redis.
9650
+ * @param string $key Redis list key.
9651
+ * @param int $count Number of items retrieved on each fetch operation.
9652
+ *
9653
+ * @throws \InvalidArgumentException
9654
+ */
9655
+ public function __construct(ClientInterface $client, $key, $count = 10)
9656
+ {
9657
+ $this->requiredCommand($client, 'LRANGE');
9658
+
9659
+ if ((false === $count = filter_var($count, FILTER_VALIDATE_INT)) || $count < 0) {
9660
+ throw new InvalidArgumentException('The $count argument must be a positive integer.');
9661
+ }
9662
+
9663
+ $this->client = $client;
9664
+ $this->key = $key;
9665
+ $this->count = $count;
9666
+
9667
+ $this->reset();
9668
+ }
9669
+
9670
+ /**
9671
+ * Ensures that the client instance supports the specified Redis command
9672
+ * required to fetch elements from the server to perform the iteration.
9673
+ *
9674
+ * @param ClientInterface $client Client connected to Redis.
9675
+ * @param string $commandID Command ID.
9676
+ *
9677
+ * @throws NotSupportedException
9678
+ */
9679
+ protected function requiredCommand(ClientInterface $client, $commandID)
9680
+ {
9681
+ if (!$client->getProfile()->supportsCommand($commandID)) {
9682
+ throw new NotSupportedException("The current profile does not support '$commandID'.");
9683
+ }
9684
+ }
9685
+
9686
+ /**
9687
+ * Resets the inner state of the iterator.
9688
+ */
9689
+ protected function reset()
9690
+ {
9691
+ $this->valid = true;
9692
+ $this->fetchmore = true;
9693
+ $this->elements = array();
9694
+ $this->position = -1;
9695
+ $this->current = null;
9696
+ }
9697
+
9698
+ /**
9699
+ * Fetches a new set of elements from the remote collection, effectively
9700
+ * advancing the iteration process.
9701
+ *
9702
+ * @return array
9703
+ */
9704
+ protected function executeCommand()
9705
+ {
9706
+ return $this->client->lrange($this->key, $this->position + 1, $this->position + $this->count);
9707
+ }
9708
+
9709
+ /**
9710
+ * Populates the local buffer of elements fetched from the server during the
9711
+ * iteration.
9712
+ */
9713
+ protected function fetch()
9714
+ {
9715
+ $elements = $this->executeCommand();
9716
+
9717
+ if (count($elements) < $this->count) {
9718
+ $this->fetchmore = false;
9719
+ }
9720
+
9721
+ $this->elements = $elements;
9722
+ }
9723
+
9724
+ /**
9725
+ * Extracts next values for key() and current().
9726
+ */
9727
+ protected function extractNext()
9728
+ {
9729
+ $this->position++;
9730
+ $this->current = array_shift($this->elements);
9731
+ }
9732
+
9733
+ /**
9734
+ * {@inheritdoc}
9735
+ */
9736
+ public function rewind()
9737
+ {
9738
+ $this->reset();
9739
+ $this->next();
9740
+ }
9741
+
9742
+ /**
9743
+ * {@inheritdoc}
9744
+ */
9745
+ public function current()
9746
+ {
9747
+ return $this->current;
9748
+ }
9749
+
9750
+ /**
9751
+ * {@inheritdoc}
9752
+ */
9753
+ public function key()
9754
+ {
9755
+ return $this->position;
9756
+ }
9757
+
9758
+ /**
9759
+ * {@inheritdoc}
9760
+ */
9761
+ public function next()
9762
+ {
9763
+ if (!$this->elements && $this->fetchmore) {
9764
+ $this->fetch();
9765
+ }
9766
+
9767
+ if ($this->elements) {
9768
+ $this->extractNext();
9769
+ } else {
9770
+ $this->valid = false;
9771
+ }
9772
+ }
9773
+
9774
+ /**
9775
+ * {@inheritdoc}
9776
+ */
9777
+ public function valid()
9778
+ {
9779
+ return $this->valid;
9780
+ }
9781
+ }
9782
+
9783
+ /* --------------------------------------------------------------------------- */
9784
+
9785
+ namespace Predis\Cluster;
9786
+
9787
+ use InvalidArgumentException;
9788
+ use Predis\Command\CommandInterface;
9789
+ use Predis\Command\ScriptCommand;
9790
+ use Predis\Cluster\Distributor\DistributorInterface;
9791
+ use Predis\Cluster\Distributor\HashRing;
9792
+ use Predis\NotSupportedException;
9793
+ use Predis\Cluster\Hash\HashGeneratorInterface;
9794
+ use Predis\Cluster\Hash\CRC16;
9795
+
9796
+ /**
9797
+ * Interface for classes defining the strategy used to calculate an hash out of
9798
+ * keys extracted from supported commands.
9799
+ *
9800
+ * This is mostly useful to support clustering via client-side sharding.
9801
+ *
9802
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9803
+ */
9804
+ interface StrategyInterface
9805
+ {
9806
+ /**
9807
+ * Returns a slot for the given command used for clustering distribution or
9808
+ * NULL when this is not possible.
9809
+ *
9810
+ * @param CommandInterface $command Command instance.
9811
+ *
9812
+ * @return int
9813
+ */
9814
+ public function getSlot(CommandInterface $command);
9815
+
9816
+ /**
9817
+ * Returns a slot for the given key used for clustering distribution or NULL
9818
+ * when this is not possible.
9819
+ *
9820
+ * @param string $key Key string.
9821
+ *
9822
+ * @return int
9823
+ */
9824
+ public function getSlotByKey($key);
9825
+
9826
+ /**
9827
+ * Returns a distributor instance to be used by the cluster.
9828
+ *
9829
+ * @return DistributorInterface
9830
+ */
9831
+ public function getDistributor();
9832
+ }
9833
+
9834
+ /**
9835
+ * Common class implementing the logic needed to support clustering strategies.
9836
+ *
9837
+ * @author Daniele Alessandri <suppakilla@gmail.com>
9838
+ */
9839
+ abstract class ClusterStrategy implements StrategyInterface
9840
+ {
9841
+ protected $commands;
9842
+
9843
+ /**
9844
+ *
9845
+ */
9846
+ public function __construct()
9847
+ {
9848
+ $this->commands = $this->getDefaultCommands();
9849
+ }
9850
+
9851
+ /**
9852
+ * Returns the default map of supported commands with their handlers.
9853
+ *
9854
+ * @return array
9855
+ */
9856
+ protected function getDefaultCommands()
9857
+ {
9858
+ $getKeyFromFirstArgument = array($this, 'getKeyFromFirstArgument');
9859
+ $getKeyFromAllArguments = array($this, 'getKeyFromAllArguments');
9860
+
9861
+ return array(
9862
+ /* commands operating on the key space */
9863
+ 'EXISTS' => $getKeyFromFirstArgument,
9864
+ 'DEL' => $getKeyFromAllArguments,
9865
+ 'TYPE' => $getKeyFromFirstArgument,
9866
+ 'EXPIRE' => $getKeyFromFirstArgument,
9867
+ 'EXPIREAT' => $getKeyFromFirstArgument,
9868
+ 'PERSIST' => $getKeyFromFirstArgument,
9869
+ 'PEXPIRE' => $getKeyFromFirstArgument,
9870
+ 'PEXPIREAT' => $getKeyFromFirstArgument,
9871
+ 'TTL' => $getKeyFromFirstArgument,
9872
+ 'PTTL' => $getKeyFromFirstArgument,
9873
+ 'SORT' => $getKeyFromFirstArgument, // TODO
9874
+ 'DUMP' => $getKeyFromFirstArgument,
9875
+ 'RESTORE' => $getKeyFromFirstArgument,
9876
+
9877
+ /* commands operating on string values */
9878
+ 'APPEND' => $getKeyFromFirstArgument,
9879
+ 'DECR' => $getKeyFromFirstArgument,
9880
+ 'DECRBY' => $getKeyFromFirstArgument,
9881
+ 'GET' => $getKeyFromFirstArgument,
9882
+ 'GETBIT' => $getKeyFromFirstArgument,
9883
+ 'MGET' => $getKeyFromAllArguments,
9884
+ 'SET' => $getKeyFromFirstArgument,
9885
+ 'GETRANGE' => $getKeyFromFirstArgument,
9886
+ 'GETSET' => $getKeyFromFirstArgument,
9887
+ 'INCR' => $getKeyFromFirstArgument,
9888
+ 'INCRBY' => $getKeyFromFirstArgument,
9889
+ 'INCRBYFLOAT' => $getKeyFromFirstArgument,
9890
+ 'SETBIT' => $getKeyFromFirstArgument,
9891
+ 'SETEX' => $getKeyFromFirstArgument,
9892
+ 'MSET' => array($this, 'getKeyFromInterleavedArguments'),
9893
+ 'MSETNX' => array($this, 'getKeyFromInterleavedArguments'),
9894
+ 'SETNX' => $getKeyFromFirstArgument,
9895
+ 'SETRANGE' => $getKeyFromFirstArgument,
9896
+ 'STRLEN' => $getKeyFromFirstArgument,
9897
+ 'SUBSTR' => $getKeyFromFirstArgument,
9898
+ 'BITOP' => array($this, 'getKeyFromBitOp'),
9899
+ 'BITCOUNT' => $getKeyFromFirstArgument,
9900
+
9901
+ /* commands operating on lists */
9902
+ 'LINSERT' => $getKeyFromFirstArgument,
9903
+ 'LINDEX' => $getKeyFromFirstArgument,
9904
+ 'LLEN' => $getKeyFromFirstArgument,
9905
+ 'LPOP' => $getKeyFromFirstArgument,
9906
+ 'RPOP' => $getKeyFromFirstArgument,
9907
+ 'RPOPLPUSH' => $getKeyFromAllArguments,
9908
+ 'BLPOP' => array($this, 'getKeyFromBlockingListCommands'),
9909
+ 'BRPOP' => array($this, 'getKeyFromBlockingListCommands'),
9910
+ 'BRPOPLPUSH' => array($this, 'getKeyFromBlockingListCommands'),
9911
+ 'LPUSH' => $getKeyFromFirstArgument,
9912
+ 'LPUSHX' => $getKeyFromFirstArgument,
9913
+ 'RPUSH' => $getKeyFromFirstArgument,
9914
+ 'RPUSHX' => $getKeyFromFirstArgument,
9915
+ 'LRANGE' => $getKeyFromFirstArgument,
9916
+ 'LREM' => $getKeyFromFirstArgument,
9917
+ 'LSET' => $getKeyFromFirstArgument,
9918
+ 'LTRIM' => $getKeyFromFirstArgument,
9919
+
9920
+ /* commands operating on sets */
9921
+ 'SADD' => $getKeyFromFirstArgument,
9922
+ 'SCARD' => $getKeyFromFirstArgument,
9923
+ 'SDIFF' => $getKeyFromAllArguments,
9924
+ 'SDIFFSTORE' => $getKeyFromAllArguments,
9925
+ 'SINTER' => $getKeyFromAllArguments,
9926
+ 'SINTERSTORE' => $getKeyFromAllArguments,
9927
+ 'SUNION' => $getKeyFromAllArguments,
9928
+ 'SUNIONSTORE' => $getKeyFromAllArguments,
9929
+ 'SISMEMBER' => $getKeyFromFirstArgument,
9930
+ 'SMEMBERS' => $getKeyFromFirstArgument,
9931
+ 'SSCAN' => $getKeyFromFirstArgument,
9932
+ 'SPOP' => $getKeyFromFirstArgument,
9933
+ 'SRANDMEMBER' => $getKeyFromFirstArgument,
9934
+ 'SREM' => $getKeyFromFirstArgument,
9935
+
9936
+ /* commands operating on sorted sets */
9937
+ 'ZADD' => $getKeyFromFirstArgument,
9938
+ 'ZCARD' => $getKeyFromFirstArgument,
9939
+ 'ZCOUNT' => $getKeyFromFirstArgument,
9940
+ 'ZINCRBY' => $getKeyFromFirstArgument,
9941
+ 'ZINTERSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
9942
+ 'ZRANGE' => $getKeyFromFirstArgument,
9943
+ 'ZRANGEBYSCORE' => $getKeyFromFirstArgument,
9944
+ 'ZRANK' => $getKeyFromFirstArgument,
9945
+ 'ZREM' => $getKeyFromFirstArgument,
9946
+ 'ZREMRANGEBYRANK' => $getKeyFromFirstArgument,
9947
+ 'ZREMRANGEBYSCORE' => $getKeyFromFirstArgument,
9948
+ 'ZREVRANGE' => $getKeyFromFirstArgument,
9949
+ 'ZREVRANGEBYSCORE' => $getKeyFromFirstArgument,
9950
+ 'ZREVRANK' => $getKeyFromFirstArgument,
9951
+ 'ZSCORE' => $getKeyFromFirstArgument,
9952
+ 'ZUNIONSTORE' => array($this, 'getKeyFromZsetAggregationCommands'),
9953
+ 'ZSCAN' => $getKeyFromFirstArgument,
9954
+ 'ZLEXCOUNT' => $getKeyFromFirstArgument,
9955
+ 'ZRANGEBYLEX' => $getKeyFromFirstArgument,
9956
+ 'ZREMRANGEBYLEX' => $getKeyFromFirstArgument,
9957
+
9958
+ /* commands operating on hashes */
9959
+ 'HDEL' => $getKeyFromFirstArgument,
9960
+ 'HEXISTS' => $getKeyFromFirstArgument,
9961
+ 'HGET' => $getKeyFromFirstArgument,
9962
+ 'HGETALL' => $getKeyFromFirstArgument,
9963
+ 'HMGET' => $getKeyFromFirstArgument,
9964
+ 'HMSET' => $getKeyFromFirstArgument,
9965
+ 'HINCRBY' => $getKeyFromFirstArgument,
9966
+ 'HINCRBYFLOAT' => $getKeyFromFirstArgument,
9967
+ 'HKEYS' => $getKeyFromFirstArgument,
9968
+ 'HLEN' => $getKeyFromFirstArgument,
9969
+ 'HSET' => $getKeyFromFirstArgument,
9970
+ 'HSETNX' => $getKeyFromFirstArgument,
9971
+ 'HVALS' => $getKeyFromFirstArgument,
9972
+ 'HSCAN' => $getKeyFromFirstArgument,
9973
+
9974
+ /* commands operating on HyperLogLog */
9975
+ 'PFADD' => $getKeyFromFirstArgument,
9976
+ 'PFCOUNT' => $getKeyFromAllArguments,
9977
+ 'PFMERGE' => $getKeyFromAllArguments,
9978
+
9979
+ /* scripting */
9980
+ 'EVAL' => array($this, 'getKeyFromScriptingCommands'),
9981
+ 'EVALSHA' => array($this, 'getKeyFromScriptingCommands'),
9982
+ );
9983
+ }
9984
+
9985
+ /**
9986
+ * Returns the list of IDs for the supported commands.
9987
+ *
9988
+ * @return array
9989
+ */
9990
+ public function getSupportedCommands()
9991
+ {
9992
+ return array_keys($this->commands);
9993
+ }
9994
+
9995
+ /**
9996
+ * Sets an handler for the specified command ID.
9997
+ *
9998
+ * The signature of the callback must have a single parameter of type
9999
+ * Predis\Command\CommandInterface.
10000
+ *
10001
+ * When the callback argument is omitted or NULL, the previously associated
10002
+ * handler for the specified command ID is removed.
10003
+ *
10004
+ * @param string $commandID Command ID.
10005
+ * @param mixed $callback A valid callable object, or NULL to unset the handler.
10006
+ *
10007
+ * @throws \InvalidArgumentException
10008
+ */
10009
+ public function setCommandHandler($commandID, $callback = null)
10010
+ {
10011
+ $commandID = strtoupper($commandID);
10012
+
10013
+ if (!isset($callback)) {
10014
+ unset($this->commands[$commandID]);
10015
+
10016
+ return;
10017
+ }
10018
+
10019
+ if (!is_callable($callback)) {
10020
+ throw new InvalidArgumentException(
10021
+ "The argument must be a callable object or NULL."
10022
+ );
10023
+ }
10024
+
10025
+ $this->commands[$commandID] = $callback;
10026
+ }
10027
+
10028
+ /**
10029
+ * Extracts the key from the first argument of a command instance.
10030
+ *
10031
+ * @param CommandInterface $command Command instance.
10032
+ *
10033
+ * @return string
10034
+ */
10035
+ protected function getKeyFromFirstArgument(CommandInterface $command)
10036
+ {
10037
+ return $command->getArgument(0);
10038
+ }
10039
+
10040
+ /**
10041
+ * Extracts the key from a command with multiple keys only when all keys in
10042
+ * the arguments array produce the same hash.
10043
+ *
10044
+ * @param CommandInterface $command Command instance.
10045
+ *
10046
+ * @return string|null
10047
+ */
10048
+ protected function getKeyFromAllArguments(CommandInterface $command)
10049
+ {
10050
+ $arguments = $command->getArguments();
10051
+
10052
+ if ($this->checkSameSlotForKeys($arguments)) {
10053
+ return $arguments[0];
10054
+ }
10055
+ }
10056
+
10057
+ /**
10058
+ * Extracts the key from a command with multiple keys only when all keys in
10059
+ * the arguments array produce the same hash.
10060
+ *
10061
+ * @param CommandInterface $command Command instance.
10062
+ *
10063
+ * @return string|null
10064
+ */
10065
+ protected function getKeyFromInterleavedArguments(CommandInterface $command)
10066
+ {
10067
+ $arguments = $command->getArguments();
10068
+ $keys = array();
10069
+
10070
+ for ($i = 0; $i < count($arguments); $i += 2) {
10071
+ $keys[] = $arguments[$i];
10072
+ }
10073
+
10074
+ if ($this->checkSameSlotForKeys($keys)) {
10075
+ return $arguments[0];
10076
+ }
10077
+ }
10078
+
10079
+ /**
10080
+ * Extracts the key from BLPOP and BRPOP commands.
10081
+ *
10082
+ * @param CommandInterface $command Command instance.
10083
+ *
10084
+ * @return string|null
10085
+ */
10086
+ protected function getKeyFromBlockingListCommands(CommandInterface $command)
10087
+ {
10088
+ $arguments = $command->getArguments();
10089
+
10090
+ if ($this->checkSameSlotForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
10091
+ return $arguments[0];
10092
+ }
10093
+ }
10094
+
10095
+ /**
10096
+ * Extracts the key from BITOP command.
10097
+ *
10098
+ * @param CommandInterface $command Command instance.
10099
+ *
10100
+ * @return string|null
10101
+ */
10102
+ protected function getKeyFromBitOp(CommandInterface $command)
10103
+ {
10104
+ $arguments = $command->getArguments();
10105
+
10106
+ if ($this->checkSameSlotForKeys(array_slice($arguments, 1, count($arguments)))) {
10107
+ return $arguments[1];
10108
+ }
10109
+ }
10110
+
10111
+ /**
10112
+ * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
10113
+ *
10114
+ * @param CommandInterface $command Command instance.
10115
+ *
10116
+ * @return string|null
10117
+ */
10118
+ protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
10119
+ {
10120
+ $arguments = $command->getArguments();
10121
+ $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
10122
+
10123
+ if ($this->checkSameSlotForKeys($keys)) {
10124
+ return $arguments[0];
10125
+ }
10126
+ }
10127
+
10128
+ /**
10129
+ * Extracts the key from EVAL and EVALSHA commands.
10130
+ *
10131
+ * @param CommandInterface $command Command instance.
10132
+ *
10133
+ * @return string|null
10134
+ */
10135
+ protected function getKeyFromScriptingCommands(CommandInterface $command)
10136
+ {
10137
+ if ($command instanceof ScriptCommand) {
10138
+ $keys = $command->getKeys();
10139
+ } else {
10140
+ $keys = array_slice($args = $command->getArguments(), 2, $args[1]);
10141
+ }
10142
+
10143
+ if ($keys && $this->checkSameSlotForKeys($keys)) {
10144
+ return $keys[0];
10145
+ }
10146
+ }
10147
+
10148
+ /**
10149
+ * {@inheritdoc}
10150
+ */
10151
+ public function getSlot(CommandInterface $command)
10152
+ {
10153
+ $slot = $command->getSlot();
10154
+
10155
+ if (!isset($slot) && isset($this->commands[$cmdID = $command->getId()])) {
10156
+ $key = call_user_func($this->commands[$cmdID], $command);
10157
+
10158
+ if (isset($key)) {
10159
+ $slot = $this->getSlotByKey($key);
10160
+ $command->setSlot($slot);
10161
+ }
10162
+ }
10163
+
10164
+ return $slot;
10165
+ }
10166
+
10167
+ /**
10168
+ * Checks if the specified array of keys will generate the same hash.
10169
+ *
10170
+ * @param array $keys Array of keys.
10171
+ *
10172
+ * @return bool
10173
+ */
10174
+ protected function checkSameSlotForKeys(array $keys)
10175
+ {
10176
+ if (!$count = count($keys)) {
10177
+ return false;
10178
+ }
10179
+
10180
+ $currentSlot = $this->getSlotByKey($keys[0]);
10181
+
10182
+ for ($i = 1; $i < $count; $i++) {
10183
+ $nextSlot = $this->getSlotByKey($keys[$i]);
10184
+
10185
+ if ($currentSlot !== $nextSlot) {
10186
+ return false;
10187
+ }
10188
+
10189
+ $currentSlot = $nextSlot;
10190
+ }
10191
+
10192
+ return true;
10193
+ }
10194
+
10195
+ /**
10196
+ * Returns only the hashable part of a key (delimited by "{...}"), or the
10197
+ * whole key if a key tag is not found in the string.
10198
+ *
10199
+ * @param string $key A key.
10200
+ *
10201
+ * @return string
10202
+ */
10203
+ protected function extractKeyTag($key)
10204
+ {
10205
+ if (false !== $start = strpos($key, '{')) {
10206
+ if (false !== ($end = strpos($key, '}', $start)) && $end !== ++$start) {
10207
+ $key = substr($key, $start, $end - $start);
10208
+ }
10209
+ }
10210
+
10211
+ return $key;
10212
+ }
10213
+ }
10214
+
10215
+ /**
10216
+ * Default cluster strategy used by Predis to handle client-side sharding.
10217
+ *
10218
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10219
+ */
10220
+ class PredisStrategy extends ClusterStrategy
10221
+ {
10222
+ protected $distributor;
10223
+
10224
+ /**
10225
+ * @param DistributorInterface $distributor Optional distributor instance.
10226
+ */
10227
+ public function __construct(DistributorInterface $distributor = null)
10228
+ {
10229
+ parent::__construct();
10230
+
10231
+ $this->distributor = $distributor ?: new HashRing();
10232
+ }
10233
+
10234
+ /**
10235
+ * {@inheritdoc}
10236
+ */
10237
+ public function getSlotByKey($key)
10238
+ {
10239
+ $key = $this->extractKeyTag($key);
10240
+ $hash = $this->distributor->hash($key);
10241
+ $slot = $this->distributor->getSlot($hash);
10242
+
10243
+ return $slot;
10244
+ }
10245
+
10246
+ /**
10247
+ * {@inheritdoc}
10248
+ */
10249
+ protected function checkSameSlotForKeys(array $keys)
10250
+ {
10251
+ if (!$count = count($keys)) {
10252
+ return false;
10253
+ }
10254
+
10255
+ $currentKey = $this->extractKeyTag($keys[0]);
10256
+
10257
+ for ($i = 1; $i < $count; $i++) {
10258
+ $nextKey = $this->extractKeyTag($keys[$i]);
10259
+
10260
+ if ($currentKey !== $nextKey) {
10261
+ return false;
10262
+ }
10263
+
10264
+ $currentKey = $nextKey;
10265
+ }
10266
+
10267
+ return true;
10268
+ }
10269
+
10270
+ /**
10271
+ * {@inheritdoc}
10272
+ */
10273
+ public function getDistributor()
10274
+ {
10275
+ return $this->distributor;
10276
+ }
10277
+ }
10278
+
10279
+ /**
10280
+ * Default class used by Predis to calculate hashes out of keys of
10281
+ * commands supported by redis-cluster.
10282
+ *
10283
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10284
+ */
10285
+ class RedisStrategy extends ClusterStrategy
10286
+ {
10287
+ protected $hashGenerator;
10288
+
10289
+ /**
10290
+ * @param HashGeneratorInterface $hashGenerator Hash generator instance.
10291
+ */
10292
+ public function __construct(HashGeneratorInterface $hashGenerator = null)
10293
+ {
10294
+ parent::__construct();
10295
+
10296
+ $this->hashGenerator = $hashGenerator ?: new CRC16();
10297
+ }
10298
+
10299
+ /**
10300
+ * {@inheritdoc}
10301
+ */
10302
+ public function getSlotByKey($key)
10303
+ {
10304
+ $key = $this->extractKeyTag($key);
10305
+ $slot = $this->hashGenerator->hash($key) & 0x3FFF;
10306
+
10307
+ return $slot;
10308
+ }
10309
+
10310
+ /**
10311
+ * {@inheritdoc}
10312
+ */
10313
+ public function getDistributor()
10314
+ {
10315
+ throw new NotSupportedException(
10316
+ 'This cluster strategy does not provide an external distributor'
10317
+ );
10318
+ }
10319
+ }
10320
+
10321
+ /* --------------------------------------------------------------------------- */
10322
+
10323
+ namespace Predis\Protocol;
10324
+
10325
+ use Predis\CommunicationException;
10326
+ use Predis\Command\CommandInterface;
10327
+ use Predis\Connection\CompositeConnectionInterface;
10328
+
10329
+ /**
10330
+ * Defines a pluggable protocol processor capable of serializing commands and
10331
+ * deserializing responses into PHP objects directly from a connection.
10332
+ *
10333
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10334
+ */
10335
+ interface ProtocolProcessorInterface
10336
+ {
10337
+ /**
10338
+ * Writes a request over a connection to Redis.
10339
+ *
10340
+ * @param CompositeConnectionInterface $connection Redis connection.
10341
+ * @param CommandInterface $command Command instance.
10342
+ */
10343
+ public function write(CompositeConnectionInterface $connection, CommandInterface $command);
10344
+
10345
+ /**
10346
+ * Reads a response from a connection to Redis.
10347
+ *
10348
+ * @param CompositeConnectionInterface $connection Redis connection.
10349
+ *
10350
+ * @return mixed
10351
+ */
10352
+ public function read(CompositeConnectionInterface $connection);
10353
+ }
10354
+
10355
+ /**
10356
+ * Defines a pluggable reader capable of parsing responses returned by Redis and
10357
+ * deserializing them to PHP objects.
10358
+ *
10359
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10360
+ */
10361
+ interface ResponseReaderInterface
10362
+ {
10363
+ /**
10364
+ * Reads a response from a connection to Redis.
10365
+ *
10366
+ * @param CompositeConnectionInterface $connection Redis connection.
10367
+ *
10368
+ * @return mixed
10369
+ */
10370
+ public function read(CompositeConnectionInterface $connection);
10371
+ }
10372
+
10373
+ /**
10374
+ * Defines a pluggable serializer for Redis commands.
10375
+ *
10376
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10377
+ */
10378
+ interface RequestSerializerInterface
10379
+ {
10380
+ /**
10381
+ * Serializes a Redis command.
10382
+ *
10383
+ * @param CommandInterface $command Redis command.
10384
+ *
10385
+ * @return string
10386
+ */
10387
+ public function serialize(CommandInterface $command);
10388
+ }
10389
+
10390
+ /**
10391
+ * Exception used to indentify errors encountered while parsing the Redis wire
10392
+ * protocol.
10393
+ *
10394
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10395
+ */
10396
+ class ProtocolException extends CommunicationException
10397
+ {
10398
+ }
10399
+
10400
+ /* --------------------------------------------------------------------------- */
10401
+
10402
+ namespace Predis\Connection\Aggregate;
10403
+
10404
+ use Predis\Connection\AggregateConnectionInterface;
10405
+ use InvalidArgumentException;
10406
+ use RuntimeException;
10407
+ use Predis\Command\CommandInterface;
10408
+ use Predis\Connection\NodeConnectionInterface;
10409
+ use Predis\Replication\ReplicationStrategy;
10410
+ use ArrayIterator;
10411
+ use Countable;
10412
+ use IteratorAggregate;
10413
+ use Predis\NotSupportedException;
10414
+ use Predis\Cluster\PredisStrategy;
10415
+ use Predis\Cluster\StrategyInterface;
10416
+ use OutOfBoundsException;
10417
+ use Predis\Cluster\RedisStrategy as RedisClusterStrategy;
10418
+ use Predis\Command\RawCommand;
10419
+ use Predis\Connection\FactoryInterface;
10420
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
10421
+
10422
+ /**
10423
+ * Defines a cluster of Redis servers formed by aggregating multiple connection
10424
+ * instances to single Redis nodes.
10425
+ *
10426
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10427
+ */
10428
+ interface ClusterInterface extends AggregateConnectionInterface
10429
+ {
10430
+ }
10431
+
10432
+ /**
10433
+ * Defines a group of Redis nodes in a master / slave replication setup.
10434
+ *
10435
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10436
+ */
10437
+ interface ReplicationInterface extends AggregateConnectionInterface
10438
+ {
10439
+ /**
10440
+ * Switches the internal connection instance in use.
10441
+ *
10442
+ * @param string $connection Alias of a connection
10443
+ */
10444
+ public function switchTo($connection);
10445
+
10446
+ /**
10447
+ * Returns the connection instance currently in use by the aggregate
10448
+ * connection.
10449
+ *
10450
+ * @return NodeConnectionInterface
10451
+ */
10452
+ public function getCurrent();
10453
+
10454
+ /**
10455
+ * Returns the connection instance for the master Redis node.
10456
+ *
10457
+ * @return NodeConnectionInterface
10458
+ */
10459
+ public function getMaster();
10460
+
10461
+ /**
10462
+ * Returns a list of connection instances to slave nodes.
10463
+ *
10464
+ * @return NodeConnectionInterface
10465
+ */
10466
+ public function getSlaves();
10467
+ }
10468
+
10469
+ /**
10470
+ * Abstraction for a Redis-backed cluster of nodes (Redis >= 3.0.0).
10471
+ *
10472
+ * This connection backend offers smart support for redis-cluster by handling
10473
+ * automatic slots map (re)generation upon -MOVED or -ASK responses returned by
10474
+ * Redis when redirecting a client to a different node.
10475
+ *
10476
+ * The cluster can be pre-initialized using only a subset of the actual nodes in
10477
+ * the cluster, Predis will do the rest by adjusting the slots map and creating
10478
+ * the missing underlying connection instances on the fly.
10479
+ *
10480
+ * It is possible to pre-associate connections to a slots range with the "slots"
10481
+ * parameter in the form "$first-$last". This can greatly reduce runtime node
10482
+ * guessing and redirections.
10483
+ *
10484
+ * It is also possible to ask for the full and updated slots map directly to one
10485
+ * of the nodes and optionally enable such a behaviour upon -MOVED redirections.
10486
+ * Asking for the cluster configuration to Redis is actually done by issuing a
10487
+ * CLUSTER SLOTS command to a random node in the pool.
10488
+ *
10489
+ * @author Daniele Alessandri <suppakilla@gmail.com>
10490
+ */
10491
+ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable
10492
+ {
10493
+ private $useClusterSlots = true;
10494
+ private $defaultParameters = array();
10495
+ private $pool = array();
10496
+ private $slots = array();
10497
+ private $slotsMap;
10498
+ private $strategy;
10499
+ private $connections;
10500
+
10501
+ /**
10502
+ * @param FactoryInterface $connections Optional connection factory.
10503
+ * @param StrategyInterface $strategy Optional cluster strategy.
10504
+ */
10505
+ public function __construct(
10506
+ FactoryInterface $connections,
10507
+ StrategyInterface $strategy = null
10508
+ ) {
10509
+ $this->connections = $connections;
10510
+ $this->strategy = $strategy ?: new RedisClusterStrategy();
10511
+ }
10512
+
10513
+ /**
10514
+ * {@inheritdoc}
10515
+ */
10516
+ public function isConnected()
10517
+ {
10518
+ foreach ($this->pool as $connection) {
10519
+ if ($connection->isConnected()) {
10520
+ return true;
10521
+ }
10522
+ }
10523
+
10524
+ return false;
10525
+ }
10526
+
10527
+ /**
10528
+ * {@inheritdoc}
10529
+ */
10530
+ public function connect()
10531
+ {
10532
+ if ($connection = $this->getRandomConnection()) {
10533
+ $connection->connect();
10534
+ }
10535
+ }
10536
+
10537
+ /**
10538
+ * {@inheritdoc}
10539
+ */
10540
+ public function disconnect()
10541
+ {
10542
+ foreach ($this->pool as $connection) {
10543
+ $connection->disconnect();
10544
+ }
10545
+ }
10546
+
10547
+ /**
10548
+ * {@inheritdoc}
10549
+ */
10550
+ public function add(NodeConnectionInterface $connection)
10551
+ {
10552
+ $this->pool[(string) $connection] = $connection;
10553
+ unset($this->slotsMap);
10554
+ }
10555
+
10556
+ /**
10557
+ * {@inheritdoc}
10558
+ */
10559
+ public function remove(NodeConnectionInterface $connection)
10560
+ {
10561
+ if (false !== $id = array_search($connection, $this->pool, true)) {
10562
+ unset(
10563
+ $this->pool[$id],
10564
+ $this->slotsMap
10565
+ );
10566
+
10567
+ return true;
10568
+ }
10569
+
10570
+ return false;
10571
+ }
10572
+
10573
+ /**
10574
+ * Removes a connection instance by using its identifier.
10575
+ *
10576
+ * @param string $connectionID Connection identifier.
10577
+ *
10578
+ * @return bool True if the connection was in the pool.
10579
+ */
10580
+ public function removeById($connectionID)
10581
+ {
10582
+ if (isset($this->pool[$connectionID])) {
10583
+ unset(
10584
+ $this->pool[$connectionID],
10585
+ $this->slotsMap
10586
+ );
10587
+
10588
+ return true;
10589
+ }
10590
+
10591
+ return false;
10592
+ }
10593
+
10594
+ /**
10595
+ * Generates the current slots map by guessing the cluster configuration out
10596
+ * of the connection parameters of the connections in the pool.
10597
+ *
10598
+ * Generation is based on the same algorithm used by Redis to generate the
10599
+ * cluster, so it is most effective when all of the connections supplied on
10600
+ * initialization have the "slots" parameter properly set accordingly to the
10601
+ * current cluster configuration.
10602
+ */
10603
+ public function buildSlotsMap()
10604
+ {
10605
+ $this->slotsMap = array();
10606
+
10607
+ foreach ($this->pool as $connectionID => $connection) {
10608
+ $parameters = $connection->getParameters();
10609
+
10610
+ if (!isset($parameters->slots)) {
10611
+ continue;
10612
+ }
10613
+
10614
+ $slots = explode('-', $parameters->slots, 2);
10615
+ $this->setSlots($slots[0], $slots[1], $connectionID);
10616
+ }
10617
+ }
10618
+
10619
+ /**
10620
+ * Generates an updated slots map fetching the cluster configuration using
10621
+ * the CLUSTER SLOTS command against the specified node or a random one from
10622
+ * the pool.
10623
+ *
10624
+ * @param NodeConnectionInterface $connection Optional connection instance.
10625
+ *
10626
+ * @return array
10627
+ */
10628
+ public function askSlotsMap(NodeConnectionInterface $connection = null)
10629
+ {
10630
+ if (!$connection && !$connection = $this->getRandomConnection()) {
10631
+ return array();
10632
+ }
10633
+ $command = RawCommand::create('CLUSTER', 'SLOTS');
10634
+ $response = $connection->executeCommand($command);
10635
+
10636
+ foreach ($response as $slots) {
10637
+ // We only support master servers for now, so we ignore subsequent
10638
+ // elements in the $slots array identifying slaves.
10639
+ list($start, $end, $master) = $slots;
10640
+
10641
+ if ($master[0] === '') {
10642
+ $this->setSlots($start, $end, (string) $connection);
10643
+ } else {
10644
+ $this->setSlots($start, $end, "{$master[0]}:{$master[1]}");
10645
+ }
10646
+ }
10647
+
10648
+ return $this->slotsMap;
10649
+ }
10650
+
10651
+ /**
10652
+ * Returns the current slots map for the cluster.
10653
+ *
10654
+ * @return array
10655
+ */
10656
+ public function getSlotsMap()
10657
+ {
10658
+ if (!isset($this->slotsMap)) {
10659
+ $this->slotsMap = array();
10660
+ }
10661
+
10662
+ return $this->slotsMap;
10663
+ }
10664
+
10665
+ /**
10666
+ * Pre-associates a connection to a slots range to avoid runtime guessing.
10667
+ *
10668
+ * @param int $first Initial slot of the range.
10669
+ * @param int $last Last slot of the range.
10670
+ * @param NodeConnectionInterface|string $connection ID or connection instance.
10671
+ *
10672
+ * @throws \OutOfBoundsException
10673
+ */
10674
+ public function setSlots($first, $last, $connection)
10675
+ {
10676
+ if ($first < 0x0000 || $first > 0x3FFF ||
10677
+ $last < 0x0000 || $last > 0x3FFF ||
10678
+ $last < $first
10679
+ ) {
10680
+ throw new OutOfBoundsException(
10681
+ "Invalid slot range for $connection: [$first-$last]."
10682
+ );
10683
+ }
10684
+
10685
+ $slots = array_fill($first, $last - $first + 1, (string) $connection);
10686
+ $this->slotsMap = $this->getSlotsMap() + $slots;
10687
+ }
10688
+
10689
+ /**
10690
+ * Guesses the correct node associated to a given slot using a precalculated
10691
+ * slots map, falling back to the same logic used by Redis to initialize a
10692
+ * cluster (best-effort).
10693
+ *
10694
+ * @param int $slot Slot index.
10695
+ *
10696
+ * @return string Connection ID.
10697
+ */
10698
+ protected function guessNode($slot)
10699
+ {
10700
+ if (!isset($this->slotsMap)) {
10701
+ $this->buildSlotsMap();
10702
+ }
10703
+
10704
+ if (isset($this->slotsMap[$slot])) {
10705
+ return $this->slotsMap[$slot];
10706
+ }
10707
+
10708
+ $count = count($this->pool);
10709
+ $index = min((int) ($slot / (int) (16384 / $count)), $count - 1);
10710
+ $nodes = array_keys($this->pool);
10711
+
10712
+ return $nodes[$index];
10713
+ }
10714
+
10715
+ /**
10716
+ * Creates a new connection instance from the given connection ID.
10717
+ *
10718
+ * @param string $connectionID Identifier for the connection.
10719
+ *
10720
+ * @return NodeConnectionInterface
10721
+ */
10722
+ protected function createConnection($connectionID)
10723
+ {
10724
+ $host = explode(':', $connectionID, 2);
10725
+
10726
+ $parameters = array_merge($this->defaultParameters, array(
10727
+ 'host' => $host[0],
10728
+ 'port' => $host[1],
10729
+ ));
10730
+
10731
+ $connection = $this->connections->create($parameters);
10732
+
10733
+ return $connection;
10734
+ }
10735
+
10736
+ /**
10737
+ * {@inheritdoc}
10738
+ */
10739
+ public function getConnection(CommandInterface $command)
10740
+ {
10741
+ $slot = $this->strategy->getSlot($command);
10742
+
10743
+ if (!isset($slot)) {
10744
+ throw new NotSupportedException(
10745
+ "Cannot use '{$command->getId()}' with redis-cluster."
10746
+ );
10747
+ }
10748
+
10749
+ if (isset($this->slots[$slot])) {
10750
+ return $this->slots[$slot];
10751
+ } else {
10752
+ return $this->getConnectionBySlot($slot);
10753
+ }
10754
+ }
10755
+
10756
+ /**
10757
+ * Returns the connection currently associated to a given slot.
10758
+ *
10759
+ * @param int $slot Slot index.
10760
+ *
10761
+ * @return NodeConnectionInterface
10762
+ *
10763
+ * @throws \OutOfBoundsException
10764
+ */
10765
+ public function getConnectionBySlot($slot)
10766
+ {
10767
+ if ($slot < 0x0000 || $slot > 0x3FFF) {
10768
+ throw new OutOfBoundsException("Invalid slot [$slot].");
10769
+ }
10770
+
10771
+ if (isset($this->slots[$slot])) {
10772
+ return $this->slots[$slot];
10773
+ }
10774
+
10775
+ $connectionID = $this->guessNode($slot);
10776
+
10777
+ if (!$connection = $this->getConnectionById($connectionID)) {
10778
+ $connection = $this->createConnection($connectionID);
10779
+ $this->pool[$connectionID] = $connection;
10780
+ }
10781
+
10782
+ return $this->slots[$slot] = $connection;
10783
+ }
10784
+
10785
+ /**
10786
+ * {@inheritdoc}
10787
+ */
10788
+ public function getConnectionById($connectionID)
10789
+ {
10790
+ if (isset($this->pool[$connectionID])) {
10791
+ return $this->pool[$connectionID];
10792
+ }
10793
+ }
10794
+
10795
+ /**
10796
+ * Returns a random connection from the pool.
10797
+ *
10798
+ * @return NodeConnectionInterface|null
10799
+ */
10800
+ protected function getRandomConnection()
10801
+ {
10802
+ if ($this->pool) {
10803
+ return $this->pool[array_rand($this->pool)];
10804
+ }
10805
+ }
10806
+
10807
+ /**
10808
+ * Permanently associates the connection instance to a new slot.
10809
+ * The connection is added to the connections pool if not yet included.
10810
+ *
10811
+ * @param NodeConnectionInterface $connection Connection instance.
10812
+ * @param int $slot Target slot index.
10813
+ */
10814
+ protected function move(NodeConnectionInterface $connection, $slot)
10815
+ {
10816
+ $this->pool[(string) $connection] = $connection;
10817
+ $this->slots[(int) $slot] = $connection;
10818
+ }
10819
+
10820
+ /**
10821
+ * Handles -ERR responses returned by Redis.
10822
+ *
10823
+ * @param CommandInterface $command Command that generated the -ERR response.
10824
+ * @param ErrorResponseInterface $error Redis error response object.
10825
+ *
10826
+ * @return mixed
10827
+ */
10828
+ protected function onErrorResponse(CommandInterface $command, ErrorResponseInterface $error)
10829
+ {
10830
+ $details = explode(' ', $error->getMessage(), 2);
10831
+
10832
+ switch ($details[0]) {
10833
+ case 'MOVED':
10834
+ return $this->onMovedResponse($command, $details[1]);
10835
+
10836
+ case 'ASK':
10837
+ return $this->onAskResponse($command, $details[1]);
10838
+
10839
+ default:
10840
+ return $error;
10841
+ }
10842
+ }
10843
+
10844
+ /**
10845
+ * Handles -MOVED responses by executing again the command against the node
10846
+ * indicated by the Redis response.
10847
+ *
10848
+ * @param CommandInterface $command Command that generated the -MOVED response.
10849
+ * @param string $details Parameters of the -MOVED response.
10850
+ *
10851
+ * @return mixed
10852
+ */
10853
+ protected function onMovedResponse(CommandInterface $command, $details)
10854
+ {
10855
+ list($slot, $connectionID) = explode(' ', $details, 2);
10856
+
10857
+ if (!$connection = $this->getConnectionById($connectionID)) {
10858
+ $connection = $this->createConnection($connectionID);
10859
+ }
10860
+
10861
+ if ($this->useClusterSlots) {
10862
+ $this->askSlotsMap($connection);
10863
+ }
10864
+
10865
+ $this->move($connection, $slot);
10866
+ $response = $this->executeCommand($command);
10867
+
10868
+ return $response;
10869
+ }
10870
+
10871
+ /**
10872
+ * Handles -ASK responses by executing again the command against the node
10873
+ * indicated by the Redis response.
10874
+ *
10875
+ * @param CommandInterface $command Command that generated the -ASK response.
10876
+ * @param string $details Parameters of the -ASK response.
10877
+ * @return mixed
10878
+ */
10879
+ protected function onAskResponse(CommandInterface $command, $details)
10880
+ {
10881
+ list($slot, $connectionID) = explode(' ', $details, 2);
10882
+
10883
+ if (!$connection = $this->getConnectionById($connectionID)) {
10884
+ $connection = $this->createConnection($connectionID);
10885
+ }
10886
+ $connection->executeCommand(RawCommand::create('ASKING'));
10887
+ $response = $connection->executeCommand($command);
10888
+
10889
+ return $response;
10890
+ }
10891
+
10892
+ /**
10893
+ * {@inheritdoc}
10894
+ */
10895
+ public function writeRequest(CommandInterface $command)
10896
+ {
10897
+ $this->getConnection($command)->writeRequest($command);
10898
+ }
10899
+
10900
+ /**
10901
+ * {@inheritdoc}
10902
+ */
10903
+ public function readResponse(CommandInterface $command)
10904
+ {
10905
+ return $this->getConnection($command)->readResponse($command);
10906
+ }
10907
+
10908
+ /**
10909
+ * {@inheritdoc}
10910
+ */
10911
+ public function executeCommand(CommandInterface $command)
10912
+ {
10913
+ $connection = $this->getConnection($command);
10914
+ $response = $connection->executeCommand($command);
10915
+
10916
+ if ($response instanceof ErrorResponseInterface) {
10917
+ return $this->onErrorResponse($command, $response);
10918
+ }
10919
+
10920
+ return $response;
10921
+ }
10922
+
10923
+ /**
10924
+ * {@inheritdoc}
10925
+ */
10926
+ public function count()
10927
+ {
10928
+ return count($this->pool);
10929
+ }
10930
+
10931
+ /**
10932
+ * {@inheritdoc}
10933
+ */
10934
+ public function getIterator()
10935
+ {
10936
+ return new ArrayIterator(array_values($this->pool));
10937
+ }
10938
+
10939
+ /**
10940
+ * Returns the underlying command hash strategy used to hash commands by
10941
+ * using keys found in their arguments.
10942
+ *
10943
+ * @return StrategyInterface
10944
+ */
10945
+ public function getClusterStrategy()
10946
+ {
10947
+ return $this->strategy;
10948
+ }
10949
+
10950
+ /**
10951
+ * Returns the underlying connection factory used to create new connection
10952
+ * instances to Redis nodes indicated by redis-cluster.
10953
+ *
10954
+ * @return FactoryInterface
10955
+ */
10956
+ public function getConnectionFactory()
10957
+ {
10958
+ return $this->connections;
10959
+ }
10960
+
10961
+ /**
10962
+ * Enables automatic fetching of the current slots map from one of the nodes
10963
+ * using the CLUSTER SLOTS command. This option is disabled by default but
10964
+ * asking the current slots map to Redis upon -MOVED responses may reduce
10965
+ * overhead by eliminating the trial-and-error nature of the node guessing
10966
+ * procedure, mostly when targeting many keys that would end up in a lot of
10967
+ * redirections.
10968
+ *
10969
+ * The slots map can still be manually fetched using the askSlotsMap()
10970
+ * method whether or not this option is enabled.
10971
+ *
10972
+ * @param bool $value Enable or disable the use of CLUSTER SLOTS.
10973
+ */
10974
+ public function useClusterSlots($value)
10975
+ {
10976
+ $this->useClusterSlots = (bool) $value;
10977
+ }
10978
+
10979
+ /**
10980
+ * Sets a default array of connection parameters to be applied when creating
10981
+ * new connection instances on the fly when they are not part of the initial
10982
+ * pool supplied upon cluster initialization.
10983
+ *
10984
+ * These parameters are not applied to connections added to the pool using
10985
+ * the add() method.
10986
+ *
10987
+ * @param array $parameters Array of connection parameters.
10988
+ */
10989
+ public function setDefaultParameters(array $parameters)
10990
+ {
10991
+ $this->defaultParameters = array_merge(
10992
+ $this->defaultParameters,
10993
+ $parameters ?: array()
10994
+ );
10995
+ }
10996
+ }
10997
+
10998
+ /**
10999
+ * Abstraction for a cluster of aggregate connections to various Redis servers
11000
+ * implementing client-side sharding based on pluggable distribution strategies.
11001
+ *
11002
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11003
+ * @todo Add the ability to remove connections from pool.
11004
+ */
11005
+ class PredisCluster implements ClusterInterface, IteratorAggregate, Countable
11006
+ {
11007
+ private $pool;
11008
+ private $strategy;
11009
+ private $distributor;
11010
+
11011
+ /**
11012
+ * @param StrategyInterface $strategy Optional cluster strategy.
11013
+ */
11014
+ public function __construct(StrategyInterface $strategy = null)
11015
+ {
11016
+ $this->pool = array();
11017
+ $this->strategy = $strategy ?: new PredisStrategy();
11018
+ $this->distributor = $this->strategy->getDistributor();
11019
+ }
11020
+
11021
+ /**
11022
+ * {@inheritdoc}
11023
+ */
11024
+ public function isConnected()
11025
+ {
11026
+ foreach ($this->pool as $connection) {
11027
+ if ($connection->isConnected()) {
11028
+ return true;
11029
+ }
11030
+ }
11031
+
11032
+ return false;
11033
+ }
11034
+
11035
+ /**
11036
+ * {@inheritdoc}
11037
+ */
11038
+ public function connect()
11039
+ {
11040
+ foreach ($this->pool as $connection) {
11041
+ $connection->connect();
11042
+ }
11043
+ }
11044
+
11045
+ /**
11046
+ * {@inheritdoc}
11047
+ */
11048
+ public function disconnect()
11049
+ {
11050
+ foreach ($this->pool as $connection) {
11051
+ $connection->disconnect();
11052
+ }
11053
+ }
11054
+
11055
+ /**
11056
+ * {@inheritdoc}
11057
+ */
11058
+ public function add(NodeConnectionInterface $connection)
11059
+ {
11060
+ $parameters = $connection->getParameters();
11061
+
11062
+ if (isset($parameters->alias)) {
11063
+ $this->pool[$parameters->alias] = $connection;
11064
+ } else {
11065
+ $this->pool[] = $connection;
11066
+ }
11067
+
11068
+ $weight = isset($parameters->weight) ? $parameters->weight : null;
11069
+ $this->distributor->add($connection, $weight);
11070
+ }
11071
+
11072
+ /**
11073
+ * {@inheritdoc}
11074
+ */
11075
+ public function remove(NodeConnectionInterface $connection)
11076
+ {
11077
+ if (($id = array_search($connection, $this->pool, true)) !== false) {
11078
+ unset($this->pool[$id]);
11079
+ $this->distributor->remove($connection);
11080
+
11081
+ return true;
11082
+ }
11083
+
11084
+ return false;
11085
+ }
11086
+
11087
+ /**
11088
+ * Removes a connection instance using its alias or index.
11089
+ *
11090
+ * @param string $connectionID Alias or index of a connection.
11091
+ *
11092
+ * @return bool Returns true if the connection was in the pool.
11093
+ */
11094
+ public function removeById($connectionID)
11095
+ {
11096
+ if ($connection = $this->getConnectionById($connectionID)) {
11097
+ return $this->remove($connection);
11098
+ }
11099
+
11100
+ return false;
11101
+ }
11102
+
11103
+ /**
11104
+ * {@inheritdoc}
11105
+ */
11106
+ public function getConnection(CommandInterface $command)
11107
+ {
11108
+ $slot = $this->strategy->getSlot($command);
11109
+
11110
+ if (!isset($slot)) {
11111
+ throw new NotSupportedException(
11112
+ "Cannot use '{$command->getId()}' over clusters of connections."
11113
+ );
11114
+ }
11115
+
11116
+ $node = $this->distributor->getBySlot($slot);
11117
+
11118
+ return $node;
11119
+ }
11120
+
11121
+ /**
11122
+ * {@inheritdoc}
11123
+ */
11124
+ public function getConnectionById($connectionID)
11125
+ {
11126
+ return isset($this->pool[$connectionID]) ? $this->pool[$connectionID] : null;
11127
+ }
11128
+
11129
+ /**
11130
+ * Retrieves a connection instance from the cluster using a key.
11131
+ *
11132
+ * @param string $key Key string.
11133
+ *
11134
+ * @return NodeConnectionInterface
11135
+ */
11136
+ public function getConnectionByKey($key)
11137
+ {
11138
+ $hash = $this->strategy->getSlotByKey($key);
11139
+ $node = $this->distributor->getBySlot($hash);
11140
+
11141
+ return $node;
11142
+ }
11143
+
11144
+ /**
11145
+ * Returns the underlying command hash strategy used to hash commands by
11146
+ * using keys found in their arguments.
11147
+ *
11148
+ * @return StrategyInterface
11149
+ */
11150
+ public function getClusterStrategy()
11151
+ {
11152
+ return $this->strategy;
11153
+ }
11154
+
11155
+ /**
11156
+ * {@inheritdoc}
11157
+ */
11158
+ public function count()
11159
+ {
11160
+ return count($this->pool);
11161
+ }
11162
+
11163
+ /**
11164
+ * {@inheritdoc}
11165
+ */
11166
+ public function getIterator()
11167
+ {
11168
+ return new ArrayIterator($this->pool);
11169
+ }
11170
+
11171
+ /**
11172
+ * {@inheritdoc}
11173
+ */
11174
+ public function writeRequest(CommandInterface $command)
11175
+ {
11176
+ $this->getConnection($command)->writeRequest($command);
11177
+ }
11178
+
11179
+ /**
11180
+ * {@inheritdoc}
11181
+ */
11182
+ public function readResponse(CommandInterface $command)
11183
+ {
11184
+ return $this->getConnection($command)->readResponse($command);
11185
+ }
11186
+
11187
+ /**
11188
+ * {@inheritdoc}
11189
+ */
11190
+ public function executeCommand(CommandInterface $command)
11191
+ {
11192
+ return $this->getConnection($command)->executeCommand($command);
11193
+ }
11194
+
11195
+ /**
11196
+ * Executes the specified Redis command on all the nodes of a cluster.
11197
+ *
11198
+ * @param CommandInterface $command A Redis command.
11199
+ *
11200
+ * @return array
11201
+ */
11202
+ public function executeCommandOnNodes(CommandInterface $command)
11203
+ {
11204
+ $responses = array();
11205
+ foreach ($this->pool as $connection) {
11206
+ $responses[] = $connection->executeCommand($command);
11207
+ }
11208
+
11209
+ return $responses;
11210
+ }
11211
+ }
11212
+
11213
+ /**
11214
+ * Aggregate connection handling replication of Redis nodes configured in a
11215
+ * single master / multiple slaves setup.
11216
+ *
11217
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11218
+ */
11219
+ class MasterSlaveReplication implements ReplicationInterface
11220
+ {
11221
+ protected $strategy;
11222
+ protected $master;
11223
+ protected $slaves;
11224
+ protected $current;
11225
+
11226
+ /**
11227
+ * {@inheritdoc}
11228
+ */
11229
+ public function __construct(ReplicationStrategy $strategy = null)
11230
+ {
11231
+ $this->slaves = array();
11232
+ $this->strategy = $strategy ?: new ReplicationStrategy();
11233
+ }
11234
+
11235
+ /**
11236
+ * Checks if one master and at least one slave have been defined.
11237
+ */
11238
+ protected function check()
11239
+ {
11240
+ if (!isset($this->master) || !$this->slaves) {
11241
+ throw new RuntimeException('Replication needs one master and at least one slave.');
11242
+ }
11243
+ }
11244
+
11245
+ /**
11246
+ * Resets the connection state.
11247
+ */
11248
+ protected function reset()
11249
+ {
11250
+ $this->current = null;
11251
+ }
11252
+
11253
+ /**
11254
+ * {@inheritdoc}
11255
+ */
11256
+ public function add(NodeConnectionInterface $connection)
11257
+ {
11258
+ $alias = $connection->getParameters()->alias;
11259
+
11260
+ if ($alias === 'master') {
11261
+ $this->master = $connection;
11262
+ } else {
11263
+ $this->slaves[$alias ?: count($this->slaves)] = $connection;
11264
+ }
11265
+
11266
+ $this->reset();
11267
+ }
11268
+
11269
+ /**
11270
+ * {@inheritdoc}
11271
+ */
11272
+ public function remove(NodeConnectionInterface $connection)
11273
+ {
11274
+ if ($connection->getParameters()->alias === 'master') {
11275
+ $this->master = null;
11276
+ $this->reset();
11277
+
11278
+ return true;
11279
+ } else {
11280
+ if (($id = array_search($connection, $this->slaves, true)) !== false) {
11281
+ unset($this->slaves[$id]);
11282
+ $this->reset();
11283
+
11284
+ return true;
11285
+ }
11286
+ }
11287
+
11288
+ return false;
11289
+ }
11290
+
11291
+ /**
11292
+ * {@inheritdoc}
11293
+ */
11294
+ public function getConnection(CommandInterface $command)
11295
+ {
11296
+ if ($this->current === null) {
11297
+ $this->check();
11298
+ $this->current = $this->strategy->isReadOperation($command)
11299
+ ? $this->pickSlave()
11300
+ : $this->master;
11301
+
11302
+ return $this->current;
11303
+ }
11304
+
11305
+ if ($this->current === $this->master) {
11306
+ return $this->current;
11307
+ }
11308
+
11309
+ if (!$this->strategy->isReadOperation($command)) {
11310
+ $this->current = $this->master;
11311
+ }
11312
+
11313
+ return $this->current;
11314
+ }
11315
+
11316
+ /**
11317
+ * {@inheritdoc}
11318
+ */
11319
+ public function getConnectionById($connectionId)
11320
+ {
11321
+ if ($connectionId === 'master') {
11322
+ return $this->master;
11323
+ }
11324
+
11325
+ if (isset($this->slaves[$connectionId])) {
11326
+ return $this->slaves[$connectionId];
11327
+ }
11328
+
11329
+ return null;
11330
+ }
11331
+
11332
+ /**
11333
+ * {@inheritdoc}
11334
+ */
11335
+ public function switchTo($connection)
11336
+ {
11337
+ $this->check();
11338
+
11339
+ if (!$connection instanceof NodeConnectionInterface) {
11340
+ $connection = $this->getConnectionById($connection);
11341
+ }
11342
+ if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
11343
+ throw new InvalidArgumentException('Invalid connection or connection not found.');
11344
+ }
11345
+
11346
+ $this->current = $connection;
11347
+ }
11348
+
11349
+ /**
11350
+ * {@inheritdoc}
11351
+ */
11352
+ public function getCurrent()
11353
+ {
11354
+ return $this->current;
11355
+ }
11356
+
11357
+ /**
11358
+ * {@inheritdoc}
11359
+ */
11360
+ public function getMaster()
11361
+ {
11362
+ return $this->master;
11363
+ }
11364
+
11365
+ /**
11366
+ * {@inheritdoc}
11367
+ */
11368
+ public function getSlaves()
11369
+ {
11370
+ return array_values($this->slaves);
11371
+ }
11372
+
11373
+ /**
11374
+ * Returns the underlying replication strategy.
11375
+ *
11376
+ * @return ReplicationStrategy
11377
+ */
11378
+ public function getReplicationStrategy()
11379
+ {
11380
+ return $this->strategy;
11381
+ }
11382
+
11383
+ /**
11384
+ * Returns a random slave.
11385
+ *
11386
+ * @return NodeConnectionInterface
11387
+ */
11388
+ protected function pickSlave()
11389
+ {
11390
+ return $this->slaves[array_rand($this->slaves)];
11391
+ }
11392
+
11393
+ /**
11394
+ * {@inheritdoc}
11395
+ */
11396
+ public function isConnected()
11397
+ {
11398
+ return $this->current ? $this->current->isConnected() : false;
11399
+ }
11400
+
11401
+ /**
11402
+ * {@inheritdoc}
11403
+ */
11404
+ public function connect()
11405
+ {
11406
+ if ($this->current === null) {
11407
+ $this->check();
11408
+ $this->current = $this->pickSlave();
11409
+ }
11410
+
11411
+ $this->current->connect();
11412
+ }
11413
+
11414
+ /**
11415
+ * {@inheritdoc}
11416
+ */
11417
+ public function disconnect()
11418
+ {
11419
+ if ($this->master) {
11420
+ $this->master->disconnect();
11421
+ }
11422
+
11423
+ foreach ($this->slaves as $connection) {
11424
+ $connection->disconnect();
11425
+ }
11426
+ }
11427
+
11428
+ /**
11429
+ * {@inheritdoc}
11430
+ */
11431
+ public function writeRequest(CommandInterface $command)
11432
+ {
11433
+ $this->getConnection($command)->writeRequest($command);
11434
+ }
11435
+
11436
+ /**
11437
+ * {@inheritdoc}
11438
+ */
11439
+ public function readResponse(CommandInterface $command)
11440
+ {
11441
+ return $this->getConnection($command)->readResponse($command);
11442
+ }
11443
+
11444
+ /**
11445
+ * {@inheritdoc}
11446
+ */
11447
+ public function executeCommand(CommandInterface $command)
11448
+ {
11449
+ return $this->getConnection($command)->executeCommand($command);
11450
+ }
11451
+
11452
+ /**
11453
+ * {@inheritdoc}
11454
+ */
11455
+ public function __sleep()
11456
+ {
11457
+ return array('master', 'slaves', 'strategy');
11458
+ }
11459
+ }
11460
+
11461
+ /* --------------------------------------------------------------------------- */
11462
+
11463
+ namespace Predis\Pipeline;
11464
+
11465
+ use SplQueue;
11466
+ use Predis\ClientException;
11467
+ use Predis\ClientInterface;
11468
+ use Predis\Connection\ConnectionInterface;
11469
+ use Predis\Connection\NodeConnectionInterface;
11470
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
11471
+ use Predis\Response\ResponseInterface;
11472
+ use Predis\Response\ServerException;
11473
+ use Predis\NotSupportedException;
11474
+ use Predis\CommunicationException;
11475
+ use Predis\Connection\Aggregate\ClusterInterface;
11476
+ use Exception;
11477
+ use InvalidArgumentException;
11478
+ use Predis\ClientContextInterface;
11479
+ use Predis\Command\CommandInterface;
11480
+ use Predis\Connection\Aggregate\ReplicationInterface;
11481
+
11482
+ /**
11483
+ * Implementation of a command pipeline in which write and read operations of
11484
+ * Redis commands are pipelined to alleviate the effects of network round-trips.
11485
+ *
11486
+ * {@inheritdoc}
11487
+ *
11488
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11489
+ */
11490
+ class Pipeline implements ClientContextInterface
11491
+ {
11492
+ private $client;
11493
+ private $pipeline;
11494
+
11495
+ private $responses = array();
11496
+ private $running = false;
11497
+
11498
+ /**
11499
+ * @param ClientInterface $client Client instance used by the context.
11500
+ */
11501
+ public function __construct(ClientInterface $client)
11502
+ {
11503
+ $this->client = $client;
11504
+ $this->pipeline = new SplQueue();
11505
+ }
11506
+
11507
+ /**
11508
+ * Queues a command into the pipeline buffer.
11509
+ *
11510
+ * @param string $method Command ID.
11511
+ * @param array $arguments Arguments for the command.
11512
+ *
11513
+ * @return $this
11514
+ */
11515
+ public function __call($method, $arguments)
11516
+ {
11517
+ $command = $this->client->createCommand($method, $arguments);
11518
+ $this->recordCommand($command);
11519
+
11520
+ return $this;
11521
+ }
11522
+
11523
+ /**
11524
+ * Queues a command instance into the pipeline buffer.
11525
+ *
11526
+ * @param CommandInterface $command Command to be queued in the buffer.
11527
+ */
11528
+ protected function recordCommand(CommandInterface $command)
11529
+ {
11530
+ $this->pipeline->enqueue($command);
11531
+ }
11532
+
11533
+ /**
11534
+ * Queues a command instance into the pipeline buffer.
11535
+ *
11536
+ * @param CommandInterface $command Command instance to be queued in the buffer.
11537
+ *
11538
+ * @return $this
11539
+ */
11540
+ public function executeCommand(CommandInterface $command)
11541
+ {
11542
+ $this->recordCommand($command);
11543
+
11544
+ return $this;
11545
+ }
11546
+
11547
+ /**
11548
+ * Throws an exception on -ERR responses returned by Redis.
11549
+ *
11550
+ * @param ConnectionInterface $connection Redis connection that returned the error.
11551
+ * @param ErrorResponseInterface $response Instance of the error response.
11552
+ *
11553
+ * @throws ServerException
11554
+ */
11555
+ protected function exception(ConnectionInterface $connection, ErrorResponseInterface $response)
11556
+ {
11557
+ $connection->disconnect();
11558
+ $message = $response->getMessage();
11559
+
11560
+ throw new ServerException($message);
11561
+ }
11562
+
11563
+ /**
11564
+ * Returns the underlying connection to be used by the pipeline.
11565
+ *
11566
+ * @return ConnectionInterface
11567
+ */
11568
+ protected function getConnection()
11569
+ {
11570
+ $connection = $this->getClient()->getConnection();
11571
+
11572
+ if ($connection instanceof ReplicationInterface) {
11573
+ $connection->switchTo('master');
11574
+ }
11575
+
11576
+ return $connection;
11577
+ }
11578
+
11579
+ /**
11580
+ * Implements the logic to flush the queued commands and read the responses
11581
+ * from the current connection.
11582
+ *
11583
+ * @param ConnectionInterface $connection Current connection instance.
11584
+ * @param SplQueue $commands Queued commands.
11585
+ *
11586
+ * @return array
11587
+ */
11588
+ protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
11589
+ {
11590
+ foreach ($commands as $command) {
11591
+ $connection->writeRequest($command);
11592
+ }
11593
+
11594
+ $responses = array();
11595
+ $exceptions = $this->throwServerExceptions();
11596
+
11597
+ while (!$commands->isEmpty()) {
11598
+ $command = $commands->dequeue();
11599
+ $response = $connection->readResponse($command);
11600
+
11601
+ if (!$response instanceof ResponseInterface) {
11602
+ $responses[] = $command->parseResponse($response);
11603
+ } elseif ($response instanceof ErrorResponseInterface && $exceptions) {
11604
+ $this->exception($connection, $response);
11605
+ } else {
11606
+ $responses[] = $response;
11607
+ }
11608
+ }
11609
+
11610
+ return $responses;
11611
+ }
11612
+
11613
+ /**
11614
+ * Flushes the buffer holding all of the commands queued so far.
11615
+ *
11616
+ * @param bool $send Specifies if the commands in the buffer should be sent to Redis.
11617
+ *
11618
+ * @return $this
11619
+ */
11620
+ public function flushPipeline($send = true)
11621
+ {
11622
+ if ($send && !$this->pipeline->isEmpty()) {
11623
+ $responses = $this->executePipeline($this->getConnection(), $this->pipeline);
11624
+ $this->responses = array_merge($this->responses, $responses);
11625
+ } else {
11626
+ $this->pipeline = new SplQueue();
11627
+ }
11628
+
11629
+ return $this;
11630
+ }
11631
+
11632
+ /**
11633
+ * Marks the running status of the pipeline.
11634
+ *
11635
+ * @param bool $bool Sets the running status of the pipeline.
11636
+ *
11637
+ * @throws ClientException
11638
+ */
11639
+ private function setRunning($bool)
11640
+ {
11641
+ if ($bool && $this->running) {
11642
+ throw new ClientException('The current pipeline context is already being executed.');
11643
+ }
11644
+
11645
+ $this->running = $bool;
11646
+ }
11647
+
11648
+ /**
11649
+ * Handles the actual execution of the whole pipeline.
11650
+ *
11651
+ * @param mixed $callable Optional callback for execution.
11652
+ *
11653
+ * @return array
11654
+ *
11655
+ * @throws Exception
11656
+ * @throws InvalidArgumentException
11657
+ */
11658
+ public function execute($callable = null)
11659
+ {
11660
+ if ($callable && !is_callable($callable)) {
11661
+ throw new InvalidArgumentException('The argument must be a callable object.');
11662
+ }
11663
+
11664
+ $exception = null;
11665
+ $this->setRunning(true);
11666
+
11667
+ try {
11668
+ if ($callable) {
11669
+ call_user_func($callable, $this);
11670
+ }
11671
+
11672
+ $this->flushPipeline();
11673
+ } catch (Exception $exception) {
11674
+ // NOOP
11675
+ }
11676
+
11677
+ $this->setRunning(false);
11678
+
11679
+ if ($exception) {
11680
+ throw $exception;
11681
+ }
11682
+
11683
+ return $this->responses;
11684
+ }
11685
+
11686
+ /**
11687
+ * Returns if the pipeline should throw exceptions on server errors.
11688
+ *
11689
+ * @return bool
11690
+ */
11691
+ protected function throwServerExceptions()
11692
+ {
11693
+ return (bool) $this->client->getOptions()->exceptions;
11694
+ }
11695
+
11696
+ /**
11697
+ * Returns the underlying client instance used by the pipeline object.
11698
+ *
11699
+ * @return ClientInterface
11700
+ */
11701
+ public function getClient()
11702
+ {
11703
+ return $this->client;
11704
+ }
11705
+ }
11706
+
11707
+ /**
11708
+ * Command pipeline that writes commands to the servers but discards responses.
11709
+ *
11710
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11711
+ */
11712
+ class FireAndForget extends Pipeline
11713
+ {
11714
+ /**
11715
+ * {@inheritdoc}
11716
+ */
11717
+ protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
11718
+ {
11719
+ while (!$commands->isEmpty()) {
11720
+ $connection->writeRequest($commands->dequeue());
11721
+ }
11722
+
11723
+ $connection->disconnect();
11724
+
11725
+ return array();
11726
+ }
11727
+ }
11728
+
11729
+ /**
11730
+ * Command pipeline that does not throw exceptions on connection errors, but
11731
+ * returns the exception instances as the rest of the response elements.
11732
+ *
11733
+ * @todo Awful naming!
11734
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11735
+ */
11736
+ class ConnectionErrorProof extends Pipeline
11737
+ {
11738
+ /**
11739
+ * {@inheritdoc}
11740
+ */
11741
+ protected function getConnection()
11742
+ {
11743
+ return $this->getClient()->getConnection();
11744
+ }
11745
+
11746
+ /**
11747
+ * {@inheritdoc}
11748
+ */
11749
+ protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
11750
+ {
11751
+ if ($connection instanceof NodeConnectionInterface) {
11752
+ return $this->executeSingleNode($connection, $commands);
11753
+ } elseif ($connection instanceof ClusterInterface) {
11754
+ return $this->executeCluster($connection, $commands);
11755
+ } else {
11756
+ $class = get_class($connection);
11757
+
11758
+ throw new NotSupportedException("The connection class '$class' is not supported.");
11759
+ }
11760
+ }
11761
+
11762
+ /**
11763
+ * {@inheritdoc}
11764
+ */
11765
+ protected function executeSingleNode(NodeConnectionInterface $connection, SplQueue $commands)
11766
+ {
11767
+ $responses = array();
11768
+ $sizeOfPipe = count($commands);
11769
+
11770
+ foreach ($commands as $command) {
11771
+ try {
11772
+ $connection->writeRequest($command);
11773
+ } catch (CommunicationException $exception) {
11774
+ return array_fill(0, $sizeOfPipe, $exception);
11775
+ }
11776
+ }
11777
+
11778
+ for ($i = 0; $i < $sizeOfPipe; $i++) {
11779
+ $command = $commands->dequeue();
11780
+
11781
+ try {
11782
+ $responses[$i] = $connection->readResponse($command);
11783
+ } catch (CommunicationException $exception) {
11784
+ $add = count($commands) - count($responses);
11785
+ $responses = array_merge($responses, array_fill(0, $add, $exception));
11786
+
11787
+ break;
11788
+ }
11789
+ }
11790
+
11791
+ return $responses;
11792
+ }
11793
+
11794
+ /**
11795
+ * {@inheritdoc}
11796
+ */
11797
+ protected function executeCluster(ClusterInterface $connection, SplQueue $commands)
11798
+ {
11799
+ $responses = array();
11800
+ $sizeOfPipe = count($commands);
11801
+ $exceptions = array();
11802
+
11803
+ foreach ($commands as $command) {
11804
+ $cmdConnection = $connection->getConnection($command);
11805
+
11806
+ if (isset($exceptions[spl_object_hash($cmdConnection)])) {
11807
+ continue;
11808
+ }
11809
+
11810
+ try {
11811
+ $cmdConnection->writeRequest($command);
11812
+ } catch (CommunicationException $exception) {
11813
+ $exceptions[spl_object_hash($cmdConnection)] = $exception;
11814
+ }
11815
+ }
11816
+
11817
+ for ($i = 0; $i < $sizeOfPipe; $i++) {
11818
+ $command = $commands->dequeue();
11819
+
11820
+ $cmdConnection = $connection->getConnection($command);
11821
+ $connectionHash = spl_object_hash($cmdConnection);
11822
+
11823
+ if (isset($exceptions[$connectionHash])) {
11824
+ $responses[$i] = $exceptions[$connectionHash];
11825
+ continue;
11826
+ }
11827
+
11828
+ try {
11829
+ $responses[$i] = $cmdConnection->readResponse($command);
11830
+ } catch (CommunicationException $exception) {
11831
+ $responses[$i] = $exception;
11832
+ $exceptions[$connectionHash] = $exception;
11833
+ }
11834
+ }
11835
+
11836
+ return $responses;
11837
+ }
11838
+ }
11839
+
11840
+ /**
11841
+ * Command pipeline wrapped into a MULTI / EXEC transaction.
11842
+ *
11843
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11844
+ */
11845
+ class Atomic extends Pipeline
11846
+ {
11847
+ /**
11848
+ * {@inheritdoc}
11849
+ */
11850
+ public function __construct(ClientInterface $client)
11851
+ {
11852
+ if (!$client->getProfile()->supportsCommands(array('multi', 'exec', 'discard'))) {
11853
+ throw new ClientException(
11854
+ "The current profile does not support 'MULTI', 'EXEC' and 'DISCARD'."
11855
+ );
11856
+ }
11857
+
11858
+ parent::__construct($client);
11859
+ }
11860
+
11861
+ /**
11862
+ * {@inheritdoc}
11863
+ */
11864
+ protected function getConnection()
11865
+ {
11866
+ $connection = $this->getClient()->getConnection();
11867
+
11868
+ if (!$connection instanceof NodeConnectionInterface) {
11869
+ $class = __CLASS__;
11870
+
11871
+ throw new ClientException("The class '$class' does not support aggregate connections.");
11872
+ }
11873
+
11874
+ return $connection;
11875
+ }
11876
+
11877
+ /**
11878
+ * {@inheritdoc}
11879
+ */
11880
+ protected function executePipeline(ConnectionInterface $connection, SplQueue $commands)
11881
+ {
11882
+ $profile = $this->getClient()->getProfile();
11883
+ $connection->executeCommand($profile->createCommand('multi'));
11884
+
11885
+ foreach ($commands as $command) {
11886
+ $connection->writeRequest($command);
11887
+ }
11888
+
11889
+ foreach ($commands as $command) {
11890
+ $response = $connection->readResponse($command);
11891
+
11892
+ if ($response instanceof ErrorResponseInterface) {
11893
+ $connection->executeCommand($profile->createCommand('discard'));
11894
+ throw new ServerException($response->getMessage());
11895
+ }
11896
+ }
11897
+
11898
+ $executed = $connection->executeCommand($profile->createCommand('exec'));
11899
+
11900
+ if (!isset($executed)) {
11901
+ // TODO: should be throwing a more appropriate exception.
11902
+ throw new ClientException(
11903
+ 'The underlying transaction has been aborted by the server.'
11904
+ );
11905
+ }
11906
+
11907
+ if (count($executed) !== count($commands)) {
11908
+ $expected = count($commands);
11909
+ $received = count($executed);
11910
+
11911
+ throw new ClientException(
11912
+ "Invalid number of responses [expected $expected, received $received]."
11913
+ );
11914
+ }
11915
+
11916
+ $responses = array();
11917
+ $sizeOfPipe = count($commands);
11918
+ $exceptions = $this->throwServerExceptions();
11919
+
11920
+ for ($i = 0; $i < $sizeOfPipe; $i++) {
11921
+ $command = $commands->dequeue();
11922
+ $response = $executed[$i];
11923
+
11924
+ if (!$response instanceof ResponseInterface) {
11925
+ $responses[] = $command->parseResponse($response);
11926
+ } elseif ($response instanceof ErrorResponseInterface && $exceptions) {
11927
+ $this->exception($connection, $response);
11928
+ } else {
11929
+ $responses[] = $response;
11930
+ }
11931
+
11932
+ unset($executed[$i]);
11933
+ }
11934
+
11935
+ return $responses;
11936
+ }
11937
+ }
11938
+
11939
+ /* --------------------------------------------------------------------------- */
11940
+
11941
+ namespace Predis\Cluster\Distributor;
11942
+
11943
+ use Predis\Cluster\Hash\HashGeneratorInterface;
11944
+ use Exception;
11945
+
11946
+ /**
11947
+ * A distributor implements the logic to automatically distribute keys among
11948
+ * several nodes for client-side sharding.
11949
+ *
11950
+ * @author Daniele Alessandri <suppakilla@gmail.com>
11951
+ */
11952
+ interface DistributorInterface
11953
+ {
11954
+ /**
11955
+ * Adds a node to the distributor with an optional weight.
11956
+ *
11957
+ * @param mixed $node Node object.
11958
+ * @param int $weight Weight for the node.
11959
+ */
11960
+ public function add($node, $weight = null);
11961
+
11962
+ /**
11963
+ * Removes a node from the distributor.
11964
+ *
11965
+ * @param mixed $node Node object.
11966
+ */
11967
+ public function remove($node);
11968
+
11969
+ /**
11970
+ * Returns the corresponding slot of a node from the distributor using the
11971
+ * computed hash of a key.
11972
+ *
11973
+ * @param mixed $hash
11974
+ *
11975
+ * @return mixed
11976
+ */
11977
+ public function getSlot($hash);
11978
+
11979
+ /**
11980
+ * Returns a node from the distributor using its assigned slot ID.
11981
+ *
11982
+ * @param mixed $slot
11983
+ *
11984
+ * @return mixed|null
11985
+ */
11986
+ public function getBySlot($slot);
11987
+
11988
+ /**
11989
+ * Returns a node from the distributor using the computed hash of a key.
11990
+ *
11991
+ * @param mixed $hash
11992
+ *
11993
+ * @return mixed
11994
+ */
11995
+ public function getByHash($hash);
11996
+
11997
+ /**
11998
+ * Returns a node from the distributor mapping to the specified value.
11999
+ *
12000
+ * @param string $value
12001
+ *
12002
+ * @return mixed
12003
+ */
12004
+ public function get($value);
12005
+
12006
+ /**
12007
+ * Returns the underlying hash generator instance.
12008
+ *
12009
+ * @return HashGeneratorInterface
12010
+ */
12011
+ public function getHashGenerator();
12012
+ }
12013
+
12014
+ /**
12015
+ * This class implements an hashring-based distributor that uses the same
12016
+ * algorithm of memcache to distribute keys in a cluster using client-side
12017
+ * sharding.
12018
+ *
12019
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12020
+ * @author Lorenzo Castelli <lcastelli@gmail.com>
12021
+ */
12022
+ class HashRing implements DistributorInterface, HashGeneratorInterface
12023
+ {
12024
+ const DEFAULT_REPLICAS = 128;
12025
+ const DEFAULT_WEIGHT = 100;
12026
+
12027
+ private $ring;
12028
+ private $ringKeys;
12029
+ private $ringKeysCount;
12030
+ private $replicas;
12031
+ private $nodeHashCallback;
12032
+ private $nodes = array();
12033
+
12034
+ /**
12035
+ * @param int $replicas Number of replicas in the ring.
12036
+ * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
12037
+ */
12038
+ public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
12039
+ {
12040
+ $this->replicas = $replicas;
12041
+ $this->nodeHashCallback = $nodeHashCallback;
12042
+ }
12043
+
12044
+ /**
12045
+ * Adds a node to the ring with an optional weight.
12046
+ *
12047
+ * @param mixed $node Node object.
12048
+ * @param int $weight Weight for the node.
12049
+ */
12050
+ public function add($node, $weight = null)
12051
+ {
12052
+ // In case of collisions in the hashes of the nodes, the node added
12053
+ // last wins, thus the order in which nodes are added is significant.
12054
+ $this->nodes[] = array(
12055
+ 'object' => $node,
12056
+ 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT
12057
+ );
12058
+
12059
+ $this->reset();
12060
+ }
12061
+
12062
+ /**
12063
+ * {@inheritdoc}
12064
+ */
12065
+ public function remove($node)
12066
+ {
12067
+ // A node is removed by resetting the ring so that it's recreated from
12068
+ // scratch, in order to reassign possible hashes with collisions to the
12069
+ // right node according to the order in which they were added in the
12070
+ // first place.
12071
+ for ($i = 0; $i < count($this->nodes); ++$i) {
12072
+ if ($this->nodes[$i]['object'] === $node) {
12073
+ array_splice($this->nodes, $i, 1);
12074
+ $this->reset();
12075
+
12076
+ break;
12077
+ }
12078
+ }
12079
+ }
12080
+
12081
+ /**
12082
+ * Resets the distributor.
12083
+ */
12084
+ private function reset()
12085
+ {
12086
+ unset(
12087
+ $this->ring,
12088
+ $this->ringKeys,
12089
+ $this->ringKeysCount
12090
+ );
12091
+ }
12092
+
12093
+ /**
12094
+ * Returns the initialization status of the distributor.
12095
+ *
12096
+ * @return bool
12097
+ */
12098
+ private function isInitialized()
12099
+ {
12100
+ return isset($this->ringKeys);
12101
+ }
12102
+
12103
+ /**
12104
+ * Calculates the total weight of all the nodes in the distributor.
12105
+ *
12106
+ * @return int
12107
+ */
12108
+ private function computeTotalWeight()
12109
+ {
12110
+ $totalWeight = 0;
12111
+
12112
+ foreach ($this->nodes as $node) {
12113
+ $totalWeight += $node['weight'];
12114
+ }
12115
+
12116
+ return $totalWeight;
12117
+ }
12118
+
12119
+ /**
12120
+ * Initializes the distributor.
12121
+ */
12122
+ private function initialize()
12123
+ {
12124
+ if ($this->isInitialized()) {
12125
+ return;
12126
+ }
12127
+
12128
+ if (!$this->nodes) {
12129
+ throw new EmptyRingException('Cannot initialize an empty hashring.');
12130
+ }
12131
+
12132
+ $this->ring = array();
12133
+ $totalWeight = $this->computeTotalWeight();
12134
+ $nodesCount = count($this->nodes);
12135
+
12136
+ foreach ($this->nodes as $node) {
12137
+ $weightRatio = $node['weight'] / $totalWeight;
12138
+ $this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio);
12139
+ }
12140
+
12141
+ ksort($this->ring, SORT_NUMERIC);
12142
+ $this->ringKeys = array_keys($this->ring);
12143
+ $this->ringKeysCount = count($this->ringKeys);
12144
+ }
12145
+
12146
+ /**
12147
+ * Implements the logic needed to add a node to the hashring.
12148
+ *
12149
+ * @param array $ring Source hashring.
12150
+ * @param mixed $node Node object to be added.
12151
+ * @param int $totalNodes Total number of nodes.
12152
+ * @param int $replicas Number of replicas in the ring.
12153
+ * @param float $weightRatio Weight ratio for the node.
12154
+ */
12155
+ protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
12156
+ {
12157
+ $nodeObject = $node['object'];
12158
+ $nodeHash = $this->getNodeHash($nodeObject);
12159
+ $replicas = (int) round($weightRatio * $totalNodes * $replicas);
12160
+
12161
+ for ($i = 0; $i < $replicas; $i++) {
12162
+ $key = crc32("$nodeHash:$i");
12163
+ $ring[$key] = $nodeObject;
12164
+ }
12165
+ }
12166
+
12167
+ /**
12168
+ * {@inheritdoc}
12169
+ */
12170
+ protected function getNodeHash($nodeObject)
12171
+ {
12172
+ if (!isset($this->nodeHashCallback)) {
12173
+ return (string) $nodeObject;
12174
+ }
12175
+
12176
+ return call_user_func($this->nodeHashCallback, $nodeObject);
12177
+ }
12178
+
12179
+ /**
12180
+ * {@inheritdoc}
12181
+ */
12182
+ public function hash($value)
12183
+ {
12184
+ return crc32($value);
12185
+ }
12186
+
12187
+ /**
12188
+ * {@inheritdoc}
12189
+ */
12190
+ public function getByHash($hash)
12191
+ {
12192
+ return $this->ring[$this->getSlot($hash)];
12193
+ }
12194
+
12195
+ /**
12196
+ * {@inheritdoc}
12197
+ */
12198
+ public function getBySlot($slot)
12199
+ {
12200
+ $this->initialize();
12201
+
12202
+ if (isset($this->ring[$slot])) {
12203
+ return $this->ring[$slot];
12204
+ }
12205
+ }
12206
+
12207
+ /**
12208
+ * {@inheritdoc}
12209
+ */
12210
+ public function getSlot($hash)
12211
+ {
12212
+ $this->initialize();
12213
+
12214
+ $ringKeys = $this->ringKeys;
12215
+ $upper = $this->ringKeysCount - 1;
12216
+ $lower = 0;
12217
+
12218
+ while ($lower <= $upper) {
12219
+ $index = ($lower + $upper) >> 1;
12220
+ $item = $ringKeys[$index];
12221
+
12222
+ if ($item > $hash) {
12223
+ $upper = $index - 1;
12224
+ } elseif ($item < $hash) {
12225
+ $lower = $index + 1;
12226
+ } else {
12227
+ return $item;
12228
+ }
12229
+ }
12230
+
12231
+ return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)];
12232
+ }
12233
+
12234
+ /**
12235
+ * {@inheritdoc}
12236
+ */
12237
+ public function get($value)
12238
+ {
12239
+ $hash = $this->hash($value);
12240
+ $node = $this->getByHash($hash);
12241
+
12242
+ return $node;
12243
+ }
12244
+
12245
+ /**
12246
+ * Implements a strategy to deal with wrap-around errors during binary searches.
12247
+ *
12248
+ * @param int $upper
12249
+ * @param int $lower
12250
+ * @param int $ringKeysCount
12251
+ *
12252
+ * @return int
12253
+ */
12254
+ protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
12255
+ {
12256
+ // Binary search for the last item in ringkeys with a value less or
12257
+ // equal to the key. If no such item exists, return the last item.
12258
+ return $upper >= 0 ? $upper : $ringKeysCount - 1;
12259
+ }
12260
+
12261
+ /**
12262
+ * {@inheritdoc}
12263
+ */
12264
+ public function getHashGenerator()
12265
+ {
12266
+ return $this;
12267
+ }
12268
+ }
12269
+
12270
+ /**
12271
+ * This class implements an hashring-based distributor that uses the same
12272
+ * algorithm of libketama to distribute keys in a cluster using client-side
12273
+ * sharding.
12274
+ *
12275
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12276
+ * @author Lorenzo Castelli <lcastelli@gmail.com>
12277
+ */
12278
+ class KetamaRing extends HashRing
12279
+ {
12280
+ const DEFAULT_REPLICAS = 160;
12281
+
12282
+ /**
12283
+ * @param mixed $nodeHashCallback Callback returning a string used to calculate the hash of nodes.
12284
+ */
12285
+ public function __construct($nodeHashCallback = null)
12286
+ {
12287
+ parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
12288
+ }
12289
+
12290
+ /**
12291
+ * {@inheritdoc}
12292
+ */
12293
+ protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
12294
+ {
12295
+ $nodeObject = $node['object'];
12296
+ $nodeHash = $this->getNodeHash($nodeObject);
12297
+ $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4));
12298
+
12299
+ for ($i = 0; $i < $replicas; $i++) {
12300
+ $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
12301
+
12302
+ foreach ($unpackedDigest as $key) {
12303
+ $ring[$key] = $nodeObject;
12304
+ }
12305
+ }
12306
+ }
12307
+
12308
+ /**
12309
+ * {@inheritdoc}
12310
+ */
12311
+ public function hash($value)
12312
+ {
12313
+ $hash = unpack('V', md5($value, true));
12314
+
12315
+ return $hash[1];
12316
+ }
12317
+
12318
+ /**
12319
+ * {@inheritdoc}
12320
+ */
12321
+ protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
12322
+ {
12323
+ // Binary search for the first item in ringkeys with a value greater
12324
+ // or equal to the key. If no such item exists, return the first item.
12325
+ return $lower < $ringKeysCount ? $lower : 0;
12326
+ }
12327
+ }
12328
+
12329
+ /**
12330
+ * Exception class that identifies empty rings.
12331
+ *
12332
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12333
+ */
12334
+ class EmptyRingException extends Exception
12335
+ {
12336
+ }
12337
+
12338
+ /* --------------------------------------------------------------------------- */
12339
+
12340
+ namespace Predis\Response\Iterator;
12341
+
12342
+ use Predis\Connection\NodeConnectionInterface;
12343
+ use Iterator;
12344
+ use Countable;
12345
+ use Predis\Response\ResponseInterface;
12346
+ use OuterIterator;
12347
+ use InvalidArgumentException;
12348
+ use UnexpectedValueException;
12349
+
12350
+ /**
12351
+ * Iterator that abstracts the access to multibulk responses allowing them to be
12352
+ * consumed in a streamable fashion without keeping the whole payload in memory.
12353
+ *
12354
+ * This iterator does not support rewinding which means that the iteration, once
12355
+ * consumed, cannot be restarted.
12356
+ *
12357
+ * Always make sure that the whole iteration is consumed (or dropped) to prevent
12358
+ * protocol desynchronization issues.
12359
+ *
12360
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12361
+ */
12362
+ abstract class MultiBulkIterator implements Iterator, Countable, ResponseInterface
12363
+ {
12364
+ protected $current;
12365
+ protected $position;
12366
+ protected $size;
12367
+
12368
+ /**
12369
+ * {@inheritdoc}
12370
+ */
12371
+ public function rewind()
12372
+ {
12373
+ // NOOP
12374
+ }
12375
+
12376
+ /**
12377
+ * {@inheritdoc}
12378
+ */
12379
+ public function current()
12380
+ {
12381
+ return $this->current;
12382
+ }
12383
+
12384
+ /**
12385
+ * {@inheritdoc}
12386
+ */
12387
+ public function key()
12388
+ {
12389
+ return $this->position;
12390
+ }
12391
+
12392
+ /**
12393
+ * {@inheritdoc}
12394
+ */
12395
+ public function next()
12396
+ {
12397
+ if (++$this->position < $this->size) {
12398
+ $this->current = $this->getValue();
12399
+ }
12400
+ }
12401
+
12402
+ /**
12403
+ * {@inheritdoc}
12404
+ */
12405
+ public function valid()
12406
+ {
12407
+ return $this->position < $this->size;
12408
+ }
12409
+
12410
+ /**
12411
+ * Returns the number of items comprising the whole multibulk response.
12412
+ *
12413
+ * This method should be used instead of iterator_count() to get the size of
12414
+ * the current multibulk response since the former consumes the iteration to
12415
+ * count the number of elements, but our iterators do not support rewinding.
12416
+ *
12417
+ * @return int
12418
+ */
12419
+ public function count()
12420
+ {
12421
+ return $this->size;
12422
+ }
12423
+
12424
+ /**
12425
+ * Returns the current position of the iterator.
12426
+ *
12427
+ * @return int
12428
+ */
12429
+ public function getPosition()
12430
+ {
12431
+ return $this->position;
12432
+ }
12433
+
12434
+ /**
12435
+ * {@inheritdoc}
12436
+ */
12437
+ abstract protected function getValue();
12438
+ }
12439
+
12440
+ /**
12441
+ * Streamable multibulk response.
12442
+ *
12443
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12444
+ */
12445
+ class MultiBulk extends MultiBulkIterator
12446
+ {
12447
+ private $connection;
12448
+
12449
+ /**
12450
+ * @param NodeConnectionInterface $connection Connection to Redis.
12451
+ * @param int $size Number of elements of the multibulk response.
12452
+ */
12453
+ public function __construct(NodeConnectionInterface $connection, $size)
12454
+ {
12455
+ $this->connection = $connection;
12456
+ $this->size = $size;
12457
+ $this->position = 0;
12458
+ $this->current = $size > 0 ? $this->getValue() : null;
12459
+ }
12460
+
12461
+ /**
12462
+ * Handles the synchronization of the client with the Redis protocol when
12463
+ * the garbage collector kicks in (e.g. when the iterator goes out of the
12464
+ * scope of a foreach or it is unset).
12465
+ */
12466
+ public function __destruct()
12467
+ {
12468
+ $this->drop(true);
12469
+ }
12470
+
12471
+ /**
12472
+ * Drop queued elements that have not been read from the connection either
12473
+ * by consuming the rest of the multibulk response or quickly by closing the
12474
+ * underlying connection.
12475
+ *
12476
+ * @param bool $disconnect Consume the iterator or drop the connection.
12477
+ */
12478
+ public function drop($disconnect = false)
12479
+ {
12480
+ if ($disconnect) {
12481
+ if ($this->valid()) {
12482
+ $this->position = $this->size;
12483
+ $this->connection->disconnect();
12484
+ }
12485
+ } else {
12486
+ while ($this->valid()) {
12487
+ $this->next();
12488
+ }
12489
+ }
12490
+ }
12491
+
12492
+ /**
12493
+ * Reads the next item of the multibulk response from the connection.
12494
+ *
12495
+ * @return mixed
12496
+ */
12497
+ protected function getValue()
12498
+ {
12499
+ return $this->connection->read();
12500
+ }
12501
+ }
12502
+
12503
+ /**
12504
+ * Outer iterator consuming streamable multibulk responses by yielding tuples of
12505
+ * keys and values.
12506
+ *
12507
+ * This wrapper is useful for responses to commands such as `HGETALL` that can
12508
+ * be iterater as $key => $value pairs.
12509
+ *
12510
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12511
+ */
12512
+ class MultiBulkTuple extends MultiBulk implements OuterIterator
12513
+ {
12514
+ private $iterator;
12515
+
12516
+ /**
12517
+ * @param MultiBulk $iterator Inner multibulk response iterator.
12518
+ */
12519
+ public function __construct(MultiBulk $iterator)
12520
+ {
12521
+ $this->checkPreconditions($iterator);
12522
+
12523
+ $this->size = count($iterator) / 2;
12524
+ $this->iterator = $iterator;
12525
+ $this->position = $iterator->getPosition();
12526
+ $this->current = $this->size > 0 ? $this->getValue() : null;
12527
+ }
12528
+
12529
+ /**
12530
+ * Checks for valid preconditions.
12531
+ *
12532
+ * @param MultiBulk $iterator Inner multibulk response iterator.
12533
+ *
12534
+ * @throws \InvalidArgumentException
12535
+ * @throws \UnexpectedValueException
12536
+ */
12537
+ protected function checkPreconditions(MultiBulk $iterator)
12538
+ {
12539
+ if ($iterator->getPosition() !== 0) {
12540
+ throw new InvalidArgumentException(
12541
+ 'Cannot initialize a tuple iterator using an already initiated iterator.'
12542
+ );
12543
+ }
12544
+
12545
+ if (($size = count($iterator)) % 2 !== 0) {
12546
+ throw new UnexpectedValueException("Invalid response size for a tuple iterator.");
12547
+ }
12548
+ }
12549
+
12550
+ /**
12551
+ * {@inheritdoc}
12552
+ */
12553
+ public function getInnerIterator()
12554
+ {
12555
+ return $this->iterator;
12556
+ }
12557
+
12558
+ /**
12559
+ * {@inheritdoc}
12560
+ */
12561
+ public function __destruct()
12562
+ {
12563
+ $this->iterator->drop(true);
12564
+ }
12565
+
12566
+ /**
12567
+ * {@inheritdoc}
12568
+ */
12569
+ protected function getValue()
12570
+ {
12571
+ $k = $this->iterator->current();
12572
+ $this->iterator->next();
12573
+
12574
+ $v = $this->iterator->current();
12575
+ $this->iterator->next();
12576
+
12577
+ return array($k, $v);
12578
+ }
12579
+ }
12580
+
12581
+ /* --------------------------------------------------------------------------- */
12582
+
12583
+ namespace Predis\Cluster\Hash;
12584
+
12585
+ /**
12586
+ * An hash generator implements the logic used to calculate the hash of a key to
12587
+ * distribute operations among Redis nodes.
12588
+ *
12589
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12590
+ */
12591
+ interface HashGeneratorInterface
12592
+ {
12593
+ /**
12594
+ * Generates an hash from a string to be used for distribution.
12595
+ *
12596
+ * @param string $value String value.
12597
+ *
12598
+ * @return int
12599
+ */
12600
+ public function hash($value);
12601
+ }
12602
+
12603
+ /**
12604
+ * Hash generator implementing the CRC-CCITT-16 algorithm used by redis-cluster.
12605
+ *
12606
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12607
+ */
12608
+ class CRC16 implements HashGeneratorInterface
12609
+ {
12610
+ private static $CCITT_16 = array(
12611
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
12612
+ 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
12613
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
12614
+ 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
12615
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
12616
+ 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
12617
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
12618
+ 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
12619
+ 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
12620
+ 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
12621
+ 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
12622
+ 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
12623
+ 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
12624
+ 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
12625
+ 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
12626
+ 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
12627
+ 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
12628
+ 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
12629
+ 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
12630
+ 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
12631
+ 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
12632
+ 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
12633
+ 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
12634
+ 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
12635
+ 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
12636
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
12637
+ 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
12638
+ 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
12639
+ 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
12640
+ 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
12641
+ 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
12642
+ 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
12643
+ );
12644
+
12645
+ /**
12646
+ * {@inheritdoc}
12647
+ */
12648
+ public function hash($value)
12649
+ {
12650
+ // CRC-CCITT-16 algorithm
12651
+ $crc = 0;
12652
+ $CCITT_16 = self::$CCITT_16;
12653
+ $strlen = strlen($value);
12654
+
12655
+ for ($i = 0; $i < $strlen; $i++) {
12656
+ $crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
12657
+ }
12658
+
12659
+ return $crc;
12660
+ }
12661
+ }
12662
+
12663
+ /* --------------------------------------------------------------------------- */
12664
+
12665
+ namespace Predis\Command\Processor;
12666
+
12667
+ use InvalidArgumentException;
12668
+ use Predis\Command\CommandInterface;
12669
+ use Predis\Command\PrefixableCommandInterface;
12670
+ use ArrayAccess;
12671
+ use ArrayIterator;
12672
+
12673
+ /**
12674
+ * A command processor processes Redis commands before they are sent to Redis.
12675
+ *
12676
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12677
+ */
12678
+ interface ProcessorInterface
12679
+ {
12680
+ /**
12681
+ * Processes the given Redis command.
12682
+ *
12683
+ * @param CommandInterface $command Command instance.
12684
+ */
12685
+ public function process(CommandInterface $command);
12686
+ }
12687
+
12688
+ /**
12689
+ * Default implementation of a command processors chain.
12690
+ *
12691
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12692
+ */
12693
+ class ProcessorChain implements ArrayAccess, ProcessorInterface
12694
+ {
12695
+ private $processors = array();
12696
+
12697
+ /**
12698
+ * @param array $processors List of instances of ProcessorInterface.
12699
+ */
12700
+ public function __construct($processors = array())
12701
+ {
12702
+ foreach ($processors as $processor) {
12703
+ $this->add($processor);
12704
+ }
12705
+ }
12706
+
12707
+ /**
12708
+ * {@inheritdoc}
12709
+ */
12710
+ public function add(ProcessorInterface $processor)
12711
+ {
12712
+ $this->processors[] = $processor;
12713
+ }
12714
+
12715
+ /**
12716
+ * {@inheritdoc}
12717
+ */
12718
+ public function remove(ProcessorInterface $processor)
12719
+ {
12720
+ if (false !== $index = array_search($processor, $this->processors, true)) {
12721
+ unset($this[$index]);
12722
+ }
12723
+ }
12724
+
12725
+ /**
12726
+ * {@inheritdoc}
12727
+ */
12728
+ public function process(CommandInterface $command)
12729
+ {
12730
+ for ($i = 0; $i < $count = count($this->processors); $i++) {
12731
+ $this->processors[$i]->process($command);
12732
+ }
12733
+ }
12734
+
12735
+ /**
12736
+ * {@inheritdoc}
12737
+ */
12738
+ public function getProcessors()
12739
+ {
12740
+ return $this->processors;
12741
+ }
12742
+
12743
+ /**
12744
+ * Returns an iterator over the list of command processor in the chain.
12745
+ *
12746
+ * @return ArrayIterator
12747
+ */
12748
+ public function getIterator()
12749
+ {
12750
+ return new ArrayIterator($this->processors);
12751
+ }
12752
+
12753
+ /**
12754
+ * Returns the number of command processors in the chain.
12755
+ *
12756
+ * @return int
12757
+ */
12758
+ public function count()
12759
+ {
12760
+ return count($this->processors);
12761
+ }
12762
+
12763
+ /**
12764
+ * {@inheritdoc}
12765
+ */
12766
+ public function offsetExists($index)
12767
+ {
12768
+ return isset($this->processors[$index]);
12769
+ }
12770
+
12771
+ /**
12772
+ * {@inheritdoc}
12773
+ */
12774
+ public function offsetGet($index)
12775
+ {
12776
+ return $this->processors[$index];
12777
+ }
12778
+
12779
+ /**
12780
+ * {@inheritdoc}
12781
+ */
12782
+ public function offsetSet($index, $processor)
12783
+ {
12784
+ if (!$processor instanceof ProcessorInterface) {
12785
+ throw new InvalidArgumentException(
12786
+ "A processor chain accepts only instances of ".
12787
+ "'Predis\Command\Processor\ProcessorInterface'."
12788
+ );
12789
+ }
12790
+
12791
+ $this->processors[$index] = $processor;
12792
+ }
12793
+
12794
+ /**
12795
+ * {@inheritdoc}
12796
+ */
12797
+ public function offsetUnset($index)
12798
+ {
12799
+ unset($this->processors[$index]);
12800
+ $this->processors = array_values($this->processors);
12801
+ }
12802
+ }
12803
+
12804
+ /**
12805
+ * Command processor capable of prefixing keys stored in the arguments of Redis
12806
+ * commands supported.
12807
+ *
12808
+ * @author Daniele Alessandri <suppakilla@gmail.com>
12809
+ */
12810
+ class KeyPrefixProcessor implements ProcessorInterface
12811
+ {
12812
+ private $prefix;
12813
+ private $commands;
12814
+
12815
+ /**
12816
+ * @param string $prefix Prefix for the keys.
12817
+ */
12818
+ public function __construct($prefix)
12819
+ {
12820
+ $this->prefix = $prefix;
12821
+ $this->commands = array(
12822
+ /* ---------------- Redis 1.2 ---------------- */
12823
+ 'EXISTS' => 'self::first',
12824
+ 'DEL' => 'self::all',
12825
+ 'TYPE' => 'self::first',
12826
+ 'KEYS' => 'self::first',
12827
+ 'RENAME' => 'self::all',
12828
+ 'RENAMENX' => 'self::all',
12829
+ 'EXPIRE' => 'self::first',
12830
+ 'EXPIREAT' => 'self::first',
12831
+ 'TTL' => 'self::first',
12832
+ 'MOVE' => 'self::first',
12833
+ 'SORT' => 'self::sort',
12834
+ 'DUMP' => 'self::first',
12835
+ 'RESTORE' => 'self::first',
12836
+ 'SET' => 'self::first',
12837
+ 'SETNX' => 'self::first',
12838
+ 'MSET' => 'self::interleaved',
12839
+ 'MSETNX' => 'self::interleaved',
12840
+ 'GET' => 'self::first',
12841
+ 'MGET' => 'self::all',
12842
+ 'GETSET' => 'self::first',
12843
+ 'INCR' => 'self::first',
12844
+ 'INCRBY' => 'self::first',
12845
+ 'DECR' => 'self::first',
12846
+ 'DECRBY' => 'self::first',
12847
+ 'RPUSH' => 'self::first',
12848
+ 'LPUSH' => 'self::first',
12849
+ 'LLEN' => 'self::first',
12850
+ 'LRANGE' => 'self::first',
12851
+ 'LTRIM' => 'self::first',
12852
+ 'LINDEX' => 'self::first',
12853
+ 'LSET' => 'self::first',
12854
+ 'LREM' => 'self::first',
12855
+ 'LPOP' => 'self::first',
12856
+ 'RPOP' => 'self::first',
12857
+ 'RPOPLPUSH' => 'self::all',
12858
+ 'SADD' => 'self::first',
12859
+ 'SREM' => 'self::first',
12860
+ 'SPOP' => 'self::first',
12861
+ 'SMOVE' => 'self::skipLast',
12862
+ 'SCARD' => 'self::first',
12863
+ 'SISMEMBER' => 'self::first',
12864
+ 'SINTER' => 'self::all',
12865
+ 'SINTERSTORE' => 'self::all',
12866
+ 'SUNION' => 'self::all',
12867
+ 'SUNIONSTORE' => 'self::all',
12868
+ 'SDIFF' => 'self::all',
12869
+ 'SDIFFSTORE' => 'self::all',
12870
+ 'SMEMBERS' => 'self::first',
12871
+ 'SRANDMEMBER' => 'self::first',
12872
+ 'ZADD' => 'self::first',
12873
+ 'ZINCRBY' => 'self::first',
12874
+ 'ZREM' => 'self::first',
12875
+ 'ZRANGE' => 'self::first',
12876
+ 'ZREVRANGE' => 'self::first',
12877
+ 'ZRANGEBYSCORE' => 'self::first',
12878
+ 'ZCARD' => 'self::first',
12879
+ 'ZSCORE' => 'self::first',
12880
+ 'ZREMRANGEBYSCORE' => 'self::first',
12881
+ /* ---------------- Redis 2.0 ---------------- */
12882
+ 'SETEX' => 'self::first',
12883
+ 'APPEND' => 'self::first',
12884
+ 'SUBSTR' => 'self::first',
12885
+ 'BLPOP' => 'self::skipLast',
12886
+ 'BRPOP' => 'self::skipLast',
12887
+ 'ZUNIONSTORE' => 'self::zsetStore',
12888
+ 'ZINTERSTORE' => 'self::zsetStore',
12889
+ 'ZCOUNT' => 'self::first',
12890
+ 'ZRANK' => 'self::first',
12891
+ 'ZREVRANK' => 'self::first',
12892
+ 'ZREMRANGEBYRANK' => 'self::first',
12893
+ 'HSET' => 'self::first',
12894
+ 'HSETNX' => 'self::first',
12895
+ 'HMSET' => 'self::first',
12896
+ 'HINCRBY' => 'self::first',
12897
+ 'HGET' => 'self::first',
12898
+ 'HMGET' => 'self::first',
12899
+ 'HDEL' => 'self::first',
12900
+ 'HEXISTS' => 'self::first',
12901
+ 'HLEN' => 'self::first',
12902
+ 'HKEYS' => 'self::first',
12903
+ 'HVALS' => 'self::first',
12904
+ 'HGETALL' => 'self::first',
12905
+ 'SUBSCRIBE' => 'self::all',
12906
+ 'UNSUBSCRIBE' => 'self::all',
12907
+ 'PSUBSCRIBE' => 'self::all',
12908
+ 'PUNSUBSCRIBE' => 'self::all',
12909
+ 'PUBLISH' => 'self::first',
12910
+ /* ---------------- Redis 2.2 ---------------- */
12911
+ 'PERSIST' => 'self::first',
12912
+ 'STRLEN' => 'self::first',
12913
+ 'SETRANGE' => 'self::first',
12914
+ 'GETRANGE' => 'self::first',
12915
+ 'SETBIT' => 'self::first',
12916
+ 'GETBIT' => 'self::first',
12917
+ 'RPUSHX' => 'self::first',
12918
+ 'LPUSHX' => 'self::first',
12919
+ 'LINSERT' => 'self::first',
12920
+ 'BRPOPLPUSH' => 'self::skipLast',
12921
+ 'ZREVRANGEBYSCORE' => 'self::first',
12922
+ 'WATCH' => 'self::all',
12923
+ /* ---------------- Redis 2.6 ---------------- */
12924
+ 'PTTL' => 'self::first',
12925
+ 'PEXPIRE' => 'self::first',
12926
+ 'PEXPIREAT' => 'self::first',
12927
+ 'PSETEX' => 'self::first',
12928
+ 'INCRBYFLOAT' => 'self::first',
12929
+ 'BITOP' => 'self::skipFirst',
12930
+ 'BITCOUNT' => 'self::first',
12931
+ 'HINCRBYFLOAT' => 'self::first',
12932
+ 'EVAL' => 'self::evalKeys',
12933
+ 'EVALSHA' => 'self::evalKeys',
12934
+ /* ---------------- Redis 2.8 ---------------- */
12935
+ 'SSCAN' => 'self::first',
12936
+ 'ZSCAN' => 'self::first',
12937
+ 'HSCAN' => 'self::first',
12938
+ 'PFADD' => 'self::first',
12939
+ 'PFCOUNT' => 'self::all',
12940
+ 'PFMERGE' => 'self::all',
12941
+ 'ZLEXCOUNT' => 'self::first',
12942
+ 'ZRANGEBYLEX' => 'self::first',
12943
+ 'ZREMRANGEBYLEX' => 'self::first',
12944
+ );
12945
+ }
12946
+
12947
+ /**
12948
+ * Sets a prefix that is applied to all the keys.
12949
+ *
12950
+ * @param string $prefix Prefix for the keys.
12951
+ */
12952
+ public function setPrefix($prefix)
12953
+ {
12954
+ $this->prefix = $prefix;
12955
+ }
12956
+
12957
+ /**
12958
+ * Gets the current prefix.
12959
+ *
12960
+ * @return string
12961
+ */
12962
+ public function getPrefix()
12963
+ {
12964
+ return $this->prefix;
12965
+ }
12966
+
12967
+ /**
12968
+ * {@inheritdoc}
12969
+ */
12970
+ public function process(CommandInterface $command)
12971
+ {
12972
+ if ($command instanceof PrefixableCommandInterface) {
12973
+ $command->prefixKeys($this->prefix);
12974
+ } elseif (isset($this->commands[$commandID = strtoupper($command->getId())])) {
12975
+ call_user_func($this->commands[$commandID], $command, $this->prefix);
12976
+ }
12977
+ }
12978
+
12979
+ /**
12980
+ * Sets an handler for the specified command ID.
12981
+ *
12982
+ * The callback signature must have 2 parameters of the following types:
12983
+ *
12984
+ * - Predis\Command\CommandInterface (command instance)
12985
+ * - String (prefix)
12986
+ *
12987
+ * When the callback argument is omitted or NULL, the previously
12988
+ * associated handler for the specified command ID is removed.
12989
+ *
12990
+ * @param string $commandID The ID of the command to be handled.
12991
+ * @param mixed $callback A valid callable object or NULL.
12992
+ *
12993
+ * @throws \InvalidArgumentException
12994
+ */
12995
+ public function setCommandHandler($commandID, $callback = null)
12996
+ {
12997
+ $commandID = strtoupper($commandID);
12998
+
12999
+ if (!isset($callback)) {
13000
+ unset($this->commands[$commandID]);
13001
+
13002
+ return;
13003
+ }
13004
+
13005
+ if (!is_callable($callback)) {
13006
+ throw new InvalidArgumentException(
13007
+ "Callback must be a valid callable object or NULL"
13008
+ );
13009
+ }
13010
+
13011
+ $this->commands[$commandID] = $callback;
13012
+ }
13013
+
13014
+ /**
13015
+ * {@inheritdoc}
13016
+ */
13017
+ public function __toString()
13018
+ {
13019
+ return $this->getPrefix();
13020
+ }
13021
+
13022
+ /**
13023
+ * Applies the specified prefix only the first argument.
13024
+ *
13025
+ * @param CommandInterface $command Command instance.
13026
+ * @param string $prefix Prefix string.
13027
+ */
13028
+ public static function first(CommandInterface $command, $prefix)
13029
+ {
13030
+ if ($arguments = $command->getArguments()) {
13031
+ $arguments[0] = "$prefix{$arguments[0]}";
13032
+ $command->setRawArguments($arguments);
13033
+ }
13034
+ }
13035
+
13036
+ /**
13037
+ * Applies the specified prefix to all the arguments.
13038
+ *
13039
+ * @param CommandInterface $command Command instance.
13040
+ * @param string $prefix Prefix string.
13041
+ */
13042
+ public static function all(CommandInterface $command, $prefix)
13043
+ {
13044
+ if ($arguments = $command->getArguments()) {
13045
+ foreach ($arguments as &$key) {
13046
+ $key = "$prefix$key";
13047
+ }
13048
+
13049
+ $command->setRawArguments($arguments);
13050
+ }
13051
+ }
13052
+
13053
+ /**
13054
+ * Applies the specified prefix only to even arguments in the list.
13055
+ *
13056
+ * @param CommandInterface $command Command instance.
13057
+ * @param string $prefix Prefix string.
13058
+ */
13059
+ public static function interleaved(CommandInterface $command, $prefix)
13060
+ {
13061
+ if ($arguments = $command->getArguments()) {
13062
+ $length = count($arguments);
13063
+
13064
+ for ($i = 0; $i < $length; $i += 2) {
13065
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13066
+ }
13067
+
13068
+ $command->setRawArguments($arguments);
13069
+ }
13070
+ }
13071
+
13072
+ /**
13073
+ * Applies the specified prefix to all the arguments but the first one.
13074
+ *
13075
+ * @param CommandInterface $command Command instance.
13076
+ * @param string $prefix Prefix string.
13077
+ */
13078
+ public static function skipFirst(CommandInterface $command, $prefix)
13079
+ {
13080
+ if ($arguments = $command->getArguments()) {
13081
+ $length = count($arguments);
13082
+
13083
+ for ($i = 1; $i < $length; $i++) {
13084
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13085
+ }
13086
+
13087
+ $command->setRawArguments($arguments);
13088
+ }
13089
+ }
13090
+
13091
+ /**
13092
+ * Applies the specified prefix to all the arguments but the last one.
13093
+ *
13094
+ * @param CommandInterface $command Command instance.
13095
+ * @param string $prefix Prefix string.
13096
+ */
13097
+ public static function skipLast(CommandInterface $command, $prefix)
13098
+ {
13099
+ if ($arguments = $command->getArguments()) {
13100
+ $length = count($arguments);
13101
+
13102
+ for ($i = 0; $i < $length - 1; $i++) {
13103
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13104
+ }
13105
+
13106
+ $command->setRawArguments($arguments);
13107
+ }
13108
+ }
13109
+
13110
+ /**
13111
+ * Applies the specified prefix to the keys of a SORT command.
13112
+ *
13113
+ * @param CommandInterface $command Command instance.
13114
+ * @param string $prefix Prefix string.
13115
+ */
13116
+ public static function sort(CommandInterface $command, $prefix)
13117
+ {
13118
+ if ($arguments = $command->getArguments()) {
13119
+ $arguments[0] = "$prefix{$arguments[0]}";
13120
+
13121
+ if (($count = count($arguments)) > 1) {
13122
+ for ($i = 1; $i < $count; $i++) {
13123
+ switch ($arguments[$i]) {
13124
+ case 'BY':
13125
+ case 'STORE':
13126
+ $arguments[$i] = "$prefix{$arguments[++$i]}";
13127
+ break;
13128
+
13129
+ case 'GET':
13130
+ $value = $arguments[++$i];
13131
+ if ($value !== '#') {
13132
+ $arguments[$i] = "$prefix$value";
13133
+ }
13134
+ break;
13135
+
13136
+ case 'LIMIT';
13137
+ $i += 2;
13138
+ break;
13139
+ }
13140
+ }
13141
+ }
13142
+
13143
+ $command->setRawArguments($arguments);
13144
+ }
13145
+ }
13146
+
13147
+ /**
13148
+ * Applies the specified prefix to the keys of an EVAL-based command.
13149
+ *
13150
+ * @param CommandInterface $command Command instance.
13151
+ * @param string $prefix Prefix string.
13152
+ */
13153
+ public static function evalKeys(CommandInterface $command, $prefix)
13154
+ {
13155
+ if ($arguments = $command->getArguments()) {
13156
+ for ($i = 2; $i < $arguments[1] + 2; $i++) {
13157
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13158
+ }
13159
+
13160
+ $command->setRawArguments($arguments);
13161
+ }
13162
+ }
13163
+
13164
+ /**
13165
+ * Applies the specified prefix to the keys of Z[INTERSECTION|UNION]STORE.
13166
+ *
13167
+ * @param CommandInterface $command Command instance.
13168
+ * @param string $prefix Prefix string.
13169
+ */
13170
+ public static function zsetStore(CommandInterface $command, $prefix)
13171
+ {
13172
+ if ($arguments = $command->getArguments()) {
13173
+ $arguments[0] = "$prefix{$arguments[0]}";
13174
+ $length = ((int) $arguments[1]) + 2;
13175
+
13176
+ for ($i = 2; $i < $length; $i++) {
13177
+ $arguments[$i] = "$prefix{$arguments[$i]}";
13178
+ }
13179
+
13180
+ $command->setRawArguments($arguments);
13181
+ }
13182
+ }
13183
+ }
13184
+
13185
+ /* --------------------------------------------------------------------------- */
13186
+
13187
+ namespace Predis\Protocol\Text;
13188
+
13189
+ use Predis\Command\CommandInterface;
13190
+ use Predis\Connection\CompositeConnectionInterface;
13191
+ use Predis\Protocol\ProtocolProcessorInterface;
13192
+ use Predis\Protocol\RequestSerializerInterface;
13193
+ use Predis\Protocol\ResponseReaderInterface;
13194
+ use Predis\CommunicationException;
13195
+ use Predis\Protocol\ProtocolException;
13196
+ use Predis\Response\Status as StatusResponse;
13197
+ use Predis\Response\Error as ErrorResponse;
13198
+ use Predis\Response\Iterator\MultiBulk as MultiBulkIterator;
13199
+
13200
+ /**
13201
+ * Response reader for the standard Redis wire protocol.
13202
+ *
13203
+ * @link http://redis.io/topics/protocol
13204
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13205
+ */
13206
+ class ResponseReader implements ResponseReaderInterface
13207
+ {
13208
+ protected $handlers;
13209
+
13210
+ /**
13211
+ *
13212
+ */
13213
+ public function __construct()
13214
+ {
13215
+ $this->handlers = $this->getDefaultHandlers();
13216
+ }
13217
+
13218
+ /**
13219
+ * Returns the default handlers for the supported type of responses.
13220
+ *
13221
+ * @return array
13222
+ */
13223
+ protected function getDefaultHandlers()
13224
+ {
13225
+ return array(
13226
+ '+' => new Handler\StatusResponse(),
13227
+ '-' => new Handler\ErrorResponse(),
13228
+ ':' => new Handler\IntegerResponse(),
13229
+ '$' => new Handler\BulkResponse(),
13230
+ '*' => new Handler\MultiBulkResponse(),
13231
+ );
13232
+ }
13233
+
13234
+ /**
13235
+ * Sets the handler for the specified prefix identifying the response type.
13236
+ *
13237
+ * @param string $prefix Identifier of the type of response.
13238
+ * @param Handler\ResponseHandlerInterface $handler Response handler.
13239
+ */
13240
+ public function setHandler($prefix, Handler\ResponseHandlerInterface $handler)
13241
+ {
13242
+ $this->handlers[$prefix] = $handler;
13243
+ }
13244
+
13245
+ /**
13246
+ * Returns the response handler associated to a certain type of response.
13247
+ *
13248
+ * @param string $prefix Identifier of the type of response.
13249
+ *
13250
+ * @return Handler\ResponseHandlerInterface
13251
+ */
13252
+ public function getHandler($prefix)
13253
+ {
13254
+ if (isset($this->handlers[$prefix])) {
13255
+ return $this->handlers[$prefix];
13256
+ }
13257
+
13258
+ return null;
13259
+ }
13260
+
13261
+ /**
13262
+ * {@inheritdoc}
13263
+ */
13264
+ public function read(CompositeConnectionInterface $connection)
13265
+ {
13266
+ $header = $connection->readLine();
13267
+
13268
+ if ($header === '') {
13269
+ $this->onProtocolError($connection, 'Unexpected empty reponse header.');
13270
+ }
13271
+
13272
+ $prefix = $header[0];
13273
+
13274
+ if (!isset($this->handlers[$prefix])) {
13275
+ $this->onProtocolError($connection, "Unknown response prefix: '$prefix'.");
13276
+ }
13277
+
13278
+ $payload = $this->handlers[$prefix]->handle($connection, substr($header, 1));
13279
+
13280
+ return $payload;
13281
+ }
13282
+
13283
+ /**
13284
+ * Handles protocol errors generated while reading responses from a
13285
+ * connection.
13286
+ *
13287
+ * @param CompositeConnectionInterface $connection Redis connection that generated the error.
13288
+ * @param string $message Error message.
13289
+ */
13290
+ protected function onProtocolError(CompositeConnectionInterface $connection, $message)
13291
+ {
13292
+ CommunicationException::handle(
13293
+ new ProtocolException($connection, $message)
13294
+ );
13295
+ }
13296
+ }
13297
+
13298
+ /**
13299
+ * Request serializer for the standard Redis wire protocol.
13300
+ *
13301
+ * @link http://redis.io/topics/protocol
13302
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13303
+ */
13304
+ class RequestSerializer implements RequestSerializerInterface
13305
+ {
13306
+ /**
13307
+ * {@inheritdoc}
13308
+ */
13309
+ public function serialize(CommandInterface $command)
13310
+ {
13311
+ $commandID = $command->getId();
13312
+ $arguments = $command->getArguments();
13313
+
13314
+ $cmdlen = strlen($commandID);
13315
+ $reqlen = count($arguments) + 1;
13316
+
13317
+ $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
13318
+
13319
+ for ($i = 0, $reqlen--; $i < $reqlen; $i++) {
13320
+ $argument = $arguments[$i];
13321
+ $arglen = strlen($argument);
13322
+ $buffer .= "\${$arglen}\r\n{$argument}\r\n";
13323
+ }
13324
+
13325
+ return $buffer;
13326
+ }
13327
+ }
13328
+
13329
+ /**
13330
+ * Protocol processor for the standard Redis wire protocol.
13331
+ *
13332
+ * @link http://redis.io/topics/protocol
13333
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13334
+ */
13335
+ class ProtocolProcessor implements ProtocolProcessorInterface
13336
+ {
13337
+ protected $mbiterable;
13338
+ protected $serializer;
13339
+
13340
+ /**
13341
+ *
13342
+ */
13343
+ public function __construct()
13344
+ {
13345
+ $this->mbiterable = false;
13346
+ $this->serializer = new RequestSerializer();
13347
+ }
13348
+
13349
+ /**
13350
+ * {@inheritdoc}
13351
+ */
13352
+ public function write(CompositeConnectionInterface $connection, CommandInterface $command)
13353
+ {
13354
+ $request = $this->serializer->serialize($command);
13355
+ $connection->writeBuffer($request);
13356
+ }
13357
+
13358
+ /**
13359
+ * {@inheritdoc}
13360
+ */
13361
+ public function read(CompositeConnectionInterface $connection)
13362
+ {
13363
+ $chunk = $connection->readLine();
13364
+ $prefix = $chunk[0];
13365
+ $payload = substr($chunk, 1);
13366
+
13367
+ switch ($prefix) {
13368
+ case '+':
13369
+ return new StatusResponse($payload);
13370
+
13371
+ case '$':
13372
+ $size = (int) $payload;
13373
+ if ($size === -1) {
13374
+ return null;
13375
+ }
13376
+
13377
+ return substr($connection->readBuffer($size + 2), 0, -2);
13378
+
13379
+ case '*':
13380
+ $count = (int) $payload;
13381
+
13382
+ if ($count === -1) {
13383
+ return null;
13384
+ }
13385
+ if ($this->mbiterable) {
13386
+ return new MultiBulkIterator($connection, $count);
13387
+ }
13388
+
13389
+ $multibulk = array();
13390
+
13391
+ for ($i = 0; $i < $count; $i++) {
13392
+ $multibulk[$i] = $this->read($connection);
13393
+ }
13394
+
13395
+ return $multibulk;
13396
+
13397
+ case ':':
13398
+ return (int) $payload;
13399
+
13400
+ case '-':
13401
+ return new ErrorResponse($payload);
13402
+
13403
+ default:
13404
+ CommunicationException::handle(new ProtocolException(
13405
+ $connection, "Unknown response prefix: '$prefix'."
13406
+ ));
13407
+
13408
+ return;
13409
+ }
13410
+ }
13411
+
13412
+ /**
13413
+ * Enables or disables returning multibulk responses as specialized PHP
13414
+ * iterators used to stream bulk elements of a multibulk response instead
13415
+ * returning a plain array.
13416
+ *
13417
+ * Streamable multibulk responses are not globally supported by the
13418
+ * abstractions built-in into Predis, such as transactions or pipelines.
13419
+ * Use them with care!
13420
+ *
13421
+ * @param bool $value Enable or disable streamable multibulk responses.
13422
+ */
13423
+ public function useIterableMultibulk($value)
13424
+ {
13425
+ $this->mbiterable = (bool) $value;
13426
+ }
13427
+ }
13428
+
13429
+ /**
13430
+ * Composite protocol processor for the standard Redis wire protocol using
13431
+ * pluggable handlers to serialize requests and deserialize responses.
13432
+ *
13433
+ * @link http://redis.io/topics/protocol
13434
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13435
+ */
13436
+ class CompositeProtocolProcessor implements ProtocolProcessorInterface
13437
+ {
13438
+ /*
13439
+ * @var RequestSerializerInterface
13440
+ */
13441
+ protected $serializer;
13442
+
13443
+ /*
13444
+ * @var ResponseReaderInterface
13445
+ */
13446
+ protected $reader;
13447
+
13448
+ /**
13449
+ * @param RequestSerializerInterface $serializer Request serializer.
13450
+ * @param ResponseReaderInterface $reader Response reader.
13451
+ */
13452
+ public function __construct(
13453
+ RequestSerializerInterface $serializer = null,
13454
+ ResponseReaderInterface $reader = null
13455
+ ) {
13456
+ $this->setRequestSerializer($serializer ?: new RequestSerializer());
13457
+ $this->setResponseReader($reader ?: new ResponseReader());
13458
+ }
13459
+
13460
+ /**
13461
+ * {@inheritdoc}
13462
+ */
13463
+ public function write(CompositeConnectionInterface $connection, CommandInterface $command)
13464
+ {
13465
+ $connection->writeBuffer($this->serializer->serialize($command));
13466
+ }
13467
+
13468
+ /**
13469
+ * {@inheritdoc}
13470
+ */
13471
+ public function read(CompositeConnectionInterface $connection)
13472
+ {
13473
+ return $this->reader->read($connection);
13474
+ }
13475
+
13476
+ /**
13477
+ * Sets the request serializer used by the protocol processor.
13478
+ *
13479
+ * @param RequestSerializerInterface $serializer Request serializer.
13480
+ */
13481
+ public function setRequestSerializer(RequestSerializerInterface $serializer)
13482
+ {
13483
+ $this->serializer = $serializer;
13484
+ }
13485
+
13486
+ /**
13487
+ * Returns the request serializer used by the protocol processor.
13488
+ *
13489
+ * @return RequestSerializerInterface
13490
+ */
13491
+ public function getRequestSerializer()
13492
+ {
13493
+ return $this->serializer;
13494
+ }
13495
+
13496
+ /**
13497
+ * Sets the response reader used by the protocol processor.
13498
+ *
13499
+ * @param ResponseReaderInterface $reader Response reader.
13500
+ */
13501
+ public function setResponseReader(ResponseReaderInterface $reader)
13502
+ {
13503
+ $this->reader = $reader;
13504
+ }
13505
+
13506
+ /**
13507
+ * Returns the Response reader used by the protocol processor.
13508
+ *
13509
+ * @return ResponseReaderInterface
13510
+ */
13511
+ public function getResponseReader()
13512
+ {
13513
+ return $this->reader;
13514
+ }
13515
+ }
13516
+
13517
+ /* --------------------------------------------------------------------------- */
13518
+
13519
+ namespace Predis\PubSub;
13520
+
13521
+ use Iterator;
13522
+ use Predis\ClientException;
13523
+ use Predis\ClientInterface;
13524
+ use Predis\Command\Command;
13525
+ use Predis\NotSupportedException;
13526
+ use Predis\Connection\AggregateConnectionInterface;
13527
+ use InvalidArgumentException;
13528
+
13529
+ /**
13530
+ * Base implementation of a PUB/SUB consumer abstraction based on PHP iterators.
13531
+ *
13532
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13533
+ */
13534
+ abstract class AbstractConsumer implements Iterator
13535
+ {
13536
+ const SUBSCRIBE = 'subscribe';
13537
+ const UNSUBSCRIBE = 'unsubscribe';
13538
+ const PSUBSCRIBE = 'psubscribe';
13539
+ const PUNSUBSCRIBE = 'punsubscribe';
13540
+ const MESSAGE = 'message';
13541
+ const PMESSAGE = 'pmessage';
13542
+ const PONG = 'pong';
13543
+
13544
+ const STATUS_VALID = 1; // 0b0001
13545
+ const STATUS_SUBSCRIBED = 2; // 0b0010
13546
+ const STATUS_PSUBSCRIBED = 4; // 0b0100
13547
+
13548
+ private $position = null;
13549
+ private $statusFlags = self::STATUS_VALID;
13550
+
13551
+ /**
13552
+ * Automatically stops the consumer when the garbage collector kicks in.
13553
+ */
13554
+ public function __destruct()
13555
+ {
13556
+ $this->stop(true);
13557
+ }
13558
+
13559
+ /**
13560
+ * Checks if the specified flag is valid based on the state of the consumer.
13561
+ *
13562
+ * @param int $value Flag.
13563
+ *
13564
+ * @return bool
13565
+ */
13566
+ protected function isFlagSet($value)
13567
+ {
13568
+ return ($this->statusFlags & $value) === $value;
13569
+ }
13570
+
13571
+ /**
13572
+ * Subscribes to the specified channels.
13573
+ *
13574
+ * @param mixed $channel,... One or more channel names.
13575
+ */
13576
+ public function subscribe($channel /*, ... */)
13577
+ {
13578
+ $this->writeRequest(self::SUBSCRIBE, func_get_args());
13579
+ $this->statusFlags |= self::STATUS_SUBSCRIBED;
13580
+ }
13581
+
13582
+ /**
13583
+ * Unsubscribes from the specified channels.
13584
+ *
13585
+ * @param string ... One or more channel names.
13586
+ */
13587
+ public function unsubscribe(/* ... */)
13588
+ {
13589
+ $this->writeRequest(self::UNSUBSCRIBE, func_get_args());
13590
+ }
13591
+
13592
+ /**
13593
+ * Subscribes to the specified channels using a pattern.
13594
+ *
13595
+ * @param mixed $pattern,... One or more channel name patterns.
13596
+ */
13597
+ public function psubscribe($pattern /* ... */)
13598
+ {
13599
+ $this->writeRequest(self::PSUBSCRIBE, func_get_args());
13600
+ $this->statusFlags |= self::STATUS_PSUBSCRIBED;
13601
+ }
13602
+
13603
+ /**
13604
+ * Unsubscribes from the specified channels using a pattern.
13605
+ *
13606
+ * @param string ... One or more channel name patterns.
13607
+ */
13608
+ public function punsubscribe(/* ... */)
13609
+ {
13610
+ $this->writeRequest(self::PUNSUBSCRIBE, func_get_args());
13611
+ }
13612
+
13613
+ /**
13614
+ * PING the server with an optional payload that will be echoed as a
13615
+ * PONG message in the pub/sub loop.
13616
+ *
13617
+ * @param string $payload Optional PING payload.
13618
+ */
13619
+ public function ping($payload = null)
13620
+ {
13621
+ $this->writeRequest('PING', array($payload));
13622
+ }
13623
+
13624
+ /**
13625
+ * Closes the context by unsubscribing from all the subscribed channels. The
13626
+ * context can be forcefully closed by dropping the underlying connection.
13627
+ *
13628
+ * @param bool $drop Indicates if the context should be closed by dropping the connection.
13629
+ *
13630
+ * @return bool Returns false when there are no pending messages.
13631
+ */
13632
+ public function stop($drop = false)
13633
+ {
13634
+ if (!$this->valid()) {
13635
+ return false;
13636
+ }
13637
+
13638
+ if ($drop) {
13639
+ $this->invalidate();
13640
+ $this->disconnect();
13641
+ } else {
13642
+ if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) {
13643
+ $this->unsubscribe();
13644
+ }
13645
+ if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) {
13646
+ $this->punsubscribe();
13647
+ }
13648
+ }
13649
+
13650
+ return !$drop;
13651
+ }
13652
+
13653
+ /**
13654
+ * Closes the underlying connection when forcing a disconnection.
13655
+ */
13656
+ abstract protected function disconnect();
13657
+
13658
+ /**
13659
+ * Writes a Redis command on the underlying connection.
13660
+ *
13661
+ * @param string $method Command ID.
13662
+ * @param array $arguments Arguments for the command.
13663
+ */
13664
+ abstract protected function writeRequest($method, $arguments);
13665
+
13666
+ /**
13667
+ * {@inheritdoc}
13668
+ */
13669
+ public function rewind()
13670
+ {
13671
+ // NOOP
13672
+ }
13673
+
13674
+ /**
13675
+ * Returns the last message payload retrieved from the server and generated
13676
+ * by one of the active subscriptions.
13677
+ *
13678
+ * @return array
13679
+ */
13680
+ public function current()
13681
+ {
13682
+ return $this->getValue();
13683
+ }
13684
+
13685
+ /**
13686
+ * {@inheritdoc}
13687
+ */
13688
+ public function key()
13689
+ {
13690
+ return $this->position;
13691
+ }
13692
+
13693
+ /**
13694
+ * {@inheritdoc}
13695
+ */
13696
+ public function next()
13697
+ {
13698
+ if ($this->valid()) {
13699
+ $this->position++;
13700
+ }
13701
+
13702
+ return $this->position;
13703
+ }
13704
+
13705
+ /**
13706
+ * Checks if the the consumer is still in a valid state to continue.
13707
+ *
13708
+ * @return bool
13709
+ */
13710
+ public function valid()
13711
+ {
13712
+ $isValid = $this->isFlagSet(self::STATUS_VALID);
13713
+ $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED;
13714
+ $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0;
13715
+
13716
+ return $isValid && $hasSubscriptions;
13717
+ }
13718
+
13719
+ /**
13720
+ * Resets the state of the consumer.
13721
+ */
13722
+ protected function invalidate()
13723
+ {
13724
+ $this->statusFlags = 0; // 0b0000;
13725
+ }
13726
+
13727
+ /**
13728
+ * Waits for a new message from the server generated by one of the active
13729
+ * subscriptions and returns it when available.
13730
+ *
13731
+ * @return array
13732
+ */
13733
+ abstract protected function getValue();
13734
+ }
13735
+
13736
+ /**
13737
+ * Method-dispatcher loop built around the client-side abstraction of a Redis
13738
+ * PUB / SUB context.
13739
+ *
13740
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13741
+ */
13742
+ class DispatcherLoop
13743
+ {
13744
+ private $pubsub;
13745
+
13746
+ protected $callbacks;
13747
+ protected $defaultCallback;
13748
+ protected $subscriptionCallback;
13749
+
13750
+ /**
13751
+ * @param Consumer $pubsub PubSub consumer instance used by the loop.
13752
+ */
13753
+ public function __construct(Consumer $pubsub)
13754
+ {
13755
+ $this->callbacks = array();
13756
+ $this->pubsub = $pubsub;
13757
+ }
13758
+
13759
+ /**
13760
+ * Checks if the passed argument is a valid callback.
13761
+ *
13762
+ * @param mixed $callable A callback.
13763
+ *
13764
+ * @throws \InvalidArgumentException
13765
+ */
13766
+ protected function assertCallback($callable)
13767
+ {
13768
+ if (!is_callable($callable)) {
13769
+ throw new InvalidArgumentException('The given argument must be a callable object.');
13770
+ }
13771
+ }
13772
+
13773
+ /**
13774
+ * Returns the underlying PUB / SUB context.
13775
+ *
13776
+ * @return Consumer
13777
+ */
13778
+ public function getPubSubConsumer()
13779
+ {
13780
+ return $this->pubsub;
13781
+ }
13782
+
13783
+ /**
13784
+ * Sets a callback that gets invoked upon new subscriptions.
13785
+ *
13786
+ * @param mixed $callable A callback.
13787
+ */
13788
+ public function subscriptionCallback($callable = null)
13789
+ {
13790
+ if (isset($callable)) {
13791
+ $this->assertCallback($callable);
13792
+ }
13793
+
13794
+ $this->subscriptionCallback = $callable;
13795
+ }
13796
+
13797
+ /**
13798
+ * Sets a callback that gets invoked when a message is received on a
13799
+ * channel that does not have an associated callback.
13800
+ *
13801
+ * @param mixed $callable A callback.
13802
+ */
13803
+ public function defaultCallback($callable = null)
13804
+ {
13805
+ if (isset($callable)) {
13806
+ $this->assertCallback($callable);
13807
+ }
13808
+
13809
+ $this->subscriptionCallback = $callable;
13810
+ }
13811
+
13812
+ /**
13813
+ * Binds a callback to a channel.
13814
+ *
13815
+ * @param string $channel Channel name.
13816
+ * @param Callable $callback A callback.
13817
+ */
13818
+ public function attachCallback($channel, $callback)
13819
+ {
13820
+ $callbackName = $this->getPrefixKeys() . $channel;
13821
+
13822
+ $this->assertCallback($callback);
13823
+ $this->callbacks[$callbackName] = $callback;
13824
+ $this->pubsub->subscribe($channel);
13825
+ }
13826
+
13827
+ /**
13828
+ * Stops listening to a channel and removes the associated callback.
13829
+ *
13830
+ * @param string $channel Redis channel.
13831
+ */
13832
+ public function detachCallback($channel)
13833
+ {
13834
+ $callbackName = $this->getPrefixKeys() . $channel;
13835
+
13836
+ if (isset($this->callbacks[$callbackName])) {
13837
+ unset($this->callbacks[$callbackName]);
13838
+ $this->pubsub->unsubscribe($channel);
13839
+ }
13840
+ }
13841
+
13842
+ /**
13843
+ * Starts the dispatcher loop.
13844
+ */
13845
+ public function run()
13846
+ {
13847
+ foreach ($this->pubsub as $message) {
13848
+ $kind = $message->kind;
13849
+
13850
+ if ($kind !== Consumer::MESSAGE && $kind !== Consumer::PMESSAGE) {
13851
+ if (isset($this->subscriptionCallback)) {
13852
+ $callback = $this->subscriptionCallback;
13853
+ call_user_func($callback, $message);
13854
+ }
13855
+
13856
+ continue;
13857
+ }
13858
+
13859
+ if (isset($this->callbacks[$message->channel])) {
13860
+ $callback = $this->callbacks[$message->channel];
13861
+ call_user_func($callback, $message->payload);
13862
+ } elseif (isset($this->defaultCallback)) {
13863
+ $callback = $this->defaultCallback;
13864
+ call_user_func($callback, $message);
13865
+ }
13866
+ }
13867
+ }
13868
+
13869
+ /**
13870
+ * Terminates the dispatcher loop.
13871
+ */
13872
+ public function stop()
13873
+ {
13874
+ $this->pubsub->stop();
13875
+ }
13876
+
13877
+ /**
13878
+ * Return the prefix used for keys
13879
+ *
13880
+ * @return string
13881
+ */
13882
+ protected function getPrefixKeys()
13883
+ {
13884
+ $options = $this->pubsub->getClient()->getOptions();
13885
+
13886
+ if (isset($options->prefix)) {
13887
+ return $options->prefix->getPrefix();
13888
+ }
13889
+
13890
+ return '';
13891
+ }
13892
+ }
13893
+
13894
+ /**
13895
+ * PUB/SUB consumer abstraction.
13896
+ *
13897
+ * @author Daniele Alessandri <suppakilla@gmail.com>
13898
+ */
13899
+ class Consumer extends AbstractConsumer
13900
+ {
13901
+ private $client;
13902
+ private $options;
13903
+
13904
+ /**
13905
+ * @param ClientInterface $client Client instance used by the consumer.
13906
+ * @param array $options Options for the consumer initialization.
13907
+ */
13908
+ public function __construct(ClientInterface $client, array $options = null)
13909
+ {
13910
+ $this->checkCapabilities($client);
13911
+
13912
+ $this->options = $options ?: array();
13913
+ $this->client = $client;
13914
+
13915
+ $this->genericSubscribeInit('subscribe');
13916
+ $this->genericSubscribeInit('psubscribe');
13917
+ }
13918
+
13919
+ /**
13920
+ * Returns the underlying client instance used by the pub/sub iterator.
13921
+ *
13922
+ * @return ClientInterface
13923
+ */
13924
+ public function getClient()
13925
+ {
13926
+ return $this->client;
13927
+ }
13928
+
13929
+ /**
13930
+ * Checks if the client instance satisfies the required conditions needed to
13931
+ * initialize a PUB/SUB consumer.
13932
+ *
13933
+ * @param ClientInterface $client Client instance used by the consumer.
13934
+ *
13935
+ * @throws NotSupportedException
13936
+ */
13937
+ private function checkCapabilities(ClientInterface $client)
13938
+ {
13939
+ if ($client->getConnection() instanceof AggregateConnectionInterface) {
13940
+ throw new NotSupportedException(
13941
+ 'Cannot initialize a PUB/SUB consumer over aggregate connections.'
13942
+ );
13943
+ }
13944
+
13945
+ $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe');
13946
+
13947
+ if ($client->getProfile()->supportsCommands($commands) === false) {
13948
+ throw new NotSupportedException(
13949
+ 'The current profile does not support PUB/SUB related commands.'
13950
+ );
13951
+ }
13952
+ }
13953
+
13954
+ /**
13955
+ * This method shares the logic to handle both SUBSCRIBE and PSUBSCRIBE.
13956
+ *
13957
+ * @param string $subscribeAction Type of subscription.
13958
+ */
13959
+ private function genericSubscribeInit($subscribeAction)
13960
+ {
13961
+ if (isset($this->options[$subscribeAction])) {
13962
+ $this->$subscribeAction($this->options[$subscribeAction]);
13963
+ }
13964
+ }
13965
+
13966
+ /**
13967
+ * {@inheritdoc}
13968
+ */
13969
+ protected function writeRequest($method, $arguments)
13970
+ {
13971
+ $this->client->getConnection()->writeRequest(
13972
+ $this->client->createCommand($method,
13973
+ Command::normalizeArguments($arguments)
13974
+ )
13975
+ );
13976
+ }
13977
+
13978
+ /**
13979
+ * {@inheritdoc}
13980
+ */
13981
+ protected function disconnect()
13982
+ {
13983
+ $this->client->disconnect();
13984
+ }
13985
+
13986
+ /**
13987
+ * {@inheritdoc}
13988
+ */
13989
+ protected function getValue()
13990
+ {
13991
+ $response = $this->client->getConnection()->read();
13992
+
13993
+ switch ($response[0]) {
13994
+ case self::SUBSCRIBE:
13995
+ case self::UNSUBSCRIBE:
13996
+ case self::PSUBSCRIBE:
13997
+ case self::PUNSUBSCRIBE:
13998
+ if ($response[2] === 0) {
13999
+ $this->invalidate();
14000
+ }
14001
+ // The missing break here is intentional as we must process
14002
+ // subscriptions and unsubscriptions as standard messages.
14003
+ // no break
14004
+
14005
+ case self::MESSAGE:
14006
+ return (object) array(
14007
+ 'kind' => $response[0],
14008
+ 'channel' => $response[1],
14009
+ 'payload' => $response[2],
14010
+ );
14011
+
14012
+ case self::PMESSAGE:
14013
+ return (object) array(
14014
+ 'kind' => $response[0],
14015
+ 'pattern' => $response[1],
14016
+ 'channel' => $response[2],
14017
+ 'payload' => $response[3],
14018
+ );
14019
+
14020
+ case self::PONG:
14021
+ return (object) array(
14022
+ 'kind' => $response[0],
14023
+ 'payload' => $response[1],
14024
+ );
14025
+
14026
+ default:
14027
+ throw new ClientException(
14028
+ "Unknown message type '{$response[0]}' received in the PUB/SUB context."
14029
+ );
14030
+ }
14031
+ }
14032
+ }
14033
+
14034
+ /* --------------------------------------------------------------------------- */
14035
+
14036
+ namespace Predis\Transaction;
14037
+
14038
+ use Predis\PredisException;
14039
+ use Exception;
14040
+ use InvalidArgumentException;
14041
+ use SplQueue;
14042
+ use Predis\ClientContextInterface;
14043
+ use Predis\ClientException;
14044
+ use Predis\ClientInterface;
14045
+ use Predis\CommunicationException;
14046
+ use Predis\NotSupportedException;
14047
+ use Predis\Response\ErrorInterface as ErrorResponseInterface;
14048
+ use Predis\Response\ServerException;
14049
+ use Predis\Response\Status as StatusResponse;
14050
+ use Predis\Command\CommandInterface;
14051
+ use Predis\Connection\AggregateConnectionInterface;
14052
+ use Predis\Protocol\ProtocolException;
14053
+
14054
+ /**
14055
+ * Utility class used to track the state of a MULTI / EXEC transaction.
14056
+ *
14057
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14058
+ */
14059
+ class MultiExecState
14060
+ {
14061
+ const INITIALIZED = 1; // 0b00001
14062
+ const INSIDEBLOCK = 2; // 0b00010
14063
+ const DISCARDED = 4; // 0b00100
14064
+ const CAS = 8; // 0b01000
14065
+ const WATCH = 16; // 0b10000
14066
+
14067
+ private $flags;
14068
+
14069
+ /**
14070
+ *
14071
+ */
14072
+ public function __construct()
14073
+ {
14074
+ $this->flags = 0;
14075
+ }
14076
+
14077
+ /**
14078
+ * Sets the internal state flags.
14079
+ *
14080
+ * @param int $flags Set of flags
14081
+ */
14082
+ public function set($flags)
14083
+ {
14084
+ $this->flags = $flags;
14085
+ }
14086
+
14087
+ /**
14088
+ * Gets the internal state flags.
14089
+ *
14090
+ * @return int
14091
+ */
14092
+ public function get()
14093
+ {
14094
+ return $this->flags;
14095
+ }
14096
+
14097
+ /**
14098
+ * Sets one or more flags.
14099
+ *
14100
+ * @param int $flags Set of flags
14101
+ */
14102
+ public function flag($flags)
14103
+ {
14104
+ $this->flags |= $flags;
14105
+ }
14106
+
14107
+ /**
14108
+ * Resets one or more flags.
14109
+ *
14110
+ * @param int $flags Set of flags
14111
+ */
14112
+ public function unflag($flags)
14113
+ {
14114
+ $this->flags &= ~$flags;
14115
+ }
14116
+
14117
+ /**
14118
+ * Returns if the specified flag or set of flags is set.
14119
+ *
14120
+ * @param int $flags Flag
14121
+ *
14122
+ * @return bool
14123
+ */
14124
+ public function check($flags)
14125
+ {
14126
+ return ($this->flags & $flags) === $flags;
14127
+ }
14128
+
14129
+ /**
14130
+ * Resets the state of a transaction.
14131
+ */
14132
+ public function reset()
14133
+ {
14134
+ $this->flags = 0;
14135
+ }
14136
+
14137
+ /**
14138
+ * Returns the state of the RESET flag.
14139
+ *
14140
+ * @return bool
14141
+ */
14142
+ public function isReset()
14143
+ {
14144
+ return $this->flags === 0;
14145
+ }
14146
+
14147
+ /**
14148
+ * Returns the state of the INITIALIZED flag.
14149
+ *
14150
+ * @return bool
14151
+ */
14152
+ public function isInitialized()
14153
+ {
14154
+ return $this->check(self::INITIALIZED);
14155
+ }
14156
+
14157
+ /**
14158
+ * Returns the state of the INSIDEBLOCK flag.
14159
+ *
14160
+ * @return bool
14161
+ */
14162
+ public function isExecuting()
14163
+ {
14164
+ return $this->check(self::INSIDEBLOCK);
14165
+ }
14166
+
14167
+ /**
14168
+ * Returns the state of the CAS flag.
14169
+ *
14170
+ * @return bool
14171
+ */
14172
+ public function isCAS()
14173
+ {
14174
+ return $this->check(self::CAS);
14175
+ }
14176
+
14177
+ /**
14178
+ * Returns if WATCH is allowed in the current state.
14179
+ *
14180
+ * @return bool
14181
+ */
14182
+ public function isWatchAllowed()
14183
+ {
14184
+ return $this->check(self::INITIALIZED) && !$this->check(self::CAS);
14185
+ }
14186
+
14187
+ /**
14188
+ * Returns the state of the WATCH flag.
14189
+ *
14190
+ * @return bool
14191
+ */
14192
+ public function isWatching()
14193
+ {
14194
+ return $this->check(self::WATCH);
14195
+ }
14196
+
14197
+ /**
14198
+ * Returns the state of the DISCARDED flag.
14199
+ *
14200
+ * @return bool
14201
+ */
14202
+ public function isDiscarded()
14203
+ {
14204
+ return $this->check(self::DISCARDED);
14205
+ }
14206
+ }
14207
+
14208
+ /**
14209
+ * Client-side abstraction of a Redis transaction based on MULTI / EXEC.
14210
+ *
14211
+ * {@inheritdoc}
14212
+ *
14213
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14214
+ */
14215
+ class MultiExec implements ClientContextInterface
14216
+ {
14217
+ private $state;
14218
+
14219
+ protected $client;
14220
+ protected $commands;
14221
+ protected $exceptions = true;
14222
+ protected $attempts = 0;
14223
+ protected $watchKeys = array();
14224
+ protected $modeCAS = false;
14225
+
14226
+ /**
14227
+ * @param ClientInterface $client Client instance used by the transaction.
14228
+ * @param array $options Initialization options.
14229
+ */
14230
+ public function __construct(ClientInterface $client, array $options = null)
14231
+ {
14232
+ $this->assertClient($client);
14233
+
14234
+ $this->client = $client;
14235
+ $this->state = new MultiExecState();
14236
+
14237
+ $this->configure($client, $options ?: array());
14238
+ $this->reset();
14239
+ }
14240
+
14241
+ /**
14242
+ * Checks if the passed client instance satisfies the required conditions
14243
+ * needed to initialize the transaction object.
14244
+ *
14245
+ * @param ClientInterface $client Client instance used by the transaction object.
14246
+ *
14247
+ * @throws NotSupportedException
14248
+ */
14249
+ private function assertClient(ClientInterface $client)
14250
+ {
14251
+ if ($client->getConnection() instanceof AggregateConnectionInterface) {
14252
+ throw new NotSupportedException(
14253
+ 'Cannot initialize a MULTI/EXEC transaction over aggregate connections.'
14254
+ );
14255
+ }
14256
+
14257
+ if (!$client->getProfile()->supportsCommands(array('MULTI', 'EXEC', 'DISCARD'))) {
14258
+ throw new NotSupportedException(
14259
+ 'The current profile does not support MULTI, EXEC and DISCARD.'
14260
+ );
14261
+ }
14262
+ }
14263
+
14264
+ /**
14265
+ * Configures the transaction using the provided options.
14266
+ *
14267
+ * @param ClientInterface $client Underlying client instance.
14268
+ * @param array $options Array of options for the transaction.
14269
+ **/
14270
+ protected function configure(ClientInterface $client, array $options)
14271
+ {
14272
+ if (isset($options['exceptions'])) {
14273
+ $this->exceptions = (bool) $options['exceptions'];
14274
+ } else {
14275
+ $this->exceptions = $client->getOptions()->exceptions;
14276
+ }
14277
+
14278
+ if (isset($options['cas'])) {
14279
+ $this->modeCAS = (bool) $options['cas'];
14280
+ }
14281
+
14282
+ if (isset($options['watch']) && $keys = $options['watch']) {
14283
+ $this->watchKeys = $keys;
14284
+ }
14285
+
14286
+ if (isset($options['retry'])) {
14287
+ $this->attempts = (int) $options['retry'];
14288
+ }
14289
+ }
14290
+
14291
+ /**
14292
+ * Resets the state of the transaction.
14293
+ */
14294
+ protected function reset()
14295
+ {
14296
+ $this->state->reset();
14297
+ $this->commands = new SplQueue();
14298
+ }
14299
+
14300
+ /**
14301
+ * Initializes the transaction context.
14302
+ */
14303
+ protected function initialize()
14304
+ {
14305
+ if ($this->state->isInitialized()) {
14306
+ return;
14307
+ }
14308
+
14309
+ if ($this->modeCAS) {
14310
+ $this->state->flag(MultiExecState::CAS);
14311
+ }
14312
+
14313
+ if ($this->watchKeys) {
14314
+ $this->watch($this->watchKeys);
14315
+ }
14316
+
14317
+ $cas = $this->state->isCAS();
14318
+ $discarded = $this->state->isDiscarded();
14319
+
14320
+ if (!$cas || ($cas && $discarded)) {
14321
+ $this->call('MULTI');
14322
+
14323
+ if ($discarded) {
14324
+ $this->state->unflag(MultiExecState::CAS);
14325
+ }
14326
+ }
14327
+
14328
+ $this->state->unflag(MultiExecState::DISCARDED);
14329
+ $this->state->flag(MultiExecState::INITIALIZED);
14330
+ }
14331
+
14332
+ /**
14333
+ * Dynamically invokes a Redis command with the specified arguments.
14334
+ *
14335
+ * @param string $method Command ID.
14336
+ * @param array $arguments Arguments for the command.
14337
+ *
14338
+ * @return mixed
14339
+ */
14340
+ public function __call($method, $arguments)
14341
+ {
14342
+ return $this->executeCommand(
14343
+ $this->client->createCommand($method, $arguments)
14344
+ );
14345
+ }
14346
+
14347
+ /**
14348
+ * Executes a Redis command bypassing the transaction logic.
14349
+ *
14350
+ * @param string $commandID Command ID.
14351
+ * @param array $arguments Arguments for the command.
14352
+ *
14353
+ * @return mixed
14354
+ *
14355
+ * @throws ServerException
14356
+ */
14357
+ protected function call($commandID, array $arguments = array())
14358
+ {
14359
+ $response = $this->client->executeCommand(
14360
+ $this->client->createCommand($commandID, $arguments)
14361
+ );
14362
+
14363
+ if ($response instanceof ErrorResponseInterface) {
14364
+ throw new ServerException($response->getMessage());
14365
+ }
14366
+
14367
+ return $response;
14368
+ }
14369
+
14370
+ /**
14371
+ * Executes the specified Redis command.
14372
+ *
14373
+ * @param CommandInterface $command Command instance.
14374
+ *
14375
+ * @return $this|mixed
14376
+ *
14377
+ * @throws AbortedMultiExecException
14378
+ * @throws CommunicationException
14379
+ */
14380
+ public function executeCommand(CommandInterface $command)
14381
+ {
14382
+ $this->initialize();
14383
+ if ($this->state->isCAS()) {
14384
+ return $this->client->executeCommand($command);
14385
+ }
14386
+
14387
+ $response = $this->client->getConnection()->executeCommand($command);
14388
+
14389
+ if ($response instanceof StatusResponse && $response == 'QUEUED') {
14390
+ $this->commands->enqueue($command);
14391
+ } elseif ($response instanceof ErrorResponseInterface) {
14392
+ throw new AbortedMultiExecException($this, $response->getMessage());
14393
+ } else {
14394
+ $this->onProtocolError('The server did not return a +QUEUED status response.');
14395
+ }
14396
+
14397
+ return $this;
14398
+ }
14399
+
14400
+ /**
14401
+ * Executes WATCH against one or more keys.
14402
+ *
14403
+ * @param string|array $keys One or more keys.
14404
+ *
14405
+ * @return mixed
14406
+ *
14407
+ * @throws NotSupportedException
14408
+ * @throws ClientException
14409
+ */
14410
+ public function watch($keys)
14411
+ {
14412
+ if (!$this->client->getProfile()->supportsCommand('WATCH')) {
14413
+ throw new NotSupportedException('WATCH is not supported by the current profile.');
14414
+ }
14415
+
14416
+ if ($this->state->isWatchAllowed()) {
14417
+ throw new ClientException('Sending WATCH after MULTI is not allowed.');
14418
+ }
14419
+
14420
+ $response = $this->call('WATCH', is_array($keys) ? $keys : array($keys));
14421
+ $this->state->flag(MultiExecState::WATCH);
14422
+
14423
+ return $response;
14424
+ }
14425
+
14426
+ /**
14427
+ * Finalizes the transaction by executing MULTI on the server.
14428
+ *
14429
+ * @return MultiExec
14430
+ */
14431
+ public function multi()
14432
+ {
14433
+ if ($this->state->check(MultiExecState::INITIALIZED | MultiExecState::CAS)) {
14434
+ $this->state->unflag(MultiExecState::CAS);
14435
+ $this->call('MULTI');
14436
+ } else {
14437
+ $this->initialize();
14438
+ }
14439
+
14440
+ return $this;
14441
+ }
14442
+
14443
+ /**
14444
+ * Executes UNWATCH.
14445
+ *
14446
+ * @return MultiExec
14447
+ *
14448
+ * @throws NotSupportedException
14449
+ */
14450
+ public function unwatch()
14451
+ {
14452
+ if (!$this->client->getProfile()->supportsCommand('UNWATCH')) {
14453
+ throw new NotSupportedException(
14454
+ 'UNWATCH is not supported by the current profile.'
14455
+ );
14456
+ }
14457
+
14458
+ $this->state->unflag(MultiExecState::WATCH);
14459
+ $this->__call('UNWATCH', array());
14460
+
14461
+ return $this;
14462
+ }
14463
+
14464
+ /**
14465
+ * Resets the transaction by UNWATCH-ing the keys that are being WATCHed and
14466
+ * DISCARD-ing pending commands that have been already sent to the server.
14467
+ *
14468
+ * @return MultiExec
14469
+ */
14470
+ public function discard()
14471
+ {
14472
+ if ($this->state->isInitialized()) {
14473
+ $this->call($this->state->isCAS() ? 'UNWATCH' : 'DISCARD');
14474
+
14475
+ $this->reset();
14476
+ $this->state->flag(MultiExecState::DISCARDED);
14477
+ }
14478
+
14479
+ return $this;
14480
+ }
14481
+
14482
+ /**
14483
+ * Executes the whole transaction.
14484
+ *
14485
+ * @return mixed
14486
+ */
14487
+ public function exec()
14488
+ {
14489
+ return $this->execute();
14490
+ }
14491
+
14492
+ /**
14493
+ * Checks the state of the transaction before execution.
14494
+ *
14495
+ * @param mixed $callable Callback for execution.
14496
+ *
14497
+ * @throws InvalidArgumentException
14498
+ * @throws ClientException
14499
+ */
14500
+ private function checkBeforeExecution($callable)
14501
+ {
14502
+ if ($this->state->isExecuting()) {
14503
+ throw new ClientException(
14504
+ 'Cannot invoke "execute" or "exec" inside an active transaction context.'
14505
+ );
14506
+ }
14507
+
14508
+ if ($callable) {
14509
+ if (!is_callable($callable)) {
14510
+ throw new InvalidArgumentException('The argument must be a callable object.');
14511
+ }
14512
+
14513
+ if (!$this->commands->isEmpty()) {
14514
+ $this->discard();
14515
+
14516
+ throw new ClientException(
14517
+ 'Cannot execute a transaction block after using fluent interface.'
14518
+ );
14519
+ }
14520
+ } elseif ($this->attempts) {
14521
+ $this->discard();
14522
+
14523
+ throw new ClientException(
14524
+ 'Automatic retries are supported only when a callable block is provided.'
14525
+ );
14526
+ }
14527
+ }
14528
+
14529
+ /**
14530
+ * Handles the actual execution of the whole transaction.
14531
+ *
14532
+ * @param mixed $callable Optional callback for execution.
14533
+ *
14534
+ * @return array
14535
+ *
14536
+ * @throws CommunicationException
14537
+ * @throws AbortedMultiExecException
14538
+ * @throws ServerException
14539
+ */
14540
+ public function execute($callable = null)
14541
+ {
14542
+ $this->checkBeforeExecution($callable);
14543
+
14544
+ $execResponse = null;
14545
+ $attempts = $this->attempts;
14546
+
14547
+ do {
14548
+ if ($callable) {
14549
+ $this->executeTransactionBlock($callable);
14550
+ }
14551
+
14552
+ if ($this->commands->isEmpty()) {
14553
+ if ($this->state->isWatching()) {
14554
+ $this->discard();
14555
+ }
14556
+
14557
+ return null;
14558
+ }
14559
+
14560
+ $execResponse = $this->call('EXEC');
14561
+
14562
+ if ($execResponse === null) {
14563
+ if ($attempts === 0) {
14564
+ throw new AbortedMultiExecException(
14565
+ $this, 'The current transaction has been aborted by the server.'
14566
+ );
14567
+ }
14568
+
14569
+ $this->reset();
14570
+
14571
+ continue;
14572
+ }
14573
+
14574
+ break;
14575
+ } while ($attempts-- > 0);
14576
+
14577
+ $response = array();
14578
+ $commands = $this->commands;
14579
+ $size = count($execResponse);
14580
+
14581
+ if ($size !== count($commands)) {
14582
+ $this->onProtocolError('EXEC returned an unexpected number of response items.');
14583
+ }
14584
+
14585
+ for ($i = 0; $i < $size; $i++) {
14586
+ $cmdResponse = $execResponse[$i];
14587
+
14588
+ if ($cmdResponse instanceof ErrorResponseInterface && $this->exceptions) {
14589
+ throw new ServerException($cmdResponse->getMessage());
14590
+ }
14591
+
14592
+ $response[$i] = $commands->dequeue()->parseResponse($cmdResponse);
14593
+ }
14594
+
14595
+ return $response;
14596
+ }
14597
+
14598
+ /**
14599
+ * Passes the current transaction object to a callable block for execution.
14600
+ *
14601
+ * @param mixed $callable Callback.
14602
+ *
14603
+ * @throws CommunicationException
14604
+ * @throws ServerException
14605
+ */
14606
+ protected function executeTransactionBlock($callable)
14607
+ {
14608
+ $exception = null;
14609
+ $this->state->flag(MultiExecState::INSIDEBLOCK);
14610
+
14611
+ try {
14612
+ call_user_func($callable, $this);
14613
+ } catch (CommunicationException $exception) {
14614
+ // NOOP
14615
+ } catch (ServerException $exception) {
14616
+ // NOOP
14617
+ } catch (Exception $exception) {
14618
+ $this->discard();
14619
+ }
14620
+
14621
+ $this->state->unflag(MultiExecState::INSIDEBLOCK);
14622
+
14623
+ if ($exception) {
14624
+ throw $exception;
14625
+ }
14626
+ }
14627
+
14628
+ /**
14629
+ * Helper method for protocol errors encountered inside the transaction.
14630
+ *
14631
+ * @param string $message Error message.
14632
+ */
14633
+ private function onProtocolError($message)
14634
+ {
14635
+ // Since a MULTI/EXEC block cannot be initialized when using aggregate
14636
+ // connections we can safely assume that Predis\Client::getConnection()
14637
+ // will return a Predis\Connection\NodeConnectionInterface instance.
14638
+ CommunicationException::handle(new ProtocolException(
14639
+ $this->client->getConnection(), $message
14640
+ ));
14641
+ }
14642
+ }
14643
+
14644
+ /**
14645
+ * Exception class that identifies a MULTI / EXEC transaction aborted by Redis.
14646
+ *
14647
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14648
+ */
14649
+ class AbortedMultiExecException extends PredisException
14650
+ {
14651
+ private $transaction;
14652
+
14653
+ /**
14654
+ * @param MultiExec $transaction Transaction that generated the exception.
14655
+ * @param string $message Error message.
14656
+ * @param int $code Error code.
14657
+ */
14658
+ public function __construct(MultiExec $transaction, $message, $code = null)
14659
+ {
14660
+ parent::__construct($message, $code);
14661
+ $this->transaction = $transaction;
14662
+ }
14663
+
14664
+ /**
14665
+ * Returns the transaction that generated the exception.
14666
+ *
14667
+ * @return MultiExec
14668
+ */
14669
+ public function getTransaction()
14670
+ {
14671
+ return $this->transaction;
14672
+ }
14673
+ }
14674
+
14675
+ /* --------------------------------------------------------------------------- */
14676
+
14677
+ namespace Predis\Session;
14678
+
14679
+ use SessionHandlerInterface;
14680
+ use Predis\ClientInterface;
14681
+
14682
+ /**
14683
+ * Session handler class that relies on Predis\Client to store PHP's sessions
14684
+ * data into one or multiple Redis servers.
14685
+ *
14686
+ * This class is mostly intended for PHP 5.4 but it can be used under PHP 5.3
14687
+ * provided that a polyfill for `SessionHandlerInterface` is defined by either
14688
+ * you or an external package such as `symfony/http-foundation`.
14689
+ *
14690
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14691
+ */
14692
+ class Handler implements SessionHandlerInterface
14693
+ {
14694
+ protected $client;
14695
+ protected $ttl;
14696
+
14697
+ /**
14698
+ * @param ClientInterface $client Fully initialized client instance.
14699
+ * @param array $options Session handler options.
14700
+ */
14701
+ public function __construct(ClientInterface $client, array $options = array())
14702
+ {
14703
+ $this->client = $client;
14704
+
14705
+ if (isset($options['gc_maxlifetime'])) {
14706
+ $this->ttl = (int) $options['gc_maxlifetime'];
14707
+ } else {
14708
+ $this->ttl = ini_get('session.gc_maxlifetime');
14709
+ }
14710
+ }
14711
+
14712
+ /**
14713
+ * Registers this instance as the current session handler.
14714
+ */
14715
+ public function register()
14716
+ {
14717
+ if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
14718
+ session_set_save_handler($this, true);
14719
+ } else {
14720
+ session_set_save_handler(
14721
+ array($this, 'open'),
14722
+ array($this, 'close'),
14723
+ array($this, 'read'),
14724
+ array($this, 'write'),
14725
+ array($this, 'destroy'),
14726
+ array($this, 'gc')
14727
+ );
14728
+ }
14729
+ }
14730
+
14731
+ /**
14732
+ * {@inheritdoc}
14733
+ */
14734
+ public function open($save_path, $session_id)
14735
+ {
14736
+ // NOOP
14737
+ return true;
14738
+ }
14739
+
14740
+ /**
14741
+ * {@inheritdoc}
14742
+ */
14743
+ public function close()
14744
+ {
14745
+ // NOOP
14746
+ return true;
14747
+ }
14748
+
14749
+ /**
14750
+ * {@inheritdoc}
14751
+ */
14752
+ public function gc($maxlifetime)
14753
+ {
14754
+ // NOOP
14755
+ return true;
14756
+ }
14757
+
14758
+ /**
14759
+ * {@inheritdoc}
14760
+ */
14761
+ public function read($session_id)
14762
+ {
14763
+ if ($data = $this->client->get($session_id)) {
14764
+ return $data;
14765
+ }
14766
+
14767
+ return '';
14768
+ }
14769
+ /**
14770
+ * {@inheritdoc}
14771
+ */
14772
+ public function write($session_id, $session_data)
14773
+ {
14774
+ $this->client->setex($session_id, $this->ttl, $session_data);
14775
+
14776
+ return true;
14777
+ }
14778
+
14779
+ /**
14780
+ * {@inheritdoc}
14781
+ */
14782
+ public function destroy($session_id)
14783
+ {
14784
+ $this->client->del($session_id);
14785
+
14786
+ return true;
14787
+ }
14788
+
14789
+ /**
14790
+ * Returns the underlying client instance.
14791
+ *
14792
+ * @return ClientInterface
14793
+ */
14794
+ public function getClient()
14795
+ {
14796
+ return $this->client;
14797
+ }
14798
+
14799
+ /**
14800
+ * Returns the session max lifetime value.
14801
+ *
14802
+ * @return int
14803
+ */
14804
+ public function getMaxLifeTime()
14805
+ {
14806
+ return $this->ttl;
14807
+ }
14808
+ }
14809
+
14810
+ /* --------------------------------------------------------------------------- */
14811
+
14812
+ namespace Predis\Monitor;
14813
+
14814
+ use Iterator;
14815
+ use Predis\ClientInterface;
14816
+ use Predis\NotSupportedException;
14817
+ use Predis\Connection\AggregateConnectionInterface;
14818
+
14819
+ /**
14820
+ * Redis MONITOR consumer.
14821
+ *
14822
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14823
+ */
14824
+ class Consumer implements Iterator
14825
+ {
14826
+ private $client;
14827
+ private $valid;
14828
+ private $position;
14829
+
14830
+ /**
14831
+ * @param ClientInterface $client Client instance used by the consumer.
14832
+ */
14833
+ public function __construct(ClientInterface $client)
14834
+ {
14835
+ $this->assertClient($client);
14836
+
14837
+ $this->client = $client;
14838
+
14839
+ $this->start();
14840
+ }
14841
+
14842
+ /**
14843
+ * Automatically stops the consumer when the garbage collector kicks in.
14844
+ */
14845
+ public function __destruct()
14846
+ {
14847
+ $this->stop();
14848
+ }
14849
+
14850
+ /**
14851
+ * Checks if the passed client instance satisfies the required conditions
14852
+ * needed to initialize a monitor consumer.
14853
+ *
14854
+ * @param ClientInterface $client Client instance used by the consumer.
14855
+ *
14856
+ * @throws NotSupportedException
14857
+ */
14858
+ private function assertClient(ClientInterface $client)
14859
+ {
14860
+ if ($client->getConnection() instanceof AggregateConnectionInterface) {
14861
+ throw new NotSupportedException(
14862
+ 'Cannot initialize a monitor consumer over aggregate connections.'
14863
+ );
14864
+ }
14865
+
14866
+ if ($client->getProfile()->supportsCommand('MONITOR') === false) {
14867
+ throw new NotSupportedException("The current profile does not support 'MONITOR'.");
14868
+ }
14869
+ }
14870
+
14871
+ /**
14872
+ * Initializes the consumer and sends the MONITOR command to the server.
14873
+ */
14874
+ protected function start()
14875
+ {
14876
+ $this->client->executeCommand(
14877
+ $this->client->createCommand('MONITOR')
14878
+ );
14879
+ $this->valid = true;
14880
+ }
14881
+
14882
+ /**
14883
+ * Stops the consumer. Internally this is done by disconnecting from server
14884
+ * since there is no way to terminate the stream initialized by MONITOR.
14885
+ */
14886
+ public function stop()
14887
+ {
14888
+ $this->client->disconnect();
14889
+ $this->valid = false;
14890
+ }
14891
+
14892
+ /**
14893
+ * {@inheritdoc}
14894
+ */
14895
+ public function rewind()
14896
+ {
14897
+ // NOOP
14898
+ }
14899
+
14900
+ /**
14901
+ * Returns the last message payload retrieved from the server.
14902
+ *
14903
+ * @return Object
14904
+ */
14905
+ public function current()
14906
+ {
14907
+ return $this->getValue();
14908
+ }
14909
+
14910
+ /**
14911
+ * {@inheritdoc}
14912
+ */
14913
+ public function key()
14914
+ {
14915
+ return $this->position;
14916
+ }
14917
+
14918
+ /**
14919
+ * {@inheritdoc}
14920
+ */
14921
+ public function next()
14922
+ {
14923
+ $this->position++;
14924
+ }
14925
+
14926
+ /**
14927
+ * Checks if the the consumer is still in a valid state to continue.
14928
+ *
14929
+ * @return bool
14930
+ */
14931
+ public function valid()
14932
+ {
14933
+ return $this->valid;
14934
+ }
14935
+
14936
+ /**
14937
+ * Waits for a new message from the server generated by MONITOR and returns
14938
+ * it when available.
14939
+ *
14940
+ * @return Object
14941
+ */
14942
+ private function getValue()
14943
+ {
14944
+ $database = 0;
14945
+ $client = null;
14946
+ $event = $this->client->getConnection()->read();
14947
+
14948
+ $callback = function ($matches) use (&$database, &$client) {
14949
+ if (2 === $count = count($matches)) {
14950
+ // Redis <= 2.4
14951
+ $database = (int) $matches[1];
14952
+ }
14953
+
14954
+ if (4 === $count) {
14955
+ // Redis >= 2.6
14956
+ $database = (int) $matches[2];
14957
+ $client = $matches[3];
14958
+ }
14959
+
14960
+ return ' ';
14961
+ };
14962
+
14963
+ $event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1);
14964
+ @list($timestamp, $command, $arguments) = explode(' ', $event, 3);
14965
+
14966
+ return (object) array(
14967
+ 'timestamp' => (float) $timestamp,
14968
+ 'database' => $database,
14969
+ 'client' => $client,
14970
+ 'command' => substr($command, 1, -1),
14971
+ 'arguments' => $arguments,
14972
+ );
14973
+ }
14974
+ }
14975
+
14976
+ /* --------------------------------------------------------------------------- */
14977
+
14978
+ namespace Predis\Replication;
14979
+
14980
+ use Predis\NotSupportedException;
14981
+ use Predis\Command\CommandInterface;
14982
+
14983
+ /**
14984
+ * Defines a strategy for master/slave replication.
14985
+ *
14986
+ * @author Daniele Alessandri <suppakilla@gmail.com>
14987
+ */
14988
+ class ReplicationStrategy
14989
+ {
14990
+ protected $disallowed;
14991
+ protected $readonly;
14992
+ protected $readonlySHA1;
14993
+
14994
+ /**
14995
+ *
14996
+ */
14997
+ public function __construct()
14998
+ {
14999
+ $this->disallowed = $this->getDisallowedOperations();
15000
+ $this->readonly = $this->getReadOnlyOperations();
15001
+ $this->readonlySHA1 = array();
15002
+ }
15003
+
15004
+ /**
15005
+ * Returns if the specified command will perform a read-only operation
15006
+ * on Redis or not.
15007
+ *
15008
+ * @param CommandInterface $command Command instance.
15009
+ *
15010
+ * @return bool
15011
+ *
15012
+ * @throws NotSupportedException
15013
+ */
15014
+ public function isReadOperation(CommandInterface $command)
15015
+ {
15016
+ if (isset($this->disallowed[$id = $command->getId()])) {
15017
+ throw new NotSupportedException(
15018
+ "The command '$id' is not allowed in replication mode."
15019
+ );
15020
+ }
15021
+
15022
+ if (isset($this->readonly[$id])) {
15023
+ if (true === $readonly = $this->readonly[$id]) {
15024
+ return true;
15025
+ }
15026
+
15027
+ return call_user_func($readonly, $command);
15028
+ }
15029
+
15030
+ if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
15031
+ $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
15032
+
15033
+ if (isset($this->readonlySHA1[$sha1])) {
15034
+ if (true === $readonly = $this->readonlySHA1[$sha1]) {
15035
+ return true;
15036
+ }
15037
+
15038
+ return call_user_func($readonly, $command);
15039
+ }
15040
+ }
15041
+
15042
+ return false;
15043
+ }
15044
+
15045
+ /**
15046
+ * Returns if the specified command is not allowed for execution in a master
15047
+ * / slave replication context.
15048
+ *
15049
+ * @param CommandInterface $command Command instance.
15050
+ *
15051
+ * @return bool
15052
+ */
15053
+ public function isDisallowedOperation(CommandInterface $command)
15054
+ {
15055
+ return isset($this->disallowed[$command->getId()]);
15056
+ }
15057
+
15058
+ /**
15059
+ * Checks if a SORT command is a readable operation by parsing the arguments
15060
+ * array of the specified commad instance.
15061
+ *
15062
+ * @param CommandInterface $command Command instance.
15063
+ *
15064
+ * @return bool
15065
+ */
15066
+ protected function isSortReadOnly(CommandInterface $command)
15067
+ {
15068
+ $arguments = $command->getArguments();
15069
+
15070
+ return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
15071
+ }
15072
+
15073
+ /**
15074
+ * Marks a command as a read-only operation.
15075
+ *
15076
+ * When the behavior of a command can be decided only at runtime depending
15077
+ * on its arguments, a callable object can be provided to dynamically check
15078
+ * if the specified command performs a read or a write operation.
15079
+ *
15080
+ * @param string $commandID Command ID.
15081
+ * @param mixed $readonly A boolean value or a callable object.
15082
+ */
15083
+ public function setCommandReadOnly($commandID, $readonly = true)
15084
+ {
15085
+ $commandID = strtoupper($commandID);
15086
+
15087
+ if ($readonly) {
15088
+ $this->readonly[$commandID] = $readonly;
15089
+ } else {
15090
+ unset($this->readonly[$commandID]);
15091
+ }
15092
+ }
15093
+
15094
+ /**
15095
+ * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
15096
+ * the behaviour of a script can be decided only at runtime depending on
15097
+ * its arguments, a callable object can be provided to dynamically check
15098
+ * if the passed instance of EVAL or EVALSHA performs write operations or
15099
+ * not.
15100
+ *
15101
+ * @param string $script Body of the Lua script.
15102
+ * @param mixed $readonly A boolean value or a callable object.
15103
+ */
15104
+ public function setScriptReadOnly($script, $readonly = true)
15105
+ {
15106
+ $sha1 = sha1($script);
15107
+
15108
+ if ($readonly) {
15109
+ $this->readonlySHA1[$sha1] = $readonly;
15110
+ } else {
15111
+ unset($this->readonlySHA1[$sha1]);
15112
+ }
15113
+ }
15114
+
15115
+ /**
15116
+ * Returns the default list of disallowed commands.
15117
+ *
15118
+ * @return array
15119
+ */
15120
+ protected function getDisallowedOperations()
15121
+ {
15122
+ return array(
15123
+ 'SHUTDOWN' => true,
15124
+ 'INFO' => true,
15125
+ 'DBSIZE' => true,
15126
+ 'LASTSAVE' => true,
15127
+ 'CONFIG' => true,
15128
+ 'MONITOR' => true,
15129
+ 'SLAVEOF' => true,
15130
+ 'SAVE' => true,
15131
+ 'BGSAVE' => true,
15132
+ 'BGREWRITEAOF' => true,
15133
+ 'SLOWLOG' => true,
15134
+ );
15135
+ }
15136
+
15137
+ /**
15138
+ * Returns the default list of commands performing read-only operations.
15139
+ *
15140
+ * @return array
15141
+ */
15142
+ protected function getReadOnlyOperations()
15143
+ {
15144
+ return array(
15145
+ 'EXISTS' => true,
15146
+ 'TYPE' => true,
15147
+ 'KEYS' => true,
15148
+ 'SCAN' => true,
15149
+ 'RANDOMKEY' => true,
15150
+ 'TTL' => true,
15151
+ 'GET' => true,
15152
+ 'MGET' => true,
15153
+ 'SUBSTR' => true,
15154
+ 'STRLEN' => true,
15155
+ 'GETRANGE' => true,
15156
+ 'GETBIT' => true,
15157
+ 'LLEN' => true,
15158
+ 'LRANGE' => true,
15159
+ 'LINDEX' => true,
15160
+ 'SCARD' => true,
15161
+ 'SISMEMBER' => true,
15162
+ 'SINTER' => true,
15163
+ 'SUNION' => true,
15164
+ 'SDIFF' => true,
15165
+ 'SMEMBERS' => true,
15166
+ 'SSCAN' => true,
15167
+ 'SRANDMEMBER' => true,
15168
+ 'ZRANGE' => true,
15169
+ 'ZREVRANGE' => true,
15170
+ 'ZRANGEBYSCORE' => true,
15171
+ 'ZREVRANGEBYSCORE' => true,
15172
+ 'ZCARD' => true,
15173
+ 'ZSCORE' => true,
15174
+ 'ZCOUNT' => true,
15175
+ 'ZRANK' => true,
15176
+ 'ZREVRANK' => true,
15177
+ 'ZSCAN' => true,
15178
+ 'ZLEXCOUNT' => true,
15179
+ 'ZRANGEBYLEX' => true,
15180
+ 'HGET' => true,
15181
+ 'HMGET' => true,
15182
+ 'HEXISTS' => true,
15183
+ 'HLEN' => true,
15184
+ 'HKEYS' => true,
15185
+ 'HVALS' => true,
15186
+ 'HGETALL' => true,
15187
+ 'HSCAN' => true,
15188
+ 'PING' => true,
15189
+ 'AUTH' => true,
15190
+ 'SELECT' => true,
15191
+ 'ECHO' => true,
15192
+ 'QUIT' => true,
15193
+ 'OBJECT' => true,
15194
+ 'BITCOUNT' => true,
15195
+ 'TIME' => true,
15196
+ 'PFCOUNT' => true,
15197
+ 'SORT' => array($this, 'isSortReadOnly'),
15198
+ );
15199
+ }
15200
+ }
15201
+
15202
+ /* --------------------------------------------------------------------------- */
composer.json CHANGED
@@ -1,28 +1,28 @@
1
  {
2
- "name": "rtcamp/nginx-helper",
3
- "description": "Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does a few more things.",
4
- "keywords": ["wordpress", "plugin", "nginx", "nginx-helper", "fastcgi", "redis-cache", "redis", "cache"],
5
- "homepage": "https://rtcamp.com/nginx-helper/",
6
- "license": "GPL-2.0+",
7
- "authors": [{
8
- "name": "rtCamp",
9
- "email": "support@rtcamp.com",
10
- "homepage": "https://rtcamp.com"
11
- }],
12
- "minimum-stability": "dev",
13
  "prefer-stable": true,
14
- "type": "wordpress-plugin",
15
- "support": {
16
  "issues": "https://github.com/rtCamp/nginx-helper/issues",
17
  "forum": "https://wordpress.org/support/plugin/nginx-helper",
18
  "wiki": "https://github.com/rtCamp/nginx-helper/wiki",
19
  "source": "https://github.com/rtCamp/nginx-helper/"
20
  },
21
- "require": {
22
- "php": ">=5.3.2",
23
- "composer/installers": "^1.0"
24
- },
25
- "require-dev": {
26
- "wpreadme2markdown/wpreadme2markdown": "*"
27
- }
28
  }
1
  {
2
+ "name": "rtcamp/nginx-helper",
3
+ "description": "Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does a few more things.",
4
+ "keywords": ["wordpress", "plugin", "nginx", "nginx-helper", "fastcgi", "redis-cache", "redis", "cache"],
5
+ "homepage": "https://rtcamp.com/nginx-helper/",
6
+ "license": "GPL-2.0+",
7
+ "authors": [{
8
+ "name": "rtCamp",
9
+ "email": "support@rtcamp.com",
10
+ "homepage": "https://rtcamp.com"
11
+ }],
12
+ "minimum-stability": "dev",
13
  "prefer-stable": true,
14
+ "type": "wordpress-plugin",
15
+ "support": {
16
  "issues": "https://github.com/rtCamp/nginx-helper/issues",
17
  "forum": "https://wordpress.org/support/plugin/nginx-helper",
18
  "wiki": "https://github.com/rtCamp/nginx-helper/wiki",
19
  "source": "https://github.com/rtCamp/nginx-helper/"
20
  },
21
+ "require": {
22
+ "php": ">=5.3.2",
23
+ "composer/installers": "^1.0"
24
+ },
25
+ "require-dev": {
26
+ "wpreadme2markdown/wpreadme2markdown": "*"
27
+ }
28
  }
includes/class-nginx-helper-activator.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Fired during plugin activation.
4
+ *
5
+ * This class defines all code necessary to run during the plugin's activation.
6
+ *
7
+ * @since 2.0.0
8
+ * @link https://rtcamp.com/nginx-helper/
9
+ *
10
+ * @package nginx-helper
11
+ * @subpackage nginx-helper/includes
12
+ *
13
+ * @author rtCamp
14
+ */
15
+
16
+ /**
17
+ * Class Nginx_Helper_Activator
18
+ */
19
+ class Nginx_Helper_Activator {
20
+
21
+ /**
22
+ * Create log directory. Add capability of nginx helper.
23
+ * Schedule event to check log file size daily.
24
+ *
25
+ * @since 2.0.0
26
+ *
27
+ * @global Nginx_Helper_Admin $nginx_helper_admin
28
+ */
29
+ public static function activate() {
30
+
31
+ global $nginx_helper_admin;
32
+
33
+ $path = $nginx_helper_admin->functional_asset_path();
34
+
35
+ if ( ! is_dir( $path ) ) {
36
+ mkdir( $path );
37
+ }
38
+
39
+ if ( ! current_user_can( 'activate_plugins' ) ) {
40
+ return;
41
+ }
42
+
43
+ $role = get_role( 'administrator' );
44
+
45
+ if ( empty( $role ) ) {
46
+
47
+ update_site_option(
48
+ 'rt_wp_nginx_helper_init_check',
49
+ __( 'Sorry, you need to be an administrator to use Nginx Helper', 'nginx-helper' )
50
+ );
51
+
52
+ return;
53
+
54
+ }
55
+
56
+ $role->add_cap( 'Nginx Helper | Config' );
57
+ $role->add_cap( 'Nginx Helper | Purge cache' );
58
+
59
+ wp_schedule_event( time(), 'daily', 'rt_wp_nginx_helper_check_log_file_size_daily' );
60
+
61
+ }
62
+
63
+ }
includes/class-nginx-helper-deactivator.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Contains Nginx_Helper_Deactivator class.
4
+ *
5
+ * @package nginx-helper
6
+ */
7
+
8
+ /**
9
+ * Fired during plugin deactivation.
10
+ *
11
+ * This class defines all code necessary to run during the plugin's deactivation.
12
+ *
13
+ * @since 2.0.0
14
+ * @link https://rtcamp.com/nginx-helper/
15
+ *
16
+ * @package nginx-helper
17
+ * @subpackage nginx-helper/includes
18
+ *
19
+ * @author rtCamp
20
+ */
21
+ class Nginx_Helper_Deactivator {
22
+
23
+ /**
24
+ * Schedule event to check log file size daily. Remove nginx helper capability.
25
+ *
26
+ * @since 2.0.0
27
+ */
28
+ public static function deactivate() {
29
+
30
+ wp_clear_scheduled_hook( 'rt_wp_nginx_helper_check_log_file_size_daily' );
31
+
32
+ $role = get_role( 'administrator' );
33
+ $role->remove_cap( 'Nginx Helper | Config' );
34
+ $role->remove_cap( 'Nginx Helper | Purge cache' );
35
+
36
+ }
37
+
38
+ }
includes/class-nginx-helper-i18n.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Define the internationalization functionality
4
+ *
5
+ * Loads and defines the internationalization files for this plugin
6
+ * so that it is ready for translation.
7
+ *
8
+ * @link https://rtcamp.com/nginx-helper/
9
+ * @since 2.0.0
10
+ *
11
+ * @package nginx-helper
12
+ * @subpackage nginx-helper/includes
13
+ */
14
+
15
+ /**
16
+ * Define the internationalization functionality.
17
+ *
18
+ * Loads and defines the internationalization files for this plugin
19
+ * so that it is ready for translation.
20
+ *
21
+ * @since 2.0.0
22
+ * @package nginx-helper
23
+ * @subpackage nginx-helper/includes
24
+ * @author rtCamp
25
+ */
26
+ class Nginx_Helper_i18n {
27
+
28
+ /**
29
+ * The domain specified for this plugin.
30
+ *
31
+ * @since 2.0.0
32
+ * @access private
33
+ * @var string $domain The domain identifier for this plugin.
34
+ */
35
+ private $domain;
36
+
37
+ /**
38
+ * Load the plugin text domain for translation.
39
+ *
40
+ * @since 2.0.0
41
+ */
42
+ public function load_plugin_textdomain() {
43
+
44
+ load_plugin_textdomain(
45
+ $this->domain,
46
+ false,
47
+ dirname( dirname( plugin_basename( __FILE__ ) ) ) . '/languages/'
48
+ );
49
+
50
+ }
51
+
52
+ /**
53
+ * Set the domain equal to that of the specified domain.
54
+ *
55
+ * @since 2.0.0
56
+ *
57
+ * @param string $domain The domain that represents the locale of this plugin.
58
+ */
59
+ public function set_domain( $domain ) {
60
+ $this->domain = $domain;
61
+ }
62
+
63
+ }
includes/class-nginx-helper-loader.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Register all actions and filters for the plugin
4
+ *
5
+ * @link https://rtcamp.com/nginx-helper/
6
+ * @since 2.0.0
7
+ *
8
+ * @package nginx-helper
9
+ * @subpackage nginx-helper/includes
10
+ */
11
+
12
+ /**
13
+ * Register all actions and filters for the plugin.
14
+ *
15
+ * Maintain a list of all hooks that are registered throughout
16
+ * the plugin, and register them with the WordPress API. Call the
17
+ * run function to execute the list of actions and filters.
18
+ *
19
+ * @package nginx-helper
20
+ *
21
+ * @subpackage nginx-helper/includes
22
+ *
23
+ * @author rtCamp
24
+ */
25
+ class Nginx_Helper_Loader {
26
+
27
+ /**
28
+ * The array of actions registered with WordPress.
29
+ *
30
+ * @since 2.0.0
31
+ *
32
+ * @access protected
33
+ *
34
+ * @var array $actions The actions registered with WordPress to fire when the plugin loads.
35
+ */
36
+ protected $actions;
37
+
38
+ /**
39
+ * The array of filters registered with WordPress.
40
+ *
41
+ * @since 2.0.0
42
+ *
43
+ * @access protected
44
+ *
45
+ * @var array $filters The filters registered with WordPress to fire when the plugin loads.
46
+ */
47
+ protected $filters;
48
+
49
+ /**
50
+ * Initialize the collections used to maintain the actions and filters.
51
+ *
52
+ * @since 2.0.0
53
+ */
54
+ public function __construct() {
55
+
56
+ $this->actions = array();
57
+ $this->filters = array();
58
+
59
+ }
60
+
61
+ /**
62
+ * Add a new action to the collection to be registered with WordPress.
63
+ *
64
+ * @since 2.0.0
65
+ *
66
+ * @param string $hook The name of the WordPress action that is being registered.
67
+ * @param object $component A reference to the instance of the object on which the action is defined.
68
+ * @param string $callback The name of the function definition on the $component.
69
+ * @param int $priority The priority at which the function should be fired.
70
+ * @param int $accepted_args The number of arguments that should be passed to the $callback.
71
+ */
72
+ public function add_action( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
73
+ $this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args );
74
+ }
75
+
76
+ /**
77
+ * Add a new filter to the collection to be registered with WordPress.
78
+ *
79
+ * @since 2.0.0
80
+ *
81
+ * @param string $hook The name of the WordPress filter that is being registered.
82
+ * @param object $component A reference to the instance of the object on which the filter is defined.
83
+ * @param string $callback The name of the function definition on the $component.
84
+ * @param int $priority The priority at which the function should be fired.
85
+ * @param int $accepted_args The number of arguments that should be passed to the $callback.
86
+ */
87
+ public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) {
88
+ $this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args );
89
+ }
90
+
91
+ /**
92
+ * A utility function that is used to register the actions and hooks into a single
93
+ * collection.
94
+ *
95
+ * @since 2.0.0
96
+ *
97
+ * @access private
98
+ *
99
+ * @param array $hooks The collection of hooks that is being registered (that is, actions or filters).
100
+ * @param string $hook The name of the WordPress filter that is being registered.
101
+ * @param object $component A reference to the instance of the object on which the filter is defined.
102
+ * @param string $callback The name of the function definition on the $component.
103
+ * @param int $priority The priority at which the function should be fired.
104
+ * @param int $accepted_args The number of arguments that should be passed to the $callback.
105
+ *
106
+ * @return array The collection of actions and filters registered with WordPress.
107
+ */
108
+ private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) {
109
+
110
+ $hooks[] = array(
111
+ 'hook' => $hook,
112
+ 'component' => $component,
113
+ 'callback' => $callback,
114
+ 'priority' => $priority,
115
+ 'accepted_args' => $accepted_args
116
+ );
117
+
118
+ return $hooks;
119
+
120
+ }
121
+
122
+ /**
123
+ * Register the filters and actions with WordPress.
124
+ *
125
+ * @since 2.0.0
126
+ */
127
+ public function run() {
128
+
129
+ foreach ( $this->filters as $hook ) {
130
+ add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
131
+ }
132
+
133
+ foreach ( $this->actions as $hook ) {
134
+ add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] );
135
+ }
136
+
137
+ }
138
+
139
+ }
includes/class-nginx-helper.php ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The file that defines the core plugin class
4
+ *
5
+ * A class definition that includes attributes and functions used across both the
6
+ * public-facing side of the site and the admin area.
7
+ *
8
+ * @link https://rtcamp.com/nginx-helper/
9
+ * @since 2.0.0
10
+ *
11
+ * @package nginx-helper
12
+ * @subpackage nginx-helper/includes
13
+ */
14
+
15
+ /**
16
+ * The core plugin class.
17
+ *
18
+ * This is used to define internationalization, admin-specific hooks, and
19
+ * public-facing site hooks.
20
+ *
21
+ * Also maintains the unique identifier of this plugin as well as the current
22
+ * version of the plugin.
23
+ *
24
+ * @since 2.0.0
25
+ * @package nginx-helper
26
+ * @subpackage nginx-helper/includes
27
+ * @author rtCamp
28
+ */
29
+ class Nginx_Helper {
30
+
31
+ /**
32
+ * The loader that's responsible for maintaining and registering all hooks that power
33
+ * the plugin.
34
+ *
35
+ * @since 2.0.0
36
+ * @access protected
37
+ * @var Nginx_Helper_Loader $loader Maintains and registers all hooks for the plugin.
38
+ */
39
+ protected $loader;
40
+
41
+ /**
42
+ * The unique identifier of this plugin.
43
+ *
44
+ * @since 2.0.0
45
+ * @access protected
46
+ * @var string $plugin_name The string used to uniquely identify this plugin.
47
+ */
48
+ protected $plugin_name;
49
+
50
+ /**
51
+ * The current version of the plugin.
52
+ *
53
+ * @since 2.0.0
54
+ * @access protected
55
+ * @var string $version The current version of the plugin.
56
+ */
57
+ protected $version;
58
+
59
+ /**
60
+ * Minimum WordPress Version Required.
61
+ *
62
+ * @since 2.0.0
63
+ * @access public
64
+ * @var string $minium_WP
65
+ */
66
+ protected $minimum_WP;
67
+
68
+ /**
69
+ * Define the core functionality of the plugin.
70
+ *
71
+ * Set the plugin name and the plugin version that can be used throughout the plugin.
72
+ * Load the dependencies, define the locale, and set the hooks for the admin area and
73
+ * the public-facing side of the site.
74
+ *
75
+ * @since 2.0.0
76
+ */
77
+ public function __construct() {
78
+
79
+ $this->plugin_name = 'nginx-helper';
80
+ $this->version = '2.0.0';
81
+ $this->minimum_WP = '3.0';
82
+
83
+ if ( ! $this->required_wp_version() ) {
84
+ return;
85
+ }
86
+
87
+ if ( ! defined( 'RT_WP_NGINX_HELPER_CACHE_PATH' ) ) {
88
+ define( 'RT_WP_NGINX_HELPER_CACHE_PATH', '/var/run/nginx-cache' );
89
+ }
90
+
91
+ $this->load_dependencies();
92
+ $this->set_locale();
93
+ $this->define_admin_hooks();
94
+ }
95
+
96
+ /**
97
+ * Load the required dependencies for this plugin.
98
+ *
99
+ * Include the following files that make up the plugin:
100
+ *
101
+ * - Nginx_Helper_Loader. Orchestrates the hooks of the plugin.
102
+ * - Nginx_Helper_i18n. Defines internationalization functionality.
103
+ * - Nginx_Helper_Admin. Defines all hooks for the admin area.
104
+ *
105
+ * Create an instance of the loader which will be used to register the hooks
106
+ * with WordPress.
107
+ *
108
+ * @since 2.0.0
109
+ * @access private
110
+ */
111
+ private function load_dependencies() {
112
+
113
+ /**
114
+ * The class responsible for orchestrating the actions and filters of the
115
+ * core plugin.
116
+ */
117
+ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-nginx-helper-loader.php';
118
+
119
+ /**
120
+ * The class responsible for defining internationalization functionality
121
+ * of the plugin.
122
+ */
123
+ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'includes/class-nginx-helper-i18n.php';
124
+
125
+ /**
126
+ * The class responsible for defining all actions that required for purging urls.
127
+ */
128
+ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-purger.php';
129
+
130
+ /**
131
+ * The class responsible for defining all actions that occur in the admin area.
132
+ */
133
+ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-nginx-helper-admin.php';
134
+
135
+ /**
136
+ * The class responsible for defining all actions that occur in the public-facing
137
+ * side of the site.
138
+ */
139
+ $this->loader = new Nginx_Helper_Loader();
140
+
141
+ }
142
+
143
+ /**
144
+ * Define the locale for this plugin for internationalization.
145
+ *
146
+ * Uses the Nginx_Helper_i18n class in order to set the domain and to register the hook
147
+ * with WordPress.
148
+ *
149
+ * @since 2.0.0
150
+ * @access private
151
+ */
152
+ private function set_locale() {
153
+
154
+ $plugin_i18n = new Nginx_Helper_i18n();
155
+ $plugin_i18n->set_domain( $this->get_plugin_name() );
156
+
157
+ $this->loader->add_action( 'plugins_loaded', $plugin_i18n, 'load_plugin_textdomain' );
158
+
159
+ }
160
+
161
+ /**
162
+ * Register all of the hooks related to the admin area functionality of the plugin.
163
+ *
164
+ * @since 2.0.0
165
+ * @access private
166
+ */
167
+ private function define_admin_hooks() {
168
+
169
+ global $nginx_helper_admin, $nginx_purger;
170
+
171
+ $nginx_helper_admin = new Nginx_Helper_Admin( $this->get_plugin_name(), $this->get_version() );
172
+
173
+ // Defines global variables.
174
+ if ( ! empty( $nginx_helper_admin->options['cache_method'] ) && $nginx_helper_admin->options['cache_method'] === 'enable_redis' ) {
175
+
176
+ if ( class_exists( 'Redis' ) ) { // Use PHP5-Redis extension if installed.
177
+
178
+ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-phpredis-purger.php';
179
+ $nginx_purger = new PhpRedis_Purger();
180
+
181
+ } else {
182
+
183
+ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-predis-purger.php';
184
+ $nginx_purger = new Predis_Purger();
185
+
186
+ }
187
+
188
+ } else {
189
+
190
+ require_once plugin_dir_path( dirname( __FILE__ ) ) . 'admin/class-fastcgi-purger.php';
191
+ $nginx_purger = new FastCGI_Purger();
192
+
193
+ }
194
+
195
+ $this->loader->add_action( 'admin_enqueue_scripts', $nginx_helper_admin, 'enqueue_styles' );
196
+ $this->loader->add_action( 'admin_enqueue_scripts', $nginx_helper_admin, 'enqueue_scripts' );
197
+
198
+ if ( is_multisite() ) {
199
+ $this->loader->add_action( 'network_admin_menu', $nginx_helper_admin, 'nginx_helper_admin_menu' );
200
+ $this->loader->add_filter( 'network_admin_plugin_action_links_' . NGINX_HELPER_BASENAME, $nginx_helper_admin, 'nginx_helper_settings_link' );
201
+ } else {
202
+ $this->loader->add_action( 'admin_menu', $nginx_helper_admin, 'nginx_helper_admin_menu' );
203
+ $this->loader->add_filter( 'plugin_action_links_' . NGINX_HELPER_BASENAME, $nginx_helper_admin, 'nginx_helper_settings_link' );
204
+ }
205
+
206
+ $this->loader->add_action( 'admin_bar_menu', $nginx_helper_admin, 'nginx_helper_toolbar_purge_link', 100 );
207
+ $this->loader->add_action( 'wp_ajax_rt_get_feeds', $nginx_helper_admin, 'nginx_helper_get_feeds' );
208
+
209
+ $this->loader->add_action( 'shutdown', $nginx_helper_admin, 'add_timestamps', 99999 );
210
+ $this->loader->add_action( 'add_init', $nginx_helper_admin, 'update_map' );
211
+
212
+ // Add actions to purge.
213
+ $this->loader->add_action( 'wp_insert_comment', $nginx_purger, 'purge_post_on_comment', 200, 2 );
214
+ $this->loader->add_action( 'transition_comment_status', $nginx_purger, 'purge_post_on_comment_change', 200, 3 );
215
+ $this->loader->add_action( 'transition_post_status', $nginx_helper_admin, 'set_future_post_option_on_future_status', 20, 3 );
216
+ $this->loader->add_action( 'delete_post', $nginx_helper_admin, 'unset_future_post_option_on_delete', 20, 1 );
217
+ $this->loader->add_action( 'rt_wp_nginx_helper_check_log_file_size_daily', $nginx_purger, 'check_and_truncate_log_file', 100, 1 );
218
+ $this->loader->add_action( 'edit_attachment', $nginx_purger, 'purge_image_on_edit', 100, 1 );
219
+ $this->loader->add_action( 'wpmu_new_blog', $nginx_helper_admin, 'update_new_blog_options', 10, 1 );
220
+ $this->loader->add_action( 'transition_post_status', $nginx_purger, 'purge_on_post_moved_to_trash', 20, 3 );
221
+ $this->loader->add_action( 'edit_term', $nginx_purger, 'purge_on_term_taxonomy_edited', 20, 3 );
222
+ $this->loader->add_action( 'delete_term', $nginx_purger, 'purge_on_term_taxonomy_edited', 20, 3 );
223
+ $this->loader->add_action( 'check_ajax_referer', $nginx_purger, 'purge_on_check_ajax_referer', 20 );
224
+ $this->loader->add_action( 'admin_init', $nginx_helper_admin, 'purge_all' );
225
+
226
+ // expose action to allow other plugins to purge the cache.
227
+ $this->loader->add_action( 'rt_nginx_helper_purge_all', $nginx_purger, 'purge_all' );
228
+ }
229
+
230
+ /**
231
+ * Run the loader to execute all of the hooks with WordPress.
232
+ *
233
+ * @since 2.0.0
234
+ */
235
+ public function run() {
236
+ $this->loader->run();
237
+ }
238
+
239
+ /**
240
+ * The name of the plugin used to uniquely identify it within the context of
241
+ * WordPress and to define internationalization functionality.
242
+ *
243
+ * @since 2.0.0
244
+ * @return string The name of the plugin.
245
+ */
246
+ public function get_plugin_name() {
247
+ return $this->plugin_name;
248
+ }
249
+
250
+ /**
251
+ * The reference to the class that orchestrates the hooks with the plugin.
252
+ *
253
+ * @since 2.0.0
254
+ *
255
+ * @return Nginx_Helper_Loader Orchestrates the hooks of the plugin.
256
+ */
257
+ public function get_loader() {
258
+ return $this->loader;
259
+ }
260
+
261
+ /**
262
+ * Retrieve the version number of the plugin.
263
+ *
264
+ * @since 2.0.0
265
+ * @return string The version number of the plugin.
266
+ */
267
+ public function get_version() {
268
+ return $this->version;
269
+ }
270
+
271
+ /**
272
+ * Check wp version.
273
+ *
274
+ * @since 2.0.0
275
+ *
276
+ * @global string $wp_version
277
+ *
278
+ * @return boolean
279
+ */
280
+ public function required_wp_version() {
281
+
282
+ global $wp_version;
283
+
284
+ $wp_ok = version_compare( $wp_version, $this->minimum_WP, '>=' );
285
+
286
+ if ( false === $wp_ok ) {
287
+
288
+ add_action( 'admin_notices', array( &$this, 'display_notices' ) );
289
+ add_action( 'network_admin_notices', array( &$this, 'display_notices' ) );
290
+ return false;
291
+
292
+ }
293
+
294
+ return true;
295
+
296
+ }
297
+
298
+ /**
299
+ * Dispay plugin notices.
300
+ */
301
+ public function display_notices() {
302
+ ?>
303
+ <div id="message" class="error">
304
+ <p>
305
+ <strong>
306
+ <?php
307
+ printf(
308
+ /* translators: %s is Minimum WP version. */
309
+ esc_html__( 'Sorry, Nginx Helper requires WordPress %s or higher', 'nginx-helper' ), esc_html( $this->minimum_WP )
310
+ );
311
+ ?>
312
+ </strong>
313
+ </p>
314
+ </div>
315
+ <?php
316
+ }
317
+ }
includes/index.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Silence is golden.
4
+ *
5
+ * @package nginx-helper
6
+ */
7
+
8
+ // Silence.
index.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Silence is golden.
4
+ *
5
+ * @package nginx-helper
6
+ */
7
+
8
+ // Silence.
languages/nginx-helper.mo CHANGED
Binary file
languages/nginx-helper.po CHANGED
@@ -1,501 +1,599 @@
 
 
1
  msgid ""
2
  msgstr ""
3
- "Project-Id-Version: Nginx Helper 1.7.6\n"
4
- "Report-Msgid-Bugs-To: \n"
5
- "POT-Creation-Date: 2014-01-02 17:41+0530\n"
6
- "PO-Revision-Date: 2014-01-02 17:41+0530\n"
7
- "Last-Translator: rtCamp <support@rtcamp.com>\n"
8
- "Language-Team: rtCampers <support@rtcamp.com>\n"
9
- "Language: en\n"
10
  "MIME-Version: 1.0\n"
11
  "Content-Type: text/plain; charset=UTF-8\n"
12
  "Content-Transfer-Encoding: 8bit\n"
13
- "X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c\n"
14
- "X-Poedit-Basepath: .\n"
15
- "Plural-Forms: nplurals=2; plural=n != 1;\n"
16
- "X-Generator: Poedit 1.5.4\n"
17
- "X-Poedit-SearchPath-0: ..\n"
 
 
 
 
 
18
 
19
- #: ../nginx-helper.php:254
20
- msgid "Purge initiated"
 
 
 
 
21
  msgstr ""
22
 
23
- #: ../nginx-helper.php:340
 
 
 
 
24
  msgid "Settings"
25
  msgstr ""
26
 
27
- #: ../nginx-helper.php:367
28
  msgid "No items"
29
  msgstr ""
30
 
31
- #: ../nginx-helper.php:372
32
  msgid "Posted "
33
  msgstr ""
34
 
35
- #: ../purger.php:366
36
- #, php-format
37
- msgid "Purging homepage '%s'"
 
 
 
38
  msgstr ""
39
 
40
- #: ../purger.php:376
 
 
 
 
41
  msgid "Purging personal urls"
42
  msgstr ""
43
 
44
- #: ../purger.php:384
45
  msgid "No personal urls available"
46
  msgstr ""
47
 
48
- #: ../purger.php:392
49
  msgid "Purging category archives"
50
  msgstr ""
51
 
52
- #: ../purger.php:396
53
- #, php-format
54
  msgid "Purging category '%d'"
55
  msgstr ""
56
 
57
- #: ../purger.php:406
58
  msgid "Purging tags archives"
59
  msgstr ""
60
 
61
- #: ../purger.php:410 ../purger.php:468
62
- #, php-format
63
- msgid "Purging tag '%s' (id %d)"
64
  msgstr ""
65
 
66
- #: ../purger.php:420
67
  msgid "Purging post custom taxonomies related"
68
  msgstr ""
69
 
70
- #: ../purger.php:424 ../purger.php:485
71
- #, php-format
72
  msgid "Purging custom taxonomy '%s'"
73
  msgstr ""
74
 
75
- #: ../purger.php:434 ../purger.php:495
76
- #, php-format
77
  msgid "Your built-in taxonomy '%s' has param '_builtin' set to false."
78
  msgstr ""
79
 
80
- #: ../purger.php:438 ../purger.php:499
81
  msgid "No custom taxonomies"
82
  msgstr ""
83
 
84
- #: ../purger.php:446
85
  msgid "Purging all categories"
86
  msgstr ""
87
 
88
- #: ../purger.php:451
89
- #, php-format
90
- msgid "Purging category '%s' (id %d)"
91
  msgstr ""
92
 
93
- #: ../purger.php:455
94
  msgid "No categories archives"
95
  msgstr ""
96
 
97
- #: ../purger.php:463
98
  msgid "Purging all tags"
99
  msgstr ""
100
 
101
- #: ../purger.php:472
102
  msgid "No tags archives"
103
  msgstr ""
104
 
105
- #: ../purger.php:480
106
  msgid "Purging all custom taxonomies"
107
  msgstr ""
108
 
109
- #: ../purger.php:516
110
  msgid "Purging all posts, pages and custom post types."
111
  msgstr ""
112
 
113
- #: ../purger.php:526
114
- #, php-format
115
- msgid "Purging post id '%d' (post type '%s')"
116
  msgstr ""
117
 
118
- #: ../purger.php:530
119
  msgid "No posts"
120
  msgstr ""
121
 
122
- #: ../purger.php:538
123
  msgid "Purging all date-based archives."
124
  msgstr ""
125
 
126
- #: ../purger.php:553
127
  msgid "Purging all daily archives."
128
  msgstr ""
129
 
130
- #: ../purger.php:566
131
- #, php-format
132
- msgid "Purging daily archive '%s/%s/%s'"
133
  msgstr ""
134
 
135
- #: ../purger.php:570
136
  msgid "No daily archives"
137
  msgstr ""
138
 
139
- #: ../purger.php:578
140
  msgid "Purging all monthly archives."
141
  msgstr ""
142
 
143
- #: ../purger.php:591
144
- #, php-format
145
- msgid "Purging monthly archive '%s/%s'"
146
  msgstr ""
147
 
148
- #: ../purger.php:595
149
  msgid "No monthly archives"
150
  msgstr ""
151
 
152
- #: ../purger.php:603
153
  msgid "Purging all yearly archives."
154
  msgstr ""
155
 
156
- #: ../purger.php:616
157
- #, php-format
158
  msgid "Purging yearly archive '%s'"
159
  msgstr ""
160
 
161
- #: ../purger.php:620
162
  msgid "No yearly archives"
163
  msgstr ""
164
 
165
- #: ../purger.php:626
166
  msgid "Let's purge everything!"
167
  msgstr ""
168
 
169
- #: ../purger.php:638
170
- msgid "Everthing purged!"
171
  msgstr ""
172
 
173
- #: ../purger.php:645
174
  msgid "Term taxonomy edited or deleted"
175
  msgstr ""
176
 
177
- #: ../purger.php:648
178
- #, php-format
179
- msgid "Term taxonomy '%s' edited, (tt_id '%d', term_id '%d', taxonomy '%s')"
180
  msgstr ""
181
 
182
- #: ../purger.php:650
183
- #, php-format
184
- msgid ""
185
- "A term taxonomy has been deleted from taxonomy '%s', (tt_id '%d', term_id "
186
- "'%d')"
187
  msgstr ""
188
 
189
- #: ../purger.php:663
190
  msgid "Widget saved, moved or removed in a sidebar"
191
  msgstr ""
192
 
193
- #: ../admin/admin.php:26
194
- msgid "General"
195
  msgstr ""
196
 
197
- #: ../admin/admin.php:30
198
- msgid "Support"
199
  msgstr ""
200
 
201
- #: ../admin/admin.php:40 ../admin/admin.php:47
202
- msgid "Nginx Helper"
203
  msgstr ""
204
 
205
- #: ../admin/admin.php:78
206
- msgid "Nginx Settings"
207
  msgstr ""
208
 
209
- #: ../admin/admin.php:123 ../admin/lib/nginx-general.php:79
210
- #: ../admin/lib/nginx-general.php:89
211
- msgid "Purge Cache"
212
  msgstr ""
213
 
214
- #: ../admin/install.php:22
215
- msgid "Sorry, you need to be an administrator to use Nginx Helper"
216
  msgstr ""
217
 
218
- #: ../admin/lib/nginx-general.php:26
219
- msgid "Log file size must be a number"
220
  msgstr ""
221
 
222
- #: ../admin/lib/nginx-general.php:61
223
- msgid "Settings saved."
224
  msgstr ""
225
 
226
- #: ../admin/lib/nginx-general.php:87
227
- msgid "Purge All Cache"
228
  msgstr ""
229
 
230
- #: ../admin/lib/nginx-general.php:99
231
- msgid "Plugin Options"
232
  msgstr ""
233
 
234
- #: ../admin/lib/nginx-general.php:109
235
- #, php-format
236
- msgid ""
237
- "Enable Cache Purge (<a target=\"_blank\" href=\"%s\" title=\"External "
238
- "settings for nginx\">requires external settings for nginx</a>)"
239
  msgstr ""
240
 
241
- #: ../admin/lib/nginx-general.php:117
242
- msgid "Enable Nginx Map."
243
  msgstr ""
244
 
245
- #: ../admin/lib/nginx-general.php:124
246
- msgid "Enable Logging"
247
  msgstr ""
248
 
249
- #: ../admin/lib/nginx-general.php:130
250
- msgid "Enable Nginx Timestamp in HTML"
251
  msgstr ""
252
 
253
- #: ../admin/lib/nginx-general.php:139
254
- msgid "Purging Options"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  msgstr ""
256
 
257
- #: ../admin/lib/nginx-general.php:145
258
  msgid "Purge Homepage:"
259
  msgstr ""
260
 
261
- #: ../admin/lib/nginx-general.php:149
262
  msgid "when a post/page/custom post is modified or added."
263
  msgstr ""
264
 
265
- #: ../admin/lib/nginx-general.php:153 ../admin/lib/nginx-general.php:216
266
- msgid ""
267
- "when a <strong>post</strong> (or page/custom post) is <strong>modified</"
268
- "strong> or <strong>added</strong>."
 
 
 
 
 
 
 
 
 
 
269
  msgstr ""
270
 
271
- #: ../admin/lib/nginx-general.php:158
 
 
 
 
 
 
 
 
272
  msgid "when an existing post/page/custom post is modified."
273
  msgstr ""
274
 
275
- #: ../admin/lib/nginx-general.php:162 ../admin/lib/nginx-general.php:225
276
- msgid ""
277
- "when a <strong>published post</strong> (or page/custom post) is "
278
- "<strong>trashed</strong>."
 
 
279
  msgstr ""
280
 
281
- #: ../admin/lib/nginx-general.php:170
282
  msgid "Purge Post/Page/Custom Post Type:"
283
  msgstr ""
284
 
285
- #: ../admin/lib/nginx-general.php:175
286
- msgid "when a post/page/custom post is published."
287
  msgstr ""
288
 
289
- #: ../admin/lib/nginx-general.php:179
290
- msgid "when a <strong>post</strong> is <strong>published</strong>."
291
  msgstr ""
292
 
293
- #: ../admin/lib/nginx-general.php:184
294
  msgid "when a comment is approved/published."
295
  msgstr ""
296
 
297
- #: ../admin/lib/nginx-general.php:188 ../admin/lib/nginx-general.php:235
298
- msgid "when a <strong>comment</strong> is <strong>approved/published</strong>."
 
 
 
 
299
  msgstr ""
300
 
301
- #: ../admin/lib/nginx-general.php:193
302
  msgid "when a comment is unapproved/deleted."
303
  msgstr ""
304
 
305
- #: ../admin/lib/nginx-general.php:197 ../admin/lib/nginx-general.php:244
306
- msgid "when a <strong>comment</strong> is <strong>unapproved/deleted</strong>."
307
  msgstr ""
308
 
309
- #: ../admin/lib/nginx-general.php:206
310
  msgid "Purge Archives:"
311
  msgstr ""
312
 
313
- #: ../admin/lib/nginx-general.php:207
314
  msgid "(date, category, tag, author, custom taxonomies)"
315
  msgstr ""
316
 
317
- #: ../admin/lib/nginx-general.php:212
318
- msgid "when an post/page/custom post is modified or added.</span>"
319
  msgstr ""
320
 
321
- #: ../admin/lib/nginx-general.php:221
322
- msgid "when an existing post/page/custom post is trashed.</span>"
323
  msgstr ""
324
 
325
- #: ../admin/lib/nginx-general.php:231
326
- msgid "when a comment is approved/published.</span>"
327
  msgstr ""
328
 
329
- #: ../admin/lib/nginx-general.php:240
330
- msgid "when a comment is unapproved/deleted.</span>"
 
 
 
 
 
 
 
 
 
 
 
 
331
  msgstr ""
332
 
333
- #: ../admin/lib/nginx-general.php:259
334
  msgid "Nginx Map"
335
  msgstr ""
336
 
337
- #: ../admin/lib/nginx-general.php:263
338
- #, php-format
339
- msgid ""
340
- "Can't write on map file.<br /><br />Check you have write permission on "
341
- "<strong>%s</strong>"
342
  msgstr ""
343
 
344
- #: ../admin/lib/nginx-general.php:268
345
- msgid ""
346
- "Nginx Map path to include in nginx settings<br /><small>(recommended)</small>"
347
  msgstr ""
348
 
349
- #: ../admin/lib/nginx-general.php:274
350
- msgid ""
351
- "Or,<br />Text to manually copy and paste in nginx settings<br /><small>(if "
352
- "your network is small and new sites are not added frequently)</small>"
 
 
 
 
 
 
353
  msgstr ""
354
 
355
- #: ../admin/lib/nginx-general.php:286
 
 
 
 
 
 
 
 
356
  msgid "Logging Options"
357
  msgstr ""
358
 
359
- #: ../admin/lib/nginx-general.php:304
360
- #, php-format
361
- msgid ""
362
- "Can't write on log file.<br /><br />Check you have write permission on "
363
- "<strong>%s</strong>"
364
  msgstr ""
365
 
366
- #: ../admin/lib/nginx-general.php:310
367
  msgid "Logs path"
368
  msgstr ""
369
 
370
- #: ../admin/lib/nginx-general.php:314
371
  msgid "View Log"
372
  msgstr ""
373
 
374
- #: ../admin/lib/nginx-general.php:315
375
  msgid "Log"
376
  msgstr ""
377
 
378
- #: ../admin/lib/nginx-general.php:318
379
  msgid "Log level"
380
  msgstr ""
381
 
382
- #: ../admin/lib/nginx-general.php:321
383
  msgid "None"
384
  msgstr ""
385
 
386
- #: ../admin/lib/nginx-general.php:322
387
  msgid "Info"
388
  msgstr ""
389
 
390
- #: ../admin/lib/nginx-general.php:323
391
  msgid "Warning"
392
  msgstr ""
393
 
394
- #: ../admin/lib/nginx-general.php:324
395
  msgid "Error"
396
  msgstr ""
397
 
398
- #: ../admin/lib/nginx-general.php:329
399
  msgid "Max log file size"
400
  msgstr ""
401
 
402
- #: ../admin/lib/nginx-general.php:331
403
  msgid "Mb"
404
  msgstr ""
405
 
406
- #: ../admin/lib/nginx-general.php:342
407
  msgid "Save All Changes"
408
  msgstr ""
409
 
410
- #: ../admin/lib/nginx-sidebar.php:8
 
 
 
 
411
  msgid "Need Help?"
412
  msgstr ""
413
 
414
- #: ../admin/lib/nginx-sidebar.php:11
415
- #, php-format
416
- msgid "Please use our <a href=\"%s\">free support forum</a>."
 
 
 
417
  msgstr ""
418
 
419
- #: ../admin/lib/nginx-sidebar.php:17
420
  msgid "Getting Social is Good"
421
  msgstr ""
422
 
423
- #: ../admin/lib/nginx-sidebar.php:20
424
  msgid "Become a fan on Facebook"
425
  msgstr ""
426
 
427
- #: ../admin/lib/nginx-sidebar.php:21
428
  msgid "Follow us on Twitter"
429
  msgstr ""
430
 
431
- #: ../admin/lib/nginx-sidebar.php:22
432
  msgid "Add to Circle"
433
  msgstr ""
434
 
435
- #: ../admin/lib/nginx-sidebar.php:23
436
  msgid "Subscribe to our feeds"
437
  msgstr ""
438
 
439
- #: ../admin/lib/nginx-sidebar.php:29
440
  msgid "Useful Links"
441
  msgstr ""
442
 
443
- #: ../admin/lib/nginx-sidebar.php:34
444
  msgid "WordPress-Nginx Solutions"
445
  msgstr ""
446
 
447
- #: ../admin/lib/nginx-sidebar.php:37
448
  msgid "WordPress Theme Devleopment"
449
  msgstr ""
450
 
451
- #: ../admin/lib/nginx-sidebar.php:40
452
  msgid "WordPress Plugin Development"
453
  msgstr ""
454
 
455
- #: ../admin/lib/nginx-sidebar.php:43
456
  msgid "WordPress Consultancy"
457
  msgstr ""
458
 
459
- #: ../admin/lib/nginx-sidebar.php:46
460
  msgid "easyengine (ee)"
461
  msgstr ""
462
 
463
- #: ../admin/lib/nginx-sidebar.php:53
464
  msgid "Click to toggle"
465
  msgstr ""
466
 
467
- #: ../admin/lib/nginx-sidebar.php:54
468
  msgid "Latest News"
469
  msgstr ""
470
 
471
- #: ../admin/lib/nginx-sidebar.php:55
472
  msgid "Loading..."
473
  msgstr ""
474
 
475
- #: ../admin/lib/nginx-support.php:8
476
  msgid "Support Forums"
477
  msgstr ""
478
 
479
- #: ../admin/lib/nginx-support.php:13
480
- msgid "Need Help!, ask your queries on our forums:"
481
- msgstr ""
482
-
483
- #: ../admin/lib/nginx-support.php:16
484
  msgid "Free Support"
485
  msgstr ""
486
 
487
- #: ../admin/lib/nginx-support.php:18
488
  msgid "Free Support Forum"
489
  msgstr ""
490
 
491
- #: ../admin/lib/nginx-support.php:18 ../admin/lib/nginx-support.php:24
492
  msgid "Link to forum"
493
  msgstr ""
494
 
495
- #: ../admin/lib/nginx-support.php:22
496
  msgid "Premium Support"
497
  msgstr ""
498
 
499
- #: ../admin/lib/nginx-support.php:24
500
  msgid "Premium Support Forum"
501
  msgstr ""
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2018 nginx-helper
2
+ # This file is distributed under the same license as the nginx-helper package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: nginx-helper\n"
 
 
 
 
 
 
6
  "MIME-Version: 1.0\n"
7
  "Content-Type: text/plain; charset=UTF-8\n"
8
  "Content-Transfer-Encoding: 8bit\n"
9
+ "X-Poedit-Basepath: ..\n"
10
+ "X-Poedit-KeywordsList: __;_e;_ex:1,2c;_n:1,2;_n_noop:1,2;_nx:1,2,4c;_nx_noop:1,2,3c;_x:1,2c;esc_attr__;esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c\n"
11
+ "X-Poedit-SearchPath-0: .\n"
12
+ "X-Poedit-SearchPathExcluded-0: *.js\n"
13
+ "X-Poedit-SourceCharset: UTF-8\n"
14
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
15
+
16
+ #: ../admin/class-nginx-helper-admin.php:87
17
+ msgid "General"
18
+ msgstr ""
19
 
20
+ #: ../admin/class-nginx-helper-admin.php:91
21
+ msgid "Support"
22
+ msgstr ""
23
+
24
+ #: ../admin/class-nginx-helper-admin.php:171, ../admin/class-nginx-helper-admin.php:172, ../admin/class-nginx-helper-admin.php:182, ../admin/class-nginx-helper-admin.php:183
25
+ msgid "Nginx Helper"
26
  msgstr ""
27
 
28
+ #: ../admin/class-nginx-helper-admin.php:216, ../admin/class-nginx-helper-admin.php:218
29
+ msgid "Purge Cache"
30
+ msgstr ""
31
+
32
+ #: ../admin/class-nginx-helper-admin.php:319
33
  msgid "Settings"
34
  msgstr ""
35
 
36
+ #: ../admin/class-nginx-helper-admin.php:382
37
  msgid "No items"
38
  msgstr ""
39
 
40
+ #: ../admin/class-nginx-helper-admin.php:393
41
  msgid "Posted "
42
  msgstr ""
43
 
44
+ #: ../admin/class-nginx-helper-admin.php:691
45
+ msgid "Purge initiated"
46
+ msgstr ""
47
+
48
+ #: ../admin/class-purger.php:607
49
+ msgid "Purging homepage (WPML) "
50
  msgstr ""
51
 
52
+ #: ../admin/class-purger.php:612
53
+ msgid "Purging homepage "
54
+ msgstr ""
55
+
56
+ #: ../admin/class-purger.php:631
57
  msgid "Purging personal urls"
58
  msgstr ""
59
 
60
+ #: ../admin/class-purger.php:640
61
  msgid "No personal urls available"
62
  msgstr ""
63
 
64
+ #: ../admin/class-purger.php:656
65
  msgid "Purging category archives"
66
  msgstr ""
67
 
68
+ #: ../admin/class-purger.php:664
 
69
  msgid "Purging category '%d'"
70
  msgstr ""
71
 
72
+ #: ../admin/class-purger.php:683
73
  msgid "Purging tags archives"
74
  msgstr ""
75
 
76
+ #: ../admin/class-purger.php:691, ../admin/class-purger.php:794
77
+ msgid "Purging tag '%1$s' ( id %2$d )"
 
78
  msgstr ""
79
 
80
+ #: ../admin/class-purger.php:711
81
  msgid "Purging post custom taxonomies related"
82
  msgstr ""
83
 
84
+ #: ../admin/class-purger.php:724, ../admin/class-purger.php:827
 
85
  msgid "Purging custom taxonomy '%s'"
86
  msgstr ""
87
 
88
+ #: ../admin/class-purger.php:739, ../admin/class-purger.php:844
 
89
  msgid "Your built-in taxonomy '%s' has param '_builtin' set to false."
90
  msgstr ""
91
 
92
+ #: ../admin/class-purger.php:744, ../admin/class-purger.php:850
93
  msgid "No custom taxonomies"
94
  msgstr ""
95
 
96
+ #: ../admin/class-purger.php:757
97
  msgid "Purging all categories"
98
  msgstr ""
99
 
100
+ #: ../admin/class-purger.php:765
101
+ msgid "Purging category '%1$s' ( id %2$d )"
 
102
  msgstr ""
103
 
104
+ #: ../admin/class-purger.php:772
105
  msgid "No categories archives"
106
  msgstr ""
107
 
108
+ #: ../admin/class-purger.php:786
109
  msgid "Purging all tags"
110
  msgstr ""
111
 
112
+ #: ../admin/class-purger.php:800
113
  msgid "No tags archives"
114
  msgstr ""
115
 
116
+ #: ../admin/class-purger.php:814
117
  msgid "Purging all custom taxonomies"
118
  msgstr ""
119
 
120
+ #: ../admin/class-purger.php:877
121
  msgid "Purging all posts, pages and custom post types."
122
  msgstr ""
123
 
124
+ #: ../admin/class-purger.php:892
125
+ msgid "Purging post id '%1$d' ( post type '%2$s' )"
 
126
  msgstr ""
127
 
128
+ #: ../admin/class-purger.php:898
129
  msgid "No posts"
130
  msgstr ""
131
 
132
+ #: ../admin/class-purger.php:912
133
  msgid "Purging all date-based archives."
134
  msgstr ""
135
 
136
+ #: ../admin/class-purger.php:929
137
  msgid "Purging all daily archives."
138
  msgstr ""
139
 
140
+ #: ../admin/class-purger.php:948
141
+ msgid "Purging daily archive '%1$s/%2$s/%3$s'"
 
142
  msgstr ""
143
 
144
+ #: ../admin/class-purger.php:957
145
  msgid "No daily archives"
146
  msgstr ""
147
 
148
+ #: ../admin/class-purger.php:969
149
  msgid "Purging all monthly archives."
150
  msgstr ""
151
 
152
+ #: ../admin/class-purger.php:995
153
+ msgid "Purging monthly archive '%1$s/%2$s'"
 
154
  msgstr ""
155
 
156
+ #: ../admin/class-purger.php:1001
157
  msgid "No monthly archives"
158
  msgstr ""
159
 
160
+ #: ../admin/class-purger.php:1013
161
  msgid "Purging all yearly archives."
162
  msgstr ""
163
 
164
+ #: ../admin/class-purger.php:1038
 
165
  msgid "Purging yearly archive '%s'"
166
  msgstr ""
167
 
168
+ #: ../admin/class-purger.php:1044
169
  msgid "No yearly archives"
170
  msgstr ""
171
 
172
+ #: ../admin/class-purger.php:1056
173
  msgid "Let's purge everything!"
174
  msgstr ""
175
 
176
+ #: ../admin/class-purger.php:1062
177
+ msgid "Everything purged!"
178
  msgstr ""
179
 
180
+ #: ../admin/class-purger.php:1079
181
  msgid "Term taxonomy edited or deleted"
182
  msgstr ""
183
 
184
+ #: ../admin/class-purger.php:1086
185
+ msgid "Term taxonomy '%1$s' edited, (tt_id '%2$d', term_id '%3$d', taxonomy '%4$s')"
 
186
  msgstr ""
187
 
188
+ #: ../admin/class-purger.php:1090
189
+ msgid "A term taxonomy has been deleted from taxonomy '%1$s', (tt_id '%2$d', term_id '%3$d')"
 
 
 
190
  msgstr ""
191
 
192
+ #: ../admin/class-purger.php:1112
193
  msgid "Widget saved, moved or removed in a sidebar"
194
  msgstr ""
195
 
196
+ #: ../admin/partials/nginx-helper-admin-display.php:21
197
+ msgid "Nginx Settings"
198
  msgstr ""
199
 
200
+ #: ../admin/partials/nginx-helper-general-options.php:56
201
+ msgid "Log file size must be a number."
202
  msgstr ""
203
 
204
+ #: ../admin/partials/nginx-helper-general-options.php:66
205
+ msgid "Settings saved."
206
  msgstr ""
207
 
208
+ #: ../admin/partials/nginx-helper-general-options.php:93
209
+ msgid "Purging Options"
210
  msgstr ""
211
 
212
+ #: ../admin/partials/nginx-helper-general-options.php:100
213
+ msgid "Enable Purge"
 
214
  msgstr ""
215
 
216
+ #: ../admin/partials/nginx-helper-general-options.php:110
217
+ msgid "Caching Method"
218
  msgstr ""
219
 
220
+ #: ../admin/partials/nginx-helper-general-options.php:123
221
+ msgid "nginx Fastcgi cache"
222
  msgstr ""
223
 
224
+ #: ../admin/partials/nginx-helper-general-options.php:123
225
+ msgid "External settings for nginx"
226
  msgstr ""
227
 
228
+ #: ../admin/partials/nginx-helper-general-options.php:123
229
+ msgid "requires external settings for nginx"
230
  msgstr ""
231
 
232
+ #: ../admin/partials/nginx-helper-general-options.php:135
233
+ msgid "Redis cache"
234
  msgstr ""
235
 
236
+ #: ../admin/partials/nginx-helper-general-options.php:145
237
+ msgid "Purge Method"
 
 
 
238
  msgstr ""
239
 
240
+ #: ../admin/partials/nginx-helper-general-options.php:155, ../admin/partials/nginx-helper-general-options.php:337
241
+ msgid "when a post/page/custom post is published."
242
  msgstr ""
243
 
244
+ #: ../admin/partials/nginx-helper-general-options.php:165
245
+ msgid "Using a GET request to"
246
  msgstr ""
247
 
248
+ #: ../admin/partials/nginx-helper-general-options.php:165
249
+ msgid "(Default option)"
250
  msgstr ""
251
 
252
+ #: ../admin/partials/nginx-helper-general-options.php:176
253
+ msgid "Uses the"
254
+ msgstr ""
255
+
256
+ #: ../admin/partials/nginx-helper-general-options.php:176
257
+ msgid "module"
258
+ msgstr ""
259
+
260
+ #: ../admin/partials/nginx-helper-general-options.php:188
261
+ msgid "Delete local server cache files"
262
+ msgstr ""
263
+
264
+ #: ../admin/partials/nginx-helper-general-options.php:196
265
+ msgid "Checks for matching cache file in "
266
+ msgstr ""
267
+
268
+ #: ../admin/partials/nginx-helper-general-options.php:196
269
+ msgid "Does not require any other modules. Requires that the cache be stored on the same server as WordPress. You must also be using the default nginx cache options (levels=1:2) and (fastcgi_cache_key \"$scheme$request_method$host$request_uri\")."
270
+ msgstr ""
271
+
272
+ #: ../admin/partials/nginx-helper-general-options.php:212
273
+ msgid "Redis Settings"
274
+ msgstr ""
275
+
276
+ #: ../admin/partials/nginx-helper-general-options.php:217
277
+ msgid "Hostname"
278
+ msgstr ""
279
+
280
+ #: ../admin/partials/nginx-helper-general-options.php:224, ../admin/partials/nginx-helper-general-options.php:239, ../admin/partials/nginx-helper-general-options.php:254
281
+ msgid "Overridden by constant variables."
282
+ msgstr ""
283
+
284
+ #: ../admin/partials/nginx-helper-general-options.php:232
285
+ msgid "Port"
286
+ msgstr ""
287
+
288
+ #: ../admin/partials/nginx-helper-general-options.php:247
289
+ msgid "Prefix"
290
+ msgstr ""
291
+
292
+ #: ../admin/partials/nginx-helper-general-options.php:267
293
+ msgid "Purging Conditions"
294
  msgstr ""
295
 
296
+ #: ../admin/partials/nginx-helper-general-options.php:272
297
  msgid "Purge Homepage:"
298
  msgstr ""
299
 
300
+ #: ../admin/partials/nginx-helper-general-options.php:279
301
  msgid "when a post/page/custom post is modified or added."
302
  msgstr ""
303
 
304
+ #: ../admin/partials/nginx-helper-general-options.php:290, ../admin/partials/nginx-helper-general-options.php:314, ../admin/partials/nginx-helper-general-options.php:348, ../admin/partials/nginx-helper-general-options.php:372, ../admin/partials/nginx-helper-general-options.php:396, ../admin/partials/nginx-helper-general-options.php:432, ../admin/partials/nginx-helper-general-options.php:456, ../admin/partials/nginx-helper-general-options.php:481, ../admin/partials/nginx-helper-general-options.php:505
305
+ msgid "when a "
306
+ msgstr ""
307
+
308
+ #: ../admin/partials/nginx-helper-general-options.php:290, ../admin/partials/nginx-helper-general-options.php:348, ../admin/partials/nginx-helper-general-options.php:432
309
+ msgid "post"
310
+ msgstr ""
311
+
312
+ #: ../admin/partials/nginx-helper-general-options.php:290, ../admin/partials/nginx-helper-general-options.php:314, ../admin/partials/nginx-helper-general-options.php:432, ../admin/partials/nginx-helper-general-options.php:456
313
+ msgid " (or page/custom post) is "
314
+ msgstr ""
315
+
316
+ #: ../admin/partials/nginx-helper-general-options.php:290, ../admin/partials/nginx-helper-general-options.php:432
317
+ msgid "modified"
318
  msgstr ""
319
 
320
+ #: ../admin/partials/nginx-helper-general-options.php:290, ../admin/partials/nginx-helper-general-options.php:432
321
+ msgid " or "
322
+ msgstr ""
323
+
324
+ #: ../admin/partials/nginx-helper-general-options.php:290, ../admin/partials/nginx-helper-general-options.php:432
325
+ msgid "added"
326
+ msgstr ""
327
+
328
+ #: ../admin/partials/nginx-helper-general-options.php:303
329
  msgid "when an existing post/page/custom post is modified."
330
  msgstr ""
331
 
332
+ #: ../admin/partials/nginx-helper-general-options.php:314, ../admin/partials/nginx-helper-general-options.php:456
333
+ msgid "published post"
334
+ msgstr ""
335
+
336
+ #: ../admin/partials/nginx-helper-general-options.php:314, ../admin/partials/nginx-helper-general-options.php:456
337
+ msgid "trashed"
338
  msgstr ""
339
 
340
+ #: ../admin/partials/nginx-helper-general-options.php:329
341
  msgid "Purge Post/Page/Custom Post Type:"
342
  msgstr ""
343
 
344
+ #: ../admin/partials/nginx-helper-general-options.php:348, ../admin/partials/nginx-helper-general-options.php:372, ../admin/partials/nginx-helper-general-options.php:396, ../admin/partials/nginx-helper-general-options.php:481, ../admin/partials/nginx-helper-general-options.php:505
345
+ msgid " is "
346
  msgstr ""
347
 
348
+ #: ../admin/partials/nginx-helper-general-options.php:348
349
+ msgid "published"
350
  msgstr ""
351
 
352
+ #: ../admin/partials/nginx-helper-general-options.php:361, ../admin/partials/nginx-helper-general-options.php:470
353
  msgid "when a comment is approved/published."
354
  msgstr ""
355
 
356
+ #: ../admin/partials/nginx-helper-general-options.php:372, ../admin/partials/nginx-helper-general-options.php:396, ../admin/partials/nginx-helper-general-options.php:481, ../admin/partials/nginx-helper-general-options.php:505
357
+ msgid "comment"
358
+ msgstr ""
359
+
360
+ #: ../admin/partials/nginx-helper-general-options.php:372, ../admin/partials/nginx-helper-general-options.php:481
361
+ msgid "approved/published"
362
  msgstr ""
363
 
364
+ #: ../admin/partials/nginx-helper-general-options.php:385, ../admin/partials/nginx-helper-general-options.php:494
365
  msgid "when a comment is unapproved/deleted."
366
  msgstr ""
367
 
368
+ #: ../admin/partials/nginx-helper-general-options.php:396, ../admin/partials/nginx-helper-general-options.php:505
369
+ msgid "unapproved/deleted"
370
  msgstr ""
371
 
372
+ #: ../admin/partials/nginx-helper-general-options.php:411
373
  msgid "Purge Archives:"
374
  msgstr ""
375
 
376
+ #: ../admin/partials/nginx-helper-general-options.php:413
377
  msgid "(date, category, tag, author, custom taxonomies)"
378
  msgstr ""
379
 
380
+ #: ../admin/partials/nginx-helper-general-options.php:421
381
+ msgid "when an post/page/custom post is modified or added"
382
  msgstr ""
383
 
384
+ #: ../admin/partials/nginx-helper-general-options.php:445
385
+ msgid "when an existing post/page/custom post is trashed."
386
  msgstr ""
387
 
388
+ #: ../admin/partials/nginx-helper-general-options.php:519
389
+ msgid "Custom Purge URL:"
390
  msgstr ""
391
 
392
+ #: ../admin/partials/nginx-helper-general-options.php:537
393
+ msgid "Debug Options"
394
+ msgstr ""
395
+
396
+ #: ../admin/partials/nginx-helper-general-options.php:547
397
+ msgid "Enable Nginx Map."
398
+ msgstr ""
399
+
400
+ #: ../admin/partials/nginx-helper-general-options.php:556
401
+ msgid "Enable Logging"
402
+ msgstr ""
403
+
404
+ #: ../admin/partials/nginx-helper-general-options.php:564
405
+ msgid "Enable Nginx Timestamp in HTML"
406
  msgstr ""
407
 
408
+ #: ../admin/partials/nginx-helper-general-options.php:578
409
  msgid "Nginx Map"
410
  msgstr ""
411
 
412
+ #: ../admin/partials/nginx-helper-general-options.php:590
413
+ msgid "Can't write on map file."
 
 
 
414
  msgstr ""
415
 
416
+ #: ../admin/partials/nginx-helper-general-options.php:590, ../admin/partials/nginx-helper-general-options.php:664
417
+ msgid "Check you have write permission on "
 
418
  msgstr ""
419
 
420
+ #: ../admin/partials/nginx-helper-general-options.php:607
421
+ msgid "Nginx Map path to include in nginx settings"
422
+ msgstr ""
423
+
424
+ #: ../admin/partials/nginx-helper-general-options.php:607
425
+ msgid "(recommended)"
426
+ msgstr ""
427
+
428
+ #: ../admin/partials/nginx-helper-general-options.php:625
429
+ msgid "Or,"
430
  msgstr ""
431
 
432
+ #: ../admin/partials/nginx-helper-general-options.php:625
433
+ msgid "Text to manually copy and paste in nginx settings"
434
+ msgstr ""
435
+
436
+ #: ../admin/partials/nginx-helper-general-options.php:625
437
+ msgid "(if your network is small and new sites are not added frequently)"
438
+ msgstr ""
439
+
440
+ #: ../admin/partials/nginx-helper-general-options.php:645
441
  msgid "Logging Options"
442
  msgstr ""
443
 
444
+ #: ../admin/partials/nginx-helper-general-options.php:664
445
+ msgid "Can't write on log file."
 
 
 
446
  msgstr ""
447
 
448
+ #: ../admin/partials/nginx-helper-general-options.php:680
449
  msgid "Logs path"
450
  msgstr ""
451
 
452
+ #: ../admin/partials/nginx-helper-general-options.php:692
453
  msgid "View Log"
454
  msgstr ""
455
 
456
+ #: ../admin/partials/nginx-helper-general-options.php:697
457
  msgid "Log"
458
  msgstr ""
459
 
460
+ #: ../admin/partials/nginx-helper-general-options.php:704
461
  msgid "Log level"
462
  msgstr ""
463
 
464
+ #: ../admin/partials/nginx-helper-general-options.php:709
465
  msgid "None"
466
  msgstr ""
467
 
468
+ #: ../admin/partials/nginx-helper-general-options.php:710
469
  msgid "Info"
470
  msgstr ""
471
 
472
+ #: ../admin/partials/nginx-helper-general-options.php:711
473
  msgid "Warning"
474
  msgstr ""
475
 
476
+ #: ../admin/partials/nginx-helper-general-options.php:712
477
  msgid "Error"
478
  msgstr ""
479
 
480
+ #: ../admin/partials/nginx-helper-general-options.php:719
481
  msgid "Max log file size"
482
  msgstr ""
483
 
484
+ #: ../admin/partials/nginx-helper-general-options.php:725
485
  msgid "Mb"
486
  msgstr ""
487
 
488
+ #: ../admin/partials/nginx-helper-general-options.php:741
489
  msgid "Save All Changes"
490
  msgstr ""
491
 
492
+ #: ../admin/partials/nginx-helper-sidebar-display.php:26
493
+ msgid "Purge Entire Cache"
494
+ msgstr ""
495
+
496
+ #: ../admin/partials/nginx-helper-sidebar-display.php:31
497
  msgid "Need Help?"
498
  msgstr ""
499
 
500
+ #: ../admin/partials/nginx-helper-sidebar-display.php:39
501
+ msgid "Please use our"
502
+ msgstr ""
503
+
504
+ #: ../admin/partials/nginx-helper-sidebar-display.php:39
505
+ msgid "free support forum"
506
  msgstr ""
507
 
508
+ #: ../admin/partials/nginx-helper-sidebar-display.php:51
509
  msgid "Getting Social is Good"
510
  msgstr ""
511
 
512
+ #: ../admin/partials/nginx-helper-sidebar-display.php:55
513
  msgid "Become a fan on Facebook"
514
  msgstr ""
515
 
516
+ #: ../admin/partials/nginx-helper-sidebar-display.php:56
517
  msgid "Follow us on Twitter"
518
  msgstr ""
519
 
520
+ #: ../admin/partials/nginx-helper-sidebar-display.php:57
521
  msgid "Add to Circle"
522
  msgstr ""
523
 
524
+ #: ../admin/partials/nginx-helper-sidebar-display.php:58
525
  msgid "Subscribe to our feeds"
526
  msgstr ""
527
 
528
+ #: ../admin/partials/nginx-helper-sidebar-display.php:64
529
  msgid "Useful Links"
530
  msgstr ""
531
 
532
+ #: ../admin/partials/nginx-helper-sidebar-display.php:69, ../admin/partials/nginx-helper-sidebar-display.php:69
533
  msgid "WordPress-Nginx Solutions"
534
  msgstr ""
535
 
536
+ #: ../admin/partials/nginx-helper-sidebar-display.php:72, ../admin/partials/nginx-helper-sidebar-display.php:72
537
  msgid "WordPress Theme Devleopment"
538
  msgstr ""
539
 
540
+ #: ../admin/partials/nginx-helper-sidebar-display.php:75, ../admin/partials/nginx-helper-sidebar-display.php:75
541
  msgid "WordPress Plugin Development"
542
  msgstr ""
543
 
544
+ #: ../admin/partials/nginx-helper-sidebar-display.php:78, ../admin/partials/nginx-helper-sidebar-display.php:78
545
  msgid "WordPress Consultancy"
546
  msgstr ""
547
 
548
+ #: ../admin/partials/nginx-helper-sidebar-display.php:81, ../admin/partials/nginx-helper-sidebar-display.php:81
549
  msgid "easyengine (ee)"
550
  msgstr ""
551
 
552
+ #: ../admin/partials/nginx-helper-sidebar-display.php:88
553
  msgid "Click to toggle"
554
  msgstr ""
555
 
556
+ #: ../admin/partials/nginx-helper-sidebar-display.php:89
557
  msgid "Latest News"
558
  msgstr ""
559
 
560
+ #: ../admin/partials/nginx-helper-sidebar-display.php:90
561
  msgid "Loading..."
562
  msgstr ""
563
 
564
+ #: ../admin/partials/nginx-helper-support-options.php:18
565
  msgid "Support Forums"
566
  msgstr ""
567
 
568
+ #: ../admin/partials/nginx-helper-support-options.php:24
 
 
 
 
569
  msgid "Free Support"
570
  msgstr ""
571
 
572
+ #: ../admin/partials/nginx-helper-support-options.php:27
573
  msgid "Free Support Forum"
574
  msgstr ""
575
 
576
+ #: ../admin/partials/nginx-helper-support-options.php:28, ../admin/partials/nginx-helper-support-options.php:38
577
  msgid "Link to forum"
578
  msgstr ""
579
 
580
+ #: ../admin/partials/nginx-helper-support-options.php:34
581
  msgid "Premium Support"
582
  msgstr ""
583
 
584
+ #: ../admin/partials/nginx-helper-support-options.php:37
585
  msgid "Premium Support Forum"
586
  msgstr ""
587
+
588
+ #: ../includes/class-nginx-helper-activator.php:49
589
+ msgid "Sorry, you need to be an administrator to use Nginx Helper"
590
+ msgstr ""
591
+
592
+ #. translators: %s is Minimum WP version.
593
+ #: ../includes/class-nginx-helper.php:309
594
+ msgid "Sorry, Nginx Helper requires WordPress %s or higher"
595
+ msgstr ""
596
+
597
+ #: ../wp-cli.php:40
598
+ msgid "Purged Everything!"
599
+ msgstr ""
nginx-helper.php CHANGED
@@ -1,458 +1,97 @@
1
  <?php
2
  /**
3
- * Plugin Name: Nginx Helper
4
- * Plugin URI: https://rtcamp.com/nginx-helper/
5
- * Description: Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does a few more things.
6
- * Version: 1.9.12
7
- * Author: rtCamp
8
- * Author URI: https://rtcamp.com
9
- * Text Domain: nginx-helper
 
10
  * Requires at least: 3.0
11
  * Tested up to: 4.9.8
12
  *
13
- * @package nginx-helper
 
 
14
  */
15
 
16
- namespace rtCamp\WP\Nginx {
17
- define( 'rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH', plugin_dir_path( __FILE__ ) );
18
- define( 'rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_URL', plugin_dir_url( __FILE__ ) );
19
-
20
- class Helper {
21
-
22
- var $minium_WP = '3.0';
23
- var $options = null;
24
- var $plugin_name = 'nginx-helper';
25
-
26
- const WP_CLI_COMMAND = 'nginx-helper';
27
-
28
- function __construct()
29
- {
30
-
31
- if ( !$this->required_wp_version() )
32
- if ( !$this->required_php_version() )
33
- return;
34
-
35
- // Load Plugin Text Domain
36
- add_action( 'init', array( $this, 'load_plugin_textdomain' ) );
37
-
38
- $this->load_options();
39
- $this->plugin_name = plugin_basename( __FILE__ );
40
-
41
- register_activation_hook( $this->plugin_name, array( &$this, 'activate' ) );
42
- register_deactivation_hook( $this->plugin_name, array( &$this, 'deactivate' ) );
43
-
44
- add_action( 'init', array( &$this, 'start_helper' ), 15 );
45
- }
46
-
47
- function start_helper()
48
- {
49
-
50
- global $rt_wp_nginx_purger;
51
- add_action( 'shutdown', array( &$this, 'add_timestamps' ), 99999 );
52
- add_action( 'add_init', array( &$this, 'update_map' ) );
53
-
54
- //add_action( 'save_post', array( &$rt_wp_nginx_purger, 'purgePost' ), 200, 1 );
55
- // add_action( 'publish_post', array( &$rt_wp_nginx_purger, 'purgePost' ), 200, 1 );
56
- // add_action( 'publish_page', array( &$rt_wp_nginx_purger, 'purgePost' ), 200, 1 );
57
- add_action( 'wp_insert_comment', array( &$rt_wp_nginx_purger, 'purgePostOnComment' ), 200, 2 );
58
- add_action( 'transition_comment_status', array( &$rt_wp_nginx_purger, 'purgePostOnCommentChange' ), 200, 3 );
59
-
60
- // $args = array( '_builtin' => false );
61
- // $_rt_custom_post_types = get_post_types( $args );
62
- // if ( isset( $post_types ) && !empty( $post_types ) ) {
63
- // if ( $this->options['rt_wp_custom_post_types'] == true ) {
64
- // foreach ( $_rt_custom_post_types as $post_type ) {
65
- // add_action( 'publish_' . trim( $post_type ), array( &$rt_wp_nginx_purger, 'purgePost' ), 200, 1 );
66
- // }
67
- // }
68
- // }
69
-
70
- add_action( 'transition_post_status', array( &$this, 'set_future_post_option_on_future_status' ), 20, 3 );
71
- add_action( 'delete_post', array( &$this, 'unset_future_post_option_on_delete' ), 20, 1 );
72
- add_action( 'nm_check_log_file_size_daily', array( &$rt_wp_nginx_purger, 'checkAndTruncateLogFile' ), 100, 1 );
73
- add_action( 'edit_attachment', array( &$rt_wp_nginx_purger, 'purgeImageOnEdit' ), 100, 1 );
74
- add_action( 'wpmu_new_blog', array( &$this, 'update_new_blog_options' ), 10, 1 );
75
- add_action( 'transition_post_status', array( &$rt_wp_nginx_purger, 'purge_on_post_moved_to_trash' ), 20, 3 );
76
- add_action( 'edit_term', array( &$rt_wp_nginx_purger, 'purge_on_term_taxonomy_edited' ), 20, 3 );
77
- add_action( 'delete_term', array( &$rt_wp_nginx_purger, 'purge_on_term_taxonomy_edited' ), 20, 3 );
78
- add_action( 'check_ajax_referer', array( &$rt_wp_nginx_purger, 'purge_on_check_ajax_referer' ), 20, 2 );
79
- add_action( 'admin_init', array( &$this, 'purge_all' ) );
80
-
81
- // expose action to allow other plugins to purge the cache
82
- add_action( 'rt_nginx_helper_purge_all', array( &$this, 'true_purge_all' ) );
83
-
84
- // Load WP-CLI command
85
- if ( defined( 'WP_CLI' ) && WP_CLI ) {
86
- require_once RT_WP_NGINX_HELPER_PATH . 'wp-cli.php';
87
- \WP_CLI::add_command( self::WP_CLI_COMMAND, 'Nginx_Helper_WP_CLI_Command' );
88
- }
89
- }
90
-
91
- function activate()
92
- {
93
-
94
- $path = $this->functional_asset_path();
95
- if ( !is_dir( $path ) ) {
96
- mkdir( $path );
97
- }
98
- include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
99
- rt_wp_nginx_helper_install();
100
- }
101
-
102
- function deactivate()
103
- {
104
- include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
105
- rt_wp_nginx_helper_uninstall();
106
- }
107
-
108
- function required_wp_version()
109
- {
110
-
111
- global $wp_version;
112
- $wp_ok = version_compare( $wp_version, $this->minium_WP, '>=' );
113
- if ( ($wp_ok == FALSE ) ) {
114
- add_action( 'admin_notices', create_function( '', 'global $rt_wp_nginx_helper; printf (\'<div id="message" class="error"><p><strong>\' . __(\'Sorry, Nginx Helper requires WordPress %s or higher\', "nginx-helper" ) . \'</strong></p></div>\', $rt_wp_nginx_helper->minium_WP );' ) );
115
- add_action( 'network_admin_notices', create_function( '', 'global $rt_wp_nginx_helper; printf (\'<div id="message" class="error"><p><strong>\' . __(\'Sorry, Nginx Helper requires WordPress %s or higher\', "nginx-helper" ) . \'</strong></p></div>\', $rt_wp_nginx_helper->minium_WP );' ) );
116
- return false;
117
- }
118
-
119
- return true;
120
- }
121
-
122
- function load_options()
123
- {
124
- $this->options = get_site_option( 'rt_wp_nginx_helper_options' );
125
- }
126
-
127
- function set_future_post_option_on_future_status( $new_status, $old_status, $post )
128
- {
129
-
130
- global $blog_id, $rt_wp_nginx_purger;
131
- $purge_status = array( 'publish', 'future' );
132
-
133
- if ( !$this->options['enable_purge'] ) {
134
- return;
135
- }
136
-
137
- if( in_array( $old_status, $purge_status ) || in_array( $new_status, $purge_status ) ) {
138
- $rt_wp_nginx_purger->log( "Purge post on transition post STATUS from " . $old_status . " to " . $new_status );
139
- $rt_wp_nginx_purger->purgePost( $post->ID );
140
- }
141
-
142
- if ( $new_status == 'future' ) {
143
- if ( $post && $post->post_status == 'future' && ( ( $post->post_type == 'post' || $post->post_type == 'page' ) || ( isset( $this->options['custom_post_types_recognized'] ) && in_array( $post->post_type, $this->options['custom_post_types_recognized'] ) ) ) ) {
144
- $rt_wp_nginx_purger->log( "Set/update future_posts option (post id = " . $post->ID . " and blog id = " . $blog_id . ")" );
145
- $this->options['future_posts'][$blog_id][$post->ID] = strtotime( $post->post_date_gmt ) + 60;
146
- update_site_option( "rt_wp_nginx_helper_global_options", $this->options );
147
- }
148
- }
149
- }
150
-
151
- function unset_future_post_option_on_delete( $post_id )
152
- {
153
-
154
- global $blog_id, $rt_wp_nginx_purger;
155
- if ( !$this->options['enable_purge'] ) {
156
- return;
157
- }
158
- if ( $post_id && !wp_is_post_revision( $post_id ) ) {
159
-
160
- if ( isset( $this->options['future_posts'][$blog_id][$post_id] ) && count( $this->options['future_posts'][$blog_id][$post_id] ) ) {
161
- $rt_wp_nginx_purger->log( "Unset future_posts option (post id = " . $post_id . " and blog id = " . $blog_id . ")" );
162
- unset( $this->options['future_posts'][$blog_id][$post_id] );
163
- update_site_option( "rt_wp_nginx_helper_global_options", $this->options );
164
-
165
- if ( !count( $this->options['future_posts'][$blog_id] ) ) {
166
- unset( $this->options['future_posts'][$blog_id] );
167
- update_site_option( "rt_wp_nginx_helper_global_options", $this->options );
168
- }
169
- }
170
- }
171
- }
172
-
173
- function update_new_blog_options( $blog_id )
174
- {
175
- global $rt_wp_nginx_purger;
176
- include_once (RT_WP_NGINX_HELPER_PATH . 'admin/install.php');
177
- $rt_wp_nginx_purger->log( "New site added (id $blog_id)" );
178
- $this->update_map();
179
- $rt_wp_nginx_purger->log( "New site added to nginx map (id $blog_id)" );
180
- $helper_options = rt_wp_nginx_helper_get_options();
181
- update_blog_option( $blog_id, "rt_wp_nginx_helper_options", $helper_options );
182
- $rt_wp_nginx_purger->log( "Default options updated for the new blog (id $blog_id)" );
183
- }
184
-
185
- function get_map()
186
- {
187
- if ( !$this->options['enable_map'] ) {
188
- return;
189
- }
190
-
191
- if ( is_multisite() ) {
192
-
193
- global $wpdb;
194
-
195
- $rt_all_blogs = $wpdb->get_results( $wpdb->prepare( "SELECT blog_id, domain, path FROM " . $wpdb->blogs . " WHERE site_id = %d AND archived = '0' AND mature = '0' AND spam = '0' AND deleted = '0'", $wpdb->siteid ) );
196
- $wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
197
- $rt_domain_map_sites = '';
198
- if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->dmtable}'" ) == $wpdb->dmtable ) {
199
- $rt_domain_map_sites = $wpdb->get_results( "SELECT blog_id, domain FROM {$wpdb->dmtable} ORDER BY id DESC" );
200
- }
201
- $rt_nginx_map = "";
202
- $rt_nginx_map_array = array();
203
-
204
-
205
- if ( $rt_all_blogs )
206
- foreach ( $rt_all_blogs as $blog ) {
207
- if ( SUBDOMAIN_INSTALL == "yes" ) {
208
- $rt_nginx_map_array[$blog->domain] = $blog->blog_id;
209
- } else {
210
- if ( $blog->blog_id != 1 ) {
211
- $rt_nginx_map_array[$blog->path] = $blog->blog_id;
212
- }
213
- }
214
- }
215
-
216
- if ( $rt_domain_map_sites ) {
217
- foreach ( $rt_domain_map_sites as $site ) {
218
- $rt_nginx_map_array[$site->domain] = $site->blog_id;
219
- }
220
- }
221
-
222
- foreach ( $rt_nginx_map_array as $domain => $domain_id ) {
223
- $rt_nginx_map .= "\t" . $domain . "\t" . $domain_id . ";\n";
224
- }
225
-
226
- return $rt_nginx_map;
227
- }
228
- }
229
-
230
- function functional_asset_path()
231
- {
232
- $path = WP_CONTENT_DIR . '/uploads/nginx-helper/';
233
- return apply_filters( 'nginx_asset_path', $path );
234
- }
235
-
236
- function functional_asset_url()
237
- {
238
- $url = WP_CONTENT_URL . '/uploads/nginx-helper/';
239
- return apply_filters( 'nginx_asset_url', $url );
240
- }
241
-
242
- function update_map()
243
- {
244
- if ( is_multisite() ) {
245
- $rt_nginx_map = $this->get_map();
246
-
247
- if ( $fp = fopen( $this->functional_asset_path() . 'map.conf', 'w+' ) ) {
248
- fwrite( $fp, $rt_nginx_map );
249
- fclose( $fp );
250
- return true;
251
- }
252
- }
253
- }
254
-
255
- function add_timestamps()
256
- {
257
- if ( $this->options['enable_purge'] != 1 )
258
- return;
259
- if ( $this->options['enable_stamp'] != 1 )
260
- return;
261
- if ( is_admin() )
262
- return;
263
- foreach ( headers_list() as $header ) {
264
- list($key, $value) = explode( ':', $header, 2 );
265
- if ( $key == 'Content-Type' && strpos( trim( $value ), 'text/html' ) !== 0 ) {
266
- return;
267
- }
268
- if ( $key == 'Content-Type' )
269
- break;
270
- }
271
-
272
- if ( defined( 'DOING_AJAX' ) && DOING_AJAX )
273
- return;
274
- if ( defined( 'DOING_CRON' ) && DOING_CRON )
275
- return;
276
- if ( defined( 'WP_CLI' ) && WP_CLI )
277
- return;
278
- $timestamps = "\n<!--" .
279
- "Cached using Nginx-Helper on " . current_time( 'mysql' ) . ". " .
280
- "It took " . get_num_queries() . " queries executed in " . timer_stop() . " seconds." .
281
- "-->\n" .
282
- "<!--Visit http://wordpress.org/extend/plugins/nginx-helper/faq/ for more details-->";
283
- echo $timestamps;
284
- }
285
-
286
- function show_notice()
287
- {
288
- echo '<div class="updated"><p>' . __( 'Purge initiated', 'nginx-helper' ) . '</p></div>';
289
- }
290
-
291
- function purge_all()
292
- {
293
- if ( !isset( $_REQUEST['nginx_helper_action'] ) )
294
- return;
295
-
296
- if ( !current_user_can( 'manage_options' ) )
297
- wp_die( 'Sorry, you do not have the necessary privileges to edit these options.' );
298
-
299
- $action = $_REQUEST['nginx_helper_action'];
300
-
301
- if ( $action == 'done' ) {
302
- add_action( 'admin_notices', array( &$this, 'show_notice' ) );
303
- add_action( 'network_admin_notices', array( &$this, 'show_notice' ) );
304
- return;
305
- }
306
-
307
- check_admin_referer( 'nginx_helper-purge_all' );
308
-
309
- switch ( $action ) {
310
- case 'purge':
311
- $this->true_purge_all();
312
- break;
313
- }
314
- wp_redirect( esc_url_raw( add_query_arg( array( 'nginx_helper_action' => 'done' ) ) ) );
315
- }
316
-
317
- function true_purge_all()
318
- {
319
- global $rt_wp_nginx_purger;
320
- $rt_wp_nginx_purger->true_purge_all();
321
- }
322
-
323
- /**
324
- * Load the translation file for current language.
325
- */
326
- function load_plugin_textdomain()
327
- {
328
- load_plugin_textdomain( 'nginx-helper', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
329
- }
330
-
331
- }
332
-
333
  }
334
 
335
- namespace {
336
-
337
- if ( !defined( 'RT_WP_NGINX_HELPER_CACHE_PATH' ) ) {
338
- define( 'RT_WP_NGINX_HELPER_CACHE_PATH', '/var/run/nginx-cache' );
339
- }
340
- global $current_blog;
341
-
342
- if ( is_admin() ) {
343
- require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . '/admin/admin.php');
344
- $rtwpAdminPanel = new \rtCamp\WP\Nginx\Admin();
345
- }
346
-
347
- require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . 'purger.php');
348
- require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . 'redis-purger.php');
349
- require_once (rtCamp\WP\Nginx\RT_WP_NGINX_HELPER_PATH . 'compatibility.php');
350
-
351
- global $rt_wp_nginx_helper, $rt_wp_nginx_purger, $rt_wp_nginx_compatibility;
352
- $rt_wp_nginx_helper = new \rtCamp\WP\Nginx\Helper;
353
-
354
- if ( !empty( $rt_wp_nginx_helper->options['cache_method'] ) && $rt_wp_nginx_helper->options['cache_method'] == "enable_redis" ) {
355
- $rt_wp_nginx_purger = new \rtCamp\WP\Nginx\Redispurger;
356
- } else {
357
- $rt_wp_nginx_purger = new \rtCamp\WP\Nginx\Purger;
358
- }
359
- $rt_wp_nginx_compatibility = namespace\rtCamp\WP\Nginx\Compatibility::instance();
360
- if ( $rt_wp_nginx_compatibility->haveNginx() && !function_exists( 'wp_redirect' ) ) {
361
-
362
- function wp_redirect( $location, $status = 302 )
363
- {
364
- $location = apply_filters( 'wp_redirect', $location, $status );
365
-
366
- if ( empty( $location ) ) {
367
- return false;
368
- }
369
-
370
- $status = apply_filters( 'wp_redirect_status', $status, $location );
371
- if ( $status < 300 || $status > 399 ) {
372
- $status = 302;
373
- }
374
-
375
- if ( function_exists( 'wp_sanitize_redirect' ) ) {
376
- $location = wp_sanitize_redirect( $location );
377
- }
378
- header( 'Location: ' . $location, true, $status );
379
-
380
- return true;
381
- }
382
 
383
- }
 
 
 
 
 
384
 
385
- // Add settings link on plugin page
386
- function nginx_settings_link( $links )
387
- {
388
- if ( is_network_admin() ) {
389
- $u = 'settings.php';
390
- } else {
391
- $u = 'options-general.php';
392
- }
393
- $settings_link = '<a href="' . $u . '?page=nginx">' . __( 'Settings', 'nginx-helper' ) . '</a>';
394
- array_unshift( $links, $settings_link );
395
- return $links;
396
- }
397
 
398
- if ( is_multisite() ) {
399
- add_filter( "network_admin_plugin_action_links_" . plugin_basename( __FILE__ ), 'nginx_settings_link' );
400
- } else {
401
- add_filter( "plugin_action_links_" . plugin_basename( __FILE__ ), 'nginx_settings_link' );
402
- }
 
 
 
403
 
404
- /**
405
- * Get latest news
406
- *
407
- * @return void
408
- */
409
- function rt_nginx_get_news() {
 
 
410
 
411
- $is_rt_news_request = sanitize_text_field( filter_input( INPUT_GET, 'action' ) );
 
412
 
413
- if ( empty( $is_rt_news_request ) || 'rt_nginx_get_news' !== $is_rt_news_request ) {
414
- return;
415
- }
 
 
416
 
417
- require_once ABSPATH . WPINC . '/feed.php';
 
 
 
 
 
 
 
 
 
418
 
419
- $maxitems = 0;
420
 
421
- // Get a SimplePie feed object from the specified feed source.
422
- $rss = fetch_feed( 'https://rtcamp.com/blog/feed/' );
423
 
424
- // Checks that the object is created correctly.
425
- if ( ! is_wp_error( $rss ) ) {
426
 
427
- // Figure out how many total items there are, but limit it to 5.
428
- $maxitems = $rss->get_item_quantity( 5 );
429
 
430
- // Build an array of all the items, starting with element 0 (first element).
431
- $rss_items = $rss->get_items( 0, $maxitems );
432
- }
433
- ?>
434
- <ul role="list">
435
- <?php
436
- if ( 0 === $maxitems ) {
437
- echo '<li role="listitem">' . esc_html__( 'No items', 'nginx-helper' ) . '.</li>';
438
- } else {
439
- // Loop through each feed item and display each item as a hyperlink.
440
- foreach ( $rss_items as $item ) {
441
- ?>
442
- <li role="listitem">
443
- <a href='<?php echo esc_url( $item->get_permalink() ); ?>' title='<?php echo esc_attr__( 'Posted ', 'nginx-helper' ) . esc_attr( $item->get_date( 'j F Y | g:i a' ) ); ?>'>
444
- <?php echo esc_html( $item->get_title() ); ?>
445
- </a>
446
- </li>
447
- <?php
448
- }
449
- }
450
- ?>
451
- </ul>
452
- <?php
453
- die();
454
  }
455
 
456
- add_action( 'wp_ajax_rt_nginx_get_news', 'rt_nginx_get_news' );
457
-
458
  }
 
1
  <?php
2
  /**
3
+ * Plugin Name: Nginx Helper
4
+ * Plugin URI: https://rtcamp.com/nginx-helper/
5
+ * Description: Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does few more things.
6
+ * Version: 2.0.0
7
+ * Author: rtCamp
8
+ * Author URI: https://rtcamp.com
9
+ * Text Domain: nginx-helper
10
+ * Domain Path: /languages
11
  * Requires at least: 3.0
12
  * Tested up to: 4.9.8
13
  *
14
+ * @link https://rtcamp.com/nginx-helper/
15
+ * @since 2.0.0
16
+ * @package nginx-helper
17
  */
18
 
19
+ // If this file is called directly, abort.
20
+ if ( ! defined( 'WPINC' ) ) {
21
+ die;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
 
24
+ /**
25
+ * Base URL of plugin
26
+ */
27
+ if ( ! defined( 'NGINX_HELPER_BASEURL' ) ) {
28
+ define( 'NGINX_HELPER_BASEURL', plugin_dir_url( __FILE__ ) );
29
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ /**
32
+ * Base Name of plugin
33
+ */
34
+ if ( ! defined( 'NGINX_HELPER_BASENAME' ) ) {
35
+ define( 'NGINX_HELPER_BASENAME', plugin_basename( __FILE__ ) );
36
+ }
37
 
38
+ /**
39
+ * Base PATH of plugin
40
+ */
41
+ if ( ! defined( 'NGINX_HELPER_BASEPATH' ) ) {
42
+ define( 'NGINX_HELPER_BASEPATH', plugin_dir_path( __FILE__ ) );
43
+ }
 
 
 
 
 
 
44
 
45
+ /**
46
+ * The code that runs during plugin activation.
47
+ * This action is documented in includes/class-nginx-helper-activator.php
48
+ */
49
+ function activate_nginx_helper() {
50
+ require_once NGINX_HELPER_BASEPATH . 'includes/class-nginx-helper-activator.php';
51
+ Nginx_Helper_Activator::activate();
52
+ }
53
 
54
+ /**
55
+ * The code that runs during plugin deactivation.
56
+ * This action is documented in includes/class-nginx-helper-deactivator.php
57
+ */
58
+ function deactivate_nginx_helper() {
59
+ require_once NGINX_HELPER_BASEPATH . 'includes/class-nginx-helper-deactivator.php';
60
+ Nginx_Helper_Deactivator::deactivate();
61
+ }
62
 
63
+ register_activation_hook( __FILE__, 'activate_nginx_helper' );
64
+ register_deactivation_hook( __FILE__, 'deactivate_nginx_helper' );
65
 
66
+ /**
67
+ * The core plugin class that is used to define internationalization,
68
+ * admin-specific hooks, and public-facing site hooks.
69
+ */
70
+ require NGINX_HELPER_BASEPATH . 'includes/class-nginx-helper.php';
71
 
72
+ /**
73
+ * Begins execution of the plugin.
74
+ *
75
+ * Since everything within the plugin is registered via hooks,
76
+ * then kicking off the plugin from this point in the file does
77
+ * not affect the page life cycle.
78
+ *
79
+ * @since 2.0.0
80
+ */
81
+ function run_nginx_helper() {
82
 
83
+ global $nginx_helper;
84
 
85
+ $nginx_helper = new Nginx_Helper();
86
+ $nginx_helper->run();
87
 
88
+ // Load WP-CLI command.
89
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
90
 
91
+ require_once NGINX_HELPER_BASEPATH . 'wp-cli.php';
92
+ \WP_CLI::add_command( 'nginx-helper', 'Nginx_Helper_WP_CLI_Command' );
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
 
 
 
96
  }
97
+ run_nginx_helper();
phpcs.xml ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0"?>
2
+ <ruleset name="WordPress Coding Standards">
3
+
4
+ <!-- Set a description for this ruleset. -->
5
+ <description>A custom set of code standard rules to check for Nginx helper plugin.</description>
6
+
7
+ <!-- Pass some flags to PHPCS:
8
+ p flag: Show progress of the run.
9
+ s flag: Show sniff codes in all reports.
10
+ -->
11
+ <arg value="psvn"/>
12
+
13
+ <!-- Check up to 8 files simultanously. -->
14
+ <arg name="parallel" value="8"/>
15
+
16
+ <!-- Only check the PHP files. JS files are checked separately with JSCS and JSHint. -->
17
+ <arg name="extensions" value="php"/>
18
+
19
+ <!-- Check all files in this directory and the directories below it. -->
20
+ <file>.</file>
21
+
22
+ <!-- Exclude folders. -->
23
+ <exclude-pattern>*/languages/*</exclude-pattern>
24
+ <exclude-pattern>*/admin/predis.php</exclude-pattern> <!-- Predis library file -->
25
+
26
+ <!-- Include the WordPress standard. -->
27
+ <rule ref="WordPress-VIP-Go">
28
+ <exclude name="WordPress.VIP.FileSystemWritesDisallow.directory_mkdir" />
29
+ <exclude name="WordPress.WP.AlternativeFunctions.file_system_read_fopen" />
30
+ <exclude name="WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable" />
31
+ <exclude name="WordPress.WP.AlternativeFunctions.file_system_read_fclose" />
32
+ <exclude name="WordPress.VIP.FileSystemWritesDisallow.file_ops_fwrite" />
33
+ <exclude name="WordPress.VIP.FileSystemWritesDisallow.directory_rmdir" />
34
+ <exclude name="WordPress.WP.AlternativeFunctions.file_system_read_fwrite" />
35
+ <exclude name="WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink" />
36
+ </rule>
37
+ <rule ref="WordPress-Docs"/>
38
+
39
+ <!-- Verify that the text_domain is set to the desired text-domain.
40
+ Multiple valid text domains can be provided as a comma-delimited list. -->
41
+ <rule ref="WordPress.WP.I18n">
42
+ <properties>
43
+ <property name="text_domain" type="array" value="nginx-helper" />
44
+ </properties>
45
+ </rule>
46
+
47
+ <!-- Verify that no WP functions are used which are deprecated or have been removed.
48
+ The minimum version set here should be in line with the minimum WP version
49
+ as set in the "Requires at least" tag in the readme.txt file. -->
50
+ <rule ref="WordPress.WP.DeprecatedFunctions">
51
+ <properties>
52
+ <property name="minimum_supported_version" value="3.0" />
53
+ </properties>
54
+ </rule>
55
+
56
+ <!-- Include sniffs for PHP cross-version compatibility. -->
57
+ <rule ref="PHPCompatibility"/>
58
+ <config name="testVersion" value="5.3-99.0"/>
59
+
60
+ </ruleset>
readme.txt CHANGED
@@ -1,12 +1,12 @@
1
  === Nginx Helper ===
2
- Contributors: rtcamp, rahul286, saurabhshukla, manishsongirkar36, faishal, desaiuditd, darren-slatten, jk3us, daankortenbach, telofy, pjv, llonchj, jinnko, weskoop, bcole808, gungeekatx, rohanveer, chandrapatel, gagan0123, ravanh, michaelbeil, samedwards, niwreg, entr, nuvoPoint
3
  Donate Link: http://rtcamp.com/donate/
4
  Tags: nginx, cache, purge, nginx map, nginx cache, maps, fastcgi, proxy, redis, redis-cache, rewrite, permalinks
5
  License: GPLv2 or later (of-course)
6
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
7
  Requires at least: 3.0
8
  Tested up to: 4.9.8
9
- Stable tag: 1.9.12
10
 
11
  Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does a few more things.
12
 
@@ -127,6 +127,12 @@ Please post your problem in [our free support forum](http://community.rtcamp.com
127
 
128
  == Changelog ==
129
 
 
 
 
 
 
 
130
  = 1.9.12 =
131
  * Allow override Redis host/port/prefix by defining constant in wp-config.php [#152](https://github.com/rtCamp/nginx-helper/pull/152) - by [vincent-lu](https://github.com/vincent-lu)
132
 
1
  === Nginx Helper ===
2
+ Contributors: rtcamp, rahul286, saurabhshukla, manishsongirkar36, faishal, desaiuditd, darren-slatten, jk3us, daankortenbach, telofy, pjv, llonchj, jinnko, weskoop, bcole808, gungeekatx, rohanveer, chandrapatel, gagan0123, ravanh, michaelbeil, samedwards, niwreg, entr, nuvoPoint, iam404, rittesh.patel, vishalkakadiya, BhargavBhandari90, vincent-lu, murrayjbrown, bryant1410, 1gor, matt-h, pySilver, johan-chassaing, dotsam, sanketio, petenelson, nathanielks, rigagoogoo, dslatten, jinschoi, kelin1003, vaishuagola27, rahulsprajapati
3
  Donate Link: http://rtcamp.com/donate/
4
  Tags: nginx, cache, purge, nginx map, nginx cache, maps, fastcgi, proxy, redis, redis-cache, rewrite, permalinks
5
  License: GPLv2 or later (of-course)
6
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
7
  Requires at least: 3.0
8
  Tested up to: 4.9.8
9
+ Stable tag: 2.0.0
10
 
11
  Cleans nginx's fastcgi/proxy cache or redis-cache whenever a post is edited/published. Also does a few more things.
12
 
127
 
128
  == Changelog ==
129
 
130
+ = 2.0.0 =
131
+ * Fix typo causing failure to purge on trashed comment. [#159](https://github.com/rtCamp/nginx-helper/pull/159) - by [jinschoi](https://github.com/jinschoi)
132
+ * Refactor Plugin structure and remove unused code. Initial code by [chandrapatel](https://github.com/chandrapatel), [#153](https://github.com/rtCamp/nginx-helper/pull/153) - by [jinschoi](https://github.com/kelin1003),
133
+ * Run phpcs and fix warning. [#158](https://github.com/rtCamp/nginx-helper/pull/158)
134
+ * Make compatible with EasyEngine v4.
135
+
136
  = 1.9.12 =
137
  * Allow override Redis host/port/prefix by defining constant in wp-config.php [#152](https://github.com/rtCamp/nginx-helper/pull/152) - by [vincent-lu](https://github.com/vincent-lu)
138
 
uninstall.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Fired when the plugin is uninstalled.
4
+ *
5
+ * When populating this file, consider the following flow
6
+ * of control:
7
+ *
8
+ * - This method should be static
9
+ * - Check if the $_REQUEST content actually is the plugin name
10
+ * - Run an admin referrer check to make sure it goes through authentication
11
+ * - Verify the output of $_GET makes sense
12
+ * - Repeat with other user roles. Best directly by using the links/query string parameters.
13
+ * - Repeat things for multisite. Once for a single site in the network, once sitewide.
14
+ *
15
+ * This file may be updated more in future version of the Boilerplate; however, this is the
16
+ * general skeleton and outline for how the file should work.
17
+ *
18
+ * For more information, see the following discussion:
19
+ * https://github.com/tommcfarlin/WordPress-Plugin-Boilerplate/pull/123#issuecomment-28541913
20
+ *
21
+ * @link https://rtcamp.com/nginx-helper/
22
+ * @since 2.0.0
23
+ *
24
+ * @package nginx-helper
25
+ */
26
+
27
+ // If uninstall not called from WordPress, then exit.
28
+ if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
29
+ exit;
30
+ }
wp-cli.php CHANGED
@@ -1,9 +1,9 @@
1
  <?php
2
  /**
3
- * Created by PhpStorm.
4
- * User: udit
5
- * Date: 19/3/15
6
- * Time: 2:06 PM
7
  */
8
 
9
  /**
@@ -15,6 +15,9 @@ if ( ! defined( 'ABSPATH' ) ) {
15
 
16
  if ( ! class_exists( 'Nginx_Helper_WP_CLI_Command' ) ) {
17
 
 
 
 
18
  class Nginx_Helper_WP_CLI_Command extends WP_CLI_Command {
19
 
20
  /**
@@ -24,14 +27,21 @@ if ( ! class_exists( 'Nginx_Helper_WP_CLI_Command' ) ) {
24
  * wp nginx-helper purge-all
25
  *
26
  * @subcommand purge-all
 
 
 
27
  */
28
  public function purge_all( $args, $assoc_args ) {
29
- global $rt_wp_nginx_purger;
30
- $rt_wp_nginx_purger->true_purge_all();
31
- $message = __( 'Purged Everything!' );
 
 
 
32
  WP_CLI::success( $message );
 
33
  }
34
 
35
  }
36
 
37
- }
1
  <?php
2
  /**
3
+ * Contains class for WP-CLI command.
4
+ *
5
+ * @since 2.0.0
6
+ * @package nginx-helper
7
  */
8
 
9
  /**
15
 
16
  if ( ! class_exists( 'Nginx_Helper_WP_CLI_Command' ) ) {
17
 
18
+ /**
19
+ * Class Nginx_Helper_WP_CLI_Command
20
+ */
21
  class Nginx_Helper_WP_CLI_Command extends WP_CLI_Command {
22
 
23
  /**
27
  * wp nginx-helper purge-all
28
  *
29
  * @subcommand purge-all
30
+ *
31
+ * @param array $args Arguments.
32
+ * @param array $assoc_args Arguments in associative array.
33
  */
34
  public function purge_all( $args, $assoc_args ) {
35
+
36
+ global $nginx_purger;
37
+
38
+ $nginx_purger->purge_all();
39
+
40
+ $message = __( 'Purged Everything!', 'nginx-helper' );
41
  WP_CLI::success( $message );
42
+
43
  }
44
 
45
  }
46
 
47
+ }