PluginProbe
wpForo Forum / 3.1.0
wpForo Forum v3.1.0
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / themes / classic / activity.php

activity.php in wpForo Forum 3.1.0, at themes/classic/activity.php

262 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Exit if accessed directly
3 if( ! defined( 'ABSPATH' ) ) exit;
4
5 $activities = wpfval( WPF()->current_object, 'activities' ) ?: [];
6 $args = wpfval( WPF()->current_object, 'args' ) ?: [];
7 $activity_type = wpfval( $args, 'activity_type' ) ?: 'all';
8 $period = wpfval( $args, 'period' ) ?: 'week';
9 $items_count = wpfval( WPF()->current_object, 'items_count' ) ?: 0;
10 $paged = wpfval( WPF()->current_object, 'paged' ) ?: 1;
11 $items_per_page = 20;
12 $has_more = ( $paged * $items_per_page ) < $items_count;
13 ?>
14 <div class="wpforo-recent-activity-wrap">
15 <?php do_action( 'wpforo_activity_list_head' ); ?>
16 <div class="wpf-head-bar">
17 <div class="wpf-head-top">
18 <h1 id="wpforo-title"><?php wpforo_phrase( 'Recent Activity Timeline' ) ?></h1>
19 </div>
20 <div class="wpf-head-bottom">
21 <div class="wpf-head-prefix">
22 <form method="get" action="<?php echo esc_url( wpforo_home_url( 'recent-activity' ) ) ?>" class="wpf-recent-activity-filter-form">
23 <select name="type" id="wpf-activity-type-filter" onchange="this.form.submit()">
24 <option value="all" <?php selected( $activity_type, 'all' ) ?>><?php wpforo_phrase( 'All Activities' ) ?></option>
25 <option value="topic" <?php selected( $activity_type, 'topic' ) ?>><?php wpforo_phrase( 'Topics' ) ?></option>
26 <option value="reply" <?php selected( $activity_type, 'reply' ) ?>><?php wpforo_phrase( 'Replies' ) ?></option>
27 <option value="reaction" <?php selected( $activity_type, 'reaction' ) ?>><?php wpforo_phrase( 'Reactions' ) ?></option>
28 <option value="favorite" <?php selected( $activity_type, 'favorite' ) ?>><?php wpforo_phrase( 'Favorites' ) ?></option>
29 <option value="solved" <?php selected( $activity_type, 'solved' ) ?>><?php wpforo_phrase( 'Solved Topics' ) ?></option>
30 <option value="closed" <?php selected( $activity_type, 'closed' ) ?>><?php wpforo_phrase( 'Closed Topics' ) ?></option>
31 <option value="answer" <?php selected( $activity_type, 'answer' ) ?>><?php wpforo_phrase( 'Marked as Answer' ) ?></option>
32 </select>
33 <select name="period" id="wpf-activity-period-filter" onchange="this.form.submit()">
34 <option value="day" <?php selected( $period, 'day' ) ?>><?php wpforo_phrase( 'Last 24 Hours' ) ?></option>
35 <option value="week" <?php selected( $period, 'week' ) ?>><?php wpforo_phrase( 'Last Week' ) ?></option>
36 <option value="month" <?php selected( $period, 'month' ) ?>><?php wpforo_phrase( 'Last Month' ) ?></option>
37 <option value="all" <?php selected( $period, 'all' ) ?>><?php wpforo_phrase( 'All Time' ) ?></option>
38 </select>
39 </form>
40 </div>
41 </div>
42 </div>
43
44 <div class="wpf-recent-activity-content">
45 <?php if( ! empty( $activities ) ) : ?>
46 <div class="wpf-timeline" data-page="<?php echo esc_attr( $paged ) ?>" data-type="<?php echo esc_attr( $activity_type ) ?>" data-period="<?php echo esc_attr( $period ) ?>">
47 <div class="wpf-timeline-line"></div>
48 <?php
49 $can_delete = WPF()->usergroup->can( 'aum' );
50 foreach( $activities as $index => $activity ) :
51 // For non-content actions (reactions, favorites, votes, etc.), the actor is in itemid_second
52 $actor_actions = [ 'new_like', 'new_dislike', 'new_up_vote', 'new_down_vote', 'new_reaction', 'new_favorite', 'topic_solved', 'topic_closed', 'post_answer' ];
53 $actor_userid = in_array( $activity['type'], $actor_actions ) && ! empty( $activity['itemid_second'] )
54 ? $activity['itemid_second']
55 : $activity['userid'];
56 $member = wpforo_member( $actor_userid );
57 $icon = wpforo_activity_icon( $activity['type'] );
58 $info_html = wpforo_activity_info_html( $activity, $member );
59 $show_excerpt = in_array( $activity['type'], [ 'wpforo_topic', 'wpforo_post' ] );
60 $excerpt = '';
61 if( $show_excerpt && $activity['itemid'] ) {
62 // For wpforo_topic, itemid is topicid - get first post via topic's first_postid
63 if( $activity['type'] === 'wpforo_topic' ) {
64 $topic_data = wpforo_topic( $activity['itemid'] );
65 if( $topic_data && ! empty( $topic_data['first_postid'] ) ) {
66 $first_post = wpforo_post( $topic_data['first_postid'] );
67 if( $first_post && ! empty( $first_post['body'] ) ) {
68 $excerpt = wpforo_text( $first_post['body'], 300, false );
69 }
70 }
71 } else {
72 $post = wpforo_post( $activity['itemid'] );
73 if( $post && ! empty( $post['body'] ) ) {
74 $excerpt = wpforo_text( $post['body'], 300, false );
75 }
76 }
77 }
78 ?>
79 <div class="wpf-timeline-item wpf-timeline-<?php echo esc_attr( $activity['type'] ) ?>" data-activity-id="<?php echo esc_attr( $activity['id'] ) ?>" style="--delay: <?php echo $index * 0.03 ?>s">
80 <!-- Left Side: User Info -->
81 <div class="wpf-timeline-left">
82 <?php if( WPF()->usergroup->can( 'va' ) && wpforo_setting( 'profiles', 'avatars' ) ) : ?>
83 <div class="wpf-timeline-avatar"><?php echo wpforo_user_avatar( $member, 40 ) ?></div>
84 <?php endif; ?>
85 <div class="wpf-timeline-user"><?php wpforo_member_link( $member ) ?></div>
86 <div class="wpf-timeline-date" style="margin-right: -51px; color: #8a969a;"><?php wpforo_date( $activity['date'], 'ago' ) ?> <span class="wpf-timeline-date-stick">&nbsp</span></div>
87 </div>
88
89 <!-- Center: Icon Node -->
90 <div class="wpf-timeline-node">
91 <div class="wpf-timeline-icon"><?php echo $icon ?></div>
92 </div>
93
94 <!-- Right Side: Activity Content -->
95 <div class="wpf-timeline-right">
96 <div class="wpf-timeline-card">
97 <?php if( $can_delete ) : ?>
98 <button type="button" class="wpf-activity-delete" title="<?php wpforo_phrase( 'Delete Activity', false ) ?>">
99 <i class="fas fa-trash-alt"></i>
100 </button>
101 <?php endif; ?>
102 <!-- Mobile-only header with user and date -->
103 <div class="wpf-timeline-card-header">
104 <div class="wpf-timeline-date"><?php wpforo_date( $activity['date'], 'ago' ) ?></div>
105 <div class="wpf-timeline-user"><?php wpforo_member_link( $member ) ?></div>
106 </div>
107 <div class="wpf-timeline-activity"><?php echo $info_html ?></div>
108 <?php if( $show_excerpt && $excerpt ) : ?>
109 <div class="wpf-timeline-excerpt"><?php echo esc_html( $excerpt ) ?></div>
110 <?php endif; ?>
111 </div>
112 </div>
113 </div>
114 <?php endforeach; ?>
115 </div>
116
117 <?php if( $has_more ) : ?>
118 <div class="wpf-timeline-load-more-wrap">
119 <button type="button" class="wpf-timeline-load-more wpf-button wpf-button-primary" data-loading="<?php wpforo_phrase( 'Loading...', false ) ?>">
120 <span class="wpf-load-more-text"><?php wpforo_phrase( 'Load More Activities' ) ?></span>
121 <span class="wpf-load-more-spinner"></span>
122 </button>
123 </div>
124 <?php endif; ?>
125
126 <?php else : ?>
127 <div class="wpf-timeline-empty">
128 <div class="wpf-timeline-empty-icon"><i class="far fa-clock"></i></div>
129 <p><?php wpforo_phrase( 'No activities found' ) ?></p>
130 </div>
131 <?php endif; ?>
132 </div>
133
134 <?php do_action( 'wpforo_activity_list_footer' ); ?>
135 </div>
136
137 <script>
138 (function() {
139 document.addEventListener('DOMContentLoaded', function() {
140 // Intersection Observer for fade-in animations
141 const observer = new IntersectionObserver((entries) => {
142 entries.forEach(entry => {
143 if (entry.isIntersecting) {
144 entry.target.classList.add('wpf-timeline-visible');
145 observer.unobserve(entry.target);
146 }
147 });
148 }, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' });
149
150 document.querySelectorAll('.wpf-timeline-item').forEach(item => {
151 observer.observe(item);
152 });
153
154 // Load More functionality
155 const loadMoreBtn = document.querySelector('.wpf-timeline-load-more');
156 if (loadMoreBtn) {
157 loadMoreBtn.addEventListener('click', function() {
158 const timeline = document.querySelector('.wpf-timeline');
159 const btn = this;
160 const btnText = btn.querySelector('.wpf-load-more-text');
161 const originalText = btnText.textContent;
162
163 if (btn.classList.contains('wpf-loading')) return;
164
165 btn.classList.add('wpf-loading');
166 btnText.textContent = btn.dataset.loading;
167
168 const currentPage = parseInt(timeline.dataset.page) || 1;
169 const nextPage = currentPage + 1;
170 const type = timeline.dataset.type || 'all';
171 const period = timeline.dataset.period || 'week';
172
173 const formData = new FormData();
174 formData.append('action', 'wpforo_load_more_activities');
175 formData.append('page', nextPage);
176 formData.append('type', type);
177 formData.append('period', period);
178 formData.append('_wpfnonce', (typeof wpforo !== 'undefined' && wpforo.nonces ? wpforo.nonces.wpforo_load_more_activities : ''));
179
180 fetch((typeof wpforo !== 'undefined' ? wpforo.ajax_url : '/wp-admin/admin-ajax.php'), {
181 method: 'POST',
182 body: formData
183 })
184 .then(response => response.json())
185 .then(data => {
186 btn.classList.remove('wpf-loading');
187 btnText.textContent = originalText;
188
189 if (data.success && data.data.html) {
190 // Append new items at the end of the timeline
191 timeline.insertAdjacentHTML('beforeend', data.data.html);
192 timeline.dataset.page = nextPage;
193
194 // Observe new items
195 timeline.querySelectorAll('.wpf-timeline-item:not(.wpf-timeline-visible)').forEach(item => {
196 observer.observe(item);
197 });
198
199 if (!data.data.has_more) {
200 btn.parentElement.remove();
201 }
202 }
203 })
204 .catch(error => {
205 btn.classList.remove('wpf-loading');
206 btnText.textContent = originalText;
207 console.error('Load more error:', error);
208 });
209 });
210 }
211
212 // Delete activity functionality
213 document.addEventListener('click', function(e) {
214 const deleteBtn = e.target.closest('.wpf-activity-delete');
215 if (!deleteBtn) return;
216
217 e.preventDefault();
218 e.stopPropagation();
219
220 const timelineItem = deleteBtn.closest('.wpf-timeline-item');
221 if (!timelineItem) return;
222
223 const activityId = timelineItem.dataset.activityId;
224 if (!activityId) return;
225
226 if (!confirm('<?php echo esc_js( wpforo_phrase( 'Are you sure you want to delete this activity?', false ) ) ?>')) {
227 return;
228 }
229
230 deleteBtn.classList.add('wpf-deleting');
231
232 const formData = new FormData();
233 formData.append('action', 'wpforo_delete_activity');
234 formData.append('activity_id', activityId);
235 formData.append('_wpfnonce', (typeof wpforo !== 'undefined' && wpforo.nonces ? wpforo.nonces.wpforo_delete_activity : ''));
236
237 fetch((typeof wpforo !== 'undefined' ? wpforo.ajax_url : '/wp-admin/admin-ajax.php'), {
238 method: 'POST',
239 body: formData
240 })
241 .then(response => response.json())
242 .then(data => {
243 if (data.success) {
244 timelineItem.style.opacity = '0';
245 timelineItem.style.transform = 'translateX(100px)';
246 setTimeout(() => {
247 timelineItem.remove();
248 }, 300);
249 } else {
250 deleteBtn.classList.remove('wpf-deleting');
251 alert(data.data && data.data.message ? data.data.message : '<?php echo esc_js( wpforo_phrase( 'Failed to delete activity', false ) ) ?>');
252 }
253 })
254 .catch(error => {
255 deleteBtn.classList.remove('wpf-deleting');
256 console.error('Delete error:', error);
257 });
258 });
259 });
260 })();
261 </script>
262