Leaderboard
Popular Content
Showing content with the highest reputation on 05/23/2019 in all areas
-
I had a similar need recently for a new Fieldtype & Inputfield called AdminComments (allows users to post comments on backend pages). The way i did it was to have a Process module which installs when you install the Fieldtype and Inputfield. The javascript gets the endpoint from a data attribute that the inputfield generates (i put all the data attributes for the post url and other info directly in the inputfield, though it could have been put in the JS config also). If it helps to see this method, i can share that beta module with you. I also researched extensively to see if i could get away without a process module, and use some other endpoint, but was unsuccessful, so i just went with a Process Module. I made the module permission page-edit so that it doesn't need any special permissions to access when doing the post requests.5 points
-
Something I have done is add a method through a hook such as: $wire->addHook("ProcessPageEdit::executeCustomModule", function(){ ... }) So then you can make a request to: page/edit/custom-module4 points
-
Hello and welcome to the forum. You should edit your thread title... "Mr" is not very descriptive ? JSON support should be included in the core with every PHP installation, according to the manual. Check with your hosting company if they can enable it. The GD library is also very common. Again, ask your hoster to enable it. Or, if you are developing locally, and use something like Laragon, you can probably enable the extension with a few clicks:3 points
-
Not sure I understand your question. Why don't you just look them up in the css files or browsers devtools?2 points
-
Fieldsets are only logical groups, but they don't change how data is stored/retrieved. Each field in a fieldset still is a direct child of the page and any nesting is only presentational. If you want nesting we have https://processwire.com/blog/posts/processwire-3.0.73-and-new-fieldset-types/#fieldset-page-fieldtypefieldsetpage. This is real nesting with another page behind the scenes, which does also allow things like reusing a field in the "fieldset" as well as in the parent page.2 points
-
Hello Milo and welcome to the forums. $config->userAuthSalt For security reasons this should be different for every page. This value is used to hash your passwords to add an extra layer of security. That means when you change it you won't be able to log in anymore without regenerating the password. $config->httpHosts This should be just the host, without the protocol. So without http/https site/assets/files As far as I know there is no better way to do this, so you would have to delete files that are not needed on the new site manually. I could be wrong tho. As for the title of the topic: try to add a couple more words so people with the same questions will have it easier to find your topic in the future ? Best regards and again, welcome to the community2 points
-
One of PW 3.010's major novelty was the introduction of Horst's new image resizing engine that uses ImageMagick. Now I understand that ImageMagick can convert images to Webp, the image format that Google says can reduce image size up to 34% compared to JPEG. Mozilla is apparently adding support to Firefox, and even the Safari team is playing with it, so it looks like Webp is soon going to be available in most major browsers. If Horst's module can be extended to add Webp conversion, that would be a great addition to PW's already very powerful image manipulation arsenal. I'm currently using the free ImageEngine Lite to serve Webp images to supporting browsers, and the results are impressive. I routinely get images that are between 25 and 60% smaller compared to JPEG, with the same visual quality. I would love to eliminate the need to rely on a third-party service though.1 point
-
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: 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. 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. 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"}". 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"}". 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.zip1 point
-
Here's a couple of other projects that I looked at. I'm not sure if it is needed, but they include a couple of other directives as well. https://github.com/vincentorback/WebP-images-with-htaccess https://git.drupalcode.org/project/webp/blob/8.x-1.x/.htaccess from the Drupal 8 webp module https://github.com/nomidi/silverstripe-webp-image/blob/master/templates/SilverStripe/Assets/Flysystem/PublicAssetAdapter_HTAccess.ss for Silverstripe CMS https://github.com/S1SYPHOS/kirby-webp - for Kirby CMS I wasn't sure about this one: <IfModule mod_headers.c> Header append Vary Accept env=REQUEST_image </IfModule> Do you think something like that should be included?1 point
-
Important security update! Hi RockGrid users, I'm very sorry for that, but I have to announce a security update. If you are using RockGrid on a public site please upgrade to v0.0.22 (Fieldtype) immediately. It is a simple but important update: https://github.com/BernhardBaumrock/FieldtypeRockGrid/commit/0be2086139c84f775937246ed2985ac4c4a3e9c3; The proplem exists on all RockGrid fields with AJAX turned ON. In this case it was theoretically possible to expose the field data to a user that should not be allowed to see this data (in the worst case even a guest user) if the user knew how to do it and he also knew the name of the rockgrid field. The update now restricts access for AJAX field data to superusers only. You can easily adjust that via simple hooks: // rockgrid access control $wire->addHookAfter("InputfieldRockGrid::access", function(HookEvent $event) { // all grid data is accessible for all logged in users $event->return = $this->user->isLoggedin(); }); Or more granular via the fieldname: // rockgrid access control $wire->addHookAfter("InputfieldRockGrid::access", function(HookEvent $event) { $field = $event->arguments(0); $user = $this->user; $access = $event->return; switch($field) { case 'field1': case 'field2': case 'field3': $access = $user->isLoggedin(); break; case 'field4': $access = ($user->name == 'foo'); break; } $event->return = $access; }); Field 1-3 is allowed for logged in users, field4 only for user foo and all other fields only for superusers (default rule). I'm not totally happy any more with several aspects of RockFinder and RockGrid, but it is the best option I have so far (until I can build something totally new, maybe with tabulator if tests work well). Special thx to @Zeka for bringing this issue to my attention by coincidence in the other topic!1 point
-
Hello, I have a wonderful webshop with lovely skills here : cheval-ami.fr I need some support sometimes to make sure that everything is nice and up to date. We want to improve the integration of Paypal, Stripe, DHL ... create personal accounts for our clients, keep our visual configurateur smooth, include a search enigne and be the nicest shop in the equine industry. We are a start up and very much interested in a long time, kind and friendly relationship without any stress, but good support in case of urgency. We speak english, german and french. We a have our own designer aware of ProcessWire. We could meet in hamburg or Paris. Contact: mail@cheval-ami.fr or +33 626 22 1000 or +49 170 20 28 215 Thank you !1 point
-
@Zeka, you can hook before any Process execute method that the user has access to and replace the output. You can see an example here where I replace ProcessLogin::executeLogout (because that is something that all users have access to). I like the sound of @elabx's suggestion of adding a new execute method and accessing the URL segment for it, but with some quick testing I couldn't get that to work. But that's probably just me.1 point
-
Ah right, unfortunately AdminThemeUikit::renderBreadcrumbs() returns early unless some protected class properties are set, and there is no setter method provided. It's a shame that AdminThemeUikit wasn't designed with flexibility for customisation in mind, because it means that users like yourself have to create a totally separate theme that duplicates almost the entirety of the AdminThemeUikit code just to achieve a few minor changes. It would have been nice if something like Markup Regions could have been employed in the theme template files. But it looks like there's no better way for now, so I've merged your PR.1 point
-
@rjgamer There is Fieldset (Page) that have desired API behavior ( it doesn't installed by default). You can read about it here.1 point
-
I had the exact same issue with huge blank spaces @buster808. One thing you can do to avoid the huge white space is use the truncate method introduced in PW 3.0.101. Ryan wrote a great blog post on how to use it: https://processwire.com/blog/posts/processwire-3.0.101-core-updates/ Hope this helps.1 point
-
You can also edit your local HOSTS file, so the domains always stay the same. Just remember to comment your hosts file when working on the live site ?1 point
-
We have a local config.env.php file which is read by our general config.php. Everything that is commun is in config.php, but local settings are kept in config.env.php. In config.php: /** * Environment configuration file * */ $configFile = $config->paths->site . 'config.env.php'; @require_once($configFile);1 point
-
Looking good! Thanks for posting. @Martijn Geerts reminds you of something? ?1 point
-
I have a config-dev.php for dev server or local setup. and the "root" stays the same just the domains are different.1 point
-
I'm confused. Do you want to fetch data from a remote URL, another site? Or from another PW page in the same installation? I guess you could use CURL or WireHttp. Not sure you need a separate page under admin though.1 point
-
1 point
-
On the reasons why floats are bad for money values: https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency https://mathjs.org/docs/datatypes/numbers.html#roundoff-errors Math.js seems to support something called "BigNumber" to do arbitrary precition math, which should always be use for monetary calculations. But math.js does seem to also detect issues in floating point math and give you a rounded value, so maybe it's fine without that for simple calculations, where automatic rounding doesn't result in errors in the result.1 point
-
Thanks @flydev, but that solution feels less than ideal - I think there is a better way. If you want to implement the AdminThemeUikit breadcrumbs in your custom theme (which you would have to in order for the Breadcrumb Dropdowns module to work) then you can just call AdminThemeUikit::renderBreadcrumbs() because it is a public method. So in your custom theme template files where you want to output the breadcrumbs you can do something like this: echo $modules->get('AdminThemeUikit')->renderBreadcrumbs();1 point
-
1 point
-
No; Products are still pages ? (just NOT your own pages). ProcessWire is so versatile you can throw many things at it and it handles just fine. A custom Fieldtype is just one that doesn't ship with ProcessWire core. It is still a Fieldtype like any other. An Inputfield is just for displaying and capturing edited data. A Fieldtype does not need to have an Inputfield. As for custom db tables, even Padloper 1 uses them :-).1 point
-
You must have edited the wrong file then ?. That's exactly where it is.1 point
-
Thx for sharing your learnings @Chris Bennett I've also customized colors of the admin via CSS once, but that was a pain IMHO. Changing LESS would be a LOT more readable: @reno-text-color: #354b60; @reno-link-color: #ffae00; @reno-link-hover-color: darken(@reno-link-color, 10%); @reno-dark-background: rgb(0, 94, 202); @reno-muted-background: #f0f3f7; @reno-muted-text-color: #8d939e; @reno-muted-text-color-alternate: #6c8dae; @reno-primary-background: #1a0ce7; @reno-secondary-background: #e2e9ef; @reno-success-background: #8bccde; @reno-success-color: @reno-text-color; @reno-warning-background: #FFD15E; @reno-warning-color: @reno-text-color; Ideally we would have two skins, one skin for dark main colors (like blue, dark orange, red etc) and one for bright colors (yellow, light green etc). Then we could just choose between light.less or dark.less and modify the color to fit to our client's CI. Then change the logo and boom, we have a completely customized admin that feels like home for the client.1 point
-
Using native css variables would be nice, browser support according to caniuse is not that bad: https://caniuse.com/#feat=css-variables1 point
-
Let's say you want to add an image using the PW API from an outside service that doesn't provide a clean filename like my-cool-image.jpg (such as from dummyimage.com, urlbox.io, etc). Doing it this way will add the image successfully, but the filename will be problematic and have an invalid extension (bad): $p = $pages->get(1); $p->your_single_image_field = "https://dummyimage.com/600x400/000/fff"; $p->save(); Instead, you need to rename the file right before save (good): $p = $pages->get(1); $p->your_single_image_field = "https://dummyimage.com/600x400/000/fff"; $p->your_single_image_field->first->rename("better.png"); // need 'first' here $p->save();1 point
-
Am i right in thinking this is a PHP 7 issue and not so much a Processwire issue? General google seems to suggest so. Just moved a site to a new host and getting following on a simple call Fatal error: Uncaught ArgumentCountError: Too few arguments to function ProcessWire\Pageimage::size(), 1 passed in /home/site.not/site/assets/cache/FileCompiler/site/templates/team-detail.php on line 43 and at least 2 expected in /home/site.not/wire/core/Pageimage.php:519 Stack trace: #0 /home/site.not/site/assets/cache/FileCompiler/site/templates/team-detail.php(43): ProcessWire\Pageimage->size(150) #1 /home/site.not/wire/core/TemplateFile.php(287): require('/home/...') #2 /home/site.not/wire/core/Wire.php(380): ProcessWire\TemplateFile->___render() #3 /home/site.not/wire/core/WireHooks.php(714): ProcessWire\Wire->_callMethod('___render', Array) #4 /home/site.not/wire/core/Wire.php(442): ProcessWire\WireHooks->runHooks(Object(ProcessWire\TemplateFile), 'render', Array) #5 /home/site.not/wire/modules/PageRender.module(514): ProcessWire\Wire->__call('render', Array) #6 /home/site.not/wire/core/Wire. in /home/home/site.not/wire/core/Pageimage.php on line 519 <?php foreach($page->images as $image){ $thumb = $image->size(150); echo "<img src='{$thumb->url}' class='team-photo-detail' alt='{$thumb->description}'" ; }?>1 point