PluginProbe
ANAC XML Bandi di Gara / 7.8
ANAC XML Bandi di Gara v7.8
7.8 trunk 0.1 1.0.0.1 1.0.1 1.0.2 1.0.3 1.0.4 1.1 1.1.1 1.1.2 2.0 2.0.1 2.0.2 2.0.3 2.1 2.2 2.3 2.3.1 3 3.0.1 3.1 3.1.1 3.1.2 3.1.3 All 57 releases
avcp / tablegen.php

tablegen.php in ANAC XML Bandi di Gara 7.8, at tablegen.php

349 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * AVCP Table Generator
4 *
5 * Generates a filterable table of AVCP tender data with export functionality
6 *
7 * @package AVCP
8 * @since 1.0.0
9 */
10
11 // Prevent direct access
12 if (!defined('ABSPATH')) {
13 exit;
14 }
15
16 // Validate and sanitize the year parameter
17 if (!isset($anno)) {
18 $anno = 'all';
19 }
20
21 // Enhanced validation for year parameter
22 $anno = sanitize_text_field($anno);
23 if ($anno !== 'all' && !empty($anno)) {
24 // Validate year format (should be 4 digits)
25 if (!preg_match('/^\d{4}$/', $anno)) {
26 $anno = 'all';
27 } else {
28 // Ensure year is within reasonable range
29 $year_int = intval($anno);
30 $current_year = date('Y');
31 if ($year_int < 2000 || $year_int > ($current_year + 10)) {
32 $anno = 'all';
33 }
34 }
35 }
36 ?>
37 <script type="text/javascript" src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'includes/excellentexport.min.js'); ?>"></script>
38 <div class="avcp-table-container">
39 <table class="order-table table" id="gare" role="table" aria-label="<?php esc_attr_e('Tabella bandi di gara', 'avcp'); ?>">
40 <caption class="screen-reader-text">
41 <?php esc_html_e('Elenco dei bandi di gara pubblicati', 'avcp'); ?>
42 </caption>
43 <thead>
44 <tr>
45 <td colspan="6" class="table-header-info">
46 <div class="table-title">
47 <?php esc_html_e('Bandi di gara', 'avcp'); ?> - <strong><?php
48 echo ($anno !== 'all') ? esc_html($anno) : esc_html__('Tutti gli anni', 'avcp');
49 ?></strong>
50 </div>
51 <div class="table-search">
52 <label for="s" class="screen-reader-text"><?php esc_html_e('Cerca nei bandi:', 'avcp'); ?></label>
53 <input type="search" id="s" class="light-table-filter"
54 data-table="order-table"
55 placeholder="<?php esc_attr_e('Cerca...', 'avcp'); ?>"
56 maxlength="100"
57 autocomplete="off"
58 aria-describedby="search-help">
59 <span id="search-help" class="screen-reader-text">
60 <?php esc_html_e('Digita per filtrare i risultati della tabella', 'avcp'); ?>
61 </span>
62 </div>
63 </td>
64 </tr>
65 <tr role="row">
66 <th scope="col" colspan="2"><?php esc_html_e('Oggetto', 'avcp'); ?></th>
67 <th scope="col"><?php esc_html_e('CIG', 'avcp'); ?></th>
68 <th scope="col"><?php esc_html_e('Importo aggiudicazione', 'avcp'); ?></th>
69 <th scope="col"><?php esc_html_e('Durata lavori', 'avcp'); ?></th>
70 <th scope="col"><?php esc_html_e('Modalità affidamento', 'avcp'); ?></th>
71 </tr>
72 </thead>
73 <tbody role="rowgroup">
74
75 <?php
76 // Prepare query arguments with validation
77 $args = array(
78 'post_type' => 'avcp',
79 'orderby' => 'date',
80 'order' => 'DESC',
81 'posts_per_page' => -1,
82 'post_status' => 'publish',
83 'meta_query' => array(),
84 'fields' => 'ids', // Only get IDs first for better performance
85 'no_found_rows' => true, // Skip pagination count for better performance
86 );
87
88 // Add taxonomy filter if specific year is requested
89 if ($anno !== '' && $anno !== 'all') {
90 $args['tax_query'] = array(
91 array(
92 'taxonomy' => 'annirif',
93 'field' => 'slug',
94 'terms' => sanitize_title($anno),
95 ),
96 );
97 }
98
99 // Execute query with error handling
100 $post_ids = get_posts($args);
101
102 if (!empty($post_ids) && !is_wp_error($post_ids)) :
103
104 // Pre-load all meta data in one query for better performance
105 $meta_keys = array('avcp_data_inizio', 'avcp_data_fine', 'avcp_cig', 'avcp_aggiudicazione', 'avcp_contraente');
106 $meta_cache = array();
107
108 foreach ($post_ids as $post_id) {
109 $post_id = absint($post_id);
110 if ($post_id) {
111 $meta_cache[$post_id] = get_post_meta($post_id);
112 }
113 }
114
115 foreach ($post_ids as $post_id) :
116 $post_id = absint($post_id);
117 if (!$post_id) {
118 continue;
119 }
120
121 $post = get_post($post_id);
122 if (!$post || !is_object($post)) {
123 continue;
124 }
125
126 // Get meta data from cache
127 $post_meta = isset($meta_cache[$post_id]) ? $meta_cache[$post_id] : array();
128
129 $d_i = isset($post_meta['avcp_data_inizio'][0]) ? sanitize_text_field($post_meta['avcp_data_inizio'][0]) : '';
130 $d_f = isset($post_meta['avcp_data_fine'][0]) ? sanitize_text_field($post_meta['avcp_data_fine'][0]) : '';
131 $cig = isset($post_meta['avcp_cig'][0]) ? sanitize_text_field($post_meta['avcp_cig'][0]) : '';
132 $aggiudicazione = isset($post_meta['avcp_aggiudicazione'][0]) ? sanitize_text_field($post_meta['avcp_aggiudicazione'][0]) : '';
133 $contraente = isset($post_meta['avcp_contraente'][0]) ? sanitize_text_field($post_meta['avcp_contraente'][0]) : '';
134
135 // Format dates safely
136 $d_i_fmt = '-';
137 $d_f_fmt = '-';
138
139 if ($d_i && strtotime($d_i)) {
140 $d_i_fmt = date_i18n("d/m/Y", strtotime($d_i));
141 }
142
143 if ($d_f && strtotime($d_f)) {
144 $d_f_fmt = date_i18n("d/m/Y", strtotime($d_f));
145 }
146
147 // Output table row with proper escaping and accessibility
148 echo '<tr role="row">';
149
150 // Title column with link
151 $post_title = get_the_title($post_id);
152 $post_permalink = get_permalink($post_id);
153
154 if ($post_title && $post_permalink) {
155 echo '<td colspan="2" role="gridcell">';
156 echo '<a href="' . esc_url($post_permalink) . '" title="' . esc_attr(sprintf(__('Visualizza dettagli di: %s', 'avcp'), $post_title)) . '">';
157 echo esc_html($post_title);
158 echo '</a></td>';
159 } else {
160 echo '<td colspan="2" role="gridcell">' . esc_html__('Oggetto non disponibile', 'avcp') . '</td>';
161 }
162
163 // CIG column
164 echo '<td role="gridcell">' . esc_html($cig) . '</td>';
165
166 // Amount column
167 echo '<td role="gridcell" style="text-align: center;">€<strong>' . esc_html($aggiudicazione) . '</strong></td>';
168
169 // Duration column
170 echo '<td role="gridcell" style="text-align: center;">';
171 echo '<div class="date-info">';
172 echo '<span class="start-date">' . esc_html($d_i_fmt) . '</span><br/>';
173 echo '<span class="end-date">' . esc_html($d_f_fmt) . '</span><br/>';
174
175 // Calculate duration if both dates are valid
176 if (class_exists('DateTime') && $d_i && $d_f && strtotime($d_i) && strtotime($d_f)) {
177 try {
178 $date1 = new DateTime($d_i);
179 $date2 = new DateTime($d_f);
180 $diff = $date2->diff($date1);
181 if ($diff && $diff->days !== false) {
182 echo '<small class="duration"><strong>' . esc_html($diff->days) . '</strong> ' . esc_html__('gg', 'avcp') . '</small>';
183 }
184 } catch (Exception $e) {
185 // Log error if needed, but don't display to user
186 error_log('AVCP Date calculation error: ' . $e->getMessage());
187 }
188 }
189 echo '</div></td>';
190
191 // Contract type column
192 $contraente_display = '';
193 if ($contraente && strlen($contraente) > 3) {
194 $contraente_display = strtolower(substr($contraente, 3));
195 }
196 echo '<td role="gridcell">' . esc_html($contraente_display) . '</td>';
197
198 echo '</tr>';
199
200 endforeach;
201 else :
202 // No posts found
203 echo '<tr role="row"><td colspan="6" role="gridcell" style="text-align: center; padding: 20px;">' . esc_html__('Nessun bando trovato per i criteri selezionati.', 'avcp') . '</td></tr>';
204 endif;
205
206 echo '</tbody>
207 <tfoot role="rowgroup">
208 <tr role="row">
209 <td colspan="6" role="gridcell" class="table-footer">';
210
211 // Export buttons section
212 echo '<div class="export-buttons" style="float:right;" role="group" aria-label="' . esc_attr__('Opzioni di esportazione', 'avcp') . '">';
213 echo '<span class="export-label">' . esc_html__('Scarica in', 'avcp') . ':</span> ';
214
215 // XML export
216 $xml_url = get_site_url() . '/avcp';
217 echo '<a href="' . esc_url($xml_url) . '" target="_blank" rel="noopener" title="' . esc_attr__('Scarica file XML', 'avcp') . '" class="export-btn">';
218 echo '<button type="button" aria-label="' . esc_attr__('Esporta in formato XML', 'avcp') . '">' . esc_html__('XML', 'avcp') . '</button></a> ';
219
220 // Excel export
221 $site_name = sanitize_file_name(get_bloginfo('name'));
222 $excel_filename = $site_name . '-gare' . sanitize_file_name($anno) . '.xls';
223 echo '<a download="' . esc_attr($excel_filename) . '" href="#" onclick="return ExcellentExport.excel(this, \'gare\', \'Gare\');" class="export-btn">';
224 echo '<button type="button" aria-label="' . esc_attr__('Esporta in formato Excel', 'avcp') . '">' . esc_html__('EXCEL', 'avcp') . '</button></a> ';
225
226 // CSV export
227 $csv_filename = $site_name . '-gare' . sanitize_file_name($anno) . '.csv';
228 echo '<a download="' . esc_attr($csv_filename) . '" href="#" onclick="return ExcellentExport.csv(this, \'gare\');" class="export-btn">';
229 echo '<button type="button" aria-label="' . esc_attr__('Esporta in formato CSV', 'avcp') . '">' . esc_html__('CSV', 'avcp') . '</button></a>';
230
231 echo '</div>';
232
233 // WPGov logo section
234 if (get_option('wpgov_show_love')) {
235 $logo_url = plugin_dir_url(__FILE__) . 'images/wpgov.png';
236 echo '<div class="wpgov-credit">';
237 echo '<a href="' . esc_url('https://www.wpgov.it') . '" target="_blank" rel="noopener" title="' . esc_attr__('Software sviluppato da WPGov', 'avcp') . '">';
238 echo '<img src="' . esc_url($logo_url) . '" alt="' . esc_attr__('Logo WPGov', 'avcp') . '" style="max-height: 30px;" /></a>';
239 echo '</div>';
240 }
241
242 echo '</td>
243 </tr>
244 </tfoot>
245 </table>
246 </div>';
247
248 echo '<div class="clear"></div>';
249 ?>
250
251 <script type="text/javascript">
252 /**
253 * AVCP Table Filter
254 * Provides client-side filtering functionality for the tender table
255 */
256 (function(document) {
257 'use strict';
258
259 var AVCPTableFilter = (function(Arr) {
260 var _input;
261
262 /**
263 * Handle input events for table filtering
264 * @param {Event} e - The input event
265 */
266 function _onInputEvent(e) {
267 _input = e.target;
268 var tables = document.getElementsByClassName(_input.getAttribute('data-table'));
269
270 if (!tables || tables.length === 0) {
271 return;
272 }
273
274 Arr.forEach.call(tables, function(table) {
275 if (table.tBodies) {
276 Arr.forEach.call(table.tBodies, function(tbody) {
277 if (tbody.rows) {
278 Arr.forEach.call(tbody.rows, _filter);
279 }
280 });
281 }
282 });
283 }
284
285 /**
286 * Filter individual table rows based on search input
287 * @param {HTMLElement} row - The table row to filter
288 */
289 function _filter(row) {
290 if (!row || !row.textContent || !_input) {
291 return;
292 }
293
294 var text = row.textContent.toLowerCase();
295 var val = _input.value.toLowerCase().trim();
296
297 // Show all rows if search is empty
298 if (val === '') {
299 row.style.display = 'table-row';
300 return;
301 }
302
303 // Hide/show row based on text match
304 row.style.display = text.indexOf(val) === -1 ? 'none' : 'table-row';
305 }
306
307 /**
308 * Initialize the table filter functionality
309 */
310 function _init() {
311 var inputs = document.getElementsByClassName('light-table-filter');
312
313 if (!inputs || inputs.length === 0) {
314 return;
315 }
316
317 Arr.forEach.call(inputs, function(input) {
318 if (input) {
319 input.oninput = _onInputEvent;
320 // Also handle paste events
321 input.onpaste = function() {
322 setTimeout(_onInputEvent.bind(null, {target: input}), 10);
323 };
324 }
325 });
326 }
327
328 return {
329 init: _init
330 };
331 })(Array.prototype);
332
333 /**
334 * Initialize when DOM is ready
335 */
336 document.addEventListener('readystatechange', function() {
337 if (document.readyState === 'complete') {
338 AVCPTableFilter.init();
339 }
340 });
341
342 // Fallback for older browsers
343 if (document.readyState === 'complete') {
344 AVCPTableFilter.init();
345 }
346
347 })(document);
348 </script>
349