Plugin Directory

Changeset 3443682


Ignore:
Timestamp:
01/21/2026 01:08:42 AM (2 months ago)
Author:
brygs
Message:

Version 2.2.1

Location:
petpress
Files:
273 added
11 edited

Legend:

Unmodified
Added
Removed
  • petpress/trunk/includes/pp-style.css

    r3438868 r3443682  
    205205  width: 100%;
    206206  height: 100%;
    207   background: rgba(0, 0, 0, 0.7);
    208 z-index: 3;
     207  background: rgba(0, 0, 0, 0.9);
     208  z-index: 9999;
     209  justify-content: center;
     210  align-items: center;
     211}
     212
     213#pp_lightbox_content {
     214  display: flex;
     215  flex-direction: column;
     216  align-items: center;
     217  justify-content: center;
     218  height: 100%;
     219  width: 100%;
     220  padding: 60px 80px;
     221  box-sizing: border-box;
    209222}
    210223
    211224#pp_lightbox img {
    212   max-width: 80%;
    213   max-height: 80%;
    214   display: block;
    215   margin: auto;
    216   margin-top: 20%;
    217 z-index: 4;
    218 }
    219 
    220 #pp_lightbox_close_btn, #pp_lightbox_prev_btn, #pp_lightbox_next_btn {
     225  width: 80vw;
     226  max-height: calc(100vh - 100px);
     227  object-fit: contain;
     228}
     229
     230#pp_lightbox_counter {
     231  color: white;
     232  font-size: 14px;
     233  margin-top: 10px;
     234  text-align: center;
     235}
     236
     237#pp_lightbox_close_btn,
     238#pp_lightbox_prev_btn,
     239#pp_lightbox_next_btn {
     240  position: absolute;
     241  color: white;
     242  font-size: 36px;
     243  cursor: pointer;
     244  padding: 16px;
     245  user-select: none;
     246  transition: opacity 0.2s;
     247  z-index: 10000;
     248}
     249
     250#pp_lightbox_close_btn:hover,
     251#pp_lightbox_prev_btn:hover,
     252#pp_lightbox_next_btn:hover {
     253  opacity: 0.7;
     254}
     255
     256#pp_lightbox_close_btn {
     257  top: 10px;
     258  right: 20px;
     259  font-size: 40px;
     260}
     261
     262#pp_lightbox_prev_btn,
     263#pp_lightbox_next_btn {
     264  top: 50%;
     265  transform: translateY(-50%);
     266}
     267
     268#pp_lightbox_prev_btn {
     269  left: 20px;
     270}
     271
     272#pp_lightbox_next_btn {
     273  right: 20px;
     274}
     275
     276@media (max-width: 768px) {
     277  #pp_lightbox_content {
     278    padding: 50px 10px;
     279  }
     280
     281  #pp_lightbox_prev_btn,
     282  #pp_lightbox_next_btn {
     283    font-size: 24px;
     284    padding: 12px;
     285  }
     286
     287  #pp_lightbox_prev_btn {
     288    left: 5px;
     289  }
     290
     291  #pp_lightbox_next_btn {
     292    right: 5px;
     293  }
     294}
     295
     296.pp_hidden_tile
     297{
     298  display:none;
     299}
     300.pp_shown_tile{
     301  display:block;
     302}
     303
     304#pp_sort_btn_container {
     305  display: flex;
     306  width: 100%;
     307  gap: 8px;
     308}
     309
     310.pp_sort_btn {
     311  flex: 1;
     312  white-space: nowrap;
     313  padding: 8px 12px;
     314  position: relative;
     315  transition: all 0.15s ease;
     316  text-align: center;
     317}
     318
     319.pp_sort_btn.pp_sort_active {
     320  /* background: linear-gradient(to bottom, #d4ebf7 5%, #b8dff5 100%); */
     321  /* color: #005a87; */
     322    border-color: #666;
     323  font-weight: bold;
     324}
     325.pp_sort_btn.pp_sort_active::before,
     326.pp_sort_btn.pp_sort_active::after {
    221327  position: absolute;
    222328  top: 50%;
    223329  transform: translateY(-50%);
    224   color: white;
    225   font-size: 30px;
    226   cursor: pointer;
    227   padding:2em;
    228 }
    229 
    230 #pp_lightbox_close_btn {
    231   top:10px;
    232   right: 10px;
    233 }
    234 
    235 #pp_lightbox_prev_btn {
    236   left: 10px;
    237 }
    238 
    239 #pp_lightbox_next_btn {
    240   right: 10px;
    241 }
    242 
    243 .pp_hidden_tile
    244 {
    245   display:none;
    246 }
    247 .pp_shown_tile{
    248   display:block;
    249 }
    250 
    251 #pp_sort_btn_container
    252 {
    253   display:flex;
    254 }
    255 
    256 .pp_sort_btn {
    257   display: inline;
    258   margin-right: 10px;
    259  flex:1;
    260 
     330  font-size: 12px;
     331  line-height: 1;
     332}
     333
     334.pp_sort_btn.pp_sort_active::before {
     335  left: 6px;
     336}
     337
     338.pp_sort_btn.pp_sort_active::after {
     339  right: 6px;
     340}
     341
     342.pp_sort_btn.pp_sort_asc::before,
     343.pp_sort_btn.pp_sort_asc::after {
     344  content: "▼";
     345}
     346
     347.pp_sort_btn.pp_sort_desc::before,
     348.pp_sort_btn.pp_sort_desc::after {
     349  content: "▲";
     350}
     351
     352@media (max-width: 480px) {
     353  .pp_sort_btn {
     354    padding: 6px 4px;
     355    font-size: 0.85em;
     356  }
     357
     358  .pp_sort_btn.pp_sort_active::before,
     359  .pp_sort_btn.pp_sort_active::after {
     360    font-size: 10px;
     361  }
     362
     363  .pp_sort_btn.pp_sort_active::before {
     364    left: 4px;
     365  }
     366
     367  .pp_sort_btn.pp_sort_active::after {
     368    right: 4px;
     369  }
    261370}
    262371
     
    297406}
    298407
    299 /* Remove margin from the last button */
    300 .pp_sort_btn:last-child {
    301   margin-right: 0;
    302 }
    303408
    304409.pp_stickie1_img { position: absolute;
  • petpress/trunk/includes/pp.js

    r3438868 r3443682  
    88
    99
    10   jQuery("#pp_moreinfo li").each(function() {
    11     var text = jQuery(this)
    12       .contents()
    13       .filter(function() { return this.nodeType === 3; }) 
    14       .first();
    15 
    16     if (text.length && jQuery.trim(text.text()).indexOf("Price:") === 0) {
    17       text[0].nodeValue = text[0].nodeValue.replace("Price:", "Adoption Fee:");
    18     }
    19   });
    20 
    21 
    22 
    23 
    2410    pp_globals.intialNumTiles = jQuery('.pp_tile_container').attr('numtiles');
    2511
     
    3319        Lightbox effect for detail page
    3420    */
    35     if (typeof cPhoto1 !== 'undefined')
    36     {
     21    (function() {
     22        var $lightbox = jQuery('#pp_lightbox');
     23        if (!$lightbox.length) return;
     24
     25        var photosData = $lightbox.attr('data-photos');
     26        if (!photosData) return;
     27
    3728        var images = [];
    38 
    39         if (cPhoto1.trim() !== '') {
    40             images.push(cPhoto1);
    41         }
    42         if (cPhoto2.trim() !== '') {
    43             images.push(cPhoto2);
    44         }
    45         if (cPhoto3.trim() !== '') {
    46             images.push(cPhoto3);
    47         }
     29        try {
     30            images = JSON.parse(photosData);
     31        } catch (e) {
     32            return;
     33        }
     34
     35        if (!images.length) return;
    4836
    4937        var currentIndex = 0;
     38        var $img = jQuery('#pp_lightboxImg');
     39        var $counter = jQuery('#pp_lightbox_counter');
     40        var $prevBtn = jQuery('#pp_lightbox_prev_btn');
     41        var $nextBtn = jQuery('#pp_lightbox_next_btn');
     42
     43        function updateNavVisibility() {
     44            if (images.length <= 1) {
     45                $prevBtn.hide();
     46                $nextBtn.hide();
     47                $counter.hide();
     48            } else {
     49                $prevBtn.show();
     50                $nextBtn.show();
     51                $counter.show();
     52            }
     53        }
    5054
    5155        function showImage(index) {
    52             jQuery('#pp_lightboxImg').attr('src', images[index]);
    5356            currentIndex = index;
    54         }
    55 
    56         showImage(currentIndex);
    57 
    58         jQuery('.pp_lightbox_trigger').click(function () {
    59             var clickedIndex = jQuery(this).attr('data-index');
    60             showImage(clickedIndex);
    61             jQuery('#pp_lightbox').fadeIn();
    62         });
     57            $img.attr('src', images[index]);
     58            $counter.text((index + 1) + ' / ' + images.length);
     59            updateNavVisibility();
     60        }
     61
     62        function openLightbox(index) {
     63            showImage(index);
     64            $lightbox.fadeIn(200);
     65            jQuery('body').css('overflow', 'hidden');
     66        }
    6367
    6468        function closeLightbox() {
    65             jQuery('#pp_lightbox').fadeOut();
    66         }
    67 
    68         jQuery('#pp_lightbox, #pp_lightbox_close_btn').click(function (e) {
    69             if (e.target.id === 'pp_lightbox' || e.target.id === 'pp_lightbox_close_btn') {
     69            $lightbox.fadeOut(200);
     70            jQuery('body').css('overflow', '');
     71        }
     72
     73        function nextImage() {
     74            if (images.length > 1) {
     75                showImage((currentIndex + 1) % images.length);
     76            }
     77        }
     78
     79        function prevImage() {
     80            if (images.length > 1) {
     81                showImage((currentIndex - 1 + images.length) % images.length);
     82            }
     83        }
     84
     85        // Open lightbox when clicking trigger images
     86        jQuery('.pp_lightbox_trigger').on('click', function() {
     87            var index = parseInt(jQuery(this).attr('data-index'), 10) || 0;
     88            openLightbox(index);
     89        });
     90
     91        // Close on background click (darkened area, not image or buttons)
     92        $lightbox.on('click', function(e) {
     93            var targetId = e.target.id;
     94            if (targetId === 'pp_lightbox' || targetId === 'pp_lightbox_content' || targetId === 'pp_lightbox_close_btn') {
    7095                closeLightbox();
    7196            }
    7297        });
    7398
    74         jQuery('#pp_lightbox_prev_btn').click(function (e) {
     99        // Navigation buttons
     100        $prevBtn.on('click', function(e) {
    75101            e.stopPropagation();
    76             currentIndex = (currentIndex - 1 + images.length) % images.length;
    77             showImage(currentIndex);
    78         });
    79 
    80         jQuery('#pp_lightbox_next_btn').click(function (e) {
     102            prevImage();
     103        });
     104
     105        $nextBtn.on('click', function(e) {
    81106            e.stopPropagation();
    82             currentIndex = (currentIndex + 1) % images.length;
    83             showImage(currentIndex);
    84         });
    85 
    86         jQuery('#pp_lightboxImg').click(function (e) {
     107            nextImage();
     108        });
     109
     110        // Prevent closing when clicking the image
     111        $img.on('click', function(e) {
    87112            e.stopPropagation();
    88113        });
    89     }
     114
     115        // Keyboard navigation
     116        jQuery(document).on('keydown', function(e) {
     117            if (!$lightbox.is(':visible')) return;
     118
     119            switch (e.key) {
     120                case 'Escape':
     121                    closeLightbox();
     122                    break;
     123                case 'ArrowLeft':
     124                    prevImage();
     125                    break;
     126                case 'ArrowRight':
     127                    nextImage();
     128                    break;
     129            }
     130        });
     131
     132        // Touch/swipe support
     133        var touchStartX = 0;
     134        var touchEndX = 0;
     135
     136        $lightbox.on('touchstart', function(e) {
     137            touchStartX = e.originalEvent.changedTouches[0].screenX;
     138        });
     139
     140        $lightbox.on('touchend', function(e) {
     141            touchEndX = e.originalEvent.changedTouches[0].screenX;
     142            var diff = touchStartX - touchEndX;
     143            if (Math.abs(diff) > 50) {
     144                if (diff > 0) {
     145                    nextImage();
     146                } else {
     147                    prevImage();
     148                }
     149            }
     150        });
     151    })();
    90152});
    91153
    92154function setSortIndicator(attributeIN){
     155    var buttonId;
     156    var newSortOrder = 0;
     157
    93158    switch (attributeIN){
    94159        case "data-name":
    95             theArrow = "#sortNameBTN > span";
    96             newSortOrder = 0; // a-z
     160            buttonId = "#sortNameBTN";
    97161            break;
    98162        case "data-age":
    99163        case "data-agegroupindex":
    100             theArrow = "#sortAgeBTN > span";
    101             newSortOrder = 0; // youngest to oldest
     164            buttonId = "#sortAgeBTN";
    102165            break;
    103166        case "data-weight":
    104167        case "data-sizeindex":
    105             theArrow = "#sortSizeBTN > span";
    106             newSortOrder = 0; // smallest to largest
     168            buttonId = "#sortSizeBTN";
    107169            break;
    108170        case "data-daysin":
    109             theArrow = "#sortDaysInBTN > span";
    110             newSortOrder = 0; // long term to short term
     171            buttonId = "#sortDaysInBTN";
    111172            break;
    112173    }
    113174
    114     sameButtonSelected = (jQuery(theArrow).hasClass("dashicons-arrow-up-alt") || jQuery(theArrow).hasClass("dashicons-arrow-down-alt"));
    115 
    116    // sortUp = jQuery(theArrow).hasClass("dashicons-arrow-up-alt");
    117    // sortDown = jQuery(theArrow).hasClass("dashicons-arrow-down-alt");
    118 
    119     jQuery(".pp_sort_btn > span").removeClass("dashicons-arrow-up-alt");
    120     jQuery(".pp_sort_btn > span").removeClass("dashicons-arrow-down-alt");
    121 
    122     if (!sameButtonSelected) { pp_globals.sortOrder = newSortOrder;}
    123     if (pp_globals.sortOrder == 0)
    124     {
    125         jQuery(theArrow).addClass("dashicons-arrow-down-alt");
     175    var $btn = jQuery(buttonId);
     176    var sameButtonSelected = $btn.hasClass("pp_sort_active");
     177
     178    // Clear all buttons
     179    jQuery(".pp_sort_btn").removeClass("pp_sort_active pp_sort_asc pp_sort_desc");
     180
     181    // Toggle sort order if same button clicked
     182    if (!sameButtonSelected) {
     183        pp_globals.sortOrder = newSortOrder;
     184    }
     185
     186    // Apply active state and direction
     187    $btn.addClass("pp_sort_active");
     188    if (pp_globals.sortOrder == 0) {
     189        $btn.addClass("pp_sort_desc");
    126190    } else {
    127         jQuery(theArrow).addClass("dashicons-arrow-up-alt");
     191        $btn.addClass("pp_sort_asc");
    128192    }
    129193}
  • petpress/trunk/petpress.php

    r3438868 r3443682  
    44* Plugin Name:      PetPress
    55* Plugin URI:       https://www.airdriemedia.com/petpress
    6 * Version:          2.2
     6* Version:          2.2.1
    77* Description:      Display adoptable animals on your shelter's website. Compatible with PetPoint and AnimalsFirst or without external data source.
    88* Author:           Airdrie Media
     
    1313*/
    1414if ( !defined( 'PP_PLUGIN_VERSION' ) ) {
    15     define( 'PP_PLUGIN_VERSION', '2.2' );
     15    define( 'PP_PLUGIN_VERSION', '2.2.1' );
    1616}
    1717if ( !defined( 'PP_DATABASE_VERSION' ) ) {
     
    9393            require_once 'pp-Utilities.php';
    9494            require_once 'pp-Animal.php';
     95            // Cron retry and self-healing hooks (after ppUtils is loaded)
     96            add_action( 'petpress_cron_retry', 'ppUtils::retryCronSchedule' );
     97            add_action( 'admin_init', 'ppUtils::ensureCronScheduled' );
    9598            // todo check to see if option is set to this website
    9699            $options = get_option( 'petpress_options' );
     
    107110            add_action( 'wp_enqueue_scripts', [$this, 'pp_scripts'] );
    108111            add_action( 'init', [$this, 'petpress_update_check'] );
    109             //add_filter( 'cron_schedules', [$this,'petpress_add_everyhalfhour_schedule']);
    110             //add_filter( 'cron_schedules', [$this,'petpress_add_everyquarterhour_schedule']);
    111112            add_filter( 'cron_schedules', 'ppUtils::add_30_schedule' );
    112113            add_filter( 'cron_schedules', 'ppUtils::add_15_schedule' );
     
    187188                $db = new ppDB();
    188189                $db->activate();
    189                 //ppUtils::createCronJob("30m", "PetPressDB"); // JWB initial interval?
    190190            } catch ( InvalidArgumentException $e ) {
    191                 // Handle the error
    192191                logError( "Could not create database: " . $e->getMessage() );
    193192            }
     
    203202                }
    204203            } catch ( InvalidArgumentException $e ) {
    205                 // Handle the error
    206204                echo "Could not create database: " . $e->getMessage();
    207205            }
  • petpress/trunk/pp-DataSource.php

    r3438868 r3443682  
    1010
    1111    public function __construct(){
    12  //       add_filter( 'cron_schedules', 'pp_add_everyhalfhour_schedule' ); // JWB unused (?)
     12
    1313    }
    1414
     
    4747        global $wpdb;
    4848
    49         $theCurDate = ppUtils::getDBTime(0);
    50    
    51         $table_name = $wpdb->prefix . "petpress_animals";
    52    
    53         $dateofbirth = $critterIN->get_dateofbirth();
    54         if (isset($dateofbirth)){
    55             $dateofbirth = date("Y-m-d", strtotime($dateofbirth));
    56         }
    57 
    58         $lastintakedate = $critterIN->get_lastintakedate();
    59         if (isset($lastintakedate)){
    60             $lastintakedate = date("Y-m-d", strtotime($lastintakedate));
    61         }
    62         $rc = $wpdb->replace(
    63             $table_name,
    64                 array(
    65                     'time' => $theCurDate,
    66                     'id' => $critterIN->get_id(),
    67                     'adoptionapplicationurl' => $critterIN->get_adoptionapplicationurl(),
    68                     'agegroup' =>  $critterIN->get_agegroup(),
    69                     'altered' =>  $critterIN->get_altered(),
    70                     'arn' =>  $critterIN->get_arn(),
    71                     'behaviorresult' => $critterIN->get_behaviorresult(),
    72                     'behaviortestlist' =>  $critterIN->get_behaviortestlist(),
    73                     'breed' => ppUtils::unscrambleBreedName($critterIN->get_breed()) ,
    74                     'chipnumber' =>   $critterIN->get_chipnumber(),
    75                     'coatlength' =>  $critterIN->get_coatlength() , 
    76                     'companyid' =>  $critterIN->get_companyid() , 
    77                     'colorpattern' =>  $critterIN->get_colorpattern() ,
    78                     'dateofbirth' =>  $dateofbirth  ,
    79                     'declawed' => $critterIN->get_declawed(),
    80                     'featured' => $critterIN->get_featured(),
    81                     'housetrained' => $critterIN->get_housetrained(),
    82                     'lastintakedate' => $lastintakedate ,
    83                     'livedwithanimals' => $critterIN->get_livedwithanimals() ,
    84                     'livedwithanimaltypes' => $critterIN->get_livedwithanimaltypes() ,
    85                     'livedwithchildren' => $critterIN->get_livedwithchildren() ,
    86                     'location' =>  $critterIN->get_location(),
    87                     'memo' => $critterIN->get_memo(),
    88                     'name' =>  $critterIN->get_name(),
    89                     'nocats' =>  $critterIN->get_nocats(),
    90                     'nodogs' =>  $critterIN->get_nodogs(),
    91                     'nokids' =>  $critterIN->get_nokids(),
    92                     'onhold' =>  $critterIN->get_onhold(),
    93                     'price' =>  $critterIN->get_price(),
    94                     'primarycolor' => $critterIN->get_primarycolor(),
    95                     'photo1' =>  $critterIN->get_photo1(),
    96                     'photo2' =>  $critterIN->get_photo2(),
    97                     'photo3' =>  $critterIN->get_photo3(),
    98                     'reasonforsurrender' =>  $critterIN->get_reasonforsurrender(),
    99                     'secondarybreed' =>  ppUtils::unscrambleBreedName($critterIN->get_secondarybreed()),
    100                     'secondarycolor' => $critterIN->get_secondarycolor(),
    101                     'sex' =>  $critterIN->get_sex(),
    102                     'shotscurrent' =>  $critterIN->get_shotscurrent() , 
    103                     'sitename' =>  $critterIN->get_sitename(),
    104                     'size' =>  $critterIN->get_size(),
    105                     'specialneeds' => $critterIN->get_specialneeds(),
    106                     'species' => $critterIN->get_species(),
    107                     'stage' =>  $critterIN->get_stage(),
    108                     'sublocation' => $critterIN->get_sublocation(),
    109                     'systemofrecord' => $critterIN->get_systemofrecord(),
    110                     'videoid' => $critterIN->get_videoid(),
    111                     'weight' => $critterIN->get_weight()
    112                 )
    113             )  ;
    114             $testval = $rc;
     49
     50            $theCurDate = ppUtils::getDBTime(0);
     51       
     52            $table_name = $wpdb->prefix . "petpress_animals";
     53       
     54            $dateofbirth = $critterIN->get_dateofbirth();
     55            if (isset($dateofbirth)){
     56                $dateofbirth = date("Y-m-d", strtotime($dateofbirth));
     57            }
     58
     59            $lastintakedate = $critterIN->get_lastintakedate();
     60            if (isset($lastintakedate)){
     61                $lastintakedate = date("Y-m-d", strtotime($lastintakedate));
     62            }
     63            $rc = $wpdb->replace(
     64                $table_name,
     65                    array(
     66                        'time' => $theCurDate,
     67                        'id' => $critterIN->get_id(),
     68                        'adoptionapplicationurl' => $critterIN->get_adoptionapplicationurl(),
     69                        'agegroup' =>  $critterIN->get_agegroup(),
     70                        'altered' =>  $critterIN->get_altered(),
     71                        'arn' =>  $critterIN->get_arn(),
     72                        'behaviorresult' => $critterIN->get_behaviorresult(),
     73                        'behaviortestlist' =>  $critterIN->get_behaviortestlist(),
     74                        'breed' => ppUtils::unscrambleBreedName($critterIN->get_breed()) ,
     75                        'chipnumber' =>   $critterIN->get_chipnumber(),
     76                        'coatlength' =>  $critterIN->get_coatlength() , 
     77                        'companyid' =>  $critterIN->get_companyid() , 
     78                        'colorpattern' =>  $critterIN->get_colorpattern() ,
     79                        'dateofbirth' =>  $dateofbirth  ,
     80                        'declawed' => $critterIN->get_declawed(),
     81                        'featured' => $critterIN->get_featured(),
     82                        'housetrained' => $critterIN->get_housetrained(),
     83                        'lastintakedate' => $lastintakedate ,
     84                        'livedwithanimals' => $critterIN->get_livedwithanimals() ,
     85                        'livedwithanimaltypes' => $critterIN->get_livedwithanimaltypes() ,
     86                        'livedwithchildren' => $critterIN->get_livedwithchildren() ,
     87                        'location' =>  $critterIN->get_location(),
     88                        'memo' => $critterIN->get_memo(),
     89                        'name' =>  $critterIN->get_name(),
     90                        'nocats' =>  $critterIN->get_nocats(),
     91                        'nodogs' =>  $critterIN->get_nodogs(),
     92                        'nokids' =>  $critterIN->get_nokids(),
     93                        'onhold' =>  $critterIN->get_onhold(),
     94                        'price' =>  $critterIN->get_price(),
     95                        'primarycolor' => $critterIN->get_primarycolor(),
     96                        'photo1' =>  $critterIN->get_photo1(),
     97                        'photo2' =>  $critterIN->get_photo2(),
     98                        'photo3' =>  $critterIN->get_photo3(),
     99                        'reasonforsurrender' =>  $critterIN->get_reasonforsurrender(),
     100                        'secondarybreed' =>  ppUtils::unscrambleBreedName($critterIN->get_secondarybreed()),
     101                        'secondarycolor' => $critterIN->get_secondarycolor(),
     102                        'sex' =>  $critterIN->get_sex(),
     103                        'shotscurrent' =>  $critterIN->get_shotscurrent() , 
     104                        'sitename' =>  $critterIN->get_sitename(),
     105                        'size' =>  $critterIN->get_size(),
     106                        'specialneeds' => $critterIN->get_specialneeds(),
     107                        'species' => $critterIN->get_species(),
     108                        'stage' =>  $critterIN->get_stage(),
     109                        'sublocation' => $critterIN->get_sublocation(),
     110                        'systemofrecord' => $critterIN->get_systemofrecord(),
     111                        'videoid' => $critterIN->get_videoid(),
     112                        'weight' => $critterIN->get_weight()
     113                    )
     114                )  ;
     115                $testval = $rc;
     116       
    115117    }
    116118
     
    133135            ppUtils::petpress_log("In fetchAnimals, data purged. Writing new animal data START at " . date('Y-m-d H:i:s'));
    134136            foreach ($roster as $critter) {
    135                 $this->writeOneAnimal($critter);
     137                if ( is_wp_error( $critter ) ) {
     138                    ppUtils::petpress_log(sprintf('writeOneAnimal will not be called as wp_error was returned within roster. (%s:%d)',         __METHOD__,         __LINE__    ));
     139
     140                }
     141                else {
     142                    $this->writeOneAnimal($critter);
     143                }
    136144            }
    137145            ppUtils::petpress_log("In fetchAnimals, animals written END. Called setFetchTime at " . date('Y-m-d H:i:s'));
  • petpress/trunk/pp-DetailPage.php

    r3438868 r3443682  
    371371                if ($cBehaviorResult = $critter->get_behaviorresult())
    372372                    $h .= "<li>Behavior test result: <span data-behaviorresult='{$cBehaviorResult}'>{$cBehaviorResult}</span></li>\n";
    373    
     373    /*
    374374            if ($optPrice)
    375375                if ($cPrice = $critter->get_price())
    376376                    $h .= "<li>Price: $<span data-price='{$cPrice}'>{$cPrice}</span></li>\n";
    377    
     377    */
     378            if ( $optPrice ) {
     379                if ($cPrice = $critter->get_price()){
     380                $label = ppUtils::optionValue('price_label');
     381                if ($label == 0) {$label = "Price";}
     382                $h .= "<li>$label: $<span data-price='{$cPrice}'>{$cPrice}</span></li>\n";
     383                //echo '<div class="price">' . esc_html($animal->price) . '</div>';
     384            }}
     385
     386
    378387            if ($optShotsCurrent)
    379388                if ($cShotsCurrent = $critter->get_shotscurrent()){
     
    455464   
    456465   
    457 $lightboxscript = <<< lightboxcode
    458 <div id="pp_lightbox">
    459     <span id="pp_lightbox_close_btn">&times;</span>
    460     <span id="pp_lightbox_prev_btn">&lt;</span>
    461     <img id="pp_lightboxImg" src="" alt="Lightbox Image">
    462     <span id="pp_lightbox_next_btn">&gt;</span>
    463 </div>
    464 <script>
    465 var cPhoto1 = "$cPhoto1";
    466 var cPhoto2 = "$cPhoto2";
    467 var cPhoto3 = "$cPhoto3";
    468 </script>
    469 lightboxcode;
    470 
    471         $h .= $lightboxscript;
     466        $photos = array_filter([$cPhoto1, $cPhoto2, $cPhoto3], function($p) {
     467            return !empty($p);
     468        });
     469        $photosJson = esc_attr(wp_json_encode(array_values($photos)));
     470
     471        $h .= '<div id="pp_lightbox" data-photos="' . $photosJson . '">';
     472        $h .= '<span id="pp_lightbox_close_btn" role="button" aria-label="Close lightbox">&times;</span>';
     473        $h .= '<span id="pp_lightbox_prev_btn" role="button" aria-label="Previous image">&#10094;</span>';
     474        $h .= '<div id="pp_lightbox_content">';
     475        $h .= '<img id="pp_lightboxImg" src="" alt="' . esc_attr($cName) . '">';
     476        $h .= '<span id="pp_lightbox_counter"></span>';
     477        $h .= '</div>';
     478        $h .= '<span id="pp_lightbox_next_btn" role="button" aria-label="Next image">&#10095;</span>';
     479        $h .= '</div>';
    472480   
    473481
  • petpress/trunk/pp-Options.php

    r3438868 r3443682  
    2323        return $new_value;
    2424    }
    25 /*
    26     public function setFastRefreshOption($new_value, $old_value, $option) {
    27 
    28         if (($new_value['fastrefresh'] ?? 0) == 1) {
    29             ppUtils::createCronJob("qh");
    30         }
    31         else
    32         {
    33             ppUtils::createCronJob("hh");
    34         }
    35         return $new_value;
    36     }
    37 */
     25
    3826
    3927    public function setRefreshInterval($new_value, $old_value, $option){
     
    10290                data manager to create / modify / delete animal data directly on your website without a separate back-end system.
    10391        <?php
    104             $h = ppUtils::get_last_petpress_error(24);
     92            $h = ppUtils::get_last_petpress_error(24, true);
    10593            echo $h;
    10694        ?>
     
    210198
    211199        add_settings_field(
    212             'apikey',                    // Field ID
    213             'Rescue Groups API Key',                    // Field title
    214             array($this, 'rg_apikey_callback'), // Callback function
    215             'petpress',               // Page slug
    216             'pet_press_pro_rescuegroups_section'   // Section ID
    217         );
    218 
    219 
    220        
     200            'rg_apikey',
     201            'Rescue Groups API Key',
     202            array($this, 'rg_apikey_callback'),
     203            'petpress',
     204            'pet_press_pro_rescuegroups_section'
     205        );
     206
     207        add_settings_field(
     208            'rg_orgid',
     209            'Rescue Groups Organization ID',
     210            array($this, 'rg_orgid_callback'),
     211            'petpress',
     212            'pet_press_pro_rescuegroups_section'
     213        );
     214
    221215        add_settings_section(
    222216            'pet_press_pro_petfinder_section',  // Section ID
     
    479473            'pet_press_pro_main_section'
    480474        );
     475        /*
    481476        add_settings_field(
    482477            'price_Checkbox_Element',
     
    486481            'pet_press_pro_main_section'
    487482        );
     483        */
     484        add_settings_field(
     485    'price_element',
     486    __('', 'petpress'),
     487    [$this, 'cb_price_element_callback'],
     488    'petpress',
     489    'pet_press_pro_main_section'
     490);
    488491
    489492        add_settings_field(
     
    601604    }
    602605
    603         public function rg_apikey_callback() {
     606    public function rg_apikey_callback() {
    604607        $options = get_option('petpress_options');
    605608        ?>
    606609        <input type="text" name="petpress_options[rg_authkey]" style="width:60ch" value="<?php echo isset($options['rg_authkey']) ? esc_attr($options['rg_authkey']) : ''; ?>" />
    607610        <br>This is your RescueGroups.org API key.
     611        <?php
     612    }
     613
     614    public function rg_orgid_callback() {
     615        $options = get_option('petpress_options');
     616        ?>
     617        <input type="text" name="petpress_options[rg_orgid]" style="width:20ch" value="<?php echo isset($options['rg_orgid']) ? esc_attr($options['rg_orgid']) : ''; ?>" />
     618        <br>Your RescueGroups.org Organization ID (numeric). You can find this in your RescueGroups account settings.
    608619        <?php
    609620    }
     
    976987        echo '<label for="onhold_checkbox"> Show "on hold" status information on detail page (note: will not appear if the option "do not show on hold animals" option above is selected.)';
    977988    }
     989    /*
    978990    public function cb_price_element_callback() {
    979991        $setVal = $this->getOption('price');
     
    983995        echo '<label for="price_checkbox"> Show price information on detail page';
    984996    }
     997*/
     998public function cb_price_element_callback() {
     999    $priceVal = $this->getOption('price', 0);
     1000    $labelVal = $this->getOption('price_label', 'Price'); // Default to 'Price'
     1001   
     1002    $options = [
     1003        'Price' => 'Price',
     1004        'Adoption Fee' => 'Adoption Fee'
     1005    ];
     1006   
     1007    echo '<label style="display: flex; align-items: center; gap: 10px;">';
     1008        echo '<input type="checkbox" id="price_checkbox" name="petpress_options[price]" value="1" ';
     1009        echo esc_attr(checked(1, $priceVal, false));
     1010        echo '/> ';
     1011        echo esc_html__('Show price information on detail page, labeled as ', 'petpress');
     1012       
     1013        echo '<select id="price_label" name="petpress_options[price_label]">';
     1014        foreach ($options as $value => $label) {
     1015            printf(
     1016                '<option value="%s" %s>%s</option>',
     1017                esc_attr($value),
     1018                selected($labelVal, $value, false),
     1019                esc_html($label)
     1020            );
     1021        }
     1022        echo '</select>';
     1023    echo '</label>';
     1024}
    9851025
    9861026    public function cb_social_element_callback() {
  • petpress/trunk/pp-PetPoint.php

    r3438868 r3443682  
    5454    if (is_wp_error($outputWS)) {
    5555        error_log('PetPress getRosterXML WP_Error: ' . $outputWS->get_error_message());
    56         return new WP_Error('PetPressError', sprintf(__('PetPress Error: %s(%d) Network error', 'petpress'), __METHOD__, __LINE__), ['status' => 400]);
     56        return new WP_Error(
     57            'PetPressError',
     58                sprintf(
     59                __('PetPress Error: %s(%d) Getting Roster: %s', 'petpress'),
     60                __METHOD__,
     61                __LINE__,
     62                $outputWS->get_error_message()
     63                ),
     64            ['code' => $outputWS->get_error_code()]
     65        );
     66
    5767    }
    5868
     
    240250   
    241251        if ( is_wp_error( $outputWS ) ) {
    242             //throw new ErrorException("Error getting single animal from ". $urlWSComplete ." (LN#" . __LINE__ . "). This may be a temporary connection issue.");
    243252            return new WP_Error('PetPressError', sprintf(__('PetPress Error: %s(%d) Error getting single animal from %s. This may be a temporary connection issue.','petpress'), __METHOD__, __LINE__, $urlWSComplete ), array( 'status' => 400 ));
    244253           
     
    256265        if (is_string($outputWS["body"])){
    257266            if (strpos($outputWS["body"], "502 Bad Gateway") !== false) {
    258 //                throw new ErrorException("There is currently a problem with the PetPoint gateway (502 Bad Gateway) (LN#" . __LINE__ . "). This may be a temporary connection issue.");
    259267                return new WP_Error('PetPressError', sprintf(__('PetPress Error: %s(%d) There is currently a problem with the PetPoint gateway (502 Bad Gateway)','petpress'), __METHOD__, __LINE__ ), array( 'status' => 502 ));
    260268
  • petpress/trunk/pp-RescueGroups.php

    r3438868 r3443682  
    2525    public function getRosterXML()
    2626    {
    27 
    2827        $options = get_option('petpress_options');
    2928
    30         if (is_array($options) && isset($options['rg_authkey'])) {
    31             if (empty($options['rg_authkey'])){
    32                 //throw new ErrorException("Animals First API key is not set in PetPress settings.");
    33                 //
    34                 return new WP_Error(
    35                     'PetPressError', sprintf(__(
    36                     'PetPress Error: %s(%d) Rescue Groups API key is not set in PetPress settings.'
    37                     ,'petpress'), __METHOD__, __LINE__ ), array( 'status' => 400 )
    38                     );
    39             }
    40             $this->apikey = $options['rg_authkey'];
    41             // Use $authkey
    42         }
    43 
    44 //$apiURL = "https://api.rescuegroups.org/http/v2.json"
    45 
    46         $apiUrl = "https://api.rescuegroups.org/v5";
    47 
    48         $apiUrl = "https://api.rescuegroups.org/v5/public/animals/search/available?orgID=54&include=pictures,species&fields[pictures]=large";
    49 
    50         /*
    51         $tokenCh = curl_init($tokenUrl);
    52         curl_setopt($tokenCh, CURLOPT_RETURNTRANSFER, true);
    53         //curl_setopt($tokenCh, CURLOPT_POSTFIELDS, http_build_query($tokenData));
    54 
    55          $tokenResponse = curl_exec($tokenCh);
    56         if (PHP_VERSION_ID < 80000) { curl_close($tokenCh); }
    57 
    58 
    59         $tokenData = json_decode($tokenResponse, true);
    60         //$accessToken = $tokenData['access_token'];
    61 
    62         if (is_null($tokenData))
    63         {
    64             return new WP_Error('PetPressError', sprintf(__('AnimalsFirst Error: %s(%d) Network error', 'petpress'), __METHOD__, __LINE__), ['data' => "Animals First token is null. Possible cause: bad Authentication Key"]);
    65         }
    66 */
    67         // Initialize an array to store all animals
    68         $allAnimals = array();
    69 
    70         // Set the initial page number
    71         $page = 1;
    72 
    73         // Loop through pages until we've retrieved all animals
    74         //do {
    75             // Make API call to get dogs from the specific organization
    76             $pageUrl = $apiUrl . "&page=" . $page;
    77             $ch = curl_init($pageUrl);
    78             curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    79 /*
    80 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    81     'Authorization: ' . 'r5yhqoEU',  // TODO replace
    82     'Content-Type: application/json',
    83 ));
    84 */
    85 
    86 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    87     'Authorization: ' . $this->apikey,
    88     'Content-Type: application/json',
    89 ));
    90 
    91 
    92 
    93 
    94 
    95             //error_log( 'About to execute a call for AnimalsFirst data with URL: ' . $pageUrl );
    96            
    97             $response = curl_exec($ch);
    98             if (PHP_VERSION_ID < 80000) { curl_close($ch); }
    99 
    100 
    101             // Process the response
    102             $data = json_decode($response, true);
    103 
    104         if (is_null($data))
    105         {
    106             return new WP_Error('PetPressError', sprintf(__('RescueGroups Error: %s(%d) Network error', 'petpress'), __METHOD__, __LINE__), ['status' => "Animal data not found", 'message' => $data['message']]);
    107         }
    108 
    109             if (isset($data['errors'])) {
    110             if ($data['errors'][0]['status'] != 200){
    111                 logError("RescueGroups returned an error [{$data['errors'][0]['status']}] | Title: {$data['errors'][0]['title']} | Detail: {$data['errors'][0]['detail']}");
    112                 return new WP_Error('PetPressError', sprintf(__('RescueGroups Error: %s(%d) Network error', 'petpress'), __METHOD__, __LINE__),
    113                         ['status' => $data['errors'][0]['status'],
    114                     'title' => $data['errors'][0]['title'],
    115                     'detail' => $data['errors'][0]['detail']]
    116                     );
    117                 return[];
    118             }
    119 }
    120             if (isset($data['data'])) {
    121                 $allAnimals = array_merge($allAnimals, $data['data']);
    122                 $page++;
    123             }
    124 
    125      //       $allAnimals = $data;
    126 /*
    127             $totalPages = 1;
    128             // Check if we've reached the last page
    129             if (isset($data['pagination']['total_pages'])) {
    130                 $totalPages = $data['pagination']['total_pages'];
    131             }
    132   */           
    133        // } while ($page <= $totalPages);
    134 
    135         // Now $allAnimals contains all the animals from all pages
    136         // You can process or display them as needed
    137 
    138         // Sort the animals alphabetically
    139 
    140         if ( !empty($allAnimals) && is_array($allAnimals) ) {
    141             usort($allAnimals, function($a, $b) {
    142                 return strcmp($a['name'], $b['name']);
    143             });
    144         } else {
    145             return new WP_Error(
    146                 'PetPressError',
    147                 sprintf(__('PetPress Error: %s(%d) No valid animals found','petpress'),
    148                     __METHOD__, __LINE__ ),
    149                 array( 'status' => 400 )
    150             );
    151         }
    152 
    153 
    154         //foreach ($allAnimals as $animal) {
    155         // echo "Name: " . $animal['name'] . "\n";
    156         // echo "Breed: " . $animal['breeds']['primary'] . "\n";
    157         // echo "Status: " . $animal['status'] . "\n\n";
    158         //}
     29        if (!is_array($options) || empty($options['rg_authkey'])) {
     30            return new WP_Error(
     31                'PetPressError',
     32                sprintf(__('PetPress Error: %s(%d) Rescue Groups API key is not set in PetPress settings.', 'petpress'), __METHOD__, __LINE__),
     33                ['status' => 400]
     34            );
     35        }
     36        $this->apikey = $options['rg_authkey'];
     37        $orgId = $options['rg_orgid'] ?? '';
     38
     39        if (empty($orgId)) {
     40            return new WP_Error(
     41                'PetPressError',
     42                sprintf(__('PetPress Error: %s(%d) Rescue Groups Organization ID is not set in PetPress settings.', 'petpress'), __METHOD__, __LINE__),
     43                ['status' => 400]
     44            );
     45        }
     46
     47        // RescueGroups API v5 - fetch animals for specific organization
     48        $apiUrl = 'https://api.rescuegroups.org/v5/public
     49        /orgs/' . urlencode($orgId) . '/animals/search/available?'
     50            . 'include=pictures,videos,species'
     51            . '&fields[animals]=name,breedPrimary,breedSecondary,sex,ageGroup,birthDate,descriptionHtml,createdDate'
     52            . '&fields[pictures]=small,large,original'
     53            . '&fields[videos]=urlEmbed'
     54            . '&limit=250';
     55
     56        $ch = curl_init($apiUrl);
     57        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
     58        curl_setopt($ch, CURLOPT_HTTPHEADER, [
     59            'Authorization: ' . $this->apikey,
     60            'Content-Type: application/vnd.api+json',
     61        ]);
     62
     63        $response = curl_exec($ch);
     64        $curlError = curl_error($ch);
     65        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
     66        curl_close($ch);
     67
     68        if ($curlError) {
     69            return new WP_Error(
     70                'PetPressError',
     71                sprintf(__('RescueGroups Error: %s(%d) Network error', 'petpress'), __METHOD__, __LINE__),
     72                ['status' => 'cURL error', 'message' => $curlError]
     73            );
     74        }
     75
     76        $data = json_decode($response, true);
     77
     78        if (is_null($data)) {
     79            return new WP_Error(
     80                'PetPressError',
     81                sprintf(__('RescueGroups Error: %s(%d) Invalid JSON response', 'petpress'), __METHOD__, __LINE__),
     82                ['status' => 'Parse error', 'httpCode' => $httpCode]
     83            );
     84        }
     85
     86        // Handle API errors
     87        if (isset($data['errors']) && !empty($data['errors'])) {
     88            $error = $data['errors'][0];
     89            return new WP_Error(
     90                'PetPressError',
     91                sprintf(__('RescueGroups Error: %s(%d) API error', 'petpress'), __METHOD__, __LINE__),
     92                [
     93                    'status' => $error['status'] ?? $httpCode,
     94                    'title' => $error['title'] ?? 'Unknown error',
     95                    'detail' => $error['detail'] ?? '',
     96                ]
     97            );
     98        }
     99
     100        if (!isset($data['data']) || empty($data['data'])) {
     101            return new WP_Error(
     102                'PetPressError',
     103                sprintf(__('PetPress Error: %s(%d) No animals found', 'petpress'), __METHOD__, __LINE__),
     104                ['status' => 400]
     105            );
     106        }
     107
     108        // Build lookup maps for included resources (pictures, videos, species)
     109        $picturesMap = [];
     110        $videosMap = [];
     111        $speciesMap = [];
     112
     113        if (isset($data['included']) && is_array($data['included'])) {
     114            foreach ($data['included'] as $resource) {
     115                $type = $resource['type'] ?? null;
     116                $id = $resource['id'] ?? null;
     117                if (!$type || !$id) {
     118                    continue;
     119                }
     120
     121                $attrs = $resource['attributes'] ?? [];
     122
     123                if ($type === 'pictures') {
     124                    $picturesMap[$id] = [
     125                        'small' => $attrs['small']['url'] ?? null,
     126                        'large' => $attrs['large']['url'] ?? null,
     127                        'original' => $attrs['original']['url'] ?? null,
     128                    ];
     129                } elseif ($type === 'videos') {
     130                    $videosMap[$id] = [
     131                        'urlEmbed' => $attrs['urlEmbed'] ?? null,
     132                    ];
     133                } elseif ($type === 'species') {
     134                    $speciesMap[$id] = $attrs['singular'] ?? $attrs['plural'] ?? 'Unknown';
     135                }
     136            }
     137        }
     138
     139        // Process animals and attach related data
     140        $allAnimals = [];
     141        foreach ($data['data'] as $animal) {
     142            $attrs = $animal['attributes'] ?? [];
     143            $relationships = $animal['relationships'] ?? [];
     144
     145            // Map pictures
     146            $pictures = [];
     147            if (isset($relationships['pictures']['data'])) {
     148                foreach ($relationships['pictures']['data'] as $picRef) {
     149                    $picId = $picRef['id'] ?? null;
     150                    if ($picId && isset($picturesMap[$picId])) {
     151                        $pictures[] = $picturesMap[$picId];
     152                    }
     153                }
     154            }
     155
     156            // Map videos
     157            $videos = [];
     158            if (isset($relationships['videos']['data'])) {
     159                foreach ($relationships['videos']['data'] as $vidRef) {
     160                    $vidId = $vidRef['id'] ?? null;
     161                    if ($vidId && isset($videosMap[$vidId])) {
     162                        $videos[] = $videosMap[$vidId];
     163                    }
     164                }
     165            }
     166
     167            // Map species
     168            $species = 'Unknown';
     169            if (isset($relationships['species']['data'][0]['id'])) {
     170                $speciesId = $relationships['species']['data'][0]['id'];
     171                $species = $speciesMap[$speciesId] ?? 'Unknown';
     172            }
     173
     174            $allAnimals[] = [
     175                'id' => $animal['id'],
     176                'attributes' => [
     177                    'name' => $attrs['name'] ?? '',
     178                    'breedPrimary' => $attrs['breedPrimary'] ?? '',
     179                    'breedSecondary' => $attrs['breedSecondary'] ?? '',
     180                    'species' => $species,
     181                    'sex' => $attrs['sex'] ?? '',
     182                    'ageGroup' => $attrs['ageGroup'] ?? '',
     183                    'birthDate' => $attrs['birthDate'] ?? '',
     184                    'descriptionHTML' => $attrs['descriptionHtml'] ?? '',
     185                    'createdDate' => $attrs['createdDate'] ?? '',
     186                ],
     187                '_pictures' => $pictures,
     188                '_videos' => $videos,
     189            ];
     190        }
     191
     192        if (empty($allAnimals)) {
     193            return new WP_Error(
     194                'PetPressError',
     195                sprintf(__('PetPress Error: %s(%d) No valid animals found', 'petpress'), __METHOD__, __LINE__),
     196                ['status' => 400]
     197            );
     198        }
     199
     200        // Sort alphabetically by name
     201        usort($allAnimals, function ($a, $b) {
     202            return strcmp($a['attributes']['name'] ?? '', $b['attributes']['name'] ?? '');
     203        });
    159204
    160205        return $allAnimals;
     
    230275        }
    231276    }
    232    
    233 
    234     public function fetchAnimalsFromSource() { // returns array of animals
    235 
     277
     278    public function fetchAnimalsFromSource()
     279    {
    236280        $theRawData = $this->getRosterXML();
    237281
    238         if ( is_wp_error( $theRawData ) ) {
     282        if (is_wp_error($theRawData)) {
    239283            return $theRawData;
    240284        }
    241285
    242 
    243         $roster = array();
    244         $rosterIDs = array();
    245 
    246         foreach ($theRawData as $anAnimal){
     286        $roster = [];
     287
     288        foreach ($theRawData as $anAnimal) {
    247289            $critter = new ppAnimal();
    248             $critter->set_id($anAnimal["id"]);
    249 
    250 //            $critter->set_adoptionapplicationurl($anAnimal["url"]);
    251 
    252             $attrs = $anAnimal["attributes"];
    253 
    254             if (isset($attrs["birthDate"])) { $critter->set_age($critter->get_age_in_months($attrs["birthDate"])); }
    255 
    256             //$critter->set_agegroup($anAnimal["age"]);
    257             /*
    258             if ($anAnimal["attributes"]["spayed_neutered"] == 1)
    259             {
    260                 $critter->set_altered("Yes");
    261             }
    262             elseif ($anAnimal["attributes"]["spayed_neutered"] == 0){
    263                 $critter->set_altered("No");
    264             }
    265             else{
    266                 $critter->set_altered("Unknown or unspecified");
    267             }*/
    268             //$critter->set_altered($anAnimal["attributes"]["spayed_neutered"]);
    269 
    270 
    271             if (isset($attrs["breedPrimary"])) { $critter->set_breed($attrs["breedPrimary"]); }
    272             if (isset($attrs["breedSecondary"])) { $critter->set_secondarybreed($attrs["breedSecondary"]); }
    273            
    274             //if (isset($anAnimal["shelter_id"])) { $critter->set_companyid($anAnimal["shelter_id"]); }
    275             //if (isset($anAnimal["attributes"]["house_trained"])) { $critter->set_housetrained($anAnimal["attributes"]["house_trained"]); }
    276            
    277 if (isset($attrs["createdDate"])) { $critter->set_lastintakedate($attrs["createdDate"]); }
    278 
    279             if (isset($attrs["name"])) { $critter->set_name($attrs["name"]); }
    280 
    281             if (isset($attrs["descriptionHTML"])) {$critter->set_memo($attrs["descriptionHTML"]); }
    282             /*
    283             if (isset($anAnimal["coat"])) { $critter->set_coatlength($anAnimal["coat"]); }
    284             if (isset($anAnimal["declawed"])) {$critter->set_declawed($anAnimal["declawed"]); }
    285 */
    286       //      if (isset($anAnimal["secondary_breed"])) {$critter->set_secondarybreed($anAnimal["secondary_breed"]); }
    287 
    288             //if (isset($anAnimal["secondary_color"])) {$critter->set_secondarycolor($anAnimal["secondary_color"]); }
    289             if (isset($attrs["sex"])) {$critter->set_sex($attrs["sex"]);}
    290 
    291             //if (isset($anAnimal["adoption_fee"])) {$critter->set_price($anAnimal["adoption_fee"]);}
    292 
    293             //if (isset($anAnimal["size"])) {$critter->set_size($anAnimal["size"]);}
    294 
    295             //if (isset($anAnimal["status"])) {$critter->set_stage($anAnimal["status"]);}
    296 /*
    297             if (isset($anAnimal["shots_current"])) {$critter->set_shotscurrent($anAnimal["shots_current"]);}
    298             if (isset($anAnimal["attributes"]["special_needs"])) {$critter->set_specialneeds($anAnimal["attributes"]["special_needs"]);}
    299             */
    300             //if (isset($anAnimal["species"])) {$critter->set_species("Dog");} // TODO
    301            
    302 if (isset($anAnimal['relationships']['species']['data'][0]['id'])) {
    303     switch ($anAnimal['relationships']['species']['data'][0]['id'])
    304     {
    305         case 3:
    306             $theSpecies = "Cat";
    307             break;
    308         case 8:
    309             $theSpecies = "Dog";
    310             break;
    311         case 23:
    312             $theSpecies = "Horse";
    313             break;
    314         case 32:
    315             $theSpecies = "Rabbit";
    316             break;
    317         default:
    318             $theSpecies = $anAnimal['relationships']['species']['data'][0]['id'];
    319             break;
    320            
    321     }   
    322     $critter->set_species($theSpecies);
    323 }
    324 else
    325 {
    326     $critter->set_species("Unknown");
    327 }
    328 
    329             //if (isset($anAnimal["af"])) {$critter->set_datasource("af");}
    330             $critter->set_photo1($attrs["pictureThumbnailUrl"]);
    331             //$critter->set_photo2($anAnimal["photo_url_2"]);
    332             //$critter->set_photo3($anAnimal["photo_url_3"]);
    333 
    334             //if (isset($anAnimal["primary_color"])) {$critter->set_primarycolor($anAnimal["primary_color"]);}
    335 
    336             $critter->set_systemofrecord("RescueGroups");
    337 /*
    338             if (isset($anAnimal["videos"][0]["embed"])) {$critter->set_videoid($anAnimal["videos"][0]["embed"]);}
    339             */
    340 
    341             //if (isset($anAnimal["weight"])) {$critter->set_weight($anAnimal["weight"]);}
    342 
    343             $roster[]=$critter;
    344         }
    345 
    346         return [ 'roster' => $roster ];
    347 
     290            $attrs = $anAnimal['attributes'] ?? [];
     291
     292            $critter->set_id($anAnimal['id']);
     293            $critter->set_systemofrecord('RescueGroups');
     294
     295            if (isset($attrs['name'])) {
     296                $critter->set_name($attrs['name']);
     297            }
     298            if (!empty($attrs['birthDate'])) {
     299                $critter->set_age($critter->get_age_in_months($attrs['birthDate']));
     300            }
     301            if (!empty($attrs['ageGroup'])) {
     302                $critter->set_agegroup($attrs['ageGroup']);
     303            }
     304            if (isset($attrs['breedPrimary'])) {
     305                $critter->set_breed($attrs['breedPrimary']);
     306            }
     307            if (isset($attrs['breedSecondary'])) {
     308                $critter->set_secondarybreed($attrs['breedSecondary']);
     309            }
     310            if (isset($attrs['sex'])) {
     311                $critter->set_sex($attrs['sex']);
     312            }
     313            if (isset($attrs['descriptionHTML'])) {
     314                $critter->set_memo($attrs['descriptionHTML']);
     315            }
     316            if (isset($attrs['createdDate'])) {
     317                $critter->set_lastintakedate($attrs['createdDate']);
     318            }
     319
     320            // Species is mapped from included resources in getRosterXML
     321            $critter->set_species($attrs['species'] ?? 'Unknown');
     322
     323            // Set photos (up to 3)
     324            if (!empty($anAnimal['_pictures'])) {
     325                $photoSetters = ['set_photo1', 'set_photo2', 'set_photo3'];
     326                $photoIndex = 0;
     327                foreach ($anAnimal['_pictures'] as $pic) {
     328                    $photoUrl = $pic['large'] ?? $pic['original'] ?? $pic['small'] ?? null;
     329                    if ($photoUrl && $photoIndex < 3) {
     330                        $critter->{$photoSetters[$photoIndex]}($photoUrl);
     331                        $photoIndex++;
     332                    }
     333                }
     334            } elseif (isset($attrs['pictureThumbnailUrl'])) {
     335                $critter->set_photo1($attrs['pictureThumbnailUrl']);
     336            }
     337
     338            // Set video
     339            if (!empty($anAnimal['_videos'][0]['urlEmbed'])) {
     340                $critter->set_videoid($anAnimal['_videos'][0]['urlEmbed']);
     341            }
     342
     343            $roster[] = $critter;
     344        }
     345
     346        return ['roster' => $roster];
    348347    }
    349348
  • petpress/trunk/pp-Roster.php

    r3438868 r3443682  
    8888
    8989        $h .= '<div id="pp_sort_btn_container">'; // D1
    90         $h .= '<button class="pp_sort_btn pp_button" id="sortNameBTN" onClick="sortTiles(\'data-name\')">Name <span class="dashicons dashicons-arrow-down-alt"></span></button>';
     90        //$h .= '<button class="pp_sort_btn pp_button" id="sortNameBTN" onClick="sortTiles(\'data-name\')">Name</button>';
     91        $h .= '<button class="pp_sort_btn pp_button pp_sort_active pp_sort_desc" id="sortNameBTN" onClick="sortTiles(\'data-name\')">Name</button>'; 
    9192   
    9293        $optSizeWeight = (ppUtils::optionChecked("sizeweight") && ($dataSource != "PetFinder"));
     
    101102   
    102103        if (!ppUtils::optionChecked("sizeweight") || ($dataSource != "PetPoint")){
    103             $h .= '<button class="pp_sort_btn pp_button" id="sortAgeBTN" onClick="sortTiles(\'data-agegroupindex\')">Age <span class="dashicons"></span></button>';
    104             $h .= '<button class="pp_sort_btn pp_button" id="sortSizeBTN" onClick="sortTiles(\'data-sizeindex\')">Size <span class="dashicons"></span></button>';
     104            $h .= '<button class="pp_sort_btn pp_button" id="sortAgeBTN" onClick="sortTiles(\'data-agegroupindex\')">Age</button>';
     105            $h .= '<button class="pp_sort_btn pp_button" id="sortSizeBTN" onClick="sortTiles(\'data-sizeindex\')">Size</button>';
    105106        }
    106107        else{ // specific value checkbox is checked and datasource is PetPoint
    107             $h .= '<button class="pp_sort_btn pp_button" id="sortAgeBTN" onClick="sortTiles(\'data-age\')">Age <span class="dashicons"></span></button>';
    108             $h .= '<button class="pp_sort_btn pp_button" id="sortSizeBTN" onClick="sortTiles(\'data-weight\')">Size <span class="dashicons"></span></button>';
     108            $h .= '<button class="pp_sort_btn pp_button" id="sortAgeBTN" onClick="sortTiles(\'data-age\')">Age</button>';
     109            $h .= '<button class="pp_sort_btn pp_button" id="sortSizeBTN" onClick="sortTiles(\'data-weight\')">Size</button>';
    109110        }
    110111        if (($optDaysIn) && ($dataSource != "PetFinder")):
    111             $h .= '<button class="pp_sort_btn pp_button" id="sortDaysInBTN" onClick="sortTiles(\'data-daysin\')">Days In <span class="dashicons"></span></button>';
     112            $h .= '<button class="pp_sort_btn pp_button" id="sortDaysInBTN" onClick="sortTiles(\'data-daysin\')">Days In</button>';
    112113        endif;
    113114        $h .= '</div>'; // pp_sort_btn_container d1
     
    248249                }
    249250   
    250                 $vitals = [];
    251                
    252                 if ($optSizeWeight) { // todo set the option checked outside of the loop
    253                     if (($critter->get_age()) != "" && ($critter->get_age() > 0)){
    254                         $vitals[] = ppUtils::approximateAge($critter->get_age());
    255                     }
    256                     elseif (($critter->get_age()) != "" && ($critter->get_age() == 0)){
    257                         $vitals[] = $critter->get_agegroup();
    258                     }
    259                     if ($critter->get_sex() != ""){
    260                         $vitals[] = strtolower($critter->get_sex());
    261                     }
    262                     if
    263                     (($cWeight) != "" && ($cWeight > 0)) {
    264                         if ($dataSource == "PetFinder") {
    265                             $vitals[] = $critter->get_size();
     251                $vitals = ['age' => '', 'sex' => '', 'sizeweight' => ''];
     252
     253                $sex = $critter->get_sex();
     254                $age = $critter->get_age();
     255
     256                if ($optSizeWeight) {
     257                    if (!empty($age) && $age > 0) {
     258                        $vitals['age'] = ppUtils::approximateAge($age);
     259                    } elseif ($age !== null && $age == 0) {
     260                        $vitals['age'] = $critter->get_agegroup() ?? '';
     261                    }
     262                    if (!empty($sex)) {
     263                        $vitals['sex'] = strtolower($sex);
     264                    }
     265                    if (!empty($cWeight) && $cWeight > 0) {
     266                        if ($dataSource == 'PetFinder') {
     267                            $vitals['sizeweight'] = $critter->get_size() ?? '';
     268                        } else {
     269                            $vitals['sizeweight'] = ppUtils::shortenWeightUnit($critter->get_weight());
    266270                        }
    267                         else
    268                         {
    269                             $vitals[] = ppUtils::shortenWeightUnit($critter->get_weight());
    270                         }
    271                     }
    272                 }
    273                 else {
    274                     if ($cAgegroup != "") {
    275                         $vitals[] = strtolower($cAgegroup);
    276                     }
    277                     if ($critter->get_sex() != "") {
    278                         $vitals[] = strtolower($critter->get_sex());
    279                     }
    280                     if (($dataSource == "PetFinder") || ($dataSource == "AnimalsFirst")) {
    281                         $vitals[] = $critter->get_size();
    282                     }
    283                     else
    284                     {
    285                     if ($weightclass != "") {
    286                         $vitals[] = $weightclass;
    287                     }
    288                 }
     271                    }
     272                } else {
     273                    if (!empty($cAgegroup)) {
     274                        $vitals['age'] = strtolower($cAgegroup);
     275                    }
     276                    if (!empty($sex)) {
     277                        $vitals['sex'] = strtolower($sex);
     278                    }
     279                    if ($dataSource == 'PetFinder' || $dataSource == 'AnimalsFirst') {
     280                        $vitals['sizeweight'] = $critter->get_size() ?? '';
     281                    } elseif (!empty($weightclass)) {
     282                        $vitals['sizeweight'] = $weightclass;
     283                    }
    289284                }
    290285   
     
    305300   
    306301   
    307                 $age = $vitals[0] ?? '';
    308                 $h .= "<span class='ppListItemAge'><b>Age:</b> {$age}<br></span>";
    309 
    310                 $sex = $vitals[1] ?? '';
    311                 $h .= "<span class='ppListItemAge'><b>Sex:</b> {$sex}<br></span>";
    312 
    313                 $h .= "<span class='ppListItemSizeWeight'>";
    314 
    315                 $value = $vitals[2] ?? '';
    316 
    317                 if (!empty($value)) {
     302                if (!empty($vitals['age'])) {
     303                    $h .= "<span class='ppListItemAge'><b>Age:</b> {$vitals['age']}<br></span>";
     304                }
     305
     306                if (!empty($vitals['sex'])) {
     307                    $h .= "<span class='ppListItemAge'><b>Sex:</b> {$vitals['sex']}<br></span>";
     308                }
     309
     310                if (!empty($vitals['sizeweight'])) {
     311                    $h .= "<span class='ppListItemSizeWeight'>";
    318312                    if ($optSizeWeight) {
    319                         $h .= "<b>Weight:</b> {$value}<br>";
     313                        $h .= "<b>Weight:</b> {$vitals['sizeweight']}<br>";
    320314                    } else {
    321                         $h .= "<b>Size:</b> {$value}<br>";
    322                     }
    323                 }
    324 
    325                 $h .= "</span>";
    326                
     315                        $h .= "<b>Size:</b> {$vitals['sizeweight']}<br>";
     316                    }
     317                    $h .= "</span>";
     318                }
     319
    327320                if ($optDaysIn) {
    328321                    $daysIn = $critter->get_daysin();
     
    380373        }
    381374
    382         $h .= "<!-- LAST SERVER ERROR (1 WEEK)";
    383         $h .= ppUtils::get_last_petpress_error(168);
    384         $h .= " LAST SERVER ERROR -->";
    385 
    386 if (!PP_PETPRESS_DEBUG) { $h .= "<!--"; }
    387         $h .="This page generated at " . date('Y-m-d H:i:s') . " (server time zone: " . date_default_timezone_get() . "). If older than the current time, the page may be cached.\n";
     375        $h .= "\n<!-- PetPress Diagnostics BEGIN -->";
     376        $h .= "\n<!-- Last Server Error Data BEGIN (1 WEEK): ";
     377        $errString = ppUtils::get_last_petpress_error(168, false);
     378        if (empty($errString)) {
     379            $errString = "[no errors in the last week]";
     380        }
     381        $h .= $errString;
     382        $h .= "\nLast Server Error Data END -->";
     383
     384if (!PP_PETPRESS_DEBUG) { $h .= "\n<!--"; }
     385        $h .="This page generated at " . date('Y-m-d H:i:s') . " (server time zone: " . date_default_timezone_get() . "). If that is not the current time, the page may be cached.\n";
    388386        $h .="PetPress-fetch: The last fetch from the data source was " . ppUtils::getFetchTime() .", though if the page is cached (see above) then this information may be out-of-date.\n";
     387        $h .= ppUtils::nextPetpressCronUTC() . "\n";
     388        $cron_error = get_option( 'petpress_cron_schedule_error' );
     389        if ( ! empty( $cron_error ) && is_array( $cron_error ) ) {
     390            $h .= "PetPress-cron-error: " . esc_html( $cron_error['message'] ) . " (at " . esc_html( $cron_error['time'] ) . ")\n";
     391        }
    389392if (!PP_PETPRESS_DEBUG) { $h .= "-->"; }
    390 
     393$h .= "<!-- PetPress Diagnostics END -->";
    391394       
    392395
  • petpress/trunk/pp-Utilities.php

    r3438868 r3443682  
    2525    }
    2626   
    27     public static function setFetchTime() {
    28         $current_time = current_time('mysql');
    29         update_option('petpress_last_fetch', $current_time);
    30 
    31     }
     27public static function setFetchTime() {
     28    // Get current time as Unix timestamp in UTC
     29    $timestamp_utc = current_time( 'timestamp', true );
     30
     31    // Format as MySQL datetime in UTC
     32    $current_time = gmdate( 'Y-m-d H:i:s', $timestamp_utc );
     33
     34    update_option( 'petpress_last_fetch', $current_time . " UTC");
     35}
     36
    3237
    3338    public static function getFetchTime() {
     
    4550        return ("");
    4651    }
     52
     53public static function nextPetpressCronUTC() {
     54    $hook = 'petpress_cron';
     55    $next = wp_next_scheduled( $hook );
     56    if ( ! $next ) {
     57        return 'No wp-cron job scheduled for "' . esc_html( $hook ) . '". This is normal if using the PetPress Data Manager.';
     58    }
     59    $datetime_utc = gmdate( 'Y-m-d H:i:s', $next ) . ' UTC';
     60    return 'The next fetch of data is scheduled for ' . esc_html( $datetime_utc );
     61}
    4762
    4863    public static function getDataSource()
     
    362377        return $theDBTime;
    363378    }
    364 /*
    365 static public function createCronJob($intervalIN, $theDataSource) {
    366     $interval = in_array($intervalIN, ['15m', '10m', '05m']) ? $intervalIN : '30m';
    367 
    368     foreach (['petpress_cron','petpress_cron_dog_data_load', 'petpress_cron_cat_data_load', 'petpress_cron_other_data_load','petpress_cron_dog', 'petpress_cron_cat', 'petpress_cron_other'] as $legacy_hook) {
    369         while ($event = wp_get_scheduled_event($legacy_hook)) {
    370             wp_unschedule_event($event->timestamp, $legacy_hook);
    371         }
    372     }
    373 
    374 if (isset($theDataSource) && $theDataSource !== 'PetPressDM') {
    375    
    376     $result = wp_schedule_event( time(), $interval, 'petpress_cron' );
    377 
    378     if ( is_wp_error( $result ) ) {
    379         error_log( 'PetPress cron scheduling failed: ' . $result->get_error_message() );
    380     }
    381 
    382 }
    383 
    384 }
    385 */
    386 
    387 static public function createCronJob( $intervalIN, $theDataSource ) {
    388     $interval = in_array( $intervalIN, [ '15m', '10m', '05m' ], true ) ? $intervalIN : '30m';
    389 
    390     foreach ( [ 'petpress_cron', 'petpress_cron_dog_data_load', 'petpress_cron_cat_data_load', 'petpress_cron_other_data_load', 'petpress_cron_dog', 'petpress_cron_cat', 'petpress_cron_other' ] as $legacy_hook ) {
    391         while ( $event = wp_get_scheduled_event( $legacy_hook ) ) {
    392             wp_unschedule_event( $event->timestamp, $legacy_hook );
    393         }
    394     }
    395 
    396     if ( isset( $theDataSource ) && $theDataSource !== 'PetPressDM' ) {
    397 
    398         $max_attempts = 5;
    399         $attempt      = 0;
    400         $hook         = 'petpress_cron';
    401         $timestamp    = time();
    402 
    403         // Do not create duplicates.
     379
     380
     381    static public function createCronJob( $intervalIN, $theDataSource ) {
     382        $interval = in_array( $intervalIN, [ '15m', '10m', '05m' ], true ) ? $intervalIN : '30m';
     383        $hook     = 'petpress_cron';
     384
     385        // Store desired settings for retry mechanism
     386        update_option( 'petpress_cron_settings', [
     387            'interval'    => $interval,
     388            'datasource'  => $theDataSource,
     389        ], false );
     390
     391        // Use a transient lock to prevent race conditions
     392        $lock_key = 'petpress_cron_scheduling';
     393        if ( get_transient( $lock_key ) ) {
     394            return; // Another process is scheduling
     395        }
     396        set_transient( $lock_key, true, 30 ); // 30 second lock
     397
     398        try {
     399            // Clear all legacy hooks
     400            foreach ( [ 'petpress_cron', 'petpress_cron_dog_data_load', 'petpress_cron_cat_data_load', 'petpress_cron_other_data_load', 'petpress_cron_dog', 'petpress_cron_cat', 'petpress_cron_other' ] as $legacy_hook ) {
     401                while ( $event = wp_get_scheduled_event( $legacy_hook ) ) {
     402                    wp_unschedule_event( $event->timestamp, $legacy_hook );
     403                }
     404            }
     405
     406            if ( ! isset( $theDataSource ) || $theDataSource === 'PetPressDM' ) {
     407                delete_transient( $lock_key );
     408                delete_option( 'petpress_cron_settings' );
     409                return;
     410            }
     411
     412            // Check again after clearing (another process may have scheduled)
     413            if ( wp_next_scheduled( $hook ) ) {
     414                delete_transient( $lock_key );
     415                delete_option( 'petpress_cron_schedule_error' );
     416                return;
     417            }
     418
     419            // Schedule the event
     420            $result = wp_schedule_event( time(), $interval, $hook, [], true );
     421
     422            if ( is_wp_error( $result ) || ! $result ) {
     423                $message = is_wp_error( $result ) ? $result->get_error_message() : 'wp_schedule_event returned false';
     424                error_log( 'PetPress cron scheduling failed: ' . $message );
     425
     426                // Store error for admin notice
     427                update_option( 'petpress_cron_schedule_error', [
     428                    'message' => $message,
     429                    'time'    => current_time( 'mysql' ),
     430                ], false );
     431
     432                // Schedule a retry attempt
     433                self::scheduleRetry();
     434            } else {
     435                // Success - clear any previous error
     436                delete_option( 'petpress_cron_schedule_error' );
     437
     438                if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
     439                    error_log( 'PetPress cron scheduled successfully' );
     440                }
     441            }
     442        } finally {
     443            delete_transient( $lock_key );
     444        }
     445    }
     446
     447    /**
     448     * Schedule a retry attempt for cron creation
     449     */
     450    static private function scheduleRetry( $attempt = 1 ) {
     451        $retry_hook = 'petpress_cron_retry';
     452        $max_retries = 5;
     453
     454        if ( $attempt > $max_retries ) {
     455            error_log( "PetPress cron scheduling failed after {$max_retries} retries" );
     456            return;
     457        }
     458
     459        // Don't schedule if already scheduled
     460        if ( wp_next_scheduled( $retry_hook ) ) {
     461            return;
     462        }
     463
     464        // Store the attempt number
     465        update_option( 'petpress_cron_retry_attempt', $attempt, false );
     466
     467        // Try again in 2 minutes
     468        wp_schedule_single_event( time() + 120, $retry_hook );
     469    }
     470
     471    /**
     472     * Retry handler - called by the retry hook
     473     */
     474    static public function retryCronSchedule() {
     475        $settings = get_option( 'petpress_cron_settings' );
     476        if ( ! $settings ) {
     477            return;
     478        }
     479
     480        $hook = 'petpress_cron';
     481        $attempt = (int) get_option( 'petpress_cron_retry_attempt', 1 );
     482
     483        // Already scheduled? We're done
     484        if ( wp_next_scheduled( $hook ) ) {
     485            delete_option( 'petpress_cron_schedule_error' );
     486            delete_option( 'petpress_cron_retry_attempt' );
     487            return;
     488        }
     489
     490        $interval = $settings['interval'] ?? '30m';
     491
     492        // Attempt to schedule
     493        $result = wp_schedule_event( time(), $interval, $hook, [], true );
     494
     495        if ( is_wp_error( $result ) || ! $result ) {
     496            $message = is_wp_error( $result ) ? $result->get_error_message() : 'wp_schedule_event returned false';
     497            error_log( "PetPress cron retry {$attempt} failed: " . $message );
     498
     499            update_option( 'petpress_cron_schedule_error', [
     500                'message' => $message . " (retry {$attempt}/5)",
     501                'time'    => current_time( 'mysql' ),
     502            ], false );
     503
     504            // Schedule another retry
     505            self::scheduleRetry( $attempt + 1 );
     506        } else {
     507            delete_option( 'petpress_cron_schedule_error' );
     508            delete_option( 'petpress_cron_retry_attempt' );
     509            error_log( "PetPress cron scheduled successfully on retry {$attempt}" );
     510        }
     511    }
     512
     513    /**
     514     * Check if cron should be scheduled but isn't, and fix it
     515     * Call this on admin_init or init
     516     */
     517    static public function ensureCronScheduled() {
     518        $settings = get_option( 'petpress_cron_settings' );
     519        if ( ! $settings ) {
     520            return;
     521        }
     522
     523        $datasource = $settings['datasource'] ?? '';
     524        if ( empty( $datasource ) || $datasource === 'PetPressDM' ) {
     525            return;
     526        }
     527
     528        $hook = 'petpress_cron';
     529
     530        // If already scheduled, nothing to do
    404531        if ( wp_next_scheduled( $hook ) ) {
    405532            return;
    406533        }
    407534
    408         do {
    409             $attempt++;
    410 
    411             // Ask for WP_Error on failure (fifth param = true).
    412             $result = wp_schedule_event( $timestamp, $interval, $hook, [], true );
    413 
    414             if ( ! is_wp_error( $result ) && $result ) {
    415                 // Success.
    416                 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
    417                     error_log( "PetPress cron scheduled successfully on attempt {$attempt}" );
    418                 }
    419                 break;
    420             }
    421 
    422             // Failure: log error.
    423             $message = is_wp_error( $result )
    424                 ? $result->get_error_message()
    425                 : 'wp_schedule_event returned false';
    426 
    427             error_log( "PetPress cron scheduling failed (attempt {$attempt}): {$message}" );
    428 
    429             if ( $attempt < $max_attempts ) {
    430                 // Simple backoff: wait a bit and nudge timestamp forward.
    431                 sleep( 2 );
    432                 $timestamp = time() + ( 10 * $attempt );
    433             }
    434 
    435         } while ( $attempt < $max_attempts );
    436     }
    437 }
     535        // Not scheduled but should be - try to schedule
     536        $interval = $settings['interval'] ?? '30m';
     537        $result = wp_schedule_event( time(), $interval, $hook, [], true );
     538
     539        if ( ! is_wp_error( $result ) && $result ) {
     540            delete_option( 'petpress_cron_schedule_error' );
     541            if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
     542                error_log( 'PetPress cron scheduled via ensureCronScheduled' );
     543            }
     544        }
     545    }
    438546
    439547
     
    503611
    504612    public static function petpress_log( $message ) {
    505         // Toggle debugging here
    506613        $debug_enabled = defined( 'PP_PETPRESS_DEBUG' ) && PP_PETPRESS_DEBUG;
    507614
     
    515622    }
    516623
    517     public static function get_last_petpress_error($hoursWindow)
     624    public static function get_last_petpress_error($hoursWindow, $formatForDisplay)
    518625    {
     626
     627        $numDays = round($hoursWindow / 24, 1);  // 1 decimal place
     628// Usage: "{$hoursWindow} hours ({$numDays} days)"
     629
     630
    519631        $error = get_option( 'petpress_last_cron_error' );
    520632
     
    528640
    529641        if ( $diff <= $limit ) {
    530             $h = "<div style='border: 1px solid; padding:1em 2em'>";
    531             $h .= "<h3>Most Recent PetPress Connection Error (within the last " . $hoursWindow ." hours)</h3>";
    532             $h .= "Time: " . date( 'F j, Y \a\t g:i:s A', strtotime( $timestamp )) . " (server time, which is ".date_default_timezone_get().")<br>";
    533             $h .= "Error Code: " . $error['code']. "<br>";
     642            $h = "";
     643            if ($formatForDisplay){
     644                $h = "<div style='border: 1px solid; padding:1em 2em'>";
     645                $h .= "<h3>";
     646            }
     647            $h .= "Most Recent PetPress Connection Error within the last {$hoursWindow} hours ({$numDays} days) \n";
     648            if ($formatForDisplay){
     649                $h .= "</h3>";
     650            }
     651            $h .= "Error Time: " . date( 'F j, Y \a\t g:i:s A', strtotime( $timestamp )) . " (server time, which is ".date_default_timezone_get().")\n";
     652                        if ($formatForDisplay){ $h .= "<br>"; }
     653            $h .= "Error Code: " . $error['code']. "\n";
     654            if ($formatForDisplay){ $h .= "<br>"; }
    534655            $h .= "Error Message: " . $error['msg'];
    535656            $data = $error['data'];
    536657            $errorData = esc_html( print_r( $data, true ) );
    537658            $errorData = preg_replace('/^Array\s*\(|\)\s*$/m', '', trim($errorData));
    538             $h .= '<pre>' . $errorData . '</pre>';
     659            if ($formatForDisplay){ $h .= "<pre>"; }
     660            $h .= $errorData ;
     661            if ($formatForDisplay){ $h .= "</pre>"; }
    539662           
    540             $status = isset( $error['data']['status'] ) ? (int) $error['data']['status'] : 0;
    541             switch ($status)
    542             {
    543                 case 401:
    544                     $h .= "<p>Error '401' indicates that you are not authorized to get the information from the data source. There is likely something wrong with your authorization key / login credentials.</p>";
    545                     break;
    546                 case 403:
    547                     $h .= "<p>Error '403' indicates that the data request was not allowed. It is likely there is something wrong with your authorization key / login credentials.</p>";
    548                     break;
    549                 case 404:
    550                     $h .= "<p>Error '404' indicates that a resource was not found. This may indicate that the back-end service is not functioning correctly.</p>";
    551                     break;
    552                 case 409:
    553                     $h .= "<p>Error '409' indicates that the data source is rejecting requests from PetPress because they are too frequent. You need to set the data refresh option below to a longer interval. If you have multiple websites pulling data from the datasource, be aware that the rate limit for the data source applies to all sites and applicaitons combined, not just this instance.</p>";
    554                     break;
    555                 case 500:
    556                     $h .= "<p>Error '500' is a generic response indicating some kind of server-level error of the data source.</p>";
    557                     break;
    558                 case 502:
    559                     $h .= "<p>Error '502' indicates a gateway problem -- some element of the network between this web server and the data source. This is likely a temporary condition.</p>";
    560                     break;
    561                 case 503:
    562                     $h .= "<p>Error '503' indicates that the data source service is not available. This is likely a temporary condition.</p>";
    563                     break;
    564                 case 504:
    565                     $h .= "<p>Error '504' indicates a gateway timeout -- some element of the network between this web server and the data source did not get a timely response.</p>";
    566                     break;
    567 
    568                 }
    569 
    570             $h .= "<p>NB: <i>This error will be shown here for " .$hoursWindow." hours after the event even if the underlying issue is resolved.</i></p>";
    571 
    572             $h .= "</div>";
     663            $status = isset($error['data']['status']) ? (int)$error['data']['status'] : 0;
     664
     665$messages = [
     666    401 => "Error '401': Not authorized. Check your authorization key/login credentials.",
     667    403 => "Error '403': Request not allowed. Verify your authorization key.",
     668    404 => "Error '404': Resource not found. Data source may be down.",
     669    409 => "Error '409': Rate limit exceeded. Increase data refresh interval. Rate limit applies across all sites/apps.",
     670    500 => "Error '500': Data source server error.",
     671    502 => "Error '502': Gateway issue. Temporary network problem.",
     672    503 => "Error '503': Data source unavailable. Temporary condition.",
     673    504 => "Error '504': Gateway timeout. Network delay issue."
     674];
     675
     676if (isset($messages[$status])) {
     677    $h .= $formatForDisplay ? "<p>" . $messages[$status] . "</p>" : $messages[$status];
     678} else {
     679    $h .= $formatForDisplay ? "<p>Unknown error '{$status}'</p>" : "Unknown error '{$status}'";
     680}
     681
     682// NB message (consistent formatting)
     683$h .= $formatForDisplay
     684    ? "\n<p>NB: <i>This error will be reported for {$hoursWindow} hours ({$numDays} days) after the event even if the underlying issue is resolved.</i></p></div>\n"
     685    : "\nNB: This error will be reported for {$hoursWindow} hours ({$numDays} days) after the event even if the underlying issue is resolved.</div>\n";
    573686            return $h;
    574687        }
  • petpress/trunk/readme.txt

    r3438891 r3443682  
    55Requires at least: 5.7
    66Tested up to: 6.9
    7 Stable tag: 2.2
     7Stable tag: 2.2.1
    88Requires PHP: 7.4
    99License: GPL v2 or later
     
    3030
    3131== Changelog ==
     32Version 2.2.1
     331) When Price is shown in the detail page, allow for selection of "Price" or "Adoption Fee" as the label.
     342) Cron job scheduling more robust, with locking and re-try
     353) Sorting buttons on roster page updated look (better for phones)
     364) Cleaned up lightbox effect slightly, images appear larger (if screen size permits) and fewer issues with z-order
     37
    3238
    3339Version 2.2
Note: See TracChangeset for help on using the changeset viewer.