Jump to content
gebeer

Tutorial: Building a simple REST API in ProcessWire

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 31

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

cool :)

i think you have a typo in your first codeblock?

post-2137-0-21595300-1451738651_thumb.pn

  • Like 1

Share this post


Link to post
Share on other sites

@clsource

great resources, thank you

@BernhardB

thanks for spotting this. I amended my post.

Share this post


Link to post
Share on other sites

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. :)

Share this post


Link to post
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.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites

@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

Share this post


Link to post
Share on other sites

@gebeer that's cool, I like the idea of using the CMS API as much as possible for better consistency. How'd you rewrite this code? With vanilla PHP isset($_POST)? Can't see the equivalent with WireHttp.

if(Rest\Request::is('get')) {}
if(Rest\Request::is('put')) {}

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

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! :)

Share this post


Link to post
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.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

@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?

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Gadgetto
      SnipWire - Snipcart integration for ProcessWire
      Snipcart is a powerful 3rd party, developer-first HTML/JavaScript shopping cart platform. SnipWire is the missing link between Snipcart and the content management framework ProcessWire.
      With SnipWire, you can quickly turn any ProcessWire site into a Snipcart online shop. The SnipWire plugin helps you to get your store up and running in no time. Detailed knowledge of the Snipcart system is not required.
      SnipWire is free and open source licensed under Mozilla Public License 2.0! A lot of work and effort has gone into development. It would be nice if you could donate an amount to support further development:

      Status update links (inside this thread) for SnipWire development
      2020-07-03 -- SnipWire 0.8.7 (beta) released! Fixes some small bugs and adds an indicator for TEST mode 2020-04-06 -- SnipWire 0.8.6 (beta) released! Adds support for Snipcart subscriptions and also fixes some problems 2020-03-21 -- SnipWire 0.8.5 (beta) released! Improves SnipWires webhooks interface and provides some other fixes and additions 2020-03-03 -- SnipWire 0.8.4 (beta) released! Improves compatibility for Windows based Systems. 2020-03-01 -- SnipWire 0.8.3 (beta) released! The installation and uninstallation process has been heavily revised. 2020-02-08 -- SnipWire 0.8.2 (beta) released! Added a feature to change the cart and catalogue currency by GET, POST or SESSION param 2020-02-03 -- SnipWire 0.8.1 (beta) released! All custom classes moved into their own namespaces. 2020-02-01 -- SnipWire is now available via ProcessWire's module directory! 2020-01-30 -- SnipWire 0.8.0 (beta) first public release! (module just submitted to the PW modules directory) 2020-01-28 -- added Custom Order Fields feature (first SnipWire release version is near!) 2020-01-21 -- Snipcart v3 - when will the new cart system be implemented? 2020-01-19 -- integrated taxes provider finished (+ very flexible shipping taxes handling) 2020-01-14 -- new date range picker, discount editor, order notifiactions, order statuses, and more ... 2019-11-15 -- orders filter, order details, download + resend invoices, refunds 2019-10-18 -- list filters, REST API improvements, new docs platform, and more ... 2019-08-08 -- dashboard interface, currency selector, managing Orders, Customers and Products, Added a WireTabs, refinded caching behavior 2019-06-15 -- taxes provider, shop templates update, multiCURL implementation, and more ... 2019-06-02 -- FieldtypeSnipWireTaxSelector 2019-05-25 -- SnipWire will be free and open source Plugin Key Features
      Fast and simple store setup Full integration of the Snipcart dashboard into the ProcessWire backend (no need to leave the ProcessWire admin area) Browse and manage orders, customers, discounts, abandoned carts, and more Multi currency support Custom order and cart fields Process refunds and send customer notifications from within the ProcessWire backend Process Abandoned Carts + sending messages to customers from within the ProcessWire backend Complete Snipcart webhooks integration (all events are hookable via ProcessWire hooks) Integrated taxes provider (which is more flexible then Snipcart own provider) Useful Links
      SnipWire in PW modules directory SnipWire Docs (please note that the documentation is a work in progress) SnipWire @GitHub (feature requests and suggestions for improvement are welcome - I also accept pull requests) Snipcart Website  

       
      ---- INITIAL POST FROM 2019-05-25 ----
       
    • By MoritzLost
      I've seen a couple of questions regarding namespaces and autoloading floating around the forum recently, so I decided to write a little tutorial. In general, I often see people getting confused when they try to wrap their head around namespaces, autoloading, Composer and the mapping of namespaces to directory structures all at once. In fact, those are very much independent, distinct concept, and it is much easier to explain and understand them separately. So this guide is structured as follows:
      How namespaces work in PHP. How autoloading works in PHP. Conventions for mapping namespaces to directory structures: PSR-4. How autoloading works in Composer and ProcessWire's class loader. How to use the class loader in a ProcessWire module. Feel free to skip the sections you're already familiar with.
      Namespaces in PHP
      The purpose of namespaces in PHP is to avoid naming conflicts between classes, functions and constants, especially when you're using external libraries and frameworks. Nothing more. It's important to understand that this has nothing at all to do with autoloading, directory structures or file names. You can put namespaced stuff everywhere you want. You can even have multiple namespaces inside a single file (don't try this at home). Namespaces only exist to be able to use a generic name – for example, ProcessWire's Config class – multiple times in different contexts without getting a naming conflict. Without namespaces, I couldn't use any library that includes a Config class of it's own, because that name is already taken. With namespaces, you can have a distinction between the classes ProcessWire\Config and MoritzLost\Config. You can also use sub-namespaces to further segregate your code into logical groups. For example, I can have two classes MoritzLost\Frontend\Config and MoritzLost\Backend\Config– a class name only needs to be unique within it's namespace.
      You can declare the namespace for a PHP file using the namespace statement at the top:
      // file-one.php <?php namespace ProcessWire; // file-two.php <?php namespace MoritzLost\Frontend; This way, all classes, methods and constants defined inside this file are placed in that namespace. All ProcessWire classes live in the ProcessWire namespace.
      Now to use one of those classes – for example, to instantiate it – you have a couple of options. You can either use it's fully qualified class name or import it into the current namespace. Also, if you are inside a namespaced file, any reference to a class is relative to that namespace. Unless it starts with a backward slash, in this case it's relative to the global namespace. So all of those examples are equivalent:
      // example-one.php <?php namespace ProcessWire; $page = new Page(); // example-two.php <?php use ProcessWire\Page; $page = new Page(); // example-three.php <?php $page = new ProcessWire\Page(); // example-four.php <?php namespace MoritzLost\Somewhere\Over\The\Rainbow; $page = new \ProcessWire\Page(); The use statement in the second example can be read like this: “Inside this file, all references to Page refer to the class \ProcessWire\Page”
      How autoloading works
      Every PHP program starts with one entry file – for ProcessWire, that's usually it's index.php. But you don't want to keep all your code in one file, that would get out of hand quickly. Once you start to split your code into several individual files however, you have to take care of manually including them with require or include calls. That becomes very tedious as well. The purpose of autoloading is to be able to add new code in new files without having to import them manually. This, again, has nothing to do with namespaces, not even something with file locations. Autoloading is a pretty simple concept: If you try to use a class that hasn't been loaded yet, PHP calls upon it's registered autoloaders as a last-ditch attempt to load them before throwing an exception.
      Let's look at a simple example:
      // classes.php <?php class A { /** class stuff */ } class B { /** class stuff */ } // index.php <?php spl_autoload_register(function ($class) { include_once 'classes.php'; }); new A(); new B(); This is a complete and functional autoloader. If you don't believe me, go ahead and save those two files (classes.php and index.php) and run the index.php with php -f index.php. Then comment out the include_once call and run it again, then you'll get an error that class A was not found.
      Now here's what happens when index.php is executed (with the autoloader active):
      Our anonymous function is added to the autoload queue through spl_autoload_register. PHP tries to instantiate class A, but can't because it's not loaded yet. If there was no autoloader registered, the program would die with a fatal error at this point. But since there is an autoloader ... The autoloader is called. Our autoloader includes classes.php with the class definition. That was a close one! Since the class has been loaded, execution goes back to the index.php which can now proceed to instantiate A and B. If the class was still not loaded at this point, PHP would go back to the original plan and die. One thing to note is that the autoloader will only be called once in this example. That's because both A and B are in the same file and that file is included during the first call to the autoloader. Autoloading works on files, not on classes!
      The important takeaway is that PHP doesn't know if the autoloader knows where to find the class it asks for or, if there are multiple autoloader, which one can load it. PHP just calls each registered autoloader in turn and checks if the class has been loaded after each one. If the class still isn't loaded after the last autoloader is done, it's error time.
      What the autoloader actually does is pretty much wild wild west as well. It takes the name of the class PHP is trying to load as an argument, but it doesn't have to do anything with it. Our autoloader ignores it entirely. Instead, it just includes classes.php and says to itself “My job here is done”. If class A was in another file, it wouldn't have worked.
      This process has two main advantages:
      Since autoloaders are only called on-demand to load classes just in time, we only include the files we actually need. If in the example above class A and B are not used in some scenarios, the classes.php will not be included, which will result in better performance for larger projects (though this isn't as cut and dry, since autoloading has it's own overhead, so if you load most classes anyway during a single request, it will actually be less efficient). If the autoloader is smart enough to somehow map class names to the files they're located in, we can just let the autoloader handle including the classes we need, without having to worry about jamming include statements everywhere. That brings us to ... PSR-4, namespaces and directory structures
      As you see, namespaces and autoloading are both pretty limited concepts. And they aren't inherently linked to each other. You can namespace your classes without ever adding an autoloader, and you can autoload classes that are all in the same namespace. But they become useful when you put them together. At the core of all that autoloading talk is a simple idea: By putting classes in files named after their class names, and putting those files in directory hierarchies based on the namespace hierarchy, the autoloader can efficiently find and load those files based on the namespace. All it needs is a list of root namespaces with their corresponding directories.
      The exact way class names and namespaces are mapped to directory structures and file names is purely conventional. The accepted convention for this is PSR-4. This is a super simple standard which basically just sums up the ideas above:
      A base namespace is mapped to a specific directory in the file system. When the autoloader is asked to load a class in that namespace (or a sub-namespace of it), it starts looking in that folder. This "base" namespace may include multiple parts – for example, I could use MoritzLost\MyAwesomeLibrary as a base and map that to my source directory. PSR-4 calls this a "namespace prefix". Each sub-namespace corresponds to a sub-directory. So by looking at the namespace, you can follow subdirectories to the location where you expect to find the class file. Finally, the class name is mapped directly to the file name. So MyCoolClass needs to be put inside MyCoolClass.php. This all sounds simple and straightforward - and it absolutely is! It's only once you mash everything together, mix up language features, accepted conventions and proprietary implementations like Composer on top that it becomes hard to grasp in one go.
      Composer and ProcessWire's class loader
      Now all that's left is to talk about how Composer and ProcessWire provide autoloading.
      Composer, of course, is primarily a tool for dependency management. But because most libraries use namespaces and most developers want to have the libraries they're using autoloaded, those topics become a prerequisite to understanding what Composer does in this regard. Composer can use different autoloading mechanisms; for example, you can just give it a static list of files to include for every request, or use the older PSR-0 standard. But most modern libraries use PSR-4 to autoload classes. So all Composer needs to function is a mapping of namespace prefixes to directories. Each library maintains this mapping for it's PSR-4-structured classes through the autoload information in their composer.json. You can do this for your own site to: Just include the autoload information as shown in the documentation and point it to the directory of your class files.
      Composer collects all that information and uses it to generate a custom file at vendor/autoload.php — that's the one you need to include somewhere whenever you set up Composer in one of your projects. Bells and whistles aside, this file just registers an autoloader function that will use all the information collected from your own and your included libraries' composer.json to locate and include class files on demand.
      You can read more about how to optimize Composer's autoloader for production usage here. If you want to read up on how to set up Composer for your own sites, read my ProcessWire + Composer integration guide instead.
      And finally, what does ProcessWire do to handle all this? Turns out, ProcessWire has it's own autoloader implementation that is more or less PSR-4 compliant. You can access it as an API variable ($classLoader or wire('classLoader'), depending on context). Instead of using a static configuration file like Composer, the namespace -> directory mapping is added during the runtime by calling $classLoader->addNamespace. As you would expect, this function accepts a namespace and a directory path. You can use this to register your own custom namespaces. Alternatively, if you have site-specific classes within the ProcessWire namespace, you can just add their location to the class loader using the same method: $classLoader->addNamespace('ProcessWire', '/path/to/your/classes/').
      Utilizing custom namespaces and autoloading in ProcessWire modules
      Now as a final remark, I wanted to give an example of how to use custom namespaces and the class loader in your own modules. I'll use my TrelloWire module as an example:
      Decide what namespace you're going to use. The main module file should live in the ProcessWire namespace, but if you have other classes in your module, they can and should use a custom namespace to avoid collisions with other modules. TrelloWire uses ProcessWire\TrelloWire, but you can also use something outside the ProcessWire namespace. You need to make sure to add the namespace to the class loader as early as possible. If either you or a user of your module tries to instantiate one of your custom classes before that, it will fail. Good places to start are the constructor of your main module file, or their init or ready methods. Here's a complete example. The module uses only one custom namespaced class: ProcessWire\TrelloWire\TrelloWireApi, located in the src/ directory of the module. But with this setup, I can add more classes whenever I need without having to modify anything else.
      /** * The constructor registers the TrelloWire namespace used by this module. */ public function __construct() { $namespace = 'ProcessWire\\TrelloWire'; $classLoader = $this->wire('classLoader'); if (!$classLoader->hasNamespace($namespace)) { $srcPath = $this->wire('config')->paths->get($this) . 'src/'; $classLoader->addNamespace($namespace, $srcPath); } } Source
      Thanks for making it through to the very end! I gotta learn to keep those things short. Anyway, I hope this clears up some questions about namespaces and autoloading. Let me know if I got something wrong, and feel free to add your own tips and tricks!
    • By Ivan Gretsky
      Most of us know and use site/config-dev.php file. If present, it is used instead of site/config.php, so it is easy to set database connection and debug mode for local development, not touching the production config. It is also very useful when working with git. You can simply ignore it in the .gitignore file, so local settings won’t end up in the repo.
      But sometimes you need to add code to site/ready.php or site/init.php just for the dev environment. For example, to add ryan’s super cool on demand images mirrorer. I can’t live without it when working with big sites, which have more assets then I want to download to my desktop.
      It would be great if there was something like site/ready-dev.php for this. Not out-of-the-box, but it’s pretty easy to achieve. Unlike site/config-dev.php, site/ready.php is not hardcoded. It’s name is set with a special config setting:
      // wire/config.php $config->statusFiles = array( 'boot' => '', 'initBefore' => '', 'init' => 'init.php', 'readyBefore' => '', 'ready' => 'ready.php', 'readySite' => '', 'readyAdmin' => '', 'render' => '', 'download' => '', 'finished' => 'finished.php', 'failed' => '', ); As you can see, we can not only define, which files are loaded on init, ready and finished runtime states, but probably even add more if we need to.
      So we override this setting in site/config-dev.php like this:
      // site/config-dev.php // Change ready.php to ready-dev.php $temp = $config->statusFiles; $temp['ready'] = 'ready-dev.php'; $config->statusFiles = $temp; For some reason we can’t just do
      $config->statusFiles['ready'] = 'ready-dev.php'; and have to override the whole array. Maybe you PHP gurus can explain this in the comments.
      Now we can create the site/ready-dev.php file and place all the dev-only code there. Important thing is to include the main site/ready.php.
      // site/ready-dev.php include 'ready.php'; // DEV HOOK TO MIRROR ASSETS ON DEMAND $wire->addHookAfter('Pagefile::url, Pagefile::filename', function($event) { $config = $event->wire('config'); $file = $event->return; if($event->method == 'url') { // convert url to disk path $file = $config->paths->root . substr($file, strlen($config->urls->root)); } if(!file_exists($file)) { // download file from source if it doesn't exist here $src = 'https://mysite.com/site/assets/files/'; $url = str_replace($config->paths->files, $src, $file); $http = new WireHttp(); try { $http->download($url, $file); } catch (\Exception $e) { bd($file, "Missing file"); } } }); Do not forget to replace "mysite.com" if you’re copypasting this))
      Now, add the newly created file to the `.gitignore` and we’re done.
       
      # .gitignore # Ignore dev files site/config-dev.php site/ready-dev.php Thanks for reading!
       
    • By horst
      Change Default Language to be None-English | Walk Trough
      When you start a new (single) language site and the default language shouldn't be English, you can change it this way:
      Go to the modules core section:

       
      Select the Language ones by the filter function:

       
      We have four language related modules here, but for a single language site in none english,
      we only need the base module, named "Languages Support". So go on and install it.

       
      After that, you can leave it, ... 

       
      ... and switch to the newly created Language section under SETUP:

       
      Select the default language

       
      Enter your new language name or its Shortcut and save the page.
      I will use DE for a single language site in german here as example:

       
      Now I go to the ProcessWire online modules directory, down to the subsection for language packs and select
      and download my desired (german) one: 

       

       

       

       
      After downloading a lang pack as ZIP, I go back into my SETUP > LANGUAGES > default language page in admin,
      select the downloaded lang pack ZIP and install it:

       

       

       
       
      After the ZIP is uploaded, the files are extracted and installed, most of my screen is already in
      the new default language. To get all fully switched, we save and leave that page, ...

       
      ... and completely logout from the admin.

       
      Now, of course, we directly login back, ...

      ... and see, that now also the cached parts of the admin have switched to the new default language. 🙂

       
      That was it for a single language site in none english.
       
      If you want to have a multi language site, just add more languages to the SETUP > LANGUAGES section.
      When using a multi language site, I think you also want to use multi language input fields, and maybe different page names for your language page pendents. If so, you need to go into MODULES > CORE > filter LANGUAGE and install what you need or want to use of it, (if not already done).
      Thanks for reading and happy coding, 🙂
       
    • By Kiwi Chris
      I don't really have anything public to show, as nearly all the work is back-end, but I thought I'd post here anyway as it's a pretty good example of just how powerful Processwire can be.
      About a year ago, I inherited an incomplete Craft website made by a designer. Apart from the usual company information site, it was supposed to provide a customer portal for clients of a wine bottling company to make bookings for production runs.
      Data for stock levels of goods like bottles and labels was to come from an inventory management system Unleashed https://www.unleashedsoftware.com/
      Unleashed provides a REST API, so I had to integrate with that first by writing an API integration module, and then ended up using Processwire's core lazy cron module to periodically pull data from Unleashed using a custom module.
      The booking forms have a lot of conditional fields, eg if you are bottling a given wine variety, you should only be able to select labels that match that variety. All this conditional stuff was achieved with a lot of additions to ready.php.
      I also needed to be able to created a predefined set of pages when a new user is added if they have a 'client' role. Once again, more hooking in ready.php
      I've used the Admin Restrict Branch module so clients can only see their own records when they're logged in, but staff can see all records.
      Lister Pro provides the ability to search and view completed production runs.
      Part way through the project, as the client was happy with the way things were going, I was asked to add in logistics and dispatch which is provided by another company, which also runs Unleashed with a separate set of data, and with some clients who don't bottle wine, but will end up using the same portal, so using the roles and permissions inherent in Processwire, I set up production templates with separate roles to dispatch templates, so I could easily have clients assigned access to just the templates they need.
      Tracey Debugger got a thorough workout along the way, and the debugger console is an absolute killer tool for making quick changes to data when updating a live site to match changes from the dev site.
      At the start of this project, I'd used Processwire quite a bit, but never dived into module development or hooking, but I've now ended up with a reasonable idea how they work.
      @bernhard has produced some excellent tutorials which I found really helpful figuring out how to create modules, and other people like @Robin S have answered questions when I've got stuck. @ryan himself has been helpful when I've been trying to do things that push either the limits of my knowledge or Processwire or both 😋  .
      Could I have done this with other tools? Depends. Wordpress would have been as useless as using petrol to fight a fire, however something like ASP.Net COULD have done the job but would have probably made things a lot more complicated. In parallel, I've been working on building a REST API with ASP.Net for another client to integrate with an existing SQL Server database, and I've found that Visual Studio is inclined to break projects quite regularly, with dependencies getting messed up, or even whole configuration files getting corrupted when it has a hissy fit, so working with Processwire is a pleasure in comparison.
       
×
×
  • Create New...