Files
select-and-handle/select-and-handle.php

432 lines
18 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
ob_start(); // Sofortiges Output Buffering
/**
* Plugin Name: Select and Handle
* Description: Plugin zum Verwalten und Verarbeiten von Beiträgen, Seiten und Custom Post Types (ohne AJAX, erweiterte Funktionen).
* Version: 1.22
* Author: Victor Giers
* License: CC0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Verarbeitet Formular- und URL-Anfragen:
* - Werden per POST ausgewählte Beiträge übermittelt, so werden diese (ohne Duplikate) zur bestehenden Auswahl hinzugefügt.
* - Wird per GET der Parameter "remove" übergeben, so wird der entsprechende Eintrag entfernt.
*
* Alle relevanten GET‑Parameter (tab, paged, orderby, order, category) werden übernommen, sodass der Redirect
* den aktuellen Zustand beibehält.
* Falls bereits Output gesendet wurde, erfolgt der Redirect mittels JavaScript und Meta‑Refresh.
*/
function sah_handle_form_submissions() {
$action_taken = false;
$current_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'post';
// Alle relevanten Parameter übernehmen:
$params = array(
'tab' => $current_tab,
'paged' => isset($_GET['paged']) ? intval($_GET['paged']) : 1,
'orderby' => isset($_GET['orderby']) ? sanitize_text_field($_GET['orderby']) : 'modified',
'order' => isset($_GET['order']) ? sanitize_text_field($_GET['order']) : 'desc',
'category'=> isset($_REQUEST['category']) ? intval($_REQUEST['category']) : 0,
);
$redirect_url = add_query_arg( $params, admin_url('admin.php?page=select-and-handle') );
// Beiträge hinzufügen (POST)
if ( $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['selected_posts']) && is_array($_POST['selected_posts']) ) {
$new_posts = array_map( 'intval', $_POST['selected_posts'] );
$current = get_option( 'sah_selected_posts', [] );
$merged = array_unique( array_merge( $current, $new_posts ) );
update_option( 'sah_selected_posts', $merged );
$action_taken = true;
}
// Einzelnen Eintrag entfernen (GET)
if ( isset($_GET['remove']) ) {
$remove_id = intval($_GET['remove']);
$current = get_option( 'sah_selected_posts', [] );
if ( ($key = array_search($remove_id, $current)) !== false ) {
unset($current[$key]);
update_option( 'sah_selected_posts', $current );
$action_taken = true;
}
}
if ( $action_taken ) {
if ( headers_sent() ) {
echo '<meta http-equiv="refresh" content="0; url=' . esc_url( $redirect_url ) . '">';
echo '<script type="text/javascript">window.location.href="' . esc_js( $redirect_url ) . '";</script>';
exit;
} else {
if ( ob_get_length() ) {
ob_end_clean();
}
wp_redirect( $redirect_url );
exit;
}
}
}
/**
* Fügt das Admin-Menü hinzu.
*/
function sah_add_admin_menu() {
add_menu_page( 'Select and Handle', 'Select and Handle', 'manage_options', 'select-and-handle', 'sah_admin_page', 'dashicons-edit', 25 );
}
add_action( 'admin_menu', 'sah_add_admin_menu' );
/**
* Zeigt die Admin-Seite an.
* Die Reihenfolge: Zuerst der Titel, dann die "Gespeicherte Auswahl" (direkt darunter), anschließend die Reiter, danach der Rest.
*/
function sah_admin_page() {
sah_handle_form_submissions();
$current_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'post';
?>
<div class="wrap">
<h1>Select and Handle</h1>
<style>
.wp-list-table .checkbox-column { width: 40px; }
.selected-posts-box { margin-bottom: 20px; }
.actions { margin-bottom: 20px; }
.sortable a { text-decoration: none; }
.sortable a.sorted-asc::after { content: " ▲"; }
.sortable a.sorted-desc::after { content: " ▼"; }
</style>
<!-- Gespeicherte Auswahl direkt unter dem Titel -->
<div class="selected-posts-box">
<?php echo sah_get_selected_list_html( $current_tab ); ?>
</div>
<!-- Handle-Button (sofern IDs in der gespeicherten Auswahl vorhanden sind) -->
<?php
$saved = get_option('sah_selected_posts', []);
$button_style = 'display: none;'; // Standardmäßig unsichtbar
if ( ! empty( $saved ) ) {
$button_style = 'display: inline-block;'; // Falls Einträge vorhanden, sichtbar
}
?>
<div class="actions" style="<?php echo esc_attr($button_style); ?>">
<button id="handle-button" class="button button-primary">Handle</button>
</div>
<!-- Container für die Ausgabe von select-and-handle.php -->
<div id="handle-container" style="display: none; margin-top: 20px;">
<iframe id="handle-result" src="about:blank" style="width: 100%; border: 1px solid #ccc; min-height: 100px;"></iframe>
</div>
<script>
document.getElementById('handle-button').addEventListener('click', function(e) {
e.preventDefault();
var ids = "<?php echo implode(',', get_option('ait_selected_posts', [])); ?>";
if(ids === "") {
alert("Keine IDs vorhanden.");
return;
}
var url = "<?php echo plugin_dir_url(__FILE__); ?>handle.php?ids=" + encodeURIComponent(ids);
document.getElementById('handle-result').src = url;
document.getElementById('handle-container').style.display = 'block';
});
</script>
<!-- Reiter -->
<h2 class="nav-tab-wrapper">
<a href="?page=select-and-handle&tab=post" class="nav-tab <?php echo ($current_tab == 'post') ? 'nav-tab-active' : ''; ?>">Beiträge</a>
<a href="?page=select-and-handle&tab=page" class="nav-tab <?php echo ($current_tab == 'page') ? 'nav-tab-active' : ''; ?>">Seiten</a>
<?php
$custom_post_types = get_post_types( ['_builtin' => false], 'objects' );
foreach ( $custom_post_types as $cpt ) {
echo '<a href="?page=select-and-handle&tab=' . esc_attr( $cpt->name ) . '" class="nav-tab ' . (($current_tab == $cpt->name) ? 'nav-tab-active' : '') . '">' . esc_html( $cpt->label ) . '</a>';
}
?>
</h2>
<?php sah_display_table( $current_tab ); ?>
</div>
<?php
ob_end_flush();
}
/**
* Erzeugt den HTML-Code für die "Gespeicherte Auswahl".
* Die Titel werden fett (ohne Link) ausgegeben; es erscheint kein "Bearbeiten"-Link.
*/
function sah_get_selected_list_html( $current_tab ) {
ob_start();
$selected_posts = get_option( 'sah_selected_posts', [] );
echo '<div>';
echo '<h3>Gespeicherte Auswahl</h3>';
if ( ! empty( $selected_posts ) ) {
echo '<div id="selected-posts-list">';
$titles = [];
// Alle relevanten Parameter übernehmen, um den Zustand beizubehalten.
$params = array(
'tab' => $current_tab,
'paged' => isset($_GET['paged']) ? intval($_GET['paged']) : 1,
'orderby' => isset($_GET['orderby']) ? sanitize_text_field($_GET['orderby']) : 'modified',
'order' => isset($_GET['order']) ? sanitize_text_field($_GET['order']) : 'desc',
'category'=> isset($_GET['category']) ? intval($_GET['category']) : 0,
);
foreach ( $selected_posts as $post_id ) {
$post = get_post( $post_id );
if ( $post && ! empty( $post->post_title ) ) {
$remove_params = $params;
$remove_params['remove'] = $post_id;
$remove_url = add_query_arg( $remove_params, admin_url( 'admin.php?page=select-and-handle' ) );
$titles[] = '<span class="selected-item" data-id="' . esc_attr( $post_id ) . '">'
. '<strong>' . esc_html( $post->post_title ) . '</strong>'
. ' <a class="remove-item" href="' . esc_url( $remove_url ) . '">[x]</a>'
. '</span>';
}
}
echo implode( '<br>', $titles );
echo '</div>';
} else {
echo '<p>Keine Einträge ausgewählt.</p>';
}
echo '</div>';
return ob_get_clean();
}
/**
* Gibt für einen Beitrag alle Begriffe aus den hierarchischen Taxonomien zurück.
*/
function sah_get_post_categories( $post_id, $post_type ) {
$taxonomies = get_object_taxonomies( $post_type, 'objects' );
$category_names = [];
foreach ( $taxonomies as $taxonomy ) {
if ( $taxonomy->hierarchical ) {
$terms = get_the_terms( $post_id, $taxonomy->name );
if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
foreach ( $terms as $term ) {
$category_names[] = $term->name;
}
}
}
}
return $category_names;
}
/**
* Ermittelt die erste hierarchische Taxonomie für den Post Type.
*/
function sah_get_category_taxonomy( $post_type ) {
$taxonomies = get_object_taxonomies( $post_type, 'objects' );
foreach ( $taxonomies as $taxonomy ) {
if ( $taxonomy->hierarchical ) {
return $taxonomy->name;
}
}
return '';
}
/**
* Erzeugt einen sortierbaren Link.
* Spalten, die sortierbar sein sollen, sind "Titel" und "Last Modified".
* Jetzt wird auch der aktuelle Kategorienfilter übernommen, sodass sich der Filter nicht zurücksetzt.
*/
function sah_sort_link( $column_label, $column_key, $current_tab ) {
$current_orderby = $_GET['orderby'] ?? 'modified';
$current_order = $_GET['order'] ?? 'desc';
$next_order = 'asc';
$class = 'sortable';
if ( $current_orderby === $column_key ) {
if ( $current_order === 'asc' ) {
$next_order = 'desc';
$class .= ' sorted-asc';
} else {
$next_order = 'asc';
$class .= ' sorted-desc';
}
}
$category = isset($_REQUEST['category']) ? intval($_REQUEST['category']) : 0;
$url = add_query_arg( [
'orderby' => $column_key,
'order' => $next_order,
'tab' => $current_tab,
'category' => $category,
], admin_url( 'admin.php?page=select-and-handle' ) );
return '<a href="' . esc_url( $url ) . '" class="' . esc_attr( $class ) . '">' . esc_html( $column_label ) . '</a>';
}
/**
* Zeigt eine Tabelle mit Beiträgen, Seiten oder Custom Post Types an.
* Die Tabelle enthält:
* - Eine Checkbox-Spalte
* - Spalte "Titel": Fett dargestellter Link zum Beitrag (öffnet in neuem Tab) plus Statusanzeige (Privat/Entwurf) und einen "Bearbeiten"-Link.
* - Spalte "Kategorie"
* - Spalte "Last Modified": basierend auf dem Datum der letzten Änderung (Format: "YYYY/MM/DD at g:i a").
* Die Tabelle ist sortierbar nach "Titel" und "Last Modified".
* Die Pagination wird oben und unten angezeigt, jeweils rechtsbündig.
*
* Wichtig: Der Kategorien-Filter wird über ein separates GET-Formular geregelt – der Parameter "category" wird über $_REQUEST ausgelesen.
*/
function sah_display_table( $post_type ) {
$selected_category = intval( $_REQUEST['category'] ?? 0 );
$paged = max( 1, intval( $_GET['paged'] ?? 1 ) );
$posts_per_page = 10;
$orderby = $_GET['orderby'] ?? 'modified';
$order = $_GET['order'] ?? 'desc';
$args = [
'post_type' => $post_type,
'posts_per_page' => $posts_per_page,
'paged' => $paged,
'post_status' => [ 'publish', 'private', 'draft' ],
'orderby' => $orderby,
'order' => strtoupper( $order ),
];
$category_tax = sah_get_category_taxonomy( $post_type );
if ( $selected_category && $category_tax ) {
$args['tax_query'] = [[
'taxonomy' => $category_tax,
'field' => 'term_id',
'terms' => $selected_category,
]];
}
$query = new WP_Query( $args );
// Kategorie-Filter-Formular und Pagination oben in einem flexiblen Container
$category_filter_html = '';
if ( $category_tax ) {
ob_start();
echo '<form method="get" action="" class="category-filter">';
echo '<input type="hidden" name="page" value="select-and-handle">';
echo '<input type="hidden" name="tab" value="' . esc_attr( $post_type ) . '">';
echo '<select name="category" onchange="this.form.submit();">';
echo '<option value="0">Alle Kategorien</option>';
$terms = get_terms( [ 'taxonomy' => $category_tax, 'hide_empty' => false ] );
if ( ! is_wp_error( $terms ) ) {
foreach ( $terms as $term ) {
echo '<option value="' . esc_attr( $term->term_id ) . '" ' . selected( $selected_category, $term->term_id, false ) . '>' . esc_html( $term->name ) . '</option>';
}
}
echo '</select>';
echo '</form>';
$category_filter_html = ob_get_clean();
}
$pagination_html = '';
if ( $query->max_num_pages > 1 ) {
ob_start();
echo '<div class="pagination" style="text-align: right;">';
for ( $i = 1; $i <= $query->max_num_pages; $i++ ) {
$page_url = add_query_arg( [
'paged' => $i,
'tab' => $post_type,
'orderby' => $orderby,
'order' => $order,
'category'=> $selected_category,
], admin_url( 'admin.php?page=select-and-handle' ) );
$class = ( $paged == $i ) ? 'button button-primary' : 'button';
echo '<a href="' . esc_url( $page_url ) . '" class="' . esc_attr( $class ) . '">' . $i . '</a> ';
}
echo '</div>';
$pagination_html = ob_get_clean();
}
// Ausgabe des kombinierten Bereichs (Filter und obere Pagination)
if ( ! empty( $category_filter_html ) || ! empty( $pagination_html ) ) {
echo '<div class="filter-pagination" style="display: flex; justify-content: space-between; align-items: center; margin: 10px;">';
echo '<div class="filter-left">' . $category_filter_html . '</div>';
echo '<div class="pagination-right">' . $pagination_html . '</div>';
echo '</div>';
}
// Formular mit Tabelle
?>
<form id="handle-form" method="post" action="">
<!-- Versteckte Felder, um GET-Parameter beizubehalten -->
<input type="hidden" name="page" value="select-and-handle">
<input type="hidden" name="tab" value="<?php echo esc_attr( $post_type ); ?>">
<input type="hidden" name="category" value="<?php echo esc_attr( $selected_category ); ?>">
<input type="hidden" name="paged" value="<?php echo esc_attr( $_GET['paged'] ?? 1 ); ?>">
<input type="hidden" name="orderby" value="<?php echo esc_attr( $_GET['orderby'] ?? 'modified' ); ?>">
<input type="hidden" name="order" value="<?php echo esc_attr( $_GET['order'] ?? 'desc' ); ?>">
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th class="checkbox-column"><input type="checkbox" id="select-all"></th>
<th><?php echo sah_sort_link( 'Titel', 'title', $post_type ); ?></th>
<th>Kategorie</th>
<th><?php echo sah_sort_link( 'Last Modified', 'modified', $post_type ); ?></th>
</tr>
</thead>
<tbody>
<?php
if ( $query->have_posts() ) :
while ( $query->have_posts() ) : $query->the_post();
$post_id = get_the_ID();
$status = get_post_status( $post_id );
$status_text = '';
if ( $status === 'private' ) {
$status_text = ' <span style="color:#a00;">(Privat)</span>';
} elseif ( $status === 'draft' ) {
$status_text = ' <span style="color:#a00;">(Entwurf)</span>';
}
?>
<tr>
<td class="checkbox-column">
<input type="checkbox" name="selected_posts[]" value="<?php echo $post_id; ?>">
</td>
<td>
<a href="<?php echo esc_url( get_permalink( $post_id ) ); ?>" target="_blank">
<strong><?php echo esc_html( get_the_title() ); ?></strong>
</a>
<?php echo $status_text; ?>
<br>
<small>
<a href="<?php echo esc_url( admin_url( 'post.php?post=' . $post_id . '&action=edit' ) ); ?>" target="_blank">
Bearbeiten
</a>
</small>
</td>
<td><?php echo esc_html( implode( ', ', sah_get_post_categories( $post_id, $post_type ) ) ); ?></td>
<td>
<strong>Last Modified</strong><br>
<?php echo esc_html( get_the_modified_date( 'Y/m/d \a\t g:i a' ) ); ?>
</td>
</tr>
<?php
endwhile;
else :
?>
<tr>
<td colspan="4">Keine Beiträge gefunden.</td>
</tr>
<?php
endif;
wp_reset_postdata();
?>
</tbody>
</table>
<p>
<input type="submit" name="save_selection" class="button button-primary" value="Zur Auswahl hinzufügen">
</p>
</form>
<script>
// Select-All Funktionalität
document.addEventListener('DOMContentLoaded', function() {
var selectAllCheckbox = document.getElementById('select-all');
if ( selectAllCheckbox ) {
selectAllCheckbox.addEventListener('change', function() {
var checkboxes = document.querySelectorAll('tbody input[type="checkbox"]');
checkboxes.forEach(function(cb) {
cb.checked = selectAllCheckbox.checked;
});
});
}
});
</script>
<?php
}
?>