Leaderboard


Popular Content

Showing most liked content since 07/23/2017 in all areas

  1. 22 likes
    I like to showcase my new website acniti on the forum here. History Building and managing a website is a hobby, over the years, making websites got more complicated and more technologies, knowledge and wisdom are required. I started building my first website around 1997. It started out with a static site built with FrontPage, a WYSIWYG HTML editor. A few years later it was time for the first content management system, I looked at Joomla but settled for MediaWiki. I run those websites for 2 years on the MediaWiki platform and then moved on to WordPress. WordPress was good, it did a good job but over time, it became more complicated to make something out of the box, if it's not a blog, it becomes complicated and to have a feature rich website requires a lot of plugins. Little by little it became less fun and more and more hassle juggling the various plugins. In 2014 I became interested in learning PHP programming, I wanted to do this already for many years, but never had enough time to bite the bullet and work my way through the basics. At the end of the courses I though and now what have I learned, how to put this into action? To built modern website with PHP only is difficult, it also requires knowledge of html, MySQL, CSS, java-script etc. I started looking for a framework experimented a little with CakePHP and then came across Processwire via a CMS Critic blog post. Development setup I developed the acniti website on a Linux Ubuntu 16, with PHP 7 and MySQL as the development server. For the IDE I use PhpStorm, before using Storm I have used and tried some other IDE's such as Zend, Eclipse, Netbeans, Aptana but none of them I liked, some were feature poor, Zend and Eclipse were slow and use a lot of memory. PhpStorm not free but definitely worth the investment. I make use of the free tier Git repository of AWS called CodeCommit, I still use GIT Cola to commit the changes, I could also use PhpStorm for this but I never took the time to change my workflow. For project management I am a big fan of Redmine, Redmine is a web-based open-source project management and issue tracking tool. I use this also for my other work so it easily integrates with the website building flow as well. It's easy for maintaining lists of features you want to carry out per version, it supports a wiki which is easy for making notes and keeping a log of the activities. I use it everyday and it runs on Ruby. For images and graphics I switch back to Windows for some Photoshop. Processwire The acniti website runs on the latest stable Processwire version at the time of writing 3.0.62, the website has 4 languages including an Asian language. The Japanese language URL's are implemented with their 3 alphabets kanji, hiragana, katakana i.e. https://www.acniti.com/ja/インレットフィルタ. Some images on the site have text and image language tags help to select the correct language, the Processwire blog post from 30 June was helpful to get this running. The main site has a bootstrap theme, for the mobile version of the site the google AMP specification is implemented. This was really fun to do but challenging at times as the AMP specification is still a little limited. To visit the AMP pages type /amp/ behind any URL like https://www.acniti.com/amp/ for the homepage. The Google webmaster portal is really easy to troubleshoot and check for the correct AMP implementation. Finally structured data according to schema.org is added to the site via the JSON-LD markup. The commercial modules ProCache and Formbuilder are installed. The ProCache module is really amazing and makes the website lightning fast. Besides the commercial modules around eleven open-source modules are used, Database Backups, Tracy Debugger, Wire Mail SMTP, Protected Mode, Batcher, Upgrades, PublishAsHidden, URL (Multi-language), Twitter Feed Markup, Email Obfuscation (EMO), Login History, Selector test. During development the Processwire forum is really helpful and checked often. The forum is good for two reasons, most of the questions, I had during development of the site, are already on the site. Secondly the only 6 questions I posted over the last 2 years, are quickly and accurately answered. The downside I didn't become a very active member on the forum but see that as a compliment. An open issue on the acniti site is the AMP contact form with Formbuilder, the restricted usage of java-script for the AMP specification requires some more in-depth study. Hosting setup For the hosting services the acniti site uses Amazon EC2, I use AWS already many years to manage my cloud office so it was easy to decide to use it for the web hosting as well. The site is running on a micro instance of EC2 and with the ProCache module CloudFront is serving webpages worldwide fast. Updates from the development server are sent to CodeCommit and from there to the production server. From a site management point of view it would be nice to use AWS RDS to manage the MySQL databases, but from a cost perspective I decide not to do that for now. Via a cron I have set up automatic MySQL backups and these are via another cron job uploaded to AWS S3. To make sure the server is safe, a cron job runs daily snapshots of the server, this is getting initiated via AWS Lambda. Lambda also removes older snapshots because during creation a delete tag is attached for sevens days after their creation. It's important to make a separate MySQL backup as with snapshots the database often gets corrupted and its easier to restore a database backup than to fix a corrupted database. Another nice feature to use AWS Lambda for is a simple HTTP service health checker, which reports to you by email or sms when the website is down. Making use of all these Amazon services cost me probably somewhere between 10 - 15 $ a month, I have to estimate a little since I am running a lot more things on AWS than only the website. The site is running on a Comodo SSL certificate but next year I will change to the free LetsEncrypt, as it is easier to add and will automatically renew after 90 days. The Comodo certificate requires manually copy pasting and editing the certificates in place. Writing Content The content for the site I write in the Redmine wiki, most of the content I write requires research and it takes about two weeks before I publish the content to the Processwire site. For writing content I use the google spell checker with the grammar checker, After the Deadline. To ensure catchy headlines they are optimized with the Headline Analyzer from CoSchedule Social Media Now the site is running, it needs promotion. The robots.txt files shows the search engines the way as does the sitemap.xml both of these I have made in a template file. Most of the blog articles I promote are republished on social networks like, LinkedIn, Tumblr, Google+, Twitter, and some branch specific networks as the Waternetwork and Environmental XPRT. To check, the search engines index the site well, Google webmaster and Bing webmaster check for any problems with the site. For statics on the same server there is an instance installed of Piwik. Piwik is a leading open alternative to Google Analytics that gives full control over data. The Piwik setup works very well and gives a good overview of the site usage both on the desktop via the site or via a mobile app. As a part of a test I have installed the open-source SEO-panel on the same server to manage keywords and to further improve the scores in the search engine, a nice feature is that you can also track your competitors. I am still new to SEO panel and have to learn more how to use the tool effectively.
  2. 18 likes
    This week's version of ProcessWire on the dev branch is 3.0.69 and it includes several minor bug fixes. This week's post focuses in on a new module released today called Login for Facebook, which I think many might find useful, and we've got all the details here: https://processwire.com/blog/posts/pw-login-for-facebook/
  3. 16 likes
    OAuth2Login for ProcessWire A Module which give you ability to login an existing user using your favorite thrid-party OAuth2 provider (i.e. Facebook, GitHub, Google, LinkedIn, etc.).. You can login from the backend to the backend directly or render a form on the frontend and redirect the user to a choosen page. Built on top of ThePhpLeague OAuth2-Client lib. Registration is not handled by this module but planned. Howto Install Install the module following this procedure: - http://modules.processwire.com/modules/oauth2-login/ - https://github.com/flydev-fr/OAuth2Login Next step, in order to use a provider, you need to use Composer to install each provider ie: to install Google, open a terminal, go to your root directory of pw and type the following command-line: composer require league/oauth2-google Tested providers/packages : Google : league/oauth2-google Facebook: league/oauth2-facebook Github: league/oauth2-github LinkedIn: league/oauth2-linkedin More third-party providers are available there. You should be able to add a provider by simply adding it to the JSON config file. Howto Use It First (and for testing purpose), you should create a new user in ProcessWire that reflect your real OAuth2 account information. The important informations are, Last Name, First Name and Email. The module will compare existing users by firstname, lastname and email; If the user match the informations, then he is logged in. ie, if my Google fullname is John Wick, then in ProcessWire, I create a new user Wick-John with email johnwick@mydomain.com Next step, go to your favorite provider and create an app in order to get the ClientId and ClientSecret keys. Ask on the forum if you have difficulties getting there. Once you got the keys for a provider, just paste it into the module settings and save it. One or more button should appear bellow the standard login form. The final step is to make your JSON configuration file. In this sample, the JSON config include all tested providers, you can of course edit it to suit your needs : { "providers": { "google": { "className": "Google", "packageName": "league/oauth2-google", "helpUrl": "https://console.developers.google.com/apis/credentials" }, "facebook": { "className": "Facebook", "packageName": "league/oauth2-facebook", "helpUrl": "https://developers.facebook.com/apps/", "options": { "graphApiVersion": "v2.10", "scope": "email" } }, "github": { "className": "Github", "packageName": "league/oauth2-github", "helpUrl": "https://github.com/settings/developers", "options": { "scope": "user:email" } }, "linkedin": { "className": "LinkedIn", "packageName": "league/oauth2-linkedin", "helpUrl": "https://www.linkedin.com/secure/developer" } } } Backend Usage In ready.php, call the module : if($page->template == 'admin') { $oauth2mod = $modules->get('Oauth2Login'); if($oauth2mod) $oauth2mod->hookBackend(); } Frontend Usage Small note: At this moment the render method is pretty simple. It output a InputfieldForm with InputfieldSubmit(s) into wrapped in a ul:li tag. Feedbacks and ideas welcome! For the following example, I created a page login and a template login which contain the following code : <?php namespace ProcessWire; if(!$user->isLoggedin()) { $options = array( 'buttonClass' => 'my_button_class', 'buttonValue' => 'Login with {provider}', // {{provider}} keyword 'prependMarkup' => '<div class="wrapper">', 'appendMarkup' => '</div>' ); $redirectUri = str_lreplace('//', '/', $config->urls->httpRoot . $page->url); $content = $modules->get('Oauth2Login')->config( array( 'redirect_uri' => $redirectUri, 'success_uri' => $page->url ) )->render($options); } Screenshot
  4. 15 likes
    https://www.baumrock.com/portfolio/hr-diamonds.com/ I built this website for my client hrdiamonds.com - a great HR company based in Vienna, Austria. They were totally unhappy with the result of the former agency so it was easier to rebuild the website from scratch than adopting the old CMS (that was actually only 2 years young!)... Design by @lokomotivan built with UiKit Highlights: Frontend-Editing barba.js for smooth page transitions ProCache, of course Favourite client quote of the CEO: Happy to hear your feedback
  5. 13 likes
    ProcessWire DropboxAPI on GitHub: https://github.com/clipmagic/DropboxAPI This module is a wrapper for Kunal Varma's Dropbox PHP SDK https://github.com/kunalvarma05/dropbox-php-sdk It was created to perform a specific function for a client, ie upload PDF files on a PW website to a specific Dropbox account folder. The PDF files, created using @Wanze's excellent PagesToPdf module using the WirePDF functions, are generated from Formbuilder forms completed by front-end site visitors. Works a treat! There's more that could be done to allow ProcessWire to take advantage of all the features of the SDK, eg downloads, multiple Dropbox accounts, etc. You are welcome to request changes and/or fork the GitHub project to extend the feature set. Enjoy! System requirements PHP 5.6.4 or greater Composer The PHP mbstring extension General information This module enables you to access a single Dropbox (www.dropbox.com) account to upload files from a ProcessWire website. All kudos to (https://github.com/kunalvarma05/dropbox-php-sdk) for the PHP API. First steps Visit (https://www.dropbox.com/developers) and read the documentation. Log into Dropbox and create a new application. It's recommended to limit ProcessWire App access to a specified folder Make a note of the App key, the App secret and the name of the Dropbox folder Installation Download the zip file into your site/modules folder then expand the zip file. Next, login to ProcessWire > go to Modules > click "Refresh". You should see a note that a new module was found. Install the DropboxAPI module. Configure the module with your App key, App secret and your Call Back URL You need to generate a Dropbox access token to enable your site to communicate with the nominated Dropbox account. Dropbox will generate a token for you or you can create a page for the front end of your ProcessWire site with a template to submit the token request to Dropbox, eg: <?php namespace ProcessWire; $drop = $modules->get('DropboxAPI'); if ($input->get->code && $input->get->state) { $code = $sanitizer->text($input->get->code); $state = $sanitizer->text($input->get->state); //Fetch the AccessToken $accessToken = $drop->getAccessToken($code, $state); echo "Copy/paste this code into the module configuration: " . $accessToken; } else { echo "<p><a href='" . $drop->getAuthURL() . "'>Log in with Dropbox</a></p>"; } ?> Once you have entered the token in the module configuration, you can unpublish this page. Usage Read the dropbox-php-sdk documentation! An example template for sending a file to a Dropbox App folder from ProcessWire: <?php namespace ProcessWire; use Kunnu\Dropbox\Dropbox; use Kunnu\Dropbox\DropboxApp; use Kunnu\Dropbox\DropboxFile; // send pdf to Dropbox $drop = $modules->get('DropboxAPI'); $app = new DropboxApp($drop->app_key, $drop->app_secret, $drop->authorization_code); if ($app) { //Configure Dropbox service $dropbox = new Dropbox($app); $dropboxFile = new DropboxFile('/path/to/myfilename.pdf'); $file = $dropbox->upload($dropboxFile, "/myfilename.pdf", ['autorename' => true]); //Uploaded File meta data if ($file) { $success = $file->getName() . " uploaded to Dropbox"; $drop->log($success); } }
  6. 12 likes
    Hi @pandaman For one of my customers I made some basic experiments to connect the both systems together. Your arguments are valid. I also told him to combine the best of two worlds and let each system do what it's best for. What I can say right now, it is possible, but there might be pitfalls, that I didn't discover yet. One problem was the session management which caused conflicts so you have to modify it. Right now I have the following working solution (PW 3 and Magento 2.1): I installed Magento 2.1 in a "store" directory that lies right beside site and wire directories. I made a magento2-bridge.php in site/templates/ <?php namespace ProcessWire; require $_SERVER['DOCUMENT_ROOT'] . '/store/app/bootstrap.php'; use Magento\Framework\App\Bootstrap; class StoreApp { private static $instance; public static function get_instance() { if ( ! isset(self::$instance) && ! (self::$instance instanceof StoreApp) ) { self::$instance = new StoreApp(); } return self::$instance; } public $helper; public $quote; public $session; public $cart; public $customer; private function __construct() { $bootstrap = Bootstrap::create(BP, $_SERVER); $obj = $bootstrap->getObjectManager(); $state = $obj->get('Magento\Framework\App\State'); $state->setAreaCode('frontend'); $this->customer = $obj->get('Magento\Customer\Model\Session')->getCustomer(); $this->quote = $obj->get('Magento\Checkout\Model\Session')->getQuote(); $this->helper = $obj->get('\Magento\Checkout\Helper\Cart'); $this->session = $obj->get('Magento\Checkout\Model\Session'); $this->cart = $obj->get('\Magento\Checkout\Model\Cart'); } } I had to modify the session management in Magento, so it stores its session variables in ProcessWires directory. I hope I remember this correctly: Change the Cookie Path in Magento @ Stores -> Configuration -> Web -> Defaut Cookie Settings to "/" (without the quotes) Change the save_path for sessions under store/app/etc/env.php 'session' => array ( 'save' => 'files', 'save_path' => $_SERVER['DOCUMENT_ROOT'].'/site/assets/sessions', At some point I changed the sessionAllow parameter of ProcessWire, but I don´t know if this is needed anymore. But for completeness, here is the code I used: $config->sessionAllow = function () { if (strpos($_SERVER['REQUEST_URI'], '/processwire/') === 0) { return true; } return false; }; Then in my site/templates/home.php I have the following code, to get data from Magento (like Customer data, what is in the cart, product information): require_once __DIR__ .'/magento2-bridge.php'; $store = StoreApp::get_instance(); $quote = $store->helper->getQuote(); $quoteitems = $quote->getAllItems(); $customer = $store->customer; bd($customer->getName()); foreach ($quoteitems as $item) { // Code to get contents per product bd($item->getName()); bd($item->getQty()); } If you are wondering what bd() means. It is a debugging output from Tracy Debugger for ProcessWire (recommended install). Why do I share this information here although it was very time-consuming and expensive to figure this out? Because I had great support from the PW community and want to give something back. If you make any progress with this, please try to do the same and share your findings with our lovely community.
  7. 11 likes
    In a current project I am using a Repeater field to build a kind of pseudo-table, where each Repeater item is a row. Some of the rows are headers, and some have buttons that toggle corresponding checkbox fields in a hidden FormBuilder form. The problem was that when the Repeater items were collapsed I couldn't see which rows were headers and which contained buttons. I tried including the fields in the Repeater labels but it still didn't provide enough visual difference to be quickly recognisable. So I investigated how classes could be added to selected Repeater items in admin depending on the value of fields within the Repeater items. This is what I ended up with... In /site/ready.php // Add classes to selected service row Repeater items $this->addHookAfter('InputfieldFieldset::render', function(HookEvent $event) { /* @var $fieldset InputfieldFieldset */ $fieldset = $event->object; $attr = $fieldset->wrapAttr(); // Fieldsets in a Repeater inputfield have a data-page attribute if(isset($attr['data-page'])) { // Get the Repeater item $p = $this->pages((int) $attr['data-page']); // Check field values and add classes accordingly // If item is a header if($p->row_type && $p->row_type->id == 2) { $fieldset->addClass('header-row'); } // If item has a checkbox button if($p->fb_field) { $fieldset->addClass('has-checkbox'); } } }); In admin-custom.css (via AdminCustomFiles) /* Special repeater rows */ .Inputfield_service_rows .header-row > label { background:#29a5aa !important; } .Inputfield_service_rows .has-checkbox > label .InputfieldRepeaterItemLabel:before { font-family:'FontAwesome'; color:#73cc31; content:"\f058"; display:inline-block; margin-right:6px; } Result
  8. 11 likes
    Years ago before Wordpress had even been invented, I started work on a site providing information about my local region of New Zealand. Back then, most people were on dialup, and if you wanted a CMS you had to roll it yourself - if you could find a host that supported server scripting at an affordable price. This year, with a quiet patch with essentially no paid work, I finally decided it was time to make the move from a home-grown CMS using an obscure scripting language to something more modern, so I could spend more time adding content and features, and less time maintaining the core CMS. www.marlboroughonline.co.nz I love Processwire because it works the way I think, and when I was first introduced to it, I was up and running within 20 minutes of reading the documentation, vs several hours reading Wordpress documentation, and still not entirely sure how to create my own fields and create a theme from scratch. I come from a database programming background, particularly Microsoft Access, so being able to make fields and add them to a form or report, is the way I'm used to working, although it took a bit of getting used to Processwire not adding fields to a table by default, although I see Pro-Fields or custom field types can achieve this. (I haven't used Pro-Fields in this project as I'm essentially on a zero budget). The site itself doesn't use anything particularly fancy. I use the following modules: Map Marker Form Template Processor Social Share Buttons (With my own colour version of the button icons) AIOM+ (This is particularly handy as I'm using a customised version of Bootstrap, and it handles compiling all the LESS files) Jumplinks The biggest task was importing all the content from my existing CMS, but since I wrote it, it was easier than dealing with some third-party CMS. The site had been around for a long time, and had numerous inward links including a number from Wikipedia, and I didn't want to break them in the conversion. If you're converting a site to Processwire with a URL structure that can't be replicated in Processwire, Jumplinks is a must-have module, as it handles complex URL redirects very nicely. The site has quite a bit of content, much of it which needed updating in addition to changing the CMS, so there might be odd bits that don't look right, but that's certainly not Processwire's fault.
  9. 10 likes
    This week we added the new pages export/import feature to the core! In this post, we tell you how to install it and cover all of the new things added over the last week. https://processwire.com/blog/posts/processwire-3.0.71-adds-new-core-module/
  10. 10 likes
    In this week's post we'll take a closer look at the importing side of our upcoming export/import tool. Plus we'll look at something new in 3.0.70 called markup region hints, which are worthwhile if you are using markup regions in PW3. https://processwire.com/blog/posts/pw-3.0.70/
  11. 10 likes
    Headings Case A plugin for CKEditor fields in ProcessWire CMS/CMF. Adds a toolbar button for changing the case of all headings or selected headings between sentence case and title case. This is useful when you are copy/pasting text from a document that has been supplied with an inconsistent or incorrect system of capitalisation. Installation Copy the "headingscase" folder to /site/modules/InputfieldCKEditor/plugins/ In the field settings for each CKEditor field that you want to activate the plugin for: Check the "headingscase" checkbox at Input > Plugins > Extra Plugins Add "HeadingsCase" at Input > CKEditor Settings > CKEditor Toolbar Usage To change the case of all headings, click the toolbar button with no text selected in CKEditor. The first click applies sentence case; the second click applies title case. To change the case of a single heading, select all or part of the heading in CKEditor before clicking the toolbar button. There can be situations where the results need manual correction: proper names, acronyms, etc. Exceptions for small words Certain short English prepositions and conjunctions (three letters or less) are excluded from capitalisation when title case is applied. Edit the exceptions array in the plugin source code if you want to customise this list of exceptions. https://github.com/Toutouwai/headingscase
  12. 10 likes
    Yes, planning on this. May not be in the initial public/dev branch version though. Most definitely. I think that part of it should already work, but have not spent time testing it yet. That's probably one of the next things I'll be testing here, as this would be needed before adding it as a core module. It will work like the existing template/field import, i.e. you'll be able to un-check certain updates that you don't want to occur. Though for applying granular updates, the JSON copy/paste is really nice, as you can also just modify the JSON directly too. Not planning to pursue that. This is purely about page import/export. If it needs a particular Fieldtype module to be present in order to import a field, it'll let you know of the prerequisite. The info about required Fieldtypes are stored in the export, so that the import can look at that before attempting import. This one has already been in the core for awhile. See Setup > Templates, then see the Export/Import buttons at the bottom. We have the same for fields as well.
  13. 9 likes
    This week's post is actually last week's post, just updated with a lot of new content. Since the topic is pretty much the same as last week, I thought it fit better this way. We've continued to make a ton of progress with the export/import features over the last week, and it's all covered in the updated blog post here: https://processwire.com/blog/posts/processwire-3.0.71-adds-new-core-module/
  14. 7 likes
    Here's a mixin I put together for creating hamburger icons. Unlike most alikes this one uses the checkbox hack so you can use CSS only to show a mobile menu (or off-canvas menu, etc). Parameters: width: the width of the icon. Default: 32px thickness: the thickness of the bars. Default: 3px gap: the vertical space between the bars. Overall height is: thickness*3 + gap*2. Default: 7px color: the color of the icons. Default: #000 (black) radius: border-radius value to round edges anim: seconds of animation duration (transition-duration) or timing function + duration (eg. "ease-out 0.3s"). Default:0.25s labelselector: in case the label is not right after the checkbox, use this setting to tell the mixin where to find it. Eg. "~ div.content header nav label". Default: "+ label" CodePen demo
  15. 7 likes
    Hi PW fanatics In this post I share two of my addHookMethods for those interested. As you surely already know (if not, time to take a look at it) the Wire::addHookMethod() and the Wire::addHookProperty() API methods can be used to "breath some extra OOP" into your projects. Using addHookMethods and addHookProperty are alternatives to "Using custom page types in ProcessWire". The methods I want to share: basically the idea here is to get the URL pointing to images uploaded in the admin without writing much code in the template files. With methods like these below, it does not matter what Formatted value is set to the images, because some predefined defaults are used in all circumstances. #1 Example template file code: <?php $img_src = $latest_article->siteFeaturedImage(); ?> <img src="<?= $img_src ?>" alt="<?= $page->title ?>"> addHookMethod goes into /site/init.php <?php /* Returns URL of the original Pageimage of Article. * Image field's value can be either null (default missing image), Pageimage or Pageimages. * * @return string */ $wire->addHookMethod('Page::siteFeaturedImage', function($event) { $page = $event->object; if ($page->template != "article") { throw new WireException("Page::siteFeaturedImage() only works on 'Pages of article template', Page ID=$page is not such!"); } $article_featured = $page->getUnformatted('article_featured'); //always a Pageimages array if (count($article_featured)) { $img_url = $article_featured->first()->url; } else { $img_url = urls()->templates . "assets/img/missing-article_image.jpg"; //we show this when image is not available } $event->return = $img_url; }); ?> #2 Example template file code: <?php $img600_src = $page->siteProductImageMaxSize(600, 600, ['rotate' => 180]); ?> <img src="<?= $img600_src ?>" alt="<?= $page->title ?>"> addHookMethod goes into /site/init.php <?php /* Generates image variations for Product images. Returns URL of Pageimage. * Image field's value can be either null (default missing image), Pageimage or Pageimages. * * @param int arguments[0] Max allowed width * @param int arguments[1] Max allowed height * @param array arguments[2] See `Pageimage::size()` method for options * @return string */ $wire->addHookMethod('Page::siteProductImageMaxSize', function($event) { $page = $event->object; if ($page->template != "product") { throw new WireException("Page::siteProductImageMaxSize() only works on 'Pages of product template', Page ID=$page is not such!"); } $width = isset($event->arguments[0]) ? $event->arguments[0] : 48; //default width $height = isset($event->arguments[1]) ? $event->arguments[1] : 48; //default height $options = isset($event->arguments[2]) ? $event->arguments[2] : $options = array(); //default empty options $product_image = $page->getUnformatted('product_image'); //always a Pageimages array if (count($product_image)) { $img_url = $product_image->first()->maxSize($width, $height, $options)->url; } else { $img_url = urls()->templates . "assets/img/product-missing-image.jpg"; //we show this when image is not available } $event->return = $img_url; }); ?> BTW, you can find more examples here: How can I add a new method via a hook? Working with custom utility hooks Adding array_chunk support to WireArray addHookProperty() versus addHookMethod() Have a nice weekend!
  16. 7 likes
    I do not want to grumble at all, and I hope my positive criticism will not be misunderstood but I would like to point out that ProcessWire's development road is a bit ad hoc these days. Do not get me wrong, I like it when Ryan needs some features and surprises us with stuffing them into the core, but this method of pushing the system further is still missing a crucial step: discussing the features to be implemented/refactored by the developer community. Also, we have a long list of feature requests but no one knows how and when they will make their way into the system if ever. They just seem to be forgotten. Only one example: Changelog support with 890 views and 11 likes possibly hits the 30% threshold set by Ryan but it is still waiting to be picked up. And there are many others as well, also at Github.
  17. 7 likes
    making progress on this but there's still a lot to do until it is releasable as a module (proper documentation mostly)... sneak peak what's easily doable: a todo-app definition is as easy as that: /** * show table of todos */ public function executeTodos() { // create form $form = modules('InputfieldForm'); $form->action = './'; // add new project $b = modules('InputfieldButton'); $b->attr("id+name", "addTodo"); $b->addClass("ui-priority-primary pw-panel pw-panel-reload"); $b->value = __("Neues Todo"); $b->attr('data-href', pages(2)->url . 'page/add/?parent_id=' . pages('template=todos')); $b->icon = "plus"; $f = modules('InputfieldMarkup'); $f->value = $b->render(); $form->add($f); // table $t = modules('InputfieldRockDatatables'); $t->attr('id+name', 'manageTodos'); $t->rows = pages('template=todo'); // define the table's source rows with one selector $t->ajax = 1; // set table mode to ajax loading making it possible to reload data via a simple $table.ajax.reload(); [...] // remaining $col = new dtCol; $col->name = "remaining"; $col->title = 'Tage'; $col->className = 'minwidth'; $col->data = function($page) { if(!$page->deadline) return ''; $now = new \DateTime('now'); $then = new \DateTime(); $then->setTimestamp($page->deadline); $days = $now->diff($then)->format('%R%a'); $color = config()->colors->lightred; if($days > 0) $color = config()->colors->lightorange; if($days > 7) $color = config()->colors->lightgreen; $obj = new \stdClass(); $obj->display = $days; $obj->colorBars = [ [1, $color] ]; return $obj; }; $t->cols->add($col); [...] // add field to form $f = modules('InputfieldMarkup'); $f->value = $t->render(); $form->add($f); $out = $form->render(); return $out; }
  18. 7 likes
    On the subject: https://blog.toggl.com/how-does-music-affect-work-infographic/
  19. 6 likes
    Now, the "package manager"; From this window, you can create or delete a package, and also download a packages from the "server backups folder" to your computer. But the best feature is that you can "sync" packages stored on a third-party provider. This mean that the module will check for existing packages on GoogleDrive, Dropbox or whatever (it depend on how you configured Duplicator) and list them so you can delete or download them to your computer, and deploy your nice website in a minute! In the following example, I have packages on my Local Server, on GoogleDrive and Dropbox :
  20. 6 likes
    I agree. I'd really like to be able to support ProcessWire and @ryan much more. We should have a git-backed documentation site. The Meteor Docs, as an example, are built in Hexo and managed on Github: https://docs.meteor.com We can then make sure it is up to date as a community instead of it resting on Ryan's shoulders.
  21. 6 likes
    And I was sure YOU will ask that ! Sure, I am already trying to get back my Gitlab server. The server was hosted in a VMware guest machine, and when plugged the hdd and put the virtual machine ON, the network simply does not work anymore. Can't ping anything, but I know how to fix it, i am just too lazy those days. Edit: Just to say, I still have the code and used the module in the last days, but it look like its not the Windows compatible version..
  22. 6 likes
    Finally got a chance to try it and this module is great, and will solve innumerable content management conundrums.. Here's my first use, on a quotes rotator section: Field Config: var colheaders = ['Author', 'Publication', 'Quote']; hot.updateSettings({ colHeaders: colheaders, minCols: colheaders.length, maxCols: colheaders.length, rowHeaders: false, minRows: 1, minSpareRows: 1, maxRows: 5, width: 900, contextMenu: true, autoWrapRow: true, autoWrapCol: true, colWidths: [200,200,500], }); edit screen: output:
  23. 6 likes
    A few references to Hooks: https://processwire.com/api/hooks/ https://somatonic.github.io/Captain-Hook/index.html https://github.com/adrianbj/TracyDebugger/tree/master/panels/CaptainHook http://www.flamingruby.com/blog/using-hooks-to-alter-default-behavior-of-processwire/ https://webdesign.tutsplus.com/tutorials/a-beginners-introduction-to-writing-modules-in-processwire--cms-26862
  24. 6 likes
    I built this module because I needed a versatile solution to replace tags and simple if-blocks in some E-Mails and PDF documents. Usage: See the two example Files in the folder /replacements Methods: replacementsTable() Renders an overview of all available replacements (see the example in the Module's config file: Create new Replacements: Simply copy the sample file and adopt to your needs. Download: https://gitlab.com/baumrock/RockReplacer
  25. 6 likes
    Yes, it is. Go to the Input tab on the field settings and add a value to "Columns of Checkboxes". I forgot to say that the options are going to be divided automatically, you cannot control the column order like you mentioned.
  26. 5 likes
    You can download the source code for the old skyscrapers demo https://github.com/ryancramerdesign/SkyscrapersProfile Then extract site folder beside wire/ and other site profiles. Then perform setup and check out templates, fields, how PW works. Once you get the hang of it, replace the templates with modern v2 version. https://github.com/ryancramerdesign/skyscrapers2 If you have any questions, ask away
  27. 5 likes
    Today, there is a small a preview of a successful deployment (on localhost) using Duplicator : Create a package in the backend Download the package and the installer Upload it to the new hosting Navigate and run the installer
  28. 5 likes
    This is a perfect use case for Page Reference field. Create a new field from Setup > Fields > Add New. Give it a name. Set its type as Page Reference. Save. If you want to select a single user, specify so on the Details tab, Switch to Input tab, set input type to Select. (Optionally install InputfieldPageAutocomplete core module to enable input with auto-completion capabilities) From Selectable Pages, set template to user. Optionally, use Custom Find and specify template as user, and a role, or any other field you like. Add the field to a template. Inside your template file, you can get the user using $page->userFieldName
  29. 5 likes
    Hi @OLSA Because SEO is very important in that kind of business/site I would first of all stick to its requirements and that to other things like the usability of administration etc.d In your second example, you can change "showers and bathtubs" to something more general like "bathroom". In that case, you will have additions keyword in your URL. If your page name includes stop words (and, or, but, of, the, a, etc.), it's not critical to putting them in the URL. You don't have to leave them out, either, but it can sometimes help to make a URL shorter and more readable in some sharing contexts. Use your best judgment on whether to include or not based on the readability vs. length. As for best URL structure, I would recommend "site.com/bathroom/showers/product". You can do it by using URL segments, so you can have desired page tree in admin and perfect URL structure on the frontend. Of course, it's much more work and there is a lot of small things like canonical links, redirect etc that you have to think about. As another way to make your URL shorter you can move only last "product" part to "products", so you will have "products/product". Don't forget to use schema.org or other for breadcrumbs.
  30. 5 likes
    You can do this... $items = $pages->find('template=product-item, action=1, !outlet=[status=published]'); $group_items = $pages->find('template=product-item, action=1, outlet=[status=published]'); ...but I don't think unpublishing Group pages is a good strategy, because unpublished pages do not appear as an option in a Page Reference inputfield. So it means that in Page Edit you cannot distinguish between a product that has no Group selected, and a product that does have a Group selected but the Group has been unpublished. What I would probably do is create a new Page Reference field "group_status" with selectable pages "Active" and "Inactive", and set the "Active" page to be the default value. Add this to the Group template. Then in the Outlet field, include the group_status field in a custom format for "Label field" so you can see the status of each group in the Outlet inputfield. You can have both if you use the ConnectPageFields module.
  31. 5 likes
    I've been using clamp http://jide.github.io/clamp/ for a while now, rather than installing MAMP or MAMP PRO. Like MAMP, it delivers Apache, MySQL (via MariaDB), and PHP. Unlike MAMP it runs from the command line (the 'c' in clamp). Also unlike MAMP, all settings and data (database), sit alongside the website files in a .clamp folder (you need to add this to .gitignore to stop dev environment leaking into production). Apart from being free, it's just brilliant to use. If you get errors or stuck, check the logs, the docs are brief but excellent. Enjoy!
  32. 5 likes
    Hi @szabesz! Yes, it works! I'm running it on PW 3.0.57 right now, in production. I have two actions: 1 - "Generate Short URL on adding a page" - On page save, it calls an API on another server (mine too) that runs an installation of https://yourls.org 2 - "Set end date on events, if empty" - the name is self explanatory.
  33. 5 likes
    The module got renamed and updated a bit as its not intended to run only on the backend, but work also on frontend side. - Now, the administrator can choose to activate or not the backend login buttons. - The providers are added "dynamically". You have to simply edit a JSON config file which once saved, will show the required fields in the module settings. For example the following JSON config will only provide Google as login provider : { "providers": { "google": { "className": "Google", "packageName": "league/oauth2-google", "helpUrl": "https://console.developers.google.com/apis/credentials" } } } Small note for pw users : If like me you did not know, there is another module that manages OAuth2 authentication. Feel free to use the one which suit your needs! more info there: @jmartsch you should create a new module thread
  34. 5 likes
    I would have taken another way. As each photo are Page, he could create a module which work with a custom MySQL table where he update the like of a page with some informations aside, like the userID (the user who like the page), the pageID (the page being liked) , a likeStatus (like or unliked) and a timestamp. I made a small module to show the idea : then in the frontend, you can render a 'like' button on choosen templates, and finaly get the total number of likes of a page and the most liked page, see: <?php namespace ProcessWire; $likesmod = $modules->get('LikeSystem'); // render a 'like' button $content = $likesmod->render(); // total like of the page $content .= "Number of likes for this page: " . $likesmod->getTotal($page->id); // most liked page $limit = 1 $mostliked = $likesmod->getMostLikedPage($limit); $content .= "<br>Most liked page: " . $pages->get($mostliked[0]['pageId'])->title . "(" . $mostliked[0]['pageId'] . ") " . " (N likes: ". $mostliked[0]['likesCount'] . ")";
  35. 5 likes
    The JquerySelectize module, that all of the other Selectize family modules require, has been updated (to version 1.0.1) as follows: 1) Checks to see if the core version is 3.0.67 or higher (version at which selectize was added to core), and if so, will load the core files, and not this module's files. Using the ProcessWire core versions of selectize.js improves the user experience, because of the adjustments that Ryan made to the plugin, which relates to hitting enter key after typing a tag (which would by default submit the form). 2) In addition to loading the CSS & JS from the core, when working in versions 3.0.67 or higher, the module still provides a skin selector. While most users will probably stick to the core non-skinned version of Selectize, the module provides this option to choose a different skin. There are some scenarios where choosing the default or bootstrap skins for selectize can be preferable, as they may be higher contrast or more pronounced with color usage, and can help in some ways with accessibility for users with any type of vision impairment. Currently the update/behavior only applies to Selectize modules family that require and use the JquerySelectize module, so if you are using core image tags and have this module installed, it won't change the skin; However there is a plan to try and hook into the image field rendering and enable this module to add the custom selected skin to the css files.
  36. 5 likes
    To solve this you need to look at the code for each method you are considering hooking and ask yourself things like: What class is the method in? Does the method fire when I need it to? Does the method have an argument or return value that I need to use in my hook? So you are considering hooking ProcessLogin::afterLogin() or Session::loginSuccess(). When you look at afterLogin() you see: It is a method of ProcessLogin, a Process module that handles the PW login form. So it is only going to fire if a user logs in via the core PW login form. Maybe that isn't what you want if you are using a custom login form or logging in users via the API as part of some script. It depends on what you are doing. The method comments in the source code say it is only intended for when a superuser logs in, which could give you a clue if it is the best method to hook or not. It has no arguments that could be useful to quickly tell you things about the user who has logged in (although you could still get the $user object in other ways). So chances are Session::loginSuccess() is going to be a better option because it is a method of Session, so more closely connected to the current user session regardless of how they logged in. And it conveniently has the $user object as an argument so you can easily check properties of the user such as name, role, etc, in your hook.
  37. 5 likes
    I suspect that what is actually happening is that the 'normal' browser and 'incognito' mode are caching two different versions of the page, the 'normal' probably being older. Try a completely different browser and see what happens. BTW you could also try changing your code to $meta_title = $page->get('meta_title|title'); which should do the same thing. (Coz that ternary is hideous and unnecessary ) This won't actually help with the current problem, but is much nicer code.
  38. 5 likes
    I do believe this is to do with Let's Encrypt SSL certificates and I don't think this is anything to worry about. Malware with ProcessWire, pah
  39. 4 likes
    Now in the modules directory. Want to update the module soon with some new flavors ... http://modules.processwire.com/modules/markup-processwire-photoswipe/ Cheers!
  40. 4 likes
    Go to Setup > Fields & Setup > Templates, there's two buttons on bottom right that you can use to export/import fields & templates Also, remember to backup your database http://modules.processwire.com/modules/process-database-backups/
  41. This post cannot be displayed because it is in a forum which requires at least 10 posts to view.
  42. 4 likes
    I think perhaps I am not understanding what you are looking for, but does this suit your needs? foreach($page->my_repeater as $item) { foreach($item->fields as $field) { echo $item->{$field->name}; } }
  43. 4 likes
    That approach sounds good to me - very flexible. The only dedicated user hook is: Users::saveReady so you might need to add some logic if you don't want existing users altered when their details are changed. Regarding finding appropriate hooks, take a look at the Captain Hook panel in Tracy: https://processwire.com/blog/posts/introducing-tracy-debugger/#captain-hook-panel It makes it easy to search through classes and methods for things you might need and if you have the editor link protocol set up properly, it will open the file to the class/method you click on.
  44. 4 likes
    I'd love to know which module was causing the problem
  45. 4 likes
    There are some good reads in the forum too :
  46. 4 likes
    Just a couple of observations. It really doesn't 'hurt' anything to have high numbers of pages - they are only database rows after all (slight oversimplification). It might feel wrong or clumsy coming from other CMSs, but PW is more flexible. As far as a selector for recent news items, bear in mind that you can pass any string that can be parsed by strtotime() to a date selector so something like $latestNews = $pages->find("template=mini_news,created>='-18 hours'"); will return a pageArray of all items created in the last 18 hours, which you can manipulate further if necessary. The advantage of using a 'real' page for things like this is that you have all the flexibility that comes with that.
  47. 4 likes
  48. 4 likes
    What @Alxndre' is suggesting is something like the below... First you get your category pages, then you loop over the categories, for each category finding pages that have that category selected in the Page Reference field. If there are some matching pages, output the category heading and the list of posts. $categories = $pages->find("template=category"); foreach($categories as $category) { $posts = $pages->find("template=article-post, category=$category"); if(count($posts)) { echo "<h3>$category->title</h3>"; foreach($posts as $post) { // whatever markup you want for the post echo "<p>$post->title</p>"; } } } This is fine for a lot of circumstances, but note that you do a database query to get the categories and then for each category you do a database query. If you have a lot of categories this would mean a lot of database queries. There is another approach that is more efficient: get all the posts sorted by category (as you are already), then for each post check if the category is different from the previous post's category. If it is different then output a heading. $posts = $pages->find("template=article-post, sort=category"); $category_title = ''; foreach($posts as $post) { if($post->category->title !== $category_title) { $category_title = $post->category->title; echo "<h3>$category_title</h3>"; } // whatever markup you want for the post echo "<p>$post->title</p>"; }
  49. 4 likes
    Turns out it's not difficult to do this using the core ImageSizer. Here's a function showing the basics - could be turned into a module, enhanced with some error checking, etc. /** * Resize Pageimage using custom name/path * * @param Pageimage $pageimage * @param int $width * @param int $height * @param array $options * * @return string * */ function resizeImage($pageimage, $width = 0, $height = 0, $options = array()) { $filename = $pageimage->filename(); $basename = $pageimage->basename(); $path = rtrim($filename, $basename); $dirname = $width . 'x' . $height; $variation_name = $path . $dirname . '/' . $basename; $variation_url = rtrim($pageimage->url(), $basename) . $dirname . '/' . $basename; $force_new = isset($options['forceNew']) ? $options['forceNew'] : false; if(!file_exists($variation_name) || $force_new) { wire('files')->mkdir($path . $dirname . '/'); copy($filename, $variation_name); $sizer = new ImageSizer($variation_name); $sizer->setOptions($options); $sizer->resize($width, $height); wire('files')->chmod($variation_name); } return $variation_url; } // example $image = $page->images->first(); $src = resizeImage($image, 500, 300); echo "<img src='$src'>";
  50. 4 likes
    You can still use CSS in the admin if you feel so, eg. with Admin Custom Files or AdminOnSteroids, for example.