PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.26
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.26
1.6.6 1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 All 49 releases
fluent-cart / app / Modules / Integrations / FluentPlugins / FluentCRMDeepIntegration.php

FluentCRMDeepIntegration.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.26, at app/Modules/Integrations/FluentPlugins/FluentCRMDeepIntegration.php

642 lines 26.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Modules\Integrations\FluentPlugins;
4
5 use FluentAffiliate\App\Models\Affiliate;
6 use FluentCart\Api\ModuleSettings;
7 use FluentCart\App\Helpers\Status;
8 use FluentCart\App\Models\Customer;
9 use FluentCart\App\Models\Product;
10 use FluentCart\App\Services\URL;
11 use FluentCrm\App\Models\Subscriber;
12 use FluentCrm\App\Services\Helper;
13 use FluentCrm\App\Services\Libs\ConditionAssessor;
14 use FluentCrm\Framework\Support\Arr;
15
16 class FluentCRMDeepIntegration
17 {
18 private $importKey = 'fluent_cart';
19
20 public function init()
21 {
22 // Advanced Filters
23 add_filter('fluentcrm_contacts_filter_fluent_cart', array($this, 'applyAdvancedFilters'), 10, 2);
24 add_filter('fluentcrm_advanced_filter_options', array($this, 'addAdvancedFilterOptions'), 10, 1);
25
26 add_filter('fluentcrm_ajax_options_product_selector_fluent_cart', [$this, 'handleProductSelectorAjax'], 10, 3);
27 add_filter('fluent_crm/cascade_selection_options_fct_variations', [$this, 'handleProductVariationsAjax'], 1, 2);
28
29 add_filter('fluent_crm/subscriber_info_widgets', [$this, 'pushInfoWidgetToContact'], 1, 2);
30
31
32 // temp
33 // @todo: Remove after FluentCRM version 3 release
34 add_filter('fluent_crm/purchase_history_fluent_cart', function ($data) {
35
36 if (!$data || empty($data['data'])) {
37 return $data;
38 }
39
40 $dataItems = $data['data'];
41
42 foreach ($dataItems as $index => $item) {
43 $action = $item['action'];
44 $action = str_replace('fluent-crm#/orders/', 'fluent-cart#/orders/', $action);
45 $dataItems[$index]['action'] = $action;
46 }
47
48 $data['data'] = $dataItems;
49
50 return $data;
51 }, 99, 1);
52
53 }
54
55 /**
56 * @param \FluentCrm\Framework\Database\Orm\Builder|\FluentCrm\Framework\Database\Query\Builder $query
57 * @param array $filters
58 * @return \FluentCrm\Framework\Database\Orm\Builder|\FluentCrm\Framework\Database\Query\Builder
59 */
60 public function applyAdvancedFilters($query, $filters)
61 {
62 foreach ($filters as $filter) {
63 $query = $this->applyFilter($query, $filter);
64 }
65
66 return $query;
67 }
68
69 public function addAdvancedFilterOptions($groups)
70 {
71 $conditionItems = [
72 [
73 'value' => 'commerce_exist',
74 'label' => __('Is a customer?', 'fluent-cart'),
75 'type' => 'selections',
76 'is_multiple' => false,
77 'disable_values' => true,
78 'value_description' => __('This filter will check if a contact has at least one shop order or not', 'fluent-cart'),
79 'custom_operators' => [
80 'exist' => __('Yes', 'fluent-cart'),
81 'not_exist' => __('No', 'fluent-cart'),
82 ]
83 ],
84 [
85 'value' => 'ltv',
86 'label' => __('Lifetime Value', 'fluent-cart'),
87 'type' => 'numeric'
88 ],
89 [
90 'value' => 'aov',
91 'label' => __('Average Order Value', 'fluent-cart'),
92 'type' => 'numeric',
93 ],
94 [
95 'value' => 'first_purchase_date',
96 'label' => __('First Order Date', 'fluent-cart'),
97 'type' => 'dates'
98 ],
99 [
100 'value' => 'last_purchase_date',
101 'label' => __('Last Order Date', 'fluent-cart'),
102 'type' => 'dates'
103 ],
104 [
105 'value' => 'purchased_items',
106 'label' => __('Products', 'fluent-cart'),
107 'type' => 'selections',
108 'component' => 'product_selector',
109 'is_multiple' => true,
110 'custom_operators' => [
111 'exist' => __('purchased', 'fluent-cart'),
112 'not_exist' => __('not purchased', 'fluent-cart'),
113 ],
114 'help' => __('Will filter the contacts who have at least one order', 'fluent-cart')
115 ],
116 [
117 'value' => 'variation_purchased',
118 'label' => __('Product Variations', 'fluent-cart'),
119 'type' => 'cascade_selections',
120 'provider' => 'fct_variations',
121 'is_multiple' => true,
122 'value_description' => __('This filter will check if a contact has purchased at least one specific product variation or not', 'fluent-cart'),
123 'custom_operators' => [
124 'exist' => __('purchased', 'fluent-cart'),
125 'not_exist' => __('not purchased', 'fluent-cart'),
126 ]
127 ],
128 [
129 'value' => 'purchased_categories',
130 'label' => __('Product Categories', 'fluent-cart'),
131 'type' => 'selections',
132 'component' => 'tax_selector',
133 'taxonomy' => 'product-categories',
134 'is_multiple' => true,
135 'disabled' => true,
136 'help' => __('Will filter the contacts who have at least one order', 'fluent-cart'),
137 'custom_operators' => [
138 'exist' => __('purchased', 'fluent-cart'),
139 'not_exist' => __('not purchased', 'fluent-cart'),
140 ]
141 ],
142 [
143 'value' => 'commerce_coupons',
144 'label' => __('Used Coupons', 'fluent-cart'),
145 'type' => 'selections',
146 'component' => 'ajax_selector',
147 'option_key' => 'fct_coupons',
148 'is_multiple' => true,
149 'disabled' => true,
150 'custom_operators' => [
151 'exist' => __('in', 'fluent-cart'),
152 'not_exist' => __('not in', 'fluent-cart'),
153 ],
154 'help' => __('Will filter the contacts who have at least one order', 'fluent-cart')
155 ]
156 ];
157
158 if (ModuleSettings::isActive('license')) {
159 $conditionItems[] = [
160 'value' => 'active_licenses',
161 'label' => __('Active Licenses', 'fluent-cart'),
162 'type' => 'selections',
163 'component' => 'product_selector',
164 'is_multiple' => true,
165 'custom_operators' => [
166 'exist' => __('have', 'fluent-cart'),
167 'not_exist' => __('do not have', 'fluent-cart'),
168 ],
169 'help' => __('Will filter the contacts who have at least one active licenses or not', 'fluent-cart')
170 ];
171 $conditionItems[] = [
172 'value' => 'active_variation_licenses',
173 'label' => __('Active Variation Licenses', 'fluent-cart'),
174 'type' => 'cascade_selections',
175 'provider' => 'fct_variations',
176 'is_multiple' => true,
177 'value_description' => __('This filter will check if a contact has at least one specific variation license or not', 'fluent-cart'),
178 'custom_operators' => [
179 'exist' => __('have', 'fluent-cart'),
180 'not_exist' => __('do not have', 'fluent-cart'),
181 ]
182 ];
183 $conditionItems[] = [
184 'value' => 'expired_licenses',
185 'label' => __('Expired Licenses', 'fluent-cart'),
186 'type' => 'selections',
187 'component' => 'product_selector',
188 'is_multiple' => true,
189 'custom_operators' => [
190 'exist' => __('have', 'fluent-cart'),
191 'not_exist' => __('do not have', 'fluent-cart'),
192 ],
193 'help' => __('Will filter the contacts who have at least one expired licenses or not', 'fluent-cart')
194 ];
195 $conditionItems[] = [
196 'value' => 'expired_variation_licenses',
197 'label' => __('Expired Variation Licenses', 'fluent-cart'),
198 'type' => 'cascade_selections',
199 'provider' => 'fct_variations',
200 'is_multiple' => true,
201 'value_description' => __('This filter will check if a contact has at least one specific variation expired license or not', 'fluent-cart'),
202 'custom_operators' => [
203 'exist' => __('have', 'fluent-cart'),
204 'not_exist' => __('do not have', 'fluent-cart'),
205 ]
206 ];
207 $conditionItems[] = [
208 'value' => 'license_exist',
209 'label' => __('Has any active license?', 'fluent-cart'),
210 'type' => 'selections',
211 'is_multiple' => false,
212 'disable_values' => true,
213 'value_description' => __('Check if contacts has any active license from any products', 'fluent-cart'),
214 'custom_operators' => [
215 'exist' => __('Yes', 'fluent-cart'),
216 'not_exist' => __('No', 'fluent-cart'),
217 ]
218 ];
219 }
220
221 $groups['fluent_cart'] = [
222 'label' => __('FluentCart', 'fluent-cart'),
223 'value' => 'fluent_cart',
224 'children' => $conditionItems
225 ];
226
227 return $groups;
228 }
229
230 private function applyFilter($query, $filter)
231 {
232 $key = Arr::get($filter, 'property', '');
233 $value = Arr::get($filter, 'value', '');
234 $operator = Arr::get($filter, 'operator', '');
235
236 if (!$key || !$operator) {
237 return $query;
238 }
239
240 if ($key == 'commerce_exist') {
241 if ($operator === 'exist') {
242 return $query->whereExists(function ($q) {
243 $q->select(fluentCrmDb()->raw(1))
244 ->from('fct_customers')
245 ->whereColumn('fct_customers.email', 'fc_subscribers.email');
246 });
247 }
248
249 return $query->whereNotExists(function ($q) {
250 $q->select(fluentCrmDb()->raw(1))
251 ->from('fct_customers')
252 ->whereColumn('fct_customers.email', 'fc_subscribers.email');
253 });
254 }
255
256 if ($key === 'license_exist') {
257 if ($operator === 'exist') {
258 return $query->whereExists(function ($q) {
259 $q->select(fluentCrmDb()->raw(1))
260 ->from('fct_customers')
261 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
262 ->join('fct_licenses', function ($join) {
263 $join->on('fct_licenses.customer_id', '=', 'fct_customers.id')
264 ->whereIn('fct_licenses.status', ['active', 'inactive']);
265 });
266 });
267 }
268
269 return $query->whereNotExists(function ($q) {
270 $q->select(fluentCrmDb()->raw(1))
271 ->from('fct_customers')
272 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
273 ->join('fct_licenses', function ($join) {
274 $join->on('fct_licenses.customer_id', '=', 'fct_customers.id')
275 ->whereIn('fct_licenses.status', ['active', 'inactive']);
276 });
277 });
278 }
279
280 if ($value === '') {
281 return $query;
282 }
283
284 $customerProperties = ['ltv', 'aov'];
285
286 if (in_array($key, $customerProperties)) {
287 $value = \FluentCart\App\Helpers\Helper::toCent($value);
288 return $query->whereExists(function ($q) use ($key, $value, $operator) {
289 $q->select(fluentCrmDb()->raw(1))
290 ->from('fct_customers')
291 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
292 ->where('fct_customers.' . $key, $operator, $value);
293 });
294 }
295
296 if ($key == 'first_purchase_date' || $key == 'last_purchase_date') {
297 $filter = Subscriber::filterParser($filter);
298 return $query->whereExists(function ($q) use ($filter, $key) {
299 $q->select(fluentCrmDb()->raw(1))
300 ->from('fct_customers')
301 ->whereColumn('fct_customers.email', 'fc_subscribers.email');
302 if ($filter['operator'] == 'BETWEEN') {
303 return $q->whereBetween('fct_customers.' . $key, $filter['value']);
304 } else {
305 return $q->where('fct_customers.' . $key, $filter['operator'], $filter['value']);
306 }
307 });
308 }
309
310 if ($key == 'last_payout_date') {
311 $filter = Subscriber::filterParser($filter);
312 return $query->whereExists(function ($q) use ($filter) {
313 $q->select(fluentCrmDb()->raw(1))
314 ->from('fa_affiliates')
315 ->whereColumn('fa_affiliates.user_id', 'fc_subscribers.user_id')
316 ->join('fa_payout_transactions', 'fa_payout_transactions.affiliate_id', '=', 'fa_affiliates.id');
317 if ($filter['operator'] == 'BETWEEN') {
318 return $q->whereBetween('fa_payout_transactions.created_at', $filter['value']);
319 }
320
321 return $q->where('fa_payout_transactions.created_at', $filter['operator'], $filter['value']);
322 });
323 }
324
325 if ($key === 'purchased_items' || $key === 'variation_purchased') {
326 if ($key === 'variation_purchased') {
327 $value = array_map(function ($item) {
328 $parts = explode('||', $item);
329 return isset($parts[1]) ? (int)$parts[1] : 0;
330 }, is_array($value) ? $value : [$value]);
331
332 $value = array_values(array_filter($value, 'is_numeric'));
333 }
334
335 $itemColumn = $key === 'purchased_items' ? 'post_id' : 'object_id';
336 $value = is_array($value) ? $value : [$value];
337
338 if ($operator === 'exist') {
339 return $query->whereExists(function ($q) use ($itemColumn, $value) {
340 $q->select(fluentCrmDb()->raw(1))
341 ->from('fct_customers')
342 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
343 ->join('fct_orders', function ($join) {
344 $join->on('fct_orders.customer_id', '=', 'fct_customers.id')
345 ->whereIn('fct_orders.payment_status', Status::getOrderPaymentSuccessStatuses());
346 })
347 ->join('fct_order_items', 'fct_order_items.order_id', '=', 'fct_orders.id')
348 ->whereIn('fct_order_items.' . $itemColumn, $value);
349 });
350 }
351
352 return $query->whereNotExists(function ($q) use ($itemColumn, $value) {
353 $q->select(fluentCrmDb()->raw(1))
354 ->from('fct_customers')
355 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
356 ->join('fct_orders', function ($join) {
357 $join->on('fct_orders.customer_id', '=', 'fct_customers.id')
358 ->whereIn('fct_orders.payment_status', Status::getOrderPaymentSuccessStatuses());
359 })
360 ->join('fct_order_items', 'fct_order_items.order_id', '=', 'fct_orders.id')
361 ->whereIn('fct_order_items.' . $itemColumn, $value);
362 });
363 }
364
365 if ($key === 'active_licenses' || $key === 'active_variation_licenses') {
366 if ($key === 'active_variation_licenses') {
367 $value = array_map(function ($item) {
368 $parts = explode('||', $item);
369 return isset($parts[1]) ? (int)$parts[1] : 0;
370 }, is_array($value) ? $value : [$value]);
371 $value = array_values(array_filter($value, 'is_numeric'));
372 }
373
374 $itemColumn = $key === 'active_licenses' ? 'product_id' : 'variation_id';
375 $value = is_array($value) ? $value : [$value];
376
377 if ($operator === 'exist') {
378 return $query->whereExists(function ($q) use ($itemColumn, $value) {
379 $q->select(fluentCrmDb()->raw(1))
380 ->from('fct_customers')
381 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
382 ->join('fct_licenses', function ($join) {
383 $join->on('fct_licenses.customer_id', '=', 'fct_customers.id')
384 ->whereIn('fct_licenses.status', ['active', 'inactive']);
385 })
386 ->whereIn('fct_licenses.' . $itemColumn, $value);
387 });
388 }
389
390 return $query->whereNotExists(function ($q) use ($itemColumn, $value) {
391 $q->select(fluentCrmDb()->raw(1))
392 ->from('fct_customers')
393 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
394 ->join('fct_licenses', function ($join) {
395 $join->on('fct_licenses.customer_id', '=', 'fct_customers.id')
396 ->whereIn('fct_licenses.status', ['active', 'inactive']);
397 })
398 ->whereIn('fct_licenses.' . $itemColumn, $value);
399 });
400 }
401
402 if ($key === 'expired_licenses' || $key === 'expired_variation_licenses') {
403 if ($key === 'expired_variation_licenses') {
404 $value = array_map(function ($item) {
405 $parts = explode('||', $item);
406 return isset($parts[1]) ? (int)$parts[1] : 0;
407 }, is_array($value) ? $value : [$value]);
408 $value = array_values(array_filter($value, 'is_numeric'));
409 }
410
411 $itemColumn = $key === 'expired_licenses' ? 'product_id' : 'variation_id';
412 $value = is_array($value) ? $value : [$value];
413
414 if ($operator === 'exist') {
415 return $query->whereExists(function ($q) use ($itemColumn, $value) {
416 $q->select(fluentCrmDb()->raw(1))
417 ->from('fct_customers')
418 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
419 ->join('fct_licenses', function ($join) {
420 $join->on('fct_licenses.customer_id', '=', 'fct_customers.id');
421 })
422 ->where('fct_licenses.status', 'expired')
423 ->whereNotIn('fct_licenses.status', ['active', 'inactive'])
424 ->whereIn('fct_licenses.' . $itemColumn, $value);
425 });
426 }
427
428 return $query->whereNotExists(function ($q) use ($itemColumn, $value) {
429 $q->select(fluentCrmDb()->raw(1))
430 ->from('fct_customers')
431 ->whereColumn('fct_customers.email', 'fc_subscribers.email')
432 ->join('fct_licenses', function ($join) {
433 $join->on('fct_licenses.customer_id', '=', 'fct_customers.id');
434 })
435 ->where('fct_licenses.status', 'expired')
436 ->whereNotIn('fct_licenses.status', ['active', 'inactive'])
437 ->whereIn('fct_licenses.' . $itemColumn, $value);
438 });
439 }
440
441 return $query;
442 }
443
444 public function handleProductSelectorAjax($options, $searchTerm, $includedIds = [])
445 {
446 if (!$includedIds || !is_array($includedIds)) {
447 $includedIds = [];
448 }
449
450 $products = Product::query()->whereLike('post_title', $searchTerm)->limit(50)->get();
451
452 $formattedProducts = [];
453
454 $pushedIds = [];
455
456 foreach ($products as $product) {
457 $pushedIds[] = (string)$product->ID;
458 $formattedProducts[] = [
459 'id' => (string)$product->ID,
460 'title' => $product->post_title
461 ];
462 }
463
464 $leftoverIds = array_diff($includedIds, $pushedIds);
465
466 if ($leftoverIds) {
467 $leftoverProducts = Product::query()->whereIn('ID', $leftoverIds)->get();
468 foreach ($leftoverProducts as $product) {
469 $formattedProducts[] = [
470 'id' => (string)$product->ID,
471 'title' => $product->post_title
472 ];
473 }
474 }
475
476 return $formattedProducts;
477 }
478
479 public function handleProductVariationsAjax($response, $reqestData = [])
480 {
481 $prevValues = Arr::get($reqestData, 'values', []);
482 $search = Arr::get($reqestData, 'search', '');
483 $prevItemIds = [];
484
485 if ($prevValues) {
486 $prevValues = (array)$prevValues;
487 foreach ($prevValues as $prevValue) {
488 $prevItemIds[] = explode('||', $prevValue)[0];
489 }
490 $prevItemIds = array_values(array_unique($prevItemIds));
491 }
492
493 // get wc variable products
494 $variableProducts = Product::query()->whereLike('post_title', $search)
495 ->with(['variants'])
496 ->limit(50)
497 ->get();
498
499 $formattedProducts = [];
500
501 $includedProductIds = [];
502
503 foreach ($variableProducts as $product) {
504 $item = [
505 'value' => (string)$product->ID,
506 'label' => $product->post_title
507 ];
508
509 $formattedVariations = [];
510 foreach ($product->variants as $variant) {
511 $formattedVariations[] = [
512 'value' => $item['value'] . '||' . $variant->id,
513 'label' => $variant->variation_title
514 ];
515 }
516 $item['children'] = $formattedVariations;
517 $formattedProducts[] = $item;
518
519 $includedProductIds[] = $item['value'];
520 }
521
522 if ($prevItemIds) {
523 $includedProductIds = array_diff($prevItemIds, $includedProductIds);
524 if ($includedProductIds) {
525 $variableProducts = Product::query()->whereIn('ID', $includedProductIds)
526 ->with(['variants'])
527 ->get();
528
529 foreach ($variableProducts as $product) {
530 $item = [
531 'value' => (string)$product->id,
532 'label' => $product->post_title
533 ];
534
535 $variations = $product->get_children();
536 $formattedVariations = [];
537 foreach ($product->variants as $variant) {
538 $formattedVariations[] = [
539 'value' => $item['value'] . '||' . $variant->id,
540 'label' => $variant->variation_title
541 ];
542 }
543 $item['children'] = $formattedVariations;
544 $formattedProducts[] = $item;
545 }
546 }
547 }
548
549 $formattedProducts = array_filter($formattedProducts, function ($item) {
550 return !empty($item['children']) && count($item['children']) > 1;
551 });
552
553
554 return [
555 'options' => $formattedProducts,
556 'has_more' => true
557 ];
558 }
559
560 public function pushInfoWidgetToContact($widgets, $subscriber)
561 {
562 $userId = $subscriber->user_id;
563 $customer = Customer::query();
564 if ($userId) {
565 $customer = $customer->where('user_id', $userId)
566 ->orWhere('email', $subscriber->email);
567 } else {
568 $customer = $customer->where('email', $subscriber->email);
569 }
570
571 $customer = $customer->first();
572
573 if (!$customer) {
574 return $widgets;
575 }
576
577
578 $widgets['fluent_cart'] = [
579 'title' => __('Commerce Info', 'fluent-cart'),
580 'content' => $this->getStatsHtml($customer)
581 ];
582
583 return $widgets;
584 }
585
586 public function getStatsHtml($customer)
587 {
588 $stats = [
589 [
590 'title' => __('Lifetime Value', 'fluent-cart'),
591 'value' => '<a href="' . URL::getDashboardUrl('customers/' . $customer->id . '/view') . '" target="_blank" class="fc_view_more">' . \FluentCart\App\Helpers\Helper::toDecimal($customer->ltv) . '</a>'
592 ],
593 [
594 'title' => __('Purchases', 'fluent-cart'),
595 'value' => $customer->purchase_count
596 ],
597 [
598 'title' => __('First Purchased: ', 'fluent-cart'),
599 'value' => $customer->first_purchase_date ? gmdate('F j, Y', strtotime($customer->first_purchase_date)) : 'N/A'
600 ],
601 [
602 'title' => __('Last Purchased: ', 'fluent-cart'),
603 'value' => $customer->last_purchase_date ? gmdate('F j, Y', strtotime($customer->last_purchase_date)) : 'N/A'
604 ],
605 ];
606
607 $html = '<ul class="fc_full_listed fcrm_fluentcart_customer_commerce_info">';
608 foreach ($stats as $stat) {
609 $html .= '<li><span class="fc_list_sub">' . $stat['title'] . '</span> <span class="fc_list_value">' . $stat['value'] . '</span></li>';
610 }
611
612 $orderedItems = $customer->success_order_items()->orderBy('id', 'DESC')->get();
613
614 $formattedItems = [];
615
616 foreach ($orderedItems as $orderedItem) {
617 $count = isset($formattedItems[$orderedItem->object_id]) ? $formattedItems[$orderedItem->object_id]['count'] + 1 : 1;
618 $formattedItems[$orderedItem->object_id] = [
619 'title' => $orderedItem->title,
620 'post_title' => $orderedItem->post_title,
621 'count' => $count,
622 'created_at' => $orderedItem->created_at
623 ];
624 }
625
626 foreach ($formattedItems as $formattedItem) {
627 $countHtml = '';
628
629 if ($formattedItem['count'] > 1) {
630 $countHtml = ' <span style="background: #673AB7;color: #fffef1;font-size: 10px;padding: 3px 5px;border-radius: 50%;display: inline-block;line-height: 100%;">' . $formattedItem['count'] . '</span> ';
631 }
632
633 $html .= '<li>' . $countHtml . $formattedItem['post_title'] . ' - <span style="font-style: italic;">' . $formattedItem['title'] . '</span> (' . gmdate('F j, Y', strtotime($formattedItem['created_at'])) . ')</li>';
634 }
635
636 $html .= '</ul>';
637
638 return $html;
639 }
640
641 }
642