Version Description
- Added error and app performance monitoring to help us fix bugs and improve performance.
- Requires php 5.4 or greater.
Download this release
Release Info
Developer | sovrn |
Plugin | WordPress Related Posts |
Version | 3.6.4 |
Comparing to | |
See all releases |
Code changes from version 3.6.3 to 3.6.4
- analytics.php +189 -0
- config.php +18 -2
- init.php +15 -5
- mixpanel/.gitignore +5 -0
- mixpanel/.travis.yml +19 -0
- mixpanel/LICENSE +190 -0
- mixpanel/README.md +110 -0
- mixpanel/composer.json +26 -0
- mixpanel/examples/aliasing.php +15 -0
- mixpanel/examples/all_calls.php +74 -0
- mixpanel/examples/consumers/ObConsumer.php +21 -0
- mixpanel/examples/curl_consumer.php +14 -0
- mixpanel/examples/custom_consumer.php +13 -0
- mixpanel/examples/error_handling.php +21 -0
- mixpanel/examples/profile_advanced.php +30 -0
- mixpanel/examples/profile_simple.php +12 -0
- mixpanel/examples/socket_consumer.php +11 -0
- mixpanel/examples/track_advanced.php +21 -0
- mixpanel/examples/track_simple.php +4 -0
- mixpanel/lib/Base/MixpanelBase.php +65 -0
- mixpanel/lib/ConsumerStrategies/AbstractConsumer.php +57 -0
- mixpanel/lib/ConsumerStrategies/CurlConsumer.php +221 -0
- mixpanel/lib/ConsumerStrategies/FileConsumer.php +38 -0
- mixpanel/lib/ConsumerStrategies/SocketConsumer.php +308 -0
- mixpanel/lib/Mixpanel.php +302 -0
- mixpanel/lib/Producers/MixpanelBaseProducer.php +229 -0
- mixpanel/lib/Producers/MixpanelEvents.php +164 -0
- mixpanel/lib/Producers/MixpanelPeople.php +147 -0
- mixpanel/phpunit.xml.dist +29 -0
- mixpanel/test/Base/MixpanelBaseProducerTest.php +82 -0
- mixpanel/test/ConsumerStrategies/AbstractConsumerTest.php +45 -0
- mixpanel/test/ConsumerStrategies/CurlConsumerTest.php +149 -0
- mixpanel/test/ConsumerStrategies/FileConsumerTest.php +30 -0
- mixpanel/test/ConsumerStrategies/SocketConsumerTest.php +32 -0
- mixpanel/test/MixpanelTest.php +29 -0
- mixpanel/test/Producers/MixpanelEventsProducerTest.php +86 -0
- mixpanel/test/Producers/MixpanelPeopleProducerTest.php +146 -0
- readme.txt +10 -2
- static/img/arrow_down.png +0 -0
- static/img/arrow_right.png +0 -0
- static/img/check.png +0 -0
- static/img/close.png +0 -0
- static/img/cross.png +0 -0
- static/img/cross_2x.png +0 -0
- static/img/desktop_icon.png +0 -0
- static/img/down.png +0 -0
- static/img/icon_support.png +0 -0
- static/img/measure_icon.png +0 -0
- static/img/measure_icon_2x.png +0 -0
- static/img/menu_icon.png +0 -0
- static/img/menu_icon_2x.png +0 -0
- static/img/mobile_icon.png +0 -0
- static/img/network_icon.png +0 -0
- static/img/outlink.png +0 -0
- static/img/promoted_arrow.png +0 -0
- static/img/turnonscreen.jpg +0 -0
- static/img/up.png +0 -0
- static/js/dashboard.js +124 -4
- static/js/edit_related_posts.js +740 -23
- static/js/extras.js +16 -1
- static/js/infiniterecs.js +104 -4
- static/js/pinterest.js +250 -7
- static/js/themes.js +156 -5
- static/thumbs/0.jpg +0 -0
- static/thumbs/1.jpg +0 -0
- static/thumbs/15.jpg +0 -0
- static/thumbs/16.jpg +0 -0
- static/thumbs/19.jpg +0 -0
- static/thumbs/2.jpg +0 -0
- static/thumbs/22.jpg +0 -0
- static/thumbs/23.jpg +0 -0
- static/thumbs/24.jpg +0 -0
- static/thumbs/29.jpg +0 -0
- static/thumbs/4.jpg +0 -0
- static/thumbs/5.jpg +0 -0
- static/thumbs/6.jpg +0 -0
- utils.php +50 -0
- wp_related_posts.php +1 -1
- zemanta/img/logo.png +0 -0
- zemanta/img/menu_icon.png +0 -0
- zemanta/zemanta.php +66 -65
analytics.php
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class wprp_analytics {
|
4 |
+
/**
|
5 |
+
* The utils instance of this class.
|
6 |
+
*
|
7 |
+
* @since 1.1.0
|
8 |
+
* @access protected
|
9 |
+
* @var sovrn\workbench\utils $utils The utils instance of this class.
|
10 |
+
*/
|
11 |
+
protected $utils;
|
12 |
+
|
13 |
+
|
14 |
+
/**
|
15 |
+
* The Mixpanel library instance of this class.
|
16 |
+
*
|
17 |
+
* @since 1.1.0
|
18 |
+
* @access private
|
19 |
+
* @var Mixpanel $mp The Mixpanel library instance of this class.
|
20 |
+
*/
|
21 |
+
protected $mp;
|
22 |
+
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Initialize the class and set its properties.
|
26 |
+
*
|
27 |
+
* @since 1.1.0
|
28 |
+
*/
|
29 |
+
public function __construct($mixpanel_token) {
|
30 |
+
// set utils instance to this class
|
31 |
+
$this->utils = new wprp_utils(False);
|
32 |
+
|
33 |
+
// get site as distinct id
|
34 |
+
$this->distinct_id = $this->utils->get_site();
|
35 |
+
|
36 |
+
// get client ip address
|
37 |
+
$this->ip_address = $this->utils->get_client_ip_address();
|
38 |
+
|
39 |
+
// set mixpanel options
|
40 |
+
$mixpanel_options = ['fork' => true, 'consumer' => 'socket', 'async' => false, 'connect_timeout' => 1];
|
41 |
+
if (getenv('SOVRN_ENV') == "QA") {
|
42 |
+
$mixpanel_options = ['fork' => true, 'consumer' => 'file', 'async' => false, 'connect_timeout' => 0, 'file' => '/tmp/mixpanel.txt'];
|
43 |
+
}
|
44 |
+
|
45 |
+
// set mixpanel library
|
46 |
+
$this->mp = Mixpanel::getInstance($mixpanel_token, $mixpanel_options);
|
47 |
+
|
48 |
+
// set mixpanel identity
|
49 |
+
$this->mp->identify($this->distinct_id);
|
50 |
+
|
51 |
+
// end function
|
52 |
+
return null;
|
53 |
+
}
|
54 |
+
|
55 |
+
|
56 |
+
public function get_label($label_id) {
|
57 |
+
// set list of labels
|
58 |
+
$labels = [
|
59 |
+
'name' => '$name',
|
60 |
+
'created' => 'Created',
|
61 |
+
'installed' => 'Installed',
|
62 |
+
'uninstalled' => 'Uninstalled',
|
63 |
+
'workbench_activated' => 'Activated - WB',
|
64 |
+
'workbench_active_install' => 'Active Install - WB',
|
65 |
+
'ednet_activated' => 'Activated - EA',
|
66 |
+
'ednet_active_install' => 'Active Install - EA',
|
67 |
+
'rpbs_activated' => 'Activated - RPBS',
|
68 |
+
'rpbs_active_install' => 'Active Install - RPBS',
|
69 |
+
'wprp_activated' => 'Activated - WPRP',
|
70 |
+
'wprp_active_install' => 'Active Install - WPRP',
|
71 |
+
'rp_activated' => 'Activated - RP',
|
72 |
+
'rp_active_install' => 'Active Install - RP',
|
73 |
+
'platform' => 'Platform',
|
74 |
+
'url' => 'URL',
|
75 |
+
'acknowledged_legal' => 'Acknowledged Legal',
|
76 |
+
'registered' => 'Registered',
|
77 |
+
'forgot_password' => 'Forgot Password',
|
78 |
+
'logged_in' => 'Logged In',
|
79 |
+
'logged_out' => 'Logged Out',
|
80 |
+
'email' => '$email',
|
81 |
+
'shared_post' => 'Shared Post',
|
82 |
+
'included_status' => 'Included Status',
|
83 |
+
'channels' => 'Channels',
|
84 |
+
|
85 |
+
'php_version' => 'PHP Version',
|
86 |
+
];
|
87 |
+
|
88 |
+
// get label
|
89 |
+
$label = $label_id && isset($labels[$label_id]) ? $labels[$label_id] : null;
|
90 |
+
|
91 |
+
// return label
|
92 |
+
return $label;
|
93 |
+
|
94 |
+
}
|
95 |
+
|
96 |
+
public function create_profile($props, $platform) {
|
97 |
+
// set defaults
|
98 |
+
$props['name'] = $this->distinct_id;
|
99 |
+
$props['created'] = date(DATE_ATOM);
|
100 |
+
$props['php_version'] = phpversion();
|
101 |
+
$props[$platform . '_activated'] = true;
|
102 |
+
|
103 |
+
// set profile properties with labels
|
104 |
+
$props_with_labels = [];
|
105 |
+
|
106 |
+
// iterate on props
|
107 |
+
foreach ($props as $label_id => $value) {
|
108 |
+
// get label by label id
|
109 |
+
$label = $this->get_label($label_id);
|
110 |
+
// check if have label
|
111 |
+
if ($label) {
|
112 |
+
// add to event label and value to props_with_labels
|
113 |
+
$props_with_labels[$label] = $value;
|
114 |
+
}
|
115 |
+
}
|
116 |
+
|
117 |
+
// set mixpanel profile properties
|
118 |
+
$this->mp->people->setOnce($this->distinct_id, $props_with_labels, $this->ip_address);
|
119 |
+
|
120 |
+
// end function
|
121 |
+
return $props_with_labels;
|
122 |
+
}
|
123 |
+
|
124 |
+
public function update_profile_property($label_id, $value) {
|
125 |
+
|
126 |
+
// get label by label id
|
127 |
+
$label = $this->get_label($label_id);
|
128 |
+
|
129 |
+
// set property array
|
130 |
+
$prop = [$label => $value];
|
131 |
+
|
132 |
+
// update mixpanel profile property
|
133 |
+
$this->mp->people->set($this->distinct_id, $prop, $this->ip_address);
|
134 |
+
|
135 |
+
// end function
|
136 |
+
return null;
|
137 |
+
}
|
138 |
+
|
139 |
+
|
140 |
+
public function track($label_id, $props=[]) {
|
141 |
+
|
142 |
+
// get event label by event id
|
143 |
+
$label = $this->get_label($label_id);
|
144 |
+
|
145 |
+
// set profile properties with labels
|
146 |
+
$props_with_labels = [];
|
147 |
+
|
148 |
+
// iterate on props
|
149 |
+
foreach ($props as $prop_label_id => $value) {
|
150 |
+
|
151 |
+
// get label by label id
|
152 |
+
$prop_label = $this->get_label($prop_label_id);
|
153 |
+
|
154 |
+
// check if have label
|
155 |
+
if ($prop_label) {
|
156 |
+
|
157 |
+
// add to event label and value to props_with_labels
|
158 |
+
$props_with_labels[$prop_label] = $value;
|
159 |
+
|
160 |
+
}
|
161 |
+
|
162 |
+
}
|
163 |
+
|
164 |
+
// track mixpanel event
|
165 |
+
$this->mp->track($label, $props_with_labels);
|
166 |
+
|
167 |
+
|
168 |
+
// end function
|
169 |
+
return null;
|
170 |
+
|
171 |
+
}
|
172 |
+
|
173 |
+
/**
|
174 |
+
* active_install
|
175 |
+
*
|
176 |
+
* Is this an active install?
|
177 |
+
* Only check once per day
|
178 |
+
*
|
179 |
+
* WordPress ONLY method
|
180 |
+
*/
|
181 |
+
public function active_install($platform) {
|
182 |
+
$last_sent = get_option($platform . "_last_sent", 0);
|
183 |
+
if (date('Ymd') != date('Ymd', strtotime($last_sent))) {
|
184 |
+
// track "active install"
|
185 |
+
$this->track($platform . "_active_install");
|
186 |
+
update_option($platform . "_last_sent", date('Ymd', time()));
|
187 |
+
}
|
188 |
+
}
|
189 |
+
}
|
config.php
CHANGED
@@ -124,11 +124,20 @@ function wp_rp_set_global_notice() {
|
|
124 |
|
125 |
function wp_rp_activate_hook() {
|
126 |
wp_rp_get_options();
|
127 |
-
|
|
|
|
|
|
|
|
|
|
|
128 |
}
|
129 |
|
130 |
function wp_rp_deactivate_hook() {
|
131 |
-
|
|
|
|
|
|
|
|
|
132 |
}
|
133 |
|
134 |
function wp_rp_upgrade() {
|
@@ -256,6 +265,13 @@ function wp_rp_is_classic() {
|
|
256 |
return false;
|
257 |
}
|
258 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
259 |
function wp_rp_migrate_3_6_2() {
|
260 |
$meta = get_option('wp_rp_meta');
|
261 |
$meta['version'] = '3.6.3';
|
124 |
|
125 |
function wp_rp_activate_hook() {
|
126 |
wp_rp_get_options();
|
127 |
+
|
128 |
+
// setup mixpanel
|
129 |
+
global $wprp_mp;
|
130 |
+
$wprp_mp->create_profile([], "wprp");
|
131 |
+
$wprp_mp->update_profile_property("wprp_activated", true);
|
132 |
+
$wprp_mp->track("wprp_activated");
|
133 |
}
|
134 |
|
135 |
function wp_rp_deactivate_hook() {
|
136 |
+
// setup mixpanel
|
137 |
+
global $wprp_mp;
|
138 |
+
$wprp_mp->create_profile([], "wprp");
|
139 |
+
$wprp_mp->update_profile_property("wprp_activated", false);
|
140 |
+
$wprp_mp->track("wprp_activated");
|
141 |
}
|
142 |
|
143 |
function wp_rp_upgrade() {
|
265 |
return false;
|
266 |
}
|
267 |
|
268 |
+
function wp_rp_migrate_3_6_3() {
|
269 |
+
$meta = get_option('wp_rp_meta');
|
270 |
+
$meta['version'] = '3.6.4';
|
271 |
+
$meta['new_user'] = false;
|
272 |
+
update_option('wp_rp_meta', $meta);
|
273 |
+
}
|
274 |
+
|
275 |
function wp_rp_migrate_3_6_2() {
|
276 |
$meta = get_option('wp_rp_meta');
|
277 |
$meta['version'] = '3.6.3';
|
init.php
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
<?php
|
2 |
-
define('WP_RP_VERSION', '3.6.
|
3 |
|
4 |
define('WP_RP_PLUGIN_FILE', plugin_basename(__FILE__));
|
5 |
|
@@ -15,13 +15,23 @@ include_once(dirname(__FILE__) . '/recommendations.php');
|
|
15 |
include_once(dirname(__FILE__) . '/edit_related_posts.php');
|
16 |
include_once(dirname(__FILE__) . '/compatibility.php');
|
17 |
|
18 |
-
register_activation_hook(__FILE__, 'wp_rp_activate_hook');
|
19 |
-
register_deactivation_hook(__FILE__, 'wp_rp_deactivate_hook');
|
20 |
|
21 |
-
|
|
|
22 |
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
function wp_rp_init_zemanta() {
|
27 |
include_once(dirname(__FILE__) . '/zemanta/zemanta.php');
|
1 |
<?php
|
2 |
+
define('WP_RP_VERSION', '3.6.4');
|
3 |
|
4 |
define('WP_RP_PLUGIN_FILE', plugin_basename(__FILE__));
|
5 |
|
15 |
include_once(dirname(__FILE__) . '/edit_related_posts.php');
|
16 |
include_once(dirname(__FILE__) . '/compatibility.php');
|
17 |
|
|
|
|
|
18 |
|
19 |
+
require_once(__DIR__ . "/analytics.php");
|
20 |
+
require_once(__DIR__ . "/utils.php");
|
21 |
|
22 |
+
if(!class_exists("Mixpanel")) {
|
23 |
+
require_once(__DIR__ . "/mixpanel/lib/Mixpanel.php");
|
24 |
+
}
|
25 |
+
|
26 |
+
// setup mixpanel globally for RP
|
27 |
+
global $wprp_mp;
|
28 |
+
$wprp_mp = new wprp_analytics("ef0f7a11e41c9b6da92490c75b45e141");
|
29 |
|
30 |
+
register_activation_hook(__DIR__ . "/wp_related_posts.php", 'wp_rp_activate_hook');
|
31 |
+
register_deactivation_hook(__DIR__ . "/wp_related_posts.php", 'wp_rp_deactivate_hook');
|
32 |
+
|
33 |
+
add_action('wp_head', 'wp_rp_head_resources');
|
34 |
+
add_action('plugins_loaded', 'wp_rp_init_zemanta');
|
35 |
|
36 |
function wp_rp_init_zemanta() {
|
37 |
include_once(dirname(__FILE__) . '/zemanta/zemanta.php');
|
mixpanel/.gitignore
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*~
|
2 |
+
*.log
|
3 |
+
*.lock
|
4 |
+
*.idea
|
5 |
+
vendor
|
mixpanel/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
language: php
|
2 |
+
sudo: false
|
3 |
+
|
4 |
+
php:
|
5 |
+
- 5.3
|
6 |
+
- 5.4
|
7 |
+
- 5.5
|
8 |
+
- 5.6
|
9 |
+
- 7.0
|
10 |
+
- 7.1
|
11 |
+
- hhvm
|
12 |
+
|
13 |
+
install:
|
14 |
+
- travis_retry composer self-update
|
15 |
+
- travis_retry composer install
|
16 |
+
- phpenv rehash
|
17 |
+
|
18 |
+
script:
|
19 |
+
- ./vendor/bin/phpunit --coverage-text
|
mixpanel/LICENSE
ADDED
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Copyright 2012 Mixpanel, Inc.
|
2 |
+
|
3 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
you may not use this work except in compliance with the License.
|
5 |
+
You may obtain a copy of the License below, or at:
|
6 |
+
|
7 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
8 |
+
|
9 |
+
Unless required by applicable law or agreed to in writing, software
|
10 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
+
See the License for the specific language governing permissions and
|
13 |
+
limitations under the License.
|
14 |
+
|
15 |
+
Apache License
|
16 |
+
Version 2.0, January 2004
|
17 |
+
http://www.apache.org/licenses/
|
18 |
+
|
19 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
20 |
+
|
21 |
+
1. Definitions.
|
22 |
+
|
23 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
24 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
25 |
+
|
26 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
27 |
+
the copyright owner that is granting the License.
|
28 |
+
|
29 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
30 |
+
other entities that control, are controlled by, or are under common
|
31 |
+
control with that entity. For the purposes of this definition,
|
32 |
+
"control" means (i) the power, direct or indirect, to cause the
|
33 |
+
direction or management of such entity, whether by contract or
|
34 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
35 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
36 |
+
|
37 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
38 |
+
exercising permissions granted by this License.
|
39 |
+
|
40 |
+
"Source" form shall mean the preferred form for making modifications,
|
41 |
+
including but not limited to software source code, documentation
|
42 |
+
source, and configuration files.
|
43 |
+
|
44 |
+
"Object" form shall mean any form resulting from mechanical
|
45 |
+
transformation or translation of a Source form, including but
|
46 |
+
not limited to compiled object code, generated documentation,
|
47 |
+
and conversions to other media types.
|
48 |
+
|
49 |
+
"Work" shall mean the work of authorship, whether in Source or
|
50 |
+
Object form, made available under the License, as indicated by a
|
51 |
+
copyright notice that is included in or attached to the work
|
52 |
+
(an example is provided in the Appendix below).
|
53 |
+
|
54 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
55 |
+
form, that is based on (or derived from) the Work and for which the
|
56 |
+
editorial revisions, annotations, elaborations, or other modifications
|
57 |
+
represent, as a whole, an original work of authorship. For the purposes
|
58 |
+
of this License, Derivative Works shall not include works that remain
|
59 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
60 |
+
the Work and Derivative Works thereof.
|
61 |
+
|
62 |
+
"Contribution" shall mean any work of authorship, including
|
63 |
+
the original version of the Work and any modifications or additions
|
64 |
+
to that Work or Derivative Works thereof, that is intentionally
|
65 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
66 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
67 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
68 |
+
means any form of electronic, verbal, or written communication sent
|
69 |
+
to the Licensor or its representatives, including but not limited to
|
70 |
+
communication on electronic mailing lists, source code control systems,
|
71 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
72 |
+
Licensor for the purpose of discussing and improving the Work, but
|
73 |
+
excluding communication that is conspicuously marked or otherwise
|
74 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
75 |
+
|
76 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
77 |
+
on behalf of whom a Contribution has been received by Licensor and
|
78 |
+
subsequently incorporated within the Work.
|
79 |
+
|
80 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
81 |
+
this License, each Contributor hereby grants to You a perpetual,
|
82 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
83 |
+
copyright license to reproduce, prepare Derivative Works of,
|
84 |
+
publicly display, publicly perform, sublicense, and distribute the
|
85 |
+
Work and such Derivative Works in Source or Object form.
|
86 |
+
|
87 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
88 |
+
this License, each Contributor hereby grants to You a perpetual,
|
89 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
90 |
+
(except as stated in this section) patent license to make, have made,
|
91 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
92 |
+
where such license applies only to those patent claims licensable
|
93 |
+
by such Contributor that are necessarily infringed by their
|
94 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
95 |
+
with the Work to which such Contribution(s) was submitted. If You
|
96 |
+
institute patent litigation against any entity (including a
|
97 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
98 |
+
or a Contribution incorporated within the Work constitutes direct
|
99 |
+
or contributory patent infringement, then any patent licenses
|
100 |
+
granted to You under this License for that Work shall terminate
|
101 |
+
as of the date such litigation is filed.
|
102 |
+
|
103 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
104 |
+
Work or Derivative Works thereof in any medium, with or without
|
105 |
+
modifications, and in Source or Object form, provided that You
|
106 |
+
meet the following conditions:
|
107 |
+
|
108 |
+
(a) You must give any other recipients of the Work or
|
109 |
+
Derivative Works a copy of this License; and
|
110 |
+
|
111 |
+
(b) You must cause any modified files to carry prominent notices
|
112 |
+
stating that You changed the files; and
|
113 |
+
|
114 |
+
(c) You must retain, in the Source form of any Derivative Works
|
115 |
+
that You distribute, all copyright, patent, trademark, and
|
116 |
+
attribution notices from the Source form of the Work,
|
117 |
+
excluding those notices that do not pertain to any part of
|
118 |
+
the Derivative Works; and
|
119 |
+
|
120 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
121 |
+
distribution, then any Derivative Works that You distribute must
|
122 |
+
include a readable copy of the attribution notices contained
|
123 |
+
within such NOTICE file, excluding those notices that do not
|
124 |
+
pertain to any part of the Derivative Works, in at least one
|
125 |
+
of the following places: within a NOTICE text file distributed
|
126 |
+
as part of the Derivative Works; within the Source form or
|
127 |
+
documentation, if provided along with the Derivative Works; or,
|
128 |
+
within a display generated by the Derivative Works, if and
|
129 |
+
wherever such third-party notices normally appear. The contents
|
130 |
+
of the NOTICE file are for informational purposes only and
|
131 |
+
do not modify the License. You may add Your own attribution
|
132 |
+
notices within Derivative Works that You distribute, alongside
|
133 |
+
or as an addendum to the NOTICE text from the Work, provided
|
134 |
+
that such additional attribution notices cannot be construed
|
135 |
+
as modifying the License.
|
136 |
+
|
137 |
+
You may add Your own copyright statement to Your modifications and
|
138 |
+
may provide additional or different license terms and conditions
|
139 |
+
for use, reproduction, or distribution of Your modifications, or
|
140 |
+
for any such Derivative Works as a whole, provided Your use,
|
141 |
+
reproduction, and distribution of the Work otherwise complies with
|
142 |
+
the conditions stated in this License.
|
143 |
+
|
144 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
145 |
+
any Contribution intentionally submitted for inclusion in the Work
|
146 |
+
by You to the Licensor shall be under the terms and conditions of
|
147 |
+
this License, without any additional terms or conditions.
|
148 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
149 |
+
the terms of any separate license agreement you may have executed
|
150 |
+
with Licensor regarding such Contributions.
|
151 |
+
|
152 |
+
6. Trademarks. This License does not grant permission to use the trade
|
153 |
+
names, trademarks, service marks, or product names of the Licensor,
|
154 |
+
except as required for reasonable and customary use in describing the
|
155 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
156 |
+
|
157 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
158 |
+
agreed to in writing, Licensor provides the Work (and each
|
159 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
160 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
161 |
+
implied, including, without limitation, any warranties or conditions
|
162 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
163 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
164 |
+
appropriateness of using or redistributing the Work and assume any
|
165 |
+
risks associated with Your exercise of permissions under this License.
|
166 |
+
|
167 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
168 |
+
whether in tort (including negligence), contract, or otherwise,
|
169 |
+
unless required by applicable law (such as deliberate and grossly
|
170 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
171 |
+
liable to You for damages, including any direct, indirect, special,
|
172 |
+
incidental, or consequential damages of any character arising as a
|
173 |
+
result of this License or out of the use or inability to use the
|
174 |
+
Work (including but not limited to damages for loss of goodwill,
|
175 |
+
work stoppage, computer failure or malfunction, or any and all
|
176 |
+
other commercial damages or losses), even if such Contributor
|
177 |
+
has been advised of the possibility of such damages.
|
178 |
+
|
179 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
180 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
181 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
182 |
+
or other liability obligations and/or rights consistent with this
|
183 |
+
License. However, in accepting such obligations, You may act only
|
184 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
185 |
+
of any other Contributor, and only if You agree to indemnify,
|
186 |
+
defend, and hold each Contributor harmless for any liability
|
187 |
+
incurred by, or claims asserted against, such Contributor by reason
|
188 |
+
of your accepting any such warranty or additional liability.
|
189 |
+
|
190 |
+
END OF TERMS AND CONDITIONS
|
mixpanel/README.md
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Mixpanel PHP Library [![Build Status](https://travis-ci.org/mixpanel/mixpanel-php.svg)](https://travis-ci.org/mixpanel/mixpanel-php)
|
2 |
+
============
|
3 |
+
This library provides an API to track events and update profiles on Mixpanel.
|
4 |
+
|
5 |
+
Install with Composer
|
6 |
+
------------
|
7 |
+
Add mixpanel/mixpanel-php as a dependency and run composer update
|
8 |
+
|
9 |
+
```json
|
10 |
+
"require": {
|
11 |
+
...
|
12 |
+
"mixpanel/mixpanel-php" : "2.*"
|
13 |
+
...
|
14 |
+
}
|
15 |
+
```
|
16 |
+
|
17 |
+
Now you can start tracking events and people:
|
18 |
+
|
19 |
+
```php
|
20 |
+
<?php
|
21 |
+
// import dependencies
|
22 |
+
require 'vendor/autoload.php';
|
23 |
+
|
24 |
+
// get the Mixpanel class instance, replace with your project token
|
25 |
+
$mp = Mixpanel::getInstance("MIXPANEL_PROJECT_TOKEN");
|
26 |
+
|
27 |
+
// track an event
|
28 |
+
$mp->track("button clicked", array("label" => "sign-up"));
|
29 |
+
|
30 |
+
// create/update a profile for user id 12345
|
31 |
+
$mp->people->set(12345, array(
|
32 |
+
'$first_name' => "John",
|
33 |
+
'$last_name' => "Doe",
|
34 |
+
'$email' => "john.doe@example.com",
|
35 |
+
'$phone' => "5555555555",
|
36 |
+
"Favorite Color" => "red"
|
37 |
+
));
|
38 |
+
```
|
39 |
+
|
40 |
+
|
41 |
+
Install Manually
|
42 |
+
------------
|
43 |
+
1. <a href="https://github.com/mixpanel/mixpanel-php/archive/master.zip">Download the Mixpanel PHP Library</a>
|
44 |
+
2. Extract the zip file to a directory called "mixpanel-php" in your project root
|
45 |
+
3. Now you can start tracking events and people:
|
46 |
+
|
47 |
+
```php
|
48 |
+
<?php
|
49 |
+
// import Mixpanel
|
50 |
+
require 'mixpanel-php/lib/Mixpanel.php';
|
51 |
+
|
52 |
+
// get the Mixpanel class instance, replace with your project token
|
53 |
+
$mp = Mixpanel::getInstance("MIXPANEL_PROJECT_TOKEN");
|
54 |
+
|
55 |
+
// track an event
|
56 |
+
$mp->track("button clicked", array("label" => "sign-up"));
|
57 |
+
|
58 |
+
// create/update a profile for user id 12345
|
59 |
+
$mp->people->set(12345, array(
|
60 |
+
'$first_name' => "John",
|
61 |
+
'$last_name' => "Doe",
|
62 |
+
'$email' => "john.doe@example.com",
|
63 |
+
'$phone' => "5555555555",
|
64 |
+
"Favorite Color" => "red"
|
65 |
+
));
|
66 |
+
```
|
67 |
+
|
68 |
+
Production Notes
|
69 |
+
-------------
|
70 |
+
By default, data is sent using ssl over cURL. This works fine when you're tracking a small number of events or aren't concerned with the potentially blocking nature of the PHP cURL calls. However, this isn't very efficient when you're sending hundreds of events (such as in batch processing). Our library comes packaged with an easy way to use a persistent socket connection for much more efficient writes. To enable the persistent socket, simply pass `'consumer' => 'socket'` as an entry in the `$options` array when you instantiate the Mixpanel class. Additionally, you can contribute your own persistence implementation by creating a custom Consumer.
|
71 |
+
|
72 |
+
Documentation
|
73 |
+
-------------
|
74 |
+
* <a href="https://mixpanel.com/help/reference/php" target="_blank">Reference Docs</a>
|
75 |
+
* <a href="http://mixpanel.github.io/mixpanel-php" target="_blank">Full API Reference</a>
|
76 |
+
|
77 |
+
For further examples and options checkout out the "examples" folder
|
78 |
+
|
79 |
+
Changelog
|
80 |
+
-------------
|
81 |
+
Version 2.6.2:
|
82 |
+
* Added support for $ignore_time
|
83 |
+
* Cleaned up some comments to be more clear
|
84 |
+
|
85 |
+
Version 2.6.1:
|
86 |
+
* Fixed bug in SocketConsumer timeout
|
87 |
+
|
88 |
+
Version 2.6:
|
89 |
+
* Updated default for `connect_timeout` in SocketConsumer to be 5
|
90 |
+
|
91 |
+
Version 2.5:
|
92 |
+
* `timeout` option now refers to `CURLOPT_TIMEOUT` instead of `CURLOPT_CONNECTTIMEOUT` in non-forked cURL calls, it has been removed from the SocketConsumer in favor of a new `connect_timeout` option.
|
93 |
+
* Added a new `connect_timeout` option for CURLOPT_CONNECTTIMEOUT in non-forked cURL calls (CurlConsumer) and the socket timeout (SocketConsumer)
|
94 |
+
* Set default timeout (CURLOPT_TIMEOUT) to 30 seconds in non-forked cURL calls
|
95 |
+
* Set default connection timeoute (CURLOPT_CONNECTTIMEOUT) to 5 seconds in non-forked cURL calls
|
96 |
+
* We now pass cURL errors from non-forked cURL calls to `_handle_error` with the curl errno and message
|
97 |
+
|
98 |
+
|
99 |
+
Version 2.4:
|
100 |
+
* Fixed a bug where passing the integer 0 for the `ip` parameter would be ignored
|
101 |
+
|
102 |
+
Version 2.1 - 2.3:
|
103 |
+
* Broken releases
|
104 |
+
|
105 |
+
Version 2.0:
|
106 |
+
* Changed the default consumer to be 'curl' (CurlConsumer)
|
107 |
+
* Changed the default setting of 'fork' to false in the Curl Consumer. This means that by default, events and profile updates are sent synchronously using the PHP cURL lib when using the Curl Consumer.
|
108 |
+
* 'createAlias' uses the CurlConsumer with 'fork' explicitly set to false (as we need this to be synchronous) instead of the SocketConsumer.
|
109 |
+
* Fixed bug where max_queue_size was never read
|
110 |
+
|
mixpanel/composer.json
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "mixpanel/mixpanel-php",
|
3 |
+
"description": "The Official PHP library for Mixpanel",
|
4 |
+
"keywords": [
|
5 |
+
"mixpanel",
|
6 |
+
"mixpanel php"
|
7 |
+
],
|
8 |
+
"homepage": "https://mixpanel.com/help/reference/php",
|
9 |
+
"license": "MIT",
|
10 |
+
"authors": [
|
11 |
+
{
|
12 |
+
"name": "Mixpanel <dev@mixpanel.com>",
|
13 |
+
"homepage": "https://mixpanel.com/"
|
14 |
+
}
|
15 |
+
],
|
16 |
+
"require": {
|
17 |
+
"php": ">=5.0"
|
18 |
+
},
|
19 |
+
"require-dev": {
|
20 |
+
"phpunit/phpunit": "4.7.*",
|
21 |
+
"phpdocumentor/phpdocumentor": "2.*"
|
22 |
+
},
|
23 |
+
"autoload": {
|
24 |
+
"files": ["lib/Mixpanel.php"]
|
25 |
+
}
|
26 |
+
}
|
mixpanel/examples/aliasing.php
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
// import the Mixpanel class
|
3 |
+
require_once("../lib/Mixpanel.php");
|
4 |
+
|
5 |
+
// instantiate the Mixpanel class
|
6 |
+
$mp = Mixpanel::getInstance("MIXPANEL_PROJECT_TOKEN");
|
7 |
+
|
8 |
+
// create an alias for user id 12345 (note that this is a synchronous call)
|
9 |
+
$mp->createAlias(12345, "john.doe@example.com");
|
10 |
+
|
11 |
+
// update the record previously identified by 12345 using the new alias
|
12 |
+
$mp->people->set("john.doe@example.com", array(
|
13 |
+
'$last_name' => "Doe-Aliased",
|
14 |
+
'aliased' => "indeed"
|
15 |
+
));
|
mixpanel/examples/all_calls.php
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once("/path/to/vendor/mixpanel/mixpanel-php/lib/Mixpanel.php"); // import the Mixpanel class
|
3 |
+
|
4 |
+
$mp = new Mixpanel("MIXPANEL_PROJECT_TOKEN", array(
|
5 |
+
"debug" => true,
|
6 |
+
));
|
7 |
+
|
8 |
+
// this would likely come from a database or session variable
|
9 |
+
$user_id = 12345;
|
10 |
+
|
11 |
+
// associate user 12345 to all subsequent track calls
|
12 |
+
$mp->identify($user_id);
|
13 |
+
|
14 |
+
// send property "color" = "red" with all subsequent track calls
|
15 |
+
$mp->register("color", "red");
|
16 |
+
|
17 |
+
// send property "number" = 1 with all subsequent track calls, don't overwrite an existing value
|
18 |
+
$mp->registerOnce("number", 1);
|
19 |
+
$mp->registerOnce("number", 2); // this will do nothing
|
20 |
+
|
21 |
+
// send all of these properties with all subsequent track calls, overwriting previously set values
|
22 |
+
$mp->registerAll(array("color" => "green", "prop2" => "val2")); // color is now green instead of red
|
23 |
+
|
24 |
+
// send all of these properties with all subsequent track calls, NOT overwriting previously set values
|
25 |
+
$mp->registerAllOnce(array("color" => "blue", "prop3" => "val3")); // color is still green but prop3 is now set to val3
|
26 |
+
|
27 |
+
// track a custom "button click" event
|
28 |
+
$mp->track("button click", array("label" => "Login"));
|
29 |
+
|
30 |
+
// track a custom "logged in" event
|
31 |
+
$mp->track("logged in", array("landing page" => "/specials"));
|
32 |
+
|
33 |
+
// create/update a profile identified by id 12345 with $first_name set to John and $email set to john.doe@example.com
|
34 |
+
// now we can send them Notifications!
|
35 |
+
$mp->people->set($user_id, array(
|
36 |
+
'$first_name' => "John",
|
37 |
+
'$email' => "john.doe@example.com"
|
38 |
+
));
|
39 |
+
|
40 |
+
// update John's profile with property ad_source to be "google" but don't override ad_source if it exists already
|
41 |
+
$mp->people->setOnce($user_id, array("ad_source" => "google"));
|
42 |
+
|
43 |
+
// increment John's total logins by one
|
44 |
+
$mp->people->increment($user_id, "login count", 1);
|
45 |
+
|
46 |
+
// append a new favorite to John's favorites
|
47 |
+
$mp->people->append($user_id, "favorites", "Apples");
|
48 |
+
|
49 |
+
// append a few more favorites to John's favorites
|
50 |
+
$mp->people->append($user_id, "favorites", array("Baseball", "Reading"));
|
51 |
+
|
52 |
+
// track a purchase or charge of $9.99 for user 12345 where the transaction happened just now
|
53 |
+
$mp->people->trackCharge($user_id, "9.99");
|
54 |
+
|
55 |
+
// track a purchase or charge of $20 for user 12345 where the transaction happened on June 01, 2013 at 5pm EST
|
56 |
+
$mp->people->trackCharge($user_id, "20.00", strtotime("01 Jun 2013 5:00:00 PM EST"));
|
57 |
+
|
58 |
+
// clear all purchases for user 12345
|
59 |
+
$mp->people->clearCharges($user_id);
|
60 |
+
|
61 |
+
// delete the profile for user 12345
|
62 |
+
$mp->people->deleteUser($user_id);
|
63 |
+
|
64 |
+
// create an alias for user 12345 (note that this is done synchronously)
|
65 |
+
$mp->createAlias($user_id, "johndoe1");
|
66 |
+
|
67 |
+
// track an even using the alias
|
68 |
+
$mp->track("logout", array("distinct_id" => "johndoe1"));
|
69 |
+
|
70 |
+
// manually put messages on the queue (useful for batch processing)
|
71 |
+
$mp->enqueueAll(array(
|
72 |
+
array("event" => "test"),
|
73 |
+
array("event" => "test2")
|
74 |
+
));
|
mixpanel/examples/consumers/ObConsumer.php
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once(dirname(__FILE__) . "/../../lib/ConsumerStrategies/AbstractConsumer.php");
|
3 |
+
|
4 |
+
class ObConsumer extends ConsumerStrategies_AbstractConsumer {
|
5 |
+
public function persist($batch) {
|
6 |
+
|
7 |
+
if (isset($batch[0]['event']) && $batch[0]['event'] == "force_error") {
|
8 |
+
$this->_handleError(0, "This is the data from a fake error");
|
9 |
+
return false;
|
10 |
+
} else {
|
11 |
+
echo "<pre>";
|
12 |
+
echo "printing batch:\n";
|
13 |
+
echo "---------------\n";
|
14 |
+
echo json_encode($batch) . "\n";
|
15 |
+
echo "---------------\n\n";
|
16 |
+
echo "</pre>";
|
17 |
+
|
18 |
+
return true;
|
19 |
+
}
|
20 |
+
}
|
21 |
+
}
|
mixpanel/examples/curl_consumer.php
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once("../lib/Mixpanel.php"); // import the Mixpanel class
|
3 |
+
|
4 |
+
// Make calls using the PHP cURL extension not using SSL
|
5 |
+
// Warning: This will block until the requests are complete.
|
6 |
+
$mp = new Mixpanel("MIXPANEL_PROJECT_TOKEN", array(
|
7 |
+
"debug" => true,
|
8 |
+
"consumer" => "curl",
|
9 |
+
"fork" => false,
|
10 |
+
"use_ssl" => false
|
11 |
+
));
|
12 |
+
|
13 |
+
$mp->track("test_event", array("color" => "blue"));
|
14 |
+
$mp->track("test_event", array("color" => "red"));
|
mixpanel/examples/custom_consumer.php
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once("../lib/Mixpanel.php"); // import the Mixpanel class
|
3 |
+
require_once("consumers/ObConsumer.php"); // import the custom consumer
|
4 |
+
|
5 |
+
$mp = new Mixpanel("MIXPANEL_PROJECT_TOKEN", array(
|
6 |
+
"debug" => true,
|
7 |
+
"max_batch_size" => 1,
|
8 |
+
"consumers" => array("ob" => "ObConsumer"),
|
9 |
+
"consumer" => "ob"
|
10 |
+
));
|
11 |
+
|
12 |
+
$mp->track("test_event", array("color" => "blue"));
|
13 |
+
$mp->track("test_event", array("color" => "red"));
|
mixpanel/examples/error_handling.php
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once("../lib/Mixpanel.php"); // import the Mixpanel class
|
3 |
+
require_once("consumers/ObConsumer.php"); // import the Mixpanel class
|
4 |
+
|
5 |
+
// define a callback function to handle errors made in a consumer
|
6 |
+
function handleError($code, $data) {
|
7 |
+
echo "This is my customer error handler. I've received an error! code = " . $code . " : data = " . $data . "<br />";
|
8 |
+
}
|
9 |
+
|
10 |
+
// instantiate Mixpanel with some different options including a custom consumer and a custom error callback
|
11 |
+
$mp = new Mixpanel("MIXPANEL_PROJECT_TOKEN", array(
|
12 |
+
"debug" => true,
|
13 |
+
"max_batch_size" => 1,
|
14 |
+
"consumers" => array("ob" => "ObConsumer"),
|
15 |
+
"consumer" => "ob",
|
16 |
+
"error_callback" => "handleError" // register the error callback
|
17 |
+
));
|
18 |
+
|
19 |
+
$mp->track("test_event", array("color" => "blue"));
|
20 |
+
$mp->track("test_event", array("color" => "red"));
|
21 |
+
$mp->track("force_error"); // a magical event we've defined as an error in our custom "ObConsumer"
|
mixpanel/examples/profile_advanced.php
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
// import the Mixpanel class
|
3 |
+
require_once("../lib/Mixpanel.php");
|
4 |
+
|
5 |
+
// instantiate the Mixpanel class
|
6 |
+
$mp = Mixpanel::getInstance("MIXPANEL_PROJECT_TOKEN");
|
7 |
+
|
8 |
+
// create or update a profile with First Name, Last Name, E-Mail Address, Phone Number, and Favorite Color
|
9 |
+
$mp->people->set(12345, array(
|
10 |
+
'$first_name' => "John",
|
11 |
+
'$last_name' => "Doe",
|
12 |
+
'$email' => "john.doe@example.com",
|
13 |
+
'$phone' => "5555555555",
|
14 |
+
"Favorite Color" => "red"
|
15 |
+
));
|
16 |
+
|
17 |
+
// increment the count of login attempts for user 12345
|
18 |
+
$mp->people->increment(12345, "Login Attempts", 1);
|
19 |
+
|
20 |
+
// add "Home Page" to a list of "Page Views" for user 12345
|
21 |
+
$mp->people->append(12345, "Page Views", "Home Page");
|
22 |
+
|
23 |
+
// add Cats, Pizza, and Baseball to a list of "Favorites" for user 12345
|
24 |
+
$mp->people->append(12345, "Favorites", array("Cats", "Pizza", "Baseball"));
|
25 |
+
|
26 |
+
// track a purchase or charge of $9.99 for user 12345 where the transaction happened just now
|
27 |
+
$mp->people->trackCharge(12345, "9.99");
|
28 |
+
|
29 |
+
// track a purchase or charge of $20 for user 12345 where the transaction happened on June 01, 2013 at 5pm EST
|
30 |
+
$mp->people->trackCharge(12345, "20.00", strtotime("01 Jun 2013 5:00:00 PM EST"));
|
mixpanel/examples/profile_simple.php
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once("../lib/Mixpanel.php"); // import the Mixpanel class
|
3 |
+
$mp = Mixpanel::getInstance("MIXPANEL_PROJECT_TOKEN"); // instantiate the Mixpanel class
|
4 |
+
|
5 |
+
// create or update a profile with First Name, Last Name, E-Mail Address, Phone Number, and Favorite Color
|
6 |
+
$mp->people->set(12345, array(
|
7 |
+
'$first_name' => "John",
|
8 |
+
'$last_name' => "Doe",
|
9 |
+
'$email' => "john.doe@example.com",
|
10 |
+
'$phone' => "5555555555",
|
11 |
+
"Favorite Color" => "red"
|
12 |
+
));
|
mixpanel/examples/socket_consumer.php
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once("../lib/Mixpanel.php"); // import the Mixpanel class
|
3 |
+
|
4 |
+
$mp = new Mixpanel("MIXPANEL_PROJECT_TOKEN", array(
|
5 |
+
"debug" => true,
|
6 |
+
"consumer" => "socket",
|
7 |
+
"use_ssl" => false
|
8 |
+
));
|
9 |
+
|
10 |
+
$mp->track("test_event", array("color" => "blue"));
|
11 |
+
$mp->track("test_event", array("color" => "red"));
|
mixpanel/examples/track_advanced.php
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
// import the Mixpanel class
|
3 |
+
require_once("../lib/Mixpanel.php");
|
4 |
+
|
5 |
+
// instantiate the Mixpanel class
|
6 |
+
$mp = Mixpanel::getInstance("MIXPANEL_PROJECT_TOKEN");
|
7 |
+
|
8 |
+
// associate a user id to subsequent events
|
9 |
+
$mp->identify(12345);
|
10 |
+
|
11 |
+
// track a "Login Success" event with a property "Ad Source" having value "Google"
|
12 |
+
$mp->track("Login Success", array("Ad Source" => "Google"));
|
13 |
+
|
14 |
+
// track an "Item Viewed" event with a property "Item" having value "Cool New Shoes"
|
15 |
+
$mp->track("Item Viewed", array("Item" => "Cool New Shoes"));
|
16 |
+
|
17 |
+
// stop associating events to user 12345
|
18 |
+
$mp->unregister("distinct_id");
|
19 |
+
|
20 |
+
// event "Some Event" won't be associated with user 12345
|
21 |
+
$mp->track("Some Event");
|
mixpanel/examples/track_simple.php
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once("../lib/Mixpanel.php"); // import the Mixpanel class
|
3 |
+
$mp = Mixpanel::getInstance("MIXPANEL_PROJECT_TOKEN"); // instantiate the Mixpanel class
|
4 |
+
$mp->track("login_clicked"); // track an event
|
mixpanel/lib/Base/MixpanelBase.php
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
/**
|
4 |
+
* This a Base class which all Mixpanel classes extend from to provide some very basic
|
5 |
+
* debugging and logging functionality. It also serves to persist $_options across the library.
|
6 |
+
*
|
7 |
+
*/
|
8 |
+
class Base_MixpanelBase {
|
9 |
+
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Default options that can be overridden via the $options constructor arg
|
13 |
+
* @var array
|
14 |
+
*/
|
15 |
+
private $_defaults = array(
|
16 |
+
"max_batch_size" => 50, // the max batch size Mixpanel will accept is 50,
|
17 |
+
"max_queue_size" => 1000, // the max num of items to hold in memory before flushing
|
18 |
+
"debug" => false, // enable/disable debug mode
|
19 |
+
"consumer" => "curl", // which consumer to use
|
20 |
+
"host" => "api.mixpanel.com", // the host name for api calls
|
21 |
+
"events_endpoint" => "/track", // host relative endpoint for events
|
22 |
+
"people_endpoint" => "/engage", // host relative endpoint for people updates
|
23 |
+
"use_ssl" => true, // use ssl when available
|
24 |
+
"error_callback" => null // callback to use on consumption failures
|
25 |
+
);
|
26 |
+
|
27 |
+
|
28 |
+
/**
|
29 |
+
* An array of options to be used by the Mixpanel library.
|
30 |
+
* @var array
|
31 |
+
*/
|
32 |
+
protected $_options = array();
|
33 |
+
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Construct a new MixpanelBase object and merge custom options with defaults
|
37 |
+
* @param array $options
|
38 |
+
*/
|
39 |
+
public function __construct($options = array()) {
|
40 |
+
$options = array_merge($this->_defaults, $options);
|
41 |
+
$this->_options = $options;
|
42 |
+
}
|
43 |
+
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Log a message to PHP's error log
|
47 |
+
* @param $msg
|
48 |
+
*/
|
49 |
+
protected function _log($msg) {
|
50 |
+
$arr = debug_backtrace();
|
51 |
+
$class = $arr[0]['class'];
|
52 |
+
$line = $arr[0]['line'];
|
53 |
+
error_log ( "[ $class - line $line ] : " . $msg );
|
54 |
+
}
|
55 |
+
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Returns true if in debug mode, false if in production mode
|
59 |
+
* @return bool
|
60 |
+
*/
|
61 |
+
protected function _debug() {
|
62 |
+
return array_key_exists("debug", $this->_options) && $this->_options["debug"] == true;
|
63 |
+
}
|
64 |
+
|
65 |
+
}
|
mixpanel/lib/ConsumerStrategies/AbstractConsumer.php
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once(dirname(__FILE__) . "/../Base/MixpanelBase.php");
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Provides some base methods for use by a Consumer implementation
|
6 |
+
*/
|
7 |
+
abstract class ConsumerStrategies_AbstractConsumer extends Base_MixpanelBase {
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Creates a new AbstractConsumer
|
11 |
+
* @param array $options
|
12 |
+
*/
|
13 |
+
function __construct($options = array()) {
|
14 |
+
|
15 |
+
parent::__construct($options);
|
16 |
+
|
17 |
+
if ($this->_debug()) {
|
18 |
+
$this->_log("Instantiated new Consumer");
|
19 |
+
}
|
20 |
+
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Encode an array to be persisted
|
25 |
+
* @param array $params
|
26 |
+
* @return string
|
27 |
+
*/
|
28 |
+
protected function _encode($params) {
|
29 |
+
return base64_encode(json_encode($params));
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Handles errors that occur in a consumer
|
34 |
+
* @param $code
|
35 |
+
* @param $msg
|
36 |
+
*/
|
37 |
+
protected function _handleError($code, $msg) {
|
38 |
+
if (isset($this->_options['error_callback'])) {
|
39 |
+
$handler = $this->_options['error_callback'];
|
40 |
+
call_user_func($handler, $code, $msg);
|
41 |
+
}
|
42 |
+
|
43 |
+
if ($this->_debug()) {
|
44 |
+
$arr = debug_backtrace();
|
45 |
+
$class = get_class($arr[0]['object']);
|
46 |
+
$line = $arr[0]['line'];
|
47 |
+
error_log ( "[ $class - line $line ] : " . print_r($msg, true) );
|
48 |
+
}
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Persist a batch of messages in whatever way the implementer sees fit
|
53 |
+
* @param array $batch an array of messages to consume
|
54 |
+
* @return boolean success or fail
|
55 |
+
*/
|
56 |
+
abstract function persist($batch);
|
57 |
+
}
|
mixpanel/lib/ConsumerStrategies/CurlConsumer.php
ADDED
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once(dirname(__FILE__) . "/AbstractConsumer.php");
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Consumes messages and sends them to a host/endpoint using cURL
|
6 |
+
*/
|
7 |
+
class ConsumerStrategies_CurlConsumer extends ConsumerStrategies_AbstractConsumer {
|
8 |
+
|
9 |
+
/**
|
10 |
+
* @var string the host to connect to (e.g. api.mixpanel.com)
|
11 |
+
*/
|
12 |
+
protected $_host;
|
13 |
+
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @var string the host-relative endpoint to write to (e.g. /engage)
|
17 |
+
*/
|
18 |
+
protected $_endpoint;
|
19 |
+
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @var int connect_timeout The number of seconds to wait while trying to connect. Default is 5 seconds.
|
23 |
+
*/
|
24 |
+
protected $_connect_timeout;
|
25 |
+
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @var int timeout The maximum number of seconds to allow cURL call to execute. Default is 30 seconds.
|
29 |
+
*/
|
30 |
+
protected $_timeout;
|
31 |
+
|
32 |
+
|
33 |
+
/**
|
34 |
+
* @var string the protocol to use for the cURL connection
|
35 |
+
*/
|
36 |
+
protected $_protocol;
|
37 |
+
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @var bool|null true to fork the cURL process (using exec) or false to use PHP's cURL extension. false by default
|
41 |
+
*/
|
42 |
+
protected $_fork = null;
|
43 |
+
|
44 |
+
|
45 |
+
/**
|
46 |
+
* Creates a new CurlConsumer and assigns properties from the $options array
|
47 |
+
* @param array $options
|
48 |
+
* @throws Exception
|
49 |
+
*/
|
50 |
+
function __construct($options) {
|
51 |
+
parent::__construct($options);
|
52 |
+
|
53 |
+
$this->_host = $options['host'];
|
54 |
+
$this->_endpoint = $options['endpoint'];
|
55 |
+
$this->_connect_timeout = array_key_exists('connect_timeout', $options) ? $options['connect_timeout'] : 5;
|
56 |
+
$this->_timeout = array_key_exists('timeout', $options) ? $options['timeout'] : 30;
|
57 |
+
$this->_protocol = array_key_exists('use_ssl', $options) && $options['use_ssl'] == true ? "https" : "http";
|
58 |
+
$this->_fork = array_key_exists('fork', $options) ? ($options['fork'] == true) : false;
|
59 |
+
|
60 |
+
// ensure the environment is workable for the given settings
|
61 |
+
if ($this->_fork == true) {
|
62 |
+
$exists = function_exists('exec');
|
63 |
+
if (!$exists) {
|
64 |
+
throw new Exception('The "exec" function must exist to use the cURL consumer in "fork" mode. Try setting fork = false or use another consumer.');
|
65 |
+
}
|
66 |
+
$disabled = explode(', ', ini_get('disable_functions'));
|
67 |
+
$enabled = !in_array('exec', $disabled);
|
68 |
+
if (!$enabled) {
|
69 |
+
throw new Exception('The "exec" function must be enabled to use the cURL consumer in "fork" mode. Try setting fork = false or use another consumer.');
|
70 |
+
}
|
71 |
+
} else {
|
72 |
+
if (!function_exists('curl_init')) {
|
73 |
+
throw new Exception('The cURL PHP extension is required to use the cURL consumer with fork = false. Try setting fork = true or use another consumer.');
|
74 |
+
}
|
75 |
+
}
|
76 |
+
}
|
77 |
+
|
78 |
+
|
79 |
+
/**
|
80 |
+
* Write to the given host/endpoint using either a forked cURL process or using PHP's cURL extension
|
81 |
+
* @param array $batch
|
82 |
+
* @return bool
|
83 |
+
*/
|
84 |
+
public function persist($batch) {
|
85 |
+
if (count($batch) > 0) {
|
86 |
+
$data = "data=" . $this->_encode($batch);
|
87 |
+
$url = $this->_protocol . "://" . $this->_host . $this->_endpoint;
|
88 |
+
if ($this->_fork) {
|
89 |
+
return $this->_execute_forked($url, $data);
|
90 |
+
} else {
|
91 |
+
return $this->_execute($url, $data);
|
92 |
+
}
|
93 |
+
} else {
|
94 |
+
return true;
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
|
99 |
+
/**
|
100 |
+
* Write using the cURL php extension
|
101 |
+
* @param $url
|
102 |
+
* @param $data
|
103 |
+
* @return bool
|
104 |
+
*/
|
105 |
+
protected function _execute($url, $data) {
|
106 |
+
if ($this->_debug()) {
|
107 |
+
$this->_log("Making blocking cURL call to $url");
|
108 |
+
}
|
109 |
+
|
110 |
+
$ch = curl_init();
|
111 |
+
curl_setopt($ch, CURLOPT_URL, $url);
|
112 |
+
curl_setopt($ch, CURLOPT_HEADER, 0);
|
113 |
+
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->_connect_timeout);
|
114 |
+
curl_setopt($ch, CURLOPT_TIMEOUT, $this->_timeout);
|
115 |
+
curl_setopt($ch, CURLOPT_POST, 1);
|
116 |
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
117 |
+
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
|
118 |
+
$response = curl_exec($ch);
|
119 |
+
if (false === $response) {
|
120 |
+
$curl_error = curl_error($ch);
|
121 |
+
$curl_errno = curl_errno($ch);
|
122 |
+
curl_close($ch);
|
123 |
+
$this->_handleError($curl_errno, $curl_error);
|
124 |
+
return false;
|
125 |
+
} else {
|
126 |
+
curl_close($ch);
|
127 |
+
if (trim($response) == "1") {
|
128 |
+
return true;
|
129 |
+
} else {
|
130 |
+
$this->_handleError(0, $response);
|
131 |
+
return false;
|
132 |
+
}
|
133 |
+
}
|
134 |
+
}
|
135 |
+
|
136 |
+
|
137 |
+
/**
|
138 |
+
* Write using a forked cURL process
|
139 |
+
* @param $url
|
140 |
+
* @param $data
|
141 |
+
* @return bool
|
142 |
+
*/
|
143 |
+
protected function _execute_forked($url, $data) {
|
144 |
+
|
145 |
+
if ($this->_debug()) {
|
146 |
+
$this->_log("Making forked cURL call to $url");
|
147 |
+
}
|
148 |
+
|
149 |
+
$exec = 'curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d ' . $data . ' "' . $url . '"';
|
150 |
+
|
151 |
+
if(!$this->_debug()) {
|
152 |
+
$exec .= " >/dev/null 2>&1 &";
|
153 |
+
}
|
154 |
+
|
155 |
+
exec($exec, $output, $return_var);
|
156 |
+
|
157 |
+
if ($return_var != 0) {
|
158 |
+
$this->_handleError($return_var, $output);
|
159 |
+
}
|
160 |
+
|
161 |
+
return $return_var == 0;
|
162 |
+
}
|
163 |
+
|
164 |
+
/**
|
165 |
+
* @return int
|
166 |
+
*/
|
167 |
+
public function getConnectTimeout()
|
168 |
+
{
|
169 |
+
return $this->_connect_timeout;
|
170 |
+
}
|
171 |
+
|
172 |
+
/**
|
173 |
+
* @return string
|
174 |
+
*/
|
175 |
+
public function getEndpoint()
|
176 |
+
{
|
177 |
+
return $this->_endpoint;
|
178 |
+
}
|
179 |
+
|
180 |
+
/**
|
181 |
+
* @return bool|null
|
182 |
+
*/
|
183 |
+
public function getFork()
|
184 |
+
{
|
185 |
+
return $this->_fork;
|
186 |
+
}
|
187 |
+
|
188 |
+
/**
|
189 |
+
* @return string
|
190 |
+
*/
|
191 |
+
public function getHost()
|
192 |
+
{
|
193 |
+
return $this->_host;
|
194 |
+
}
|
195 |
+
|
196 |
+
/**
|
197 |
+
* @return array
|
198 |
+
*/
|
199 |
+
public function getOptions()
|
200 |
+
{
|
201 |
+
return $this->_options;
|
202 |
+
}
|
203 |
+
|
204 |
+
/**
|
205 |
+
* @return string
|
206 |
+
*/
|
207 |
+
public function getProtocol()
|
208 |
+
{
|
209 |
+
return $this->_protocol;
|
210 |
+
}
|
211 |
+
|
212 |
+
/**
|
213 |
+
* @return int
|
214 |
+
*/
|
215 |
+
public function getTimeout()
|
216 |
+
{
|
217 |
+
return $this->_timeout;
|
218 |
+
}
|
219 |
+
|
220 |
+
|
221 |
+
}
|
mixpanel/lib/ConsumerStrategies/FileConsumer.php
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once(dirname(__FILE__) . "/AbstractConsumer.php");
|
3 |
+
/**
|
4 |
+
* Consumes messages and writes them to a file
|
5 |
+
*/
|
6 |
+
class ConsumerStrategies_FileConsumer extends ConsumerStrategies_AbstractConsumer {
|
7 |
+
|
8 |
+
/**
|
9 |
+
* @var string path to a file that we want to write the messages to
|
10 |
+
*/
|
11 |
+
private $_file;
|
12 |
+
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Creates a new FileConsumer and assigns properties from the $options array
|
16 |
+
* @param array $options
|
17 |
+
*/
|
18 |
+
function __construct($options) {
|
19 |
+
parent::__construct($options);
|
20 |
+
|
21 |
+
// what file to write to?
|
22 |
+
$this->_file = array_key_exists("file", $options) ? $options['file'] : dirname(__FILE__)."/../../messages.txt";
|
23 |
+
}
|
24 |
+
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Append $batch to a file
|
28 |
+
* @param array $batch
|
29 |
+
* @return bool
|
30 |
+
*/
|
31 |
+
public function persist($batch) {
|
32 |
+
if (count($batch) > 0) {
|
33 |
+
return file_put_contents($this->_file, json_encode($batch)."\n", FILE_APPEND | LOCK_EX) !== false;
|
34 |
+
} else {
|
35 |
+
return true;
|
36 |
+
}
|
37 |
+
}
|
38 |
+
}
|
mixpanel/lib/ConsumerStrategies/SocketConsumer.php
ADDED
@@ -0,0 +1,308 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Portions of this class were borrowed from
|
4 |
+
* https://github.com/segmentio/analytics-php/blob/master/lib/Analytics/Consumer/Socket.php.
|
5 |
+
* Thanks for the work!
|
6 |
+
*
|
7 |
+
* WWWWWW||WWWWWW
|
8 |
+
* W W W||W W W
|
9 |
+
* ||
|
10 |
+
* ( OO )__________
|
11 |
+
* / | \
|
12 |
+
* /o o| MIT \
|
13 |
+
* \___/||_||__||_|| *
|
14 |
+
* || || || ||
|
15 |
+
* _||_|| _||_||
|
16 |
+
* (__|__|(__|__|
|
17 |
+
* (The MIT License)
|
18 |
+
*
|
19 |
+
* Copyright (c) 2013 Segment.io Inc. friends@segment.io
|
20 |
+
*
|
21 |
+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
22 |
+
* documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the
|
23 |
+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
24 |
+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
25 |
+
*
|
26 |
+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
27 |
+
* Software.
|
28 |
+
*
|
29 |
+
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
30 |
+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
31 |
+
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
32 |
+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
33 |
+
*/
|
34 |
+
require_once(dirname(__FILE__) . "/AbstractConsumer.php");
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Consumes messages and writes them to host/endpoint using a persistent socket
|
38 |
+
*/
|
39 |
+
class ConsumerStrategies_SocketConsumer extends ConsumerStrategies_AbstractConsumer {
|
40 |
+
|
41 |
+
/**
|
42 |
+
* @var string the host to connect to (e.g. api.mixpanel.com)
|
43 |
+
*/
|
44 |
+
private $_host;
|
45 |
+
|
46 |
+
|
47 |
+
/**
|
48 |
+
* @var string the host-relative endpoint to write to (e.g. /engage)
|
49 |
+
*/
|
50 |
+
private $_endpoint;
|
51 |
+
|
52 |
+
|
53 |
+
/**
|
54 |
+
* @var int connect_timeout the socket connection timeout in seconds
|
55 |
+
*/
|
56 |
+
private $_connect_timeout;
|
57 |
+
|
58 |
+
|
59 |
+
/**
|
60 |
+
* @var string the protocol to use for the socket connection
|
61 |
+
*/
|
62 |
+
private $_protocol;
|
63 |
+
|
64 |
+
|
65 |
+
/**
|
66 |
+
* @var resource holds the socket resource
|
67 |
+
*/
|
68 |
+
private $_socket;
|
69 |
+
|
70 |
+
/**
|
71 |
+
* @var bool whether or not to wait for a response
|
72 |
+
*/
|
73 |
+
private $_async;
|
74 |
+
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Creates a new SocketConsumer and assigns properties from the $options array
|
78 |
+
* @param array $options
|
79 |
+
*/
|
80 |
+
public function __construct($options = array()) {
|
81 |
+
parent::__construct($options);
|
82 |
+
|
83 |
+
|
84 |
+
$this->_host = $options['host'];
|
85 |
+
$this->_endpoint = $options['endpoint'];
|
86 |
+
$this->_connect_timeout = array_key_exists('connect_timeout', $options) ? $options['connect_timeout'] : 5;
|
87 |
+
$this->_async = array_key_exists('async', $options) && $options['async'] === false ? false : true;
|
88 |
+
|
89 |
+
if (array_key_exists('use_ssl', $options) && $options['use_ssl'] == true) {
|
90 |
+
$this->_protocol = "ssl";
|
91 |
+
$this->_port = 443;
|
92 |
+
} else {
|
93 |
+
$this->_protocol = "tcp";
|
94 |
+
$this->_port = 80;
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
|
99 |
+
/**
|
100 |
+
* Write using a persistent socket connection.
|
101 |
+
* @param array $batch
|
102 |
+
* @return bool
|
103 |
+
*/
|
104 |
+
public function persist($batch) {
|
105 |
+
|
106 |
+
$socket = $this->_getSocket();
|
107 |
+
if (!is_resource($socket)) {
|
108 |
+
return false;
|
109 |
+
}
|
110 |
+
|
111 |
+
$data = "data=".$this->_encode($batch);
|
112 |
+
|
113 |
+
$body = "";
|
114 |
+
$body.= "POST ".$this->_endpoint." HTTP/1.1\r\n";
|
115 |
+
$body.= "Host: " . $this->_host . "\r\n";
|
116 |
+
$body.= "Content-Type: application/x-www-form-urlencoded\r\n";
|
117 |
+
$body.= "Accept: application/json\r\n";
|
118 |
+
$body.= "Content-length: " . strlen($data) . "\r\n";
|
119 |
+
$body.= "\r\n";
|
120 |
+
$body.= $data;
|
121 |
+
|
122 |
+
return $this->_write($socket, $body);
|
123 |
+
}
|
124 |
+
|
125 |
+
|
126 |
+
/**
|
127 |
+
* Return cached socket if open or create a new persistent socket
|
128 |
+
* @return bool|resource
|
129 |
+
*/
|
130 |
+
private function _getSocket() {
|
131 |
+
if(is_resource($this->_socket)) {
|
132 |
+
|
133 |
+
if ($this->_debug()) {
|
134 |
+
$this->_log("Using existing socket");
|
135 |
+
}
|
136 |
+
|
137 |
+
return $this->_socket;
|
138 |
+
} else {
|
139 |
+
|
140 |
+
if ($this->_debug()) {
|
141 |
+
$this->_log("Creating new socket at ".time());
|
142 |
+
}
|
143 |
+
|
144 |
+
return $this->_createSocket();
|
145 |
+
}
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Attempt to open a new socket connection, cache it, and return the resource
|
150 |
+
* @param bool $retry
|
151 |
+
* @return bool|resource
|
152 |
+
*/
|
153 |
+
private function _createSocket($retry = true) {
|
154 |
+
try {
|
155 |
+
$socket = pfsockopen($this->_protocol . "://" . $this->_host, $this->_port, $err_no, $err_msg, $this->_connect_timeout);
|
156 |
+
|
157 |
+
if ($this->_debug()) {
|
158 |
+
$this->_log("Opening socket connection to " . $this->_protocol . "://" . $this->_host . ":" . $this->_port);
|
159 |
+
}
|
160 |
+
|
161 |
+
if ($err_no != 0) {
|
162 |
+
$this->_handleError($err_no, $err_msg);
|
163 |
+
return $retry == true ? $this->_createSocket(false) : false;
|
164 |
+
} else {
|
165 |
+
// cache the socket
|
166 |
+
$this->_socket = $socket;
|
167 |
+
return $socket;
|
168 |
+
}
|
169 |
+
|
170 |
+
} catch (Exception $e) {
|
171 |
+
$this->_handleError($e->getCode(), $e->getMessage());
|
172 |
+
return $retry == true ? $this->_createSocket(false) : false;
|
173 |
+
}
|
174 |
+
}
|
175 |
+
|
176 |
+
/**
|
177 |
+
* Attempt to close and dereference a socket resource
|
178 |
+
*/
|
179 |
+
private function _destroySocket() {
|
180 |
+
$socket = $this->_socket;
|
181 |
+
$this->_socket = null;
|
182 |
+
fclose($socket);
|
183 |
+
}
|
184 |
+
|
185 |
+
|
186 |
+
/**
|
187 |
+
* Write $data through the given $socket
|
188 |
+
* @param $socket
|
189 |
+
* @param $data
|
190 |
+
* @param bool $retry
|
191 |
+
* @return bool
|
192 |
+
*/
|
193 |
+
private function _write($socket, $data, $retry = true) {
|
194 |
+
|
195 |
+
$bytes_sent = 0;
|
196 |
+
$bytes_total = strlen($data);
|
197 |
+
$socket_closed = false;
|
198 |
+
$success = true;
|
199 |
+
$max_bytes_per_write = 8192;
|
200 |
+
|
201 |
+
// if we have no data to write just return true
|
202 |
+
if ($bytes_total == 0) {
|
203 |
+
return true;
|
204 |
+
}
|
205 |
+
|
206 |
+
// try to write the data
|
207 |
+
while (!$socket_closed && $bytes_sent < $bytes_total) {
|
208 |
+
|
209 |
+
try {
|
210 |
+
$bytes = fwrite($socket, $data, $max_bytes_per_write);
|
211 |
+
|
212 |
+
if ($this->_debug()) {
|
213 |
+
$this->_log("Socket wrote ".$bytes." bytes");
|
214 |
+
}
|
215 |
+
|
216 |
+
// if we actually wrote data, then remove the written portion from $data left to write
|
217 |
+
if ($bytes > 0) {
|
218 |
+
$data = substr($data, $max_bytes_per_write);
|
219 |
+
}
|
220 |
+
|
221 |
+
} catch (Exception $e) {
|
222 |
+
$this->_handleError($e->getCode(), $e->getMessage());
|
223 |
+
$socket_closed = true;
|
224 |
+
}
|
225 |
+
|
226 |
+
if (isset($bytes) && $bytes) {
|
227 |
+
$bytes_sent += $bytes;
|
228 |
+
} else {
|
229 |
+
$socket_closed = true;
|
230 |
+
}
|
231 |
+
}
|
232 |
+
|
233 |
+
// create a new socket if the current one is closed and retry the message
|
234 |
+
if ($socket_closed) {
|
235 |
+
|
236 |
+
$this->_destroySocket();
|
237 |
+
|
238 |
+
if ($retry) {
|
239 |
+
if ($this->_debug()) {
|
240 |
+
$this->_log("Retrying socket write...");
|
241 |
+
}
|
242 |
+
$socket = $this->_getSocket();
|
243 |
+
if ($socket) return $this->_write($socket, $data, false);
|
244 |
+
}
|
245 |
+
|
246 |
+
return false;
|
247 |
+
}
|
248 |
+
|
249 |
+
|
250 |
+
// only wait for the response in debug mode or if we explicitly want to be synchronous
|
251 |
+
if ($this->_debug() || !$this->_async) {
|
252 |
+
$res = $this->handleResponse(fread($socket, 2048));
|
253 |
+
if ($res["status"] != "200") {
|
254 |
+
$this->_handleError($res["status"], $res["body"]);
|
255 |
+
$success = false;
|
256 |
+
}
|
257 |
+
}
|
258 |
+
|
259 |
+
return $success;
|
260 |
+
}
|
261 |
+
|
262 |
+
|
263 |
+
/**
|
264 |
+
* Parse the response from a socket write (only used for debugging)
|
265 |
+
* @param $response
|
266 |
+
* @return array
|
267 |
+
*/
|
268 |
+
private function handleResponse($response) {
|
269 |
+
|
270 |
+
$lines = explode("\n", $response);
|
271 |
+
|
272 |
+
// extract headers
|
273 |
+
$headers = array();
|
274 |
+
foreach($lines as $line) {
|
275 |
+
$kvsplit = explode(":", $line);
|
276 |
+
if (count($kvsplit) == 2) {
|
277 |
+
$header = $kvsplit[0];
|
278 |
+
$value = $kvsplit[1];
|
279 |
+
$headers[$header] = trim($value);
|
280 |
+
}
|
281 |
+
|
282 |
+
}
|
283 |
+
|
284 |
+
// extract status
|
285 |
+
$line_one_exploded = explode(" ", $lines[0]);
|
286 |
+
$status = $line_one_exploded[1];
|
287 |
+
|
288 |
+
// extract body
|
289 |
+
$body = $lines[count($lines) - 1];
|
290 |
+
|
291 |
+
// if the connection has been closed lets kill the socket
|
292 |
+
if ($headers['Connection'] == "close") {
|
293 |
+
$this->_destroySocket();
|
294 |
+
if ($this->_debug()) {
|
295 |
+
$this->_log("Server told us connection closed so lets destroy the socket so it'll reconnect on next call");
|
296 |
+
}
|
297 |
+
}
|
298 |
+
|
299 |
+
$ret = array(
|
300 |
+
"status" => $status,
|
301 |
+
"body" => $body,
|
302 |
+
);
|
303 |
+
|
304 |
+
return $ret;
|
305 |
+
}
|
306 |
+
|
307 |
+
|
308 |
+
}
|
mixpanel/lib/Mixpanel.php
ADDED
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
require_once(dirname(__FILE__) . "/Base/MixpanelBase.php");
|
4 |
+
require_once(dirname(__FILE__) . "/Producers/MixpanelPeople.php");
|
5 |
+
require_once(dirname(__FILE__) . "/Producers/MixpanelEvents.php");
|
6 |
+
|
7 |
+
/**
|
8 |
+
* This is the main class for the Mixpanel PHP Library which provides all of the methods you need to track events and
|
9 |
+
* create/update profiles.
|
10 |
+
*
|
11 |
+
* Architecture
|
12 |
+
* -------------
|
13 |
+
*
|
14 |
+
* This library is built such that all messages are buffered in an in-memory "queue"
|
15 |
+
* The queue will be automatically flushed at the end of every request. Alternatively, you can call "flush()" manually
|
16 |
+
* at any time. Flushed messages will be passed to a Consumer's "persist" method. The library comes with a handful of
|
17 |
+
* Consumers. The "CurlConsumer" is used by default which will send the messages to Mixpanel using forked cURL processes.
|
18 |
+
* You can implement your own custom Consumer to customize how a message is sent to Mixpanel. This can be useful when
|
19 |
+
* you want to put messages onto a distributed queue (such as ActiveMQ or Kestrel) instead of writing to Mixpanel in
|
20 |
+
* the user thread.
|
21 |
+
*
|
22 |
+
* Options
|
23 |
+
* -------------
|
24 |
+
*
|
25 |
+
* <table width="100%" cellpadding="5">
|
26 |
+
* <tr>
|
27 |
+
* <th>Option</th>
|
28 |
+
* <th>Description</th>
|
29 |
+
* <th>Default</th>
|
30 |
+
* </tr>
|
31 |
+
* <tr>
|
32 |
+
* <td>max_queue_size</td>
|
33 |
+
* <td>The maximum number of items to buffer in memory before flushing</td>
|
34 |
+
* <td>1000</td>
|
35 |
+
* </tr>
|
36 |
+
* <tr>
|
37 |
+
* <td>debug</td>
|
38 |
+
* <td>Enable/disable debug mode</td>
|
39 |
+
* <td>false</td>
|
40 |
+
* </tr>
|
41 |
+
* <tr>
|
42 |
+
* <td>consumer</td>
|
43 |
+
* <td>The consumer to use for writing messages</td>
|
44 |
+
* <td>curl</td>
|
45 |
+
* </tr>
|
46 |
+
* <tr>
|
47 |
+
* <td>consumers</td>
|
48 |
+
* <td>An array of custom consumers in the format array(consumer_key => class_name)</td>
|
49 |
+
* <td>null</td>
|
50 |
+
* </tr>
|
51 |
+
* <tr>
|
52 |
+
* <td>host</td>
|
53 |
+
* <td>The host name for api calls (used by some consumers)</td>
|
54 |
+
* <td>api.mixpanel.com</td>
|
55 |
+
* </tr>
|
56 |
+
* <tr>
|
57 |
+
* <td>events_endpoint</td>
|
58 |
+
* <td>The endpoint for tracking events (relative to the host)</td>
|
59 |
+
* <td>/events</td>
|
60 |
+
* </tr>
|
61 |
+
* <tr>
|
62 |
+
* <td>people_endpoint</td>
|
63 |
+
* <td>The endpoint for making people updates (relative to the host)</td>
|
64 |
+
* <td>/engage</td>
|
65 |
+
* </tr>
|
66 |
+
* <tr>
|
67 |
+
* <td>use_ssl</td>
|
68 |
+
* <td>Tell the consumer whether or not to use ssl (when available)</td>
|
69 |
+
* <td>true</td>
|
70 |
+
* </tr>
|
71 |
+
* <tr>
|
72 |
+
* <td>error_callback</td>
|
73 |
+
* <td>The name of a function to be called on consumption failures</td>
|
74 |
+
* <td>null</td>
|
75 |
+
* </tr>
|
76 |
+
* <tr>
|
77 |
+
* <td>connect_timeout</td>
|
78 |
+
* <td>In both the SocketConsumer and CurlConsumer, this is used for the connection timeout (i.e. How long it has take to actually make a connection).
|
79 |
+
* <td>5</td>
|
80 |
+
* </tr>
|
81 |
+
* <tr>
|
82 |
+
* <td>timeout</td>
|
83 |
+
* <td>In the CurlConsumer (non-forked), it is used to determine how long the cURL call has to execute.
|
84 |
+
* <td>30</td>
|
85 |
+
* </tr>
|
86 |
+
* </table>
|
87 |
+
*
|
88 |
+
* Example: Tracking an Event
|
89 |
+
* -------------
|
90 |
+
*
|
91 |
+
* $mp = Mixpanel::getInstance("MY_TOKEN");
|
92 |
+
*
|
93 |
+
* $mp->track("My Event");
|
94 |
+
*
|
95 |
+
* Example: Setting Profile Properties
|
96 |
+
* -------------
|
97 |
+
*
|
98 |
+
* $mp = Mixpanel::getInstance("MY_TOKEN", array("use_ssl" => false));
|
99 |
+
*
|
100 |
+
* $mp->people->set(12345, array(
|
101 |
+
* '$first_name' => "John",
|
102 |
+
* '$last_name' => "Doe",
|
103 |
+
* '$email' => "john.doe@example.com",
|
104 |
+
* '$phone' => "5555555555",
|
105 |
+
* 'Favorite Color' => "red"
|
106 |
+
* ));
|
107 |
+
*
|
108 |
+
*/
|
109 |
+
class Mixpanel extends Base_MixpanelBase {
|
110 |
+
|
111 |
+
|
112 |
+
/**
|
113 |
+
* An instance of the MixpanelPeople class (used to create/update profiles)
|
114 |
+
* @var MixpanelPeople
|
115 |
+
*/
|
116 |
+
public $people;
|
117 |
+
|
118 |
+
|
119 |
+
/**
|
120 |
+
* An instance of the MixpanelEvents class
|
121 |
+
* @var Producers_MixpanelEvents
|
122 |
+
*/
|
123 |
+
private $_events;
|
124 |
+
|
125 |
+
|
126 |
+
/**
|
127 |
+
* An instance of the Mixpanel class (for singleton use)
|
128 |
+
* @var Mixpanel
|
129 |
+
*/
|
130 |
+
private static $_instance;
|
131 |
+
|
132 |
+
|
133 |
+
/**
|
134 |
+
* Instantiates a new Mixpanel instance.
|
135 |
+
* @param $token
|
136 |
+
* @param array $options
|
137 |
+
*/
|
138 |
+
public function __construct($token, $options = array()) {
|
139 |
+
parent::__construct($options);
|
140 |
+
$this->people = new Producers_MixpanelPeople($token, $options);
|
141 |
+
$this->_events = new Producers_MixpanelEvents($token, $options);
|
142 |
+
}
|
143 |
+
|
144 |
+
|
145 |
+
/**
|
146 |
+
* Returns a singleton instance of Mixpanel
|
147 |
+
* @param $token
|
148 |
+
* @param array $options
|
149 |
+
* @return Mixpanel
|
150 |
+
*/
|
151 |
+
public static function getInstance($token, $options = array()) {
|
152 |
+
if(!isset(self::$_instance)) {
|
153 |
+
self::$_instance = new Mixpanel($token, $options);
|
154 |
+
}
|
155 |
+
return self::$_instance;
|
156 |
+
}
|
157 |
+
|
158 |
+
|
159 |
+
/**
|
160 |
+
* Add an array representing a message to be sent to Mixpanel to the in-memory queue.
|
161 |
+
* @param array $message
|
162 |
+
*/
|
163 |
+
public function enqueue($message = array()) {
|
164 |
+
$this->_events->enqueue($message);
|
165 |
+
}
|
166 |
+
|
167 |
+
|
168 |
+
/**
|
169 |
+
* Add an array representing a list of messages to be sent to Mixpanel to a queue.
|
170 |
+
* @param array $messages
|
171 |
+
*/
|
172 |
+
public function enqueueAll($messages = array()) {
|
173 |
+
$this->_events->enqueueAll($messages);
|
174 |
+
}
|
175 |
+
|
176 |
+
|
177 |
+
/**
|
178 |
+
* Flush the events queue
|
179 |
+
* @param int $desired_batch_size
|
180 |
+
*/
|
181 |
+
public function flush($desired_batch_size = 50) {
|
182 |
+
$this->_events->flush($desired_batch_size);
|
183 |
+
}
|
184 |
+
|
185 |
+
|
186 |
+
/**
|
187 |
+
* Empty the events queue
|
188 |
+
*/
|
189 |
+
public function reset() {
|
190 |
+
$this->_events->reset();
|
191 |
+
}
|
192 |
+
|
193 |
+
|
194 |
+
/**
|
195 |
+
* Identify the user you want to associate to tracked events
|
196 |
+
* @param string|int $user_id
|
197 |
+
*/
|
198 |
+
public function identify($user_id) {
|
199 |
+
$this->_events->identify($user_id);
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Track an event defined by $event associated with metadata defined by $properties
|
204 |
+
* @param string $event
|
205 |
+
* @param array $properties
|
206 |
+
*/
|
207 |
+
public function track($event, $properties = array()) {
|
208 |
+
$this->_events->track($event, $properties);
|
209 |
+
}
|
210 |
+
|
211 |
+
|
212 |
+
/**
|
213 |
+
* Register a property to be sent with every event.
|
214 |
+
*
|
215 |
+
* If the property has already been registered, it will be
|
216 |
+
* overwritten. NOTE: Registered properties are only persisted for the life of the Mixpanel class instance.
|
217 |
+
* @param string $property
|
218 |
+
* @param mixed $value
|
219 |
+
*/
|
220 |
+
public function register($property, $value) {
|
221 |
+
$this->_events->register($property, $value);
|
222 |
+
}
|
223 |
+
|
224 |
+
|
225 |
+
/**
|
226 |
+
* Register multiple properties to be sent with every event.
|
227 |
+
*
|
228 |
+
* If any of the properties have already been registered,
|
229 |
+
* they will be overwritten. NOTE: Registered properties are only persisted for the life of the Mixpanel class
|
230 |
+
* instance.
|
231 |
+
* @param array $props_and_vals
|
232 |
+
*/
|
233 |
+
public function registerAll($props_and_vals = array()) {
|
234 |
+
$this->_events->registerAll($props_and_vals);
|
235 |
+
}
|
236 |
+
|
237 |
+
|
238 |
+
/**
|
239 |
+
* Register a property to be sent with every event.
|
240 |
+
*
|
241 |
+
* If the property has already been registered, it will NOT be
|
242 |
+
* overwritten. NOTE: Registered properties are only persisted for the life of the Mixpanel class instance.
|
243 |
+
* @param $property
|
244 |
+
* @param $value
|
245 |
+
*/
|
246 |
+
public function registerOnce($property, $value) {
|
247 |
+
$this->_events->registerOnce($property, $value);
|
248 |
+
}
|
249 |
+
|
250 |
+
|
251 |
+
/**
|
252 |
+
* Register multiple properties to be sent with every event.
|
253 |
+
*
|
254 |
+
* If any of the properties have already been registered,
|
255 |
+
* they will NOT be overwritten. NOTE: Registered properties are only persisted for the life of the Mixpanel class
|
256 |
+
* instance.
|
257 |
+
* @param array $props_and_vals
|
258 |
+
*/
|
259 |
+
public function registerAllOnce($props_and_vals = array()) {
|
260 |
+
$this->_events->registerAllOnce($props_and_vals);
|
261 |
+
}
|
262 |
+
|
263 |
+
|
264 |
+
/**
|
265 |
+
* Un-register an property to be sent with every event.
|
266 |
+
* @param string $property
|
267 |
+
*/
|
268 |
+
public function unregister($property) {
|
269 |
+
$this->_events->unregister($property);
|
270 |
+
}
|
271 |
+
|
272 |
+
|
273 |
+
/**
|
274 |
+
* Un-register a list of properties to be sent with every event.
|
275 |
+
* @param array $properties
|
276 |
+
*/
|
277 |
+
public function unregisterAll($properties) {
|
278 |
+
$this->_events->unregisterAll($properties);
|
279 |
+
}
|
280 |
+
|
281 |
+
|
282 |
+
/**
|
283 |
+
* Get a property that is set to be sent with every event
|
284 |
+
* @param string $property
|
285 |
+
* @return mixed
|
286 |
+
*/
|
287 |
+
public function getProperty($property)
|
288 |
+
{
|
289 |
+
return $this->_events->getProperty($property);
|
290 |
+
}
|
291 |
+
|
292 |
+
|
293 |
+
/**
|
294 |
+
* Alias an existing id with a different unique id. This is helpful when you want to associate a generated id
|
295 |
+
* (such as a session id) to a user id or username.
|
296 |
+
* @param string|int $original_id
|
297 |
+
* @param string|int $new_id
|
298 |
+
*/
|
299 |
+
public function createAlias($original_id, $new_id) {
|
300 |
+
$this->_events->createAlias($original_id, $new_id);
|
301 |
+
}
|
302 |
+
}
|
mixpanel/lib/Producers/MixpanelBaseProducer.php
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once(dirname(__FILE__) . "/../Base/MixpanelBase.php");
|
3 |
+
require_once(dirname(__FILE__) . "/../ConsumerStrategies/FileConsumer.php");
|
4 |
+
require_once(dirname(__FILE__) . "/../ConsumerStrategies/CurlConsumer.php");
|
5 |
+
require_once(dirname(__FILE__) . "/../ConsumerStrategies/SocketConsumer.php");
|
6 |
+
|
7 |
+
if (!function_exists('json_encode')) {
|
8 |
+
throw new Exception('The JSON PHP extension is required.');
|
9 |
+
}
|
10 |
+
|
11 |
+
/**
|
12 |
+
* Provides some base methods for use by a message Producer
|
13 |
+
*/
|
14 |
+
abstract class Producers_MixpanelBaseProducer extends Base_MixpanelBase {
|
15 |
+
|
16 |
+
|
17 |
+
/**
|
18 |
+
* @var string a token associated to a Mixpanel project
|
19 |
+
*/
|
20 |
+
protected $_token;
|
21 |
+
|
22 |
+
|
23 |
+
/**
|
24 |
+
* @var array a queue to hold messages in memory before flushing in batches
|
25 |
+
*/
|
26 |
+
private $_queue = array();
|
27 |
+
|
28 |
+
|
29 |
+
/**
|
30 |
+
* @var ConsumerStrategies_AbstractConsumer the consumer to use when flushing messages
|
31 |
+
*/
|
32 |
+
private $_consumer = null;
|
33 |
+
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @var array The list of available consumers
|
37 |
+
*/
|
38 |
+
private $_consumers = array(
|
39 |
+
"file" => "ConsumerStrategies_FileConsumer",
|
40 |
+
"curl" => "ConsumerStrategies_CurlConsumer",
|
41 |
+
"socket" => "ConsumerStrategies_SocketConsumer"
|
42 |
+
);
|
43 |
+
|
44 |
+
|
45 |
+
/**
|
46 |
+
* If the queue reaches this size we'll auto-flush to prevent out of memory errors
|
47 |
+
* @var int
|
48 |
+
*/
|
49 |
+
protected $_max_queue_size = 1000;
|
50 |
+
|
51 |
+
|
52 |
+
/**
|
53 |
+
* Creates a new MixpanelBaseProducer, assings Mixpanel project token, registers custom Consumers, and instantiates
|
54 |
+
* the desired consumer
|
55 |
+
* @param $token
|
56 |
+
* @param array $options
|
57 |
+
*/
|
58 |
+
public function __construct($token, $options = array()) {
|
59 |
+
|
60 |
+
parent::__construct($options);
|
61 |
+
|
62 |
+
// register any customer consumers
|
63 |
+
if (array_key_exists("consumers", $options)) {
|
64 |
+
$this->_consumers = array_merge($this->_consumers, $options['consumers']);
|
65 |
+
}
|
66 |
+
|
67 |
+
// set max queue size
|
68 |
+
if (array_key_exists("max_queue_size", $options)) {
|
69 |
+
$this->_max_queue_size = $options['max_queue_size'];
|
70 |
+
}
|
71 |
+
|
72 |
+
// associate token
|
73 |
+
$this->_token = $token;
|
74 |
+
|
75 |
+
if ($this->_debug()) {
|
76 |
+
$this->_log("Using token: ".$this->_token);
|
77 |
+
}
|
78 |
+
|
79 |
+
// instantiate the chosen consumer
|
80 |
+
$this->_consumer = $this->_getConsumer();
|
81 |
+
|
82 |
+
}
|
83 |
+
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Flush the queue when we destruct the client with retries
|
87 |
+
*/
|
88 |
+
public function __destruct() {
|
89 |
+
$attempts = 0;
|
90 |
+
$max_attempts = 10;
|
91 |
+
$success = false;
|
92 |
+
while (!$success && $attempts < $max_attempts) {
|
93 |
+
if ($this->_debug()) {
|
94 |
+
$this->_log("destruct flush attempt #".($attempts+1));
|
95 |
+
}
|
96 |
+
$success = $this->flush();
|
97 |
+
$attempts++;
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
|
102 |
+
/**
|
103 |
+
* Iterate the queue and write in batches using the instantiated Consumer Strategy
|
104 |
+
* @param int $desired_batch_size
|
105 |
+
* @return bool whether or not the flush was successful
|
106 |
+
*/
|
107 |
+
public function flush($desired_batch_size = 50) {
|
108 |
+
$queue_size = count($this->_queue);
|
109 |
+
$succeeded = true;
|
110 |
+
if ($this->_debug()) {
|
111 |
+
$this->_log("Flush called - queue size: ".$queue_size);
|
112 |
+
}
|
113 |
+
|
114 |
+
while($queue_size > 0 && $succeeded) {
|
115 |
+
$batch_size = min(array($queue_size, $desired_batch_size, $this->_options['max_batch_size']));
|
116 |
+
$batch = array_splice($this->_queue, 0, $batch_size);
|
117 |
+
$succeeded = $this->_persist($batch);
|
118 |
+
|
119 |
+
if (!$succeeded) {
|
120 |
+
if ($this->_debug()) {
|
121 |
+
$this->_log("Batch consumption failed!");
|
122 |
+
}
|
123 |
+
$this->_queue = array_merge($batch, $this->_queue);
|
124 |
+
|
125 |
+
if ($this->_debug()) {
|
126 |
+
$this->_log("added batch back to queue, queue size is now $queue_size");
|
127 |
+
}
|
128 |
+
}
|
129 |
+
|
130 |
+
$queue_size = count($this->_queue);
|
131 |
+
|
132 |
+
if ($this->_debug()) {
|
133 |
+
$this->_log("Batch of $batch_size consumed, queue size is now $queue_size");
|
134 |
+
}
|
135 |
+
}
|
136 |
+
return $succeeded;
|
137 |
+
}
|
138 |
+
|
139 |
+
|
140 |
+
/**
|
141 |
+
* Empties the queue without persisting any of the messages
|
142 |
+
*/
|
143 |
+
public function reset() {
|
144 |
+
$this->_queue = array();
|
145 |
+
}
|
146 |
+
|
147 |
+
|
148 |
+
/**
|
149 |
+
* Returns the in-memory queue
|
150 |
+
* @return array
|
151 |
+
*/
|
152 |
+
public function getQueue() {
|
153 |
+
return $this->_queue;
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Returns the current Mixpanel project token
|
158 |
+
* @return string
|
159 |
+
*/
|
160 |
+
public function getToken() {
|
161 |
+
return $this->_token;
|
162 |
+
}
|
163 |
+
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Given a strategy type, return a new PersistenceStrategy object
|
167 |
+
* @return ConsumerStrategies_AbstractConsumer
|
168 |
+
*/
|
169 |
+
protected function _getConsumer() {
|
170 |
+
$key = $this->_options['consumer'];
|
171 |
+
$Strategy = $this->_consumers[$key];
|
172 |
+
if ($this->_debug()) {
|
173 |
+
$this->_log("Using consumer: " . $key . " -> " . $Strategy);
|
174 |
+
}
|
175 |
+
$this->_options['endpoint'] = $this->_getEndpoint();
|
176 |
+
|
177 |
+
return new $Strategy($this->_options);
|
178 |
+
}
|
179 |
+
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Add an array representing a message to be sent to Mixpanel to a queue.
|
183 |
+
* @param array $message
|
184 |
+
*/
|
185 |
+
public function enqueue($message = array()) {
|
186 |
+
array_push($this->_queue, $message);
|
187 |
+
|
188 |
+
// force a flush if we've reached our threshold
|
189 |
+
if (count($this->_queue) > $this->_max_queue_size) {
|
190 |
+
$this->flush();
|
191 |
+
}
|
192 |
+
|
193 |
+
if ($this->_debug()) {
|
194 |
+
$this->_log("Queued message: ".json_encode($message));
|
195 |
+
}
|
196 |
+
}
|
197 |
+
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Add an array representing a list of messages to be sent to Mixpanel to a queue.
|
201 |
+
* @param array $messages
|
202 |
+
*/
|
203 |
+
public function enqueueAll($messages = array()) {
|
204 |
+
foreach($messages as $message) {
|
205 |
+
$this->enqueue($message);
|
206 |
+
}
|
207 |
+
|
208 |
+
}
|
209 |
+
|
210 |
+
|
211 |
+
/**
|
212 |
+
* Given an array of messages, persist it with the instantiated Persistence Strategy
|
213 |
+
* @param $message
|
214 |
+
* @return mixed
|
215 |
+
*/
|
216 |
+
protected function _persist($message) {
|
217 |
+
return $this->_consumer->persist($message);
|
218 |
+
}
|
219 |
+
|
220 |
+
|
221 |
+
|
222 |
+
|
223 |
+
/**
|
224 |
+
* Return the endpoint that should be used by a consumer that consumes messages produced by this producer.
|
225 |
+
* @return string
|
226 |
+
*/
|
227 |
+
abstract function _getEndpoint();
|
228 |
+
|
229 |
+
}
|
mixpanel/lib/Producers/MixpanelEvents.php
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once(dirname(__FILE__) . "/MixpanelBaseProducer.php");
|
3 |
+
require_once(dirname(__FILE__) . "/MixpanelPeople.php");
|
4 |
+
require_once(dirname(__FILE__) . "/../ConsumerStrategies/CurlConsumer.php");
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Provides an API to track events on Mixpanel
|
8 |
+
*/
|
9 |
+
class Producers_MixpanelEvents extends Producers_MixpanelBaseProducer {
|
10 |
+
|
11 |
+
/**
|
12 |
+
* An array of properties to attach to every tracked event
|
13 |
+
* @var array
|
14 |
+
*/
|
15 |
+
private $_super_properties = array("mp_lib" => "php");
|
16 |
+
|
17 |
+
|
18 |
+
/**
|
19 |
+
* Track an event defined by $event associated with metadata defined by $properties
|
20 |
+
* @param string $event
|
21 |
+
* @param array $properties
|
22 |
+
*/
|
23 |
+
public function track($event, $properties = array()) {
|
24 |
+
|
25 |
+
// if no token is passed in, use current token
|
26 |
+
if (!array_key_exists("token", $properties)) $properties['token'] = $this->_token;
|
27 |
+
|
28 |
+
// if no time is passed in, use the current time
|
29 |
+
if (!array_key_exists('time', $properties)) $properties['time'] = time();
|
30 |
+
|
31 |
+
$params['event'] = $event;
|
32 |
+
$params['properties'] = array_merge($this->_super_properties, $properties);
|
33 |
+
|
34 |
+
$this->enqueue($params);
|
35 |
+
}
|
36 |
+
|
37 |
+
|
38 |
+
/**
|
39 |
+
* Register a property to be sent with every event. If the property has already been registered, it will be
|
40 |
+
* overwritten.
|
41 |
+
* @param string $property
|
42 |
+
* @param mixed $value
|
43 |
+
*/
|
44 |
+
public function register($property, $value) {
|
45 |
+
$this->_super_properties[$property] = $value;
|
46 |
+
}
|
47 |
+
|
48 |
+
|
49 |
+
/**
|
50 |
+
* Register multiple properties to be sent with every event. If any of the properties have already been registered,
|
51 |
+
* they will be overwritten.
|
52 |
+
* @param array $props_and_vals
|
53 |
+
*/
|
54 |
+
public function registerAll($props_and_vals = array()) {
|
55 |
+
foreach($props_and_vals as $property => $value) {
|
56 |
+
$this->register($property, $value);
|
57 |
+
}
|
58 |
+
}
|
59 |
+
|
60 |
+
|
61 |
+
/**
|
62 |
+
* Register a property to be sent with every event. If the property has already been registered, it will NOT be
|
63 |
+
* overwritten.
|
64 |
+
* @param $property
|
65 |
+
* @param $value
|
66 |
+
*/
|
67 |
+
public function registerOnce($property, $value) {
|
68 |
+
if (!isset($this->_super_properties[$property])) {
|
69 |
+
$this->register($property, $value);
|
70 |
+
}
|
71 |
+
}
|
72 |
+
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Register multiple properties to be sent with every event. If any of the properties have already been registered,
|
76 |
+
* they will NOT be overwritten.
|
77 |
+
* @param array $props_and_vals
|
78 |
+
*/
|
79 |
+
public function registerAllOnce($props_and_vals = array()) {
|
80 |
+
foreach($props_and_vals as $property => $value) {
|
81 |
+
if (!isset($this->_super_properties[$property])) {
|
82 |
+
$this->register($property, $value);
|
83 |
+
}
|
84 |
+
}
|
85 |
+
}
|
86 |
+
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Un-register an property to be sent with every event.
|
90 |
+
* @param string $property
|
91 |
+
*/
|
92 |
+
public function unregister($property) {
|
93 |
+
unset($this->_super_properties[$property]);
|
94 |
+
}
|
95 |
+
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Un-register a list of properties to be sent with every event.
|
99 |
+
* @param array $properties
|
100 |
+
*/
|
101 |
+
public function unregisterAll($properties) {
|
102 |
+
foreach($properties as $property) {
|
103 |
+
$this->unregister($property);
|
104 |
+
}
|
105 |
+
}
|
106 |
+
|
107 |
+
|
108 |
+
/**
|
109 |
+
* Get a property that is set to be sent with every event
|
110 |
+
* @param string $property
|
111 |
+
* @return mixed
|
112 |
+
*/
|
113 |
+
public function getProperty($property) {
|
114 |
+
return $this->_super_properties[$property];
|
115 |
+
}
|
116 |
+
|
117 |
+
|
118 |
+
/**
|
119 |
+
* Identify the user you want to associate to tracked events
|
120 |
+
* @param string|int $user_id
|
121 |
+
*/
|
122 |
+
public function identify($user_id) {
|
123 |
+
$this->register("distinct_id", $user_id);
|
124 |
+
}
|
125 |
+
|
126 |
+
|
127 |
+
/**
|
128 |
+
* Alias an existing id with a different unique id. This is helpful when you want to associate a generated id to
|
129 |
+
* a username or e-mail address.
|
130 |
+
*
|
131 |
+
* Because aliasing can be extremely vulnerable to race conditions and ordering issues, we'll make a synchronous
|
132 |
+
* call directly to Mixpanel when this method is called. If it fails we'll throw an Exception as subsequent
|
133 |
+
* events are likely to be incorrectly tracked.
|
134 |
+
* @param string|int $original_id
|
135 |
+
* @param string|int $new_id
|
136 |
+
* @return array $msg
|
137 |
+
* @throws Exception
|
138 |
+
*/
|
139 |
+
public function createAlias($original_id, $new_id) {
|
140 |
+
$msg = array(
|
141 |
+
"event" => '$create_alias',
|
142 |
+
"properties" => array("distinct_id" => $original_id, "alias" => $new_id, "token" => $this->_token)
|
143 |
+
);
|
144 |
+
|
145 |
+
$options = array_merge($this->_options, array("endpoint" => $this->_getEndpoint(), "fork" => false));
|
146 |
+
$curlConsumer = new ConsumerStrategies_CurlConsumer($options);
|
147 |
+
$success = $curlConsumer->persist(array($msg));
|
148 |
+
if (!$success) {
|
149 |
+
error_log("Creating Mixpanel Alias (original id: $original_id, new id: $new_id) failed");
|
150 |
+
throw new Exception("Tried to create an alias but the call was not successful");
|
151 |
+
} else {
|
152 |
+
return $msg;
|
153 |
+
}
|
154 |
+
}
|
155 |
+
|
156 |
+
|
157 |
+
/**
|
158 |
+
* Returns the "events" endpoint
|
159 |
+
* @return string
|
160 |
+
*/
|
161 |
+
function _getEndpoint() {
|
162 |
+
return $this->_options['events_endpoint'];
|
163 |
+
}
|
164 |
+
}
|
mixpanel/lib/Producers/MixpanelPeople.php
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require_once(dirname(__FILE__) . "/MixpanelBaseProducer.php");
|
3 |
+
|
4 |
+
/**
|
5 |
+
* Provides an API to create/update profiles on Mixpanel
|
6 |
+
*/
|
7 |
+
class Producers_MixpanelPeople extends Producers_MixpanelBaseProducer {
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Internal method to prepare a message given the message data
|
11 |
+
* @param $distinct_id
|
12 |
+
* @param $operation
|
13 |
+
* @param $value
|
14 |
+
* @param null $ip
|
15 |
+
* @return array
|
16 |
+
*/
|
17 |
+
private function _constructPayload($distinct_id, $operation, $value, $ip = null, $ignore_time = false) {
|
18 |
+
$payload = array(
|
19 |
+
'$token' => $this->_token,
|
20 |
+
'$distinct_id' => $distinct_id,
|
21 |
+
$operation => $value
|
22 |
+
);
|
23 |
+
if ($ip !== null) $payload['$ip'] = $ip;
|
24 |
+
if ($ignore_time === true) $payload['$ignore_time'] = true;
|
25 |
+
return $payload;
|
26 |
+
}
|
27 |
+
|
28 |
+
/**
|
29 |
+
* Set properties on a user record. If the profile does not exist, it creates it with these properties.
|
30 |
+
* If it does exist, it sets the properties to these values, overwriting existing values.
|
31 |
+
* @param string|int $distinct_id the distinct_id or alias of a user
|
32 |
+
* @param array $props associative array of properties to set on the profile
|
33 |
+
* @param string|null $ip the ip address of the client (used for geo-location)
|
34 |
+
* @param boolean $ignore_time If the $ignore_time property is true, Mixpanel will not automatically update the "Last Seen" property of the profile. Otherwise, Mixpanel will add a "Last Seen" property associated with the current time
|
35 |
+
*/
|
36 |
+
public function set($distinct_id, $props, $ip = null, $ignore_time = false) {
|
37 |
+
$payload = $this->_constructPayload($distinct_id, '$set', $props, $ip, $ignore_time);
|
38 |
+
$this->enqueue($payload);
|
39 |
+
}
|
40 |
+
|
41 |
+
/**
|
42 |
+
* Set properties on a user record. If the profile does not exist, it creates it with these properties.
|
43 |
+
* If it does exist, it sets the properties to these values but WILL NOT overwrite existing values.
|
44 |
+
* @param string|int $distinct_id the distinct_id or alias of a user
|
45 |
+
* @param array $props associative array of properties to set on the profile
|
46 |
+
* @param string|null $ip the ip address of the client (used for geo-location)
|
47 |
+
* @param boolean $ignore_time If the $ignore_time property is true, Mixpanel will not automatically update the "Last Seen" property of the profile. Otherwise, Mixpanel will add a "Last Seen" property associated with the current time
|
48 |
+
*/
|
49 |
+
public function setOnce($distinct_id, $props, $ip = null, $ignore_time = false) {
|
50 |
+
$payload = $this->_constructPayload($distinct_id, '$set_once', $props, $ip, $ignore_time);
|
51 |
+
$this->enqueue($payload);
|
52 |
+
}
|
53 |
+
|
54 |
+
/**
|
55 |
+
* Unset properties on a user record. If the profile does not exist, it creates it with no properties.
|
56 |
+
* If it does exist, it unsets these properties. NOTE: In other libraries we use 'unset' which is
|
57 |
+
* a reserved word in PHP.
|
58 |
+
* @param string|int $distinct_id the distinct_id or alias of a user
|
59 |
+
* @param array $props associative array of properties to unset on the profile
|
60 |
+
* @param string|null $ip the ip address of the client (used for geo-location)
|
61 |
+
* @param boolean $ignore_time If the $ignore_time property is true, Mixpanel will not automatically update the "Last Seen" property of the profile. Otherwise, Mixpanel will add a "Last Seen" property associated with the current time
|
62 |
+
*/
|
63 |
+
public function remove($distinct_id, $props, $ip = null, $ignore_time = false) {
|
64 |
+
$payload = $this->_constructPayload($distinct_id, '$unset', $props, $ip, $ignore_time);
|
65 |
+
$this->enqueue($payload);
|
66 |
+
}
|
67 |
+
|
68 |
+
/**
|
69 |
+
* Increments the value of a property on a user record. If the profile does not exist, it creates it and sets the
|
70 |
+
* property to the increment value.
|
71 |
+
* @param string|int $distinct_id the distinct_id or alias of a user
|
72 |
+
* @param $prop string the property to increment
|
73 |
+
* @param int $val the amount to increment the property by
|
74 |
+
* @param string|null $ip the ip address of the client (used for geo-location)
|
75 |
+
* @param boolean $ignore_time If the $ignore_time property is true, Mixpanel will not automatically update the "Last Seen" property of the profile. Otherwise, Mixpanel will add a "Last Seen" property associated with the current time
|
76 |
+
*/
|
77 |
+
public function increment($distinct_id, $prop, $val, $ip = null, $ignore_time = false) {
|
78 |
+
$payload = $this->_constructPayload($distinct_id, '$add', array("$prop" => $val), $ip, $ignore_time);
|
79 |
+
$this->enqueue($payload);
|
80 |
+
}
|
81 |
+
|
82 |
+
/**
|
83 |
+
* Adds $val to a list located at $prop. If the property does not exist, it will be created. If $val is a string
|
84 |
+
* and the list is empty or does not exist, a new list with one value will be created.
|
85 |
+
* @param string|int $distinct_id the distinct_id or alias of a user
|
86 |
+
* @param string $prop the property that holds the list
|
87 |
+
* @param string|array $val items to add to the list
|
88 |
+
* @param string|null $ip the ip address of the client (used for geo-location)
|
89 |
+
* @param boolean $ignore_time If the $ignore_time property is true, Mixpanel will not automatically update the "Last Seen" property of the profile. Otherwise, Mixpanel will add a "Last Seen" property associated with the current time
|
90 |
+
*/
|
91 |
+
public function append($distinct_id, $prop, $val, $ip = null, $ignore_time = false) {
|
92 |
+
$operation = gettype($val) == "array" ? '$union' : '$append';
|
93 |
+
$payload = $this->_constructPayload($distinct_id, $operation, array("$prop" => $val), $ip, $ignore_time);
|
94 |
+
$this->enqueue($payload);
|
95 |
+
}
|
96 |
+
|
97 |
+
/**
|
98 |
+
* Adds a transaction to the user's profile for revenue tracking
|
99 |
+
* @param string|int $distinct_id the distinct_id or alias of a user
|
100 |
+
* @param string $amount the transaction amount e.g. "20.50"
|
101 |
+
* @param null $timestamp the timestamp of when the transaction occurred (default to current timestamp)
|
102 |
+
* @param string|null $ip the ip address of the client (used for geo-location)
|
103 |
+
* @param boolean $ignore_time If the $ignore_time property is true, Mixpanel will not automatically update the "Last Seen" property of the profile. Otherwise, Mixpanel will add a "Last Seen" property associated with the current time
|
104 |
+
*/
|
105 |
+
public function trackCharge($distinct_id, $amount, $timestamp = null, $ip = null, $ignore_time = false) {
|
106 |
+
$timestamp = $timestamp == null ? time() : $timestamp;
|
107 |
+
$date_iso = date("c", $timestamp);
|
108 |
+
$transaction = array(
|
109 |
+
'$time' => $date_iso,
|
110 |
+
'$amount' => $amount
|
111 |
+
);
|
112 |
+
$val = array('$transactions' => $transaction);
|
113 |
+
$payload = $this->_constructPayload($distinct_id, '$append', $val, $ip, $ignore_time);
|
114 |
+
$this->enqueue($payload);
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Clear all transactions stored on a user's profile
|
119 |
+
* @param string|int $distinct_id the distinct_id or alias of a user
|
120 |
+
* @param string|null $ip the ip address of the client (used for geo-location)
|
121 |
+
* @param boolean $ignore_time If the $ignore_time property is true, Mixpanel will not automatically update the "Last Seen" property of the profile. Otherwise, Mixpanel will add a "Last Seen" property associated with the current time
|
122 |
+
*/
|
123 |
+
public function clearCharges($distinct_id, $ip = null, $ignore_time = false) {
|
124 |
+
$payload = $this->_constructPayload($distinct_id, '$set', array('$transactions' => array()), $ip, $ignore_time);
|
125 |
+
$this->enqueue($payload);
|
126 |
+
}
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Delete this profile from Mixpanel
|
130 |
+
* @param string|int $distinct_id the distinct_id or alias of a user
|
131 |
+
* @param string|null $ip the ip address of the client (used for geo-location)
|
132 |
+
* @param boolean $ignore_time If the $ignore_time property is true, Mixpanel will not automatically update the "Last Seen" property of the profile. Otherwise, Mixpanel will add a "Last Seen" property associated with the current time
|
133 |
+
*/
|
134 |
+
public function deleteUser($distinct_id, $ip = null, $ignore_time = false) {
|
135 |
+
$payload = $this->_constructPayload($distinct_id, '$delete', "", $ip, $ignore_time);
|
136 |
+
$this->enqueue($payload);
|
137 |
+
}
|
138 |
+
|
139 |
+
/**
|
140 |
+
* Returns the "engage" endpoint
|
141 |
+
* @return string
|
142 |
+
*/
|
143 |
+
function _getEndpoint() {
|
144 |
+
return $this->_options['people_endpoint'];
|
145 |
+
}
|
146 |
+
|
147 |
+
}
|
mixpanel/phpunit.xml.dist
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
2 |
+
|
3 |
+
<phpunit backupGlobals="false"
|
4 |
+
backupStaticAttributes="false"
|
5 |
+
bootstrap="vendor/autoload.php"
|
6 |
+
cacheTokens="true"
|
7 |
+
colors="false"
|
8 |
+
convertErrorsToExceptions="true"
|
9 |
+
convertNoticesToExceptions="true"
|
10 |
+
convertWarningsToExceptions="true"
|
11 |
+
processIsolation="false"
|
12 |
+
stopOnFailure="false"
|
13 |
+
syntaxCheck="false"
|
14 |
+
verbose="false">
|
15 |
+
|
16 |
+
<testsuites>
|
17 |
+
<testsuite name="Mixpanel Test">
|
18 |
+
<directory>./test/</directory>
|
19 |
+
</testsuite>
|
20 |
+
</testsuites>
|
21 |
+
|
22 |
+
<filter>
|
23 |
+
<blacklist>
|
24 |
+
<directory>examples</directory>
|
25 |
+
<directory>vendor</directory>
|
26 |
+
<directory>test</directory>
|
27 |
+
</blacklist>
|
28 |
+
</filter>
|
29 |
+
</phpunit>
|
mixpanel/test/Base/MixpanelBaseProducerTest.php
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class MixpanelBaseProducerTest extends PHPUnit_Framework_TestCase {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @var _Producers_MixpanelBaseProducer
|
7 |
+
*/
|
8 |
+
protected $_instance = null;
|
9 |
+
protected $_file = null;
|
10 |
+
protected function setUp() {
|
11 |
+
parent::setUp();
|
12 |
+
$this->_file = dirname(__FILE__)."/output-".time().".txt";
|
13 |
+
$this->_instance = new _Producers_MixpanelBaseProducer("token", array("consumer" => "file", "debug" => true, "file" => $this->_file));
|
14 |
+
}
|
15 |
+
|
16 |
+
protected function tearDown() {
|
17 |
+
parent::tearDown();
|
18 |
+
$this->_instance->reset();
|
19 |
+
$this->_instance = null;
|
20 |
+
@unlink($this->_file);
|
21 |
+
}
|
22 |
+
|
23 |
+
public function testTokenMatch() {
|
24 |
+
$this->assertEquals("token", $this->_instance->getToken());
|
25 |
+
}
|
26 |
+
|
27 |
+
public function testFlush() {
|
28 |
+
$event1 = array("event" => "test", "properties" => array("prop1" => "val1"));
|
29 |
+
$event2 = array("event" => "test2", "properties" => array("prop2" => "val2"));
|
30 |
+
$this->_instance->enqueue($event1);
|
31 |
+
$this->_instance->enqueue($event2);
|
32 |
+
$this->_instance->flush(1);
|
33 |
+
$contents = file_get_contents($this->_file);
|
34 |
+
$this->assertEquals('[{"event":"test","properties":{"prop1":"val1"}}]'."\n".
|
35 |
+
'[{"event":"test2","properties":{"prop2":"val2"}}]'."\n", $contents);
|
36 |
+
}
|
37 |
+
|
38 |
+
public function testReset() {
|
39 |
+
$event1 = array("event" => "test", "properties" => array("prop1" => "val1"));
|
40 |
+
$this->_instance->enqueue($event1);
|
41 |
+
$this->_instance->reset();
|
42 |
+
$this->assertEmpty($this->_instance->getQueue());
|
43 |
+
}
|
44 |
+
|
45 |
+
public function testEnqueue() {
|
46 |
+
$this->_instance->reset();
|
47 |
+
$event1 = array("event" => "test", "properties" => array("prop1" => "val1"));
|
48 |
+
$this->_instance->enqueue($event1);
|
49 |
+
$queue = $this->_instance->getQueue();
|
50 |
+
$this->assertCount(1, $queue);
|
51 |
+
$this->assertEquals($event1, $queue[0]);
|
52 |
+
}
|
53 |
+
|
54 |
+
public function testEnqueueAll() {
|
55 |
+
$this->_instance->reset();
|
56 |
+
$event1 = array("event" => "test", "properties" => array("prop1" => "val1"));
|
57 |
+
$event2 = array("event" => "test2", "properties" => array("prop1" => "val1"));
|
58 |
+
$events = array($event1, $event2);
|
59 |
+
$this->_instance->enqueueAll($events);
|
60 |
+
$queue = $this->_instance->getQueue();
|
61 |
+
$this->assertCount(2, $queue);
|
62 |
+
$this->assertEquals($event1, $queue[0]);
|
63 |
+
$this->assertEquals($event2, $queue[1]);
|
64 |
+
}
|
65 |
+
|
66 |
+
public function testSetMaxQueueSize() {
|
67 |
+
$this->_instance->enqueue(array("event" => "test"));
|
68 |
+
$queue = $this->_instance->getQueue();
|
69 |
+
$this->assertEquals(1, count($queue));
|
70 |
+
$this->_instance->flush();
|
71 |
+
$new_instance = new Producers_MixpanelEvents("token", array('max_queue_size' => 0));
|
72 |
+
$new_instance->track("test");
|
73 |
+
$queue = $new_instance->getQueue();
|
74 |
+
$this->assertEquals(0, count($queue));
|
75 |
+
}
|
76 |
+
}
|
77 |
+
|
78 |
+
// stub for tests
|
79 |
+
class _Producers_MixpanelBaseProducer extends Producers_MixpanelBaseProducer {
|
80 |
+
function _getEndpoint() {
|
81 |
+
}
|
82 |
+
}
|
mixpanel/test/ConsumerStrategies/AbstractConsumerTest.php
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ConsumerStrategies_AbstractConsumerTest extends PHPUnit_Framework_TestCase {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @var AbstractConsumer
|
7 |
+
*/
|
8 |
+
protected $_instance = null;
|
9 |
+
|
10 |
+
protected function setUp()
|
11 |
+
{
|
12 |
+
parent::setUp();
|
13 |
+
$this->_instance = new AbstractConsumer();
|
14 |
+
}
|
15 |
+
|
16 |
+
protected function tearDown()
|
17 |
+
{
|
18 |
+
parent::tearDown();
|
19 |
+
$this->_instance = null;
|
20 |
+
}
|
21 |
+
|
22 |
+
public function test_encode() {
|
23 |
+
$encoded = base64_encode(json_encode(array("1" => "one")));
|
24 |
+
$this->assertEquals($encoded, $this->_instance->encode(array("1" => "one")));
|
25 |
+
}
|
26 |
+
|
27 |
+
}
|
28 |
+
|
29 |
+
class AbstractConsumer extends ConsumerStrategies_AbstractConsumer {
|
30 |
+
/**
|
31 |
+
* Persist a batch of messages in whatever way the implementer sees fit
|
32 |
+
* @param array $batch an array of messages to consume
|
33 |
+
* @return boolean success or fail
|
34 |
+
*/
|
35 |
+
function persist($batch)
|
36 |
+
{
|
37 |
+
// TODO: Implement persist() method.
|
38 |
+
}
|
39 |
+
|
40 |
+
function encode($msg) {
|
41 |
+
return $this->_encode($msg);
|
42 |
+
}
|
43 |
+
|
44 |
+
}
|
45 |
+
|
mixpanel/test/ConsumerStrategies/CurlConsumerTest.php
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ConsumerStrategies_CurlConsumerTest extends PHPUnit_Framework_TestCase {
|
4 |
+
|
5 |
+
public function testSettings() {
|
6 |
+
$consumer = new CurlConsumer(array(
|
7 |
+
"host" => "localhost",
|
8 |
+
"endpoint" => "/endpoint",
|
9 |
+
"timeout" => 2,
|
10 |
+
"use_ssl" => false,
|
11 |
+
"fork" => false
|
12 |
+
));
|
13 |
+
|
14 |
+
$this->assertEquals("localhost", $consumer->getHost());
|
15 |
+
$this->assertEquals("/endpoint", $consumer->getEndpoint());
|
16 |
+
$this->assertEquals(2, $consumer->getTimeout());
|
17 |
+
$this->assertEquals("http", $consumer->getProtocol());
|
18 |
+
$this->assertEquals(false, $consumer->getFork());
|
19 |
+
}
|
20 |
+
|
21 |
+
public function testBlocking() {
|
22 |
+
$consumer = new CurlConsumer(array(
|
23 |
+
"host" => "localhost",
|
24 |
+
"endpoint" => "/endpoint",
|
25 |
+
"timeout" => 2,
|
26 |
+
"use_ssl" => true,
|
27 |
+
"fork" => false
|
28 |
+
));
|
29 |
+
$consumer->persist(array("msg"));
|
30 |
+
$this->assertEquals(1, $consumer->blockingCalls);
|
31 |
+
}
|
32 |
+
|
33 |
+
public function testForked() {
|
34 |
+
$consumer = new CurlConsumer(array(
|
35 |
+
"host" => "localhost",
|
36 |
+
"endpoint" => "/endpoint",
|
37 |
+
"timeout" => 2,
|
38 |
+
"use_ssl" => true,
|
39 |
+
"fork" => true
|
40 |
+
));
|
41 |
+
$consumer->persist(array("msg"));
|
42 |
+
$this->assertEquals(1, $consumer->forkedCalls);
|
43 |
+
}
|
44 |
+
|
45 |
+
public function testExecuteCurlFailure() {
|
46 |
+
$error_handler = new ErrorHandler();
|
47 |
+
$consumer = new ConsumerStrategies_CurlConsumer(array(
|
48 |
+
"host" => "some.domain.that.should.not.ever.exist.tld",
|
49 |
+
"endpoint" => "/endpoint",
|
50 |
+
"timeout" => 2,
|
51 |
+
"use_ssl" => false,
|
52 |
+
"fork" => false,
|
53 |
+
"error_callback" => array($error_handler, 'handle_error')
|
54 |
+
));
|
55 |
+
$resp = $consumer->persist(array("msg"));
|
56 |
+
$this->assertFalse($resp);
|
57 |
+
$this->assertEquals($error_handler->last_code, CURLE_COULDNT_RESOLVE_HOST);
|
58 |
+
}
|
59 |
+
|
60 |
+
public function testOptions() {
|
61 |
+
function callback() { }
|
62 |
+
|
63 |
+
$consumer = new ConsumerStrategies_CurlConsumer(array(
|
64 |
+
"host" => "localhost",
|
65 |
+
"endpoint" => "/endpoint",
|
66 |
+
"timeout" => 2,
|
67 |
+
"connect_timeout" => 1,
|
68 |
+
"use_ssl" => true,
|
69 |
+
"fork" => false,
|
70 |
+
"error_callback" => 'callback'
|
71 |
+
));
|
72 |
+
|
73 |
+
$this->assertEquals($consumer->getHost(), "localhost");
|
74 |
+
$this->assertEquals($consumer->getEndpoint(), "/endpoint");
|
75 |
+
$this->assertEquals($consumer->getTimeout(), 2);
|
76 |
+
$this->assertEquals($consumer->getConnectTimeout(), 1);
|
77 |
+
$this->assertEquals($consumer->getProtocol(), "https");
|
78 |
+
}
|
79 |
+
|
80 |
+
}
|
81 |
+
|
82 |
+
class ErrorHandler {
|
83 |
+
public $last_code = -1;
|
84 |
+
public $last_msg = "";
|
85 |
+
|
86 |
+
public function handle_error($code, $msg) {
|
87 |
+
$this->last_code = $code;
|
88 |
+
$this->last_msg = $msg;
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
class CurlConsumer extends ConsumerStrategies_CurlConsumer {
|
93 |
+
|
94 |
+
public $forkedCalls = 0;
|
95 |
+
public $blockingCalls = 0;
|
96 |
+
|
97 |
+
/**
|
98 |
+
* @return string
|
99 |
+
*/
|
100 |
+
public function getEndpoint()
|
101 |
+
{
|
102 |
+
return $this->_endpoint;
|
103 |
+
}
|
104 |
+
|
105 |
+
/**
|
106 |
+
* @return bool|null
|
107 |
+
*/
|
108 |
+
public function getFork()
|
109 |
+
{
|
110 |
+
return $this->_fork;
|
111 |
+
}
|
112 |
+
|
113 |
+
/**
|
114 |
+
* @return string
|
115 |
+
*/
|
116 |
+
public function getHost()
|
117 |
+
{
|
118 |
+
return $this->_host;
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* @return string
|
123 |
+
*/
|
124 |
+
public function getProtocol()
|
125 |
+
{
|
126 |
+
return $this->_protocol;
|
127 |
+
}
|
128 |
+
|
129 |
+
/**
|
130 |
+
* @return int
|
131 |
+
*/
|
132 |
+
public function getTimeout()
|
133 |
+
{
|
134 |
+
return $this->_timeout;
|
135 |
+
}
|
136 |
+
|
137 |
+
protected function _execute($url, $data)
|
138 |
+
{
|
139 |
+
$this->blockingCalls++;
|
140 |
+
return parent::_execute($url, $data);
|
141 |
+
}
|
142 |
+
|
143 |
+
protected function _execute_forked($url, $data)
|
144 |
+
{
|
145 |
+
$this->forkedCalls++;
|
146 |
+
return parent::_execute_forked($url, $data);
|
147 |
+
}
|
148 |
+
|
149 |
+
}
|
mixpanel/test/ConsumerStrategies/FileConsumerTest.php
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ConsumerStrategies_FileConsumerTest extends PHPUnit_Framework_TestCase {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @var ConsumerStrategies_FileConsumer
|
7 |
+
*/
|
8 |
+
protected $_instance = null;
|
9 |
+
protected $_file = null;
|
10 |
+
protected function setUp()
|
11 |
+
{
|
12 |
+
parent::setUp();
|
13 |
+
$this->_file = dirname(__FILE__)."/output-".time().".txt";
|
14 |
+
$this->_instance = new ConsumerStrategies_FileConsumer(array("file" => $this->_file));
|
15 |
+
}
|
16 |
+
|
17 |
+
protected function tearDown()
|
18 |
+
{
|
19 |
+
parent::tearDown();
|
20 |
+
$this->_instance = null;
|
21 |
+
@unlink($this->_file);
|
22 |
+
}
|
23 |
+
|
24 |
+
public function testPersist() {
|
25 |
+
$this->_instance->persist(array("msg"));
|
26 |
+
$contents = file_get_contents($this->_file);
|
27 |
+
$this->assertEquals('["msg"]'."\n", $contents);
|
28 |
+
}
|
29 |
+
|
30 |
+
}
|
mixpanel/test/ConsumerStrategies/SocketConsumerTest.php
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ConsumerStrategies_SocketConsumerTest extends PHPUnit_Framework_TestCase {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @var ConsumerStrategies_SocketConsumer
|
7 |
+
*/
|
8 |
+
protected $_instance = null;
|
9 |
+
protected $_file = null;
|
10 |
+
protected function setUp()
|
11 |
+
{
|
12 |
+
parent::setUp();
|
13 |
+
$this->_instance = new ConsumerStrategies_SocketConsumer(array(
|
14 |
+
"host" => "localhost",
|
15 |
+
"endpoint" => "/endpoint",
|
16 |
+
"timeout" => 2,
|
17 |
+
"use_ssl" => false
|
18 |
+
));
|
19 |
+
}
|
20 |
+
|
21 |
+
protected function tearDown()
|
22 |
+
{
|
23 |
+
parent::tearDown();
|
24 |
+
$this->_instance = null;
|
25 |
+
}
|
26 |
+
|
27 |
+
public function testPersist() {
|
28 |
+
|
29 |
+
}
|
30 |
+
|
31 |
+
|
32 |
+
}
|
mixpanel/test/MixpanelTest.php
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class MixpanelTest extends PHPUnit_Framework_TestCase {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @var Mixpanel
|
7 |
+
*/
|
8 |
+
protected $_instance = null;
|
9 |
+
|
10 |
+
protected function setUp() {
|
11 |
+
parent::setUp();
|
12 |
+
$this->_instance = Mixpanel::getInstance("token");
|
13 |
+
}
|
14 |
+
|
15 |
+
protected function tearDown() {
|
16 |
+
parent::tearDown();
|
17 |
+
$this->_instance->reset();
|
18 |
+
$this->_instance = null;
|
19 |
+
}
|
20 |
+
|
21 |
+
public function testGetInstance() {
|
22 |
+
$instance = Mixpanel::getInstance("token");
|
23 |
+
$this->assertInstanceOf("Mixpanel", $instance);
|
24 |
+
$this->assertEquals($this->_instance, $instance);
|
25 |
+
$this->assertInstanceOf("Producers_MixpanelPeople", $this->_instance->people);
|
26 |
+
}
|
27 |
+
|
28 |
+
}
|
29 |
+
|
mixpanel/test/Producers/MixpanelEventsProducerTest.php
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class MixpanelEventsProducerTest extends PHPUnit_Framework_TestCase {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @var Producers_MixpanelEvents
|
7 |
+
*/
|
8 |
+
protected $_instance = null;
|
9 |
+
|
10 |
+
protected function setUp()
|
11 |
+
{
|
12 |
+
parent::setUp();
|
13 |
+
$this->_instance = new Producers_MixpanelEvents("token");
|
14 |
+
}
|
15 |
+
|
16 |
+
protected function tearDown()
|
17 |
+
{
|
18 |
+
parent::tearDown();
|
19 |
+
$this->_instance->reset();
|
20 |
+
$this->_instance = null;
|
21 |
+
}
|
22 |
+
|
23 |
+
|
24 |
+
public function testTrack() {
|
25 |
+
$this->_instance->track("test_event", array("number" => 1));
|
26 |
+
$queue = $this->_instance->getQueue();
|
27 |
+
$this->assertEquals(1, count($queue));
|
28 |
+
$this->assertEquals("test_event", $queue[0]['event']);
|
29 |
+
$this->assertEquals(1, $queue[0]['properties']['number']);
|
30 |
+
}
|
31 |
+
|
32 |
+
public function testRegister() {
|
33 |
+
$this->_instance->register("super_property", "super_value");
|
34 |
+
$this->assertEquals("super_value", $this->_instance->getProperty("super_property"));
|
35 |
+
}
|
36 |
+
|
37 |
+
|
38 |
+
public function testRegisterAll() {
|
39 |
+
$this->_instance->registerAll(array("prop1" => "val1", "prop2" => "val2"));
|
40 |
+
$this->assertEquals("val1", $this->_instance->getProperty("prop1"));
|
41 |
+
$this->assertEquals("val2", $this->_instance->getProperty("prop2"));
|
42 |
+
}
|
43 |
+
|
44 |
+
public function testRegisterOnce() {
|
45 |
+
$this->_instance->registerOnce("prop3", "val3");
|
46 |
+
$this->_instance->registerOnce("prop3", "val4");
|
47 |
+
$this->assertEquals("val3", $this->_instance->getProperty("prop3"));
|
48 |
+
}
|
49 |
+
|
50 |
+
public function testRegisterAllOnce() {
|
51 |
+
$this->_instance->registerAllOnce(array("prop5" => "val5", "prop6" => "val6"));
|
52 |
+
$this->_instance->registerAllOnce(array("prop5" => "val6", "prop6" => "val7"));
|
53 |
+
$this->assertEquals("val5", $this->_instance->getProperty("prop5"));
|
54 |
+
$this->assertEquals("val6", $this->_instance->getProperty("prop6"));
|
55 |
+
}
|
56 |
+
|
57 |
+
public function unregister() {
|
58 |
+
$this->_instance->register("prop7", "val7");
|
59 |
+
$this->_instance->register("prop8", "val8");
|
60 |
+
$this->assertEquals("val7", $this->_instance->getProperty("prop7"));
|
61 |
+
$this->assertEquals("val8", $this->_instance->getProperty("prop8"));
|
62 |
+
$this->_instance->unregister("prop7");
|
63 |
+
$this->assertEquals(null, $this->_instance->getProperty("prop7"));
|
64 |
+
$this->assertEquals("val8", $this->_instance->getProperty("prop8"));
|
65 |
+
}
|
66 |
+
|
67 |
+
public function unregisterAll() {
|
68 |
+
$this->_instance->registerAll(array("prop9" => "val9", "prop10" => "val10"));
|
69 |
+
$this->assertEquals("val9", $this->_instance->getProperty("prop9"));
|
70 |
+
$this->assertEquals("val10", $this->_instance->getProperty("prop10"));
|
71 |
+
$this->assertEquals("val11", $this->_instance->getProperty("prop11"));
|
72 |
+
$this->_instance->unregisterAll(array("prop9", "prop10"));
|
73 |
+
$this->assertEquals(null, $this->_instance->getProperty("prop9"));
|
74 |
+
$this->assertEquals(null, $this->_instance->getProperty("prop10"));
|
75 |
+
$this->assertEquals("val11", $this->_instance->getProperty("prop11"));
|
76 |
+
}
|
77 |
+
|
78 |
+
public function testCreateAlias() {
|
79 |
+
$original_id = 1;
|
80 |
+
$new_id = 2;
|
81 |
+
$msg = $this->_instance->createAlias($original_id, $new_id);
|
82 |
+
$this->assertEquals('$create_alias', $msg['event']);
|
83 |
+
$this->assertEquals($original_id, $msg['properties']['distinct_id']);
|
84 |
+
$this->assertEquals($new_id, $msg['properties']['alias']);
|
85 |
+
}
|
86 |
+
}
|
mixpanel/test/Producers/MixpanelPeopleProducerTest.php
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class MixpanelPeopleProducerTest extends PHPUnit_Framework_TestCase {
|
4 |
+
|
5 |
+
/**
|
6 |
+
* @var Producers_MixpanelPeople
|
7 |
+
*/
|
8 |
+
protected $_instance = null;
|
9 |
+
|
10 |
+
protected function setUp()
|
11 |
+
{
|
12 |
+
parent::setUp();
|
13 |
+
$this->_instance = new Producers_MixpanelPeople("token");
|
14 |
+
}
|
15 |
+
|
16 |
+
protected function tearDown()
|
17 |
+
{
|
18 |
+
parent::tearDown();
|
19 |
+
$this->_instance->reset();
|
20 |
+
$this->_instance = null;
|
21 |
+
}
|
22 |
+
|
23 |
+
public function testSet() {
|
24 |
+
$this->_instance->set(12345, array("name" => "John"), "192.168.0.1");
|
25 |
+
$queue = $this->_instance->getQueue();
|
26 |
+
$msg = $queue[count($queue)-1];
|
27 |
+
|
28 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
29 |
+
$this->assertEquals("token", $msg['$token']);
|
30 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
31 |
+
$this->assertArrayNotHasKey('$ignore_time', $msg);
|
32 |
+
$this->assertArrayHasKey('$set', $msg);
|
33 |
+
$this->assertArrayHasKey("name", $msg['$set']);
|
34 |
+
$this->assertEquals("John", $msg['$set']['name']);
|
35 |
+
}
|
36 |
+
|
37 |
+
public function testSetIgnoreTime() {
|
38 |
+
$this->_instance->set(12345, array("name" => "John"), "192.168.0.1", true);
|
39 |
+
$queue = $this->_instance->getQueue();
|
40 |
+
$msg = $queue[count($queue)-1];
|
41 |
+
|
42 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
43 |
+
$this->assertEquals("token", $msg['$token']);
|
44 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
45 |
+
$this->assertEquals(true, $msg['$ignore_time']);
|
46 |
+
$this->assertArrayHasKey('$set', $msg);
|
47 |
+
$this->assertArrayHasKey("name", $msg['$set']);
|
48 |
+
$this->assertEquals("John", $msg['$set']['name']);
|
49 |
+
}
|
50 |
+
|
51 |
+
|
52 |
+
public function testSetOnce() {
|
53 |
+
$this->_instance->setOnce(12345, array("name" => "John"), "192.168.0.1");
|
54 |
+
$queue = $this->_instance->getQueue();
|
55 |
+
$msg = $queue[count($queue)-1];
|
56 |
+
|
57 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
58 |
+
$this->assertEquals("token", $msg['$token']);
|
59 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
60 |
+
$this->assertArrayHasKey('$set_once', $msg);
|
61 |
+
$this->assertArrayHasKey("name", $msg['$set_once']);
|
62 |
+
$this->assertEquals("John", $msg['$set_once']['name']);
|
63 |
+
}
|
64 |
+
|
65 |
+
public function testIncrement() {
|
66 |
+
$this->_instance->increment(12345, "logins", 1, "192.168.0.1");
|
67 |
+
$queue = $this->_instance->getQueue();
|
68 |
+
$msg = $queue[count($queue)-1];
|
69 |
+
|
70 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
71 |
+
$this->assertEquals("token", $msg['$token']);
|
72 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
73 |
+
$this->assertArrayHasKey('$add', $msg);
|
74 |
+
$this->assertArrayHasKey("logins", $msg['$add']);
|
75 |
+
$this->assertEquals(1, $msg['$add']['logins']);
|
76 |
+
}
|
77 |
+
|
78 |
+
public function testAppendSingle() {
|
79 |
+
$this->_instance->append(12345, "actions", "Logged In", "192.168.0.1");
|
80 |
+
$queue = $this->_instance->getQueue();
|
81 |
+
$msg = $queue[count($queue)-1];
|
82 |
+
|
83 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
84 |
+
$this->assertEquals("token", $msg['$token']);
|
85 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
86 |
+
$this->assertArrayHasKey('$append', $msg);
|
87 |
+
$this->assertArrayHasKey("actions", $msg['$append']);
|
88 |
+
$this->assertEquals("Logged In", $msg['$append']['actions']);
|
89 |
+
}
|
90 |
+
|
91 |
+
public function testAppendMultiple() {
|
92 |
+
$this->_instance->append(12345, "actions", array("Logged In", "Logged Out"), "192.168.0.1");
|
93 |
+
$queue = $this->_instance->getQueue();
|
94 |
+
$msg = $queue[count($queue)-1];
|
95 |
+
|
96 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
97 |
+
$this->assertEquals("token", $msg['$token']);
|
98 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
99 |
+
$this->assertArrayHasKey('$union', $msg);
|
100 |
+
$this->assertArrayHasKey("actions", $msg['$union']);
|
101 |
+
$this->assertEquals(array("Logged In", "Logged Out"), $msg['$union']['actions']);
|
102 |
+
}
|
103 |
+
|
104 |
+
public function testTrackCharge() {
|
105 |
+
date_default_timezone_set("America/Los_Angeles");
|
106 |
+
$time = time();
|
107 |
+
$this->_instance->trackCharge(12345, "20.00", $time, "192.168.0.1");
|
108 |
+
$queue = $this->_instance->getQueue();
|
109 |
+
$msg = $queue[count($queue)-1];
|
110 |
+
|
111 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
112 |
+
$this->assertEquals("token", $msg['$token']);
|
113 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
114 |
+
$this->assertArrayHasKey('$append', $msg);
|
115 |
+
$this->assertArrayHasKey('$transactions', $msg['$append']);
|
116 |
+
$this->assertArrayHasKey('$amount', $msg['$append']['$transactions']);
|
117 |
+
$this->assertArrayHasKey('$time', $msg['$append']['$transactions']);
|
118 |
+
$this->assertEquals("20.00", $msg['$append']['$transactions']['$amount']);
|
119 |
+
$this->assertEquals(date("c", $time), $msg['$append']['$transactions']['$time']);
|
120 |
+
}
|
121 |
+
|
122 |
+
public function testClearCharges() {
|
123 |
+
$this->_instance->clearCharges(12345,"192.168.0.1");
|
124 |
+
$queue = $this->_instance->getQueue();
|
125 |
+
$msg = $queue[count($queue)-1];
|
126 |
+
|
127 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
128 |
+
$this->assertEquals("token", $msg['$token']);
|
129 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
130 |
+
$this->assertArrayHasKey('$set', $msg);
|
131 |
+
$this->assertArrayHasKey('$transactions', $msg['$set']);
|
132 |
+
$this->assertSameSize(array(), $msg['$set']['$transactions']);
|
133 |
+
}
|
134 |
+
|
135 |
+
public function testDeleteUser() {
|
136 |
+
$this->_instance->deleteUser(12345,"192.168.0.1");
|
137 |
+
$queue = $this->_instance->getQueue();
|
138 |
+
$msg = $queue[count($queue)-1];
|
139 |
+
|
140 |
+
$this->assertEquals(12345, $msg['$distinct_id']);
|
141 |
+
$this->assertEquals("token", $msg['$token']);
|
142 |
+
$this->assertEquals("192.168.0.1", $msg['$ip']);
|
143 |
+
$this->assertArrayHasKey('$delete', $msg);
|
144 |
+
$this->assertEquals("", $msg['$delete']);
|
145 |
+
}
|
146 |
+
}
|
readme.txt
CHANGED
@@ -3,8 +3,8 @@ Contributors: sovrn, zemanta
|
|
3 |
Tags: related,posts,post,related posts,plugin,seo,sovrn
|
4 |
License: GPLv2
|
5 |
Requires at least: 3.6
|
6 |
-
Tested up to: 4.
|
7 |
-
Stable tag: 3.6.
|
8 |
|
9 |
WordPress Related Posts - the plugin for related posts with thumbnails. Caching included.
|
10 |
|
@@ -21,6 +21,10 @@ After installation, go to Settings -> Related Posts in your plugins list and Tur
|
|
21 |
|
22 |
== Frequently Asked Questions ==
|
23 |
|
|
|
|
|
|
|
|
|
24 |
= What does this plugin do? =
|
25 |
|
26 |
It places a list of links with thumbnails at the bottom of your posts. It increases page views and engages readers to stay on your site longer and discover more of your content. It also recommends related posts from across the web and in turn promotes your posts to other users. In this way you get new unique visitors to your site and improve SEO.
|
@@ -89,6 +93,10 @@ Fix for security vulnerability. Upgrade immediately.
|
|
89 |
|
90 |
== Changelog ==
|
91 |
|
|
|
|
|
|
|
|
|
92 |
= 3.6.3 =
|
93 |
* Rebrand
|
94 |
|
3 |
Tags: related,posts,post,related posts,plugin,seo,sovrn
|
4 |
License: GPLv2
|
5 |
Requires at least: 3.6
|
6 |
+
Tested up to: 4.7
|
7 |
+
Stable tag: 3.6.4
|
8 |
|
9 |
WordPress Related Posts - the plugin for related posts with thumbnails. Caching included.
|
10 |
|
21 |
|
22 |
== Frequently Asked Questions ==
|
23 |
|
24 |
+
= What php version does this plugin support? =
|
25 |
+
|
26 |
+
We currently only support php versions 5.4 or greater.
|
27 |
+
|
28 |
= What does this plugin do? =
|
29 |
|
30 |
It places a list of links with thumbnails at the bottom of your posts. It increases page views and engages readers to stay on your site longer and discover more of your content. It also recommends related posts from across the web and in turn promotes your posts to other users. In this way you get new unique visitors to your site and improve SEO.
|
93 |
|
94 |
== Changelog ==
|
95 |
|
96 |
+
= 3.6.4 =
|
97 |
+
* Added error and app performance monitoring to help us fix bugs and improve performance.
|
98 |
+
* Requires php 5.4 or greater.
|
99 |
+
|
100 |
= 3.6.3 =
|
101 |
* Rebrand
|
102 |
|
static/img/arrow_down.png
CHANGED
Binary file
|
static/img/arrow_right.png
CHANGED
Binary file
|
static/img/check.png
CHANGED
Binary file
|
static/img/close.png
CHANGED
Binary file
|
static/img/cross.png
CHANGED
Binary file
|
static/img/cross_2x.png
CHANGED
Binary file
|
static/img/desktop_icon.png
CHANGED
Binary file
|
static/img/down.png
CHANGED
Binary file
|
static/img/icon_support.png
CHANGED
Binary file
|
static/img/measure_icon.png
CHANGED
Binary file
|
static/img/measure_icon_2x.png
CHANGED
Binary file
|
static/img/menu_icon.png
CHANGED
Binary file
|
static/img/menu_icon_2x.png
CHANGED
Binary file
|
static/img/mobile_icon.png
CHANGED
Binary file
|
static/img/network_icon.png
CHANGED
Binary file
|
static/img/outlink.png
CHANGED
Binary file
|
static/img/promoted_arrow.png
CHANGED
Binary file
|
static/img/turnonscreen.jpg
CHANGED
Binary file
|
static/img/up.png
CHANGED
Binary file
|
static/js/dashboard.js
CHANGED
@@ -1,4 +1,124 @@
|
|
1 |
-
(function(
|
2 |
-
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function ($) {
|
2 |
+
var template_eval = function (template, vars) {
|
3 |
+
$.each(vars, function (k, v) {
|
4 |
+
template = template.replace(new RegExp('{{ *' + k + ' *}}'), v);
|
5 |
+
});
|
6 |
+
return template;
|
7 |
+
};
|
8 |
+
|
9 |
+
$(function () {
|
10 |
+
var nonce = $('#wp_rp_ajax_nonce').val(),
|
11 |
+
logHabit = function (action, extras) {
|
12 |
+
var img = new Image(),
|
13 |
+
protocol = ("https:" === location.protocol ? "https://" : "http://"),
|
14 |
+
payload = {
|
15 |
+
'zem-habit-platform': 'wordpress-wprp',
|
16 |
+
'zem-habit-action': action,
|
17 |
+
'zem-habit-timestamp': (new Date()).getTime()
|
18 |
+
};
|
19 |
+
extras = extras || {};
|
20 |
+
payload = $.extend(extras, payload);
|
21 |
+
img.src = protocol + "eyepatch.zemanta.com/log/?" + object_to_query(payload);
|
22 |
+
},
|
23 |
+
ajaxCallSubscribe = function (email, subscriptions, successCallback) {
|
24 |
+
$.ajax({
|
25 |
+
url: ajaxurl,
|
26 |
+
data: {
|
27 |
+
action: 'wprp_subscribe',
|
28 |
+
subscription: subscriptions,
|
29 |
+
_wpnonce: nonce,
|
30 |
+
email: email || '0'
|
31 |
+
},
|
32 |
+
success: successCallback,
|
33 |
+
type: 'POST'
|
34 |
+
});
|
35 |
+
},
|
36 |
+
disableCustomCSS = function (evt) {
|
37 |
+
var disabled = $('#wp_rp_desktop_custom_theme_enabled:checked').length !== 1;
|
38 |
+
$('#wp_rp_desktop_theme_custom_css').prop("readonly", disabled);
|
39 |
+
},
|
40 |
+
turn_on_rp = function (button_type) {
|
41 |
+
var static_url = $('#wp_rp_static_base_url').val();
|
42 |
+
|
43 |
+
$('#wp_rp_ctr_dashboard_enabled, #wp_rp_enable_themes, #wp_rp_promoted_content_enabled, #wp_rp_traffic_exchange_enabled').prop('checked', true);
|
44 |
+
$('#wp_rp_settings_form').append('<input type="hidden" value="statistics+thumbnails+promoted" name="wp_rp_turn_on_button_pressed" id="wp_rp_turn_on_button_pressed">');
|
45 |
+
$('#wp_rp_settings_form').append('<input type="hidden" value="' + button_type + '" name="wp_rp_button_type" id="wp_rp_button_type">');
|
46 |
+
|
47 |
+
$('#wp_rp_settings_form').submit();
|
48 |
+
};
|
49 |
+
|
50 |
+
disableCustomCSS();
|
51 |
+
$('#wp_rp_desktop_custom_theme_enabled').click(disableCustomCSS);
|
52 |
+
|
53 |
+
if ($('#wp_rp_subscribe_email').length) {
|
54 |
+
if (! $('#wp_rp_subscribe_email').val().length) {
|
55 |
+
$('#wp_rp_unsubscribe_button').hide();
|
56 |
+
}
|
57 |
+
else {
|
58 |
+
$('#wp_rp_subscribe_button').hide();
|
59 |
+
}
|
60 |
+
}
|
61 |
+
|
62 |
+
$('#wp_rp_subscribe_button').on('click', function (evt) {
|
63 |
+
var email = $('#wp_rp_subscribe_email').val();
|
64 |
+
evt.preventDefault();
|
65 |
+
if (!email) { return; }
|
66 |
+
$('#wp_rp_subscribe_button').attr('disabled', true);
|
67 |
+
ajaxCallSubscribe(email, 'activityreport,newsletter', function (data) {
|
68 |
+
$('#wp_rp_subscribe_button').attr('disabled', false);
|
69 |
+
if (parseInt(data)) {
|
70 |
+
$('#wp_rp_subscribe_button').prop('disabled', false);
|
71 |
+
$('#wp_rp_subscribe_button').hide();
|
72 |
+
$('#wp_rp_unsubscribe_button').show();
|
73 |
+
alert('Subscription successful!');
|
74 |
+
}
|
75 |
+
});
|
76 |
+
});
|
77 |
+
|
78 |
+
$('#wp_rp_unsubscribe_button').on('click', function (evt) {
|
79 |
+
evt.preventDefault();
|
80 |
+
$('#wp_rp_unsubscribe_button').attr('disabled', true);
|
81 |
+
ajaxCallSubscribe(false, false, function (data) {
|
82 |
+
$('#wp_rp_unsubscribe_button').attr('disabled', false);
|
83 |
+
if (parseInt(data)) {
|
84 |
+
$('#wp_rp_subscribe_email').val('');
|
85 |
+
$('#wp_rp_unsubscribe_button').hide();
|
86 |
+
$('#wp_rp_subscribe_button').show();
|
87 |
+
}
|
88 |
+
});
|
89 |
+
});
|
90 |
+
|
91 |
+
// collapsible blocks implementation
|
92 |
+
$('#wp_rp_wrap .collapsible .collapse-handle').on('click', function (event) {
|
93 |
+
var wrapper = $(this).closest('.collapsible');
|
94 |
+
var container = wrapper.find('.container');
|
95 |
+
var is_collapsed = wrapper.hasClass('collapsed');
|
96 |
+
|
97 |
+
var block = wrapper.attr('block');
|
98 |
+
|
99 |
+
var callback = function() {
|
100 |
+
wrapper.toggleClass('collapsed');
|
101 |
+
};
|
102 |
+
|
103 |
+
if(is_collapsed) {
|
104 |
+
container.slideDown();
|
105 |
+
$.post(ajaxurl, { action: 'rp_show_hide_' + block, show: true, _wpnonce: nonce});
|
106 |
+
callback();
|
107 |
+
} else {
|
108 |
+
container.slideUp();
|
109 |
+
$.post(ajaxurl, { action: 'rp_show_hide_' + block, hide: true, _wpnonce: nonce});
|
110 |
+
callback();
|
111 |
+
}
|
112 |
+
|
113 |
+
if (block === 'statistics') {
|
114 |
+
logHabit('statistics_' + (is_collapsed ? 'on' : 'off'));
|
115 |
+
}
|
116 |
+
|
117 |
+
|
118 |
+
event.preventDefault();
|
119 |
+
});
|
120 |
+
|
121 |
+
});
|
122 |
+
}(jQuery));
|
123 |
+
|
124 |
+
|
static/js/edit_related_posts.js
CHANGED
@@ -1,23 +1,740 @@
|
|
1 |
-
(function
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
var
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function ($) {
|
2 |
+
/* enable drag and drop data in jquery events */
|
3 |
+
$.event.props.push('dataTransfer');
|
4 |
+
|
5 |
+
var related_posts = function () {
|
6 |
+
var conf = {
|
7 |
+
num_articles: 30,
|
8 |
+
num_articles_to_insert: window._wp_rp_num_rel_posts,
|
9 |
+
|
10 |
+
post_id: window._wp_rp_post_id,
|
11 |
+
|
12 |
+
admin_ajax_url: window._wp_rp_admin_ajax_url,
|
13 |
+
admin_ajax_action: 'rp_update_related_posts',
|
14 |
+
|
15 |
+
plugin_static_url: window._wp_rp_plugin_static_base_url,
|
16 |
+
zemanta_thumbnail_url: 'http://i.zemanta.com/{aid}_150_150.jpg',
|
17 |
+
num_default_thumbnails: 30,
|
18 |
+
|
19 |
+
search_support: !!window._wp_rp_erp_search,
|
20 |
+
|
21 |
+
promoted: window._wp_rp_promoted_content !== false,
|
22 |
+
tx: window._wp_rp_traffic_exchange !== false,
|
23 |
+
num_external_slots: 0
|
24 |
+
};
|
25 |
+
|
26 |
+
var elms = {
|
27 |
+
holder: null, wrapper: null,
|
28 |
+
search_form: null, search_input: null,
|
29 |
+
selected_articles_wrap: null,
|
30 |
+
replace_articles_wrap: null, replace_articles_list: null, article_loader: null,
|
31 |
+
article_list: {}, articles_to_insert: null,
|
32 |
+
footer: null, save: null
|
33 |
+
};
|
34 |
+
|
35 |
+
var articles = {
|
36 |
+
selected: {}, all: [], ids: {}, special: []
|
37 |
+
};
|
38 |
+
|
39 |
+
var paintWindow = function () {
|
40 |
+
elms.replace_articles_wrap.css('width', Math.min(elms.holder.width() - 142, Math.max(680, conf.num_articles_to_insert * 110 + 130)) + 'px');
|
41 |
+
};
|
42 |
+
|
43 |
+
var close_editor = function (ev) {
|
44 |
+
ev.preventDefault();
|
45 |
+
elms.holder.remove();
|
46 |
+
$('html').css('overflow', 'visible');
|
47 |
+
};
|
48 |
+
|
49 |
+
var init = function () {
|
50 |
+
elms.holder = $('<div id="wp_rp_zem_related_posts_holder"></div>');
|
51 |
+
|
52 |
+
elms.wrapper = $('<div id="wp_rp_zem_related_posts_wrap">' +
|
53 |
+
'<div class="selected-header">' +
|
54 |
+
'<h4 class="selected-title">Selected posts</h4>' +
|
55 |
+
'<a href="#" class="save button">Save and Close</a>' +
|
56 |
+
'</div>' +
|
57 |
+
'<div class="selected-content"></div>' +
|
58 |
+
'</div>');
|
59 |
+
elms.holder.append(elms.wrapper);
|
60 |
+
elms.wrapper.bind('click', function (ev) {
|
61 |
+
ev.stopPropagation();
|
62 |
+
});
|
63 |
+
|
64 |
+
elms.save = elms.wrapper.find('.save');
|
65 |
+
elms.save.bind('click', function () {
|
66 |
+
save_picked_articles(function () {
|
67 |
+
window.location.reload();
|
68 |
+
});
|
69 |
+
return false;
|
70 |
+
});
|
71 |
+
|
72 |
+
elms.selected_articles_wrap = elms.wrapper.find('.selected-content');
|
73 |
+
|
74 |
+
var ul = $('<ul class="selected" />');
|
75 |
+
for (var i = 0; i < conf.num_articles_to_insert; i += 1) {
|
76 |
+
var li = $('<li><div class="droppable" /><span class="notice">Drag post here</span></li>');
|
77 |
+
li.data('pos', i);
|
78 |
+
elms.article_list[i] = li;
|
79 |
+
ul.append(li);
|
80 |
+
}
|
81 |
+
elms.selected_articles_wrap.append(ul);
|
82 |
+
|
83 |
+
elms.replace_articles_wrap = $('<div id="wp_rp_replace_article_wrap">' +
|
84 |
+
'<div class="remove-article-sign">Drop article here to remove it</div>' +
|
85 |
+
'<div class="recommendations-header">' +
|
86 |
+
'<h4 class="recommendations-title">Recommended posts</h4>' +
|
87 |
+
(conf.search_support ? '<form class="search" action="#">' +
|
88 |
+
'<input placeholder="search" class="search" type="text" />' +
|
89 |
+
'<input class="go button" type="submit" value="go" />' +
|
90 |
+
'</form>' :
|
91 |
+
'<div class="search notice">Please upgrade the plugin to use search.</div>') +
|
92 |
+
'</div>' +
|
93 |
+
'<div class="content">' +
|
94 |
+
'<ul></ul>' +
|
95 |
+
'</div>' +
|
96 |
+
'<div class="footer"><a href="http://www.zemanta.com/?ref=edit-rp" target="_blank">zemanta.com</a></div>' +
|
97 |
+
'</div>');
|
98 |
+
elms.wrapper.append(elms.replace_articles_wrap);
|
99 |
+
|
100 |
+
elms.replace_articles_list = elms.replace_articles_wrap.find('.content ul');
|
101 |
+
|
102 |
+
elms.article_loader = $('<div class="zem-loader-wrap">' +
|
103 |
+
'<div class="zem-no-articles">No results.</div>' +
|
104 |
+
'<div class="zem-loader">' +
|
105 |
+
'<div class="zem-loader-step zem-loader-step-1"></div>' +
|
106 |
+
'<div class="zem-loader-step zem-loader-step-2"></div>' +
|
107 |
+
'<div class="zem-loader-step zem-loader-step-3"></div>' +
|
108 |
+
'</div>' +
|
109 |
+
'</div>');
|
110 |
+
elms.replace_articles_wrap.append(elms.article_loader);
|
111 |
+
|
112 |
+
elms.remove_article_sign = elms.replace_articles_wrap.find('.remove-article-sign');
|
113 |
+
elms.search_form = elms.replace_articles_wrap.find('form.search');
|
114 |
+
elms.search_input = elms.replace_articles_wrap.find('input.search');
|
115 |
+
elms.footer = elms.replace_articles_wrap.find('.footer');
|
116 |
+
|
117 |
+
// Close pop-up with esc key
|
118 |
+
elms.holder.bind('click', close_editor);
|
119 |
+
$(document).keydown(function(e) {
|
120 |
+
if (e.keyCode == 27) {
|
121 |
+
close_editor(e);
|
122 |
+
}
|
123 |
+
});
|
124 |
+
|
125 |
+
// stop scroll
|
126 |
+
$('html').css('overflow', 'hidden');
|
127 |
+
|
128 |
+
update_articles.init();
|
129 |
+
load_saved_articles();
|
130 |
+
update_articles.update();
|
131 |
+
update_articles.render();
|
132 |
+
|
133 |
+
$('body').append(elms.holder);
|
134 |
+
|
135 |
+
paintWindow();
|
136 |
+
};
|
137 |
+
|
138 |
+
var load_saved_articles = function () {
|
139 |
+
var article_lis = $('.wp_rp:first li:not(.wp_rp_special)');
|
140 |
+
if (article_lis) {
|
141 |
+
article_lis.each(function (pos, article) {
|
142 |
+
article = $(article);
|
143 |
+
if (article.data('post-type') == 'own_sourcefeed') {
|
144 |
+
var art = {
|
145 |
+
aid: article.data('poid').split('-')[1],
|
146 |
+
|
147 |
+
url: article.find('a:first').attr('href'),
|
148 |
+
|
149 |
+
title: article.find('a.wp_rp_title').text(),
|
150 |
+
excerpt: article.find('.wp_rp_excerpt').text(),
|
151 |
+
comments: parseInt(article.find('.wp_rp_comments_count').text().replace('(', '').replace(')', ''), 10),
|
152 |
+
date: article.find('.wp_rp_publish_date').text(),
|
153 |
+
text_preview: '',
|
154 |
+
published_datetime: '',
|
155 |
+
|
156 |
+
thumbnail: article.find('img').attr('src'),
|
157 |
+
|
158 |
+
picked: true,
|
159 |
+
type: article.data('post-type'),
|
160 |
+
pos: article.data('position')
|
161 |
+
};
|
162 |
+
articles.all.push(art);
|
163 |
+
articles.ids[art.aid] = art;
|
164 |
+
articles.selected[pos] = art;
|
165 |
+
} else {
|
166 |
+
if ({'promoted': true, 'network': true, 'external': true}[article.data('post-type')]) {
|
167 |
+
articles.special.push(pos);
|
168 |
+
}
|
169 |
+
}
|
170 |
+
});
|
171 |
+
}
|
172 |
+
};
|
173 |
+
|
174 |
+
var save_picked_articles = function (cb) {
|
175 |
+
var wp_articles = [];
|
176 |
+
var articles_all_pos = 0;
|
177 |
+
var selected_articles_ids = {};
|
178 |
+
|
179 |
+
$.each(articles.selected, function (i, a) {
|
180 |
+
if (a) {selected_articles_ids[a.aid] = true;}
|
181 |
+
});
|
182 |
+
|
183 |
+
for (var pos = 0; pos < conf.num_articles_to_insert; pos += 1) {
|
184 |
+
var article = articles.selected[pos];
|
185 |
+
|
186 |
+
if (article) {
|
187 |
+
if (article.type === 'own_sourcefeed') {
|
188 |
+
wp_articles.push({
|
189 |
+
ID: article.aid,
|
190 |
+
|
191 |
+
post_url: article.url,
|
192 |
+
thumbnail: article.thumbnail,
|
193 |
+
|
194 |
+
post_title: article.title,
|
195 |
+
post_excerpt: article.excerpt || '',
|
196 |
+
post_content: '',
|
197 |
+
post_date: article.date || '',
|
198 |
+
comment_count: article.comments || 0,
|
199 |
+
|
200 |
+
picked: !!article.picked,
|
201 |
+
type: article.type,
|
202 |
+
pos: pos
|
203 |
+
});
|
204 |
+
} else {
|
205 |
+
wp_articles.push({
|
206 |
+
ID: false,
|
207 |
+
pos: pos,
|
208 |
+
type: article.type
|
209 |
+
});
|
210 |
+
}
|
211 |
+
} else {
|
212 |
+
wp_articles.push({
|
213 |
+
ID: false,
|
214 |
+
pos: pos,
|
215 |
+
type: 'empty'
|
216 |
+
});
|
217 |
+
}
|
218 |
+
}
|
219 |
+
|
220 |
+
$.post(conf.admin_ajax_url, {
|
221 |
+
'action': conf.admin_ajax_action,
|
222 |
+
'post_id': conf.post_id,
|
223 |
+
'related_posts': JSON.stringify(wp_articles),
|
224 |
+
'_wpnonce': window._wp_rp_ajax_nonce
|
225 |
+
}, cb);
|
226 |
+
};
|
227 |
+
|
228 |
+
var fetch_related_posts = (function () {
|
229 |
+
var get_internal_articles = function(search, success, error) {
|
230 |
+
var data = {
|
231 |
+
post_id: conf.post_id,
|
232 |
+
search: search || '',
|
233 |
+
action: 'wp_rp_load_articles',
|
234 |
+
count: conf.num_articles
|
235 |
+
};
|
236 |
+
$.ajax({
|
237 |
+
url: window._wp_rp_wp_ajax_url,
|
238 |
+
dataType: "json",
|
239 |
+
data: data,
|
240 |
+
success: function (response) {
|
241 |
+
var arts = [];
|
242 |
+
$.each(response, function (i, art) {
|
243 |
+
arts.push({
|
244 |
+
type: 'own_sourcefeed',
|
245 |
+
aid: 'in_' + art.id,
|
246 |
+
thumbnail: $(art.img).attr('src'),
|
247 |
+
title: art.title,
|
248 |
+
excerpt: art.excerpt,
|
249 |
+
date: art.date,
|
250 |
+
comments: art.comments,
|
251 |
+
url: art.url,
|
252 |
+
target_url: art.url
|
253 |
+
});
|
254 |
+
});
|
255 |
+
success({
|
256 |
+
'status': 'ok',
|
257 |
+
'source': 'internal',
|
258 |
+
'data': {
|
259 |
+
'results': arts
|
260 |
+
}
|
261 |
+
});
|
262 |
+
},
|
263 |
+
error: error
|
264 |
+
});
|
265 |
+
};
|
266 |
+
|
267 |
+
var get_articles = function(search, tags, title, success, error) {
|
268 |
+
var responses = {};
|
269 |
+
var num_requests = 1;
|
270 |
+
|
271 |
+
var combine_articles = function () {
|
272 |
+
var articles = [];
|
273 |
+
var urls = {};
|
274 |
+
|
275 |
+
$.each(['external', 'internal'], function (i, type) {
|
276 |
+
if (responses[type] && responses[type]['status'] === 'ok' && responses[type]['data']) {
|
277 |
+
$.each(responses[type].data.results, function (j, article) {
|
278 |
+
if (urls[article.url]) {
|
279 |
+
return true;
|
280 |
+
}
|
281 |
+
urls[article.url] = true;
|
282 |
+
articles.push(article);
|
283 |
+
});
|
284 |
+
}
|
285 |
+
});
|
286 |
+
|
287 |
+
if (responses['external'] && responses['external']['status'] === 'ok') {
|
288 |
+
conf.num_external_slots = responses['external']['data']['settings']['num_external_slots'];
|
289 |
+
}
|
290 |
+
|
291 |
+
if (articles) {
|
292 |
+
success(articles);
|
293 |
+
} else {
|
294 |
+
error();
|
295 |
+
}
|
296 |
+
};
|
297 |
+
var response_cb = function () {
|
298 |
+
num_requests -= 1;
|
299 |
+
|
300 |
+
if (num_requests <= 0) {
|
301 |
+
combine_articles();
|
302 |
+
}
|
303 |
+
};
|
304 |
+
var response_success = function (response) {
|
305 |
+
var response_type = response.source === 'internal' ? 'internal' : 'external';
|
306 |
+
responses[response_type] = response;
|
307 |
+
response_cb();
|
308 |
+
};
|
309 |
+
|
310 |
+
get_internal_articles(search, response_success, response_cb);
|
311 |
+
if (conf.remote_recommendations) {
|
312 |
+
get_sre_articles(search, tags, title, response_success, response_cb);
|
313 |
+
}
|
314 |
+
};
|
315 |
+
|
316 |
+
return function (search, force_update, success, error) {
|
317 |
+
var tags = (window._wp_rp_post_tags && window._wp_rp_post_tags.join(',')) || '';
|
318 |
+
var title = window._wp_rp_post_title || '';
|
319 |
+
|
320 |
+
search = search || false;
|
321 |
+
|
322 |
+
if (!tags && !title && search === false) {
|
323 |
+
success(false);
|
324 |
+
return;
|
325 |
+
}
|
326 |
+
|
327 |
+
get_articles(search, tags, title, function (articles) {
|
328 |
+
if (success && articles) {
|
329 |
+
success(articles);
|
330 |
+
}
|
331 |
+
},
|
332 |
+
function () {
|
333 |
+
if (error) {
|
334 |
+
error();
|
335 |
+
}
|
336 |
+
});
|
337 |
+
}
|
338 |
+
}());
|
339 |
+
|
340 |
+
var update_articles = (function () {
|
341 |
+
var init = function () {
|
342 |
+
drag_and_drop.init();
|
343 |
+
search();
|
344 |
+
|
345 |
+
elms.replace_articles_list.bind('scroll', render.render_selector_shadows);
|
346 |
+
};
|
347 |
+
|
348 |
+
var update = function (cb, search, force_update) {
|
349 |
+
elms.replace_articles_list.html('');
|
350 |
+
render.render_selector_shadows();
|
351 |
+
articles.all = [];
|
352 |
+
|
353 |
+
elms.article_loader.find('.zem-no-articles').hide();
|
354 |
+
elms.article_loader.find('.zem-loader').show();
|
355 |
+
elms.article_loader.show();
|
356 |
+
|
357 |
+
var num_ext_pre = conf.num_external_slots;
|
358 |
+
fetch_related_posts(search, force_update, function (arts) {
|
359 |
+
if (arts && arts.length) {
|
360 |
+
elms.article_loader.hide();
|
361 |
+
|
362 |
+
articles.all = $.grep(arts, function (a) {
|
363 |
+
// Skip for this post, but make sure it's not postfixed with some params or anchors
|
364 |
+
return window.location.href.indexOf(a.url) < 0;
|
365 |
+
});
|
366 |
+
|
367 |
+
$.each(articles.all, function (i, a) {
|
368 |
+
if (!articles.ids[a.aid]) {
|
369 |
+
articles.ids[a.aid] = a;
|
370 |
+
} else {
|
371 |
+
a = articles.ids[a.aid];
|
372 |
+
articles.all[i] = a;
|
373 |
+
}
|
374 |
+
});
|
375 |
+
render.article_selector();
|
376 |
+
if (num_ext_pre !== conf.num_external_slots) {
|
377 |
+
render.articles();
|
378 |
+
}
|
379 |
+
} else {
|
380 |
+
elms.article_loader.find('.zem-no-articles').show();
|
381 |
+
elms.article_loader.find('.zem-loader').hide();
|
382 |
+
}
|
383 |
+
render.render_selector_shadows();
|
384 |
+
if (cb) {cb(true);}
|
385 |
+
}, function () {
|
386 |
+
elms.article_loader.find('.zem-no-articles').show();
|
387 |
+
elms.article_loader.find('.zem-loader').hide();
|
388 |
+
render.render_selector_shadows();
|
389 |
+
|
390 |
+
if (cb) {cb(false);}
|
391 |
+
});
|
392 |
+
};
|
393 |
+
|
394 |
+
var search = function () {
|
395 |
+
elms.search_form.bind('submit', function (ev) {
|
396 |
+
ev.preventDefault();
|
397 |
+
var search = elms.search_input.val();
|
398 |
+
update(null, search, true);
|
399 |
+
});
|
400 |
+
};
|
401 |
+
|
402 |
+
var article_insert = function (article, pos, commit) {
|
403 |
+
article.picked = true;
|
404 |
+
article.pos = pos;
|
405 |
+
articles.selected[pos] = article;
|
406 |
+
articles.ids[article.aid] = article;
|
407 |
+
|
408 |
+
render.article_li_selected(article);
|
409 |
+
|
410 |
+
if (commit) {
|
411 |
+
save_picked_articles();
|
412 |
+
render.article_selector();
|
413 |
+
}
|
414 |
+
};
|
415 |
+
|
416 |
+
var article_remove = function (article, commit) {
|
417 |
+
delete articles.selected[article.pos];
|
418 |
+
article.picked = false;
|
419 |
+
article.pos = -1;
|
420 |
+
|
421 |
+
article.elm && article.elm
|
422 |
+
.html('<div class="droppable" /><span class="notice">Drag post here</span>')
|
423 |
+
.attr('draggable', false)
|
424 |
+
.removeClass('external')
|
425 |
+
.data('aid', false);
|
426 |
+
|
427 |
+
if (commit) {
|
428 |
+
save_picked_articles();
|
429 |
+
render.article_selector();
|
430 |
+
}
|
431 |
+
};
|
432 |
+
|
433 |
+
var render = {
|
434 |
+
article_li: function (li, article) {
|
435 |
+
li.html('<div class="droppable" />');
|
436 |
+
li.data('aid', article);
|
437 |
+
|
438 |
+
li.attr('draggable', true);
|
439 |
+
li.unbind('dragstart').bind('dragstart', function (ev) {
|
440 |
+
drag_and_drop.drag(ev, article, li);
|
441 |
+
});
|
442 |
+
|
443 |
+
var img = $('<img draggable="false" />');
|
444 |
+
img.error(function () {
|
445 |
+
img.unbind('error');
|
446 |
+
|
447 |
+
var aid_num = parseInt(article.aid.replace('in_')) || parseInt(Math.random() * conf.num_default_thumbnails);
|
448 |
+
var default_url = conf.plugin_static_url + 'thumbs/' + (aid_num % conf.num_default_thumbnails) + '.jpg';
|
449 |
+
article.thumbnail = default_url;
|
450 |
+
img.attr('src', default_url);
|
451 |
+
});
|
452 |
+
|
453 |
+
article.thumbnail = article.thumbnail || article.thumbnail_url;
|
454 |
+
img.attr('src', article.thumbnail);
|
455 |
+
|
456 |
+
li.append(img);
|
457 |
+
li.append('<span unselectable="on" class="title">' + article.title + '</span>');
|
458 |
+
|
459 |
+
var out_link = $('<a class="open-article" draggable="false" target="_blank" href="' + article.target_url + '">link out</a>');
|
460 |
+
out_link.bind('click', function (ev) {
|
461 |
+
ev.stopPropagation();
|
462 |
+
});
|
463 |
+
li.append(out_link);
|
464 |
+
},
|
465 |
+
|
466 |
+
article_li_selector: function (li, article) {
|
467 |
+
article.elm = li;
|
468 |
+
|
469 |
+
render.article_li(li, article);
|
470 |
+
|
471 |
+
var insert = $('<a draggable="false" class="insert overlay" href="#"><div class="txt">insert</div></a>');
|
472 |
+
|
473 |
+
insert.bind('click', function (ev) {
|
474 |
+
ev.preventDefault();
|
475 |
+
|
476 |
+
var pos = 0;
|
477 |
+
for (pos = 0; pos < conf.num_articles_to_insert - 1 && articles.selected[pos]; pos += 1) {}
|
478 |
+
|
479 |
+
if (!articles.selected[pos]) {
|
480 |
+
article_insert(article, pos, true);
|
481 |
+
}
|
482 |
+
});
|
483 |
+
|
484 |
+
li.append(insert);
|
485 |
+
},
|
486 |
+
|
487 |
+
article_li_selected: function (article) {
|
488 |
+
var li = elms.article_list[article.pos];
|
489 |
+
article.elm = li;
|
490 |
+
render.article_li(li, article);
|
491 |
+
|
492 |
+
if (article.external) {
|
493 |
+
return;
|
494 |
+
}
|
495 |
+
var remove = $('<a draggable="false" class="remove overlay" href="#"><span class="icon"></span><span class="txt">remove</span></a>');
|
496 |
+
remove.bind('click', function (ev) {
|
497 |
+
ev.preventDefault();
|
498 |
+
|
499 |
+
article_remove(article, true);
|
500 |
+
});
|
501 |
+
li.append(remove);
|
502 |
+
},
|
503 |
+
|
504 |
+
article_selector: function () {
|
505 |
+
elms.replace_articles_list.html('');
|
506 |
+
|
507 |
+
var selected_articles_ids = {};
|
508 |
+
$.each(articles.selected, function (pos, article) {
|
509 |
+
selected_articles_ids[article.aid] = true;
|
510 |
+
});
|
511 |
+
|
512 |
+
var count = 0;
|
513 |
+
$.each(articles.all, function (i, article_rend) {
|
514 |
+
if (!selected_articles_ids[article_rend.aid]) {
|
515 |
+
var li = $('<li />');
|
516 |
+
render.article_li_selector(li, article_rend);
|
517 |
+
elms.replace_articles_list.append(li);
|
518 |
+
count += 1;
|
519 |
+
}
|
520 |
+
if (count >= conf.num_articles) {
|
521 |
+
return false;
|
522 |
+
}
|
523 |
+
});
|
524 |
+
render.render_selector_shadows();
|
525 |
+
},
|
526 |
+
|
527 |
+
render_selector_shadows: function (ev) {
|
528 |
+
var pos_left = elms.replace_articles_list.scrollLeft();
|
529 |
+
var width = elms.replace_articles_list[0].scrollWidth - elms.replace_articles_list.width();
|
530 |
+
if (pos_left > 0) {
|
531 |
+
elms.replace_articles_list.addClass('scroll-left');
|
532 |
+
} else {
|
533 |
+
elms.replace_articles_list.removeClass('scroll-left');
|
534 |
+
}
|
535 |
+
if (pos_left < width) {
|
536 |
+
elms.replace_articles_list.addClass('scroll-right');
|
537 |
+
} else {
|
538 |
+
elms.replace_articles_list.removeClass('scroll-right');
|
539 |
+
}
|
540 |
+
},
|
541 |
+
articles: function() {
|
542 |
+
$.each(articles.selected, function (pos, article) {
|
543 |
+
if (article && article.aid && article.picked) {
|
544 |
+
if (pos < conf.num_articles_to_insert) {
|
545 |
+
article_insert(article, pos, false);
|
546 |
+
} else {
|
547 |
+
article_remove(article, false);
|
548 |
+
}
|
549 |
+
}
|
550 |
+
});
|
551 |
+
},
|
552 |
+
|
553 |
+
all: function () {
|
554 |
+
render.articles();
|
555 |
+
render.article_selector();
|
556 |
+
}
|
557 |
+
};
|
558 |
+
|
559 |
+
var drag_and_drop = {
|
560 |
+
hint_timeout: null,
|
561 |
+
dragged_article: null,
|
562 |
+
|
563 |
+
ie9_drag_start: function (ev) {
|
564 |
+
if (ev.which !== 1 || ev.ctrlKey || ev.metaKey) {
|
565 |
+
return;
|
566 |
+
}
|
567 |
+
if ($(this).get(0).dragDrop) {
|
568 |
+
$(this).get(0).dragDrop();
|
569 |
+
}
|
570 |
+
},
|
571 |
+
drag_hint: function (ev) {
|
572 |
+
if (ev.which !== 1 || ev.ctrlKey || ev.metaKey) {
|
573 |
+
return;
|
574 |
+
}
|
575 |
+
|
576 |
+
var $this = $(this);
|
577 |
+
|
578 |
+
var article = drag_and_drop.dragged_article || $this.data('aid');
|
579 |
+
if (!article) { return; }
|
580 |
+
|
581 |
+
drag_and_drop.hint_timeout = setTimeout(function () {
|
582 |
+
|
583 |
+
if (!article.picked || article.external) {
|
584 |
+
elms.wrapper.find('ul.selected li:not(.external)').addClass('drop-hint');
|
585 |
+
} else {
|
586 |
+
elms.wrapper.find('ul.selected li').addClass('drop-hint');
|
587 |
+
}
|
588 |
+
|
589 |
+
if (article.picked && !article.external) {
|
590 |
+
elms.remove_article_sign.show();
|
591 |
+
}
|
592 |
+
}, 100);
|
593 |
+
},
|
594 |
+
drag: function (ev, article, li) {
|
595 |
+
ev.dataTransfer.setData('text', 'wprp_article_' + article.aid);
|
596 |
+
if (ev.dataTransfer.setDragImage) {
|
597 |
+
ev.dataTransfer.setDragImage(li.get(0), li.outerWidth() / 2, li.outerHeight() / 2);
|
598 |
+
} else if (ev.dataTransfer.addElement) {
|
599 |
+
ev.dataTransfer.addElement(li.get(0));
|
600 |
+
}
|
601 |
+
|
602 |
+
drag_and_drop.dragged_article = article;
|
603 |
+
|
604 |
+
setTimeout(function () {
|
605 |
+
elms.wrapper.find('li .droppable').css('z-index', 2);
|
606 |
+
}, 1);
|
607 |
+
drag_and_drop.drag_hint(ev);
|
608 |
+
},
|
609 |
+
drop_remove: function (ev) {
|
610 |
+
ev.preventDefault();
|
611 |
+
|
612 |
+
var article = drag_and_drop.dragged_article;
|
613 |
+
|
614 |
+
if (!drag_and_drop.dragged_article || article.external) {
|
615 |
+
// External articles can't be removed
|
616 |
+
return;
|
617 |
+
}
|
618 |
+
article_remove(article, true);
|
619 |
+
|
620 |
+
drag_and_drop.dragend(ev);
|
621 |
+
},
|
622 |
+
drop: function (ev) {
|
623 |
+
$(this).removeClass('drop');
|
624 |
+
ev.preventDefault();
|
625 |
+
|
626 |
+
var article = drag_and_drop.dragged_article;
|
627 |
+
if (!article) {return false;}
|
628 |
+
|
629 |
+
var old_pos = article.pos;
|
630 |
+
var new_pos = $(this).data('pos') * 1;
|
631 |
+
if (old_pos === new_pos) { return false; }
|
632 |
+
var replaced_article = articles.selected[new_pos];
|
633 |
+
|
634 |
+
if (replaced_article && ((replaced_article.external && !article.picked) || (replaced_article.external && article.external))) {
|
635 |
+
// External articles can't be removed
|
636 |
+
return;
|
637 |
+
}
|
638 |
+
|
639 |
+
var picked = article.picked;
|
640 |
+
if (picked) {
|
641 |
+
article_remove(article, false);
|
642 |
+
}
|
643 |
+
if (replaced_article) {
|
644 |
+
article_remove(replaced_article, false);
|
645 |
+
if (picked) {
|
646 |
+
article_insert(replaced_article, old_pos, false);
|
647 |
+
}
|
648 |
+
}
|
649 |
+
|
650 |
+
article_insert(article, new_pos, true);
|
651 |
+
|
652 |
+
drag_and_drop.dragend(ev);
|
653 |
+
},
|
654 |
+
dragover: function (ev) {
|
655 |
+
ev.preventDefault();
|
656 |
+
var article = drag_and_drop.dragged_article;
|
657 |
+
var replaced_article = $(this).data('aid');
|
658 |
+
|
659 |
+
if (replaced_article && ((replaced_article.external && !article.picked) || (replaced_article.external && article.external))) {
|
660 |
+
// External articles can't be removed
|
661 |
+
return;
|
662 |
+
}
|
663 |
+
$(this).addClass('drop');
|
664 |
+
},
|
665 |
+
dragleave: function (ev) {
|
666 |
+
ev.preventDefault();
|
667 |
+
$(this).removeClass('drop');
|
668 |
+
},
|
669 |
+
dragend: function (ev) {
|
670 |
+
clearTimeout(drag_and_drop.hint_timeout);
|
671 |
+
drag_and_drop.dragged_article = null;
|
672 |
+
|
673 |
+
elms.remove_article_sign.hide();
|
674 |
+
elms.wrapper.find('li .droppable').css('z-index', -1);
|
675 |
+
elms.wrapper.find('ul.selected li').removeClass('drop-hint');
|
676 |
+
},
|
677 |
+
init: function () {
|
678 |
+
elms.selected_articles_wrap
|
679 |
+
.delegate('li', 'dragover', drag_and_drop.dragover)
|
680 |
+
.delegate('li', 'dragleave', drag_and_drop.dragleave)
|
681 |
+
.delegate('li', 'drop', drag_and_drop.drop);
|
682 |
+
|
683 |
+
elms.replace_articles_wrap
|
684 |
+
.bind('dragover', drag_and_drop.dragover)
|
685 |
+
.bind('dragleave', drag_and_drop.dragleave)
|
686 |
+
.bind('drop', drag_and_drop.drop_remove);
|
687 |
+
|
688 |
+
elms.wrapper
|
689 |
+
.delegate('li[draggable=true]', 'dragstart', drag_and_drop.drag_hint)
|
690 |
+
.delegate('li[draggable=true]', 'dragend', drag_and_drop.dragend)
|
691 |
+
.delegate('li[draggable=true]', 'mousedown', drag_and_drop.drag_hint)
|
692 |
+
.delegate('li[draggable=true]', 'mouseup', drag_and_drop.dragend)
|
693 |
+
.delegate('li[draggable=true]', 'mousemove', drag_and_drop.ie9_drag_start);
|
694 |
+
|
695 |
+
}
|
696 |
+
};
|
697 |
+
|
698 |
+
return {
|
699 |
+
update: update,
|
700 |
+
render: render.all,
|
701 |
+
init: init
|
702 |
+
};
|
703 |
+
}());
|
704 |
+
|
705 |
+
init();
|
706 |
+
|
707 |
+
return {
|
708 |
+
update: update_articles.update
|
709 |
+
};
|
710 |
+
};
|
711 |
+
|
712 |
+
var bind_edit = function () {
|
713 |
+
$('#wp_rp_edit_related_posts').click(function () {
|
714 |
+
var rp = related_posts();
|
715 |
+
return false;
|
716 |
+
});
|
717 |
+
};
|
718 |
+
(function load_edit(t, d) {
|
719 |
+
if (!d) {
|
720 |
+
d = 10; t = 0;
|
721 |
+
}
|
722 |
+
if ($('#wp_rp_edit_related_posts').length) {
|
723 |
+
bind_edit();
|
724 |
+
} else {
|
725 |
+
if (t < 30000) {
|
726 |
+
setTimeout(function () {
|
727 |
+
load_edit(t + d, d * 1.5)
|
728 |
+
}, d);
|
729 |
+
} else {
|
730 |
+
$(function () {
|
731 |
+
bind_edit();
|
732 |
+
});
|
733 |
+
}
|
734 |
+
}
|
735 |
+
}());
|
736 |
+
}(jQuery));
|
737 |
+
|
738 |
+
|
739 |
+
|
740 |
+
|
static/js/extras.js
CHANGED
@@ -1 +1,16 @@
|
|
1 |
-
(function(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function ($) {
|
2 |
+
var extra_settings = function () {
|
3 |
+
var custom_size_panel = $('#wp_rp_custom_thumb_sizes_settings');
|
4 |
+
if ($('#wp_rp_custom_size_thumbnail_enabled:checked').length) {
|
5 |
+
custom_size_panel.show();
|
6 |
+
} else {
|
7 |
+
custom_size_panel.hide();
|
8 |
+
}
|
9 |
+
|
10 |
+
$('#wp_rp_custom_size_thumbnail_enabled').click(function() {
|
11 |
+
custom_size_panel.toggle(this.checked);
|
12 |
+
});
|
13 |
+
}
|
14 |
+
|
15 |
+
$(extra_settings);
|
16 |
+
}(jQuery));
|
static/js/infiniterecs.js
CHANGED
@@ -1,4 +1,104 @@
|
|
1 |
-
var ZemRPResponse=[];
|
2 |
-
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
var ZemRPResponse = [];
|
2 |
+
|
3 |
+
jQuery(function ($) {
|
4 |
+
|
5 |
+
window._wp_rp_request_id = window._wp_rp_request_id || ((new Date().getTime() % 60466176) / 60466176).toString(36).substr(2,5) + Math.random().toString(36).substr(2,10); // 60466176 = 36 ^ 5
|
6 |
+
|
7 |
+
var scrollTimeout = 0,
|
8 |
+
win = $(window),
|
9 |
+
fetching = 10,
|
10 |
+
related = $('.related_post'),
|
11 |
+
loading = false,
|
12 |
+
from = related.find('li').length,
|
13 |
+
lastFrom = -1,
|
14 |
+
|
15 |
+
|
16 |
+
getBottomScroll = function () {
|
17 |
+
return win.height() + document.body.scrollTop;
|
18 |
+
},
|
19 |
+
getNextSet = function () {
|
20 |
+
var i = 0, result = [], next = {};
|
21 |
+
for (; i < fetching; i += 1) {
|
22 |
+
next = ZemRPResponse.shift();
|
23 |
+
if (next) {
|
24 |
+
result.push(next);
|
25 |
+
}
|
26 |
+
}
|
27 |
+
return result;
|
28 |
+
},
|
29 |
+
fillList = function () {
|
30 |
+
var ajax_url = window._wp_rp_wp_ajax_url + '?action=wp_rp_load_articles&post_id=' + window._wp_rp_post_id + '&from=' + from + '&count=50';
|
31 |
+
if (loading) {
|
32 |
+
return;
|
33 |
+
}
|
34 |
+
if (lastFrom === from){
|
35 |
+
return;
|
36 |
+
}
|
37 |
+
loading = true;
|
38 |
+
|
39 |
+
$.get(ajax_url, function (data) {
|
40 |
+
$.each(data, function (i, newOne) {
|
41 |
+
ZemRPResponse.push(newOne);
|
42 |
+
});
|
43 |
+
lastFrom = from;
|
44 |
+
from += data.length;
|
45 |
+
|
46 |
+
loading = false;
|
47 |
+
}, 'json');
|
48 |
+
},
|
49 |
+
fetchRelated = function (cb) {
|
50 |
+
cb(getNextSet());
|
51 |
+
if (ZemRPResponse.length <= (2 * fetching)) {
|
52 |
+
fillList();
|
53 |
+
}
|
54 |
+
},
|
55 |
+
loadMoreRelated = function () {
|
56 |
+
fetchRelated(function (data) {
|
57 |
+
var postContainer = $('.related_post'),
|
58 |
+
nextPosition = postContainer.find('li').last().data('position') + 1,
|
59 |
+
newElm;
|
60 |
+
$.each(data, function (i, post) {
|
61 |
+
newElm = $('<li data-position="' + nextPosition + '" data-poid="' + post.id + '"><a href="' + post.url + '" class="wp_rp_thumbnail">' + post.img + '</a><a href="' + post.url + '" class="wp_rp_title">' + post.title + '</a></li>');
|
62 |
+
newElm.data('position', nextPosition);
|
63 |
+
newElm.data('poid', post.id);
|
64 |
+
|
65 |
+
postContainer.append(newElm);
|
66 |
+
|
67 |
+
nextPosition += 1;
|
68 |
+
});
|
69 |
+
});
|
70 |
+
},
|
71 |
+
smartScrollHandler = function () {
|
72 |
+
|
73 |
+
var firstArticle = related.children(':first');
|
74 |
+
|
75 |
+
if (getBottomScroll() > firstArticle.offset().top) {
|
76 |
+
|
77 |
+
loadMoreRelated();
|
78 |
+
return;
|
79 |
+
}
|
80 |
+
|
81 |
+
},
|
82 |
+
|
83 |
+
isFocused = true,
|
84 |
+
onBlur = function () {
|
85 |
+
isFocused = false;
|
86 |
+
},
|
87 |
+
onFocus = function () {
|
88 |
+
isFocused = true;
|
89 |
+
}
|
90 |
+
;
|
91 |
+
|
92 |
+
|
93 |
+
if (/*@cc_on!@*/false) { // check for Internet Explorer
|
94 |
+
document.onfocusin = onFocus;
|
95 |
+
document.onfocusout = onBlur;
|
96 |
+
} else {
|
97 |
+
window.onfocus = onFocus;
|
98 |
+
window.onblur = onBlur;
|
99 |
+
}
|
100 |
+
|
101 |
+
fillList();
|
102 |
+
win.scroll(smartScrollHandler);
|
103 |
+
});
|
104 |
+
|
static/js/pinterest.js
CHANGED
@@ -1,7 +1,250 @@
|
|
1 |
-
(function
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function () {
|
2 |
+
var ZemRPResponse = [];
|
3 |
+
if (window.jQuery) {
|
4 |
+
jQuery(function ($) {
|
5 |
+
window._wp_rp_request_id = window._wp_rp_request_id || ((new Date().getTime() % 60466176) / 60466176).toString(36).substr(2,5) + Math.random().toString(36).substr(2,10); // 60466176 = 36 ^ 5
|
6 |
+
|
7 |
+
var scrollTimeout = 0,
|
8 |
+
win = $(window),
|
9 |
+
fetching_first_time = window._wp_rp_num_rel_posts * 1 || 5,
|
10 |
+
fetching = fetching_first_time * 2,
|
11 |
+
is_first_time = true,
|
12 |
+
related = $('.wp_rp.related_post'),
|
13 |
+
loading = false,
|
14 |
+
stop = false,
|
15 |
+
originalRPHeight = 2 * related.outerHeight(true),
|
16 |
+
|
17 |
+
from = fetching_first_time,
|
18 |
+
|
19 |
+
push_stats = function (action, extra_params) {
|
20 |
+
var img = document.createElement('img');
|
21 |
+
var params = {
|
22 |
+
action: action,
|
23 |
+
post_id: window._wp_rp_post_id,
|
24 |
+
request_id: window._wp_rp_request_id,
|
25 |
+
_: (+new Date())
|
26 |
+
};
|
27 |
+
var ajax_query = [];
|
28 |
+
var ajax_url = window._wp_rp_static_base_url + 'stats.gif?';
|
29 |
+
|
30 |
+
if (extra_params) {
|
31 |
+
for (var attrname in extra_params) {
|
32 |
+
if (extra_params.hasOwnProperty(attrname)) {
|
33 |
+
params[attrname] = extra_params[attrname];
|
34 |
+
}
|
35 |
+
}
|
36 |
+
}
|
37 |
+
|
38 |
+
for(x in params) {
|
39 |
+
if (params.hasOwnProperty(x)) {
|
40 |
+
ajax_query.push(x + '=' + params[x]);
|
41 |
+
}
|
42 |
+
}
|
43 |
+
|
44 |
+
ajax_url += ajax_query.join('&');
|
45 |
+
|
46 |
+
img.src = ajax_url;
|
47 |
+
},
|
48 |
+
getNextSet = function () {
|
49 |
+
if (ZemRPResponse.length <= 0) {
|
50 |
+
$('#wp_rp_related_load_more').parent().remove();
|
51 |
+
return [];
|
52 |
+
}
|
53 |
+
var i = 0, result = [], next = {};
|
54 |
+
for (; i < (is_first_time ? fetching_first_time : fetching); i += 1) {
|
55 |
+
next = ZemRPResponse.shift();
|
56 |
+
if (next) {
|
57 |
+
result.push(next);
|
58 |
+
}
|
59 |
+
}
|
60 |
+
is_first_time = false;
|
61 |
+
return result;
|
62 |
+
},
|
63 |
+
fillList = function (cb) {
|
64 |
+
if (stop && ZemRPResponse.length <= 0) {
|
65 |
+
$('#wp_rp_related_load_more').parent().remove();
|
66 |
+
}
|
67 |
+
if (loading || stop) {
|
68 |
+
return;
|
69 |
+
}
|
70 |
+
|
71 |
+
loading = true;
|
72 |
+
$.getJSON(window._wp_rp_wp_ajax_url + '?action=wp_rp_load_articles&post_id=' + window._wp_rp_post_id + '&from=' + from + '&count=50&size=full', function (data) {
|
73 |
+
if (!data || !data.length || typeof(data) === 'string') {
|
74 |
+
stop = true;
|
75 |
+
if (ZemRPResponse.length <= 0) {
|
76 |
+
$('#wp_rp_related_load_more').parent().remove();
|
77 |
+
}
|
78 |
+
} else {
|
79 |
+
from += data.length;
|
80 |
+
$.each(data, function (i, newOne) {
|
81 |
+
ZemRPResponse.push(newOne);
|
82 |
+
});
|
83 |
+
if (cb) {
|
84 |
+
cb();
|
85 |
+
}
|
86 |
+
}
|
87 |
+
loading = false;
|
88 |
+
});
|
89 |
+
},
|
90 |
+
fetchRelated = function (cb) {
|
91 |
+
if (ZemRPResponse.length <= 0) {
|
92 |
+
fillList(function () {
|
93 |
+
cb(getNextSet());
|
94 |
+
});
|
95 |
+
} else if (ZemRPResponse.length <= (2 * fetching)) {
|
96 |
+
cb(getNextSet());
|
97 |
+
fillList();
|
98 |
+
} else {
|
99 |
+
cb(getNextSet());
|
100 |
+
}
|
101 |
+
},
|
102 |
+
loadMoreRelated = function () {
|
103 |
+
fetchRelated(function (data) {
|
104 |
+
setTimeout(function () {
|
105 |
+
$('#wp_rp_related_load_more')
|
106 |
+
.find('.zloader').hide().end()
|
107 |
+
.find('span').show();
|
108 |
+
}, 500);
|
109 |
+
|
110 |
+
var postContainer = $('.related_post.wp_rp'),
|
111 |
+
nextPosition = postContainer.find('li ul li').length,
|
112 |
+
prevImg = null,
|
113 |
+
columns = postContainer.find('.wp_rp_related_post_column'),
|
114 |
+
appendArticle = function (newElm, newImg) {
|
115 |
+
var shortest = $();
|
116 |
+
|
117 |
+
columns.each(function (i, column) {
|
118 |
+
column = $('ul', column);
|
119 |
+
|
120 |
+
if (shortest.height() === null || shortest.height() >= column.height()) {
|
121 |
+
shortest = column;
|
122 |
+
}
|
123 |
+
});
|
124 |
+
|
125 |
+
shortest.append(newElm);
|
126 |
+
},
|
127 |
+
loadArticle = function (i, newElm, prevImg, newImg) {
|
128 |
+
setTimeout(function () {
|
129 |
+
if (!prevImg || !prevImg.length || prevImg.data('zloaded')) {
|
130 |
+
appendArticle(newElm, newImg);
|
131 |
+
} else {
|
132 |
+
prevImg
|
133 |
+
.load(function () {appendArticle(newElm, newImg);})
|
134 |
+
.error(function () {appendArticle(newElm, newImg);});
|
135 |
+
}
|
136 |
+
}, 75 * i);
|
137 |
+
};
|
138 |
+
|
139 |
+
$.each(data, function (i, post) {
|
140 |
+
var newImg = $(post.img);
|
141 |
+
newImg
|
142 |
+
.load(function () {$(this).data('zloaded', true)})
|
143 |
+
.error(function () {$(this).data('zloaded', true)});
|
144 |
+
|
145 |
+
var newElm = $('<li data-position="' + nextPosition + '" data-poid="' + post.id + '"><a href="' + post.url + '" class="wp_rp_thumbnail"></a><a href="' + post.url + '" class="wp_rp_title">' + post.title + '</a></li>');
|
146 |
+
newElm.data('position', nextPosition);
|
147 |
+
newElm.data('poid', post.id);
|
148 |
+
newElm.find('.wp_rp_thumbnail').append(newImg);
|
149 |
+
|
150 |
+
nextPosition += 1;
|
151 |
+
|
152 |
+
loadArticle(i, newElm, prevImg, newImg);
|
153 |
+
|
154 |
+
prevImg = newImg;
|
155 |
+
});
|
156 |
+
});
|
157 |
+
},
|
158 |
+
scrollHandler = function () {
|
159 |
+
if (scrollTimeout) {
|
160 |
+
clearTimeout(scrollTimeout);
|
161 |
+
}
|
162 |
+
scrollTimeout = setTimeout(function () {
|
163 |
+
var postContainer = $('.related_post.wp_rp'),
|
164 |
+
fromBottom = postContainer.offset().top + postContainer.outerHeight(true) - win.scrollTop() - win.height();
|
165 |
+
|
166 |
+
if (fromBottom + 100 < originalRPHeight) { // before the user scrolls below this threshold, load more
|
167 |
+
loadMoreRelated();
|
168 |
+
}
|
169 |
+
}, 150);
|
170 |
+
},
|
171 |
+
initScrollHandler = function () {
|
172 |
+
scrollHandler();
|
173 |
+
$(window).bind('scroll.zloader', function () {
|
174 |
+
if (is_first_time) {
|
175 |
+
scrollHandler();
|
176 |
+
} else {
|
177 |
+
$(window).unbind('scroll.zloader');
|
178 |
+
}
|
179 |
+
});
|
180 |
+
},
|
181 |
+
preparseRelatedPosts = function () {
|
182 |
+
var max_left = -1,
|
183 |
+
columns = [],
|
184 |
+
column_count = 0;
|
185 |
+
|
186 |
+
related.find('li').each(function (i, column) {
|
187 |
+
column = $(column);
|
188 |
+
column.children().find('img').data('zloaded', true);
|
189 |
+
|
190 |
+
var id = column.data('poid');
|
191 |
+
var position = column.data('position');
|
192 |
+
var post_type = column.data('post-type');
|
193 |
+
var class_name = column.attr('class');;
|
194 |
+
|
195 |
+
var first = $('<li class="' + class_name + '" data-post-type="' + post_type + '" data-position="' + position + '" data-poid="' + id + '"></li>');
|
196 |
+
var left = column.offset().left;
|
197 |
+
|
198 |
+
if (left > max_left) {
|
199 |
+
max_left = left;
|
200 |
+
column_count += 1;
|
201 |
+
}
|
202 |
+
|
203 |
+
var column_idx = i % column_count;
|
204 |
+
|
205 |
+
first.append(column.children());
|
206 |
+
|
207 |
+
if (columns[column_idx]) {
|
208 |
+
columns[column_idx].find('ul').append(first);
|
209 |
+
column.remove();
|
210 |
+
} else {
|
211 |
+
column.empty();
|
212 |
+
column.attr('data-position', null);
|
213 |
+
column.attr('data-poid', null);
|
214 |
+
column.attr('data-post-type', null);
|
215 |
+
|
216 |
+
column.addClass('wp_rp_related_post_column');
|
217 |
+
column.addClass('wp_rp_special');
|
218 |
+
|
219 |
+
column.append('<ul></ul>');
|
220 |
+
column.find('ul').append(first);
|
221 |
+
|
222 |
+
columns[column_idx] = column;
|
223 |
+
}
|
224 |
+
});
|
225 |
+
|
226 |
+
initScrollHandler();
|
227 |
+
};
|
228 |
+
|
229 |
+
|
230 |
+
preparseRelatedPosts();
|
231 |
+
fillList();
|
232 |
+
|
233 |
+
related.append($('<li class="wp_rp_related_post_load_more wp_rp_special">' +
|
234 |
+
'<a id="wp_rp_related_load_more" href="#">' +
|
235 |
+
'<span>Load more posts</span>' +
|
236 |
+
'<img src="' + window._wp_rp_static_base_url + 'img/loading.gif" class="zloader" />' +
|
237 |
+
'</a>' +
|
238 |
+
'</li>'));
|
239 |
+
|
240 |
+
related.find('#wp_rp_related_load_more').click(function (ev) {
|
241 |
+
$('span', this).hide();
|
242 |
+
$('.zloader', this).show();
|
243 |
+
|
244 |
+
ev.preventDefault();
|
245 |
+
loadMoreRelated();
|
246 |
+
push_stats('pinterest-load-more');
|
247 |
+
});
|
248 |
+
});
|
249 |
+
}
|
250 |
+
}());
|
static/js/themes.js
CHANGED
@@ -1,5 +1,156 @@
|
|
1 |
-
(function
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
(function ($) {
|
2 |
+
var theme_selector = function () {
|
3 |
+
var plugin_static_url = $('#wp_rp_plugin_static_base_url').val(),
|
4 |
+
static_url = $('#wp_rp_static_base_url').val(),
|
5 |
+
json_url = $('#wp_rp_json_url').val(),
|
6 |
+
plugin_version = $('#wp_rp_version').val(),
|
7 |
+
platforms = {
|
8 |
+
mobile: {
|
9 |
+
wrap: $('#wp_rp_mobile_theme_options_wrap'),
|
10 |
+
theme_area: $('#wp_rp_mobile_theme_area'),
|
11 |
+
current_theme: $('#wp_rp_mobile_theme_selected').val()
|
12 |
+
},
|
13 |
+
desktop: {
|
14 |
+
wrap: $('#wp_rp_desktop_theme_options_wrap'),
|
15 |
+
theme_area: $('#wp_rp_desktop_theme_area'),
|
16 |
+
current_theme: $('#wp_rp_desktop_theme_selected').val()
|
17 |
+
}
|
18 |
+
},
|
19 |
+
|
20 |
+
themes = {
|
21 |
+
'mobile': [
|
22 |
+
{
|
23 |
+
"name": "Modern",
|
24 |
+
"location": "m-modern.css"
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"name": "Infinite Stream (experimental)",
|
28 |
+
"location": "m-stream.css"
|
29 |
+
},
|
30 |
+
{
|
31 |
+
"name": "Plain (your own css)",
|
32 |
+
"location": "m-plain.css"
|
33 |
+
}
|
34 |
+
],
|
35 |
+
'desktop': [
|
36 |
+
{
|
37 |
+
"name": "Momma",
|
38 |
+
"location": "momma.css"
|
39 |
+
},
|
40 |
+
{
|
41 |
+
"name": "Modern",
|
42 |
+
"location": "modern.css"
|
43 |
+
},
|
44 |
+
{
|
45 |
+
"name": "Vertical (Large)",
|
46 |
+
"location": "vertical.css"
|
47 |
+
},
|
48 |
+
{
|
49 |
+
"name": "Vertical (Medium)",
|
50 |
+
"location": "vertical-m.css"
|
51 |
+
},
|
52 |
+
{
|
53 |
+
"name": "Vertical (Small)",
|
54 |
+
"location": "vertical-s.css"
|
55 |
+
},
|
56 |
+
{
|
57 |
+
"name": "Pinterest Inspired",
|
58 |
+
"location": "pinterest.css"
|
59 |
+
},
|
60 |
+
{
|
61 |
+
"name": "Two Columns",
|
62 |
+
"location": "twocolumns.css"
|
63 |
+
},
|
64 |
+
{
|
65 |
+
"name": "Plain (your own css)",
|
66 |
+
"location": "plain.css"
|
67 |
+
}
|
68 |
+
]
|
69 |
+
},
|
70 |
+
|
71 |
+
update_themes = function () {
|
72 |
+
$.each(platforms, function (platform, elms) {
|
73 |
+
var td = elms.wrap.find('div.theme-list');
|
74 |
+
var screenshot_wrap = elms.wrap.find('div.theme-screenshot');
|
75 |
+
var update_img = function (input) {
|
76 |
+
if (!input.val()) {
|
77 |
+
screenshot_wrap.html('');
|
78 |
+
return;
|
79 |
+
}
|
80 |
+
|
81 |
+
var screenshot_src = plugin_static_url + 'img/themes/' + input.val().replace(/\.css$/, '.jpg');
|
82 |
+
var img = screenshot_wrap.find('img');
|
83 |
+
|
84 |
+
if (!img.length) {
|
85 |
+
img = $('<img />');
|
86 |
+
screenshot_wrap.html(img);
|
87 |
+
} else if (img.attr('src') === screenshot_src) {
|
88 |
+
return;
|
89 |
+
}
|
90 |
+
|
91 |
+
img.attr('src', screenshot_src);
|
92 |
+
};
|
93 |
+
td.empty();
|
94 |
+
|
95 |
+
$.each(themes[platform], function (i, theme) {
|
96 |
+
if (theme.location == 'custom.css') {
|
97 |
+
return;
|
98 |
+
}
|
99 |
+
|
100 |
+
var selected = theme.location === elms.current_theme ? 'checked="checked"' : '';
|
101 |
+
|
102 |
+
td.append('<label class="theme-label"><input ' + selected + ' class="theme-option" type="radio" name="wp_rp_' + platform + '_theme_name" value="' + theme.location + '" /> ' + theme.name + '</label><br />');
|
103 |
+
});
|
104 |
+
|
105 |
+
td.on('hover', 'label.theme-label', function () {
|
106 |
+
update_img($('input', this));
|
107 |
+
});
|
108 |
+
|
109 |
+
td.on('mouseleave', function () {
|
110 |
+
update_img(td.find('input:checked'));
|
111 |
+
});
|
112 |
+
update_img(td.find('input:checked'));
|
113 |
+
|
114 |
+
elms.theme_area.show();
|
115 |
+
});
|
116 |
+
},
|
117 |
+
append_get_themes_script = function () {
|
118 |
+
var script = document.createElement('script'),
|
119 |
+
body = document.getElementsByTagName("body").item(0);
|
120 |
+
script.type = 'text/javascript';
|
121 |
+
script.src = json_url + 'themes2.js?plv=' + plugin_version;
|
122 |
+
|
123 |
+
body.appendChild(script);
|
124 |
+
},
|
125 |
+
themes_loaded = false;
|
126 |
+
|
127 |
+
window.wp_rp_themes_cb = function (data) {
|
128 |
+
if (data && data.themes) {
|
129 |
+
themes = data.themes;
|
130 |
+
|
131 |
+
if(themes) {
|
132 |
+
themes_loaded = true;
|
133 |
+
update_themes();
|
134 |
+
}
|
135 |
+
}
|
136 |
+
if(themes_loaded) {
|
137 |
+
$('#wp_rp_theme_options_wrap').show()
|
138 |
+
}
|
139 |
+
};
|
140 |
+
|
141 |
+
if ($('#wp_rp_enable_themes:checked').length) {
|
142 |
+
update_themes();
|
143 |
+
$('#wp_rp_theme_options_wrap').show();
|
144 |
+
}
|
145 |
+
|
146 |
+
$('#wp_rp_enable_themes').change(function () {
|
147 |
+
if ($('#wp_rp_enable_themes:checked').length) {
|
148 |
+
append_get_themes_script();
|
149 |
+
} else {
|
150 |
+
$('#wp_rp_theme_options_wrap').hide();
|
151 |
+
}
|
152 |
+
});
|
153 |
+
};
|
154 |
+
|
155 |
+
$(theme_selector);
|
156 |
+
}(jQuery));
|
static/thumbs/0.jpg
CHANGED
File without changes
|
static/thumbs/1.jpg
CHANGED
File without changes
|
static/thumbs/15.jpg
CHANGED
File without changes
|
static/thumbs/16.jpg
CHANGED
File without changes
|
static/thumbs/19.jpg
CHANGED
File without changes
|
static/thumbs/2.jpg
CHANGED
File without changes
|
static/thumbs/22.jpg
CHANGED
File without changes
|
static/thumbs/23.jpg
CHANGED
File without changes
|
static/thumbs/24.jpg
CHANGED
File without changes
|
static/thumbs/29.jpg
CHANGED
File without changes
|
static/thumbs/4.jpg
CHANGED
File without changes
|
static/thumbs/5.jpg
CHANGED
File without changes
|
static/thumbs/6.jpg
CHANGED
File without changes
|
utils.php
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class wprp_utils
|
4 |
+
{
|
5 |
+
/**
|
6 |
+
* Get site.
|
7 |
+
*
|
8 |
+
* @since 1.0.0
|
9 |
+
* @access public
|
10 |
+
* @return string The site.
|
11 |
+
*/
|
12 |
+
public function get_site() {
|
13 |
+
// get url and trim trailing slash
|
14 |
+
$url = trim(site_url(), '/');
|
15 |
+
|
16 |
+
// set site
|
17 |
+
$site = preg_replace('#^https?://#', '', $url);
|
18 |
+
|
19 |
+
// check if site starts with 'www.'
|
20 |
+
if (substr($site, 0, 4) === 'www.') {
|
21 |
+
|
22 |
+
// remove 'www.' from site
|
23 |
+
$site = substr($site, 4);
|
24 |
+
|
25 |
+
}
|
26 |
+
|
27 |
+
return $site;
|
28 |
+
}
|
29 |
+
|
30 |
+
public function get_client_ip_address() {
|
31 |
+
|
32 |
+
$ip_address = '';
|
33 |
+
if (isset($_SERVER['HTTP_CLIENT_IP'])) {
|
34 |
+
$ip_address = $_SERVER['HTTP_CLIENT_IP'];
|
35 |
+
} else if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
36 |
+
$ip_address = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
37 |
+
} else if (isset($_SERVER['HTTP_X_FORWARDED'])) {
|
38 |
+
$ip_address = $_SERVER['HTTP_X_FORWARDED'];
|
39 |
+
} else if (isset($_SERVER['HTTP_FORWARDED_FOR'])) {
|
40 |
+
$ip_address = $_SERVER['HTTP_FORWARDED_FOR'];
|
41 |
+
} else if (isset($_SERVER['HTTP_FORWARDED'])) {
|
42 |
+
$ip_address = $_SERVER['HTTP_FORWARDED'];
|
43 |
+
} else if (isset($_SERVER['REMOTE_ADDR'])) {
|
44 |
+
$ip_address = $_SERVER['REMOTE_ADDR'];
|
45 |
+
} else {
|
46 |
+
$ip_address = 'UNKNOWN';
|
47 |
+
}
|
48 |
+
return $ip_address;
|
49 |
+
}
|
50 |
+
}
|
wp_related_posts.php
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
<?php
|
2 |
/*
|
3 |
Plugin Name: WordPress Related Posts
|
4 |
-
Version: 3.6.
|
5 |
Plugin URI: http://wordpress.org/extend/plugins/wordpress-23-related-posts-plugin/
|
6 |
Description: Quickly increase your readers' engagement with your posts by adding Related Posts in the footer of your content. Click on <a href="admin.php?page=wordpress-related-posts">Related Posts tab</a> to configure your settings.
|
7 |
Author: Sovrn, zemanta
|
1 |
<?php
|
2 |
/*
|
3 |
Plugin Name: WordPress Related Posts
|
4 |
+
Version: 3.6.4
|
5 |
Plugin URI: http://wordpress.org/extend/plugins/wordpress-23-related-posts-plugin/
|
6 |
Description: Quickly increase your readers' engagement with your posts by adding Related Posts in the footer of your content. Click on <a href="admin.php?page=wordpress-related-posts">Related Posts tab</a> to configure your settings.
|
7 |
Author: Sovrn, zemanta
|
zemanta/img/logo.png
CHANGED
Binary file
|
zemanta/img/menu_icon.png
CHANGED
Binary file
|
zemanta/zemanta.php
CHANGED
@@ -26,20 +26,18 @@ class WPRPZemanta {
|
|
26 |
return;
|
27 |
}
|
28 |
global $wp_version;
|
29 |
-
|
30 |
// initialize update notes shown once on plugin update
|
31 |
$this->update_notes['1.0.5'] = __('Please double-check your upload paths in Zemanta Settings, we changed some things that might affect your images.', 'zemanta');
|
32 |
$this->update_notes['1.0.7'] = __('Please double-check your upload paths in Zemanta Settings, we changed some things that might affect your images.', 'zemanta');
|
33 |
$this->update_notes['1.0.8'] = __('Please double-check your upload paths in Zemanta Settings, we changed some things that might affect your images.', 'zemanta');
|
34 |
-
|
35 |
add_action('admin_init', array($this, 'init'));
|
36 |
add_action('admin_init', array($this, 'register_options'));
|
37 |
|
38 |
-
register_activation_hook(dirname(__FILE__) . '/zemanta.php', array($this, 'activate'));
|
39 |
-
|
40 |
$this->supported_features['featured_image'] = version_compare($wp_version, '3.1', '>=') >= 0;
|
41 |
}
|
42 |
-
|
43 |
/**
|
44 |
* admin_init
|
45 |
*
|
@@ -52,13 +50,16 @@ class WPRPZemanta {
|
|
52 |
add_action('edit_form_advanced', array($this, 'assets'), 1);
|
53 |
add_action('edit_page_form', array($this, 'assets'), 1);
|
54 |
add_action('save_post', array($this, 'save_post'), 20);
|
55 |
-
|
56 |
$this->check_plugin_updated();
|
57 |
$this->create_options();
|
58 |
$this->check_options();
|
59 |
-
|
60 |
if(!$this->check_dependencies())
|
61 |
add_action('admin_notices', array($this, 'warning'));
|
|
|
|
|
|
|
62 |
}
|
63 |
|
64 |
/**
|
@@ -66,7 +67,7 @@ class WPRPZemanta {
|
|
66 |
*
|
67 |
* Run any functions needed for plugin activation
|
68 |
*/
|
69 |
-
public function activate()
|
70 |
{
|
71 |
$this->fix_user_meta();
|
72 |
}
|
@@ -76,8 +77,8 @@ class WPRPZemanta {
|
|
76 |
*
|
77 |
* Add any assets to the edit page
|
78 |
*/
|
79 |
-
public function assets()
|
80 |
-
{
|
81 |
$this->render('assets', array(
|
82 |
'api_key' => $this->api_key,
|
83 |
'version' => $this->version,
|
@@ -118,13 +119,13 @@ class WPRPZemanta {
|
|
118 |
*
|
119 |
* Add configuration page to menu
|
120 |
*/
|
121 |
-
public function add_options()
|
122 |
{
|
123 |
$this->top_menu_slug = add_menu_page(
|
124 |
-
__('Zemanta', 'zemanta'),
|
125 |
-
__('Zemanta', 'zemanta'),
|
126 |
-
'manage_options', 'zemanta',
|
127 |
-
array($this, 'options'),
|
128 |
plugins_url('/img/menu_icon.png', __FILE__)
|
129 |
);
|
130 |
}
|
@@ -138,7 +139,7 @@ class WPRPZemanta {
|
|
138 |
{
|
139 |
$this->api_key = $this->get_api_key();
|
140 |
|
141 |
-
if (!$this->api_key)
|
142 |
{
|
143 |
$options = get_option('zemanta_options');
|
144 |
|
@@ -148,14 +149,14 @@ class WPRPZemanta {
|
|
148 |
}
|
149 |
|
150 |
$this->api_key = $this->get_api_key();
|
151 |
-
if (!$this->api_key)
|
152 |
{
|
153 |
$this->api_key = $this->fetch_api_key();
|
154 |
-
if ($this->api_key)
|
155 |
{
|
156 |
$this->set_api_key($this->api_key);
|
157 |
-
}
|
158 |
-
else
|
159 |
{
|
160 |
add_action('admin_notices', array($this, 'warning_no_api_key'));
|
161 |
}
|
@@ -185,7 +186,7 @@ class WPRPZemanta {
|
|
185 |
//,'description' => __('Using Zemanta image uploader in this way may download copyrighted images to your blog. Make sure you and your blog writers check and understand licenses of each and every image before using them in your blog posts and delete them if they infringe on author\'s rights.')
|
186 |
)
|
187 |
);
|
188 |
-
|
189 |
$this->options = apply_filters('zemanta_options', $options);
|
190 |
}
|
191 |
|
@@ -250,9 +251,9 @@ class WPRPZemanta {
|
|
250 |
*
|
251 |
* Add configuration page
|
252 |
*/
|
253 |
-
public function options()
|
254 |
{
|
255 |
-
if(!$this->api_key)
|
256 |
{
|
257 |
$this->api_key = $this->fetch_api_key();
|
258 |
$this->set_option('api_key', $this->api_key);
|
@@ -265,7 +266,7 @@ class WPRPZemanta {
|
|
265 |
|
266 |
/**
|
267 |
* sideload_image
|
268 |
-
*
|
269 |
* New image uploader, this is slightly modified version of media_sideload_image from wp-admin/includes/media.php
|
270 |
*
|
271 |
* @param string $file the URL of the image to download
|
@@ -303,7 +304,7 @@ class WPRPZemanta {
|
|
303 |
* is_uploader_enabled
|
304 |
*
|
305 |
*/
|
306 |
-
public function is_uploader_enabled()
|
307 |
{
|
308 |
return $this->get_option('image_uploader');
|
309 |
}
|
@@ -318,11 +319,11 @@ class WPRPZemanta {
|
|
318 |
// do not process revisions, autosaves and auto-drafts
|
319 |
if(wp_is_post_revision($post_id) || wp_is_post_autosave($post_id) || get_post_status($post_id) == 'auto-draft' || isset($_POST['autosave']))
|
320 |
return;
|
321 |
-
|
322 |
// do not process if uploader disabled
|
323 |
if(!$this->is_uploader_enabled())
|
324 |
return;
|
325 |
-
|
326 |
$content = stripslashes($_POST['post_content']);
|
327 |
$nlcontent = str_replace("\n", '', $content);
|
328 |
$urls = array();
|
@@ -334,9 +335,9 @@ class WPRPZemanta {
|
|
334 |
// @deprecated
|
335 |
if(preg_match_all('/<div[^>]+zemanta-img[^>]+>.+?<\/div>/', $nlcontent, $matches))
|
336 |
{
|
337 |
-
foreach($matches[0] as $str)
|
338 |
{
|
339 |
-
if(preg_match('/src="([^"]+)"/', $str, $srcurl))
|
340 |
{
|
341 |
if(preg_match('/href="([^"]+)"/', $str, $desc))
|
342 |
$descs[] = $desc[1];
|
@@ -350,13 +351,13 @@ class WPRPZemanta {
|
|
350 |
|
351 |
// this code looks for all images in the post
|
352 |
// extracts alt and src attributes for image downloader
|
353 |
-
if(preg_match_all('/<img .*?src="[^"]+".*?>/', $nlcontent, $matches))
|
354 |
{
|
355 |
-
foreach($matches[0] as $str)
|
356 |
{
|
357 |
if(preg_match('/src="([^"]+)"/', $str, $srcurl))
|
358 |
{
|
359 |
-
if(!in_array($srcurl[1], $urls))
|
360 |
{
|
361 |
if(preg_match('/alt="([^"]+)"/', $str, $desc))
|
362 |
$descs[] = strlen($desc[1]) ? $desc[1] : $srcurl[1];
|
@@ -368,21 +369,21 @@ class WPRPZemanta {
|
|
368 |
}
|
369 |
}
|
370 |
}
|
371 |
-
|
372 |
// do not do anything if there no images found in the post
|
373 |
if(empty($urls))
|
374 |
return;
|
375 |
-
|
376 |
// download images to blog and replace external URLs with local
|
377 |
for($i = 0, $c = sizeof($urls); $i < $c; $i++)
|
378 |
{
|
379 |
$url = $urls[$i];
|
380 |
$desc = $descs[$i];
|
381 |
-
|
382 |
// skip images from img.zemanta.com and FMP
|
383 |
if(strpos($url, 'http://img.zemanta.com/') !== false || preg_match('#https?://.+\.fmpub\.net/#i', $url))
|
384 |
continue;
|
385 |
-
|
386 |
// skip if already hosted on our blog
|
387 |
if(strpos($url, get_bloginfo('url')) !== false) {
|
388 |
continue;
|
@@ -392,19 +393,19 @@ class WPRPZemanta {
|
|
392 |
if(!is_wp_error($localurl) && !empty($localurl))
|
393 |
$content = str_replace($url, $localurl, $content);
|
394 |
}
|
395 |
-
|
396 |
// unhook this function so it doesn't loop infinitely
|
397 |
remove_action('save_post', array($this, 'save_post'), 20);
|
398 |
-
|
399 |
// put modified content back to _POST so other plugins can reuse it
|
400 |
$_POST['post_content'] = addslashes($content);
|
401 |
-
|
402 |
// update post in database
|
403 |
wp_update_post(array(
|
404 |
-
'ID' => $post_id,
|
405 |
'post_content' => $content)
|
406 |
);
|
407 |
-
|
408 |
// re-hook this function
|
409 |
add_action('save_post', array($this, 'save_post'), 20);
|
410 |
}
|
@@ -421,18 +422,18 @@ class WPRPZemanta {
|
|
421 |
$arguments = array_merge($arguments, array(
|
422 |
'api_key'=> $this->api_key
|
423 |
));
|
424 |
-
|
425 |
if (!isset($arguments['format']))
|
426 |
{
|
427 |
$arguments['format'] = 'xml';
|
428 |
}
|
429 |
-
|
430 |
return wp_remote_post($this->api_url, array('method' => 'POST', 'body' => $arguments));
|
431 |
}
|
432 |
-
|
433 |
/**
|
434 |
* ajax_error
|
435 |
-
*
|
436 |
* Helper function to throw WP_Errors to ajax as json
|
437 |
*/
|
438 |
public function ajax_error($wp_error) {
|
@@ -446,7 +447,7 @@ class WPRPZemanta {
|
|
446 |
)));
|
447 |
}
|
448 |
}
|
449 |
-
|
450 |
/**
|
451 |
* ajax_zemanta_set_featured_image
|
452 |
*
|
@@ -456,16 +457,16 @@ class WPRPZemanta {
|
|
456 |
public function ajax_zemanta_set_featured_image()
|
457 |
{
|
458 |
global $post_ID;
|
459 |
-
|
460 |
if(!isset($this->supported_features['featured_image'])) {
|
461 |
$this->ajax_error(new WP_Error(4, __('Featured image feature is not supported on current platform.', 'zemanta')));
|
462 |
}
|
463 |
-
|
464 |
$args = wp_parse_args($_REQUEST, array('post_id' => 0, 'image_url' => ''));
|
465 |
extract($args);
|
466 |
-
|
467 |
$post_id = (int)$post_id;
|
468 |
-
|
469 |
if(!empty($image_url) && $post_id)
|
470 |
{
|
471 |
$http_response = wp_remote_get($image_url, array('timeout' => 10));
|
@@ -473,15 +474,15 @@ class WPRPZemanta {
|
|
473 |
if(!is_wp_error($http_response))
|
474 |
{
|
475 |
$data = wp_remote_retrieve_body($http_response);
|
476 |
-
|
477 |
// throw error if there no data
|
478 |
if(empty($data)) {
|
479 |
$this->ajax_error(new WP_Error(5, __('Featured image has invalid data.', 'zemanta')));
|
480 |
}
|
481 |
-
|
482 |
$upload = wp_upload_bits(basename($image_url), null, $data);
|
483 |
|
484 |
-
if(!is_wp_error($upload) && !$upload['error'])
|
485 |
{
|
486 |
$filename = $upload['file'];
|
487 |
$wp_filetype = wp_check_filetype(basename($filename), null );
|
@@ -494,10 +495,10 @@ class WPRPZemanta {
|
|
494 |
$attach_id = wp_insert_attachment($attachment, $filename, $post_id);
|
495 |
$attach_data = wp_generate_attachment_metadata($attach_id, $filename);
|
496 |
wp_update_attachment_metadata($attach_id, $attach_data);
|
497 |
-
|
498 |
// this is necessary, or _wp_post_thumbnail_html returns broken remove link
|
499 |
$post_ID = $post_id;
|
500 |
-
|
501 |
// set_post_thumbnail available only since WordPress 3.1
|
502 |
if(set_post_thumbnail($post_id, $attach_id)) {
|
503 |
die(json_encode(array(
|
@@ -516,7 +517,7 @@ class WPRPZemanta {
|
|
516 |
$this->ajax_error(new WP_Error(3, sprintf(__('An error occurred while image download: %s', 'zemanta'), $http_response->get_error_message())));
|
517 |
}
|
518 |
}
|
519 |
-
|
520 |
die(0);
|
521 |
}
|
522 |
|
@@ -525,7 +526,7 @@ class WPRPZemanta {
|
|
525 |
*
|
526 |
* Get API Key
|
527 |
*/
|
528 |
-
public function fetch_api_key()
|
529 |
{
|
530 |
$response = $this->api(array(
|
531 |
'method' => 'zemanta.auth.create_user',
|
@@ -549,7 +550,7 @@ class WPRPZemanta {
|
|
549 |
*
|
550 |
* Adds Shim to Edit Page for Zemanta Plugin
|
551 |
*/
|
552 |
-
public function shim()
|
553 |
{
|
554 |
echo '<div id="zemanta-sidebar"></div>';
|
555 |
}
|
@@ -603,7 +604,7 @@ class WPRPZemanta {
|
|
603 |
* @param string $name Name of option to set
|
604 |
* @param string $value Value of option
|
605 |
*/
|
606 |
-
protected function set_option($name, $value)
|
607 |
{
|
608 |
$options = get_option('zemanta_options');
|
609 |
|
@@ -623,7 +624,7 @@ class WPRPZemanta {
|
|
623 |
* get_api_key
|
624 |
*
|
625 |
* Get API Key
|
626 |
-
*/
|
627 |
public function get_api_key()
|
628 |
{
|
629 |
return $this->get_option('api_key');
|
@@ -635,8 +636,8 @@ class WPRPZemanta {
|
|
635 |
* Get API Key
|
636 |
*
|
637 |
* @param string $api_key API Key to set
|
638 |
-
*/
|
639 |
-
protected function set_api_key($api_key)
|
640 |
{
|
641 |
$this->set_option('api_key', $api_key);
|
642 |
}
|
@@ -648,7 +649,7 @@ class WPRPZemanta {
|
|
648 |
*
|
649 |
* @return boolean
|
650 |
*/
|
651 |
-
protected function check_dependencies()
|
652 |
{
|
653 |
return ((function_exists('curl_init') || ini_get('allow_url_fopen')) && (function_exists('preg_match') || function_exists('ereg')));
|
654 |
}
|
@@ -686,8 +687,8 @@ class WPRPZemanta {
|
|
686 |
* @param array $arguments Arguments to pass to file
|
687 |
* @param boolean $return Whether or not to return the output or print it
|
688 |
*/
|
689 |
-
protected function render($view, $arguments = array(), $return = false)
|
690 |
-
{
|
691 |
$view_file = untrailingslashit(dirname(__FILE__)) . '/views/' . $view . '.php';
|
692 |
|
693 |
extract($arguments, EXTR_SKIP);
|
26 |
return;
|
27 |
}
|
28 |
global $wp_version;
|
29 |
+
|
30 |
// initialize update notes shown once on plugin update
|
31 |
$this->update_notes['1.0.5'] = __('Please double-check your upload paths in Zemanta Settings, we changed some things that might affect your images.', 'zemanta');
|
32 |
$this->update_notes['1.0.7'] = __('Please double-check your upload paths in Zemanta Settings, we changed some things that might affect your images.', 'zemanta');
|
33 |
$this->update_notes['1.0.8'] = __('Please double-check your upload paths in Zemanta Settings, we changed some things that might affect your images.', 'zemanta');
|
34 |
+
|
35 |
add_action('admin_init', array($this, 'init'));
|
36 |
add_action('admin_init', array($this, 'register_options'));
|
37 |
|
|
|
|
|
38 |
$this->supported_features['featured_image'] = version_compare($wp_version, '3.1', '>=') >= 0;
|
39 |
}
|
40 |
+
|
41 |
/**
|
42 |
* admin_init
|
43 |
*
|
50 |
add_action('edit_form_advanced', array($this, 'assets'), 1);
|
51 |
add_action('edit_page_form', array($this, 'assets'), 1);
|
52 |
add_action('save_post', array($this, 'save_post'), 20);
|
53 |
+
|
54 |
$this->check_plugin_updated();
|
55 |
$this->create_options();
|
56 |
$this->check_options();
|
57 |
+
|
58 |
if(!$this->check_dependencies())
|
59 |
add_action('admin_notices', array($this, 'warning'));
|
60 |
+
|
61 |
+
global $wprp_mp;
|
62 |
+
$wprp_mp->active_install("wprp");
|
63 |
}
|
64 |
|
65 |
/**
|
67 |
*
|
68 |
* Run any functions needed for plugin activation
|
69 |
*/
|
70 |
+
public function activate()
|
71 |
{
|
72 |
$this->fix_user_meta();
|
73 |
}
|
77 |
*
|
78 |
* Add any assets to the edit page
|
79 |
*/
|
80 |
+
public function assets()
|
81 |
+
{
|
82 |
$this->render('assets', array(
|
83 |
'api_key' => $this->api_key,
|
84 |
'version' => $this->version,
|
119 |
*
|
120 |
* Add configuration page to menu
|
121 |
*/
|
122 |
+
public function add_options()
|
123 |
{
|
124 |
$this->top_menu_slug = add_menu_page(
|
125 |
+
__('Zemanta', 'zemanta'),
|
126 |
+
__('Zemanta', 'zemanta'),
|
127 |
+
'manage_options', 'zemanta',
|
128 |
+
array($this, 'options'),
|
129 |
plugins_url('/img/menu_icon.png', __FILE__)
|
130 |
);
|
131 |
}
|
139 |
{
|
140 |
$this->api_key = $this->get_api_key();
|
141 |
|
142 |
+
if (!$this->api_key)
|
143 |
{
|
144 |
$options = get_option('zemanta_options');
|
145 |
|
149 |
}
|
150 |
|
151 |
$this->api_key = $this->get_api_key();
|
152 |
+
if (!$this->api_key)
|
153 |
{
|
154 |
$this->api_key = $this->fetch_api_key();
|
155 |
+
if ($this->api_key)
|
156 |
{
|
157 |
$this->set_api_key($this->api_key);
|
158 |
+
}
|
159 |
+
else
|
160 |
{
|
161 |
add_action('admin_notices', array($this, 'warning_no_api_key'));
|
162 |
}
|
186 |
//,'description' => __('Using Zemanta image uploader in this way may download copyrighted images to your blog. Make sure you and your blog writers check and understand licenses of each and every image before using them in your blog posts and delete them if they infringe on author\'s rights.')
|
187 |
)
|
188 |
);
|
189 |
+
|
190 |
$this->options = apply_filters('zemanta_options', $options);
|
191 |
}
|
192 |
|
251 |
*
|
252 |
* Add configuration page
|
253 |
*/
|
254 |
+
public function options()
|
255 |
{
|
256 |
+
if(!$this->api_key)
|
257 |
{
|
258 |
$this->api_key = $this->fetch_api_key();
|
259 |
$this->set_option('api_key', $this->api_key);
|
266 |
|
267 |
/**
|
268 |
* sideload_image
|
269 |
+
*
|
270 |
* New image uploader, this is slightly modified version of media_sideload_image from wp-admin/includes/media.php
|
271 |
*
|
272 |
* @param string $file the URL of the image to download
|
304 |
* is_uploader_enabled
|
305 |
*
|
306 |
*/
|
307 |
+
public function is_uploader_enabled()
|
308 |
{
|
309 |
return $this->get_option('image_uploader');
|
310 |
}
|
319 |
// do not process revisions, autosaves and auto-drafts
|
320 |
if(wp_is_post_revision($post_id) || wp_is_post_autosave($post_id) || get_post_status($post_id) == 'auto-draft' || isset($_POST['autosave']))
|
321 |
return;
|
322 |
+
|
323 |
// do not process if uploader disabled
|
324 |
if(!$this->is_uploader_enabled())
|
325 |
return;
|
326 |
+
|
327 |
$content = stripslashes($_POST['post_content']);
|
328 |
$nlcontent = str_replace("\n", '', $content);
|
329 |
$urls = array();
|
335 |
// @deprecated
|
336 |
if(preg_match_all('/<div[^>]+zemanta-img[^>]+>.+?<\/div>/', $nlcontent, $matches))
|
337 |
{
|
338 |
+
foreach($matches[0] as $str)
|
339 |
{
|
340 |
+
if(preg_match('/src="([^"]+)"/', $str, $srcurl))
|
341 |
{
|
342 |
if(preg_match('/href="([^"]+)"/', $str, $desc))
|
343 |
$descs[] = $desc[1];
|
351 |
|
352 |
// this code looks for all images in the post
|
353 |
// extracts alt and src attributes for image downloader
|
354 |
+
if(preg_match_all('/<img .*?src="[^"]+".*?>/', $nlcontent, $matches))
|
355 |
{
|
356 |
+
foreach($matches[0] as $str)
|
357 |
{
|
358 |
if(preg_match('/src="([^"]+)"/', $str, $srcurl))
|
359 |
{
|
360 |
+
if(!in_array($srcurl[1], $urls))
|
361 |
{
|
362 |
if(preg_match('/alt="([^"]+)"/', $str, $desc))
|
363 |
$descs[] = strlen($desc[1]) ? $desc[1] : $srcurl[1];
|
369 |
}
|
370 |
}
|
371 |
}
|
372 |
+
|
373 |
// do not do anything if there no images found in the post
|
374 |
if(empty($urls))
|
375 |
return;
|
376 |
+
|
377 |
// download images to blog and replace external URLs with local
|
378 |
for($i = 0, $c = sizeof($urls); $i < $c; $i++)
|
379 |
{
|
380 |
$url = $urls[$i];
|
381 |
$desc = $descs[$i];
|
382 |
+
|
383 |
// skip images from img.zemanta.com and FMP
|
384 |
if(strpos($url, 'http://img.zemanta.com/') !== false || preg_match('#https?://.+\.fmpub\.net/#i', $url))
|
385 |
continue;
|
386 |
+
|
387 |
// skip if already hosted on our blog
|
388 |
if(strpos($url, get_bloginfo('url')) !== false) {
|
389 |
continue;
|
393 |
if(!is_wp_error($localurl) && !empty($localurl))
|
394 |
$content = str_replace($url, $localurl, $content);
|
395 |
}
|
396 |
+
|
397 |
// unhook this function so it doesn't loop infinitely
|
398 |
remove_action('save_post', array($this, 'save_post'), 20);
|
399 |
+
|
400 |
// put modified content back to _POST so other plugins can reuse it
|
401 |
$_POST['post_content'] = addslashes($content);
|
402 |
+
|
403 |
// update post in database
|
404 |
wp_update_post(array(
|
405 |
+
'ID' => $post_id,
|
406 |
'post_content' => $content)
|
407 |
);
|
408 |
+
|
409 |
// re-hook this function
|
410 |
add_action('save_post', array($this, 'save_post'), 20);
|
411 |
}
|
422 |
$arguments = array_merge($arguments, array(
|
423 |
'api_key'=> $this->api_key
|
424 |
));
|
425 |
+
|
426 |
if (!isset($arguments['format']))
|
427 |
{
|
428 |
$arguments['format'] = 'xml';
|
429 |
}
|
430 |
+
|
431 |
return wp_remote_post($this->api_url, array('method' => 'POST', 'body' => $arguments));
|
432 |
}
|
433 |
+
|
434 |
/**
|
435 |
* ajax_error
|
436 |
+
*
|
437 |
* Helper function to throw WP_Errors to ajax as json
|
438 |
*/
|
439 |
public function ajax_error($wp_error) {
|
447 |
)));
|
448 |
}
|
449 |
}
|
450 |
+
|
451 |
/**
|
452 |
* ajax_zemanta_set_featured_image
|
453 |
*
|
457 |
public function ajax_zemanta_set_featured_image()
|
458 |
{
|
459 |
global $post_ID;
|
460 |
+
|
461 |
if(!isset($this->supported_features['featured_image'])) {
|
462 |
$this->ajax_error(new WP_Error(4, __('Featured image feature is not supported on current platform.', 'zemanta')));
|
463 |
}
|
464 |
+
|
465 |
$args = wp_parse_args($_REQUEST, array('post_id' => 0, 'image_url' => ''));
|
466 |
extract($args);
|
467 |
+
|
468 |
$post_id = (int)$post_id;
|
469 |
+
|
470 |
if(!empty($image_url) && $post_id)
|
471 |
{
|
472 |
$http_response = wp_remote_get($image_url, array('timeout' => 10));
|
474 |
if(!is_wp_error($http_response))
|
475 |
{
|
476 |
$data = wp_remote_retrieve_body($http_response);
|
477 |
+
|
478 |
// throw error if there no data
|
479 |
if(empty($data)) {
|
480 |
$this->ajax_error(new WP_Error(5, __('Featured image has invalid data.', 'zemanta')));
|
481 |
}
|
482 |
+
|
483 |
$upload = wp_upload_bits(basename($image_url), null, $data);
|
484 |
|
485 |
+
if(!is_wp_error($upload) && !$upload['error'])
|
486 |
{
|
487 |
$filename = $upload['file'];
|
488 |
$wp_filetype = wp_check_filetype(basename($filename), null );
|
495 |
$attach_id = wp_insert_attachment($attachment, $filename, $post_id);
|
496 |
$attach_data = wp_generate_attachment_metadata($attach_id, $filename);
|
497 |
wp_update_attachment_metadata($attach_id, $attach_data);
|
498 |
+
|
499 |
// this is necessary, or _wp_post_thumbnail_html returns broken remove link
|
500 |
$post_ID = $post_id;
|
501 |
+
|
502 |
// set_post_thumbnail available only since WordPress 3.1
|
503 |
if(set_post_thumbnail($post_id, $attach_id)) {
|
504 |
die(json_encode(array(
|
517 |
$this->ajax_error(new WP_Error(3, sprintf(__('An error occurred while image download: %s', 'zemanta'), $http_response->get_error_message())));
|
518 |
}
|
519 |
}
|
520 |
+
|
521 |
die(0);
|
522 |
}
|
523 |
|
526 |
*
|
527 |
* Get API Key
|
528 |
*/
|
529 |
+
public function fetch_api_key()
|
530 |
{
|
531 |
$response = $this->api(array(
|
532 |
'method' => 'zemanta.auth.create_user',
|
550 |
*
|
551 |
* Adds Shim to Edit Page for Zemanta Plugin
|
552 |
*/
|
553 |
+
public function shim()
|
554 |
{
|
555 |
echo '<div id="zemanta-sidebar"></div>';
|
556 |
}
|
604 |
* @param string $name Name of option to set
|
605 |
* @param string $value Value of option
|
606 |
*/
|
607 |
+
protected function set_option($name, $value)
|
608 |
{
|
609 |
$options = get_option('zemanta_options');
|
610 |
|
624 |
* get_api_key
|
625 |
*
|
626 |
* Get API Key
|
627 |
+
*/
|
628 |
public function get_api_key()
|
629 |
{
|
630 |
return $this->get_option('api_key');
|
636 |
* Get API Key
|
637 |
*
|
638 |
* @param string $api_key API Key to set
|
639 |
+
*/
|
640 |
+
protected function set_api_key($api_key)
|
641 |
{
|
642 |
$this->set_option('api_key', $api_key);
|
643 |
}
|
649 |
*
|
650 |
* @return boolean
|
651 |
*/
|
652 |
+
protected function check_dependencies()
|
653 |
{
|
654 |
return ((function_exists('curl_init') || ini_get('allow_url_fopen')) && (function_exists('preg_match') || function_exists('ereg')));
|
655 |
}
|
687 |
* @param array $arguments Arguments to pass to file
|
688 |
* @param boolean $return Whether or not to return the output or print it
|
689 |
*/
|
690 |
+
protected function render($view, $arguments = array(), $return = false)
|
691 |
+
{
|
692 |
$view_file = untrailingslashit(dirname(__FILE__)) . '/views/' . $view . '.php';
|
693 |
|
694 |
extract($arguments, EXTR_SKIP);
|