Jump to content

Tutorial: Building a simple REST API in ProcessWire


gebeer

Recommended Posts

In this tutorial I will cover how to use clsource's REST Helper classes to create a RESTful API endpoint within a PW-powered site and how to connect to it from the outside world with a REST client. This is a quite lengthy tutorial. If you follow all along and make it through to the end, you should get both, a working REST API with ProcessWire and hopefully some more basic understanding how these APIs work.

As always with PW, there are many ways you could do this. My way of implementing it and the code examples are loosely based on a real world project that I am working on.

Also, this is the first tutorial I am writing, so please bear with me if my instructions and examples are not that clear to understand. And please let me know if something is missing or could be made more clear.

The steps covered:

  • create templates and pages in the PW backend to get an API endpoint (an URL where the API can be accessed at)
  • copy and save the REST Helper classes to your site
  • create a template file and write some logic to receive and process data through our endpoint and send data back to the REST client
  • test the whole setup with a Browser REST Client Addon

I will not go into fundamentals and technical details on how RESTful APis are supposed to work. I assume that you have already read up on that and have a basic understanding of the principles behind that technology. Some helpful resources to brush up your knowledge:
https://en.wikipedia.org/wiki/Representational_state_transfer
http://www.restapitutorial.com/lessons/whatisrest.html

The complete pages.php template is attached to this post for copy/paste.

Lets get started.

1. create templates and pages in the PW backend to get an API endpoint (an URL where the API can be accessed)
First we need to create some templates and pages in the PW backend to make our REST API accessible from the outside world through an URL (API endpoint).
In my example this URL will be: https://mysite.dev/api/pages/
Note the "https" part. While this is not mandatory, I strongly recommend having your API endpoint use the https protocol, for security reasons.
Further down in step 3 we will use this URL to create new pages / update and get data of existing pages.
 
Go to your PW test site admin and:

  • create 2 new templates: one is called "api", the other one "pages". For a start, they both have only a title field assigned. Just create the templates. We will create the corresponding files later, when we need them.
  • enable "allow URL segments" for the "pages" template. We will need this later to access data sent by the requests from the client.
  • in the Files tab of the "pages" template check "Disable automatic append of file: _main.php"
  • create a new page under the home page with title, name and template "api" and set it to hidden
  • create a child page for the "api" page with title, name and template "pages"

The pagetree should look somewhat like this:
post-1920-0-20681300-1451720862_thumb.pn
Ok, now we're all set up for creating our API endpoint. If you browse to https://mysite.dev/api/pages/ you will most likely get a 404 error or will be redirected to your home page as we do not have a template file yet for the "pages" template. We will add that later in step 3.

2. copy and save the REST Helper classes to your site
I have the REST Helper class sitting in site/templates/inc/Rest.php and include it from there. You could save it in any other location within your site/templates folder.
I forked clsource's original code to add basic HTTP authentication support. Click here to open my raw gist, copy the contents and save them to /site/templates/inc/Rest.php

In the next step we will include this file to make the classes "Rest" and "Request" available to our template file.

3. create a template file and write some logic to receive and process data through our endpoint and send data back to the client
This will be the longest and most complex part of the tutorial. But I will try to present it in small, easy to follow chunks.

Since we access our API at https://mysite.dev/api/pages/, we need to create a template file called "pages.php" for our "pages" template and save it to /site/templates/pages.php. Go ahead and create this file (if you're lazy, copy the attached file).

Now right at the top of pages.php, we start with

<?php 
require_once "./inc/Rest.php";

to include the REST Helper classes.

Next, we initialize some variables that we will keep using later on

// set vars with the default output
$statuscode = 200;
$response = [];
$header = Rest\Header::mimeType('json');

3.1 retrieve data with a GET request
Now that we have the basics set up, we will next create the code for handling the easiest request type, a GET request. With the GET request we will ask the API to return data for an existing page. To let the API know which page it should return data for, we need to send the page id along with our request. I am attaching the page id as an url segment to the API endpoint. So the URL that the client will use to retrieve data for a page will look like: https://mysite.dev/api/pages/1234
where 1234 is the unique page id.

Add following code to pages.php



// if we have an urlsegment and it is a numeric string we get data from or update an existing page: handle GET and PUT requests
if($input->urlSegment1 && is_numeric($input->urlSegment1)) { 

    $pageId = $input->urlSegment1;

    // GET request: get data from existing page
    if(Rest\Request::is('get')) { 

        // get the page for given Id
        $p = $pages->get($pageId);

        if($p->id) {
            $pdata = ["id" => $pageId]; // array for storing page data with added page id

            $p->of(false); // set output formatting to false before retrieving page data

            // loop through the page fields and add their names and values to $pdata array
            foreach($p->template->fieldgroup as $field) {
                if($field->type instanceof FieldtypeFieldsetOpen) continue;
                $value = $p->get($field->name); 
                $pdata[$field->name] = $field->type->sleepValue($p, $field, $value);
            }

            $response = $pdata;

        } else {
            //page does not exist
            $response["error"] = "The page does not exist";
            $statuscode = 404; // Not Found (see /site/templates/inc/Rest.php)
        }

    }

} else {
    // no url segment: handle POST requests

}

// render the response and body
http_response_code($statuscode);
header($header);
echo json_encode($response);

Lets brake this down:
First we check for a numeric url segment which is our $pageId.
Then the Rest Request class comes into play and checks what type of request is coming in from the client.
For the GET request, we want to return all data that is stored for a page plus the page id. This is all good old PW API code.
I am using the $pdata array to store all page data. Then I am handing this array over to the $response variable. This will be used further down to render the JSON response body.
If the page does not exist, I am setting an error message for the $response and a status code 404 so the client will know what went wrong.
The else statement will later hold our POST request handling.

The last 3 lines of code are setting the header and status code for the response and print out the response body that is sent back to the client.

You can now browse to https://mysite.dev/api/pages/1 where you should see a JSON string with field names and values of your home page. If you enter a page id which does not exist you should see a JSON string with the error message.

Lets move on to updating pages through a PUT request

3.2 update pages with a PUT request
Since our API needs to know the id of the page we want to update, we again need to append an id to our endpoint url. In this example we will update the title and name of our homepage. So the request url will be: https://mysite.dev/api/pages/1.
For the GET request above, anyone can connect to our API to retrieve page data. For the PUT request this is not a good idea. Thus we will add basic authentication so that only authorized clients can make updates. I use basic HTTP authentication with username and password. In combination with the https protocol this should be fairly safe.
To set this up, we need an API key for the password and a username of our choosing. We add the API key in the PW backend:

  • add a new text field "key" and assign it to the "api" template.
  • edit the "api" page, enter your key and save. (I am using 123456 as key for this tutorial)

Now add following code right after the if(Rest\Request::is('get')) {...} statement:

    // PUT request: update data of existing page
    if(Rest\Request::is('put')) { 

        // get data that was sent from the client in the request body + username and pass for authentication
        $params = Rest\Request::params();

        // verify that this is an authorized request (kept very basic)
        $apiKey = $pages->get("template=api")->key;
        $apiUser = "myapiuser";

        if($params["uname"] != $apiUser || $params["upass"] != $apiKey) {
            // unauthorized request
            $response["error"] = "Authorization failed";
            $statuscode = 401; // Unauthorized (see /site/templates/inc/Rest.php)

        } else {
            // authorized request
            
            // get the page for given Id
            $p = $pages->get($pageId);

            if($p->id) {
                $p->of(false);

                $p->title = $sanitizer->text($params["title"]);
                $p->name = $sanitizer->pageName($params["name"]);

                $p->save();

                $response["success"] = "Page updated successfully";

            } else {
                // page does not exist
                $response["error"] = "The page does not exist";
                $statuscode = 404; // Not Found (see /site/templates/inc/Rest.php)
            }
        }

    }

Breakdown:
We check if the request from the client is a put request.
All data that was sent by the client is available through the $params array.
The $params array also includes $params["uname"] and $params["upass"] which hold our API user and key. We set API key and user and check if they match with the values that were sent by the client. If they don't match we store an error message to the response body and set the appropriate status code.
If authentication went through ok, we get the page via PW API and update the values that were sent in the request body by the client.
Then we put out a success message in the response body.
If the page does not exist, we send the appropriate response message and status code, just like in the GET request example above.

Now you might wonder how the client sends API user/key and new data for updating title and name. This is covered further down in step 4. If you want to test the PUT request right now, head down there and follow the instructions. If not, continue reading on how to setup a POST request for creating new pages.

3.2 create new pages with a POST request
Final part of the coding part is creating new pages through our API. For this to work we need to implement a POST request that sends all the data that we need for page creation.
We will do this at our endpoint: https://mysite.dev/api/pages/

Paste following code within the else statement that has the comment "// no url segment: handle POST requests":

    // POST request: create new page
    if(Rest\Request::is('post')) { 

        // get data that was sent from the client in the request body + username and pass for authentication
        $params = Rest\Request::params();

        // verify that this is an authorized request (kept very basic)
        $apiKey = $pages->get("template=api")->key;
        $apiUser = "myapiuser";

        if($params["uname"] != $apiUser || $params["upass"] != $apiKey) {
            // unauthorized request
            $response["error"] = "Authorization failed";
            $statuscode = 401; // Unauthorized (see /site/templates/inc/Rest.php)

        } else {
            // authorized request
            
            // create the new page
            $p = new Page();
            $p->template = $sanitizer->text($params["template"]); 
            $p->parent = $pages->get($sanitizer->text($params["parent"])); 
            $p->name = $sanitizer->pageName($params["name"]);
            $p->title = $sanitizer->text($params["title"]); 
            $p->save(); 

            if($p->id) {

                $response["success"] = "Page created successfully";
                $response["url"] = "https://mysite.dev/api/pages/{$p->id}";

            } else {
                // page does not exist
                $response["error"] = "Something went wrong";
                $statuscode = 404; // just as a dummy. Real error code depends on the type of error.
            }
        }

    }

You already know what most of this code is doing (checking authorisation etc.). Here's what is new:
We create a page through the PW API and assign it a template, a parent and basic content that was sent by the client.
We check if the page has been saved and update our response body array with a success message and the URL that this page will be accessible at through the API for future requests. The client can store this URL for making GET or PUT requests to this page.

If you're still reading, you have made it through the hard part of this tutorial. Congratulations. Having our code for reading, updating and creating pages, we now need a way to test the whole scenario. Read on to find out how this can be done.

4. test the whole setup with a Browser REST Client Addon
The link in the heading will take you to a place from which you can install the very useful RESTClient addon to your favorite browser. I am using it with Firefox which is still the dev browser of my choice.

Open a RESTClient session by clicking the little red square icon in the browsers addon bar. The UI is pretty straightforward and intuitive to use.
post-1920-0-83912000-1451721580_thumb.pn

4.1 test the GET request
Choose Method GET and fill in the URL to our endpoint.
If you do not have a SSL setup for testing, just use http://yourrealtestdomain.dev/api/pages/1.
If you happen to have a SSL test site with a self signed certificate, you need to point your browser to the URL https://yourrealtestdomain.dev/api/pages/ first in your test browser and add the security exception permanently. Otherwise RESTClient addon won't be able to retrieve data.
If you have a test site with a 'real' SSL certificate, everything should be fine with using the https://... URL
Hit send. In the Response Headers tab you should see a Status Code 200 and in the Response Body tabs a JSON string with data of your page.
post-1920-0-67120000-1451721684_thumb.pn
now change the 1 i the URL to some id that does not exist in your site and hit send again. You should get a 404 Status Code in the Response Headers tab and an error message "{"error":"The page does not exist"}" in the Response Body (Raw) tab.
If you get these results, congrats! The GET request is working.
For further testing you can save this request through the top menu Favorite Requests->Save Current Request.

4.1 test the PUT request
Choose Method PUT and fill in the URL to our endpoint ending with 1 (http://yourrealtestdomain.dev/api/pages/1).
In the top left click Headers->Content-Type: application/json to add the right content type to our request. If you miss this step, the request will not work. You will now see a "Headers" panel with all your headers for this request
Click on Authentication->Basic Authentication. In the modal window that pops up, fill in user (myapiuser) and password (your API key). Check "Remember me" and hit Okay.
You now should see Content-Type and Authorization headers in the "Headers" panel.

Next, we need to send some data in the request body for updating our page title and name. Since we're using JSON, we need to create a JSON string that contains the data that we want to send. As I will update the home page for this example, my JSON reads

{
    "title" : "Newhome",
    "name" : "newhome"
}

Be careful that you have a well formed string here. Otherwise you will get errors. Paste this into the "Body" panel of the REST Client addon.

Hit send. In the Response Headers tab you should see a Status Code 200 and in the Response Body tabs a JSON string "{"success":"Page updated successfully"}".
post-1920-0-78360100-1451721761_thumb.pn
Now go to the PW backend and check if title and name of your page have been updated. If yes, congrats again.

4.2 test the POST request
Choose Method POST and fill in the URL to our endpoint without any page id (http://yourrealtestdomain.dev/api/pages/).
In the top left click Headers->Content-Type: application/json to add the right content type to our request. If you miss this step, the request will not work. You will now see a "Headers" panel with all your headers for this request
Click on Authentication->Basic Authentication. In the modal window that pops up, fill in user (myapiuser) and password (your API key). Check "Remenber me" and hit Okay.
You now should see Content-Type and Authorization headers in the "Headers" panel.

Next, we need to send some data in the request body for updating our page title and name. Since we're using JSON, we need to create a JSON string that contains the data that we want to send. I will create a new page with template basic-page and parent /about/ for this example, my JSON reads

{
    "template" : "basic-page",
    "parent" : "/about/",
    "title" : "New Page created through api",
    "name" : "newapipage"
}

Be careful that you have a well formed string here. Otherwise you will get errors. Paste this into the "Body" panel of the REST Client addon.

Hit send. In the Response Headers tab you should see a Status Code 200 and in the Response Body tabs a JSON string "{"success":"Page created successfully","url":"https:\/\/mysite.dev\/api\/pages\/1019"}".
post-1920-0-99738300-1451721800_thumb.pn
Now go to the PW backend and check if title and name of your page have been updated. If yes, you're awesome!

Summary
By now you have learned how to build a simple REST API with ProcessWire for exchanging data with mobile devices or other websites.

Notes
I tested this on a fresh PW 2.7.2 stable install with the minimal site profile and can confirm the code is working. If you experience any difficulties in getting this to work for you, let me know and I will try to help.
There purposely is quite a lot of repetion in the example code to make it easier to digest. In real life code you might not want to use procedural coding style but rather separate repeating logic out into classes/methods.
Also, in life applications you should do more sanity checks for the authentication of clients with the API / for the data that is delivered by the client requests and more solid error handling. I skipped these to make the code shorter.
RESTful services are by definition stateless (sessionless). My implementation within PW still opens a new session for each request and I haven't found a way around that yet. If anyone can help out this would be much appreciated.
And finally big thanks to clsource for putting the Rest.php classes together.

pages.php.zip

  • Like 33
Link to comment
Share on other sites

A great tutorial mr @gebeer,

Also I recommend using postman for simple endpoint testing

https://www.getpostman.com/

http://raml.org/ for api documentation, definition and simple mockups

And for testing apis nothing beats Behat

http://docs.behat.org/en/v2.5/

http://shashikantjagtap.net/restful-api-testing-behat-guzzle/

https://github.com/Shashikant86/RESTful-Behat

  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Hi, I've been using your (and clsource's) helper to create webservices for our webapp/androidapp project.

I just want to ask if there's any advantage or downside in using the supplied $params array over the api's $input->post or $input->get?

Thanks. :)

Link to comment
Share on other sites

@Alxndre'

I can see an advantage using $params: it utilizes a standardised way of sending data through the request body in a JSON object. So there is only one place where you have access to all the data that was sent with the request.

Also, if you do basic http authentication and use my modified version of the helper classes, you will have access to $params["name"] and $params["pass"] to do request validation.

Link to comment
Share on other sites

Is there a way to get parameters from patch, put, and delete? It doesn't seem to work with these?

I can confirm that it works for put requests. For patch/delete, I'm sorry, but I can't confirm because I havent used them.

If you get an empty $params, this may have 2 reasons:

1. you did not specify the header Content-Type application/json

2. the JSON in the request body is malformed

Link to comment
Share on other sites

  • 8 months later...

Thanks a lot for this amazing tutorial. May I ask you few questions?

  1. How do you get the json info for image field type, either single or array (possibly included responsive file sizes)?
  2. How would you exclude specific fields, is it better to include each one manually or exclude the one you don't want?
  3. Do you usually add query parameters as well, for example if you want to output children of a specific page ID in the same call?
  4. Do you know if there's any example showing a tipical nested structure like mysite/api/pages/categoryList/{catID}/articleList/{articleID}/galleryList/{galleryID} etc..
    For example, in my single page app I need to output all pages of the website, both root and nested pages.
  5. Would you still use the classes used in this tutorial or this library that seems more up-to-date? https://github.com/NinjasCL/pw-rest


 

  • Like 1
Link to comment
Share on other sites

  • 2 months later...
On 10/11/2016 at 9:40 PM, microcipcip said:

Thanks a lot for this amazing tutorial. May I ask you few questions?

  1. How do you get the json info for image field type, either single or array (possibly included responsive file sizes)?
  2. How would you exclude specific fields, is it better to include each one manually or exclude the one you don't want?
  3. Do you usually add query parameters as well, for example if you want to output children of a specific page ID in the same call?
  4. Do you know if there's any example showing a tipical nested structure like mysite/api/pages/categoryList/{catID}/articleList/{articleID}/galleryList/{galleryID} etc..
    For example, in my single page app I need to output all pages of the website, both root and nested pages.
  5. Would you still use the classes used in this tutorial or this library that seems more up-to-date? https://github.com/NinjasCL/pw-rest


 

Hello, I will try to respond these questions

 

Questions 1, 2 and 4 could be resolved by using "Transformers"

a "Transformer" is simply a class that convert data from one format to another. In this case

a transformer that converts PW pages to rest json.

 

And example.

If you got a template named video that stores youtube videos

and contains the following fields: url, title.

You can create a Transformer named like
transformers/youtube.php

<?php namespace ProcessWire;
class YoutubeTransformer 
{
  public $source;
  public __construct(Page $source)
 {
  $this->source = $source;
 }

  public function toArray()
  {
    return [
     "url" => $this->source->url,
     "title" => $this->source->title
    ];
  }
  public function toJson()
 {
   return json_encode($this->toArray());
 }

}

That way you could create a special class that could render specific outputs depending on

what do you need.

For example {catID}/articleList/{articleID}/galleryList/{galleryID}
You could have a specific transformer for categories, articles and gallery items
and share the output for each class and join them in the final response.

This could also be combined with field specific templates

https://processwire.com/blog/posts/processwire-3.0.7-expands-field-rendering-page-path-history-and-more/

3.- For answering this it depends on your architecture and your api requirements. The rule of thumb is think how the developers will use the api and what information they will need.

5.- Well depends on what do you need and feel more confortable, those are just helper methods.

there are tons of good code that you could use like

https://github.com/marcj/php-rest-service

http://symfony.com/doc/current/bundles/FOSRestBundle/index.html

http://symfony.com/components/Guard
http://symfony.com/components/HttpFoundation
http://symfony.com/components/Serializer
 

  • Like 5
Link to comment
Share on other sites

  • 4 weeks later...

@microcipcip At the time of writing this tutorial and coding a real world application that uses this approach, I wasn't aware of the fact that PW brings it's own http client Class WireHttp.

So today I wouldn't bother using an external library like guzzle or phphttpclient. I would go with WireHttp. It has all the methods you need.

@clsource thanks for answering microcipcip's questions so thoroughly.

  • Like 6
Link to comment
Share on other sites

@microcipcip WireHttp is meant as a replacement for external PHP http client libraries like guzzle etc. You would want to use these when you 'talk' to the REST API that you set up with PW.

Don't confuse this with clsource's Rest Helper class that I am using in my tutorial. You would still use that to build your REST API on the PW side.

Today, If I wanted to develop a PW application that exposes JSON data through an API I would go with the brandnew GraphQL module by Nurguly. Seems like a much cleaner approach compared to REST.

  • Like 4
Link to comment
Share on other sites

  • 1 year later...

Thank you @gebeer for this awesome tutorial. I have an issue with PUT request. In the step 4.1 test the PUT request, it didn't work, although i entered Basic Authentication method: username: myapiuser and my the password as my own set key. Any help is greatly appreciated! Thank you in advance! :)

Link to comment
Share on other sites

 

17 hours ago, Batyr said:

Thank you @gebeer for this awesome tutorial. I have an issue with PUT request. In the step 4.1 test the PUT request, it didn't work, although i entered Basic Authentication method: username: myapiuser and my the password as my own set key. Any help is greatly appreciated! Thank you in advance! :)

@Batyr Happy you like the tutorial. Could you please be more specific about what exactly is not working:
Do you get any error messages? Then post them.
Do you have correct JSON in the body field of the REST Client Addon? Pls post a screenshot of how the REST Client window looks when you try to send the PUT request.

Then I might be able to help.

Link to comment
Share on other sites

1 hour ago, adrianmak said:

How to handle user authentication for some RESTful action like update, delete, insert a record

Look at 3.2 in the first post, I use basic HTTP authentication which, in combination with SSL, was save enough for that application. If you need more sophisticated auth scenarios, have a look at JSON Web Tokens. @thomasaull implements it in his REST-Api site profile.

 

  • Like 2
Link to comment
Share on other sites

  • 4 weeks later...

@gebeer thankx for this tutorial. It's a good starting point to explore REST in PW.

But like @Batyr, I'm not able to make the PUT and POST request in your given way. There are no params "uname" or "upass" in the params array. And there are no $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] set.

If my page is behind a .htaccess + .htpasswd I can submit these data as basic authentication. But if I try to submit my uname and upass values as basic auth, then it fails. Is there a possibility to submit my own key/value secure data in request header?

Link to comment
Share on other sites

Hi @gebeer

sorry, things moving on slowly - too many tasks. How can I submit the request header to you? There is nothing to copy in RestClient. I set the Basic Authentication and the content type (application/json).

If the .htpass is active, I use these crendentials for authentication and it works. If there is still the if-cause for uname and upass I have to set them in the request body to keep working. But if I remove the .htpass and set the uname and upass keys as Basic Authentication, it doesn't work anymore.

Link to comment
Share on other sites

@Fantomas Is you server setup Apache with PHP running in fast cgi mode? Then $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] will not be populated.

I just setup my tutorial in a fast cgi environment and could reproduce your problem.

I also found the problem and a possible solution and will implement that into the Rest.php gist and let you know once it is updated.

Link to comment
Share on other sites

@Fantomas @Batyr

Place this at the top of your .htaccess and try again

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0

This should make $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] available in fastcgi/cgi environments.

It works in my server setup. Please report back if this fix also works for you.

Link to comment
Share on other sites

Hi @gebeer

for me, your solution was just the first step to get it to work.

I added your line to my .htaccess (by the way, there is another way of notation, both are working for me, I don't know the difference, so use

SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0

OR
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

But I had to do a further step in the rest.inc. Before your Auth-Code-Block, I had to insert:

//Basic HTTP Authentication
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
  list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'])=explode(':',base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'],6)));
} else if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
  list($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW'])=explode(':',base64_decode(substr($_SERVER['REDIRECT_HTTP_AUTHORIZATION'],6)));
}

//and now your code
if (isset($_SERVER['PHP_AUTH_USER'])) {

...

}

My server uses the REDIRECT_HTTP_AUTHORIZATION value.

Now it works for me. Many thanks to your help.

 

  • Like 1
Link to comment
Share on other sites

@Fantomas Thank you for letting me know.

I had tried the second notation that you mentioned with exactly the same code I put in Rest.php. But on my server environment this wouldn't work.
Because HTTP_AUTHORIZATION was always empty. I never tried REDIRECT_HTTP_AUTHORIZATION. It did not occur to me since in the .htaccess rewrite rule it defines HTTP_AUTHORIZATION.

So you pointed me to the final piece of the puzzle. Thanks again for that. I now can implement this in a way that it will properly work in most server environments.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...