Plugin Directory

Changeset 3627752


Ignore:
Timestamp:
07/29/2026 11:20:46 PM (3 months ago)
Author:
wpflamingojoe
Message:

Release 1.9.6: connect als Standard-Zahlweise, Text-Anpassungen auf der Einstellungsseite

Location:
sell-ads
Files:
1 deleted
18 edited
13 copied

Legend:

Unmodified
Added
Removed
  • sell-ads/tags/1.9.6/css/style.css

    r3626502 r3627752  
    2626}
    2727
     28/* Freistehende, abgerundete Karte auf hellem Grund (SS1) statt des frueheren
     29   kantigen Vollbreiten-Bandes. Der Radius stand bis 1.8.2 per !important auf 0
     30   und musste hier aktiv ueberschrieben werden - jetzt traegt die Box Ecken und
     31   einen weichen Schatten selbst. width:100% bleibt: die Karte fuellt die Breite
     32   ihres Widgets/Containers, hebt sich aber durch Radius und Schatten ab. */
    2833.wpsap_ads_plugin_ads_box {
    2934    width: 100%;
    3035    box-sizing: border-box;
    31     background-color: var(--wpsap-canvas, #f0f1f2);
    32     border-radius: 0 !important;
     36    background-color: #F2F3F5;
     37    /* border-radius: 14px; */
     38    /* box-shadow: var(--wpsap-shadow, 0 1px 3px rgba(0, 0, 0, .08)); */
    3339    text-align: center;
    3440    display: flex;
  • sell-ads/tags/1.9.6/inc/ajax.php

    r3626067 r3627752  
    8484        update_post_meta($order_id,'order_image',$data['url']);
    8585        update_post_meta($order_id,'order_url',$site_url);
     86        // Attachment-ID mitfuehren, damit das Motiv beim Ablehnen/Kuendigen/
     87        // Ablaufen restlos aus der Mediathek entfernt werden kann
     88        // (wpsap_delete_order_media() in inc/helper.php).
     89        update_post_meta($order_id,'wpsap_order_image_id',$image);
    8690    }
    8791    else {
  • sell-ads/tags/1.9.6/inc/campaign_fields.php

    r3626502 r3627752  
    1616 function wpsap_campaign_metaboxes_html(){
    1717    global $post;
     18    // Keine Prefill-Defaults: leere Felder bleiben leer, damit im Frontend nur
     19    // erscheint, was der Betreiber aktiv eingetragen hat.
    1820    $campaign_title = get_post_meta($post->ID, "wpsap_campaign_title",true);
    19     if(empty($campaign_title)){
    20         $campaign_title = 'This Advertising space is for sale';
    21     }
    2221
    2322    $campaign_name = get_the_title($post->ID);
    … …  
    2726
    2827    $button_text = get_post_meta($post->ID, "wpsap_button_text",true);
    29     if(empty($button_text)){
    30         $button_text = 'Buy';
    31     }
    3228
    3329    // Ohne dieses Feld schlug die Nonce-Pruefung beim Speichern immer fehl
    … …  
    5349  <span class="tooltiptext">This title is used to advertise your ad space.</span>
    5450</div>
    55             <input type="text" name="wpsap_campaign_title" class="regular-text" value="<?php echo esc_attr($campaign_title); ?>" required>
     51            <input type="text" name="wpsap_campaign_title" class="regular-text" value="<?php echo esc_attr($campaign_title); ?>">
    5652            </td>
    5753         </tr>
    … …  
    6864         </tr>
    6965
     66         <?php $currency = strtoupper(wpsap_stripe_currency()); ?>
    7067         <tr>
    71             <td><h4>Monthly Price ($)</h4></td>
     68            <td><h4>Monthly Price (<?php echo esc_html($currency); ?>)</h4></td>
    7269            <td>
    7370            <div class="tooltip"><span class="dashicons dashicons-info"></span>
    74   <span class="tooltiptext">This is the monthly price in US dollars for which you want to rent out your ad space.</span>
     71  <span class="tooltiptext">This is the monthly price in <?php echo esc_html($currency); ?> for which you want to rent out your ad space.</span>
    7572</div>
    7673            <input type="number" step="0.01" min="0" name="wpsap_campaign_price" class="regular-text" value="<?php echo esc_attr(get_post_meta($post->ID, "wpsap_campaign_price",true)); ?>" required>
    … …  
    194191    $columns['title'] = __( 'Campaign', "wpsap");
    195192    $columns['price'] = __( 'Price', "wpsap");
    196    
     193    // Info-Icon mit CSS-Tooltip: erklaert die zwei gezaehlten Ereignisse. Der
     194    // native title-Tooltip erschien nicht zuverlaessig, deshalb dieselbe
     195    // .tooltiptext-Mechanik wie in der Metabox. Das noetige CSS steht - auf die
     196    // Liste gescoped - im admin_footer (wpsap_campaign_copy_field()). WordPress
     197    // gibt das Spalten-Label als HTML aus, daher ohne eigenes Enqueue.
     198    // Icon und Text stehen in getrennten Spans: nur das Icon traegt die
     199    // dashicons-Klasse, sonst wuerde der Tooltip-Text in der Icon-Schrift
     200    // gerendert.
     201    $columns['clicks'] = __( 'Clicks', "wpsap")
     202        . ' <span class="wpsap-th-tip" tabindex="0">'
     203        . '<span class="dashicons dashicons-info"></span>'
     204        . '<span class="wpsap-th-tiptext">'
     205        . esc_html__('Total clicks for this ad slot: clicks on the “Buy” button (before the slot is sold) plus outbound clicks on the published ad.','wpsap_ads_plugin')
     206        . '</span></span>';
    197207    $columns['pdate'] = __( 'Date', "wpsap");
    198208    $columns['php'] = __( 'PHP', "wpsap");
    … …  
    211221            echo  esc_html(get_post_meta($post_id, "wpsap_campaign_price",true).' '.strtoupper(wpsap_stripe_currency())." per month");
    212222            break;
     223        case 'clicks' :
     224            // Zaehler wird an zwei Stellen hochgezaehlt: beim /goto-Redirect
     225            // (inc/hooks.php) und beim Buy-Button-Klick (wpsap_start_checkout,
     226            // inc/checkout.php).
     227            // Als Link auf die Kampagne ausgegeben, damit die Zahl - wie in der
     228            // Vorlage - klickbar ist und in WP-Blau erscheint.
     229            $clicks = (int) get_post_meta($post_id, "wpsap_click_count",true);
     230            $edit_link = get_edit_post_link($post_id);
     231            if($edit_link){
     232                echo '<a href="'.esc_url($edit_link).'">'.esc_html($clicks).'</a>';
     233            } else {
     234                echo esc_html($clicks);
     235            }
     236            break;
    213237        case 'php' :
    214             echo  esc_html(wpsap_plugin_php_function($post_id));
     238            // Bewusst ohne esc_html(): wpsap_plugin_php_function() liefert das
     239            // fertige Kopier-Feld und escapt seinen dynamischen Wert selbst.
     240            echo  wpsap_plugin_php_function($post_id);
    215241            break;
    216242        case 'shortcode' :
    217             echo  esc_html(wpsap_shortcode_input($post_id));
     243            echo  wpsap_shortcode_input($post_id);
    218244            break;
    219245
    … …  
    230256    $columns['order'] = __( 'Order', "wpsap");
    231257    $columns['campaign'] = __( 'Campaign', "wpsap");
    232     $columns['pdate'] = __( 'Date', "wpsap");
     258    $columns['pdate'] = __( 'Order Date', "wpsap");
    233259    $columns['status'] = __( 'Status', "wpsap");
    234     $columns['billing'] = __( 'Billing', "wpsap");
    235260    $columns['resources'] = __( 'Resources', "wpsap");
    236261    $columns['link'] = __( 'Link', "wpsap");
    … …  
    251276            // entstanden sind - die Spalte Total zeigt trotzdem einen Betrag.
    252277            if(get_post_meta($post_id,'wpsap_test_order',true) === 'yes'){
    253                 echo '<br/><small><span style="color:#996800;">'.esc_html__('Test order — not paid','wpsap_ads_plugin').'</span></small>';
     278                echo '<br/><small><span style="color:#996800;">'.esc_html__('Test order','wpsap_ads_plugin').'</span></small>';
    254279            }
    255280            break;
    … …  
    268293                $final_dates = wpsap_order_days_left($post_id);
    269294                if($final_dates > 0){
     295                    // Ablaufdatum = Bestelldatum + 14 Tage, dieselbe Frist wie
     296                    // in wpsap_order_days_left().
     297                    $placed = get_post_time('U',true,$post_id);
     298                    $expires = $placed ? $placed + 14 * DAY_IN_SECONDS : time() + $final_dates * DAY_IN_SECONDS;
    270299                    echo '<strong>Pending</strong>';
    271                      echo '<br/><small><span>('.esc_html($final_dates).' days until expiration)</span></small>';
     300                     echo '<br/><small><span>(Request expires on '.esc_html(wp_date(get_option('date_format'),$expires)).')</span></small>';
    272301                }
    273302                else {
    … …  
    291320                   echo "<span style='color:#ff0000;'>Rejected</span>";
    292321               }
     322               else if($current_status == 'expired'){
     323                   $on = (int) get_post_meta($post_id, "wpsap_expired_on",true);
     324                   if(!$on){
     325                       $placed = get_post_time('U',true,$post_id);
     326                       $on = $placed ? $placed + 14 * DAY_IN_SECONDS : 0;
     327                   }
     328                   echo "<span style='color:#ff0000;'>Expired</span>";
     329                   if($on){
     330                       echo "<br/><small>(on ".esc_html(wp_date(get_option('date_format'),$on)).")</small>";
     331                   }
     332               }
    293333               else {
    294334                    echo esc_html($current_status);
    … …  
    297337            }
    298338            break;
    299         case 'billing' :
    300             echo  esc_html(get_post_meta($post_id, "order_email",true));
    301              break;
    302339        case 'resources' :
    303             $url = get_post_meta($post_id, "order_url",true);
    304             echo  '<a target="_blank" href="'.esc_url(get_post_meta($post_id, "order_image",true)).'"><img style="max-width:120px;" src="'.esc_url(get_post_meta($post_id, "order_image",true)).'"></a>';
     340            // Nach Reject/Cancel/Expired ist das Motiv geloescht (order_image
     341            // leer) - dann kein kaputtes <img> ausgeben.
     342            $image = get_post_meta($post_id, "order_image",true);
     343            if($image){
     344                echo  '<a target="_blank" href="'.esc_url($image).'"><img style="max-width:120px;" src="'.esc_url($image).'"></a>';
     345            } else {
     346                echo '&mdash;';
     347            }
    305348            break;
    306349        case 'link' :
    307350            $url = get_post_meta($post_id, "order_url",true);
    308             echo  '<a href="'.esc_url($url).'">'.esc_url($url).'</a>';
     351            if($url){
     352                echo  '<a href="'.esc_url($url).'">'.esc_url($url).'</a>';
     353            } else {
     354                echo '&mdash;';
     355            }
    309356            break;
    310357        case 'total' :
    311358            $status = get_post_meta($post_id, "wpsap_order_status",true);
    312359            if($status == "accepted"){
    313                 echo "<small>You have confirmed the advertising partnership. Now you need to publish the advertisement. Make sure that the ad is displayed correctly and that the product link works. If there are any errors, you should contact the buyer immediately. If the buyer does not cancel within the next 14 days, the first transaction will be executed. The monthly payment will remain until someone cancels.</small>";
     360                echo "<small>You have confirmed the advertising partnership, and the first payment was charged when you accepted. Please publish the advertisement now: make sure it displays correctly and that the product link works, and contact the buyer right away if anything is wrong. The monthly payment continues until the subscription is cancelled &mdash; either by you here, or by the buyer from their own Stripe billing portal, which they can reach through the link on their order confirmation page.</small>";
    314361            }
    315362            else {
    … …  
    320367                    $currency = wpsap_stripe_currency();
    321368                }
    322                 echo esc_html(number_format_i18n($total,2).' '.strtoupper($currency));
     369                echo esc_html(number_format_i18n($total,2).' '.strtoupper($currency).' per month');
    323370            }
    324371            break;
    … …  
    334381                echo 'This subscription was cancelled by the buyer.';
    335382            }
     383            elseif($status == 'expired'){
     384                // Nach 14 Tagen ohne Annahme ueber wpsap_expire_ads() beendet -
     385                // das Abo stand noch im Trial, es wurde also nichts berechnet.
     386                echo 'This request expired unanswered. No payment was charged.';
     387            }
    336388            elseif($status != "accepted" && $status != "published"){
    337             echo '<a href="'.esc_url(wpsap_order_action_url('reject',$post_id)).'" class="button reject_ads">Reject</a> | <a href="'.esc_url(wpsap_order_action_url('accept',$post_id)).'" class="button accept_ads">Accept</a>';
     389            echo '<a href="'.esc_url(wpsap_order_action_url('accept',$post_id)).'" class="button button-primary accept_ads">Accept</a> | <a href="'.esc_url(wpsap_order_action_url('reject',$post_id)).'" class="button reject_ads">Reject</a>';
    338390            }
    339391
    … …  
    343395             }
    344396             else {
    345                echo '<a href="'.esc_url(wpsap_order_action_url('accept',$post_id)).'" class="button button-primary accept_ads">Unpublish</a> | <a href="'.esc_url(wpsap_order_action_url('reject',$post_id)).'" class="button reject_ads">Cancel</a>';
     397               echo '<a href="'.esc_url(wpsap_order_action_url('accept',$post_id)).'" class="button accept_ads">Unpublish</a> | <a href="'.esc_url(wpsap_order_action_url('reject',$post_id)).'" class="button reject_ads">Cancel</a>';
    346398             }
    347399            }
    … …  
    356408    if(isset($_GET['post_type'])){
    357409        if(sanitize_text_field($_GET['post_type']) == 'ads_order'){
    358     ?> 
     410    ?>
    359411    <style>
    360412.tablenav.top {
    … …  
    363415</style>
    364416
    365 <?php
    366         }
    367     }
     417<?php
     418        }
     419    }
     420}
     421
     422
     423// Kopier-Verhalten und Optik der PHP-/Shortcode-Felder in der Kampagnenliste.
     424add_action('admin_footer','wpsap_campaign_copy_field');
     425function wpsap_campaign_copy_field(){
     426    if(!isset($_GET['post_type']) || sanitize_text_field(wp_unslash($_GET['post_type'])) !== 'ads_campaign'){
     427        return;
     428    }
     429    ?>
     430<style>
     431.wpsap-copy { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; }
     432/* Weisses, schreibgeschuetztes Feld mit dezentem Rahmen - wie in der Vorlage
     433   (kein grauer Fuellton). Erhoehte Spezifitaet (span + input.klasse), weil WP
     434   readonly-Felder sonst grau einfaerbt (input[readonly]). */
     435.wpsap-copy input.wpsap-copy__input { width: 150px; max-width: 100%; background: #fff; color: #3c434a; }
     436/* Randloses Kopier-Icon statt umrandetem Button: die Vorlage zeigt nur das
     437   schlichte Dokument-Symbol ohne Button-Rahmen (die WP-.button-Klasse ist im
     438   Markup deshalb entfernt). */
     439.wpsap-copy__btn { display: inline-flex; align-items: center; justify-content: center; background: none; border: none; box-shadow: none; outline: none; padding: 0 2px; min-height: 0; color: #50575e; cursor: pointer; }
     440.wpsap-copy__btn:hover { color: #2271b1; background: none; box-shadow: none; }
     441.wpsap-copy__btn:focus { background: none; box-shadow: none; outline: none; }
     442.wpsap-copy__btn .dashicons { width: 18px; height: 18px; font-size: 18px; }
     443.wpsap-copy__btn.is-copied { color: #46b450; }
     444
     445/* Info-Tooltip im Spaltenkopf "Clicks". Aufbau wie die Metabox-Tooltips, aber
     446   eigene Klassen, damit nichts mit anderen Admin-Styles kollidiert. */
     447.wpsap-th-tip { position: relative; display: inline-flex; align-items: center; vertical-align: middle; margin-top: -2px; cursor: help; color: #787c82; }
     448.wpsap-th-tip .dashicons-info { font-size: 16px; width: 16px; height: 16px; line-height: 16px; }
     449.wpsap-th-tip .wpsap-th-tiptext {
     450    visibility: hidden; opacity: 0; transition: opacity .1s ease;
     451    position: absolute; z-index: 100; top: 150%; left: 50%; transform: translateX(-50%);
     452    width: 240px; padding: 8px 10px; border-radius: 4px;
     453    background: #1d2327; color: #fff;
     454    font: 400 12px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
     455    text-align: left; white-space: normal; font-weight: 400; text-transform: none;
     456}
     457.wpsap-th-tip:hover .wpsap-th-tiptext,
     458.wpsap-th-tip:focus .wpsap-th-tiptext,
     459.wpsap-th-tip:focus-within .wpsap-th-tiptext { visibility: visible; opacity: 1; }
     460</style>
     461<script>
     462jQuery(function($){
     463    $(document).on('click','.wpsap-copy__btn',function(){
     464        var $btn = $(this);
     465        var $input = $btn.closest('.wpsap-copy').find('.wpsap-copy__input');
     466        var text = $input.val();
     467
     468        var done = function(){
     469            $btn.addClass('is-copied');
     470            setTimeout(function(){ $btn.removeClass('is-copied'); },1200);
     471        };
     472
     473        if(navigator.clipboard && navigator.clipboard.writeText){
     474            navigator.clipboard.writeText(text).then(done, function(){
     475                $input.trigger('select'); document.execCommand('copy'); done();
     476            });
     477        } else {
     478            $input.trigger('select'); document.execCommand('copy'); done();
     479        }
     480    });
     481});
     482</script>
     483    <?php
     484}
     485
     486
     487// Doppelte Bestaetigung fuer Reject/Cancel: beide Buttons loesen dieselbe
     488// irreversible Aktion aus (Stripe-Abo kuendigen). Ein eigenes Modal faengt den
     489// Klick ab und verlangt, dass der Betreiber das Aktionswort tippt, bevor der
     490// bereits nonce-gesicherte Link geoeffnet wird. Reiner Frontend-Schutz - der
     491// Serverpfad in wpsap_job_status() bleibt unveraendert.
     492add_action('admin_footer','wpsap_order_reject_confirm');
     493function wpsap_order_reject_confirm(){
     494    if(!isset($_GET['post_type']) || sanitize_text_field(wp_unslash($_GET['post_type'])) !== 'ads_order'){
     495        return;
     496    }
     497    ?>
     498<style>
     499#wpsap-reject-modal { position: fixed; inset: 0; z-index: 100000; display: none; }
     500#wpsap-reject-modal.is-open { display: block; }
     501#wpsap-reject-modal .wpsap-reject-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
     502#wpsap-reject-modal .wpsap-reject-box { position: relative; max-width: 420px; margin: 12vh auto 0; background: #fff; border-radius: 4px; box-shadow: 0 6px 30px rgba(0,0,0,0.3); padding: 24px; }
     503#wpsap-reject-modal .wpsap-reject-title { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; font-size: 18px; font-weight: 600; color: #b32d2e; }
     504#wpsap-reject-modal .wpsap-reject-text { margin: 0 0 16px; color: #3c434a; line-height: 1.5; }
     505#wpsap-reject-modal .wpsap-reject-label { display: block; margin: 0 0 6px; font-weight: 600; color: #3c434a; }
     506#wpsap-reject-modal .wpsap-reject-input { width: 100%; box-sizing: border-box; margin: 0 0 16px; }
     507#wpsap-reject-modal .wpsap-reject-actions { display: flex; justify-content: flex-end; gap: 8px; }
     508#wpsap-reject-modal .wpsap-reject-confirm { background: #b32d2e; border-color: #b32d2e; color: #fff; }
     509#wpsap-reject-modal .wpsap-reject-confirm:hover:not(:disabled) { background: #9e2423; border-color: #9e2423; color: #fff; }
     510#wpsap-reject-modal .wpsap-reject-confirm:disabled { opacity: 0.5; cursor: not-allowed; }
     511</style>
     512<div id="wpsap-reject-modal" role="dialog" aria-modal="true" aria-labelledby="wpsap-reject-title">
     513    <div class="wpsap-reject-backdrop"></div>
     514    <div class="wpsap-reject-box">
     515        <h2 id="wpsap-reject-title" class="wpsap-reject-title"><span class="dashicons dashicons-warning"></span><span class="wpsap-reject-heading">Reject this ad?</span></h2>
     516        <p class="wpsap-reject-text">This permanently cancels the buyer&#8217;s active Stripe subscription. This action cannot be undone.</p>
     517        <label class="wpsap-reject-label" for="wpsap-reject-word">Type <strong class="wpsap-reject-word">REJECT</strong> to confirm:</label>
     518        <input type="text" id="wpsap-reject-word" class="wpsap-reject-input regular-text" autocomplete="off" autocapitalize="characters" spellcheck="false">
     519        <div class="wpsap-reject-actions">
     520            <button type="button" class="button wpsap-reject-cancel">Close</button>
     521            <button type="button" class="button wpsap-reject-confirm" disabled>Confirm</button>
     522        </div>
     523    </div>
     524</div>
     525<script>
     526jQuery(function($){
     527    var $modal   = $('#wpsap-reject-modal');
     528    var $input   = $('#wpsap-reject-word');
     529    var $confirm = $modal.find('.wpsap-reject-confirm');
     530    var targetHref = '';
     531    var expectedWord = 'REJECT';
     532
     533    function closeModal(){
     534        $modal.removeClass('is-open');
     535        targetHref = '';
     536        $input.val('');
     537        $confirm.prop('disabled', true);
     538    }
     539
     540    // Reject und Cancel teilen die Klasse .reject_ads; der Wortlaut folgt dem
     541    // Text des tatsaechlich geklickten Links (Reject oder Cancel).
     542    $(document).on('click','.reject_ads',function(e){
     543        e.preventDefault();
     544        targetHref = $(this).attr('href');
     545        var label = $.trim($(this).text()) || 'Reject';
     546        expectedWord = label.toUpperCase();
     547        $modal.find('.wpsap-reject-heading').text(label + ' this ad?');
     548        $modal.find('.wpsap-reject-word').text(expectedWord);
     549        $input.val('');
     550        $confirm.prop('disabled', true);
     551        $modal.addClass('is-open');
     552        $input.trigger('focus');
     553    });
     554
     555    $input.on('input',function(){
     556        $confirm.prop('disabled', $.trim($input.val()).toUpperCase() !== expectedWord);
     557    });
     558
     559    $confirm.on('click',function(){
     560        if($confirm.prop('disabled') || !targetHref){ return; }
     561        window.location.href = targetHref;
     562    });
     563
     564    $modal.find('.wpsap-reject-cancel, .wpsap-reject-backdrop').on('click', closeModal);
     565
     566    $(document).on('keydown',function(e){
     567        if(e.key === 'Escape' && $modal.hasClass('is-open')){ closeModal(); }
     568    });
     569});
     570</script>
     571    <?php
    368572}
    369573
  • sell-ads/tags/1.9.6/inc/checkout.php

    r3626502 r3627752  
    183183    }
    184184
     185    // Buy-Button-Klick zaehlen - derselbe Zaehler wie der /goto-Redirect
     186    // (inc/hooks.php), damit die Spalte "Clicks" beide Ereignisse summiert.
     187    // Selbst-Klicks eingeloggter Redakteure verfaelschen die Statistik nicht.
     188    if(get_post_type($campaign_id) === 'ads_campaign'
     189        && !(is_user_logged_in() && current_user_can('edit_posts'))){
     190        $count = (int) get_post_meta($campaign_id,'wpsap_click_count',true);
     191        update_post_meta($campaign_id,'wpsap_click_count',$count + 1);
     192    }
     193
    185194    // Testmodus: die Zahlungsseite entfaellt ersatzlos. Die Bestellung
    186195    // entsteht hier statt aus einer bezahlten Stripe-Session, danach geht es
    … …  
    326335
    327336/**
    328  * Taeglicher Lauf: Bestellungen, die 14 Tage unbeantwortet blieben,
    329  * werden abgelehnt und das Abo beendet. Frueher passierte das mitten
    330  * im Rendern der Bestellliste.
     337 * Taeglicher Lauf: Bestellungen, die 14 Tage unbeantwortet blieben, laufen ab
     338 * (Status "expired") und das Abo wird beendet - weil es noch im Trial steht,
     339 * ohne Belastung. Frueher passierte das mitten im Rendern der Bestellliste.
    331340 */
    332341add_action('wpsap_expire_pending_orders','wpsap_expire_pending_orders');
    … …  
    348357    foreach($orders as $order_id){
    349358        if(wpsap_order_days_left($order_id) <= 0){
    350             wpsap_reject_ads($order_id);
     359            wpsap_expire_ads($order_id);
    351360        }
    352361    }
  • sell-ads/tags/1.9.6/inc/connect.php

    r3626502 r3627752  
    2020
    2121function wpsap_payment_mode(){
    22     $mode = get_option('wpsap_payment_mode','own_key');
    23     return in_array($mode,array('own_key','connect'),true) ? $mode : 'own_key';
     22    $mode = get_option('wpsap_payment_mode','connect');
     23    return in_array($mode,array('own_key','connect'),true) ? $mode : 'connect';
    2424}
    2525
    … …  
    326326}
    327327
     328function wpsap_connect_end_trial($subscription_id){
     329    return wpsap_connect_request('POST','v1/subscription/end-trial',array('subscription_id' => $subscription_id));
     330}
     331
    328332function wpsap_connect_portal_url($customer_id,$return_url){
    329333    $session = wpsap_connect_request('POST','v1/portal',array(
  • sell-ads/tags/1.9.6/inc/helper.php

    r3626067 r3627752  
    22if ( ! defined( 'ABSPATH' ) ) exit;
    33
     4/**
     5 * Baut das kopierbare Feld fuer die PHP-Einbindung einer Kampagne.
     6 *
     7 * Rueckgabe ist fertiges HTML und wird in der Spalte direkt ausgegeben -
     8 * frueher lag ein esc_html() darum, das aus dem <input> sichtbaren Rohtext
     9 * machte. Escapt wird deshalb nur noch der dynamische Wert selbst.
     10 */
    411function wpsap_plugin_php_function($content){
    5 
    6     $html = '<input type="text" readonly value="<?php echo wpsap_show('.esc_attr($content).'); ?>">';
    7     return $html;
    8 
     12    $id    = absint($content);
     13    $value = '<?php echo wpsap_show('.$id.'); ?>';
     14    return wpsap_copy_field($value);
    915}
    1016
     17/**
     18 * Baut das kopierbare Feld fuer die Shortcode-Einbindung einer Kampagne.
     19 */
    1120function wpsap_shortcode_input($content){
    12     $html = '<input type="text" readonly value="[wpsap_ads id='.esc_attr($content).']">';
    13     return $html;
     21    $id    = absint($content);
     22    $value = '[wpsap_ads id='.$id.']';
     23    return wpsap_copy_field($value);
     24}
     25
     26/**
     27 * Gemeinsames Markup fuer die Kopier-Felder der Kampagnenliste: ein
     28 * schreibgeschuetztes Eingabefeld mit Betrag daneben ein Kopier-Button.
     29 * Der zugehoerige Klick-Handler liegt im admin_footer der Liste
     30 * (inc/campaign_fields.php).
     31 */
     32function wpsap_copy_field($value){
     33    return '<span class="wpsap-copy">'
     34        .'<input type="text" class="wpsap-copy__input" readonly value="'.esc_attr($value).'">'
     35        .'<button type="button" class="wpsap-copy__btn" title="'.esc_attr__('Copy','wpsap_ads_plugin').'">'
     36        .'<span class="dashicons dashicons-admin-page"></span>'
     37        .'</button>'
     38        .'</span>';
    1439}
    1540
    … …  
    4368
    4469/**
     70 * Entfernt Motiv und Ziellink einer endgueltig toten Bestellung restlos:
     71 * das Attachment aus der Mediathek (Datei inkl. resized Varianten, ohne
     72 * Papierkorb) sowie die Meta order_image/order_url. Jedes Motiv gehoert zu
     73 * genau einer Bestellung, deshalb ist das harte Loeschen unbedenklich.
     74 *
     75 * Die Attachment-ID steht seit dem Upload in wpsap_order_image_id; fuer
     76 * Bestandsbestellungen ohne dieses Meta wird sie aus der gespeicherten URL
     77 * aufgeloest.
     78 */
     79function wpsap_delete_order_media($order_id){
     80    $attachment_id = (int) get_post_meta($order_id,'wpsap_order_image_id',true);
     81    if(!$attachment_id){
     82        $image_url = get_post_meta($order_id,'order_image',true);
     83        if($image_url){
     84            $attachment_id = attachment_url_to_postid($image_url);
     85        }
     86    }
     87
     88    if($attachment_id && get_post_type($attachment_id) === 'attachment'){
     89        wp_delete_attachment($attachment_id,true);
     90    }
     91
     92    delete_post_meta($order_id,'order_image');
     93    delete_post_meta($order_id,'order_url');
     94    delete_post_meta($order_id,'wpsap_order_image_id');
     95}
     96
     97/**
    4598 * Ablehnen heisst: das Abo bei Stripe beenden. Der lokale Status wird erst
    4699 * danach gesetzt - frueher lief es umgekehrt, wodurch Plugin und Stripe
    … …  
    62115    ) );
    63116
     117    // Erst nach dem gesicherten Statuswechsel: Motiv und Link sind jetzt tot.
     118    wpsap_delete_order_media($order_id);
     119
     120    return true;
     121}
     122
     123/**
     124 * Ablauf einer unbeantworteten Bestellung nach 14 Tagen. Wie das Ablehnen
     125 * beendet es das Abo bei Stripe - weil das noch im Trial steht, entsteht dabei
     126 * keine Rechnung, der Kaeufer wird also nicht belastet. Getrennt von
     127 * wpsap_reject_ads(), damit der Zustand in der Liste als "Expired" (statt
     128 * "Rejected") erkennbar bleibt und das Ablaufdatum festgehalten wird.
     129 */
     130function wpsap_expire_ads($order_id){
     131    $subscription_id = get_post_meta($order_id,'wpsap_subscription_id',true);
     132    if($subscription_id){
     133        $result = wpsap_stripe_cancel_subscription($subscription_id);
     134        if(is_wp_error($result)){
     135            return $result;
     136        }
     137    }
     138
     139    // Ablaufdatum = Bestelldatum + 14 Tage, dieselbe Frist wie in
     140    // wpsap_order_days_left(). Festgehalten, damit die Liste "Expired on <Tag>"
     141    // zeigen kann, ohne es jedes Mal neu herzuleiten.
     142    $placed  = get_post_time('U',true,$order_id);
     143    $expires = $placed ? $placed + 14 * DAY_IN_SECONDS : time();
     144    update_post_meta($order_id,'wpsap_expired_on',$expires);
     145
     146    update_post_meta($order_id,"wpsap_order_status",'expired');
     147    wp_update_post( array(
     148        'ID'          => $order_id,
     149        'post_status' => 'rejected',
     150    ) );
     151
     152    // Wie beim Ablehnen: Motiv und Link sind mit dem Ablauf tot.
     153    wpsap_delete_order_media($order_id);
     154
    64155    return true;
    65156}
    … …  
    80171            return new WP_Error('wpsap_subscription_gone',__('This subscription is no longer active at Stripe.','wpsap_ads_plugin'));
    81172        }
     173
     174        // Erst mit der Annahme wird belastet: das Abo lief bis hierher im Trial
     175        // (siehe wpsap_stripe_build_checkout). Das Beenden des Trials stellt die
     176        // erste Rechnung sofort aus.
     177        $charged = wpsap_stripe_end_trial($subscription_id);
     178        if(is_wp_error($charged)){
     179            return $charged;
     180        }
    82181    }
    83182
  • sell-ads/tags/1.9.6/inc/hooks.php

    r3626067 r3627752  
    144144        }
    145145
    146         $order_date = get_the_date( 'l F j, Y',$order_id);
    147         $expected_date = date('l F j, Y',strtotime($order_date."+ 7days"));
    148 
     146        // Die Datumsangaben der Bestaetigungstabelle berechnet confirm.php
     147        // selbst aus dem Bestellzeitpunkt - hier ist nichts vorzubereiten.
    149148        require WPSAP_Plugin_Path.'inc/page/confirm.php';
    150149        die();
    … …  
    229228// neu registriert - die alte Registrierung ueberschrieb ihn global fuer
    230229// alle Post-Types.
     230
     231
     232/* --------------------------------------------------------------------------
     233 * Klick-Tracking ueber /goto/<slug>
     234 *
     235 * Die veroeffentlichte Anzeige verlinkt nicht mehr direkt auf die Zielseite,
     236 * sondern ueber diesen Endpoint (inc/shortcode.php). Er zaehlt den Klick auf
     237 * der Kampagne (wpsap_click_count) und leitet dann zur Zielseite weiter. Der
     238 * Zaehler erscheint in der Spalte "Clicks" der Kampagnenliste.
     239 * ------------------------------------------------------------------------- */
     240
     241add_filter('query_vars','wpsap_goto_query_var');
     242function wpsap_goto_query_var($vars){
     243    $vars[] = 'wpsap_goto';
     244    return $vars;
     245}
     246
     247add_action('init','wpsap_goto_rewrite');
     248function wpsap_goto_rewrite(){
     249    add_rewrite_rule('^goto/([^/]+)/?$','index.php?wpsap_goto=$matches[1]','top');
     250
     251    // Beim reinen Plugin-Update laeuft der Aktivierungshook nicht - ohne
     252    // diesen Guard griffe die Rewrite-Rule auf cybie.de erst nach manuellem
     253    // Neu-Speichern der Permalinks. Der Flush erfolgt genau einmal je Version.
     254    if(get_option('wpsap_rewrite_version') !== WPSAP_Version){
     255        flush_rewrite_rules(false);
     256        update_option('wpsap_rewrite_version',WPSAP_Version);
     257    }
     258}
     259
     260add_action('template_redirect','wpsap_handle_goto');
     261function wpsap_handle_goto(){
     262    $slug = get_query_var('wpsap_goto');
     263    if($slug === '' || $slug === null){
     264        return;
     265    }
     266
     267    $home = home_url('/');
     268
     269    // Slug -> Kampagne. Numerischer Wert wird als ID behandelt, sonst ueber
     270    // den post_name aufgeloest. Der CPT ist nicht oeffentlich, get_page_by_path
     271    // findet ihn trotzdem, weil der Post-Type explizit uebergeben wird.
     272    $campaign = null;
     273    if(ctype_digit((string) $slug)){
     274        $maybe = get_post(absint($slug));
     275        if($maybe && $maybe->post_type === 'ads_campaign'){
     276            $campaign = $maybe;
     277        }
     278    }
     279    if(!$campaign){
     280        $campaign = get_page_by_path(sanitize_title($slug),OBJECT,'ads_campaign');
     281    }
     282
     283    if(!$campaign){
     284        nocache_headers();
     285        wp_redirect($home,302);
     286        exit;
     287    }
     288
     289    // Zielseite = URL der aktuell veroeffentlichten Anzeige dieses Slots.
     290    $target = '';
     291    foreach(wpsap_get_active_ads($campaign->ID) as $ad){
     292        $url = get_post_meta($ad->ID,'order_url',true);
     293        if(!empty($url)){
     294            $target = $url;
     295            break;
     296        }
     297    }
     298
     299    if(empty($target)){
     300        nocache_headers();
     301        wp_redirect($home,302);
     302        exit;
     303    }
     304
     305    // Selbst-Klicks von Redakteuren verfaelschen die Statistik nicht.
     306    if(!(is_user_logged_in() && current_user_can('edit_posts'))){
     307        $count = (int) get_post_meta($campaign->ID,'wpsap_click_count',true);
     308        update_post_meta($campaign->ID,'wpsap_click_count',$count + 1);
     309    }
     310
     311    nocache_headers();
     312    // Ziel ist eine fremde Domain - wp_safe_redirect() wuerde das blocken.
     313    wp_redirect(esc_url_raw($target),302);
     314    exit;
     315}
  • sell-ads/tags/1.9.6/inc/page/confirm.php

    r3626502 r3627752  
    4343// Fuer die Seitenspalte: beide Zahlungswege legen diese Felder an -
    4444// wpsap_sync_order_from_session() ebenso wie wpsap_create_test_order().
    45 $wpsap_place_id = (int) get_post_meta($order_id,'wpsap_place_id',true);
    46 $wpsap_campaign_label = '';
    47 if($wpsap_place_id){
    48     $wpsap_campaign_label = (string) get_post_meta($wpsap_place_id,'wpsap_campaign_title',true);
    49     if($wpsap_campaign_label === ''){
    50         $wpsap_campaign_label = (string) get_the_title($wpsap_place_id);
    51     }
    52 }
    53 
    5445$wpsap_price_label = '';
    5546$wpsap_order_total = (int) get_post_meta($order_id,'order_total',true);
    … …  
    5849    $wpsap_price_label = number_format_i18n($wpsap_order_total / 100,2).' '.strtoupper((string) get_post_meta($order_id,'wpsap_currency',true));
    5950}
     51
     52// Datumsangaben der Bestaetigungstabelle. Bezugspunkt ist der Zeitpunkt der
     53// Bestellung; wp_date() wendet die WP-Zeitzone an. Der Kaeufer-Flow ist
     54// durchgaengig englisch, deshalb erzwingt switch_to_locale('en_US') englische
     55// Monatsnamen auch auf einer de_DE-Site. Das Billing-Datum ist eine
     56// Anzeige-Schaetzung fuer den Kaeufer (Prueffrist bis 14 Tage + ein Abo-Monat) -
     57// Stripe bucht im Abo technisch bereits beim Checkout.
     58$wpsap_order_ts = get_post_timestamp($order_id);
     59$wpsap_switched_locale = switch_to_locale('en_US');
     60$wpsap_submitted_date = $wpsap_order_ts ? wp_date('F j, Y',$wpsap_order_ts) : '';
     61$wpsap_billing_date = $wpsap_order_ts ? wp_date('F j, Y',strtotime('+14 days +1 month',$wpsap_order_ts)) : '';
     62if($wpsap_switched_locale){ restore_previous_locale(); }
    6063
    6164// Solange Bild oder Link fehlen, steht der Kaeufer noch im Upload-Schritt.
    … …  
    8588<?php } ?>
    8689
    87     <div class="wpsap-flow__grid">
    88 
    89         <aside class="wpsap-flow__aside">
    90             <div class="wpsap-flow__site"><?php echo esc_html(get_bloginfo('name')); ?></div>
    91 
    92             <?php if($wpsap_campaign_label !== '' || $wpsap_price_label !== ''){ ?>
    93             <div class="wpsap-flow__facts">
    94                 <?php if($wpsap_campaign_label !== ''){ ?>
    95                 <div class="wpsap-flow__fact">
    96                     <span class="wpsap-flow__fact-label"><?php esc_html_e('Ad space','wpsap_ads_plugin'); ?></span>
    97                     <span class="wpsap-flow__fact-value"><?php echo esc_html($wpsap_campaign_label); ?></span>
    98                 </div>
    99                 <?php } ?>
    100                 <?php if($wpsap_price_label !== ''){ ?>
    101                 <div class="wpsap-flow__fact">
    102                     <span class="wpsap-flow__fact-label"><?php esc_html_e('Per month','wpsap_ads_plugin'); ?></span>
    103                     <span class="wpsap-flow__fact-value"><?php echo esc_html($wpsap_price_label); ?></span>
    104                 </div>
    105                 <?php } ?>
    106             </div>
     90    <?php /* Schritt 1: Upload - einspaltig zentriert. Bewusst NICHT im
     91             Zwei-Spalten-Grid: die Aside gehoert erst auf die Bestaetigung.
     92             Sichtbar bzw. verborgen nur ueber display:none - der gezeigte
     93             Zustand kommt aus dem Stylesheet, damit wp-sap.js ihn per
     94             .hide()/.show() korrekt umschalten kann. */ ?>
     95    <section class="upload_ads_area" <?php if($wpsap_step_done){ echo 'style="display:none"'; } ?>>
     96        <div class="wpsap-single">
     97
     98            <h1 class="wpsap-step__title">Upload your Ad</h1>
     99
     100            <p class="wpsap-step__lead">Attention: Make sure that you upload your desired advertising image (JPG, PNG, GIF) in the preferred resolution (up to max. 10 MB) and add the correct link to your offer.</p>
     101
     102            <?php if(trim($note_on_upload_page) !== ''){ ?>
     103            <div class="wpsap-note"><?php echo esc_html($note_on_upload_page); ?></div>
    107104            <?php } ?>
    108105
    109             <?php if(trim($note_on_thanks_page) !== ''){ ?>
    110             <div class="wpsap-flow__aside-note"><?php echo esc_html($note_on_thanks_page); ?></div>
    111             <?php } ?>
    112 
    113             <p class="wpsap-flow__copyright">Copyright <?php echo esc_html(get_bloginfo('name')); ?></p>
    114         </aside>
    115 
    116         <div class="wpsap-flow__main">
    117 
    118             <section class="upload_ads_area" style="<?php echo $wpsap_step_done ? 'display:none' : 'display:block'; ?>">
    119 
    120                 <h1 class="wpsap-step__title">Upload your Ad</h1>
    121                 <p class="wpsap-step__lead">Attention: Make sure that you upload your desired advertising image (JPG, PNG, GIF) in the preferred resolution (up to max. 10 MB) and add the correct link to your offer.</p>
    122 
    123                 <?php if(trim($note_on_upload_page) !== ''){ ?>
    124                 <div class="wpsap-note"><?php echo esc_html($note_on_upload_page); ?></div>
    125                 <?php } ?>
    126 
    127                 <?php /* Nimmt die Meldungen auf, fuer die wp-sap.js bis 1.8.0
    128                          alert() aufrief. */ ?>
    129                 <div class="wpsap-alert" id="wpsap_form_error" role="alert"></div>
    130 
    131                 <form action="" id="wp_sap_form" method="post" enctype="multipart/form-data" class="wpsap-card">
    132                     <div class="file-area">
    133                         <input type="hidden" name="action" value="wpsap_upload_image">
    134                         <input type="hidden" name="security" id="wpsap_security" value="">
    135                         <?php /* Die Bestellung wird serverseitig ueber die Session-ID
    136                                  aufgeloest. Eine mitgeschickte order_id waere im Browser
    137                                  frei aenderbar. */ ?>
    138                         <input type="hidden" name="session" value="<?php echo esc_attr($session_id); ?>">
    139                         <input type="file" name="image" id="images" accept="image/*" required="required">
    140                         <div class="file-dummy">
    141                             <?php if($image_uploaded){ ?>
    142                             <div class="default">
    143                                 <img src="<?php echo esc_url($image_uploaded); ?>" alt="">
    144                                 <h6>Click on the image to change it</h6>
    145                             </div>
    146                             <?php } else { ?>
    147                             <div class="default">
    148                                 <span class="wpsap-drop__icon" aria-hidden="true">
    149                                     <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
    150                                 </span>
    151                                 <h6>Drop image here or click to upload</h6>
    152                             </div>
    153                             <?php } ?>
    154                             <div class="success" id="image_uploaded_area">
    155                                 <h5>Image Uploading....</h5>
    156                             </div>
     106            <?php /* Nimmt die Meldungen auf, fuer die wp-sap.js bis 1.8.0
     107                     alert() aufrief. */ ?>
     108            <div class="wpsap-alert" id="wpsap_form_error" role="alert"></div>
     109
     110            <form action="" id="wp_sap_form" method="post" enctype="multipart/form-data" class="wpsap-card">
     111                <div class="file-area">
     112                    <input type="hidden" name="action" value="wpsap_upload_image">
     113                    <input type="hidden" name="security" id="wpsap_security" value="">
     114                    <?php /* Die Bestellung wird serverseitig ueber die Session-ID
     115                             aufgeloest. Eine mitgeschickte order_id waere im Browser
     116                             frei aenderbar. */ ?>
     117                    <input type="hidden" name="session" value="<?php echo esc_attr($session_id); ?>">
     118                    <input type="file" name="image" id="images" accept="image/*" required="required">
     119                    <div class="file-dummy">
     120                        <?php if($image_uploaded){ ?>
     121                        <div class="default">
     122                            <img src="<?php echo esc_url($image_uploaded); ?>" alt="">
     123                            <h6>Click on the image to change it</h6>
     124                        </div>
     125                        <?php } else { ?>
     126                        <div class="default">
     127                            <span class="wpsap-drop__icon" aria-hidden="true">
     128                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
     129                            </span>
     130                            <h6>Drop image here or click to upload</h6>
     131                        </div>
     132                        <?php } ?>
     133                        <div class="success" id="image_uploaded_area">
     134                            <h5>Image Uploading....</h5>
    157135                        </div>
    158136                    </div>
    159 
    160                     <div class="wpsap-field">
    161                         <input type="submit" value="Upload image" id="hidden_upload_image_btn" style="display:none;">
    162                         <button type="button" id="upload_image_btn" class="wpsap-btn wpsap-btn--secondary">Upload image</button>
    163                     </div>
    164 
    165                     <div class="wpsap-field">
    166                         <label class="wpsap-field__label" for="site_url">Link to your offer</label>
    167                         <input type="text" id="site_url" name="site_url" value="<?php echo esc_url($link_added); ?>" placeholder="Paste your product link here...">
    168                     </div>
    169                 </form>
    170 
     137                </div>
     138
     139                <div class="wpsap-field">
     140                    <input type="submit" value="Upload image" id="hidden_upload_image_btn" style="display:none;">
     141                    <?php /* Der Upload-Button ist bewusst blau (SS3) und damit von
     142                             der schwarzen Primaeraktion "Next" unterscheidbar. */ ?>
     143                    <button type="button" id="upload_image_btn" class="wpsap-btn wpsap-btn--upload">Upload image</button>
     144                </div>
     145
     146                <div class="wpsap-field">
     147                    <label class="wpsap-field__label" for="site_url">Link to your offer</label>
     148                    <input type="text" id="site_url" name="site_url" value="<?php echo esc_url($link_added); ?>" placeholder="Paste your product link here...">
     149                </div>
     150            </form>
     151
     152            <?php if($terms || $cencel || $cencel_policy){ ?>
     153            <ul class="wpsap-links">
     154                <?php if($terms){ ?>
     155                <li><a href="<?php echo esc_url(get_the_permalink($terms)); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html(get_the_title($terms)); ?></a></li>
     156                <?php } ?>
    171157                <?php if($cencel){ ?>
    172                 <ul class="wpsap-links">
    173                     <li><a href="<?php echo esc_url(get_the_permalink($cencel)); ?>"><?php echo esc_html(get_the_title($cencel)); ?></a></li>
    174                 </ul>
     158                <li><a href="<?php echo esc_url(get_the_permalink($cencel)); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html(get_the_title($cencel)); ?></a></li>
    175159                <?php } ?>
    176 
    177                 <div class="wpsap-actions">
    178                     <input type="hidden" id="ads_image" value="<?php echo esc_url($image_uploaded); ?>">
    179                     <button type="button" class="btn next wpsap-btn wpsap-btn--primary">Next</button>
    180                 </div>
    181 
    182             </section>
    183 
    184             <section class="purchased_area" style="<?php echo $wpsap_step_done ? 'display:block' : 'display:none'; ?>">
    185 
    186                 <h1 class="wpsap-step__title">Your order has been placed</h1>
    187                 <?php /* Kein Erstattungsversprechen: wpsap_reject_ads() kuendigt
    188                          das Abo bei Stripe, mehr nicht. Bis 1.8.1 stand hier
    189                          "you will automatically get your money back" - das
    190                          loeste der Code nie ein. */ ?>
    191                 <p class="wpsap-step__lead">The advertisement has been successfully sent to the website owner. It may take up to 14 days for the ad to be reviewed and accepted. If your request is rejected or stays unanswered, your subscription is cancelled automatically and you will not be charged again. If your ad has been accepted, your advertising partnership will remain active until you or the website owner cancel it.</p>
    192 
    193                 <table>
    194                     <tbody>
    195                         <tr>
    196                             <th scope="row">Start date</th>
    197                             <td>After the website owner accept your ads.</td>
    198                         </tr>
    199                         <tr>
    200                             <th scope="row">End date</th>
    201                             <td>Until someone quits</td>
    202                         </tr>
    203                     </tbody>
    204                 </table>
    205 
    206                 <div class="link_input_desc_area">
    207                     <div class="input-group">
    208                         <?php /* Fuehrt in das Billing Portal von Stripe - dort kann der Kaeufer
    209                                  selbst kuendigen und seine Rechnungen einsehen. */ ?>
    210                         <input id="foo" type="text" class="link_copied_button" readonly value="<?php echo esc_url(add_query_arg(array('action' => 'wpsap_billing_portal','session' => $session_id),admin_url('admin-post.php'))); ?>">
    211                         <span class="input-group-button">
    212                             <button class="wpsap_ads_copy_btn" type="button" data-clipboard-target="#foo" title="Copy to clipboard" aria-label="Copy to clipboard">
    213                                 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
    214                             </button>
    215                         </span>
    216                     </div>
    217 
    218                     <div class="wpsap-copy-hint">
    219                         <span class="wpsap-copy-hint__icon" aria-hidden="true">
    220                             <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
    221                         </span>
    222                         <p>Save this link. With this link you can cancel your monthly advertising space rental in the future. If you want to cancel, you can use this link or simply ask the website owner directly.</p>
    223                     </div>
    224                 </div>
    225 
    226                 <?php if($cencel_policy || $terms){ ?>
    227                 <ul class="wpsap-links">
    228                     <?php if($cencel_policy){ ?>
    229                     <li><a href="<?php echo esc_url(get_the_permalink($cencel_policy)); ?>"><?php echo esc_html(get_the_title($cencel_policy)); ?></a></li>
    230                     <?php } ?>
    231                     <?php if($terms){ ?>
    232                     <li><a href="<?php echo esc_url(get_the_permalink($terms)); ?>"><?php echo esc_html(get_the_title($terms)); ?></a></li>
    233                     <?php } ?>
    234                 </ul>
     160                <?php if($cencel_policy){ ?>
     161                <li><a href="<?php echo esc_url(get_the_permalink($cencel_policy)); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html(get_the_title($cencel_policy)); ?></a></li>
    235162                <?php } ?>
    236 
    237                 <div class="wpsap-actions">
    238                     <button type="button" class="btn_back wpsap-btn wpsap-btn--quiet">Back</button>
    239                     <button type="button" class="btn_finish wpsap-btn wpsap-btn--primary" data-link="<?php echo esc_url(get_site_url()); ?>">Finish</button>
    240                 </div>
    241 
    242             </section>
     163            </ul>
     164            <?php } ?>
     165
     166            <div class="wpsap-actions">
     167                <input type="hidden" id="ads_image" value="<?php echo esc_url($image_uploaded); ?>">
     168                <button type="button" class="btn next wpsap-btn wpsap-btn--primary">Next</button>
     169            </div>
    243170
    244171        </div>
    245     </div>
     172    </section>
     173
     174    <?php /* Schritt 2: Bestaetigung - einspaltig, zentriert wie der
     175             Upload-Schritt. Die frueher linke Aside (Logo, Fakten, Copyright)
     176             wurde bewusst entfernt; nur .wpsap-flow__main traegt den Inhalt. */ ?>
     177    <section class="purchased_area" <?php if(!$wpsap_step_done){ echo 'style="display:none"'; } ?>>
     178
     179        <div class="wpsap-flow__main">
     180
     181            <span class="wpsap-badge" aria-hidden="true">
     182                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg>
     183            </span>
     184
     185            <h1 class="wpsap-step__title">Your order has been placed</h1>
     186            <?php /* Kein Erstattungsversprechen: wpsap_reject_ads() kuendigt
     187                     das Abo bei Stripe, mehr nicht. Bis 1.8.1 stand hier
     188                     "you will automatically get your money back" - das
     189                     loeste der Code nie ein. */ ?>
     190            <p class="wpsap-step__lead">The advertisement has been successfully sent to the website owner. It may take up to 14 days for the ad to be reviewed and approved. If your request is rejected or remains unanswered, your subscription will be cancelled automatically, and you will not be charged. If your ad is approved, your advertising partnership will remain active until you or the website owner cancels it.</p>
     191
     192            <?php /* Stand bis 1.8.3 in der linken Aside. Die Notiz ist optional
     193                     konfigurierbar und darf mit der entfernten Spalte nicht
     194                     verschwinden - sie sitzt jetzt hier im Hauptbereich. */ ?>
     195            <?php if(trim($note_on_thanks_page) !== ''){ ?>
     196            <div class="wpsap-note"><?php echo esc_html($note_on_thanks_page); ?></div>
     197            <?php } ?>
     198
     199            <table>
     200                <tbody>
     201                    <tr>
     202                        <th scope="row">Offer Submitted</th>
     203                        <td><?php echo esc_html($wpsap_submitted_date); ?></td>
     204                    </tr>
     205                    <tr>
     206                        <th scope="row">Expected start date</th>
     207                        <td>Waiting for website owner to review (Maximum 14 days)</td>
     208                    </tr>
     209                    <tr>
     210                        <th scope="row">End date</th>
     211                        <td>Until you or website owner cancels</td>
     212                    </tr>
     213                    <tr>
     214                        <th scope="row">Billing date</th>
     215                        <td><?php echo esc_html($wpsap_billing_date); ?></td>
     216                    </tr>
     217                </tbody>
     218            </table>
     219
     220            <div class="link_input_desc_area">
     221                <div class="input-group">
     222                    <?php /* Fuehrt in das Billing Portal von Stripe - dort kann der Kaeufer
     223                             selbst kuendigen und seine Rechnungen einsehen. */ ?>
     224                    <input id="foo" type="text" class="link_copied_button" readonly value="<?php echo esc_url(add_query_arg(array('action' => 'wpsap_billing_portal','session' => $session_id),admin_url('admin-post.php'))); ?>">
     225                    <span class="input-group-button">
     226                        <button class="wpsap_ads_copy_btn" type="button" data-clipboard-target="#foo" title="Copy to clipboard" aria-label="Copy to clipboard">
     227                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
     228                        </button>
     229                    </span>
     230                </div>
     231
     232                <div class="wpsap-copy-hint">
     233                    <span class="wpsap-copy-hint__icon" aria-hidden="true">
     234                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
     235                    </span>
     236                    <p>Please save this link. You can use it at any time to manage your subscription or cancel your monthly advertising space rental. If you wish to cancel, simply use this link or contact the website owner directly.</p>
     237                </div>
     238            </div>
     239
     240            <div class="wpsap-actions">
     241                <button type="button" class="btn_back wpsap-btn wpsap-btn--quiet">Back</button>
     242                <button type="button" class="btn_finish wpsap-btn wpsap-btn--primary" data-link="<?php echo esc_url(get_site_url()); ?>">Finish</button>
     243            </div>
     244
     245        </div>
     246    </section>
     247
    246248</div>
    247249
  • sell-ads/tags/1.9.6/inc/page/style.css

    r3626502 r3627752  
    107107
    108108/* ==========================================================================
    109    Zweispaltiger Rahmen
     109   Rahmen: Upload einspaltig (SS3), Bestaetigung zweispaltig (SS4)
    110110   ========================================================================== */
    111111
    … …  
    120120}
    121121
    122 .wpsap-flow__grid {
     122/* Die Bestaetigung ist einspaltig (die frueher linke Aside mit Logo, Fakten
     123   und Copyright ist entfernt). flex:1 1 auto fuellt die Resthoehe unter dem
     124   Flex-#wpsap-flow - das tat vorher .wpsap-flow__grid. */
     125.purchased_area {
    123126    flex: 1 1 auto;
    124     display: grid;
    125     grid-template-columns: minmax(240px, 320px) 1fr;
    126     align-items: stretch;
    127 }
    128 
    129 .wpsap-flow__aside {
    130     background-color: var(--wpsap-canvas-soft, #fafafa);
    131     border-right: 1px solid var(--wpsap-line, #dcdfe3);
    132     padding: 2.5rem 1.75rem;
    133     display: flex;
    134     flex-direction: column;
    135     gap: 1.5rem;
    136 }
    137 
     127}
     128
     129/* Bleibt fuer den Upload-Schritt (Marken-Text als Logo-Fallback). */
    138130#wpsap-flow .wpsap-flow__site {
    139131    font-size: 22px;
    … …  
    144136}
    145137
    146 .wpsap-flow__facts {
     138/* Bestaetigung: Inhalt zentriert mit fester Maximalbreite (SS4). Die
     139   Blanket-Regel gibt jedem direkten Kind dieselbe Breite, damit Badge, Titel,
     140   Tabelle und Aktionszeile buendig uebereinanderstehen. */
     141.wpsap-flow__main {
     142    padding: 3rem 1.75rem;
    147143    display: flex;
    148144    flex-direction: column;
    149     gap: .75rem;
    150 }
    151 
    152 .wpsap-flow__fact {
    153     display: flex;
    154     flex-direction: column;
    155     gap: .15rem;
    156 }
    157 
    158 #wpsap-flow .wpsap-flow__fact-label {
    159     font-size: 12px;
     145    align-items: center;
     146    text-align: center;
     147}
     148
     149.wpsap-flow__main > * {
     150    width: 100%;
     151    max-width: 520px;
     152}
     153
     154/* Schritt 1 ist einspaltig und ohne Aside. wp-sap.js schaltet die Sichtbarkeit
     155   per .hide()/.show(); der gezeigte Zustand kommt aus dem Stylesheet, das
     156   Markup setzt inline nur display:none fuer den verborgenen Schritt - so
     157   ueberschreibt keine Inline-Regel das Grid der Bestaetigung. */
     158.upload_ads_area {
     159    flex: 1 1 auto;
     160    padding: 3rem 1.25rem;
     161}
     162
     163.wpsap-single {
     164    width: 100%;
     165    max-width: 560px;
     166    margin: 0 auto;
     167}
     168
     169/* Nur der Kopfbereich des Upload-Schritts ist zentriert; Karte und Felder
     170   darunter bleiben linksbuendig. */
     171.wpsap-single > .wpsap-flow__brand,
     172.wpsap-single > .wpsap-step__title,
     173.wpsap-single > .wpsap-step__subtitle,
     174.wpsap-single > .wpsap-step__lead {
     175    text-align: center;
     176}
     177
     178.wpsap-single > .wpsap-flow__brand {
     179    margin-bottom: 1.5rem;
     180}
     181
     182.upload_ads_area .wpsap-links {
     183    justify-content: center;
     184}
     185
     186
     187/* ==========================================================================
     188   Logo, Untertitel und Erfolgs-Badge
     189   ========================================================================== */
     190
     191/* Logo (get_custom_logo) oben im Upload-Schritt bzw. in der Aside. In der
     192   Aside bleibt es linksbuendig wie die uebrigen Angaben. */
     193#wpsap-flow .wpsap-flow__brand img,
     194#wpsap-flow .custom-logo {
     195    display: inline-block;
     196    width: auto;
     197    height: auto;
     198    max-height: 52px;
     199}
     200
     201#wpsap-flow .wpsap-step__subtitle {
     202    font-size: 16px;
    160203    font-weight: 600;
    161     letter-spacing: .04em;
    162     text-transform: uppercase;
    163     color: var(--wpsap-muted, #6b7280);
    164 }
    165 
    166 #wpsap-flow .wpsap-flow__fact-value {
    167     font-size: 16px;
    168     font-weight: 500;
    169     color: var(--wpsap-ink, #161616);
    170     overflow-wrap: anywhere;
    171 }
    172 
    173 #wpsap-flow .wpsap-flow__aside-note {
    174     font-size: 14px;
    175     line-height: 1.55;
     204    line-height: 1.35;
    176205    color: var(--wpsap-ink-soft, #4a4a4a);
    177     white-space: pre-line;
    178 }
    179 
    180 /* Schiebt das Copyright ans untere Ende, ohne es dort festzunageln -
    181    bei viel Inhalt rutscht es einfach mit. */
    182 #wpsap-flow .wpsap-flow__copyright {
    183     margin-top: auto;
    184     padding-top: 1.5rem;
    185     font-size: 13px;
    186     color: var(--wpsap-muted, #6b7280);
    187 }
    188 
    189 .wpsap-flow__main {
    190     padding: 2.5rem 1.75rem 3rem;
     206    margin: 0 0 1rem 0;
     207}
     208
     209/* Siegel mit Haken ueber dem Titel der Bestaetigung (SS4). */
     210.wpsap-badge {
    191211    display: flex;
    192212    justify-content: center;
    193 }
    194 
    195 /* Die beiden Schritte behalten ihre Bestandsklassen: inc/page/wp-sap.js
    196    blendet sie ueber .hide()/.show() um und setzt dabei display:block. */
    197 .upload_ads_area,
    198 .purchased_area {
    199     width: 100%;
    200     max-width: 640px;
     213    color: var(--wpsap-ink, #161616);
     214    margin-bottom: 1rem;
     215}
     216
     217#wpsap-flow .wpsap-badge svg {
     218    display: block;
     219    width: 64px;
     220    height: 64px;
    201221}
    202222
    … …  
    331351}
    332352
     353/* Blauer Upload-Button (SS3) - hebt das Hochladen von der schwarzen
     354   Primaeraktion "Next" ab. Fuellt die Breite der Karte. */
     355#wpsap-flow .wpsap-btn--upload {
     356    width: 100%;
     357    background-color: #337ab6;
     358    border-color: #337ab6;
     359    color: #fff;
     360}
     361
     362#wpsap-flow .wpsap-btn--upload:hover {
     363    background-color: #2b6698;
     364    border-color: #2b6698;
     365}
     366
    333367#wpsap-flow .wpsap-btn[disabled] {
    334368    opacity: .6;
    … …  
    507541    text-align: left;
    508542    vertical-align: top;
     543    /* Beide Spalten identisch: schwarze Schrift auf weissem Grund. Explizit
     544       gesetzt, damit kein Theme-CSS auf der Bestaetigungsseite durchschlaegt. */
     545    color: #000;
     546    background-color: #fff;
    509547}
    510548
    511549#wpsap-flow .purchased_area th {
    512550    width: 40%;
    513     font-weight: 600;
    514     color: var(--wpsap-ink, #161616);
    515     background-color: var(--wpsap-canvas-soft, #fafafa);
    516551}
    517552
    … …  
    521556    border-radius: var(--wpsap-radius-sm, 5px);
    522557    padding: 1rem;
     558    /* Der Hauptbereich zentriert seinen Inhalt (SS4); Feld und Hinweis bleiben
     559       darin aber linksbuendig. */
     560    text-align: left;
    523561}
    524562
    … …  
    604642    }
    605643
    606     .wpsap-flow__grid {
    607         grid-template-columns: 1fr;
    608     }
    609 
    610     .wpsap-flow__aside {
    611         border-right: 0;
    612         border-bottom: 1px solid var(--wpsap-line, #dcdfe3);
    613         padding: 1.75rem 1.25rem;
    614         gap: 1.25rem;
    615     }
    616 
    617     /* Gestapelt gehoert das Copyright unter den Inhalt der Spalte,
    618        nicht ans Ende eines nicht mehr vorhandenen Streifens. */
    619     #wpsap-flow .wpsap-flow__copyright {
    620         margin-top: 0;
    621         padding-top: 0;
    622     }
    623 
    624644    .wpsap-flow__main {
    625645        padding: 1.75rem 1.25rem 2.5rem;
  • sell-ads/tags/1.9.6/inc/settings.php

    r3626502 r3627752  
    9494                        <?php $this->wpsap_notify_callback(); ?>
    9595
    96                         <details class="wpsap-more">
    97                                 <summary><?php esc_html_e( 'Pages and texts','wpsap_ads_plugin' ); ?></summary>
     96                        <h2 class="wpsap-step"><?php esc_html_e( 'Pages and texts','wpsap_ads_plugin' ); ?></h2>
    9897                                <table class="form-table" role="presentation">
    9998                                        <tr>
    100                                                 <th scope="row"><label for="terms_condtions_0"><?php esc_html_e( 'Terms of Service','wpsap_ads_plugin' ); ?></label></th>
     99                                                <th scope="row"><label for="terms_condtions_0"><?php esc_html_e( 'Terms of Service (Displayed on the upload page)','wpsap_ads_plugin' ); ?></label></th>
    101100                                                <td><?php $this->wpsap_terms_condtions_0_callback(); ?></td>
    102101                                        </tr>
    103102                                        <tr>
    104                                                 <th scope="row"><label for="template_cencel_subscription_1"><?php esc_html_e( 'Uploading Policy','wpsap_ads_plugin' ); ?></label></th>
     103                                                <th scope="row"><label for="template_cencel_subscription_1"><?php esc_html_e( 'Uploading Policy (Displayed on the upload page)','wpsap_ads_plugin' ); ?></label></th>
    105104                                                <td><?php $this->wpsap_template_cencel_subscription_1_callback(); ?></td>
    106105                                        </tr>
    107106                                        <tr>
    108                                                 <th scope="row"><label for="template_cencel_policy_1"><?php esc_html_e( 'Cancel Policy','wpsap_ads_plugin' ); ?></label></th>
     107                                                <th scope="row"><label for="template_cencel_policy_1"><?php esc_html_e( 'Cancel Policy (Displayed on the upload page)','wpsap_ads_plugin' ); ?></label></th>
    109108                                                <td><?php $this->wpsap_template_cencel_policy_1_callback(); ?></td>
    110109                                        </tr>
    111110                                        <tr>
    112                                                 <th scope="row"><label for="notification_2"><?php esc_html_e( 'Note for the buyer','wpsap_ads_plugin' ); ?></label></th>
     111                                                <th scope="row"><label for="notification_2"><?php esc_html_e( 'Note for the buyer (Shown on the Stripe payment page)','wpsap_ads_plugin' ); ?></label></th>
    113112                                                <td><?php $this->wpsap_notification_2_callback(); ?></td>
    114113                                        </tr>
    … …  
    122121                                        </tr>
    123122                                </table>
    124                         </details>
    125123
    126124                        <?php submit_button(); ?>
    … …  
    142140                        border: 1px solid #dcdcde;
    143141                        border-left: 4px solid #2271b1;
    144                 }
    145                 .wpsap-settings .wpsap-more {
    146                         margin-top: 32px;
    147                         padding-top: 8px;
    148                         border-top: 1px solid #dcdcde;
    149                 }
    150                 .wpsap-settings .wpsap-more > summary {
    151                         cursor: pointer;
    152                         font-size: 1.15em;
    153                         font-weight: 600;
    154                         padding: 8px 0;
    155142                }
    156143                </style>
    … …  
    241228                $status = wpsap_connect_account_status();
    242229                ?>
    243                 <p>Payments run through the Sell Ads platform at <code>sellads.cybie.de</code>.
     230                <p>Payments run through our SellAds platform.
    244231                Your advertisers pay into your own Stripe account. No API key and no webhook setup needed.</p>
    245232
    … …  
    330317                </p>
    331318                <p class="description">Stripe cannot change this later, so please pick the country your business is registered in.</p>
    332                 <p class="description">
    333                         <a href="https://sellads.cybie.de/terms.html" target="_blank" rel="noopener noreferrer">Terms of service</a>
    334                         &middot;
    335                         <a href="https://sellads.cybie.de/privacy.html" target="_blank" rel="noopener noreferrer">Privacy policy</a>
    336                 </p>
    337319                <?php
    338320        }
    … …  
    505487                <label class="wpsap-choice">
    506488                        <input type="radio" name="wpsap_test_mode" value="live" <?php checked( 'live', $mode ); ?>>
    507                         Live &mdash; buyers pay through Stripe
     489                        Live
    508490                </label>
    509491                <label class="wpsap-choice">
    510492                        <input type="radio" name="wpsap_test_mode" value="test" <?php checked( 'test', $mode ); ?>>
    511                         Test mode &mdash; walk through the plugin without setting up Stripe
     493                        Test mode
    512494                </label>
    513495                <p class="description">
    … …  
    519501                if ( wpsap_test_mode() ) {
    520502                        echo '<p class="notice notice-warning inline" style="padding:8px 12px;margin:8px 0 0;">'
    521                                 . '<strong>Test mode is on.</strong> Visitors can place orders without paying, and the ad space says so.'
     503                                . '<strong>Test mode is on.</strong> Visitors can place orders without paying.'
    522504                                . '</p>';
    523505                }
    … …  
    529511                <label class="wpsap-choice">
    530512                        <input type="radio" name="wpsap_payment_mode" value="connect" <?php checked( 'connect', $mode ); ?>>
    531                         Connect with Stripe &mdash; one click, nothing to set up
     513                        Connect with Stripe Express &mdash; Suggestion for Beginners
    532514                </label>
    533515                <label class="wpsap-choice">
    534516                        <input type="radio" name="wpsap_payment_mode" value="own_key" <?php checked( 'own_key', $mode ); ?>>
    535                         Use my own Stripe key &mdash; you add the key and the webhook yourself
     517                        Use my own Stripe key &mdash; Suggestion for Advanced Users
    536518                </label>
    537519                <?php
    … …  
    627609        public function wpsap_terms_condtions_0_callback() {
    628610                ?> <select name="wpsap_option_name[terms_condtions_0]" id="terms_condtions_0">
    629                         <?php foreach($this->wpsap_get_pages() as $page){ ?>
     611                        <option value="" <?php echo empty( $this->wpsap_options['terms_condtions_0'] ) ? 'selected' : ''; ?>><?php esc_html_e( '— None —', 'wpsap_ads_plugin' ); ?></option>
     612                        <?php foreach($this->wpsap_get_pages() as $page){ ?>
    630613                                <?php $selected = (isset( $this->wpsap_options['terms_condtions_0'] ) && $this->wpsap_options['terms_condtions_0'] == $page->ID) ? 'selected' : '' ; ?>
    631614                        <option value="<?php echo esc_attr($page->ID); ?>" <?php echo esc_attr($selected); ?>><?php echo esc_html(get_the_title($page->ID)); ?></option>
    … …  
    637620        public function wpsap_template_cencel_subscription_1_callback() {
    638621                ?> <select name="wpsap_option_name[template_cencel_subscription_1]" id="template_cencel_subscription_1">
    639                        
    640                         <?php foreach($this->wpsap_get_pages() as $page){ ?> 
     622                        <option value="" <?php echo empty( $this->wpsap_options['template_cencel_subscription_1'] ) ? 'selected' : ''; ?>><?php esc_html_e( '— None —', 'wpsap_ads_plugin' ); ?></option>
     623                        <?php foreach($this->wpsap_get_pages() as $page){ ?>
    641624                                <?php $selected = (isset( $this->wpsap_options['template_cencel_subscription_1'] ) && $this->wpsap_options['template_cencel_subscription_1'] == $page->ID) ? 'selected' : '' ; ?>
    642625                        <option value="<?php echo esc_attr($page->ID); ?>" <?php echo esc_attr($selected); ?>><?php echo esc_html(get_the_title($page->ID)); ?></option>
    … …  
    647630                public function wpsap_template_cencel_policy_1_callback() {
    648631                ?> <select name="wpsap_option_name[template_cencel_policy_1]" id="template_cencel_policy_1">
    649                        
    650                         <?php foreach($this->wpsap_get_pages() as $page){ ?> 
     632                        <option value="" <?php echo empty( $this->wpsap_options['template_cencel_policy_1'] ) ? 'selected' : ''; ?>><?php esc_html_e( '— None —', 'wpsap_ads_plugin' ); ?></option>
     633                        <?php foreach($this->wpsap_get_pages() as $page){ ?>
    651634                                <?php $selected = (isset( $this->wpsap_options['template_cencel_policy_1'] ) && $this->wpsap_options['template_cencel_policy_1'] == $page->ID) ? 'selected' : '' ; ?>
    652635                        <option value="<?php echo esc_attr($page->ID); ?>" <?php echo esc_attr($selected); ?>><?php echo esc_html(get_the_title($page->ID)); ?></option>
  • sell-ads/tags/1.9.6/inc/shortcode.php

    r3626502 r3627752  
    88    $price = get_post_meta($id,"wpsap_campaign_price",true);
    99    $button_text = get_post_meta($id, "wpsap_button_text",true);
    10     if(empty($title)){
    11         $title = __('This Ad Space is for Sale','wpsap_ads_plugin');
    12     }
     10    // Kein Default-Titel: der Platz zeigt einen Titel nur, wenn der Betreiber
     11    // ihn aktiv gesetzt hat (Ausgabe unten bedingt).
    1312    if(empty($button_text)){
    1413        $button_text = __('Buy','wpsap_ads_plugin');
    … …  
    2726    <div class="wpsap_ads_plugin_ads_box">
    2827        <div class="wpsap_ads_plugin_ads_box_container">
     28        <?php if((string) $title !== ''){ ?>
    2929        <div class="wpsap_ads_plugin_ads_box_title"><?php echo esc_html($title); ?></div>
     30        <?php } ?>
    3031        <form class="wpsap_ads_plugin_ads_box_form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
    3132            <input type="hidden" name="action" value="wpsap_start_checkout">
    … …  
    4849    }
    4950    else {
     51        // Klicks auf die veroeffentlichte Anzeige laufen ueber /goto/<slug>,
     52        // damit der Zaehler der Kampagne hochlaeuft (inc/hooks.php) - derselbe
     53        // Zaehler, den auch der Buy-Button-Klick erhoeht (inc/checkout.php).
     54        // Fallback auf die ID, falls der Slug (post_name) leer ist.
     55        $slug = get_post_field('post_name',$id);
     56        if(empty($slug)){
     57            $slug = $id;
     58        }
     59        // user_trailingslashit() richtet sich nach der Permalink-Struktur der
     60        // Seite. Ohne das haengt WordPress bei Trailing-Slash-Permalinks erst
     61        // einen 301 an, bevor der Redirect greift - ein unnoetiger Hop auf
     62        // jedem Anzeigenklick.
     63        $goto_url = home_url(user_trailingslashit('goto/'.$slug));
    5064        foreach($activer_orders as $ad){
    51             $url = get_post_meta($ad->ID, "order_url",true);
    52          ?>
     65         ?>
    5366    <div class="wpsap_ads_plugin_ads_box">
    5467        <div class="wpsap_ads_plugin_ads_box_container">
    55         <a href="<?php echo esc_attr($url); ?>" target="_blank" rel="noopener noreferrer nofollow"><img style="max-width:100%;" src="<?php echo esc_attr(get_post_meta($ad->ID, "order_image",true)); ?>"></a>
     68        <a href="<?php echo esc_url($goto_url); ?>" target="_blank" rel="noopener noreferrer nofollow"><img style="max-width:100%;" src="<?php echo esc_attr(get_post_meta($ad->ID, "order_image",true)); ?>"></a>
    5669        </div>
    5770    </div>
  • sell-ads/tags/1.9.6/inc/stripe.php

    r3626067 r3627752  
    157157        ),
    158158        'subscription_data'    => array(
     159            // Waehrend der Pruefungsfrist wird nicht belastet: das Abo startet
     160            // im Trial, die erste Rechnung entsteht erst, wenn der Betreiber die
     161            // Bestellung annimmt (wpsap_stripe_end_trial) - oder gar nicht, wenn
     162            // sie nach 14 Tagen ablaeuft und das Abo vorher gekuendigt wird.
     163            // 16 statt 14 Tage: der Ablauf-Cron laeuft nur taeglich, der Puffer
     164            // stellt sicher, dass die Kuendigung immer vor dem Trial-Ende greift.
     165            'trial_period_days' => 16,
    159166            'metadata' => array(
    160167                'wpsap_place_id' => (string) $campaign_id,
    … …  
    210217    }
    211218    return wpsap_stripe_request('DELETE','subscriptions/'.rawurlencode($subscription_id));
     219}
     220
     221/**
     222 * Beendet den Trial eines Abos sofort. Dadurch stellt Stripe die erste
     223 * Rechnung unmittelbar aus. Aufgerufen beim Annehmen einer Bestellung: erst
     224 * dann - nicht schon beim Checkout - soll der Kaeufer belastet werden.
     225 */
     226function wpsap_stripe_end_trial($subscription_id){
     227    $subscription_id = sanitize_text_field($subscription_id);
     228    if(empty($subscription_id)){
     229        return new WP_Error('wpsap_no_subscription',__('No subscription stored for this order.','wpsap_ads_plugin'));
     230    }
     231    if(wpsap_connect_is_active()){
     232        return wpsap_connect_end_trial($subscription_id);
     233    }
     234    return wpsap_stripe_request('POST','subscriptions/'.rawurlencode($subscription_id),array('trial_end' => 'now'));
    212235}
    213236
  • sell-ads/tags/1.9.6/readme.txt

    r3626505 r3627752  
    55Tested up to: 7.0
    66Requires PHP: 7.0
    7 Stable tag: 1.8.2
     7Stable tag: 1.9.6
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl.html
    … …  
    241241If you want to fill unsold inventory automatically, you want an ad network, not this. This plugin
    242242does nothing until a real advertiser buys a real space from you. If you need impression counting,
    243 click tracking, geo-targeting or A/B rotation, this plugin has none of it on purpose: an ad here is
    244 an image and a link, and that is the entire point. And if you would rather never speak to an
     243geo-targeting or A/B rotation, this plugin has none of it on purpose: an ad here is an image and a
     244link, and that is the entire point. It does count clicks — first-party, on your own server, with no
     245cookies and no third-party script — but that is as far as the measuring goes. And if you would rather never speak to an
    245246advertiser, be aware that even though the purchase itself is automatic, you are the one who decides
    246247what runs on your site.
    … …  
    402403
    403404== Changelog ==
     405
     406= 1.9.0 =
     407* New: the campaign list shows a "Clicks" column again, counting how often each ad space was clicked. Published ads now link through a first-party /goto/<slug> redirect on your own domain that counts the click and forwards the visitor to the advertiser — no cookies and no third-party script are involved, and clicks from logged-in editors are not counted.
     408* Fixed: the "PHP" and "Shortcode" columns showed their raw HTML instead of a usable field. They are copyable input fields again, each with a copy button.
     409
     410= 1.8.4 =
     411* The confirmation step is now a single centred column, matching the upload step. The left column that repeated your site name, the ad space and the monthly price has been removed; the confirmation badge, the summary table and the cancellation link stay.
     412
     413= 1.8.3 =
     414* The buyer flow was reworked to match the intended design. The ad space now shows as a rounded card instead of a square band.
     415* The upload step is a single centred column again, with your site logo on top, the ad space name below the heading, and a clearly separated blue upload button. Your terms and refund pages are linked right there.
     416* The confirmation step keeps its two columns and now opens with a confirmation badge and a four-row summary: when the request was submitted, the expected review window, that the partnership runs until either side cancels, and the expected first billing date.
     417* Uses your WordPress site logo (Appearance → Customize) on both pages when one is set, otherwise your site name.
    404418
    405419= 1.8.2 =
    … …  
    456470== Upgrade Notice ==
    457471
     472= 1.9.0 =
     473The campaign list has a Clicks column again and published ads are counted through a first-party /goto redirect on your own domain. The PHP and Shortcode columns are copyable fields again. No reconfiguration needed; the redirect rule is registered automatically on update.
     474
     475= 1.8.4 =
     476The confirmation page after upload is now a single centred column; the left sidebar with the site name and price has been removed. No reconfiguration needed.
     477
     478= 1.8.3 =
     479Visual update to the pages buyers see. The ad space is a rounded card, the upload step is a single centred column with your site logo, and the confirmation shows a badge and a clear date summary. No reconfiguration needed.
     480
    458481= 1.8.2 =
    459482Documentation only — the plugin code is identical to 1.8.1. The description now covers pricing, placement and finding your first advertiser.
  • sell-ads/tags/1.9.6/wp-sap.php

    r3626505 r3627752  
    55 * Plugin URI:        https://cybie.de
    66 * Description:       The easiest way to sell adspace on your WordPress Website
    7  * Version:           1.8.2
     7 * Version:           1.9.6
    88 * Requires at least: 6.0
    99 * Requires PHP:      7.0
    … …  
    1717
    1818
    19 define("WPSAP_Version",'1.8.2');
     19define("WPSAP_Version",'1.9.6');
    2020define('WPSAP_Plugin_Path',plugin_dir_path( __FILE__ ));
    2121define('WPSAP_Plugin_Dir',plugin_dir_url( __FILE__ ));
    … …  
    8181        wp_schedule_event(time() + HOUR_IN_SECONDS,'daily','wpsap_expire_pending_orders');
    8282    }
     83
     84    // Damit /goto/<slug> sofort nach der Aktivierung greift, muss die
     85    // Rewrite-Rule bekannt sein und einmal geflusht werden. Die laufende
     86    // Absicherung uebernimmt der Versions-Guard in wpsap_goto_rewrite().
     87    wpsap_goto_rewrite();
     88    flush_rewrite_rules();
    8389}
    8490
    … …  
    8692function wpsap_deactivate(){
    8793    wp_clear_scheduled_hook('wpsap_expire_pending_orders');
     94
     95    // Die /goto-Rewrite-Rule wird ohne aktives Plugin nicht mehr registriert -
     96    // der Flush raeumt sie aus den gespeicherten Regeln. Beim naechsten
     97    // Aktivieren sorgt der Versions-Guard fuer die Neuanlage.
     98    delete_option('wpsap_rewrite_version');
     99    flush_rewrite_rules();
    88100}
    89101
  • sell-ads/trunk/css/style.css

    r3626502 r3627752  
    2626}
    2727
     28/* Freistehende, abgerundete Karte auf hellem Grund (SS1) statt des frueheren
     29   kantigen Vollbreiten-Bandes. Der Radius stand bis 1.8.2 per !important auf 0
     30   und musste hier aktiv ueberschrieben werden - jetzt traegt die Box Ecken und
     31   einen weichen Schatten selbst. width:100% bleibt: die Karte fuellt die Breite
     32   ihres Widgets/Containers, hebt sich aber durch Radius und Schatten ab. */
    2833.wpsap_ads_plugin_ads_box {
    2934    width: 100%;
    3035    box-sizing: border-box;
    31     background-color: var(--wpsap-canvas, #f0f1f2);
    32     border-radius: 0 !important;
     36    background-color: #F2F3F5;
     37    /* border-radius: 14px; */
     38    /* box-shadow: var(--wpsap-shadow, 0 1px 3px rgba(0, 0, 0, .08)); */
    3339    text-align: center;
    3440    display: flex;
  • sell-ads/trunk/inc/ajax.php

    r3626067 r3627752  
    8484        update_post_meta($order_id,'order_image',$data['url']);
    8585        update_post_meta($order_id,'order_url',$site_url);
     86        // Attachment-ID mitfuehren, damit das Motiv beim Ablehnen/Kuendigen/
     87        // Ablaufen restlos aus der Mediathek entfernt werden kann
     88        // (wpsap_delete_order_media() in inc/helper.php).
     89        update_post_meta($order_id,'wpsap_order_image_id',$image);
    8690    }
    8791    else {
  • sell-ads/trunk/inc/campaign_fields.php

    r3626502 r3627752  
    1616 function wpsap_campaign_metaboxes_html(){
    1717    global $post;
     18    // Keine Prefill-Defaults: leere Felder bleiben leer, damit im Frontend nur
     19    // erscheint, was der Betreiber aktiv eingetragen hat.
    1820    $campaign_title = get_post_meta($post->ID, "wpsap_campaign_title",true);
    19     if(empty($campaign_title)){
    20         $campaign_title = 'This Advertising space is for sale';
    21     }
    2221
    2322    $campaign_name = get_the_title($post->ID);
    … …  
    2726
    2827    $button_text = get_post_meta($post->ID, "wpsap_button_text",true);
    29     if(empty($button_text)){
    30         $button_text = 'Buy';
    31     }
    3228
    3329    // Ohne dieses Feld schlug die Nonce-Pruefung beim Speichern immer fehl
    … …  
    5349  <span class="tooltiptext">This title is used to advertise your ad space.</span>
    5450</div>
    55             <input type="text" name="wpsap_campaign_title" class="regular-text" value="<?php echo esc_attr($campaign_title); ?>" required>
     51            <input type="text" name="wpsap_campaign_title" class="regular-text" value="<?php echo esc_attr($campaign_title); ?>">
    5652            </td>
    5753         </tr>
    … …  
    6864         </tr>
    6965
     66         <?php $currency = strtoupper(wpsap_stripe_currency()); ?>
    7067         <tr>
    71             <td><h4>Monthly Price ($)</h4></td>
     68            <td><h4>Monthly Price (<?php echo esc_html($currency); ?>)</h4></td>
    7269            <td>
    7370            <div class="tooltip"><span class="dashicons dashicons-info"></span>
    74   <span class="tooltiptext">This is the monthly price in US dollars for which you want to rent out your ad space.</span>
     71  <span class="tooltiptext">This is the monthly price in <?php echo esc_html($currency); ?> for which you want to rent out your ad space.</span>
    7572</div>
    7673            <input type="number" step="0.01" min="0" name="wpsap_campaign_price" class="regular-text" value="<?php echo esc_attr(get_post_meta($post->ID, "wpsap_campaign_price",true)); ?>" required>
    … …  
    194191    $columns['title'] = __( 'Campaign', "wpsap");
    195192    $columns['price'] = __( 'Price', "wpsap");
    196    
     193    // Info-Icon mit CSS-Tooltip: erklaert die zwei gezaehlten Ereignisse. Der
     194    // native title-Tooltip erschien nicht zuverlaessig, deshalb dieselbe
     195    // .tooltiptext-Mechanik wie in der Metabox. Das noetige CSS steht - auf die
     196    // Liste gescoped - im admin_footer (wpsap_campaign_copy_field()). WordPress
     197    // gibt das Spalten-Label als HTML aus, daher ohne eigenes Enqueue.
     198    // Icon und Text stehen in getrennten Spans: nur das Icon traegt die
     199    // dashicons-Klasse, sonst wuerde der Tooltip-Text in der Icon-Schrift
     200    // gerendert.
     201    $columns['clicks'] = __( 'Clicks', "wpsap")
     202        . ' <span class="wpsap-th-tip" tabindex="0">'
     203        . '<span class="dashicons dashicons-info"></span>'
     204        . '<span class="wpsap-th-tiptext">'
     205        . esc_html__('Total clicks for this ad slot: clicks on the “Buy” button (before the slot is sold) plus outbound clicks on the published ad.','wpsap_ads_plugin')
     206        . '</span></span>';
    197207    $columns['pdate'] = __( 'Date', "wpsap");
    198208    $columns['php'] = __( 'PHP', "wpsap");
    … …  
    211221            echo  esc_html(get_post_meta($post_id, "wpsap_campaign_price",true).' '.strtoupper(wpsap_stripe_currency())." per month");
    212222            break;
     223        case 'clicks' :
     224            // Zaehler wird an zwei Stellen hochgezaehlt: beim /goto-Redirect
     225            // (inc/hooks.php) und beim Buy-Button-Klick (wpsap_start_checkout,
     226            // inc/checkout.php).
     227            // Als Link auf die Kampagne ausgegeben, damit die Zahl - wie in der
     228            // Vorlage - klickbar ist und in WP-Blau erscheint.
     229            $clicks = (int) get_post_meta($post_id, "wpsap_click_count",true);
     230            $edit_link = get_edit_post_link($post_id);
     231            if($edit_link){
     232                echo '<a href="'.esc_url($edit_link).'">'.esc_html($clicks).'</a>';
     233            } else {
     234                echo esc_html($clicks);
     235            }
     236            break;
    213237        case 'php' :
    214             echo  esc_html(wpsap_plugin_php_function($post_id));
     238            // Bewusst ohne esc_html(): wpsap_plugin_php_function() liefert das
     239            // fertige Kopier-Feld und escapt seinen dynamischen Wert selbst.
     240            echo  wpsap_plugin_php_function($post_id);
    215241            break;
    216242        case 'shortcode' :
    217             echo  esc_html(wpsap_shortcode_input($post_id));
     243            echo  wpsap_shortcode_input($post_id);
    218244            break;
    219245
    … …  
    230256    $columns['order'] = __( 'Order', "wpsap");
    231257    $columns['campaign'] = __( 'Campaign', "wpsap");
    232     $columns['pdate'] = __( 'Date', "wpsap");
     258    $columns['pdate'] = __( 'Order Date', "wpsap");
    233259    $columns['status'] = __( 'Status', "wpsap");
    234     $columns['billing'] = __( 'Billing', "wpsap");
    235260    $columns['resources'] = __( 'Resources', "wpsap");
    236261    $columns['link'] = __( 'Link', "wpsap");
    … …  
    251276            // entstanden sind - die Spalte Total zeigt trotzdem einen Betrag.
    252277            if(get_post_meta($post_id,'wpsap_test_order',true) === 'yes'){
    253                 echo '<br/><small><span style="color:#996800;">'.esc_html__('Test order — not paid','wpsap_ads_plugin').'</span></small>';
     278                echo '<br/><small><span style="color:#996800;">'.esc_html__('Test order','wpsap_ads_plugin').'</span></small>';
    254279            }
    255280            break;
    … …  
    268293                $final_dates = wpsap_order_days_left($post_id);
    269294                if($final_dates > 0){
     295                    // Ablaufdatum = Bestelldatum + 14 Tage, dieselbe Frist wie
     296                    // in wpsap_order_days_left().
     297                    $placed = get_post_time('U',true,$post_id);
     298                    $expires = $placed ? $placed + 14 * DAY_IN_SECONDS : time() + $final_dates * DAY_IN_SECONDS;
    270299                    echo '<strong>Pending</strong>';
    271                      echo '<br/><small><span>('.esc_html($final_dates).' days until expiration)</span></small>';
     300                     echo '<br/><small><span>(Request expires on '.esc_html(wp_date(get_option('date_format'),$expires)).')</span></small>';
    272301                }
    273302                else {
    … …  
    291320                   echo "<span style='color:#ff0000;'>Rejected</span>";
    292321               }
     322               else if($current_status == 'expired'){
     323                   $on = (int) get_post_meta($post_id, "wpsap_expired_on",true);
     324                   if(!$on){
     325                       $placed = get_post_time('U',true,$post_id);
     326                       $on = $placed ? $placed + 14 * DAY_IN_SECONDS : 0;
     327                   }
     328                   echo "<span style='color:#ff0000;'>Expired</span>";
     329                   if($on){
     330                       echo "<br/><small>(on ".esc_html(wp_date(get_option('date_format'),$on)).")</small>";
     331                   }
     332               }
    293333               else {
    294334                    echo esc_html($current_status);
    … …  
    297337            }
    298338            break;
    299         case 'billing' :
    300             echo  esc_html(get_post_meta($post_id, "order_email",true));
    301              break;
    302339        case 'resources' :
    303             $url = get_post_meta($post_id, "order_url",true);
    304             echo  '<a target="_blank" href="'.esc_url(get_post_meta($post_id, "order_image",true)).'"><img style="max-width:120px;" src="'.esc_url(get_post_meta($post_id, "order_image",true)).'"></a>';
     340            // Nach Reject/Cancel/Expired ist das Motiv geloescht (order_image
     341            // leer) - dann kein kaputtes <img> ausgeben.
     342            $image = get_post_meta($post_id, "order_image",true);
     343            if($image){
     344                echo  '<a target="_blank" href="'.esc_url($image).'"><img style="max-width:120px;" src="'.esc_url($image).'"></a>';
     345            } else {
     346                echo '&mdash;';
     347            }
    305348            break;
    306349        case 'link' :
    307350            $url = get_post_meta($post_id, "order_url",true);
    308             echo  '<a href="'.esc_url($url).'">'.esc_url($url).'</a>';
     351            if($url){
     352                echo  '<a href="'.esc_url($url).'">'.esc_url($url).'</a>';
     353            } else {
     354                echo '&mdash;';
     355            }
    309356            break;
    310357        case 'total' :
    311358            $status = get_post_meta($post_id, "wpsap_order_status",true);
    312359            if($status == "accepted"){
    313                 echo "<small>You have confirmed the advertising partnership. Now you need to publish the advertisement. Make sure that the ad is displayed correctly and that the product link works. If there are any errors, you should contact the buyer immediately. If the buyer does not cancel within the next 14 days, the first transaction will be executed. The monthly payment will remain until someone cancels.</small>";
     360                echo "<small>You have confirmed the advertising partnership, and the first payment was charged when you accepted. Please publish the advertisement now: make sure it displays correctly and that the product link works, and contact the buyer right away if anything is wrong. The monthly payment continues until the subscription is cancelled &mdash; either by you here, or by the buyer from their own Stripe billing portal, which they can reach through the link on their order confirmation page.</small>";
    314361            }
    315362            else {
    … …  
    320367                    $currency = wpsap_stripe_currency();
    321368                }
    322                 echo esc_html(number_format_i18n($total,2).' '.strtoupper($currency));
     369                echo esc_html(number_format_i18n($total,2).' '.strtoupper($currency).' per month');
    323370            }
    324371            break;
    … …  
    334381                echo 'This subscription was cancelled by the buyer.';
    335382            }
     383            elseif($status == 'expired'){
     384                // Nach 14 Tagen ohne Annahme ueber wpsap_expire_ads() beendet -
     385                // das Abo stand noch im Trial, es wurde also nichts berechnet.
     386                echo 'This request expired unanswered. No payment was charged.';
     387            }
    336388            elseif($status != "accepted" && $status != "published"){
    337             echo '<a href="'.esc_url(wpsap_order_action_url('reject',$post_id)).'" class="button reject_ads">Reject</a> | <a href="'.esc_url(wpsap_order_action_url('accept',$post_id)).'" class="button accept_ads">Accept</a>';
     389            echo '<a href="'.esc_url(wpsap_order_action_url('accept',$post_id)).'" class="button button-primary accept_ads">Accept</a> | <a href="'.esc_url(wpsap_order_action_url('reject',$post_id)).'" class="button reject_ads">Reject</a>';
    338390            }
    339391
    … …  
    343395             }
    344396             else {
    345                echo '<a href="'.esc_url(wpsap_order_action_url('accept',$post_id)).'" class="button button-primary accept_ads">Unpublish</a> | <a href="'.esc_url(wpsap_order_action_url('reject',$post_id)).'" class="button reject_ads">Cancel</a>';
     397               echo '<a href="'.esc_url(wpsap_order_action_url('accept',$post_id)).'" class="button accept_ads">Unpublish</a> | <a href="'.esc_url(wpsap_order_action_url('reject',$post_id)).'" class="button reject_ads">Cancel</a>';
    346398             }
    347399            }
    … …  
    356408    if(isset($_GET['post_type'])){
    357409        if(sanitize_text_field($_GET['post_type']) == 'ads_order'){
    358     ?> 
     410    ?>
    359411    <style>
    360412.tablenav.top {
    … …  
    363415</style>
    364416
    365 <?php
    366         }
    367     }
     417<?php
     418        }
     419    }
     420}
     421
     422
     423// Kopier-Verhalten und Optik der PHP-/Shortcode-Felder in der Kampagnenliste.
     424add_action('admin_footer','wpsap_campaign_copy_field');
     425function wpsap_campaign_copy_field(){
     426    if(!isset($_GET['post_type']) || sanitize_text_field(wp_unslash($_GET['post_type'])) !== 'ads_campaign'){
     427        return;
     428    }
     429    ?>
     430<style>
     431.wpsap-copy { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; }
     432/* Weisses, schreibgeschuetztes Feld mit dezentem Rahmen - wie in der Vorlage
     433   (kein grauer Fuellton). Erhoehte Spezifitaet (span + input.klasse), weil WP
     434   readonly-Felder sonst grau einfaerbt (input[readonly]). */
     435.wpsap-copy input.wpsap-copy__input { width: 150px; max-width: 100%; background: #fff; color: #3c434a; }
     436/* Randloses Kopier-Icon statt umrandetem Button: die Vorlage zeigt nur das
     437   schlichte Dokument-Symbol ohne Button-Rahmen (die WP-.button-Klasse ist im
     438   Markup deshalb entfernt). */
     439.wpsap-copy__btn { display: inline-flex; align-items: center; justify-content: center; background: none; border: none; box-shadow: none; outline: none; padding: 0 2px; min-height: 0; color: #50575e; cursor: pointer; }
     440.wpsap-copy__btn:hover { color: #2271b1; background: none; box-shadow: none; }
     441.wpsap-copy__btn:focus { background: none; box-shadow: none; outline: none; }
     442.wpsap-copy__btn .dashicons { width: 18px; height: 18px; font-size: 18px; }
     443.wpsap-copy__btn.is-copied { color: #46b450; }
     444
     445/* Info-Tooltip im Spaltenkopf "Clicks". Aufbau wie die Metabox-Tooltips, aber
     446   eigene Klassen, damit nichts mit anderen Admin-Styles kollidiert. */
     447.wpsap-th-tip { position: relative; display: inline-flex; align-items: center; vertical-align: middle; margin-top: -2px; cursor: help; color: #787c82; }
     448.wpsap-th-tip .dashicons-info { font-size: 16px; width: 16px; height: 16px; line-height: 16px; }
     449.wpsap-th-tip .wpsap-th-tiptext {
     450    visibility: hidden; opacity: 0; transition: opacity .1s ease;
     451    position: absolute; z-index: 100; top: 150%; left: 50%; transform: translateX(-50%);
     452    width: 240px; padding: 8px 10px; border-radius: 4px;
     453    background: #1d2327; color: #fff;
     454    font: 400 12px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
     455    text-align: left; white-space: normal; font-weight: 400; text-transform: none;
     456}
     457.wpsap-th-tip:hover .wpsap-th-tiptext,
     458.wpsap-th-tip:focus .wpsap-th-tiptext,
     459.wpsap-th-tip:focus-within .wpsap-th-tiptext { visibility: visible; opacity: 1; }
     460</style>
     461<script>
     462jQuery(function($){
     463    $(document).on('click','.wpsap-copy__btn',function(){
     464        var $btn = $(this);
     465        var $input = $btn.closest('.wpsap-copy').find('.wpsap-copy__input');
     466        var text = $input.val();
     467
     468        var done = function(){
     469            $btn.addClass('is-copied');
     470            setTimeout(function(){ $btn.removeClass('is-copied'); },1200);
     471        };
     472
     473        if(navigator.clipboard && navigator.clipboard.writeText){
     474            navigator.clipboard.writeText(text).then(done, function(){
     475                $input.trigger('select'); document.execCommand('copy'); done();
     476            });
     477        } else {
     478            $input.trigger('select'); document.execCommand('copy'); done();
     479        }
     480    });
     481});
     482</script>
     483    <?php
     484}
     485
     486
     487// Doppelte Bestaetigung fuer Reject/Cancel: beide Buttons loesen dieselbe
     488// irreversible Aktion aus (Stripe-Abo kuendigen). Ein eigenes Modal faengt den
     489// Klick ab und verlangt, dass der Betreiber das Aktionswort tippt, bevor der
     490// bereits nonce-gesicherte Link geoeffnet wird. Reiner Frontend-Schutz - der
     491// Serverpfad in wpsap_job_status() bleibt unveraendert.
     492add_action('admin_footer','wpsap_order_reject_confirm');
     493function wpsap_order_reject_confirm(){
     494    if(!isset($_GET['post_type']) || sanitize_text_field(wp_unslash($_GET['post_type'])) !== 'ads_order'){
     495        return;
     496    }
     497    ?>
     498<style>
     499#wpsap-reject-modal { position: fixed; inset: 0; z-index: 100000; display: none; }
     500#wpsap-reject-modal.is-open { display: block; }
     501#wpsap-reject-modal .wpsap-reject-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
     502#wpsap-reject-modal .wpsap-reject-box { position: relative; max-width: 420px; margin: 12vh auto 0; background: #fff; border-radius: 4px; box-shadow: 0 6px 30px rgba(0,0,0,0.3); padding: 24px; }
     503#wpsap-reject-modal .wpsap-reject-title { display: flex; align-items: center; gap: 8px; margin: 0 0 12px; font-size: 18px; font-weight: 600; color: #b32d2e; }
     504#wpsap-reject-modal .wpsap-reject-text { margin: 0 0 16px; color: #3c434a; line-height: 1.5; }
     505#wpsap-reject-modal .wpsap-reject-label { display: block; margin: 0 0 6px; font-weight: 600; color: #3c434a; }
     506#wpsap-reject-modal .wpsap-reject-input { width: 100%; box-sizing: border-box; margin: 0 0 16px; }
     507#wpsap-reject-modal .wpsap-reject-actions { display: flex; justify-content: flex-end; gap: 8px; }
     508#wpsap-reject-modal .wpsap-reject-confirm { background: #b32d2e; border-color: #b32d2e; color: #fff; }
     509#wpsap-reject-modal .wpsap-reject-confirm:hover:not(:disabled) { background: #9e2423; border-color: #9e2423; color: #fff; }
     510#wpsap-reject-modal .wpsap-reject-confirm:disabled { opacity: 0.5; cursor: not-allowed; }
     511</style>
     512<div id="wpsap-reject-modal" role="dialog" aria-modal="true" aria-labelledby="wpsap-reject-title">
     513    <div class="wpsap-reject-backdrop"></div>
     514    <div class="wpsap-reject-box">
     515        <h2 id="wpsap-reject-title" class="wpsap-reject-title"><span class="dashicons dashicons-warning"></span><span class="wpsap-reject-heading">Reject this ad?</span></h2>
     516        <p class="wpsap-reject-text">This permanently cancels the buyer&#8217;s active Stripe subscription. This action cannot be undone.</p>
     517        <label class="wpsap-reject-label" for="wpsap-reject-word">Type <strong class="wpsap-reject-word">REJECT</strong> to confirm:</label>
     518        <input type="text" id="wpsap-reject-word" class="wpsap-reject-input regular-text" autocomplete="off" autocapitalize="characters" spellcheck="false">
     519        <div class="wpsap-reject-actions">
     520            <button type="button" class="button wpsap-reject-cancel">Close</button>
     521            <button type="button" class="button wpsap-reject-confirm" disabled>Confirm</button>
     522        </div>
     523    </div>
     524</div>
     525<script>
     526jQuery(function($){
     527    var $modal   = $('#wpsap-reject-modal');
     528    var $input   = $('#wpsap-reject-word');
     529    var $confirm = $modal.find('.wpsap-reject-confirm');
     530    var targetHref = '';
     531    var expectedWord = 'REJECT';
     532
     533    function closeModal(){
     534        $modal.removeClass('is-open');
     535        targetHref = '';
     536        $input.val('');
     537        $confirm.prop('disabled', true);
     538    }
     539
     540    // Reject und Cancel teilen die Klasse .reject_ads; der Wortlaut folgt dem
     541    // Text des tatsaechlich geklickten Links (Reject oder Cancel).
     542    $(document).on('click','.reject_ads',function(e){
     543        e.preventDefault();
     544        targetHref = $(this).attr('href');
     545        var label = $.trim($(this).text()) || 'Reject';
     546        expectedWord = label.toUpperCase();
     547        $modal.find('.wpsap-reject-heading').text(label + ' this ad?');
     548        $modal.find('.wpsap-reject-word').text(expectedWord);
     549        $input.val('');
     550        $confirm.prop('disabled', true);
     551        $modal.addClass('is-open');
     552        $input.trigger('focus');
     553    });
     554
     555    $input.on('input',function(){
     556        $confirm.prop('disabled', $.trim($input.val()).toUpperCase() !== expectedWord);
     557    });
     558
     559    $confirm.on('click',function(){
     560        if($confirm.prop('disabled') || !targetHref){ return; }
     561        window.location.href = targetHref;
     562    });
     563
     564    $modal.find('.wpsap-reject-cancel, .wpsap-reject-backdrop').on('click', closeModal);
     565
     566    $(document).on('keydown',function(e){
     567        if(e.key === 'Escape' && $modal.hasClass('is-open')){ closeModal(); }
     568    });
     569});
     570</script>
     571    <?php
    368572}
    369573
  • sell-ads/trunk/inc/checkout.php

    r3626502 r3627752  
    183183    }
    184184
     185    // Buy-Button-Klick zaehlen - derselbe Zaehler wie der /goto-Redirect
     186    // (inc/hooks.php), damit die Spalte "Clicks" beide Ereignisse summiert.
     187    // Selbst-Klicks eingeloggter Redakteure verfaelschen die Statistik nicht.
     188    if(get_post_type($campaign_id) === 'ads_campaign'
     189        && !(is_user_logged_in() && current_user_can('edit_posts'))){
     190        $count = (int) get_post_meta($campaign_id,'wpsap_click_count',true);
     191        update_post_meta($campaign_id,'wpsap_click_count',$count + 1);
     192    }
     193
    185194    // Testmodus: die Zahlungsseite entfaellt ersatzlos. Die Bestellung
    186195    // entsteht hier statt aus einer bezahlten Stripe-Session, danach geht es
    … …  
    326335
    327336/**
    328  * Taeglicher Lauf: Bestellungen, die 14 Tage unbeantwortet blieben,
    329  * werden abgelehnt und das Abo beendet. Frueher passierte das mitten
    330  * im Rendern der Bestellliste.
     337 * Taeglicher Lauf: Bestellungen, die 14 Tage unbeantwortet blieben, laufen ab
     338 * (Status "expired") und das Abo wird beendet - weil es noch im Trial steht,
     339 * ohne Belastung. Frueher passierte das mitten im Rendern der Bestellliste.
    331340 */
    332341add_action('wpsap_expire_pending_orders','wpsap_expire_pending_orders');
    … …  
    348357    foreach($orders as $order_id){
    349358        if(wpsap_order_days_left($order_id) <= 0){
    350             wpsap_reject_ads($order_id);
     359            wpsap_expire_ads($order_id);
    351360        }
    352361    }
  • sell-ads/trunk/inc/connect.php

    r3626502 r3627752  
    2020
    2121function wpsap_payment_mode(){
    22     $mode = get_option('wpsap_payment_mode','own_key');
    23     return in_array($mode,array('own_key','connect'),true) ? $mode : 'own_key';
     22    $mode = get_option('wpsap_payment_mode','connect');
     23    return in_array($mode,array('own_key','connect'),true) ? $mode : 'connect';
    2424}
    2525
    … …  
    326326}
    327327
     328function wpsap_connect_end_trial($subscription_id){
     329    return wpsap_connect_request('POST','v1/subscription/end-trial',array('subscription_id' => $subscription_id));
     330}
     331
    328332function wpsap_connect_portal_url($customer_id,$return_url){
    329333    $session = wpsap_connect_request('POST','v1/portal',array(
  • sell-ads/trunk/inc/helper.php

    r3626067 r3627752  
    22if ( ! defined( 'ABSPATH' ) ) exit;
    33
     4/**
     5 * Baut das kopierbare Feld fuer die PHP-Einbindung einer Kampagne.
     6 *
     7 * Rueckgabe ist fertiges HTML und wird in der Spalte direkt ausgegeben -
     8 * frueher lag ein esc_html() darum, das aus dem <input> sichtbaren Rohtext
     9 * machte. Escapt wird deshalb nur noch der dynamische Wert selbst.
     10 */
    411function wpsap_plugin_php_function($content){
    5 
    6     $html = '<input type="text" readonly value="<?php echo wpsap_show('.esc_attr($content).'); ?>">';
    7     return $html;
    8 
     12    $id    = absint($content);
     13    $value = '<?php echo wpsap_show('.$id.'); ?>';
     14    return wpsap_copy_field($value);
    915}
    1016
     17/**
     18 * Baut das kopierbare Feld fuer die Shortcode-Einbindung einer Kampagne.
     19 */
    1120function wpsap_shortcode_input($content){
    12     $html = '<input type="text" readonly value="[wpsap_ads id='.esc_attr($content).']">';
    13     return $html;
     21    $id    = absint($content);
     22    $value = '[wpsap_ads id='.$id.']';
     23    return wpsap_copy_field($value);
     24}
     25
     26/**
     27 * Gemeinsames Markup fuer die Kopier-Felder der Kampagnenliste: ein
     28 * schreibgeschuetztes Eingabefeld mit Betrag daneben ein Kopier-Button.
     29 * Der zugehoerige Klick-Handler liegt im admin_footer der Liste
     30 * (inc/campaign_fields.php).
     31 */
     32function wpsap_copy_field($value){
     33    return '<span class="wpsap-copy">'
     34        .'<input type="text" class="wpsap-copy__input" readonly value="'.esc_attr($value).'">'
     35        .'<button type="button" class="wpsap-copy__btn" title="'.esc_attr__('Copy','wpsap_ads_plugin').'">'
     36        .'<span class="dashicons dashicons-admin-page"></span>'
     37        .'</button>'
     38        .'</span>';
    1439}
    1540
    … …  
    4368
    4469/**
     70 * Entfernt Motiv und Ziellink einer endgueltig toten Bestellung restlos:
     71 * das Attachment aus der Mediathek (Datei inkl. resized Varianten, ohne
     72 * Papierkorb) sowie die Meta order_image/order_url. Jedes Motiv gehoert zu
     73 * genau einer Bestellung, deshalb ist das harte Loeschen unbedenklich.
     74 *
     75 * Die Attachment-ID steht seit dem Upload in wpsap_order_image_id; fuer
     76 * Bestandsbestellungen ohne dieses Meta wird sie aus der gespeicherten URL
     77 * aufgeloest.
     78 */
     79function wpsap_delete_order_media($order_id){
     80    $attachment_id = (int) get_post_meta($order_id,'wpsap_order_image_id',true);
     81    if(!$attachment_id){
     82        $image_url = get_post_meta($order_id,'order_image',true);
     83        if($image_url){
     84            $attachment_id = attachment_url_to_postid($image_url);
     85        }
     86    }
     87
     88    if($attachment_id && get_post_type($attachment_id) === 'attachment'){
     89        wp_delete_attachment($attachment_id,true);
     90    }
     91
     92    delete_post_meta($order_id,'order_image');
     93    delete_post_meta($order_id,'order_url');
     94    delete_post_meta($order_id,'wpsap_order_image_id');
     95}
     96
     97/**
    4598 * Ablehnen heisst: das Abo bei Stripe beenden. Der lokale Status wird erst
    4699 * danach gesetzt - frueher lief es umgekehrt, wodurch Plugin und Stripe
    … …  
    62115    ) );
    63116
     117    // Erst nach dem gesicherten Statuswechsel: Motiv und Link sind jetzt tot.
     118    wpsap_delete_order_media($order_id);
     119
     120    return true;
     121}
     122
     123/**
     124 * Ablauf einer unbeantworteten Bestellung nach 14 Tagen. Wie das Ablehnen
     125 * beendet es das Abo bei Stripe - weil das noch im Trial steht, entsteht dabei
     126 * keine Rechnung, der Kaeufer wird also nicht belastet. Getrennt von
     127 * wpsap_reject_ads(), damit der Zustand in der Liste als "Expired" (statt
     128 * "Rejected") erkennbar bleibt und das Ablaufdatum festgehalten wird.
     129 */
     130function wpsap_expire_ads($order_id){
     131    $subscription_id = get_post_meta($order_id,'wpsap_subscription_id',true);
     132    if($subscription_id){
     133        $result = wpsap_stripe_cancel_subscription($subscription_id);
     134        if(is_wp_error($result)){
     135            return $result;
     136        }
     137    }
     138
     139    // Ablaufdatum = Bestelldatum + 14 Tage, dieselbe Frist wie in
     140    // wpsap_order_days_left(). Festgehalten, damit die Liste "Expired on <Tag>"
     141    // zeigen kann, ohne es jedes Mal neu herzuleiten.
     142    $placed  = get_post_time('U',true,$order_id);
     143    $expires = $placed ? $placed + 14 * DAY_IN_SECONDS : time();
     144    update_post_meta($order_id,'wpsap_expired_on',$expires);
     145
     146    update_post_meta($order_id,"wpsap_order_status",'expired');
     147    wp_update_post( array(
     148        'ID'          => $order_id,
     149        'post_status' => 'rejected',
     150    ) );
     151
     152    // Wie beim Ablehnen: Motiv und Link sind mit dem Ablauf tot.
     153    wpsap_delete_order_media($order_id);
     154
    64155    return true;
    65156}
    … …  
    80171            return new WP_Error('wpsap_subscription_gone',__('This subscription is no longer active at Stripe.','wpsap_ads_plugin'));
    81172        }
     173
     174        // Erst mit der Annahme wird belastet: das Abo lief bis hierher im Trial
     175        // (siehe wpsap_stripe_build_checkout). Das Beenden des Trials stellt die
     176        // erste Rechnung sofort aus.
     177        $charged = wpsap_stripe_end_trial($subscription_id);
     178        if(is_wp_error($charged)){
     179            return $charged;
     180        }
    82181    }
    83182
  • sell-ads/trunk/inc/hooks.php

    r3626067 r3627752  
    144144        }
    145145
    146         $order_date = get_the_date( 'l F j, Y',$order_id);
    147         $expected_date = date('l F j, Y',strtotime($order_date."+ 7days"));
    148 
     146        // Die Datumsangaben der Bestaetigungstabelle berechnet confirm.php
     147        // selbst aus dem Bestellzeitpunkt - hier ist nichts vorzubereiten.
    149148        require WPSAP_Plugin_Path.'inc/page/confirm.php';
    150149        die();
    … …  
    229228// neu registriert - die alte Registrierung ueberschrieb ihn global fuer
    230229// alle Post-Types.
     230
     231
     232/* --------------------------------------------------------------------------
     233 * Klick-Tracking ueber /goto/<slug>
     234 *
     235 * Die veroeffentlichte Anzeige verlinkt nicht mehr direkt auf die Zielseite,
     236 * sondern ueber diesen Endpoint (inc/shortcode.php). Er zaehlt den Klick auf
     237 * der Kampagne (wpsap_click_count) und leitet dann zur Zielseite weiter. Der
     238 * Zaehler erscheint in der Spalte "Clicks" der Kampagnenliste.
     239 * ------------------------------------------------------------------------- */
     240
     241add_filter('query_vars','wpsap_goto_query_var');
     242function wpsap_goto_query_var($vars){
     243    $vars[] = 'wpsap_goto';
     244    return $vars;
     245}
     246
     247add_action('init','wpsap_goto_rewrite');
     248function wpsap_goto_rewrite(){
     249    add_rewrite_rule('^goto/([^/]+)/?$','index.php?wpsap_goto=$matches[1]','top');
     250
     251    // Beim reinen Plugin-Update laeuft der Aktivierungshook nicht - ohne
     252    // diesen Guard griffe die Rewrite-Rule auf cybie.de erst nach manuellem
     253    // Neu-Speichern der Permalinks. Der Flush erfolgt genau einmal je Version.
     254    if(get_option('wpsap_rewrite_version') !== WPSAP_Version){
     255        flush_rewrite_rules(false);
     256        update_option('wpsap_rewrite_version',WPSAP_Version);
     257    }
     258}
     259
     260add_action('template_redirect','wpsap_handle_goto');
     261function wpsap_handle_goto(){
     262    $slug = get_query_var('wpsap_goto');
     263    if($slug === '' || $slug === null){
     264        return;
     265    }
     266
     267    $home = home_url('/');
     268
     269    // Slug -> Kampagne. Numerischer Wert wird als ID behandelt, sonst ueber
     270    // den post_name aufgeloest. Der CPT ist nicht oeffentlich, get_page_by_path
     271    // findet ihn trotzdem, weil der Post-Type explizit uebergeben wird.
     272    $campaign = null;
     273    if(ctype_digit((string) $slug)){
     274        $maybe = get_post(absint($slug));
     275        if($maybe && $maybe->post_type === 'ads_campaign'){
     276            $campaign = $maybe;
     277        }
     278    }
     279    if(!$campaign){
     280        $campaign = get_page_by_path(sanitize_title($slug),OBJECT,'ads_campaign');
     281    }
     282
     283    if(!$campaign){
     284        nocache_headers();
     285        wp_redirect($home,302);
     286        exit;
     287    }
     288
     289    // Zielseite = URL der aktuell veroeffentlichten Anzeige dieses Slots.
     290    $target = '';
     291    foreach(wpsap_get_active_ads($campaign->ID) as $ad){
     292        $url = get_post_meta($ad->ID,'order_url',true);
     293        if(!empty($url)){
     294            $target = $url;
     295            break;
     296        }
     297    }
     298
     299    if(empty($target)){
     300        nocache_headers();
     301        wp_redirect($home,302);
     302        exit;
     303    }
     304
     305    // Selbst-Klicks von Redakteuren verfaelschen die Statistik nicht.
     306    if(!(is_user_logged_in() && current_user_can('edit_posts'))){
     307        $count = (int) get_post_meta($campaign->ID,'wpsap_click_count',true);
     308        update_post_meta($campaign->ID,'wpsap_click_count',$count + 1);
     309    }
     310
     311    nocache_headers();
     312    // Ziel ist eine fremde Domain - wp_safe_redirect() wuerde das blocken.
     313    wp_redirect(esc_url_raw($target),302);
     314    exit;
     315}
  • sell-ads/trunk/inc/page/confirm.php

    r3626502 r3627752  
    4343// Fuer die Seitenspalte: beide Zahlungswege legen diese Felder an -
    4444// wpsap_sync_order_from_session() ebenso wie wpsap_create_test_order().
    45 $wpsap_place_id = (int) get_post_meta($order_id,'wpsap_place_id',true);
    46 $wpsap_campaign_label = '';
    47 if($wpsap_place_id){
    48     $wpsap_campaign_label = (string) get_post_meta($wpsap_place_id,'wpsap_campaign_title',true);
    49     if($wpsap_campaign_label === ''){
    50         $wpsap_campaign_label = (string) get_the_title($wpsap_place_id);
    51     }
    52 }
    53 
    5445$wpsap_price_label = '';
    5546$wpsap_order_total = (int) get_post_meta($order_id,'order_total',true);
    … …  
    5849    $wpsap_price_label = number_format_i18n($wpsap_order_total / 100,2).' '.strtoupper((string) get_post_meta($order_id,'wpsap_currency',true));
    5950}
     51
     52// Datumsangaben der Bestaetigungstabelle. Bezugspunkt ist der Zeitpunkt der
     53// Bestellung; wp_date() wendet die WP-Zeitzone an. Der Kaeufer-Flow ist
     54// durchgaengig englisch, deshalb erzwingt switch_to_locale('en_US') englische
     55// Monatsnamen auch auf einer de_DE-Site. Das Billing-Datum ist eine
     56// Anzeige-Schaetzung fuer den Kaeufer (Prueffrist bis 14 Tage + ein Abo-Monat) -
     57// Stripe bucht im Abo technisch bereits beim Checkout.
     58$wpsap_order_ts = get_post_timestamp($order_id);
     59$wpsap_switched_locale = switch_to_locale('en_US');
     60$wpsap_submitted_date = $wpsap_order_ts ? wp_date('F j, Y',$wpsap_order_ts) : '';
     61$wpsap_billing_date = $wpsap_order_ts ? wp_date('F j, Y',strtotime('+14 days +1 month',$wpsap_order_ts)) : '';
     62if($wpsap_switched_locale){ restore_previous_locale(); }
    6063
    6164// Solange Bild oder Link fehlen, steht der Kaeufer noch im Upload-Schritt.
    … …  
    8588<?php } ?>
    8689
    87     <div class="wpsap-flow__grid">
    88 
    89         <aside class="wpsap-flow__aside">
    90             <div class="wpsap-flow__site"><?php echo esc_html(get_bloginfo('name')); ?></div>
    91 
    92             <?php if($wpsap_campaign_label !== '' || $wpsap_price_label !== ''){ ?>
    93             <div class="wpsap-flow__facts">
    94                 <?php if($wpsap_campaign_label !== ''){ ?>
    95                 <div class="wpsap-flow__fact">
    96                     <span class="wpsap-flow__fact-label"><?php esc_html_e('Ad space','wpsap_ads_plugin'); ?></span>
    97                     <span class="wpsap-flow__fact-value"><?php echo esc_html($wpsap_campaign_label); ?></span>
    98                 </div>
    99                 <?php } ?>
    100                 <?php if($wpsap_price_label !== ''){ ?>
    101                 <div class="wpsap-flow__fact">
    102                     <span class="wpsap-flow__fact-label"><?php esc_html_e('Per month','wpsap_ads_plugin'); ?></span>
    103                     <span class="wpsap-flow__fact-value"><?php echo esc_html($wpsap_price_label); ?></span>
    104                 </div>
    105                 <?php } ?>
    106             </div>
     90    <?php /* Schritt 1: Upload - einspaltig zentriert. Bewusst NICHT im
     91             Zwei-Spalten-Grid: die Aside gehoert erst auf die Bestaetigung.
     92             Sichtbar bzw. verborgen nur ueber display:none - der gezeigte
     93             Zustand kommt aus dem Stylesheet, damit wp-sap.js ihn per
     94             .hide()/.show() korrekt umschalten kann. */ ?>
     95    <section class="upload_ads_area" <?php if($wpsap_step_done){ echo 'style="display:none"'; } ?>>
     96        <div class="wpsap-single">
     97
     98            <h1 class="wpsap-step__title">Upload your Ad</h1>
     99
     100            <p class="wpsap-step__lead">Attention: Make sure that you upload your desired advertising image (JPG, PNG, GIF) in the preferred resolution (up to max. 10 MB) and add the correct link to your offer.</p>
     101
     102            <?php if(trim($note_on_upload_page) !== ''){ ?>
     103            <div class="wpsap-note"><?php echo esc_html($note_on_upload_page); ?></div>
    107104            <?php } ?>
    108105
    109             <?php if(trim($note_on_thanks_page) !== ''){ ?>
    110             <div class="wpsap-flow__aside-note"><?php echo esc_html($note_on_thanks_page); ?></div>
    111             <?php } ?>
    112 
    113             <p class="wpsap-flow__copyright">Copyright <?php echo esc_html(get_bloginfo('name')); ?></p>
    114         </aside>
    115 
    116         <div class="wpsap-flow__main">
    117 
    118             <section class="upload_ads_area" style="<?php echo $wpsap_step_done ? 'display:none' : 'display:block'; ?>">
    119 
    120                 <h1 class="wpsap-step__title">Upload your Ad</h1>
    121                 <p class="wpsap-step__lead">Attention: Make sure that you upload your desired advertising image (JPG, PNG, GIF) in the preferred resolution (up to max. 10 MB) and add the correct link to your offer.</p>
    122 
    123                 <?php if(trim($note_on_upload_page) !== ''){ ?>
    124                 <div class="wpsap-note"><?php echo esc_html($note_on_upload_page); ?></div>
    125                 <?php } ?>
    126 
    127                 <?php /* Nimmt die Meldungen auf, fuer die wp-sap.js bis 1.8.0
    128                          alert() aufrief. */ ?>
    129                 <div class="wpsap-alert" id="wpsap_form_error" role="alert"></div>
    130 
    131                 <form action="" id="wp_sap_form" method="post" enctype="multipart/form-data" class="wpsap-card">
    132                     <div class="file-area">
    133                         <input type="hidden" name="action" value="wpsap_upload_image">
    134                         <input type="hidden" name="security" id="wpsap_security" value="">
    135                         <?php /* Die Bestellung wird serverseitig ueber die Session-ID
    136                                  aufgeloest. Eine mitgeschickte order_id waere im Browser
    137                                  frei aenderbar. */ ?>
    138                         <input type="hidden" name="session" value="<?php echo esc_attr($session_id); ?>">
    139                         <input type="file" name="image" id="images" accept="image/*" required="required">
    140                         <div class="file-dummy">
    141                             <?php if($image_uploaded){ ?>
    142                             <div class="default">
    143                                 <img src="<?php echo esc_url($image_uploaded); ?>" alt="">
    144                                 <h6>Click on the image to change it</h6>
    145                             </div>
    146                             <?php } else { ?>
    147                             <div class="default">
    148                                 <span class="wpsap-drop__icon" aria-hidden="true">
    149                                     <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
    150                                 </span>
    151                                 <h6>Drop image here or click to upload</h6>
    152                             </div>
    153                             <?php } ?>
    154                             <div class="success" id="image_uploaded_area">
    155                                 <h5>Image Uploading....</h5>
    156                             </div>
     106            <?php /* Nimmt die Meldungen auf, fuer die wp-sap.js bis 1.8.0
     107                     alert() aufrief. */ ?>
     108            <div class="wpsap-alert" id="wpsap_form_error" role="alert"></div>
     109
     110            <form action="" id="wp_sap_form" method="post" enctype="multipart/form-data" class="wpsap-card">
     111                <div class="file-area">
     112                    <input type="hidden" name="action" value="wpsap_upload_image">
     113                    <input type="hidden" name="security" id="wpsap_security" value="">
     114                    <?php /* Die Bestellung wird serverseitig ueber die Session-ID
     115                             aufgeloest. Eine mitgeschickte order_id waere im Browser
     116                             frei aenderbar. */ ?>
     117                    <input type="hidden" name="session" value="<?php echo esc_attr($session_id); ?>">
     118                    <input type="file" name="image" id="images" accept="image/*" required="required">
     119                    <div class="file-dummy">
     120                        <?php if($image_uploaded){ ?>
     121                        <div class="default">
     122                            <img src="<?php echo esc_url($image_uploaded); ?>" alt="">
     123                            <h6>Click on the image to change it</h6>
     124                        </div>
     125                        <?php } else { ?>
     126                        <div class="default">
     127                            <span class="wpsap-drop__icon" aria-hidden="true">
     128                                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
     129                            </span>
     130                            <h6>Drop image here or click to upload</h6>
     131                        </div>
     132                        <?php } ?>
     133                        <div class="success" id="image_uploaded_area">
     134                            <h5>Image Uploading....</h5>
    157135                        </div>
    158136                    </div>
    159 
    160                     <div class="wpsap-field">
    161                         <input type="submit" value="Upload image" id="hidden_upload_image_btn" style="display:none;">
    162                         <button type="button" id="upload_image_btn" class="wpsap-btn wpsap-btn--secondary">Upload image</button>
    163                     </div>
    164 
    165                     <div class="wpsap-field">
    166                         <label class="wpsap-field__label" for="site_url">Link to your offer</label>
    167                         <input type="text" id="site_url" name="site_url" value="<?php echo esc_url($link_added); ?>" placeholder="Paste your product link here...">
    168                     </div>
    169                 </form>
    170 
     137                </div>
     138
     139                <div class="wpsap-field">
     140                    <input type="submit" value="Upload image" id="hidden_upload_image_btn" style="display:none;">
     141                    <?php /* Der Upload-Button ist bewusst blau (SS3) und damit von
     142                             der schwarzen Primaeraktion "Next" unterscheidbar. */ ?>
     143                    <button type="button" id="upload_image_btn" class="wpsap-btn wpsap-btn--upload">Upload image</button>
     144                </div>
     145
     146                <div class="wpsap-field">
     147                    <label class="wpsap-field__label" for="site_url">Link to your offer</label>
     148                    <input type="text" id="site_url" name="site_url" value="<?php echo esc_url($link_added); ?>" placeholder="Paste your product link here...">
     149                </div>
     150            </form>
     151
     152            <?php if($terms || $cencel || $cencel_policy){ ?>
     153            <ul class="wpsap-links">
     154                <?php if($terms){ ?>
     155                <li><a href="<?php echo esc_url(get_the_permalink($terms)); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html(get_the_title($terms)); ?></a></li>
     156                <?php } ?>
    171157                <?php if($cencel){ ?>
    172                 <ul class="wpsap-links">
    173                     <li><a href="<?php echo esc_url(get_the_permalink($cencel)); ?>"><?php echo esc_html(get_the_title($cencel)); ?></a></li>
    174                 </ul>
     158                <li><a href="<?php echo esc_url(get_the_permalink($cencel)); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html(get_the_title($cencel)); ?></a></li>
    175159                <?php } ?>
    176 
    177                 <div class="wpsap-actions">
    178                     <input type="hidden" id="ads_image" value="<?php echo esc_url($image_uploaded); ?>">
    179                     <button type="button" class="btn next wpsap-btn wpsap-btn--primary">Next</button>
    180                 </div>
    181 
    182             </section>
    183 
    184             <section class="purchased_area" style="<?php echo $wpsap_step_done ? 'display:block' : 'display:none'; ?>">
    185 
    186                 <h1 class="wpsap-step__title">Your order has been placed</h1>
    187                 <?php /* Kein Erstattungsversprechen: wpsap_reject_ads() kuendigt
    188                          das Abo bei Stripe, mehr nicht. Bis 1.8.1 stand hier
    189                          "you will automatically get your money back" - das
    190                          loeste der Code nie ein. */ ?>
    191                 <p class="wpsap-step__lead">The advertisement has been successfully sent to the website owner. It may take up to 14 days for the ad to be reviewed and accepted. If your request is rejected or stays unanswered, your subscription is cancelled automatically and you will not be charged again. If your ad has been accepted, your advertising partnership will remain active until you or the website owner cancel it.</p>
    192 
    193                 <table>
    194                     <tbody>
    195                         <tr>
    196                             <th scope="row">Start date</th>
    197                             <td>After the website owner accept your ads.</td>
    198                         </tr>
    199                         <tr>
    200                             <th scope="row">End date</th>
    201                             <td>Until someone quits</td>
    202                         </tr>
    203                     </tbody>
    204                 </table>
    205 
    206                 <div class="link_input_desc_area">
    207                     <div class="input-group">
    208                         <?php /* Fuehrt in das Billing Portal von Stripe - dort kann der Kaeufer
    209                                  selbst kuendigen und seine Rechnungen einsehen. */ ?>
    210                         <input id="foo" type="text" class="link_copied_button" readonly value="<?php echo esc_url(add_query_arg(array('action' => 'wpsap_billing_portal','session' => $session_id),admin_url('admin-post.php'))); ?>">
    211                         <span class="input-group-button">
    212                             <button class="wpsap_ads_copy_btn" type="button" data-clipboard-target="#foo" title="Copy to clipboard" aria-label="Copy to clipboard">
    213                                 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
    214                             </button>
    215                         </span>
    216                     </div>
    217 
    218                     <div class="wpsap-copy-hint">
    219                         <span class="wpsap-copy-hint__icon" aria-hidden="true">
    220                             <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
    221                         </span>
    222                         <p>Save this link. With this link you can cancel your monthly advertising space rental in the future. If you want to cancel, you can use this link or simply ask the website owner directly.</p>
    223                     </div>
    224                 </div>
    225 
    226                 <?php if($cencel_policy || $terms){ ?>
    227                 <ul class="wpsap-links">
    228                     <?php if($cencel_policy){ ?>
    229                     <li><a href="<?php echo esc_url(get_the_permalink($cencel_policy)); ?>"><?php echo esc_html(get_the_title($cencel_policy)); ?></a></li>
    230                     <?php } ?>
    231                     <?php if($terms){ ?>
    232                     <li><a href="<?php echo esc_url(get_the_permalink($terms)); ?>"><?php echo esc_html(get_the_title($terms)); ?></a></li>
    233                     <?php } ?>
    234                 </ul>
     160                <?php if($cencel_policy){ ?>
     161                <li><a href="<?php echo esc_url(get_the_permalink($cencel_policy)); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html(get_the_title($cencel_policy)); ?></a></li>
    235162                <?php } ?>
    236 
    237                 <div class="wpsap-actions">
    238                     <button type="button" class="btn_back wpsap-btn wpsap-btn--quiet">Back</button>
    239                     <button type="button" class="btn_finish wpsap-btn wpsap-btn--primary" data-link="<?php echo esc_url(get_site_url()); ?>">Finish</button>
    240                 </div>
    241 
    242             </section>
     163            </ul>
     164            <?php } ?>
     165
     166            <div class="wpsap-actions">
     167                <input type="hidden" id="ads_image" value="<?php echo esc_url($image_uploaded); ?>">
     168                <button type="button" class="btn next wpsap-btn wpsap-btn--primary">Next</button>
     169            </div>
    243170
    244171        </div>
    245     </div>
     172    </section>
     173
     174    <?php /* Schritt 2: Bestaetigung - einspaltig, zentriert wie der
     175             Upload-Schritt. Die frueher linke Aside (Logo, Fakten, Copyright)
     176             wurde bewusst entfernt; nur .wpsap-flow__main traegt den Inhalt. */ ?>
     177    <section class="purchased_area" <?php if(!$wpsap_step_done){ echo 'style="display:none"'; } ?>>
     178
     179        <div class="wpsap-flow__main">
     180
     181            <span class="wpsap-badge" aria-hidden="true">
     182                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" focusable="false"><path d="M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z"/><path d="m9 12 2 2 4-4"/></svg>
     183            </span>
     184
     185            <h1 class="wpsap-step__title">Your order has been placed</h1>
     186            <?php /* Kein Erstattungsversprechen: wpsap_reject_ads() kuendigt
     187                     das Abo bei Stripe, mehr nicht. Bis 1.8.1 stand hier
     188                     "you will automatically get your money back" - das
     189                     loeste der Code nie ein. */ ?>
     190            <p class="wpsap-step__lead">The advertisement has been successfully sent to the website owner. It may take up to 14 days for the ad to be reviewed and approved. If your request is rejected or remains unanswered, your subscription will be cancelled automatically, and you will not be charged. If your ad is approved, your advertising partnership will remain active until you or the website owner cancels it.</p>
     191
     192            <?php /* Stand bis 1.8.3 in der linken Aside. Die Notiz ist optional
     193                     konfigurierbar und darf mit der entfernten Spalte nicht
     194                     verschwinden - sie sitzt jetzt hier im Hauptbereich. */ ?>
     195            <?php if(trim($note_on_thanks_page) !== ''){ ?>
     196            <div class="wpsap-note"><?php echo esc_html($note_on_thanks_page); ?></div>
     197            <?php } ?>
     198
     199            <table>
     200                <tbody>
     201                    <tr>
     202                        <th scope="row">Offer Submitted</th>
     203                        <td><?php echo esc_html($wpsap_submitted_date); ?></td>
     204                    </tr>
     205                    <tr>
     206                        <th scope="row">Expected start date</th>
     207                        <td>Waiting for website owner to review (Maximum 14 days)</td>
     208                    </tr>
     209                    <tr>
     210                        <th scope="row">End date</th>
     211                        <td>Until you or website owner cancels</td>
     212                    </tr>
     213                    <tr>
     214                        <th scope="row">Billing date</th>
     215                        <td><?php echo esc_html($wpsap_billing_date); ?></td>
     216                    </tr>
     217                </tbody>
     218            </table>
     219
     220            <div class="link_input_desc_area">
     221                <div class="input-group">
     222                    <?php /* Fuehrt in das Billing Portal von Stripe - dort kann der Kaeufer
     223                             selbst kuendigen und seine Rechnungen einsehen. */ ?>
     224                    <input id="foo" type="text" class="link_copied_button" readonly value="<?php echo esc_url(add_query_arg(array('action' => 'wpsap_billing_portal','session' => $session_id),admin_url('admin-post.php'))); ?>">
     225                    <span class="input-group-button">
     226                        <button class="wpsap_ads_copy_btn" type="button" data-clipboard-target="#foo" title="Copy to clipboard" aria-label="Copy to clipboard">
     227                            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
     228                        </button>
     229                    </span>
     230                </div>
     231
     232                <div class="wpsap-copy-hint">
     233                    <span class="wpsap-copy-hint__icon" aria-hidden="true">
     234                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" focusable="false"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
     235                    </span>
     236                    <p>Please save this link. You can use it at any time to manage your subscription or cancel your monthly advertising space rental. If you wish to cancel, simply use this link or contact the website owner directly.</p>
     237                </div>
     238            </div>
     239
     240            <div class="wpsap-actions">
     241                <button type="button" class="btn_back wpsap-btn wpsap-btn--quiet">Back</button>
     242                <button type="button" class="btn_finish wpsap-btn wpsap-btn--primary" data-link="<?php echo esc_url(get_site_url()); ?>">Finish</button>
     243            </div>
     244
     245        </div>
     246    </section>
     247
    246248</div>
    247249
  • sell-ads/trunk/inc/page/style.css

    r3626502 r3627752  
    107107
    108108/* ==========================================================================
    109    Zweispaltiger Rahmen
     109   Rahmen: Upload einspaltig (SS3), Bestaetigung zweispaltig (SS4)
    110110   ========================================================================== */
    111111
    … …  
    120120}
    121121
    122 .wpsap-flow__grid {
     122/* Die Bestaetigung ist einspaltig (die frueher linke Aside mit Logo, Fakten
     123   und Copyright ist entfernt). flex:1 1 auto fuellt die Resthoehe unter dem
     124   Flex-#wpsap-flow - das tat vorher .wpsap-flow__grid. */
     125.purchased_area {
    123126    flex: 1 1 auto;
    124     display: grid;
    125     grid-template-columns: minmax(240px, 320px) 1fr;
    126     align-items: stretch;
    127 }
    128 
    129 .wpsap-flow__aside {
    130     background-color: var(--wpsap-canvas-soft, #fafafa);
    131     border-right: 1px solid var(--wpsap-line, #dcdfe3);
    132     padding: 2.5rem 1.75rem;
    133     display: flex;
    134     flex-direction: column;
    135     gap: 1.5rem;
    136 }
    137 
     127}
     128
     129/* Bleibt fuer den Upload-Schritt (Marken-Text als Logo-Fallback). */
    138130#wpsap-flow .wpsap-flow__site {
    139131    font-size: 22px;
    … …  
    144136}
    145137
    146 .wpsap-flow__facts {
     138/* Bestaetigung: Inhalt zentriert mit fester Maximalbreite (SS4). Die
     139   Blanket-Regel gibt jedem direkten Kind dieselbe Breite, damit Badge, Titel,
     140   Tabelle und Aktionszeile buendig uebereinanderstehen. */
     141.wpsap-flow__main {
     142    padding: 3rem 1.75rem;
    147143    display: flex;
    148144    flex-direction: column;
    149     gap: .75rem;
    150 }
    151 
    152 .wpsap-flow__fact {
    153     display: flex;
    154     flex-direction: column;
    155     gap: .15rem;
    156 }
    157 
    158 #wpsap-flow .wpsap-flow__fact-label {
    159     font-size: 12px;
     145    align-items: center;
     146    text-align: center;
     147}
     148
     149.wpsap-flow__main > * {
     150    width: 100%;
     151    max-width: 520px;
     152}
     153
     154/* Schritt 1 ist einspaltig und ohne Aside. wp-sap.js schaltet die Sichtbarkeit
     155   per .hide()/.show(); der gezeigte Zustand kommt aus dem Stylesheet, das
     156   Markup setzt inline nur display:none fuer den verborgenen Schritt - so
     157   ueberschreibt keine Inline-Regel das Grid der Bestaetigung. */
     158.upload_ads_area {
     159    flex: 1 1 auto;
     160    padding: 3rem 1.25rem;
     161}
     162
     163.wpsap-single {
     164    width: 100%;
     165    max-width: 560px;
     166    margin: 0 auto;
     167}
     168
     169/* Nur der Kopfbereich des Upload-Schritts ist zentriert; Karte und Felder
     170   darunter bleiben linksbuendig. */
     171.wpsap-single > .wpsap-flow__brand,
     172.wpsap-single > .wpsap-step__title,
     173.wpsap-single > .wpsap-step__subtitle,
     174.wpsap-single > .wpsap-step__lead {
     175    text-align: center;
     176}
     177
     178.wpsap-single > .wpsap-flow__brand {
     179    margin-bottom: 1.5rem;
     180}
     181
     182.upload_ads_area .wpsap-links {
     183    justify-content: center;
     184}
     185
     186
     187/* ==========================================================================
     188   Logo, Untertitel und Erfolgs-Badge
     189   ========================================================================== */
     190
     191/* Logo (get_custom_logo) oben im Upload-Schritt bzw. in der Aside. In der
     192   Aside bleibt es linksbuendig wie die uebrigen Angaben. */
     193#wpsap-flow .wpsap-flow__brand img,
     194#wpsap-flow .custom-logo {
     195    display: inline-block;
     196    width: auto;
     197    height: auto;
     198    max-height: 52px;
     199}
     200
     201#wpsap-flow .wpsap-step__subtitle {
     202    font-size: 16px;
    160203    font-weight: 600;
    161     letter-spacing: .04em;
    162     text-transform: uppercase;
    163     color: var(--wpsap-muted, #6b7280);
    164 }
    165 
    166 #wpsap-flow .wpsap-flow__fact-value {
    167     font-size: 16px;
    168     font-weight: 500;
    169     color: var(--wpsap-ink, #161616);
    170     overflow-wrap: anywhere;
    171 }
    172 
    173 #wpsap-flow .wpsap-flow__aside-note {
    174     font-size: 14px;
    175     line-height: 1.55;
     204    line-height: 1.35;
    176205    color: var(--wpsap-ink-soft, #4a4a4a);
    177     white-space: pre-line;
    178 }
    179 
    180 /* Schiebt das Copyright ans untere Ende, ohne es dort festzunageln -
    181    bei viel Inhalt rutscht es einfach mit. */
    182 #wpsap-flow .wpsap-flow__copyright {
    183     margin-top: auto;
    184     padding-top: 1.5rem;
    185     font-size: 13px;
    186     color: var(--wpsap-muted, #6b7280);
    187 }
    188 
    189 .wpsap-flow__main {
    190     padding: 2.5rem 1.75rem 3rem;
     206    margin: 0 0 1rem 0;
     207}
     208
     209/* Siegel mit Haken ueber dem Titel der Bestaetigung (SS4). */
     210.wpsap-badge {
    191211    display: flex;
    192212    justify-content: center;
    193 }
    194 
    195 /* Die beiden Schritte behalten ihre Bestandsklassen: inc/page/wp-sap.js
    196    blendet sie ueber .hide()/.show() um und setzt dabei display:block. */
    197 .upload_ads_area,
    198 .purchased_area {
    199     width: 100%;
    200     max-width: 640px;
     213    color: var(--wpsap-ink, #161616);
     214    margin-bottom: 1rem;
     215}
     216
     217#wpsap-flow .wpsap-badge svg {
     218    display: block;
     219    width: 64px;
     220    height: 64px;
    201221}
    202222
    … …  
    331351}
    332352
     353/* Blauer Upload-Button (SS3) - hebt das Hochladen von der schwarzen
     354   Primaeraktion "Next" ab. Fuellt die Breite der Karte. */
     355#wpsap-flow .wpsap-btn--upload {
     356    width: 100%;
     357    background-color: #337ab6;
     358    border-color: #337ab6;
     359    color: #fff;
     360}
     361
     362#wpsap-flow .wpsap-btn--upload:hover {
     363    background-color: #2b6698;
     364    border-color: #2b6698;
     365}
     366
    333367#wpsap-flow .wpsap-btn[disabled] {
    334368    opacity: .6;
    … …  
    507541    text-align: left;
    508542    vertical-align: top;
     543    /* Beide Spalten identisch: schwarze Schrift auf weissem Grund. Explizit
     544       gesetzt, damit kein Theme-CSS auf der Bestaetigungsseite durchschlaegt. */
     545    color: #000;
     546    background-color: #fff;
    509547}
    510548
    511549#wpsap-flow .purchased_area th {
    512550    width: 40%;
    513     font-weight: 600;
    514     color: var(--wpsap-ink, #161616);
    515     background-color: var(--wpsap-canvas-soft, #fafafa);
    516551}
    517552
    … …  
    521556    border-radius: var(--wpsap-radius-sm, 5px);
    522557    padding: 1rem;
     558    /* Der Hauptbereich zentriert seinen Inhalt (SS4); Feld und Hinweis bleiben
     559       darin aber linksbuendig. */
     560    text-align: left;
    523561}
    524562
    … …  
    604642    }
    605643
    606     .wpsap-flow__grid {
    607         grid-template-columns: 1fr;
    608     }
    609 
    610     .wpsap-flow__aside {
    611         border-right: 0;
    612         border-bottom: 1px solid var(--wpsap-line, #dcdfe3);
    613         padding: 1.75rem 1.25rem;
    614         gap: 1.25rem;
    615     }
    616 
    617     /* Gestapelt gehoert das Copyright unter den Inhalt der Spalte,
    618        nicht ans Ende eines nicht mehr vorhandenen Streifens. */
    619     #wpsap-flow .wpsap-flow__copyright {
    620         margin-top: 0;
    621         padding-top: 0;
    622     }
    623 
    624644    .wpsap-flow__main {
    625645        padding: 1.75rem 1.25rem 2.5rem;
  • sell-ads/trunk/inc/settings.php

    r3626502 r3627752  
    9494                        <?php $this->wpsap_notify_callback(); ?>
    9595
    96                         <details class="wpsap-more">
    97                                 <summary><?php esc_html_e( 'Pages and texts','wpsap_ads_plugin' ); ?></summary>
     96                        <h2 class="wpsap-step"><?php esc_html_e( 'Pages and texts','wpsap_ads_plugin' ); ?></h2>
    9897                                <table class="form-table" role="presentation">
    9998                                        <tr>
    100                                                 <th scope="row"><label for="terms_condtions_0"><?php esc_html_e( 'Terms of Service','wpsap_ads_plugin' ); ?></label></th>
     99                                                <th scope="row"><label for="terms_condtions_0"><?php esc_html_e( 'Terms of Service (Displayed on the upload page)','wpsap_ads_plugin' ); ?></label></th>
    101100                                                <td><?php $this->wpsap_terms_condtions_0_callback(); ?></td>
    102101                                        </tr>
    103102                                        <tr>
    104                                                 <th scope="row"><label for="template_cencel_subscription_1"><?php esc_html_e( 'Uploading Policy','wpsap_ads_plugin' ); ?></label></th>
     103                                                <th scope="row"><label for="template_cencel_subscription_1"><?php esc_html_e( 'Uploading Policy (Displayed on the upload page)','wpsap_ads_plugin' ); ?></label></th>
    105104                                                <td><?php $this->wpsap_template_cencel_subscription_1_callback(); ?></td>
    106105                                        </tr>
    107106                                        <tr>
    108                                                 <th scope="row"><label for="template_cencel_policy_1"><?php esc_html_e( 'Cancel Policy','wpsap_ads_plugin' ); ?></label></th>
     107                                                <th scope="row"><label for="template_cencel_policy_1"><?php esc_html_e( 'Cancel Policy (Displayed on the upload page)','wpsap_ads_plugin' ); ?></label></th>
    109108                                                <td><?php $this->wpsap_template_cencel_policy_1_callback(); ?></td>
    110109                                        </tr>
    111110                                        <tr>
    112                                                 <th scope="row"><label for="notification_2"><?php esc_html_e( 'Note for the buyer','wpsap_ads_plugin' ); ?></label></th>
     111                                                <th scope="row"><label for="notification_2"><?php esc_html_e( 'Note for the buyer (Shown on the Stripe payment page)','wpsap_ads_plugin' ); ?></label></th>
    113112                                                <td><?php $this->wpsap_notification_2_callback(); ?></td>
    114113                                        </tr>
    … …  
    122121                                        </tr>
    123122                                </table>
    124                         </details>
    125123
    126124                        <?php submit_button(); ?>
    … …  
    142140                        border: 1px solid #dcdcde;
    143141                        border-left: 4px solid #2271b1;
    144                 }
    145                 .wpsap-settings .wpsap-more {
    146                         margin-top: 32px;
    147                         padding-top: 8px;
    148                         border-top: 1px solid #dcdcde;
    149                 }
    150                 .wpsap-settings .wpsap-more > summary {
    151                         cursor: pointer;
    152                         font-size: 1.15em;
    153                         font-weight: 600;
    154                         padding: 8px 0;
    155142                }
    156143                </style>
    … …  
    241228                $status = wpsap_connect_account_status();
    242229                ?>
    243                 <p>Payments run through the Sell Ads platform at <code>sellads.cybie.de</code>.
     230                <p>Payments run through our SellAds platform.
    244231                Your advertisers pay into your own Stripe account. No API key and no webhook setup needed.</p>
    245232
    … …  
    330317                </p>
    331318                <p class="description">Stripe cannot change this later, so please pick the country your business is registered in.</p>
    332                 <p class="description">
    333                         <a href="https://sellads.cybie.de/terms.html" target="_blank" rel="noopener noreferrer">Terms of service</a>
    334                         &middot;
    335                         <a href="https://sellads.cybie.de/privacy.html" target="_blank" rel="noopener noreferrer">Privacy policy</a>
    336                 </p>
    337319                <?php
    338320        }
    … …  
    505487                <label class="wpsap-choice">
    506488                        <input type="radio" name="wpsap_test_mode" value="live" <?php checked( 'live', $mode ); ?>>
    507                         Live &mdash; buyers pay through Stripe
     489                        Live
    508490                </label>
    509491                <label class="wpsap-choice">
    510492                        <input type="radio" name="wpsap_test_mode" value="test" <?php checked( 'test', $mode ); ?>>
    511                         Test mode &mdash; walk through the plugin without setting up Stripe
     493                        Test mode
    512494                </label>
    513495                <p class="description">
    … …  
    519501                if ( wpsap_test_mode() ) {
    520502                        echo '<p class="notice notice-warning inline" style="padding:8px 12px;margin:8px 0 0;">'
    521                                 . '<strong>Test mode is on.</strong> Visitors can place orders without paying, and the ad space says so.'
     503                                . '<strong>Test mode is on.</strong> Visitors can place orders without paying.'
    522504                                . '</p>';
    523505                }
    … …  
    529511                <label class="wpsap-choice">
    530512                        <input type="radio" name="wpsap_payment_mode" value="connect" <?php checked( 'connect', $mode ); ?>>
    531                         Connect with Stripe &mdash; one click, nothing to set up
     513                        Connect with Stripe Express &mdash; Suggestion for Beginners
    532514                </label>
    533515                <label class="wpsap-choice">
    534516                        <input type="radio" name="wpsap_payment_mode" value="own_key" <?php checked( 'own_key', $mode ); ?>>
    535                         Use my own Stripe key &mdash; you add the key and the webhook yourself
     517                        Use my own Stripe key &mdash; Suggestion for Advanced Users
    536518                </label>
    537519                <?php
    … …  
    627609        public function wpsap_terms_condtions_0_callback() {
    628610                ?> <select name="wpsap_option_name[terms_condtions_0]" id="terms_condtions_0">
    629                         <?php foreach($this->wpsap_get_pages() as $page){ ?>
     611                        <option value="" <?php echo empty( $this->wpsap_options['terms_condtions_0'] ) ? 'selected' : ''; ?>><?php esc_html_e( '— None —', 'wpsap_ads_plugin' ); ?></option>
     612                        <?php foreach($this->wpsap_get_pages() as $page){ ?>
    630613                                <?php $selected = (isset( $this->wpsap_options['terms_condtions_0'] ) && $this->wpsap_options['terms_condtions_0'] == $page->ID) ? 'selected' : '' ; ?>
    631614                        <option value="<?php echo esc_attr($page->ID); ?>" <?php echo esc_attr($selected); ?>><?php echo esc_html(get_the_title($page->ID)); ?></option>
    … …  
    637620        public function wpsap_template_cencel_subscription_1_callback() {
    638621                ?> <select name="wpsap_option_name[template_cencel_subscription_1]" id="template_cencel_subscription_1">
    639                        
    640                         <?php foreach($this->wpsap_get_pages() as $page){ ?> 
     622                        <option value="" <?php echo empty( $this->wpsap_options['template_cencel_subscription_1'] ) ? 'selected' : ''; ?>><?php esc_html_e( '— None —', 'wpsap_ads_plugin' ); ?></option>
     623                        <?php foreach($this->wpsap_get_pages() as $page){ ?>
    641624                                <?php $selected = (isset( $this->wpsap_options['template_cencel_subscription_1'] ) && $this->wpsap_options['template_cencel_subscription_1'] == $page->ID) ? 'selected' : '' ; ?>
    642625                        <option value="<?php echo esc_attr($page->ID); ?>" <?php echo esc_attr($selected); ?>><?php echo esc_html(get_the_title($page->ID)); ?></option>
    … …  
    647630                public function wpsap_template_cencel_policy_1_callback() {
    648631                ?> <select name="wpsap_option_name[template_cencel_policy_1]" id="template_cencel_policy_1">
    649                        
    650                         <?php foreach($this->wpsap_get_pages() as $page){ ?> 
     632                        <option value="" <?php echo empty( $this->wpsap_options['template_cencel_policy_1'] ) ? 'selected' : ''; ?>><?php esc_html_e( '— None —', 'wpsap_ads_plugin' ); ?></option>
     633                        <?php foreach($this->wpsap_get_pages() as $page){ ?>
    651634                                <?php $selected = (isset( $this->wpsap_options['template_cencel_policy_1'] ) && $this->wpsap_options['template_cencel_policy_1'] == $page->ID) ? 'selected' : '' ; ?>
    652635                        <option value="<?php echo esc_attr($page->ID); ?>" <?php echo esc_attr($selected); ?>><?php echo esc_html(get_the_title($page->ID)); ?></option>
  • sell-ads/trunk/inc/shortcode.php

    r3626502 r3627752  
    88    $price = get_post_meta($id,"wpsap_campaign_price",true);
    99    $button_text = get_post_meta($id, "wpsap_button_text",true);
    10     if(empty($title)){
    11         $title = __('This Ad Space is for Sale','wpsap_ads_plugin');
    12     }
     10    // Kein Default-Titel: der Platz zeigt einen Titel nur, wenn der Betreiber
     11    // ihn aktiv gesetzt hat (Ausgabe unten bedingt).
    1312    if(empty($button_text)){
    1413        $button_text = __('Buy','wpsap_ads_plugin');
    … …  
    2726    <div class="wpsap_ads_plugin_ads_box">
    2827        <div class="wpsap_ads_plugin_ads_box_container">
     28        <?php if((string) $title !== ''){ ?>
    2929        <div class="wpsap_ads_plugin_ads_box_title"><?php echo esc_html($title); ?></div>
     30        <?php } ?>
    3031        <form class="wpsap_ads_plugin_ads_box_form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
    3132            <input type="hidden" name="action" value="wpsap_start_checkout">
    … …  
    4849    }
    4950    else {
     51        // Klicks auf die veroeffentlichte Anzeige laufen ueber /goto/<slug>,
     52        // damit der Zaehler der Kampagne hochlaeuft (inc/hooks.php) - derselbe
     53        // Zaehler, den auch der Buy-Button-Klick erhoeht (inc/checkout.php).
     54        // Fallback auf die ID, falls der Slug (post_name) leer ist.
     55        $slug = get_post_field('post_name',$id);
     56        if(empty($slug)){
     57            $slug = $id;
     58        }
     59        // user_trailingslashit() richtet sich nach der Permalink-Struktur der
     60        // Seite. Ohne das haengt WordPress bei Trailing-Slash-Permalinks erst
     61        // einen 301 an, bevor der Redirect greift - ein unnoetiger Hop auf
     62        // jedem Anzeigenklick.
     63        $goto_url = home_url(user_trailingslashit('goto/'.$slug));
    5064        foreach($activer_orders as $ad){
    51             $url = get_post_meta($ad->ID, "order_url",true);
    52          ?>
     65         ?>
    5366    <div class="wpsap_ads_plugin_ads_box">
    5467        <div class="wpsap_ads_plugin_ads_box_container">
    55         <a href="<?php echo esc_attr($url); ?>" target="_blank" rel="noopener noreferrer nofollow"><img style="max-width:100%;" src="<?php echo esc_attr(get_post_meta($ad->ID, "order_image",true)); ?>"></a>
     68        <a href="<?php echo esc_url($goto_url); ?>" target="_blank" rel="noopener noreferrer nofollow"><img style="max-width:100%;" src="<?php echo esc_attr(get_post_meta($ad->ID, "order_image",true)); ?>"></a>
    5669        </div>
    5770    </div>
  • sell-ads/trunk/inc/stripe.php

    r3626067 r3627752  
    157157        ),
    158158        'subscription_data'    => array(
     159            // Waehrend der Pruefungsfrist wird nicht belastet: das Abo startet
     160            // im Trial, die erste Rechnung entsteht erst, wenn der Betreiber die
     161            // Bestellung annimmt (wpsap_stripe_end_trial) - oder gar nicht, wenn
     162            // sie nach 14 Tagen ablaeuft und das Abo vorher gekuendigt wird.
     163            // 16 statt 14 Tage: der Ablauf-Cron laeuft nur taeglich, der Puffer
     164            // stellt sicher, dass die Kuendigung immer vor dem Trial-Ende greift.
     165            'trial_period_days' => 16,
    159166            'metadata' => array(
    160167                'wpsap_place_id' => (string) $campaign_id,
    … …  
    210217    }
    211218    return wpsap_stripe_request('DELETE','subscriptions/'.rawurlencode($subscription_id));
     219}
     220
     221/**
     222 * Beendet den Trial eines Abos sofort. Dadurch stellt Stripe die erste
     223 * Rechnung unmittelbar aus. Aufgerufen beim Annehmen einer Bestellung: erst
     224 * dann - nicht schon beim Checkout - soll der Kaeufer belastet werden.
     225 */
     226function wpsap_stripe_end_trial($subscription_id){
     227    $subscription_id = sanitize_text_field($subscription_id);
     228    if(empty($subscription_id)){
     229        return new WP_Error('wpsap_no_subscription',__('No subscription stored for this order.','wpsap_ads_plugin'));
     230    }
     231    if(wpsap_connect_is_active()){
     232        return wpsap_connect_end_trial($subscription_id);
     233    }
     234    return wpsap_stripe_request('POST','subscriptions/'.rawurlencode($subscription_id),array('trial_end' => 'now'));
    212235}
    213236
  • sell-ads/trunk/readme.txt

    r3626505 r3627752  
    55Tested up to: 7.0
    66Requires PHP: 7.0
    7 Stable tag: 1.8.2
     7Stable tag: 1.9.6
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl.html
    … …  
    241241If you want to fill unsold inventory automatically, you want an ad network, not this. This plugin
    242242does nothing until a real advertiser buys a real space from you. If you need impression counting,
    243 click tracking, geo-targeting or A/B rotation, this plugin has none of it on purpose: an ad here is
    244 an image and a link, and that is the entire point. And if you would rather never speak to an
     243geo-targeting or A/B rotation, this plugin has none of it on purpose: an ad here is an image and a
     244link, and that is the entire point. It does count clicks — first-party, on your own server, with no
     245cookies and no third-party script — but that is as far as the measuring goes. And if you would rather never speak to an
    245246advertiser, be aware that even though the purchase itself is automatic, you are the one who decides
    246247what runs on your site.
    … …  
    402403
    403404== Changelog ==
     405
     406= 1.9.0 =
     407* New: the campaign list shows a "Clicks" column again, counting how often each ad space was clicked. Published ads now link through a first-party /goto/<slug> redirect on your own domain that counts the click and forwards the visitor to the advertiser — no cookies and no third-party script are involved, and clicks from logged-in editors are not counted.
     408* Fixed: the "PHP" and "Shortcode" columns showed their raw HTML instead of a usable field. They are copyable input fields again, each with a copy button.
     409
     410= 1.8.4 =
     411* The confirmation step is now a single centred column, matching the upload step. The left column that repeated your site name, the ad space and the monthly price has been removed; the confirmation badge, the summary table and the cancellation link stay.
     412
     413= 1.8.3 =
     414* The buyer flow was reworked to match the intended design. The ad space now shows as a rounded card instead of a square band.
     415* The upload step is a single centred column again, with your site logo on top, the ad space name below the heading, and a clearly separated blue upload button. Your terms and refund pages are linked right there.
     416* The confirmation step keeps its two columns and now opens with a confirmation badge and a four-row summary: when the request was submitted, the expected review window, that the partnership runs until either side cancels, and the expected first billing date.
     417* Uses your WordPress site logo (Appearance → Customize) on both pages when one is set, otherwise your site name.
    404418
    405419= 1.8.2 =
    … …  
    456470== Upgrade Notice ==
    457471
     472= 1.9.0 =
     473The campaign list has a Clicks column again and published ads are counted through a first-party /goto redirect on your own domain. The PHP and Shortcode columns are copyable fields again. No reconfiguration needed; the redirect rule is registered automatically on update.
     474
     475= 1.8.4 =
     476The confirmation page after upload is now a single centred column; the left sidebar with the site name and price has been removed. No reconfiguration needed.
     477
     478= 1.8.3 =
     479Visual update to the pages buyers see. The ad space is a rounded card, the upload step is a single centred column with your site logo, and the confirmation shows a badge and a clear date summary. No reconfiguration needed.
     480
    458481= 1.8.2 =
    459482Documentation only — the plugin code is identical to 1.8.1. The description now covers pricing, placement and finding your first advertiser.
  • sell-ads/trunk/wp-sap.php

    r3626505 r3627752  
    55 * Plugin URI:        https://cybie.de
    66 * Description:       The easiest way to sell adspace on your WordPress Website
    7  * Version:           1.8.2
     7 * Version:           1.9.6
    88 * Requires at least: 6.0
    99 * Requires PHP:      7.0
    … …  
    1717
    1818
    19 define("WPSAP_Version",'1.8.2');
     19define("WPSAP_Version",'1.9.6');
    2020define('WPSAP_Plugin_Path',plugin_dir_path( __FILE__ ));
    2121define('WPSAP_Plugin_Dir',plugin_dir_url( __FILE__ ));
    … …  
    8181        wp_schedule_event(time() + HOUR_IN_SECONDS,'daily','wpsap_expire_pending_orders');
    8282    }
     83
     84    // Damit /goto/<slug> sofort nach der Aktivierung greift, muss die
     85    // Rewrite-Rule bekannt sein und einmal geflusht werden. Die laufende
     86    // Absicherung uebernimmt der Versions-Guard in wpsap_goto_rewrite().
     87    wpsap_goto_rewrite();
     88    flush_rewrite_rules();
    8389}
    8490
    … …  
    8692function wpsap_deactivate(){
    8793    wp_clear_scheduled_hook('wpsap_expire_pending_orders');
     94
     95    // Die /goto-Rewrite-Rule wird ohne aktives Plugin nicht mehr registriert -
     96    // der Flush raeumt sie aus den gespeicherten Regeln. Beim naechsten
     97    // Aktivieren sorgt der Versions-Guard fuer die Neuanlage.
     98    delete_option('wpsap_rewrite_version');
     99    flush_rewrite_rules();
    88100}
    89101
Note: See TracChangeset for help on using the changeset viewer.