Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 08/08/2018 in all areas

  1. Wow, @adrian! I had no idea that you have taken time to create some nice documentation for your module. Great work!
    3 points
  2. TracyDebugger's Console Panel - https://adrianbj.github.io/TracyDebugger/#/debug-bar?id=console
    3 points
  3. Hello Soma and everyone else who helped me to solve my problem. SOMA: Thanks for your suggestion, which also works well and which I took as standard because of the shorter code. It's amazing how many different solutions Processwire has to offer. I've learned a lot. Thanks again to everyone and best regards Hajosh
    3 points
  4. @louisstephens I really like the answer @horst posted about this but want to ask if you intend using Lazycron for doing this? If so, please be aware of the potentially long processing times associated with doing things this way, especially on the initial read of the feed. Also; there is no facility above for removal of items that no longer appear in the feed but that are stored in PW pages. You might not need to do this though, it all depends on your application. If anyone's interested, the way I've tackled this before, in outline, is to pre-process the feed and essentially do what horst posted about calculating a hash of the content (personally I don't like crc32 which returns an int but prefer the fixed length strings returned by md5 (which is fine for this - and fast)). Do filter out any feed fields that you don't intend to store before you calculate the hash so that insignificant changes in the feed don't trigger un-needed updates. Anyway, this gives a feed_id => hash_value map for each feed item. If we do this for the feed, we end up with a PHP array of these maps. This array can be stored persistently between each read of the feed. Let's call the previously created map, $prev_map, and the map for this read of the feed, $new_map. You simply use PHP's built-in array methods to quickly find the records that have... Been added: $to_be_added = array_diff_key($new_map, $prev_map); Been deleted: $to_be_deleted = array_diff_key($prev_map, $new_map); Been updated: $to_be_updated = array_uintersect_assoc($new_map, $prev_map, 'strcasecmp'); ...all without having to go to the DB layer with selectors. On the first run, when the $prev_map is an empty array, you'll be facing a full import of the feed - potentially a LOT of work for PW to do adding pages. Even reads of the feed that add a lot of new pages or update a lot of pages could require mucho processing, so you'll need to think about how you could handle that - especially if all this is triggered using LazyCron and takes place in the context of a web server process or thread - having that go unresponsive while it adds 100,000 pages to your site may not be considered good. Finally, don't forget to overwrite $prev_map with $new_map and persist it. * NB: I've not done item 3 exactly this way before (I used array_intersect_key()), but I don't see why array_uintersect_assoc() shouldn't work.
    2 points
  5. Thanks ? It's a decent start, but I am kinda hoping for some contributions from the community for improving it. It's all available in the docs folder (markdown files) in the repo and anyone can edit and submit PRs. I think a nice Tips & Tricks section would be beneficial, as would improvements to the rest of the docs.
    2 points
  6. PHP is not properly running on the server. If it's a fully managed server I suggest to submit a ticket to get them to fix it. It could be that the apache handler for php files is not configured, but there could be other issues as well.
    2 points
  7. Anyone using Google Maps and unhappy with their recent changes might want to explore the updated ToS from maps service, Here https://venturebeat.com/2018/08/03/here-launches-new-freemium-plan-for-developers-in-response-to-google-maps-api-changes/
    2 points
  8. Small update video below. There are again many changes behind the scenes. Multiple instances of "Gridbuilder" can now exist on the same page. More "in-memory-actions" (JS) means less need to press the save button. But normal PW templates are still the base of everything. Images can be dropped directly onto a grid_image template. See video. http://theowp.bplaced.net/upload/pics.html Thank you.
    2 points
  9. Ok, I acted too fast, sorry. Solution found in this topic. I have to use the role id, not the role name. So, for example, "roles=1040" instead of "roles=client".
    2 points
  10. Hey guys, Thought I would share a quick preview of Designme. A module we (Eduardo @elabx and I) are building for visually laying out your templates/edit screens. ? This is a really quick, zero polish screen grab. FYI. Video #2 - UPDATE This new video shows the following features in Designme: Re-arranging fields via Drag & Drop Re-sizing fields via Dragging. Adjusting field settings - with live refresh. Working on "hidden" fields while Designme is active. Creating New fields. Deleting fields. Creating/Deleting Tabs. Dragging fields between tabs. Creating fieldsets. Tagging/Un-tagging fields. Fields without headers expand when hovered (like checkboxes). Live filtering of fields in the sidebar. Ability to adjust (all) Template settings without leaving Designme. Template File Tree Editing Template files source code with ACE Editor. Editing Multiple files with ACE Editor. (New Tabs) Saving files. Techie stuff Fields load their own js/css dependancies. *ready to use on creation (*most fields) Everything happens via Ajax to ProcessPageEdit (via module + hooks). Designme has a JS api that you can use. All actions trigger events. We would love any detailed feedback on what you see so far. If you are interested in testing Designme. Let me know below. ? Video #1.
    1 point
  11. 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.zip
    1 point
  12. I don't want to start a discussion here, but imho cookieless domains are way overrated. If you use AIOM, gzip, use cache wisely, etc. chances are you won't see any noticeable difference.
    1 point
  13. There are a few discussions in the forums regarding this, but I don't think any of them gave a definite solution. @teppo also created a module about 5 years ago for relocating assets. However, I am not sure regarding the compatibility with the latest processwire versions. https://github.com/teppokoivula/PageRenderRelocateAssets You might also want to look at the AIOM+ module for processwire which has domain sharding (for CSS, LESS, JS, and html).
    1 point
  14. Hey @adrian, what is that Console window? looks really usefull
    1 point
  15. From the API side it is still valid and working code. I don't think there is one best way to go. This tutorial shows one possible approach. There are many different other approaches possible. But at least it shows the basics of implementing a REST API. You can also have a look at https://modules.processwire.com/modules/rest-api-profile/ which is the most recent approach I'm aware of.
    1 point
  16. It worth to mention that there is an option called 'Language Support / Blank Behavior' on'Details' tab in the field config. Maybe it's somehow relative to your case.
    1 point
  17. Using the uncache has nothing to do with the function calls. Uncaching is usefull in loops, at least with lots of pages, to free up memory. Every page you create or update or check values of, is loaded into memory and allocate some space. Without actively freeing it, your available RAM gets smaller and smaller with each loop iteration. Therefor it is good practice to release not further needed objects, also with not that large amount of iterations.
    1 point
  18. Not new, but a website I've worked on for a year or so in continous development. http://supercarownerscircle.com/ I originally inherited the site from another web design company - upgraded PW and over the year added ecommerce using the Stripe module and a custom integration, revamped the frontend and added lots of new templates. Also did some frontend work on the shopify shop.
    1 point
  19. I have ff Works too and often use it to covert video files. While I like its GUI and ffmpeg too, the fact that ff Work's GUI is not mac OS native makes it a bit cumbersome to use in some cases but for the price/features ratio I have not found a better alternative.
    1 point
  20. Two or three things come to my mind directly: If there is no unique ID within the feed, you have to create one from the feed data per item and save it into an uneditable or hidden field of your pages. Additionally, you may concatenate all fieldvalues (strings and numbers) on the fly and generate a crc32 checksum or something that like of it and save this into a hidden field (or at least uneditable) with every new created or updated page. Then, when running a new importing loop, you extract or create the ID and create a crc32 checksum from the feed item on the fly. Query if a page with that feed-ID is allready in the sytem; if not create a new page and move on to the next item; if yes, compare the checksums. If they match, move on t the next item, if not, update the page with the new data. Code example: $http = new WireHttp(); // Get the contents of a URL $response = $http->get("feed_url"); if($response !== false) { $decodedFeed = json_decode($response); foreach($decodedFeed as $feed) { // create or fetch the unique id for the current feed $feedID = $feed->unique_id; // create a checksum $crc32 = crc32($feed->title . $feed->body . $feed->status); $u = $pages->get("template=basic-page, parent=/development/, feed_id={$feedID}"); if(0 == $u->id) { // no page with that id in the system $u = createNewPageFromFeed($feed, $feedID, $crc32); $pages->uncache($u); continue; } // page already exists, compare checksums if($crc32 == $u->crc32) { $pages->uncache($u); continue; // nothing changed } // changed values, we update the page $u = updatePageFromFeed($u, $feed, $crc32); $pages->uncache($u); } } else { echo "HTTP request failed: " . $http->getError(); } function createNewPageFromFeed($feed, $feedID, $crc32) { $u = new Page(); $u->setOutputFormatting(false); $u->template = wire('templates')->get("basic-page"); $u->parent = wire('pages')->get("/development/"); $u->name = $feed->title = $feed->id; $u->title = $feed->title; $u->status = $feed->status $u->body = $feed->title; $u->crc32 = $crc32; $u->feed_id = $feedID; $u->save(); return $u; } function updatePageFromFeed($u, $feed, $crc32) { $u->of(false); $u->title = $feed->title; $u->status = $feed->status $u->body = $feed->title; $u->crc32 = $crc32; $u->save(); return $u; }
    1 point
  21. clients. not sure I can account for that, as they replaced a 6mb video with a 70mb one... what would you do?
    1 point
  22. Thx Sergio, sure I'm happy to share some insights. The library is mPdf (version 7) https://mpdf.github.io/ and I created a little helper module and just set it to public if anyone is interested: The custom design is just a regular PDF done by a designer and it is then imported into the pdf as needed (see line one in the next screenshot). My helper module provides a possibility to create only the HTML (not the pdf) so debugging is a lot easier: This is the result. The second logo would be the logo of the actual client of course... CSS is done with LESS, so it is easy to style and modify variables. LESS is compiled via AIOM on the fly without the need of any other compilers/setup. widows/orphans are controlled by a .nopagebreak class that can be applied so some blocks that should stay together: Charts have a fixed size so the amount of text above those charts is limited so that everything stays on one page. To get the TOC working with the custom design without page numbers etc. was quite tricky but finally it works very well. Also the "annex" feature is very nice, where they can upload a custom PDF without background and page numbers and the text is imported in the document automatically with the correct background and page numbers: We just created a word-template with the correct colors, fonts and headline sizes and that's it ?
    1 point
  23. The module source is below. Example usage: a checkbox on a contact form (using Form Builder) for the user to subscribe. It's used on https://ricardo-vargas.com/contact/ EXAMPLE A method on _hooks.php. If you don't use Form Builder, use this code on your form page. $forms->addHookBefore('FormBuilderProcessor::emailForm', function($event) { $processor = $event->object; if ($processor->formName == 'contact-form') { $formData = $event->arguments(1); $contact_name = $event->sanitizer->text($formData['contact_name']); $contact_name = substr($contact_name, 0, 30); // limit length further $contact_name = $event->sanitizer->emailHeader($contact_name); $contact_email = $event->sanitizer->text($formData['contact_email']); $contact_email = $event->sanitizer->emailHeader($contact_email); $processor->emailFrom = $contact_email; //reply to $processor->emailSubject = 'Message from '.$contact_name; $form = $event->object->getInputfieldsForm(); $subscribe = $form->get('receive_updates'); $list_id = $form->get('sendy_list_id')->attr('value'); // check to see if they subscribed if ($subscribe->attr('checked')) { $success_url = '/contact'; // $fail_url = '/contact?error=1'; $ProcessSendyAPI = wire('modules')->getModule('ProcessSendyAPI'); $ProcessSendyAPI->subscribeInSendy($contact_name, $contact_email, $list_id, $success_url); } } }); MODULE https://gist.github.com/sjardim/2d834ebb0bd66d4da1ac16072f4075cd <?php namespace ProcessWire; class ProcessSendyAPI extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return array( 'title' => __('Process Sendy API'), 'summary' => __('Handle API calls to a Sendy installation'), 'author' => 'Sérgio Jardim', 'version' => '001', 'singular' => true, 'autoload' => false, 'icon' => 'envelope' ); } /** * Data as used by the get/set functions * */ protected $data = array(); /** * Default configuration for module * */ static public function getDefaultData() { return array( "sendy_api_key" => '', "sendy_installation_url" => 'http://www.example.com/sendy' ); } /** * Populate the default config data * */ public function __construct() { foreach(self::getDefaultData() as $key => $value) { $this->$key = $value; } } public static function getModuleConfigInputfields(array $data) { $data = array_merge(self::getDefaultData(), $data); $wrapper = new InputfieldWrapper(); $f = wire('modules')->get('InputfieldText'); $f->attr('name', 'sendy_api_key'); $f->label = __('Sendy API Key', __FILE__); $f->description = __('Further instructions at https://sendy.co/api', __FILE__); $f->notes = __('Get your key at http://your_sendy_installation/settings.', __FILE__); $f->value = $data['sendy_api_key']; $wrapper->add($f); $f = wire('modules')->get('InputfieldURL'); $f->attr('name', 'sendy_installation_url'); $f->label = __('Sendy instalation URL', __FILE__); $f->description = __('Your Sendy installation URL without a trailing slash', __FILE__); $f->notes = 'http://www.example.com/sendy'; $f->value = $data['sendy_installation_url']; $wrapper->add($f); return $wrapper; } /** * [subscribeUserOrGuest description] * @param [type] $name [description] * @param [type] $email [description] * @param [type] $list_id [description] * @param [type] $success_url [description] * @param [type] $fail_url [description] * @return [type] [description] */ public function subscribeInSendy($name, $email, $list_id, $success_url = null, $fail_url = null) { $api_key = $this->data['sendy_api_key']; $sendy_url = $this->data['sendy_installation_url']; $postdata = http_build_query( array( 'name' => $name, 'email' => $email, 'list' => $list_id, 'boolean' => 'true' //set this to "true" so that you'll get a plain text response ) ); $opts = array('http' => array('method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => $postdata)); $context = stream_context_create($opts); $result = file_get_contents($sendy_url.'/subscribe', false, $context); //check result and redirect if($result) { $this->wire('log')->save("newsletter", 'A new user subscribed to the site mailing list: '.$email); if($success_url) { header("Location: $success_url"); } } else { $this->wire('log')->save("error", 'Error occurred on subscribing '.$email); if($fail_url) { header("Location: $fail_url"); } } } }
    1 point
  24. I made this with Page Tables. It's not 100% perfect yet, but works for my demands _init.php: // Main Content if ($page->views) { $content = "<div id='views' data-uk-grid-margin>"; $contentview = ""; foreach ($page->views as $view) { $typ = $view->template; $id = $view->name; $colxl = $view->colxl; $gridcountxl = $colxl; $colsm = $view->colsm; $gridcountsm = $colsm; $colxs = $view->colxs; $gridcountxs = $colxs; $fw = $view->fullwidth; $nmnp = $view->removemargin; $border = $view->hasborder; switch ($typ) { case "Text": $text = $view->textfield; $ct = "<div id='{$id}' class='uk-width-small-". $colxs ."-12 uk-width-medium-". $colsm ."-12 uk-width-large-". $colxl ."-12'>$text"; $ct .= checkGrid($gridcountxs, $gridcountsm, $gridcountxl); $ct .= "</div>"; $contentview .= fullWidth($fw, $cv=$ct, $nmnp, $border); break; case "SlideGalerie": $bilder = $view->images; $sg = ""; $sg .= "<div id='{$id}' class='product-gallery views owl-carousel owl-theme'>"; foreach ($bilder as $b) { $bbig = $b->width(1920, $options); $bsmall = $b->size(485,325, $options); $bretina = $b->size(970,650, $options); $sg .= "<div class='g-item'>"; $sg .= "<a href='{$bbig->url}' data-uk-lightbox=\"{group:'{$view->name}'}\">"; $sg .= "<img data-src='{$bsmall->url}' data-src-retina='{$bretina->url}' class='owl-lazy' alt='$b->description'>"; $sg .= "</a>"; $sg .= "</div>"; } $sg .= "</div>"; $sg .= checkGrid($gridcountxs, $gridcountsm, $gridcountxl); $contentview .= fullWidth($fw, $cv=$sg, $nmnp, $border); break; // Case .. Case .. Case .. } $content .= $contentview; $content .= "</div>"; } _func.php /** * @param string $gridcountxs * @param string $gridcountsm * @param string $gridcountxl * @return string */ function checkGrid($gridcountxs = '', $gridcountsm = '', $gridcountxl = '') { $out = ''; if ($gridcountxs >= 12) { $out .= "<div class='uk-clearfix uk-visible-small'></div>"; } if ($gridcountsm >= 12) { $out .= "<div class='uk-clearfix uk-visible-medium'></div>"; } if ($gridcountxl >= 12) { $out .= "<div class='uk-clearfix uk-visible-large'></div>"; } return $out; } /** * @param string $fw * @param $cv * @param string $nmnp * @param string $border * @return string */ function fullWidth($fw = '', $cv, $nmnp = '', $border = '') { if ($nmnp) { $nomargin = 'uk-margin-top-remove'; } else { $nomargin = ''; } switch ($border) { case '1': $b = 'border-top'; break; case '2': $b = 'border-right'; break; case '3': $b = 'border-bottom'; break; case '4': $b = 'border-left'; break; case '5': $b = 'border-all'; break; default: $b = ''; break; } $fout = ''; if ($fw) { $fout = "<div class='full-width " . $nomargin . " " . $b . "'>" . $cv . "</div>"; } else { $fout = "<div class='uk-container uk-container-center uk-margin-large-top " . $nomargin . "'><div class='uk-grid " . $b . "'>" . $cv . "</div></div>"; } return $fout; }
    1 point
  25. Hey thmsnhl, I'm using ProcessWire for a few projects at work as well as for my personal projects. Originally coming from Ruby On Rails I also encountered problems with our database synchronization when it comes to team development on a single project. Depending on the project we solved this using Field/Template Export/Import while also only working on certain parts of the page. Most projects are already finished in terms of template/field structure so there is no need to have an always up-to-date local database snapshot. If the project is not finished in terms of template/field structure we usually sit together and draft a "this-solves-everything"-kind of template/field and synchronize this to our local databases once and then just code ahead. Other frameworks solve this by using Migrations and although ProcessWire does not support Migrations in the core (as of now) you can use this module: https://modules.processwire.com/modules/migrations/ (Here are a few code samples and documentation: https://lostkobrakai.github.io/Migrations/). This module does not directly add tables and columns to the database like RoR migrations do but instead allows you to do nearly everything you can do in the Admin UI using code. You can for example create templates, move fields around, install modules, setup users etc. It can be a bit tedious at first to learn all the new APIs this module offers but the benefits, especially for large projects, speak for themselves. These migrations can be safely checked into your git repository and whenever one of your teammates encounters a problem with his out-to-date database he can simply run the migrations files. So instead of adding templates and fields on production you write them in code and deploy your changes to production, then you migrate either using the CLI utility or the admin backend. You can also integrate this into your build toolchain (like Amazon CodeStar). Check this code example I bet it feels quite familiar: <?php class Migration_2018_01_16_12_32 extends Migration { public static $description = "Add postalcode to a hotel"; public function update() { $this->insertIntoTemplate('hotel', 'postalcode', 'title'); $this->editInTemplateContext('home', 'postalcode', function($f){ $f->label = "Hotel Postalcode"; $f->columnWidth = 50; }); } public function downgrade() { $t = $this->templates->get('hotel'); $t->fieldgroup->remove('postalcode'); $t->fieldgroup->save(); } } This adds a `postalcode` field right after the title field to the hotel template. And it removes this field when you rollback your migration. This is how we tackle this problem.
    1 point
  26. Hi LostKobrakai, I will be glad to write up any specific details you would like to hear about, but thought this may be a better general overview. If this does not answer your question, please let me know and I will try to be more detailed, but I am still learning how the entire migration process works myself. Since they support WordPress, it works out the box, but I don't see any reason why it would not work with PW.
    1 point
  27. @RyanJ Can you elaborate a bit more on how they manage database changes you're doing on those dev environments?
    1 point
  28. I recently had the experience to spin up a WordPress website using pantheon web service. I got to say that I am truly impressed with the workflow it provides out of the box for WordPress and even Drupal. You get three environments to work in, Development, Stage and Production. The fact that you can pull back the production database into your development environment is such a useful tool. Would love to see PW as an option for them.
    1 point
  29. One thing I'm going to be working on soon is adapting Trellis to ProcessWire. If any of you saw my video on adapting Sage for ProcessWire, Trellis is a project by the same group of people (Roots) which allows for you to setup an excellent development + staging + production environment using Ansible (and Vagrant for the development environment). Because of the similarities between WordPress and ProcessWire (well, in terms of the server stack required and the fact that they are both CMSes), a lot can be borrowed from their setup in terms of approach and techniques. They use WP-CLI for some things, but Wireshell can be swapped out for that nicely from what I've researched. I'm really excited to see how it turns out because this has been a huge missing piece to my workflow. This approach would replace things like Capistrano and any other deployment methods, assuming you use a dedicated VM for a site. I highly recommend checking out Trellis to see how it's done. It's thought out very well.
    1 point
  30. Hello, So I'm working on a PW project in a shared hosting like Hostgator. For this to work you need ssh access, and of course git. I always have problems working with FTP, I feel my productivity decreases when using them, also you have no control over changes that are made. So a single file deletion could cause a big catastrophe. The first thing you have to do is cloning the PW repo, and checkout to dev branch Here we clone the repo, use the dev branch. Delete the entire PW git directory and initialize a new one $ git clone https://github.com/ryancramerdesign/ProcessWire/ $ cd ProcessWire $ git checkout dev $ rm -rf .git $ rm .gitignore $ git init $ git add . $ git commit -m "Initial Commit" Note: The file .gitignore from the dev branch have some files that you want to be commited, like /sites. I recommend deleting that file and create a new one. Ok, now we need to configure our remote repo inside the shared hosting. You need to login using a terminal. Linux and Mac have ssh already installed, if you are in Windows you can use Putty. ssh username@domain.com -p 2222 ... Enter your password: ********* Now we need to authorize our computer in the hosting, so we can push the commits. We need the contents of ~/.ssh/id_rsa.pub located our computer. A more complete guide for generating such keys is available here. Then we will copy the contents to ~/.ssh/authorized_keys. create the authorized_keys in the hosting if not exists using touch ~/.ssh/authorized_keys The next step is creating a new repo and configure some hooks in our server. $ mkdir www/processwire $ cd www/processwire $ git init $ git config receive.denyCurrentBranch ignore $ cd .git $ cd hooks $ touch post-receive The contents of post-receive hook should be. (this is a ruby script, but could be any language available on the server) #!/usr/bin/env ruby # post-receive # 1. Read STDIN (Format: "from_commit to_commit branch_name") from, to, branch = ARGF.read.split " " # 2. Only deploy if master branch was pushed if (branch =~ /master$/) == nil puts "Received branch #{branch}, not deploying." exit end # 3. Copy files to deploy directory deploy_to_dir = File.expand_path('../') `GIT_WORK_TREE="#{deploy_to_dir}" git checkout -f master` puts "DEPLOY: master(#{to}) copied to '#{deploy_to_dir}'" # 4.TODO: Deployment Tasks # i.e.: Run Puppet Apply, Restart Daemons, etc Now we make it executable using chmod $ chmod +x post-receive With all that done, we should enable a passwordless login so we can push without a password prompt on every push. In our local machine look for this file ~/.ssh/config And add this text (Change your-domain.com and port as needed) Host your-domain.com Port 2222 PreferredAuthentications publickey Now we should go to our project directory and add the remote host with $ git remote add hosting user@domain.com:www/processwire $ git push --set-upstream hosting master This should do the trick for commiting changes and upload files without ftp. Now we must install Processwire and then pull the changes. Go to your domain and start PW setup. once you are ready installing processwire, you need to pull changes that PW installer made. Login to your hosting via ssh and commit the changes that installer made. $ ssh user@domain.com $ cd www/processwire $ git rm $(git ls-files --deleted) $ git add . -f $ git commit -m "Initial PW Setup" When that is ready go back to your local machine and pull the repo. $ cd Processwire $ git fetch $ git reset --hard hosting/master Now we are finally ready to start working with PW in shared hosts or other machines that have ssh enabled. simply makes changes like $ touch site/templates/welcome.php $ git add . $ git commit -m "Added welcome.php" $ git push Now if you have some problems you can zip the setup using $ git tag -a v0.0.1 -m "Initial PW Setup" $ git archive --format=zip --output=pw.zip v0.0.1 And download the file and work from there Thats all References: http://www.arlocarreon.com/blog/git/push-git-repo-into-shared-hosting-account-like-hostgator/ http://ahmadassaf.com/blog/miscellaneous/how-to-use-git-to-host-and-push-projects-on-shared-hosting-services/ http://motorscript.com/setting-up-a-remote-git-repo-on-shared-server-like-hostgator/ http://krisjordan.com/essays/setting-up-push-to-deploy-with-git http://git-scm.com/book/en/Customizing-Git-Git-Hooks
    1 point
  31. @n0sleeves Just following up on the idea of using git to control pushing from a dev to production situation (preferably via a test stage) and ignoring the DB sync issue for now. Caveat: Although I haven't exactly done all of what I'm about to suggest here, I have been using elements of these things for a while but never tied them together. I started using this model of linked github repos a couple of years back1. If you read the article, you'll see that it addresses a lot of what you want to do to a degree - pushing from one of the (possibly many) development copies to the 'hub' automatically gets reflected in 'prime' - his live site. If you also adopted a disciplined use of something like Vincent Driessen's Gitflow Branching Model2 (where the master branch in your repos is the only one representing releases) and make sure that the working directory on prime only ever has master checked out then you could even commit work-in-progress in other branches to your hub (thus backing it up) and not have it show up on your live site. Only when you pushed up a change to the master branch would prime's staging area change and hence the live site get updated. Anyway, hope that helps with at least part of the problem. 1 Actually, I used that article as the basis for automatically backing up my git repos by (sort-of) reversing his model. I had the bare 'hub' physically located on a remote external hard disk which was then SSHFS mounted into my local filesystem every time my machine booted. I worked on the 'prime' side of things - which was my local development repo, not a live server - and pushing a change locally was automatically mirrored onto my offsite bare 'hub' backup thanks to git hooks. 2 There are repos (original and more up-to-date clones) that provide command line tools to implement his model. Edited to add: I'll update the readme file for the script I use to create 'hub' and 'prime' repos and put the script up on github in a while. Edited to add: I no longer use the Gitflow branching model - I find it cumbersome. There are plenty of alternatives.
    1 point
  32. If you are version controlling your entire /site/ dir then you'd definitely want to exclude cache and sessions. My opinion is that if you version control this stuff, just do it on /site/templates/, and let each module be under its own version control (or use Modules Manager). I don't think it's worth versioning /site/assets/files/ for instance, because any change of versions would disconnect it with your database, which is something you wouldn't want.
    1 point
×
×
  • Create New...