DarkSky API Error in Snippet

A while back those on the forums helped me put together a snippet for retrieving weather information from DarkSky, and it worked perfectly. However, now I had to move it to a local Apache install on a Raspberry Pi, instead of just a hidden page on the public website. Now I am getting an error that it can not connect to DarkSky and the snippet doesn’t function. I’m thinking this has more to do with settings on Apache/Pi than anything else, since the code has not changed at all and the url used for retrieving the weather data works fine in my browser. Does anyone have any ideas what may be causing this? Thanks!

Errors:

[2020-10-19 20:50:41] (ERROR @ /var/www/html/core/cache/includes/elements/modsnippet/1.include.cache.php : 11) PHP warning: Invalid argument supplied for foreach()
[2020-10-19 20:55:43] (ERROR @ /var/www/html/core/cache/includes/elements/modsnippet/1.include.cache.php : 5) PHP warning: file_get_contents(https://api.darksky.net/forecast//,): failed to open stream: HTTP request failed! HTTP/1.1 400 Bad Request

Snippet

<?php
$output = [];
$coordinates = $lat . ',' . $lng;
$api_url = 'https://api.darksky.net/forecast/' . $api . '/' . $coordinates;
$forecast = json_decode(file_get_contents($api_url));
//Set timezone based on location searched
date_default_timezone_set($forecast->timezone);
//Set counter at zero
$i = 0;
// Start the foreach loop to get hourly forecast
foreach ($forecast->hourly->data as $hour) {
    $output[] = $modx->getChunk($tpl, [
        'hour' => date('g:i a', $hour->time),
        'temp' => round($hour->temperature),
        'humidity' => $hour->humidity * 100,
        'windspeed' => round($hour->windSpeed),
        'summary' => $hour->summary,
        'icon' => $hour->icon,
        'precipIntensity' => $hour->precipIntensity,
        'precipProbability' => $hour->precipProbability,
        'dewPoint' => $hour->dewPoint,
        'preassure' => $hour->preassure,
        'windGust' => $hour->windGust,
        'windBearing' => $hour->windbearing,
        'cloudCover' => $hour->cloudCover,
        'uvIndex' => $hour->uvIndex,
        'visibility' => $hour->visibility,
        'ozone' => $hour->ozone
    ]);
    // Increase counter by one for each iteration
    $i++;
    //Stop after set number of iterations
    if ($i == $hours) {
        break;
    }
}

The 400 bad request response code suggests it does make it to the API, but that the request is invalid. When moving it to your Pi, perhaps the $api/$coordinates no longer add up and the generated url is incorrect?

That seems odd, how would that affect the PHP? I would think the URL wouldn’t have changed at all between systems, it’s not like the syntax would.

This part of the error

file_get_contents(https://api.darksky.net/forecast//,)

would suggest that the variables $api, $lat and $lng are all empty.
How do you call this snippet?

Btw, it looks like you had the same issue back in january.

What I was thinking is that your move to the Pi may not have been a 100% clone, and perhaps a typo or missing content snuck into your snippet call (or where you’re fetching the lat/lng/api from) causing it to not generate the API URL properly.

Here is the snippet call I used. I did a copy and paste, not a full clone of the old site, to save myself the complexity. But here is pretty much everything I constructed.

The primary snippet call uses variables from a MIGX tv (JSON at bottom):

[[!getImageList?
     &tvname=`ds_slides`
     &tpl=`digitalSignageTpl`
]]

digitalSignageTpl:

[[!darkSkyWeatherHourly?
     &api=`[[+weather_api]]`
     &lat=`[[+weather_lat]]`
     &lng=`[[+weather_lng]]`
     &hours=`4`
     &tpl=`weatherHourlyTpl`
]]

weatherHourlyTpl:

<div class="col-3 text-center">
    <div class="px-3 mb-5 mx-4" style="position:relative;top:0;">
        [[+icon:is=`clear-day`:then=`<img src="[[++assets_url]]img/weather/sun.png" class="img-fluid">`:else=``]]
        [[+icon:is=`partly-cloudy-day`:then=`<img src="[[++assets_url]]img/weather/part_sun.png" class="img-fluid">`:else=``]]
        [[+icon:is=`clear-night`:then=`<img src="[[++assets_url]]img/weather/moon.png" class="img-fluid">`:else=``]]
        [[+icon:is=`partly-cloudy-night`:then=`<img src="[[++assets_url]]img/weather/part_moon.png" class="img-fluid">`:else=``]]
        [[+icon:is=`rain`:then=`<img src="[[++assets_url]]img/weather/rain.png" class="img-fluid">`:else=``]]
        [[+icon:is=`drizzle`:then=`<img src="[[++assets_url]]img/weather/drizzle.png" class="img-fluid">`:else=``]]
        [[+icon:is=`snow`:then=`<img src="[[++assets_url]]img/weather/snow.png" class="img-fluid">`:else=``]]
        [[+icon:is=`sleet`:then=`<img src="[[++assets_url]]img/weather/sleet.png" class="img-fluid">`:else=``]]
        [[+icon:is=`cloudy`:then=`<img src="[[++assets_url]]img/weather/cloud.png" class="img-fluid">`:else=``]]
        [[+icon:is=`hail`:then=`<img src="[[++assets_url]]img/weather/hail.png" class="img-fluid">`:else=``]]
        [[+icon:is=`thunderstorm`:then=`<img src="[[++assets_url]]img/weather/thunder_storm.png" class="img-fluid">`:else=``]]
    </div>
    <div style="position:relative;top:300;">
        <h2 class="mt-5">[[+hour]]</h2>
        <h2 style="font-size:80px;" class="my-5">[[+temp:lte=`32`:then=`<i class="fas fa-snowflake snow" style="color:#66A2FF;"></i>&nbsp;`]][[+temp:gte=`90`:then=`<i class="fas fa-thermometer-three-quarters" style="color:red;"></i>&nbsp;`]][[+temp]]&deg;</h2>
        <h2>[[+summary]]</h2>
    </div>
</div>

Tv ‘ds_slides’ JSON:

[
    {
      "MIGX_id":2,
      "caption":"Slide",
      "print_before_tabs":"0",
      "fields":[
        {
          "MIGX_id":8,
          "field":"slide_type",
          "caption":"Slide Type",
          "description":"Image or Weather slide.",
          "description_is_code":"0",
          "inputTV":"slide_type",
          "inputTVtype":"option",
          "validation":"",
          "configs":"",
          "restrictive_condition":"",
          "display":"",
          "sourceFrom":"config",
          "sources":"",
          "inputOptionValues":"Image==1||Weather==2",
          "default":1,
          "useDefaultIfEmpty":1,
          "pos":1
        },
        {
          "MIGX_id":9,
          "field":"slide_caption",
          "caption":"Slide Caption",
          "description":"Caption at bottom of slide.",
          "description_is_code":"0",
          "inputTV":"slide_caption",
          "inputTVtype":"text",
          "validation":"required",
          "configs":"",
          "restrictive_condition":"",
          "display":"",
          "sourceFrom":"config",
          "sources":"",
          "inputOptionValues":"",
          "default":"",
          "useDefaultIfEmpty":"0",
          "pos":2
        },
        {
          "MIGX_id":10,
          "field":"slide_image",
          "caption":"Slide Image",
          "description":"Select image for display if an image slide. Will not show for weather slides.",
          "description_is_code":"0",
          "inputTV":"slide_image",
          "inputTVtype":"image",
          "validation":"",
          "configs":"",
          "restrictive_condition":"",
          "display":"",
          "sourceFrom":"config",
          "sources":"",
          "inputOptionValues":"",
          "default":"",
          "useDefaultIfEmpty":"0",
          "pos":3
        },
        {
          "MIGX_id":11,
          "field":"weather_lat",
          "caption":"Latitude",
          "description":"Latitude of location to retrieve weather forecast.",
          "description_is_code":"0",
          "inputTV":"weather_lat",
          "inputTVtype":"text",
          "validation":"",
          "configs":"",
          "restrictive_condition":"",
          "display":"",
          "sourceFrom":"config",
          "sources":"",
          "inputOptionValues":"",
          "default":"",
          "useDefaultIfEmpty":"0",
          "pos":4
        },
        {
          "MIGX_id":12,
          "field":"weather_lng",
          "caption":"Longitude",
          "description":"Longitude of location to retrieve weather forecast.",
          "description_is_code":"0",
          "inputTV":"weather_lng",
          "inputTVtype":"text",
          "validation":"",
          "configs":"",
          "restrictive_condition":"",
          "display":"",
          "sourceFrom":"config",
          "sources":"",
          "inputOptionValues":"",
          "default":"",
          "useDefaultIfEmpty":"0",
          "pos":5
        },
        {
          "MIGX_id":13,
          "field":"weather_api",
          "caption":"Weather Service API",
          "description":"Dark Sky Weather API Key.",
          "description_is_code":"0",
          "inputTV":"weather_api",
          "inputTVtype":"text",
          "validation":"",
          "configs":"",
          "restrictive_condition":"",
          "display":"",
          "sourceFrom":"config",
          "sources":"",
          "inputOptionValues":"",
          "default":"",
          "useDefaultIfEmpty":"0",
          "pos":6
        },
        {
          "MIGX_id":14,
          "field":"slide_delay",
          "caption":"Slide Duration",
          "description":"Duration in seconds the slide is shown on screen. First slide will always ignore this and default to 5 seconds.",
          "description_is_code":"0",
          "inputTV":"slide_delay",
          "inputTVtype":"text",
          "validation":"required",
          "configs":"",
          "restrictive_condition":"",
          "display":"",
          "sourceFrom":"config",
          "sources":"",
          "inputOptionValues":"",
          "default":5,
          "useDefaultIfEmpty":1,
          "pos":7
        }
      ],
      "pos":1
    }
  ]

The most likely cause of your error is, that you have a row in your MIGX-TV ds_slides where the fields weather_lat, weather_lng and weather_api are empty. (Maybe a row with “Image” as the “Slide Type”?)

Either exclude this row with a &where-property in your call to getImageList or check the values in your snippet darkSkyWeatherHourly before you make the call to the weather API:

if ($lat == '' || $lng == '' || $api == ''){
    return '';
}

I will try to test out that bit of php to see if MIGX is outputting the proper data, but it should be considering the final output shows it to be using slide_type to properly deliniate what code to render. I pared down digitalSignageTpl for simplicity here, but this is the full one:

[[+slide_type:is=`1`:then=`      
            <!--Image slide-->
            <div class="carousel-item [[+idx:is=`1`:then=`active`]]" data-interval="[[+slide_delay:mpy=`1000`]]">
                <div class="view" style="background-image: url('[[+slide_image]]'); background-position: center; background-repeat: no-repeat; background-size: cover;">

                    <!-- Mask & flexbox options-->
                    <div class="mask d-flex justify-content-center align-items-end">
                        <div class="w-100 rgba-black-strong border-top">
                            <!-- Content -->
                            <div class="white-text mx-5 px wow fadeIn">
                                <div class="mr-3 d-inline">
                                    <i class="fas fa-circle"></i>
                                </div>
                                <h1 class="ml-2 my-3 d-inline-block">[[+slide_caption]]</h1>
                                <div class="clock">
                                    <ul>
                                        <li class="hours"></li>
                                        <li class="point">:</li>
                                        <li class="min"></li>
                                        <li class="ap"></li>
                                    </ul>
                                </div>
                            </div>
                            <!-- Content -->
                        </div>
                    </div>
                    <!-- Mask & flexbox options-->

                </div>
            </div>
            <!--/Image slide-->
`]]
[[+slide_type:is=`2`:then=`
            <!--Weather slide-->
            <div class="carousel-item [[+idx:is=`1`:then=`active`]]" data-interval="20000">
                <div class="view" style="background-image: url('[[++assets_url]]img/black_tile.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover;">

                    <div class="container-fluid h-100">
                        <div class="row h-100 pt-5">
                            [[-Hours must only be set to 4 unless tpl bootstrap grid in tpl is modified, 4 looks best on most screens]]
                            [[!darkSkyWeatherHourly?
                                &api=`[[+weather_api]]`
                                &lat=`[[+weather_lat]]`
                                &lng=`[[+weather_lng]]`
                                &hours=`4`
                                &tpl=`weatherHourlyTpl`
                            ]]
                        </div>
                    </div>
                    <!-- Mask & flexbox options-->
                    <div class="mask d-flex justify-content-center align-items-end">

                        <div class="w-100 rgba-black-strong border-top">
                            <!-- Content -->
                            <div class="white-text mx-5 px wow fadeIn">
                                <div class="mr-3 d-inline">
                                    <i class="fas fa-circle"></i>
                                </div>
                                <h1 class="ml-2 my-3 d-inline-block">[[+slide_caption]]</h1>
                                <div class="clock d-inline-block float-right">
                                    <ul>
                                        <li class="hours"></li>
                                        <li class="point">:</li>
                                        <li class="min"></li>
                                        <li class="ap"></li>
                                    </ul>
                                </div>
                            </div>
                            <!-- Content -->
                        </div>
                    </div>
                    <!-- Mask & flexbox options-->
                </div>
            </div>
            <!--/Weather slide-->
`]]

And the final code html from the whole thing. There is an image slide for testing, showing slide_type is being output and put through the conditional properly. So I am unsure why some variables would be passed and others not:

<!--Weather slide-->
<div class="carousel-item active" data-interval="20000">
    <div class="view" style="background-image: url('/assets/img/black_tile.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover;">

        <div class="container-fluid h-100">
            <div class="row h-100 pt-5">
                
                
            </div>
        </div>
        <!-- Mask & flexbox options-->
        <div class="mask d-flex justify-content-center align-items-end">

            <div class="w-100 rgba-black-strong border-top">
                <!-- Content -->
                <div class="white-text mx-5 px wow fadeIn">
                    <div class="mr-3 d-inline">
                        <i class="fas fa-circle"></i>
                    </div>
                    <h1 class="ml-2 my-3 d-inline-block">Forecast</h1>
                    <div class="clock d-inline-block float-right">
                        <ul>
                            <li class="hours"></li>
                            <li class="point">:</li>
                            <li class="min"></li>
                            <li class="ap"></li>
                        </ul>
                    </div>
                </div>
                <!-- Content -->
            </div>
        </div>
        <!-- Mask & flexbox options-->
    </div>
</div>
<!--/Weather slide-->

<!--Image slide-->
<div class="carousel-item " data-interval="10000">
    <div class="view" style="background-image: url('assets/img/jon_color_run.jpg'); background-position: center; background-repeat: no-repeat; background-size: cover;">

        <!-- Mask & flexbox options-->
        <div class="mask d-flex justify-content-center align-items-end">
            <div class="w-100 rgba-black-strong border-top">
                <!-- Content -->
                <div class="white-text mx-5 px wow fadeIn">
                    <div class="mr-3 d-inline">
                        <i class="fas fa-circle"></i>
                    </div>
                    <h1 class="ml-2 my-3 d-inline-block">A Gym for Everyone</h1>
                    <div class="clock">
                        <ul>
                            <li class="hours"></li>
                            <li class="point">:</li>
                            <li class="min"></li>
                            <li class="ap"></li>
                        </ul>
                    </div>
                </div>
                <!-- Content -->
            </div>
        </div>
        <!-- Mask & flexbox options-->

    </div>
</div>
<!--/Image slide-->

With nested tags it is often quite confusing in which order the tags get executed.

It seems that in this case [[!darkSkyWeatherHourly ...]] is called first for every row (regardless of the slide-type) and the checks for the slide-type [[+slide_type:is=`1`:then=``...]] are done after that.

This means that the output will be correct, but the snippet darkSkyWeatherHourly will be called with empty values. So you have to check the values in your snippet or integrate the logic of this chunk into your snippet.