Leaderboard
Popular Content
Showing content with the highest reputation on 03/03/2022 in all areas
-
I could be wrong, but what I think @wbmnfktr is looking for is a **standardized** Processwire APIs across all Processwire installations that is on by default? This could be beneficial by allowing: third party services to integrate easily with Processwire. Something like zapier.com could build a Processwire connector that consumes the API to allow for no-code workflows that connect different systems and services together? a site aggregator website that could consume the other Processwire website's API and report back the details. For example, which sites need module or Processwire updates. Something like https://sitedash.app/ for Modx Static Site Generators to consume and build a fast static website that can be hosted on a global CDN. a Single Page Application built with Vue.js/React.js/React Native, etc.. that could be replace the Processwire Admin. I think https://www.sanity.io/ can do this? Everything is fully decoupled. Why would you want a different admin? What if you wanted to build a native Mobile app to administer your Processwire site? admin components that consume that consume the API for different admin experiences? Wordpress uses the API for their new Block Editor https://developer.wordpress.org/block-editor/ Sure stuff like sitedash.app can be built right now with Processwire, but services like zapier.com and others aren't going to spend time building a API connector if it isn't included in Processwire core and isn't standardized. I agree with flydev - there are other things to consider as well like issuing API tokens, content throttling, API versioning, providing data in different formats other than json and REST like GraphQL, webhooks, autogenerated API documentation like https://swagger.io/. https://api-platform.com/ covers a lot of these topics. https://strapi.io/ does a good job with some of these things like issuing tokens for integrating third party clients. Thanks everyone for posting solutions that could work. I enjoying reading and watching the many different ways you can do things with and without modules. Thanks @flydev ?? for the AppAPI demo. Thanks @bernhard for showing/creating the RockHeadless module and demo - dang your fast. I like how you demonstrate how you can also expose the children of certain pages to the API as well. That's is another aspect that has to be considered since Processwire is different than most bucket based CMSs. Processwire is tree based around hierarchy.5 points
-
On my side, from what I understand (and what I would like to see implemented) is something like the following : - in config.php set $config->restapi = [ 'endpoint' => 'api', 'enabled' => true]; then use simples built-in functions to get data from api (eg, home page) GET /api/1 Anyway, even if it could/should be easy to use, things are a bit more complex than what we can see here in this thread. I mean, almost all functionalities of AppApi should definitely exists.4 points
-
Hello, I would really appreciate your comments on this blog post I am preparing for our site. =========== Creating a dynamic search with very little code in ProcessWire is easy. This search cannot compete with engines like Elasticsearch, Solr, and others. However, it is suitable for most "showcase" sites. Here is how we did it on the Spiria site using the small library htmx and its companion hyperscript. The goal The recipe Inclusion of libraries htmx and hyperscript (the latter is optional). A field of type textarea integrated to the page models that we want to index. A code for indexing the existing content in the file `ready.php` A search controller which we name here `api.php`. This controller will also be a page with the `api` template. A form placed in the pages requiring the search. Content indexing Before we can program, we need to index the content on which we want to apply our search. In my proof of concept, I have developed two strategies. This is probably overrated, because I am not sure of the gain in speed. Index for a single term search Indexing for a multiple term search To do this, we need to introduce two fields in each model where we want an indexation. The `search_text` field which will contain only one occurrence of each word on a page. The `search_text_long` field which will preserve all sentences without HTML tags. We place a hook in the `ready.php` page in this way: <?php namespace ProcessWire; pages()->addHookAfter("saveReady", function (HookEvent $event) { $p = $event->arguments[0]; switch ($p->template->name) { case "blog_article": $french = languages()->get('fr'); $english = languages()->get('default'); $txt_en = $p->page_content->getLanguageValue($english) . ' ' . $p->blog_summary->getLanguageValue($english); $txt_fr = $p->page_content->getLanguageValue($french) . ' ' . $p->blog_summary->getLanguageValue($french); $title_en = $p->title->getLanguageValue($english); $title_fr = $p->title->getLanguageValue($french); $resultEn = stripText($txt_en, $title_en); $resultFr = stripText($txt_fr, $title_fr); $p->setLanguageValue($english, "search_text", $resultEn[0]); $p->setLanguageValue($english, "search_text_long", $resultEn[1]); $p->setLanguageValue($french, "search_text", $resultFr[0]); $p->setLanguageValue($french, "search_text_long", $resultFr[1]); break; } }); And function stripText($t, $s) { $resultText = []; $t = strip_tags($t); $t .= " " . $s; $t = str_replace("\n", " ", $t); $t = str_replace(",", "", $t); $t = str_replace("“", "", $t); $t = str_replace("”", "", $t); $t = str_replace("'", "", $t); $t = str_replace("?", "", $t); $t = str_replace("!", "", $t); $t = str_replace(":", "", $t); $t = str_replace("«", "", $t); $t = str_replace("»", "", $t); $t = str_replace(",", "", $t); $t = str_replace(".", "", $t); $t = str_replace("l’", "", $t); $t = str_replace("d’", "", $t); $t = str_replace(" ", "", $t); $t = preg_replace('/\[\[.*\]\]/', '', $t); //$t = preg_replace('/\?|\[\[.*\]\]|“|”|«|»|\.|!|\ |l’|d’|s’/','',$t); $arrayText = explode(" ", $t); foreach ($arrayText as $item) { if (strlen(trim($item)) > 3 && !in_array($item, $resultText)) { $resultText[] = $item; } } return [implode(" ", $resultText), $t]; } If you have the ListerPro module, it becomes easy to save all the pages to be indexed in batch and any new page will then be indexed as it is created. The `stripText()` function aims at cleaning up the text as we want. Note that, in my example, I distinguish between French and English. This little algorithm is entirely perfectible! I have commented a shorter way to clean up the text, but at the expense of comprehension. As mentioned before, it is probably unnecessary to create two search fields. The most important thing would be to optimize the text as much as possible, as many small words are useless. The current code restricts to words longer than three characters, which is tricky in a computing context like our site where names like `C#`, `C++`, `PHP` compete with `the`, `for`, `not`, etc. That said, perhaps this optimization is superfluous in the context of a simple content and not and limited in number. So now see the process and the research code. The structure structure.svg This scheme is classical and needs few explanations. The `htmx` library allows a simple Ajax call. The form code01.svg The form has a `get` method that returns to a conventional search page when the user presses the `enter` key. A hidden field with the secret key generated on the fly enhances security. The third field is the `input` of the dynamic search. It has a `htmx` syntax. The first command, `hx-post`, indicates the method of sending the data to the API. Here, it is a `post`. `htmx` allows to handle events on any DOM element. So we could have several calls on different elements of a form. The second line indicates where the API response will be sent when received, which is `div#searchResult` below the form. The `hx-trigger` command describes the context of sending to the API. Here, when the user releases a key, with a delay of 200 ms between each reading of the event. The `hx-indicator` command is optional. It tells the user that something is happening. In the example, the `#indexsearch` image is displayed. This is automatically handled by htmx. We have the possibility to pass other parameters to the search with the `hx-vals` command. The given example is simplified. We send the search language. The last command comes from the `hyperscript` syntax. It indicates that if you click anywhere outside the search field, you will make the contents of `#found` disappear, which will be described below. It is clear from this example that no javascript is called, except the [htmx] and [hyperscript] libraries. It is worth visiting the web site of these two libraries to understand their philosophy and possibilities. The Search API The API resides in a normal ProcessWire page. Although it is published, it remains "hidden" from CMS searches. This page allows requests to be answered and the correct functions to be called. Several requests to the CMS can be gathered in this type of page. <?php namespace ProcessWire; $secretsearch = session()->get('secretToken'); $request = input()->post(); $lang = sanitizer()->text($request["lang"]); if (isset($request['CSRFTokenBlog'])) { if (hash_equals($secretsearch, $request['CSRFTokenBlog'])) { if (!empty($request["search"])) { echo page()->querySite(sanitizer()->text($request["search"]),$lang); } } else { echo __("A problem occurred. We are sorry of the inconvenience."); } } exit; In this case : We extract the secret token of the session, token that will have been created in the search form page. We then process everything that is in the `post` query. Note that this is a simplified example. We compare the token with the one received in the query. If all goes well, we call the SQL query. Our example uses a class residing in `site/classes/ApiPage.php`; it can therefore be called directly by `page()`. Any other strategy is valid. The following code represents the core of the process. <?php namespace ProcessWire; public function querySite($q, $l) { $this->search = ""; $this->lang = $l == 'en' ? 'default' : 'fr'; user()->setLanguage($this->lang); $whatQuery = explode(" ", $q); $this->count = count($whatQuery); if ($this->count > 1) { $this->search = 'template=blog_article,has_parent!=1099,search_text_long~|*= "' . $q . '",sort=-created'; } elseif (strlen($q) > 1) { $this->search = 'template=blog_article,has_parent!=1099,search_text*=' . $q . ',sort=-created'; } if ($this->search !== "") { $this->result = pages()->find($this->search); return $this->formatResult(); } return ""; } protected function formatResult() { $html = '<ul id="found">'; if (count($this->result) > 0) { foreach ($this->result as $result) { $html .= '<li><a href="' . $result->url . '">' . $result->title . '</a></li>'; } } else { $html .= __('Nothing found'); } $html .= '</ul></div>'; return $html; } The `formatResult()` function is simple to understand and it is here that we see the `ul#found` tag appear which, remember, is deleted by the _hyperscript_ line of the form. _="on click from elsewhere remove #found" Actually, I've commented out this line in the production code so far. Instead of clicking anywhere, it would be better to add a background behind the form when doing a search in order to focus the `click` event. But again, all strategies are good! In the current code, it is not necessary to add CSS to display the result. It is placed in an empty `#searchResult` tag, so it is invisible at first. As soon as it is filled by the search result, everything becomes accessible, the CSS being targeted on the `ul#found` list and not on its parent. Conclusion The purpose of this article was to demonstrate htmx and the possibilities that this library allows. There is already an excellent search module for ProcessWire, SearchEngine, which can coexist very well with the code described here.1 point
-
@cpx3 give a read to @MoritzLost's article: https://processwire.dev/performance-optimizations-for-processwire/#server-side-caching-template-cache-and-procache and there https://github.com/MoritzLost/CachePlaceholders - #motivation-and-breakdown1 point
-
Noted. Before I spotted your reply though I'd hooked into ProcessPageEdit to load an external js file: function resizeEditor(HookEvent $event){ $js_path=wire()->config->urls->templates . "resize_editor.js"; wire()->config->scripts->add($js_path); } wire()->addHookAfter('ProcessPageEdit::execute', null, 'resizeEditor'); and then that JS looks like this: // resize ckeditors to height of container. if(typeof CKEDITOR !== 'undefined'){ CKEDITOR.on('instanceReady', function(event){ var input_name=event.editor.name; // this is the id of our field. var parent_height = document.getElementById(input_name).parentElement.offsetHeight; console.log('Setting editor ' + event.editor.name + ' height to' + parent_height); event.editor.resize( '100%', parent_height ); }); } That sets all the editors to the height of their containers.1 point
-
If you do some testing note that it's really easy to add custom markup to any inputfield via hook: <?php $wire->addHookBefore("Inputfield(className=InputfieldCKEditor)::render", function($event) { $field = $event->object; $id = $field->id; $field->appendMarkup = "<script> \$li = $('#wrap_$id'); console.log(\$li); </script>"; }); Looking forward to your results ?1 point
-
I still don't get what you are looking for. I thought I understand your need: Making the process more standardized and easier for non-devs meaning more click click instead of writing code. That's what I tried to solve with my proof of concept module. But you said "that looks great" in your first post and then turned around and it does not seem to be what you are wanting... Eh nothing, all is ready, we have even more with JWT implementation. It's just missing a function which return the pages tree. Something like less than 10 lines of code. This question was targeted to @wbmnfktr to better understand him. When we where talking about easy json feed the very first time my answer was: Url hook + findRaw + json_encode... ( https://processwire.com/talk/topic/19112-module-page-query-boss/?do=findComment&comment=221874 ) I don't agree on this one. I have not tried the module (because I have not had the need), but it looks complicated to me. Something that definitely would take longer than 2 min even just to understand how to set it up. That's why I built my module - super simple to setup, no code necessary. But it is still not what @wbmnfktr is looking for, so I'm confused and it feels like we are going in circles...1 point
-
In case you are interested in my thoughts... I started to write down a kind of a concept about this - not yet complete by any means! Google Drive: https://docs.google.com/document/d/e/2PACX-1vSTT9seNtgtB2AzKaMx_xZl9S1HvsR00wrlfPWhtu6D-_sfYacCO0Cbs_PT6KnUTXh8F7EahjSTgEXn/pub You can't edit the document, but I might move this to Github/Markdown where you can fork and therefore add pull requests... but for now this is my collection of thoughts.1 point
-
I don't use them at all. No need at all, after deciding ProCache's benefits have more weight. And... I really never had the need or a real reason to use sessions - besides user/member pages - but in those cases my setup looks different right from the start and even those don't use sessions. In case I need something like that, I most often use cookies or local storage. Still I can count all those scenarios on 5 fingers. What's your setup or goal for your setup/project? Why do you need those sessions? In case for search or a limited part of your sites... think about disabling ProCache for those pages.1 point
-
Oh... ok... I never use anything with sessions as they don't work with ProCache (or at least not for me), which is almost always present in my projects. Do you have any caching in place? Maybe that's the reason it doesn't work. But for now... no... sorry. No clue why it doesn't work out in sessions.1 point
-
Depending on how many locations you have findRaw() might be a useful alternative to find()1 point
-
As those mapmarker details are saved within those fields for any page, you could just query them. echo $page->yourMapMarkerField->address; // outputs the address you entered echo $page->yourMapMarkerField->lat; // outputs the latitude echo $page->yourMapMarkerField->lng; // outputs the longitude echo $page->yourMapMarkerField->zoom; // outputs the zoom level https://processwire.com/modules/fieldtype-map-marker/ And from there you could pass it on to your template to build a custom map. <?php namespace ProcessWire; $mapitems = $pages->find("template=yourTemplateWithMapMarkers"); $options = array( 'markerIcon' => 'fa-icon', // your custom icon 'markerColour' => 'color', // your custom color 'height' => '100%', 'zoom' => '12', 'scrollWheelZoom' => 'false', 'useStyles' => 'false', 'popupFormatter' => function ($page) { $lines = array(); $lines[] = "$page->street"; $lines[] = "$page->postalcode $page->city"; return implode("<br />", $lines); } ); ?> That's what I use with the LeafletMapMarker Module (a fork of Map Marker for Google) Which then results in this (see bottom of that page): https://www.restaurants-neumuenster.de/restaurants/1 point
-
To add my two cents here... I'm totally on board with this and I started to donate in 2019, through 2021 to some devs for their modules. Most often through their publicly available and mentioned ways, such like PayPal, BuyMeACoffee, ... There is one thing with this... as I wrote some of them about it just to tell and check... they said: "Oh... don't have that account anymore!" or "Oh... that changed!". So... to all of us in some kind: please check if those PayPals and Co. still are up to date. To those that donate: write a message to your favourite devs. Feels awkward, but at least you can be sure the money goes where it should go. Another almost unrelated thing: I stopped using my DEV-licenses for client projects. Sure... that's what they are for but... even a hairdresser can afford FormBuilder and ProCache. Why stop there? I use those only for my very personal projects now and I feel much better with this.1 point
-
Something I remember trying too, but don't recall if it worked, is adding some Js to the admin to dynamically add the options after an ajax call literally editing the markup. If you're working on recent PW version I'm going it should be straightforward with the new URL hooks. So: Listen to on change event on the theme fiel, call backend with picked value, and do some js work to generate the options for the field and append them to the highlight colors select. Maybe something like this will work better for the scenario where you wan't to use repeaters, although I guess it will involve more work to handle the creation of new repeaters, but I am almost sure there are js events for that. Another idea that I also don't remember if it worked is, using the reload method on the inputfield.js API. So maybe then you just have to listen to the on change on the first field, and trigger reload? Maybe that'll load the field again with the right options?1 point
-
Thanks for the clarifications, Ivan. At the moment the <?= $partials->sublayouts->sublayout_name() ?> approach is probably the closest alternative one to what you've been using. Or, alternatively, you could split common parts of the layout to partials, and include those in two different layouts, leaving out the layout/sublayout separation altogether. That being said, I just mocked up locally a setup where layouts can be embedded within layouts. This is probably even closer, though I feel like it needs a bit more polish and testing ?1 point