Jump to content


  • Content Count

  • Joined

  • Last visited

  • Days Won


eelkenet last won the day on December 5 2018

eelkenet had the most liked content!

Community Reputation

68 Excellent

About eelkenet

  • Rank
    Full Member

Profile Information

  • Gender
    Not Telling
  • Location

Recent Profile Visitors

2,192 profile views
  1. I ran into issues with a slow server using this method, where wireHttp would already resolve a result while RestApi was still running. So with help from my co-worker @harmvandeven we came up with a more stable version, with some more redundancy: <?php /* api.php, a simple PW template to fill the gap between the RestApi and ProCache modules v2 @author: @eelkenet, with help from @harmvandeven & @ryan 1. Create a template called 'api' and set it up using the following settings: - Allow URL segments - Allow guests to view the page - Set the Content-Type to application/json - Make sure to NOT Prepend or Append any files 2. Add a page using this template and allow ProCache to run its magic */ $timeout = 600; $maxAttempts = 10; // Pre-check for unwanted symbols if (strpos($input->urlSegmentStr(), '.') !== false) { throw new Wire404Exception(); } // Build request URL $endpoint = $modules->get("RestApi")->endpoint; $url = $input->scheme() . "://" . $config->httpHost . "/" . $endpoint ."/" . $input->urlSegmentStr(); $http = new WireHttp(); // Set a high timeout, to deal with a slow server $http->setTimeout($timeout); // Get the HTTP status of the page, to make sure it exists in the first place $status = $http->status($url); // If the page exists, or possibly redirects to valid content if ($status >= 200 && $status < 400) { $result = false; $attempt = 0; // If the result isn't a string, something went wrong while(gettype($result) !== "string" && $attempt++ < $maxAttempts) { $result = $http->get($url); if ($attempt > 1) wire()->log->message("Loading content at $url, attempt $attempt: " . gettype($result)); } // Double check if the data is a string.. if (gettype($result) === "string"){ // .. And check if it can be decoded, if so: return the data and thus cache it if (json_decode($result) !== NULL) return $result; // If it cannot be decoded: throw exception (don't cache it) throw new WireException("Found the data at $url, but it could not be decoded. Please check your API output!"); } // Throw exception if data could not be loaded in time (don't cache it) throw new WireException("Found the data at $url, but could not load it in time, after $attempt attempts. Result has type: " . gettype($result)); } // Throw generic exception if the requested page was not found or there was another error throw new WireException("Failed to load the content at: $url, with HTTP status: " . $status);
  2. An editor wanted to be able to preview the 'alternative page' we use during maintenance, but whenever he would try to view it he would be redirected to the homepage. So to me it makes sense that anyone who is allowed to bypass the maintenance page during maintenance, should also be able to preview the alternative page. Luckily, this is just a tiny code adjustment. Replace line 133-136 with: // Else if we're not in maintenance mode and we're not an administrator (and our role isn't in the list of allowed roles to access the site in maintenance mode), make sure the maintenance page redirects to the homepage } elseif (!$this->inMaintenance && $this->showPage && ($page->id == $this->showPage) && !$user->isSuperuser() && !array_intersect(explode('|', $user->roles), $this->bypassRoles)) { $this->session->redirect($this->config->urls->root, false); }
  3. I'm sorry @thomasaull, but I don't believe I understand the module's inner workings well enough to pull that off 😕 Here is the final cleaned-up and more secure 'api' template that I am using in between the RestApi router and ProCache, perhaps it can be of some help: Edit: check this reply for an updated version:
  4. Hi @thomas, no problem. And yes, definitely! I think that would be much better. Anything that comes from inside the regular page rendering can be cached with ProCache.
  5. eelkenet


    Hi @Wanze, congrats on your nice module! I'm currently testing it out for a client and find it overall very nice, thank you for your work! I have some feedback, even though I'm not sure if I'm understanding everything you do correctly. So please excuse me if I'm mistaken 😅 Having to set the default values for the meta tags on the field/template level, instead of using the PageTree (the Page level) seems unpractical. Most of the time I give a site editor access to the PageTree, sometimes to some modules, but I definitely keep them away from 'dangerous' stuff like Fields and Templates. However, editors should always be able to edit the SEO information without bugging the developer for an update. In other words, the preferred place for the default SEO information should imho be at the root of the tree: the homepage. (Or perhaps inside the a module config). To me it would make total sense that SEO information follows the same inheritance structure as pages do. Often rendered titles will consist of a page title and some ancestor title. So you should be able to select the parent's title and expand on it, much like the 'List of fields to display in the admin Page List') -home (title: "My Site") |-projects (title: "Projects") ||-project1 (title: "Project number 1") ||-project2 (title: "Another project") It would make sense to be able to get the following: -home (seo.meta.title: {title} would result in "My Site") |-projects (seo.meta.title: {title} - {parent.title} would result in "Projects - My Site") ||-project1 (seo.meta.title: {title} - {parent.parent.title} would result in "Project number 1 - My Site") ||-project2 (seo.meta.title: {title} | {parent.title} | {parent.parent.title} would result in "Another project | Projects | My Site")
  6. Ah, I did not know that. Makes sense as a limit for the full posts. For titles though.. not sure. Besides, Invision Community (the forum software) supports it on their own support forum just fine: https://invisioncommunity.com/search/?q=SEO
  7. While I know there are a couple of modules, topics and posts regarding SEO, it is impossible to search for these: for instance https://processwire.com/talk/search/?q=SEO&amp;type=forums_topic yields no results. It seems that the minimum input length is currently set at 4 characters, though this is not mentioned. So my request, if technically feasible: can the minimum search length please be reduced to 3 character long strings? If possible 2-letter combinations could be valuable as well, though that could be a bit too much of a strain on the database I guess.
  8. Congratulations on the launch, Ryan! I do have issues with the iMac/screenshots images though, as others have said before me. Not because it is an iMac, but because of the impossible size of the content. My first reaction as a visitor is to squint my eyes and to move my head close to the monitor to see the actual PW screenshots inside it. And to be honest I think that is a problem, because: The very first impression, the first thing people look at, on any website will always be graphics/images, not text. You only get one shot at a first impression The first impression should be very clear and very convincing The iMac frame and code in the background take about 75% of the available real estate, making the actual screenshots tiny! A simple sliding carousel or slideshow would suffice, possibly showing steps like: The pagetree Editing a page Cropping an image Creating fields Creating template
  9. Great work Ryan, thank you! I fully agree with this. Our main use of Processwire is as a headless CMS / CMF for Single Page Applications. As a possible future user I would be very interested in learning about how PW can be used in this way. I would look for that on the 'Output' page (https://processwire.com/newsite/docs/front-end/output/). A simple JSON API example, without the use of any external modules, would suffice here I think.
  10. I had a quite some trouble getting this to work with the content from a ProFields Table, as this does not work the same as with a Repeater or PageTable. But I am happy to say that treating those fields as a 'file' works, by setting this in the options array. Ie: <?php $finder = new RockFinder("template=template_name,limit=10", ["title", "some_field_name"]); $field = $finder->addField("my_table_field", ["column_name", "other_column"], ["type" => "file"]); $field->seperator = ", ";
  11. Hmm, that is weird, in that case it is probably being reset somewhere. Have you tried doing a full search of your project's code for places where it might be overridden? Maybe some module or template is accidentally setting it, instead of checking for the value.
  12. You should not overwrite this file. Instead, edit the config in site/config.php.
  13. I ran into this issue too. I edited the Mollie module to fit my own needs, a bit too many changes to create a merge request, but it deals with this issue. <?php namespace ProcessWire; // Here you can find more information about the Mollie php api https://github.com/mollie/mollie-api-php require_once __DIR__ . "/vendor/autoload.php"; class PaymentMollie extends PaymentModule { public static function getModuleInfo() { return [ 'title' => 'PaymentMollie', 'version' => '0.0.2', 'summary' => 'Mollie Payment method', 'singular' => false, 'autoload' => false, 'requires' => 'ProcessWire>=3.0.98, PaymentModule', ]; } public function init() { ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); } public function getTitle() { return $this->_("Mollie: iDeal, Bancontact"); } public function getFailureReason() { return "Error"; } public function processPayment() { $payment_id = wire()->input->post->id; $order_id = wire()->input->urlSegment2; $mollie = new \Mollie\Api\MollieApiClient(); $mollie->setApiKey($this->test_api_key); if ($this->live_check == "1") { $mollie->setApiKey($this->api_key); } $payment = $mollie->payments->get($payment_id); $order_id = $payment->metadata->order_id; wire()->log->save("mollie", "Processing payment for... $order_id"); if ($payment->isPaid() == true) { wire()->log->save("mollie", "Order $order_id was paid using mollie!"); $order = wire()->pages->get($order_id); $order->of(false); $order->pad_paid = time(); $order->addNote($this->_("Order paid using Mollie") . ": " . $payment->id); $order->removeStatus(Page::statusUnpublished); $order->save(); return true; } elseif ($payment->isOpen() == FALSE) { return false; } return false; } public function getMollieMethods() { $mollie = new \Mollie\Api\MollieApiClient(); $mollie->setApiKey($this->test_api_key); if ($this->live_check == "1") { $mollie->setApiKey($this->api_key); } $methods = $mollie->methods->all(); $return = [ ]; foreach ($methods as $method) { $return[] = [ "title" => htmlspecialchars($method->description), "icon" => htmlspecialchars($method->image->size1x), "icon2x" => htmlspecialchars($method->image->size2x), ]; } return $return; } public function render() { $output = ""; $mollie = new \Mollie\Api\MollieApiClient(); $mollie->setApiKey($this->test_api_key); if ($this->live_check == "1") { $mollie->setApiKey($this->api_key); } $paymentInfo = [ "amount" => [ "value" => number_format(($this->getTotalAmount() /100), 2, '.', ''), "currency" => 'EUR', ], "description" => "Bestelling van: " . $this->customer->givenName . " " . $this->customer->familyName, "redirectUrl" => $this->httphost . "checkout/?id=" . $this->id, "webhookUrl" => $this->httphost. "checkout/processmollie/" . $this->id . "/", // Specify here wat you want "metadata" => [ "order_id" => $this->id, /* "customer_name" => $this->customer->givenName, "customer_familyName" => $this->customer->familyName, "customer_address" => $this->customer->streetAddress, "customer_locality" => $this->customer->locality, "customer_postalCode" => $this->customer->postalCode, "customer_country" => $this->customer->country, "customer_email" => $this->customer->email */ ], ]; // $output .= "<pre>" . print_r($paymentInfo, true) . "</pre>"; $payment = $mollie->payments->create($paymentInfo); wire()->log->save("mollie", "Creating the payment... $order_id"); // Type here your custom HTML // use $payment->getPaymentUrl() as url for your payment link foreach ($methods as $method) { $output .= '<div style="line-height:40px; vertical-align:top">'; $output .= '<img src="' . htmlspecialchars($method->image->size1x) . '" srcset="' . htmlspecialchars($method->image->size2x) . ' 2x"> '; $output .= htmlspecialchars($method->description) . ' (' . htmlspecialchars($method->id) . ')'; $output .= '</div>'; } $output .= "</div>"; $output .= "<div><form action='{$payment->getCheckoutUrl()}'> <button type='submit'>". __('Pay') ."</button></form></div>"; return $output; } public static function getModuleConfigInputfields(array $data) { $inputfields = new InputfieldWrapper(); // Test API $live_api_field = wire('modules')->get('InputfieldText'); $live_api_field->name = 'test_api_key'; $live_api_field->label = __("Test API Key"); $live_api_field->notes = __(""); if (isset($data['test_api_key'])) { $live_api_field->value = $data['test_api_key']; } $inputfields->add($live_api_field); // Live API $test_api_field = wire('modules')->get('InputfieldText'); $test_api_field->name = 'api_key'; $test_api_field->label = __("Live API Key"); $test_api_field->notes = __(""); if (isset($data['api_key'])) { $test_api_field->value = $data['api_key']; } $inputfields->add($test_api_field); // Select live or test API $check_api_field = wire('modules')->get('InputfieldCheckbox'); $check_api_field->name = 'live_check'; $check_api_field->checked = $data['live_check']; $check_api_field->label = __("Use Live API Key"); $check_api_field->notes = __("If not selected, the test-api will be used. Keep in mind that you also have to activate the Live api on your Mollie dashboard"); if (isset($data['live_check'])) { $check_api_field->value = $data['live_check']; } $inputfields->add($check_api_field); // HTTP Host url $httphost_field = wire('modules')->get('InputfieldURL'); $httphost_field->name = 'httphost'; $httphost_field->label = __("HTTP Host"); $httphost_field->notes = __(""); if (isset($data['httphost'])) { $httphost_field->value = $data['httphost']; } $inputfields->add($httphost_field); return $inputfields; } }
  14. I ran into an issue that is related to the way the RestAPI circumvents the pagetree structure (running the checkIfApiRequest hook before rendering any page). This method made it impossible to use ProCache for API requests that could (and should) return a cached result, such as for static site content. I thought about creating a custom caching system on top of RestApi, but ProCache is just too well designed to ignore here. I wrote a post about this on the ProCache VIP-forum, but as this forum is not accessible to all people I'd like to share my (admittedly hacky) solution for this. Basically I add another (cacheable) endpoint in the pagetree, which pipes the request to the RestApi endpoint: Create a new template and corresponding page (I called both 'api'). Set the content-type of this template to application/json, and disable any prepending/appending of files. Add the following code to the template: <?php //site/templates/api.php $protocol = $config->https ? "https://" : "http://"; $endpoint = $modules->get("RestApi")->endpoint; $hostname = $config->httpHost; $segments = implode("/", $input->urlSegments); $url = $protocol.$hostname."/".$endpoint.$segments; return file_get_contents($url); I'm sure there would be a better, cleaner way of doing this. A current downside is that there now are 2 seemingly identical endpoints for my site. One is cached, and the other is 'live'. Any ideas?
  • Create New...