← All changes
|
includes/admin/tracking/class-charitable-tracking.php
+298
-46
1.8.12
→
1.8.13
View file →
| @@ -175,8 +175,40 @@ | ||
| 175 | 175 | $data['recommended_plugins_installed'] = get_option( 'charitable_recommended_plugins_installed', array() ); |
| 176 | 176 | $data['recommended_plugins_activated'] = get_option( 'charitable_recommended_plugins_activated', array() ); |
| 177 | 177 | $data['dashboard_enhance_section_views'] = get_option( 'charitable_dashboard_enhance_views', 0 ); |
| 178 | 178 | |
| 179 | + /* | |
| 180 | + * Added BEFORE the two plugin lists, deliberately. | |
| 181 | + * | |
| 182 | + * The body is form-encoded and the receiver runs | |
| 183 | + * max_input_vars = 1000 (measured), which PHP enforces by silently | |
| 184 | + * DROPPING variables past the limit rather than erroring — so | |
| 185 | + * whatever is last in this array is first to be lost. active_plugins | |
| 186 | + * and inactive_plugins are the only unbounded part of this payload, | |
| 187 | + * and they are also the least valuable, so the bounded fields belong | |
| 188 | + * in front of them. | |
| 189 | + * | |
| 190 | + * @since 1.8.13 | |
| 191 | + */ | |
| 192 | + $data['abilities'] = $this->get_abilities_data(); | |
| 193 | + | |
| 194 | + /* | |
| 195 | + * Mirrors the cross-brand AM promo-attribution loop. The options are | |
| 196 | + * unprefixed; we claim only installs whose source names Charitable. | |
| 197 | + * | |
| 198 | + * @since 1.8.13 | |
| 199 | + */ | |
| 200 | + $promo = array( 'wpconsent', 'sugar-calendar', 'duplicator', 'uncannyautomator', 'activelayer', 'wpvibe' ); | |
| 201 | + | |
| 202 | + foreach ( $promo as $plugin ) { | |
| 203 | + $source = (string) get_option( $plugin . '_source', '' ); | |
| 204 | + $date = (int) get_option( $plugin . '_date', 0 ); | |
| 205 | + | |
| 206 | + if ( $date && false !== strpos( $source, 'Charitable' ) ) { | |
| 207 | + $data[ 'charitable_' . $plugin . '_date' ] = $date; | |
| 208 | + } | |
| 209 | + } | |
| 210 | + | |
| 179 | 211 | // Retrieve current plugin information. |
| 180 | 212 | if ( ! function_exists( 'get_plugins' ) ) { |
| 181 | 213 | include ABSPATH . '/wp-admin/includes/plugin.php'; |
| 182 | 214 | } |
| @@ -197,10 +229,219 @@ | ||
| 197 | 229 | |
| 198 | 230 | return $data; |
| 199 | 231 | } |
| 200 | 232 | |
| 233 | + /** | |
| 234 | + * Abilities / AI-assistant usage for the opt-in check-in. | |
| 235 | + * | |
| 236 | + * Fifteen fields: six integers, five booleans, two dates, one client | |
| 237 | + * slug and one JSON map. Nothing else, and in particular nothing that | |
| 238 | + * identifies a campaign, a donation, a donor or an administrator — the | |
| 239 | + * whole payload is counts drawn from Charitable_Abilities_Usage, whose | |
| 240 | + * stored keys are a closed vocabulary of registered ability slugs. | |
| 241 | + * | |
| 242 | + * A `scopes` map was briefly the sixteenth, reporting which of three | |
| 243 | + * write permissions a site had granted. Write access went back to one | |
| 244 | + * switch before 1.8.13 shipped, and `write` already carries that. | |
| 245 | + * | |
| 246 | + * PUBLIC on purpose, like get_charitable_data() and get_donation_data(). | |
| 247 | + * get_optin_data() is private, so a block built inline there cannot be | |
| 248 | + * asserted by a test, and this is the payload where an untested field is | |
| 249 | + * most expensive: the usage receiver reads named keys one at a time, so a | |
| 250 | + * field that is misnamed or the wrong shape is not an error anywhere — it | |
| 251 | + * is silently dropped on arrival and cannot be backfilled. | |
| 252 | + * | |
| 253 | + * ⚠️ TWO FIELDS ARE DELIBERATELY ABSENT and must not be added here. | |
| 254 | + * `rest_visible` (the count of abilities a REST client can actually see) | |
| 255 | + * and application-password availability are both measured by seams that | |
| 256 | + * are invalid without an authenticated HTTPS request: the first needs | |
| 257 | + * rest_do_request(), which returns an empty list with no user, and the | |
| 258 | + * second consults is_ssl(), which is false with no request. THIS RUNS IN | |
| 259 | + * CRON. Both would report "0 visible" and "not available" across the | |
| 260 | + * entire fleet of healthy sites — and both already did exactly that under | |
| 261 | + * WP-CLI, which is why the two System Info lines that report them refuse | |
| 262 | + * to measure rather than answer. A false catastrophe in a dataset nobody | |
| 263 | + * thinks to distrust is worse than a missing column. | |
| 264 | + * | |
| 265 | + * `observation_complete` IS NEW HERE, added in Lite 1.8.13 and not present | |
| 266 | + * in Pro's version of this method. It reads | |
| 267 | + * Charitable_Abilities_Usage::get_counts() rather than ::get() for exactly | |
| 268 | + * that field: wp_after_execute_ability is core @since 6.9.0 but | |
| 269 | + * wp_ability_invoked is @since 7.1.0, so on WordPress 6.9 and 7.0.x | |
| 270 | + * `calls`/`total` read 0 while `ok` can still climb. Without this flag a | |
| 271 | + * quiet WP-7.0 site and an unused site are indistinguishable on the | |
| 272 | + * receiving end. | |
| 273 | + * | |
| 274 | + * @since 1.8.13 | |
| 275 | + * | |
| 276 | + * @return array<string,int|string> | |
| 277 | + */ | |
| 278 | + public function get_abilities_data() { | |
| 201 | 279 | |
| 280 | + $usage = class_exists( 'Charitable_Abilities_Usage' ) | |
| 281 | + ? Charitable_Abilities_Usage::get_counts() | |
| 282 | + : array( | |
| 283 | + 'calls' => array(), | |
| 284 | + 'total' => 0, | |
| 285 | + 'ok' => 0, | |
| 286 | + 'denied' => 0, | |
| 287 | + 'refused' => 0, | |
| 288 | + 'failed' => 0, | |
| 289 | + 'first_at' => '', | |
| 290 | + 'last_at' => '', | |
| 291 | + 'observation_complete' => 0, | |
| 292 | + ); | |
| 293 | + | |
| 294 | + /* | |
| 295 | + * Integers, not booleans, for the flags. On the wire the two are | |
| 296 | + * identical — http_build_query() encodes true/false as 1/0 — and the | |
| 297 | + * receiver parses with filter_var( FILTER_VALIDATE_BOOLEAN ), which | |
| 298 | + * takes either. Casting here is so the shape is unambiguous to a test | |
| 299 | + * and to anyone reading a logged payload, where a bare `false` would | |
| 300 | + * render as an empty string. | |
| 301 | + */ | |
| 302 | + return array( | |
| 303 | + 'api' => (int) function_exists( 'wp_register_ability' ), | |
| 304 | + 'registered' => $this->count_registered_abilities(), | |
| 305 | + 'write' => (int) ( class_exists( 'Charitable_Abilities_Registrar' ) && Charitable_Abilities_Registrar::write_enabled() ), | |
| 306 | + 'client' => $this->get_mcp_client_slug(), | |
| 307 | + 'logs' => (int) ( ! class_exists( 'Charitable_Log' ) || Charitable_Log::is_enabled() ), | |
| 308 | + | |
| 309 | + /* | |
| 310 | + * Passed the empty string as the second argument because the | |
| 311 | + * filter is per-ability and there is no ability in hand here. | |
| 312 | + * This reports the site's DEFAULT answer, which is the only | |
| 313 | + * meaningful one fleet-wide. | |
| 314 | + */ | |
| 315 | + 'read_logs' => (int) apply_filters( 'charitable_abilities_log_reads', false, '' ), | |
| 316 | + | |
| 317 | + /* | |
| 318 | + * A JSON STRING, never a nested array, and this is load-bearing | |
| 319 | + * twice over. A nested abilities[calls][list-campaigns]=41 shape | |
| 320 | + * invites the `[]=x` payload that has twice forced a guard into | |
| 321 | + * the receiver (a (string) cast on an array raises a warning that | |
| 322 | + * Laravel rethrows as an ErrorException, 500ing an otherwise good | |
| 323 | + * check-in). And the body is form-encoded, so a 24-slug map is 24 | |
| 324 | + * more PHP input variables against a max_input_vars budget this | |
| 325 | + * payload already spends heavily on the full active and inactive | |
| 326 | + * plugin lists. One string is one variable. | |
| 327 | + * | |
| 328 | + * Cast to object so an EMPTY map encodes as `{}` rather than | |
| 329 | + * `[]`. wp_json_encode( array() ) emits a JSON array, so without | |
| 330 | + * the cast this field is an object on a site that has used the | |
| 331 | + * feature and an array on one that has not — and most sites have | |
| 332 | + * not, so the array form would be the common case for a field | |
| 333 | + * documented as a map. The receiver tolerates both, but a type | |
| 334 | + * that changes with the data is the kind of thing that breaks a | |
| 335 | + * consumer written against the common case years later. | |
| 336 | + */ | |
| 337 | + 'calls' => (string) wp_json_encode( (object) (array) $usage['calls'] ), | |
| 338 | + | |
| 339 | + /* | |
| 340 | + * Sent rather than recovered by summing `calls` receiver-side. | |
| 341 | + * get_counts() already computes it, so this passes along a value | |
| 342 | + * that exists; without it the headline usage number is only | |
| 343 | + * reachable by summing a JSON blob in every query, against no | |
| 344 | + * index. | |
| 345 | + */ | |
| 346 | + 'total' => (int) $usage['total'], | |
| 347 | + 'ok' => (int) $usage['ok'], | |
| 348 | + 'denied' => (int) $usage['denied'], | |
| 349 | + 'refused' => (int) $usage['refused'], | |
| 350 | + 'failed' => (int) $usage['failed'], | |
| 351 | + 'first_at' => (string) $usage['first_at'], | |
| 352 | + 'last_at' => (string) $usage['last_at'], | |
| 353 | + 'observation_complete' => (int) $usage['observation_complete'], | |
| 354 | + ); | |
| 355 | + } | |
| 356 | + | |
| 202 | 357 | /** |
| 358 | + * How many Charitable abilities are registered. | |
| 359 | + * | |
| 360 | + * A weak signal deliberately kept: one smallint that reveals a build or | |
| 361 | + * a partial deploy where some abilities failed to register at all. | |
| 362 | + * | |
| 363 | + * ⚠️ NOTE THE SIDE EFFECT, which is the reason this is one line of code | |
| 364 | + * and a paragraph of comment. wp_get_abilities() reaches | |
| 365 | + * WP_Abilities_Registry::get_instance(), which fires | |
| 366 | + * `wp_abilities_api_init` lazily on first use — so asking for this count | |
| 367 | + * inside the check-in FORCES the whole abilities surface to register: | |
| 368 | + * four registrar objects and twenty-four wp_register_ability() calls | |
| 369 | + * with their full input and output schemas, in a weekly cron request | |
| 370 | + * that would otherwise never touch any of it. | |
| 371 | + * | |
| 372 | + * Accepted, because it is once a week and the alternative is worse. A | |
| 373 | + * hardcoded 24 drifts the moment an ability is added or removed, and | |
| 374 | + * this field exists precisely to catch a build where the real number is | |
| 375 | + * not 24 — a count that cannot disagree with reality reports nothing. | |
| 376 | + * | |
| 377 | + * Do NOT reach for a cached value here either: a cache populated during | |
| 378 | + * a normal admin request would report yesterday's build in today's | |
| 379 | + * check-in, which is the same failure in slower motion. | |
| 380 | + * | |
| 381 | + * @since 1.8.13 | |
| 382 | + * | |
| 383 | + * @return int | |
| 384 | + */ | |
| 385 | + private function count_registered_abilities() { | |
| 386 | + | |
| 387 | + if ( ! function_exists( 'wp_get_abilities' ) ) { | |
| 388 | + return 0; | |
| 389 | + } | |
| 390 | + | |
| 391 | + $count = 0; | |
| 392 | + | |
| 393 | + foreach ( wp_get_abilities() as $ability ) { | |
| 394 | + if ( is_object( $ability ) && method_exists( $ability, 'get_name' ) && 0 === strpos( $ability->get_name(), 'charitable/' ) ) { | |
| 395 | + ++$count; | |
| 396 | + } | |
| 397 | + } | |
| 398 | + | |
| 399 | + return $count; | |
| 400 | + } | |
| 401 | + | |
| 402 | + /** | |
| 403 | + * The slug of the MCP client this site is running, or '' for none. | |
| 404 | + * | |
| 405 | + * Detection shares Charitable_Abilities' basename map with the System | |
| 406 | + * Info screen, so the two cannot disagree about whether a client is | |
| 407 | + * present. Almost every reporting site is expected to say `wpvibe`; the | |
| 408 | + * value of the field is the day one does not. | |
| 409 | + * | |
| 410 | + * First match wins where two are somehow active. A list would be a | |
| 411 | + * second JSON column to express a state that should not occur, and the | |
| 412 | + * question this answers is "which client", not "how many". | |
| 413 | + * | |
| 414 | + * DELIBERATELY NOT FILTERED, and the asymmetry is worth stating because | |
| 415 | + * it otherwise reads as an oversight: `charitable_abilities_mcp_clients` | |
| 416 | + * changes what System Info displays but has no effect here. That filter | |
| 417 | + * takes and returns human-readable LABELS for a pasted report, so it | |
| 418 | + * cannot supply the slug this field needs, and quietly running labels | |
| 419 | + * through here would write values the receiver sanitises away. A site | |
| 420 | + * using that filter to declare a bespoke client therefore shows it on | |
| 421 | + * screen and reports no client in telemetry, which is the right answer | |
| 422 | + * of the two available: the slug vocabulary is shared with the receiver, | |
| 423 | + * so it is not a site's to extend. | |
| 424 | + * | |
| 425 | + * @since 1.8.13 | |
| 426 | + * | |
| 427 | + * @return string | |
| 428 | + */ | |
| 429 | + private function get_mcp_client_slug() { | |
| 430 | + | |
| 431 | + if ( ! class_exists( 'Charitable_Abilities' ) ) { | |
| 432 | + return ''; | |
| 433 | + } | |
| 434 | + | |
| 435 | + foreach ( Charitable_Abilities::get_active_mcp_clients() as $entry ) { | |
| 436 | + return (string) $entry['slug']; | |
| 437 | + } | |
| 438 | + | |
| 439 | + return ''; | |
| 440 | + } | |
| 441 | + | |
| 442 | + | |
| 443 | + /** | |
| 203 | 444 | * Fetch tracking data. |
| 204 | 445 | * |
| 205 | 446 | * @since 1.8.4 |
| 206 | 447 | * |
| @@ -452,29 +693,41 @@ | ||
| 452 | 693 | $where_sql = array(); |
| 453 | 694 | $where_sql[] = 'WHERE 1=1'; |
| 454 | 695 | $where_sql[] = 'p.post_type = "%s"'; |
| 455 | 696 | $where_sql[] = 'p.post_status = "charitable-active"'; |
| 456 | - $where_sql[] = 'pm1.meta_key = "donation_gateway"'; | |
| 457 | - $where_sql[] = 'pm2.meta_key = "donation_period"'; | |
| 458 | - $where_sql[] = 'pm3.meta_key = "_first_donation"'; | |
| 459 | - $where_sql[] = 'pm4.meta_key = "_expiration_date"'; | |
| 460 | 697 | |
| 461 | 698 | // remove all empty values from the array. |
| 462 | 699 | $where_sql = array_filter( $where_sql ); |
| 463 | 700 | $where_args = implode( ' AND ', $where_sql ); |
| 464 | 701 | |
| 702 | + /* | |
| 703 | + * Each meta_key test belongs in its join's ON clause, NOT in the WHERE clause. A WHERE test | |
| 704 | + * against a left-joined row is an inner join, so until 1.8.12.1 any recurring donation missing | |
| 705 | + * even one of these four meta keys was dropped from the result set entirely - which silently | |
| 706 | + * undercounted recurring_donations_count and every amount derived from these rows. A | |
| 707 | + * subscription with no _expiration_date is perfectly normal, and used to vanish here. | |
| 708 | + */ | |
| 465 | 709 | $left_join = array(); |
| 466 | - $left_join[] = $wpdb->prefix . 'postmeta pm1 ON p.ID = pm1.post_id'; | |
| 467 | - $left_join[] = $wpdb->prefix . 'postmeta pm2 ON p.ID = pm2.post_id'; | |
| 468 | - $left_join[] = $wpdb->prefix . 'postmeta pm3 ON p.ID = pm3.post_id'; | |
| 469 | - $left_join[] = $wpdb->prefix . 'postmeta pm4 ON p.ID = pm4.post_id'; | |
| 710 | + $left_join[] = $wpdb->prefix . 'postmeta pm1 ON p.ID = pm1.post_id AND pm1.meta_key = "donation_gateway"'; | |
| 711 | + $left_join[] = $wpdb->prefix . 'postmeta pm2 ON p.ID = pm2.post_id AND pm2.meta_key = "donation_period"'; | |
| 712 | + $left_join[] = $wpdb->prefix . 'postmeta pm4 ON p.ID = pm4.post_id AND pm4.meta_key = "_expiration_date"'; | |
| 470 | 713 | $left_join = array_filter( $left_join ); |
| 471 | 714 | $left_join_args = 'LEFT JOIN ' . implode( ' LEFT JOIN ', $left_join ); |
| 472 | 715 | |
| 473 | - $sql = "SELECT SUM(pm4.meta_value) AS total_recurring_amount, | |
| 474 | - pm1.meta_value AS payment_gateway, | |
| 716 | + /* | |
| 717 | + * No SUM() here any more. Until 1.8.12.1 this selected `SUM(pm4.meta_value) AS | |
| 718 | + * total_recurring_amount` - i.e. it summed the _expiration_date meta, a DATE, which cast to | |
| 719 | + * '2026-06-15 23:59:59' -> 2026. Nothing ever read that alias (the aggregator re-sums in PHP), | |
| 720 | + * and with GROUP BY p.ID it was not even an aggregate. It is gone because it was the single | |
| 721 | + * most misleading line in this function: it implied the recurring amount came from a date. | |
| 722 | + * | |
| 723 | + * pm3 (_first_donation) is no longer selected either. It is a donation POST ID, not an amount | |
| 724 | + * - the Recurring addon writes it as update_post_meta( $recurring_id, '_first_donation', | |
| 725 | + * $donation_id ) - and it was previously summed as money. See | |
| 726 | + * summarise_recurring_donations() for what replaced it. | |
| 727 | + */ | |
| 728 | + $sql = "SELECT pm1.meta_value AS payment_gateway, | |
| 475 | 729 | pm2.meta_value AS donation_period, |
| 476 | - pm3.meta_value AS first_donation, | |
| 477 | 730 | pm4.meta_value AS expiration_date, |
| 478 | 731 | p.post_date AS post_date, |
| 479 | 732 | p.post_date_gmt AS post_date_gmt, |
| 480 | 733 | p.post_status AS donation_status, |
| @@ -499,21 +752,37 @@ | ||
| 499 | 752 | * |
| 500 | 753 | * Extracted from get_recurring_donation_data() so it is testable without a |
| 501 | 754 | * $wpdb stub. |
| 502 | 755 | * |
| 503 | - * recurring_donations_count is NEW in 1.8.17.4 and is the point. Stage 5 | |
| 504 | - * Power leans on recurring activity, but the only recurring signal in the | |
| 505 | - * payload was total_recurring_amount, a money figure. On the cloud side | |
| 506 | - * subscription_analyses covers just 193 of 2,547 accounts and | |
| 507 | - * RefreshSubscriptionAnalysisCommand never writes active_subscriptions at | |
| 508 | - * all, so there was no reliable recurring COUNT anywhere. | |
| 756 | + * recurring_donations_count is the point. Stage 5 Power leans on recurring | |
| 757 | + * activity, but the only recurring signal in the payload was | |
| 758 | + * total_recurring_amount. On the cloud side subscription_analyses covers just | |
| 759 | + * 193 of 2,547 accounts and RefreshSubscriptionAnalysisCommand never writes | |
| 760 | + * active_subscriptions at all, so there was no reliable recurring COUNT | |
| 761 | + * anywhere. | |
| 509 | 762 | * |
| 510 | - * NOTE ON $donation->first_donation: in this query that is the | |
| 511 | - * _first_donation POST META, which is a MONEY AMOUNT, not a date. It shares | |
| 512 | - * a name with the charitable_first_donation option, which IS a date and is | |
| 513 | - * reported separately as $data['first_donation']. Do not conflate them. | |
| 763 | + * CHANGED IN 1.8.12.1. This function used to report five money figures, all of | |
| 764 | + * which were sums of donation POST IDs, because it read the _first_donation | |
| 765 | + * post meta as an amount. It is not an amount: the Recurring addon writes it | |
| 766 | + * as update_post_meta( $recurring_id, '_first_donation', $donation_id ), and | |
| 767 | + * its accessor is get_first_donation_id(). Measured on a real site, a | |
| 768 | + * subscription whose first donation was 3.68 contributed 309, and one whose | |
| 769 | + * first-donation post had been deleted still contributed its stale ID. | |
| 514 | 770 | * |
| 771 | + * Removed: total_recurring_amount, recurring_donations_7_days, | |
| 772 | + * recurring_donations_30_days. There is no correct value to put in them here - | |
| 773 | + * the pledge amount lives only in the serialized `campaigns` post meta, | |
| 774 | + * recurring donations have no charitable_campaign_donations row, and some | |
| 775 | + * active subscriptions record no amount at all. | |
| 776 | + * | |
| 777 | + * Changed: recurring_donations_payment_gateway and recurring_donations_period | |
| 778 | + * are now COUNTS of active subscriptions rather than (bogus) amounts. | |
| 779 | + * | |
| 780 | + * Do not confuse _first_donation with the charitable_first_donation option, | |
| 781 | + * which IS a date and is reported separately as $data['first_donation']. | |
| 782 | + * | |
| 515 | 783 | * @since 1.8.12 |
| 784 | + * @version 1.8.12.1 | |
| 516 | 785 | * |
| 517 | 786 | * @param array $recurring_donations Rows from the recurring query. |
| 518 | 787 | * @return array |
| 519 | 788 | */ |
| @@ -518,35 +787,21 @@ | ||
| 518 | 787 | * @return array |
| 519 | 788 | */ |
| 520 | 789 | private function summarise_recurring_donations( array $recurring_donations ) { |
| 521 | 790 | |
| 522 | - $total_recurring_amount = 0; | |
| 523 | 791 | $recurring_count = 0; |
| 524 | - $recurring_donations_7_days = 0; | |
| 525 | - $recurring_donations_30_days = 0; | |
| 526 | 792 | $recurring_donations_payment_gateway = array(); |
| 527 | 793 | $recurring_donations_period = array(); |
| 528 | 794 | |
| 529 | - $cutoff_7 = strtotime( '-7 days' ); | |
| 530 | - $cutoff_30 = strtotime( '-30 days' ); | |
| 531 | - | |
| 532 | 795 | foreach ( $recurring_donations as $donation ) { |
| 533 | 796 | |
| 534 | - // A money amount, despite the name. See the docblock. | |
| 535 | - $amount = isset( $donation->first_donation ) ? $donation->first_donation : 0; | |
| 536 | - $when = isset( $donation->post_date ) ? strtotime( (string) $donation->post_date ) : false; | |
| 537 | - | |
| 538 | - $total_recurring_amount += $amount; | |
| 539 | 797 | ++$recurring_count; |
| 540 | 798 | |
| 541 | - if ( false !== $when && $when > $cutoff_7 ) { | |
| 542 | - $recurring_donations_7_days += $amount; | |
| 543 | - } | |
| 544 | - | |
| 545 | - if ( false !== $when && $when > $cutoff_30 ) { | |
| 546 | - $recurring_donations_30_days += $amount; | |
| 547 | - } | |
| 548 | - | |
| 799 | + // Counts, not amounts. This query cannot produce a reliable recurring money figure: the | |
| 800 | + // pledge amount lives only inside the serialized `campaigns` post meta, recurring | |
| 801 | + // donations have no charitable_campaign_donations row, and some active subscriptions | |
| 802 | + // record no amount at all. A count of active subscriptions per gateway and per period is | |
| 803 | + // the useful thing it CAN compute correctly, so that is what these now report. | |
| 549 | 804 | $gateway = isset( $donation->payment_gateway ) ? (string) $donation->payment_gateway : ''; |
| 550 | 805 | |
| 551 | 806 | if ( ! isset( $recurring_donations_payment_gateway[ $gateway ] ) ) { |
| 552 | 807 | $recurring_donations_payment_gateway[ $gateway ] = 0; |
| @@ -551,9 +806,9 @@ | ||
| 551 | 806 | if ( ! isset( $recurring_donations_payment_gateway[ $gateway ] ) ) { |
| 552 | 807 | $recurring_donations_payment_gateway[ $gateway ] = 0; |
| 553 | 808 | } |
| 554 | 809 | |
| 555 | - $recurring_donations_payment_gateway[ $gateway ] += $amount; | |
| 810 | + ++$recurring_donations_payment_gateway[ $gateway ]; | |
| 556 | 811 | |
| 557 | 812 | $period = isset( $donation->donation_period ) ? (string) $donation->donation_period : ''; |
| 558 | 813 | |
| 559 | 814 | if ( ! isset( $recurring_donations_period[ $period ] ) ) { |
| @@ -559,18 +814,15 @@ | ||
| 559 | 814 | if ( ! isset( $recurring_donations_period[ $period ] ) ) { |
| 560 | 815 | $recurring_donations_period[ $period ] = 0; |
| 561 | 816 | } |
| 562 | 817 | |
| 563 | - $recurring_donations_period[ $period ] += $amount; | |
| 818 | + ++$recurring_donations_period[ $period ]; | |
| 564 | 819 | } |
| 565 | 820 | |
| 566 | 821 | return array( |
| 567 | - 'total_recurring_amount' => $total_recurring_amount, | |
| 822 | + // Counts of ACTIVE subscriptions, keyed by gateway / by period. Amounts before 1.8.12.1. | |
| 568 | 823 | 'recurring_donations_payment_gateway' => $recurring_donations_payment_gateway, |
| 569 | 824 | 'recurring_donations_period' => $recurring_donations_period, |
| 570 | - 'recurring_donations_7_days' => $recurring_donations_7_days, | |
| 571 | - 'recurring_donations_30_days' => $recurring_donations_30_days, | |
| 572 | - // New in 1.8.17.4. | |
| 573 | 825 | 'recurring_donations_count' => $recurring_count, |
| 574 | 826 | ); |
| 575 | 827 | } |
| 576 | 828 | |