Leaderboard
Popular Content
Showing content with the highest reputation on 03/27/2018 in all areas
-
JavaScript, CSS and $this->wire('files')->render('your-file') in Runtime Markup code setting . 'your'file' has the PHP. See video demo below. Works on the fly, no need to save first + shows what's already saved. I do . See video demo. Another option is RM as suggested by @Sergio. See this quick demo. I can post the code later in RM's forum. Picture Options is a Select Options field. Options Illustration is a RuntimeMarkup Field.6 points
-
4 points
-
I don't know technical details but I think browsers cache this redirections somehow. Try it with a different browser, try writing http:// manually to the url, try adding ?v=1 to your url (with http:// of course)3 points
-
You can check for AJAX calls with: $config->ajax Sets to true if request is async.2 points
-
I figured it out, hopefully this might be useful to others as well: // Remove current thumbnail first. $currentThumb = $page->image_thumb->first(); $page->image_thumb->delete($currentThumb); // Get the image we want to copy. $original = $page->image_front; $file = $original->getFile($original); // Clone that image and append it to the image_thumb field. $new = $original->clone($file, array('action' => 'append', 'pagefiles' => $page->image_thumb)); $page->save(); I noticed that this does not work if the filename of the file you are trying to clone ends with -0 (e.g. myfile-0.jpg). I think that is a bug which I'll post to GitHub.2 points
-
Finally I've managed to migrate the site. In principle it is very easy and very close to the procedure @teppo has described. But there are some pitfalls. After the initial migration, I couldn't even call the pw admin site. Both the admin and the web site produced error messages. It took me a while to find out that I had to edit the .htaccess file as well. My prod server didn't work with some dev server settings. Last issue was that some pictures had absolute paths instead of relatives. No idea what has caused this. A bug or my mistake ? No clue, but they were of course not accessible from the prod server. I changed the wrong paths manually on the prod server and that's pretty much it. Site works now. Next time I'll try the duplicator app. Maybe that will work a bit smoother.2 points
-
I noticed that you're already did what I want to achieve on your Page Protector module and after studying your code I finally managed to display the multi-language values on the frontend. Thank you @adrian2 points
-
Welcome to the forums @redpanda Community member @dadish kindly released a PW3-compatible version of the Skyscrapers profile here: https://github.com/dadish/pw-skyscrapers-profile2 points
-
If that's the exact code, you have an error. It should read "$p->parent->lastcreated", not "$parent->lastcreated". Perhaps that's the whole cause. I'd put the code in a "saved" (or even better "added", since the created property isn't changed after the first save) hook though, so that updating the parent only happens after successfully saving the book page. <?php $pages->addHookAfter('added', function($event) { $p = $event->arguments(0); if($p->template->name == 'book') { $parent = $p->parent; $lastcreated = $parent->lastcreated; if($p->created > $lastcreated) { $parent->lastcreated = $p->created; } // Save just the lastcreated field and leave modified user and time alone $parent->save("lastcreated", ["quiet" => true]); } });2 points
-
2 points
-
This module -- actually pair of modules -- keeps track of login attempts to your site, both successful and unsuccessful. The point of this is to give you better understanding about users' activity and environments they use and favor; browsers, browser features such as Flash and Javascript, devices, screen and window sizes. Based on my experience it could also prove out to be rather helpful when debugging error reports by users. Since most of this module was written during one weekend (short time for someone like me who way too often gets stuck trying to make meaningless little details "perfect") I'm very much aware that there are still quirks and even missing features I'd consider "very important." If anyone finds this module interesting enough to give it a try, any and all ideas, comments and problem reports would be very much appreciated! http://modules.proce...-login-history/ https://github.com/t...essLoginHistory How does it work? Process Login History itself is pretty simple and focuses on showing logged data. Probably only thing worth mentioning is that user agent strings are saved to database in their original format and converted here run-time into human readable values. Currently this is done with a private function that tries to identify most common platforms, device types and browsers, but I've planned to add support to either phpbrowscap and/or PHP's native get_browser(). Login tracking is achieved by hooking after Session::login and ProcessLogin::buildLoginForm, though latter is only used to add some extra fields to login form in order to collect slightly more information about user agent. Hooks are added by separate autoload module Process Login History Hooks to avoid loading unnecessary stuff all the time. I'm not sure if this naming convention is correct though.. it's not a process module but still very much related to one -- ideas, anyone? How do you use it? When installed this module adds new database table (process_login_history) for storing data and new page called Login History under Admin > Setup. What this page should look after couple of logins / login attempts is visible in the screenshot below. For more information and some ideas I've planned for later revisions, see README.md.1 point
-
Hi! Just finished my first module This module adds a new "Google-Analytics" Page in your Admin-Panel and displays various Statistics from a Google Analytics Account. It uses the JQuery plugin "jqplot" to display the charts. Github: https://github.com/w...GoogleAnalytics Modules directory: http://modules.proce...ogle-analytics/ Features Visits by Date (Chart) General Statistics about Visits (Total visits, Visit duration, New visitors, Returning visitors etc.) Demographics: Countries, Cities, Languages System: Browsers, Operating Systems, Screen Resolutions Mobile: Operating Systems, Screen Resolutions Pageviews by Date (Chart) Top Content Traffic Sources: Keywords, Referral Traffic by Domain and URI Choose a default date range displaying statistics: last 24 hours, 2 days, 1 week, 1 month etc. Custom date range by setting a "start date" and "end date" Requirements Google Account and Analytics Account A Project in the Google APIs Console cURL Installation 1) Create a Project in the Google APIs Console: Create a new Project in the APIs Console: code.google.com/apis/console/ Under Services, enable the Analytics API Under API Access: create an Oauth 2.0 Client-ID Give a Product Name, choose "Web-Application", Domain doesn't matter Enter a Redirect URI to the GA-Page in your Processwire Installation: http://yourdomain.com/processwire/google-analytics/ Notes: The redirect URI must exactly match with the URL from the new "Google Analytics" page in Processwire. Open the Page and copy the URL from the address-bar (localhost does work too!) The project created in the APIs Console can be reused for every Processwire installation using this module. You just have to enter more redirect URIs 2) Install the module: Place the module's files in /site/modules/ProcessGoogleAnalytics Install the Module via the Admin-Panel Enter Client-ID and Client-Secret keys from the created project in the module config Load the newly created page "Google-Analytics" and click on the button "authenticate" Grant the module access to the Analytics Data Choose a Google Analytics account (Domain) from the dropdown Done: You should see the statistics. Check out the module config options for further customization In order to let other users see the Google Analytics page, you must give their role access to the "ga-view" permission. Ps. Processwire is awesome and so is this community!1 point
-
Just something I was trying out recently that might be useful to someone... With the following hook added to /site/ready.php you can adjust the CKEditor toolbar that a particular role gets for a particular field. Use the name of your CKEditor field in the hook condition. $this->addHookBefore('Field(name=my_ckeditor_field)::getInputfield', function(HookEvent $event) { $field = $event->object; // Define toolbar for a particular role if($this->user->hasRole('editor')) $field->toolbar = 'Format, Bold, Italic, -, NumberedList, BulletedList, Outdent, Indent'; }); Or what I find useful on some sites is adding extra toolbar buttons for superuser only that you don't trust editors to use. $this->addHookBefore('Field(name=my_ckeditor_field)::getInputfield', function(HookEvent $event) { $field = $event->object; // Add extra buttons for superuser only if($this->user->isSuperuser()) $field->toolbar .= ', Table, TextColor'; }); You could use the same technique to selectively define other CKEditor settings such as 'stylesSet', 'customOptions', 'extraPlugins', etc.1 point
-
Hi, So today I will writing a small tutorial on developing templates in Processwire using Twig Template, Processwire is a highly flexible CMS which gives developers/designers/users options and allows easy extension of the platform. So here goes the tutorial What is Twig Template ? Simply put in my own words, Twig is a modern templating engine that compiles down to PHP code, unlike PHP, Twig is clean on the eyes , flexible and also quite *easy* to have dynamic layout site with ease ,without pulling your hair out. Twig is trusted by various platforms. It was created by the guys behind Symfony. Take this code as an example {% for user in users %} <h1>* {{ user }}</h1> {% endfor %} This will simply be the equivalent in PHP World <?php $userArray = ["Nigeria","Russia"]; foreach($userArray as $user): ?> <h1><?= $user ?></h1> <?php endforeach; The PHP code though looks simple enough however, you start to notice that you have to be concerned about the PHP tags by ensuring they are closed properly , most times projects gets bigger and comes complex and harder to read/grasp, and also in PHP you can explicitly create variables in the template making it very hard to read as it grows and prone to getting messy WordPress is a major culprit when it comes to that regard. Have you ever wanted to created separate layouts for different pages and break your sites into different parts e.g Sidebar, Comment Section, Header Section ? the regular approach would be to create individual pages for each section and simply add them as templates for the pages and with time, you can end up having tons of templates, however Twig allows you to easily inherit templates and also override the templates where you can inject content into the block easily. Don't worry if you don't understand the concept, the following parts will explain with an example of how to easily inherit layouts and templates. Layout <!DOCTYPE html> <html lang="en"> <head> {{include("layout/elements/header.twig")}} </head> <body> <div class="container-fluid" id="minimal"> <header id="pageIntro"> <div class="bio_panel"> <div class="bio_section col-md-6"> <h1>Okeowo Aderemi</h1> <h2>{{ page.body }}</h2> </div> </div> <div class="clearfix"></div> </header> <section id="page-body"> <div class="container"> <div id="intro" class="col-md-7 col-lg-7"> <h1>About me</h1> <h2> {{ page.summary }} </h2> </div> {block name="content"}{/block} <a style="font-size:1.799783em; font-style:italic;color:#d29c23" href="{{pages.get('/notes').url }}">Read more articles</a> </div> <div class="clearfix"></div> </div> </section> </div> <footer> <div class="header-container headroom headroom--not-top headroom--pinned" id="header-container"> {{include("layout/elements/footer.twig")}} </div> </footer> </body> </html> This is basically a layout where we specify blocks and include other templates for the page, don't panic if you don't understand what is going on, I will simply break down the weird part as follows: Include This basically is similar to native PHP 'include', as it's name suggests it simply includes the templates and injects the content into the layout , nothing out of the ordinary here if you are already familiar with php's include function. {{ output }} This simply evaluates the expression and prints the value, this evaluate expressions, functions that return contents , in my own short words it's basically the same as <?= output ?> except for the fact that it's cleaner to read. {% expression %} unlike the previous this executes statements such as for loops and other Twig statements. {% for characters in attack_on_titans %} <h1> {{characters}} </h1> {% endfor %} This executes a for loop and within the for loop, it creates a context to which variables in that context can be referenced and evaluated, unlike dealing with the opening and closing PHP tags, Twig simply blends in with markup and makes it really quick to read. I will simply post the contents of both the header and footer so you can see the content of what is included in the layout header.php <meta charset="utf-8"/> <meta content="IE=edge" http-equiv="X-UA-Compatible"/> <meta content="width=device-width, initial-scale=1" name="viewport"/> <title> {{ page.title }} </title> <link href=" {{config.urls.templates }}assets/css/bootstrap.min.css" rel="stylesheet"/> <link href="{{config.urls.templates }}assets/css/main.min.css" rel="stylesheet"/> <link rel='stylesheet' type='text/css' href='{{config.urls.FieldtypeComments}}comments.css' /> <link rel="stylesheet" href="{{config.urls.siteModules}}InputfieldCKEditor/plugins/codesnippet/lib/highlight/styles/vs.css"> <script type="text/javascript" src="{{config.urls.siteModules}}InputfieldCKEditor/plugins/codesnippet/lib/highlight/highlight.pack.js"></script> <script src="{{config.urls.templates }}assets/js/vendors/jquery-1.11.3.min.js"> </script> <script src="{{config.urls.templates }}assets/js/vendors/bootstrap.min.js"> </script> <script src="{{config.urls.FieldtypeComments}}comments.js"></script> <link rel="stylesheet" type='text/css' href="{{config.urls.templates}}js/jquery.fancybox.min.css"> <script src="{{config.urls.templates}}js/jquery.fancybox.min.js"></script> {block name="javascriptcodes"}{/block} footer.php <nav class="site-nav pull-right"> <div class="trigger"> <a class="page-link" href="{{pages.get('/about').url}}"> <span>{</span> About <span>}</span> </a> <a class="page-link" href="{{pages.get('/notes').url}}"> <span>{</span> Journals <span>}</span> </a> <a class="page-link" target="_blank" href="https://ng.linkedin.com/in/okeowo-aderemi-82b75730"> <span>{</span> Linkedin <span>}</span> </a> <a class="twitter page-link" target="_blank" href="https://twitter.com/qtguru"> <span>{</span> Twitter <span>}</span> </a> </div> </nav> There's nothing special here, other than twig simply injecting these fragments into the main layout , the next part is the most interesting and important concept and benefit that Twig has to offer {% block content %}{% endblock %} This tag simply creates a placeholder in which the content would be provided by the template inheriting this layout, in lay terms it simply means child templates will provide content for that block, the 'content' simply uses the name 'content' to refer to that specific block, so assuming we were to inherit this template it would simply look like this. Inheriting Template Layout {% extends 'layout/blog.twig' %} {% block content %} <div class="container blog-container"> <section class="blog"> <header class="blog-header"> <h1> {{page.title}} </h1> <h5 class="blog_date"> {{page.published|date("F d, Y")}} </h5> <br> </br> </header> <div class="blog_content"> <hr class="small" /> {{page.body}} <hr class="small" /> </div> </section> </div> {% endblock %} {% block nav %} <div class="col-md-4 col-xs-4 col-sm-4 prev-nav"> <a href="{{page.prev.url}}"> ← Prev </a> </div> <div class="col-md-4 col-xs-4 col-sm-4 home-nav"> <a href="{{homepage.url}}"> Home </a> </div> <div class="col-md-4 col-xs-4 col-sm-4 next-nav"> <a href="{{page.next.url}}"> Next → </a> </div> {% endblock %} In this snippet you can easily notice how each blocks previously created in the header and layout are simply referenced by their names, by now you will notice that twig doesn't care how you arrange the order of each block, all Twig does is to get the contents for each blocks in the child templates and inject them in the layout theme, this allows flexible templating and also extending other layouts with ease. Twig in Processwire Thanks to @Wanze we have a Twig Module for Processwire and it's currently what i use to build PW solutions to clients https://modules.processwire.com/modules/template-engine-twig/ The Modules makes it easy to not only use Twig in PW but also specify folders to which it reads the twig templates, and also injects Processwire objects into it, which is why i can easily make reference to the Pages object, another useful feature in this module is that you can use your existing template files to serve as the data provider which will supply the data to be used for twig template. take for example, assuming I wanted the homepage to display the top six blog posts on it, TemplateEngineTwig will simply load the home.php ( Depending on what you set as the template), it is also important that your twig file bears the same name as your template name e.g home.php will render into home.twig here is an example to further explain my point. home.php <?php //Get the Top 6 Blog Posts $found=$pages->find("limit=6,include=hidden,template=blog-post,sort=-blog_date"); $view->set("posts",$found); The $view variable is the TemplateEngine which in this case would be Twig, the set method simply creates a variables posts which holds the data of the blog posts, the method allows our template 'blog.twig' to simply reference the 'posts' variable in Twig Context. Here is the content of the 'blog.twig' template blog.tpl {% extends 'layout/blog.twig' %} {% block content %} <div class="block_articles col-md-5 col-lg-5"> {% for post in posts %} <div class="article_listing"> <span class="article_date"> {{post.published}}</span> <h2 class="article_title"> <a href="{{post.url}}">{{post.title}}</a> </h2> </div> {% endfor %} {% endblock %} So home.php sets the data to be used in home.tpl once Twig processes the templates and generates the output, twig takes the output from the block and injects it in the appriopriate block in the layout, this makes Processwire templating more flexible and fun to work with. The major advantage this has; is that you can easily inherit layouts and provide contents for them with ease, without the need of running into confusions when handling complex layout issues,an example could be providing an administrator dashboard for users on the template side without allowing users into the Processwire back-end. You can also come up with several layouts and reusable templates. Feel free to ask questions and any concerns in this approach or any errors I might have made or overlooked. Thanks1 point
-
HI @Jim Bailie. Welcome to ProcessWire and the forums. I don't quite understand the question. ProcessWire does not output anything in the frontend unless you tell it to. Maybe if you could expound a little bit on your question? Otherwise, it's as simple as: if('your condition here') { // do this } else { // do that } As for Ajax..just to elaborate on @elabx's answer if($config->ajax) { // ajax request sent; output ajaxy response stuff and exit; $data = array('foo', 'bar'); header('Content-Type: application/json'); echo json_encode($data); exit;// or use PW halt() if it suits your needs } // echo normal non-ajax stuff1 point
-
1 point
-
I would adapt the local server to be as much equal as the production server, if possible.1 point
-
Hello Bernhard, Thanks that was the issue! Didn't think of clearing the cache for that issue!!! Many Thanks Jon1 point
-
May I ask why you want to clone an image in the first place? If you need thumbnails, just use $image->size($width, $height, $options) in your templates.1 point
-
Maybe this is the issue I tripped over? I just did not bother looking into it as I did not have time to do so. I had reverted back to the hook I posted above and using it ever since.1 point
-
Another approach besides the one @flydev mentioned: $pages->addHookAfter('saveReady', function(HookEvent $event) { $page = $event->arguments(0); if($page->template != 'newsletters') return; // Only for specific template $page->newsletters->sort('-year'); // Sort the repeater how you want // Sequentially set the sort property of the repeater items $i = 0; foreach($page->newsletters as $item) { $item->sort = $i; $i++; } });1 point
-
Actually, I got that wrong - the problem is that you need the trailing slash after the urlsegment for it to work, which doesn't seem right to me since it can be optional.1 point
-
from @Robin S $this->addHookAfter('FieldtypeRepeater::wakeupValue', function($event) { $field = $event->arguments('field'); if($field->name !== 'myrepeater') return; // repeater field name $pa = $event->return; $pa->sort("-created"); // or whatever sort you want $event->return = $pa; }); Will return : 2018 2017 2016 ...1 point
-
1 point
-
@gebeer It looks like you got further into the solution than I did. Tabs and repeaters were a problem for the Profile Edit screen for me, too. So I eventually gave up and gave users of that template type a role that gave them page-edit permissions, but I hadn't worked out how to make sure that people were only editing their own profiles - so I think your two hooks are helpful there! I know there's a difference in form and function for Profile Edit and Page Edit, but our scenario (where Users = Content) seems like a time when keeping the two completely separate breaks down. I think your method is clean and I will give implementation a shot. Thank you!! Edit: Just saw you'd posted twice in this thread, and just now read the first: That. Exactly that. In full! If it's on a profile page, and I've given the user the permission to edit their page...they should be able to do whatever is necessary on it.1 point
-
Maybe like this https://pragprog.com/book/btlang/seven-languages-in-seven-weeks. Not the right languages, but I've heard it's quite good.1 point
-
It's a bit on the back burner right now. I was hoping for some more elaborate possibilities with MySQL 8, but the changes to JSON support there were on the homeopathic end rather than the dynamic typing support I had hoped for. I plan to brush up the UI and client side validation a bit once PW 3.1 gets out, though, and test things out in depth with UIkit admin theme.1 point
-
Or if you prefer you could turn on the functions API: https://processwire.com/blog/posts/processwire-3.0.39-core-updates/#new-functions-api and do this: $homepage = pages('/'); PS Welcome to the forums!1 point
-
Another minor UI thing: the new UI can look a bit funny with AdminThemeDefault and AdminThemeReno because opening the history doesn't trigger the core column sizing JS, so the height of fields in the same row isn't updated to match. It's not a big deal, and the issue doesn't occur for AdminThemeUikit so maybe not worth doing anything about. If there is a column to the right of an open history container then the history gets overlapped by 1 pixel on the right edge. Again, not a big deal, but maybe these things could be avoided by using absolute positioning for the history container so it appears in a layer over the Page Edit interface and doesn't expand the inputfield column. Using the core panel UI could be a nice alternative too, but I don't know how easy it is to inject HTML directly into this. I think it's only set up to handle iframes (which if so is a bit of a shame because it reduces the range of things the panel can be used for).1 point
-
Hi @davenorton Welcome to ProcessWire! It is a scope problem: The $pages variable is not known inside your function by PHP. Use the global wire() function to get any API variable. At the beginning of your function, write this: $pages = wire('pages'); And it should start to work. Cheers1 point
-
Hello All, I started with Joomla on this CMS parade. Then later I put my hands on Wordpress and eventually settled into Concrete 5. Recently I came to know about ProcessWire CMS, while I was looking for a better replacement for Concrete 5 in terms of robust, easiness, compact. I went through few Video tutorials of PW and read the documentation. The installation of PW was smoothy. The concept of Page is awsome. I like the relation of Page, Fields & Templates. There are many things I can equal to Concrete 5 from ProcessWire, yet PW offers a tiny minimal, code level approach for CMS. We have full control over design & data. There are some pitfalls I felt such no sitewide settings, global logo setup etc but it is easily tackled with some API codes. PW api seems amazing, it is par with what Concrete 5 Offers. I have done a simple website in ProcessWire now and going to do a lot further : http://insignnia.in/1 point
-
Your website looks great! For sitewide settings, you can simply use a page which is not listed/published or: http://modules.processwire.com/modules/process-general-settings/ http://modules.processwire.com/modules/textformatter-multi-value/ EDIT: In my last project, I've simply added a fieldset with settings fields in it to the bottom of the home (root) page. Then get values from anywhere like: $pages->get(1)->site_name; //"home" is always 11 point
-
@ak1001, Forget about home.html. You only need to use home.php and home.twig. All of your PHP stuff goes into home.php and your html stuff goes into home.twig. Example: in home.php <?php namespace ProcessWire; $view->set('mytitle', $page->title); In home.twig <h1>{{ mytitle }}</h1> <div class="body-content">{{ page.body }}</div> This code assumes your Processwire home template has a title and body field.1 point
-
Hi @ak1001, Welcome, I personally like to use home.php as my controller and home.twig as the view. Using the .twig can provide Twig syntax highlighting in some text editors. Make sure you set the .twig extension in the Template Engine Twig module settings > Template File Suffix I prefer to keep those file in the same directory. It's less context switching when your looking for files. Template Engine Twig module settings > Path to templates = templates/ In your case, I would use the following: site/templates/home.twig <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <title>{{ page.title }}</title> </head> <body> <h1>{{ page.title }}</h1> {{page.body}} <hr> <p>myvar:{{myvar}}</p> </body> </html> site/templates/home.php <?php $view->set("myvar", "my test var"); Hope that helps1 point
-
Hi @ak1001 Thanks! 1. Do not delete /site/templates/home.php. This file now acts as a "controller" and you will pass variables to your twig templates via $view. 2. You don't need this file, the corresponding twig file would be in /site/templates/views/home.html. Note that this path and the file extension depend on your module configuration. 3. Yes, this is actually your twig template. The below code belongs to /site/templates/home.php <?php $view->set("myvar", "my test var"); Think of this file as layer between ProcessWire and your twig template. So instead of echo stuff there, pass it to the twig template which takes care of the rendering. Hope that helps! Cheers1 point
-
@ridgedale, the essence of your scenario is that you have some newsletters, and for each newsletter you have: 1. A file 2. Some metadata about that file (its month and its year) You want to associate the metadata with the file. Using a file naming scheme and a folder structure is one way to associate the metadata with the files, but it's not the only way, nor is it the "ProcessWire way". The ProcessWire way is to use pages and/or the metadata fields that are built into File fields. If you have a lot of metadata to associate with a file then the most powerful approach is the "one page per file" approach, which is what @LMD is suggesting. This way you can use all the available PW fields in your file page to store different kinds of metadata about each file. But the downside to this approach is that the UI/workflow can be a bit less than ideal for clients, especially if they have a lot of files that they want to upload at once and populate metadata for. That's because, generally speaking, they will have to edit each file page individually (although there can be solutions to this such as Lister Pro inline editing, Batch Child Editor, and automatically creating a page for each file similar to what is done in AutoImagePages). But you only have a little bit of metadata per file so I don't think you need to go this sophisticated. Instead I suggest you use a repeater for the years, and file tags for the months. See these screenshots... Then you can get the latest newsletter like this... $newsletters_page = $pages->get("template=newsletters"); $latest_year = $newsletters_page->newsletters->find("newsletter_files.count>0, sort=-year")->first(); $latest_newsletter = $latest_year->newsletter_files->sort("-tags")->first(); $month = substr($latest_newsletter->tags, 3); echo "<a href='$latest_newsletter->url'>$month $latest_year->year</a>"; The pages that visitors browse the newsletters through can be completely decoupled from the storage of the newsletter files. You just get the relevant repeater items by year according to the page being viewed, e.g. $years = $newsletters_page->newsletters->find("year>=1980, year<1990, sort=year"); You could even do it without actual pages for each decade, and use URL segments to get any range of years.1 point
-
How big is your navigation? If you've got a huge flyout nav with 4 levels, you should use soma's MarkupSimpleNavigation module or something similar. Maybe your site isn't multilang, but using the page-slug will not go so well if you ever decide to add another language to your site. Why not simply use <a href="<?=$pages->get(123)->url?>"><?=$pages->get(123)->title?></a> instead?1 point
-
Don't use relative paths. They are an artifact of days long gone (at least in regular websites - there are use cases in routed app components) and they create more problems than they solve. If you worry what happens when you move a site to a deeper directory, use PHP to prefix your links with $config->urls->root.1 point
-
This fieldtype wont be suitable for what you want. Instead, add a hidden textarea field to your templates, then in a saveReady hook loop over the template fields and add markup-free text (use strip_tags) to the textarea. You'll need an if/else structure to handle all the different field types you want to support.1 point
-
<?php /** * * TwigExtensions * * See README.md for usage instructions. * * @author Jens Martsch <jmartsch@gmail.com> * @version 1.0.0 * @see http://www.processwire.com */ /** * Class TwigExtensions */ class FriendChipFunctionsForTwig extends WireData implements Module { /** * Retrieves module meta data * Implementation of the Module interface * * @return array * @see http://processwire.com/apigen/class-Module.html */ public static function getModuleInfo() { return array( 'title' => 'Functions for Twig', 'summary' => 'Allows customizing twig, e.g. add extensions', 'version' => 100, 'singular' => true, 'autoload' => true, 'icon' => 'puzzle-piece', 'requires' => array( 'TemplateEngineFactory', 'TemplateEngineTwig' ) ); } public function init() { if ($this->modules->get('TemplateEngineTwig')) { $this->addHookBefore('TemplateEngineTwig::initTwig', $this, 'addExtensions'); } } /** * Hook add twig extensions * * @param HookEvent $event */ public function addExtensions(HookEvent $event) { $this->twig = $event->arguments('twig'); $this->addRelativeTimeStr(); $this->asset_path(''); } private function addRelativeTimeStr() { $twigWireRelativeTimeStr = new Twig_SimpleFunction('wireRelativeTimeStr', function ($date) { return wireRelativeTimeStr($date); }); $this->twig->addFunction($twigWireRelativeTimeStr); } /** * Returns the revved filename if a rev-manifest exists * else it returns the $filename * @param string $filename relative to the template/assets directory * @return string */ private function asset_path($filename) { $assetPathFunction = new Twig_SimpleFunction('asset_path', function ($filename) { $manifest_path = $this->config->paths->templates . 'assets/rev-manifest.json'; if (file_exists($manifest_path)) { $manifest = json_decode(file_get_contents($manifest_path), TRUE); } else { return $this->config->urls->templates . "assets/" . $filename; } if (array_key_exists($filename, $manifest)) { $manifest_filename = \ProcessWire\wire()->config->urls->templates . "assets/public/" . $manifest[$filename]; return $manifest_filename; } }); $this->twig->addFunction($assetPathFunction); } } or take a look at https://github.com/justb3a/processwire-twigextensions1 point
-
Maybe people should be forking some of these kind of modules and breathing some new life into them.1 point
-
I have actually used it on an old site that I upgraded to PW 3 and it seemed to work fine. The site isn't really active anymore unfortunately (government budgets and all mean that the program is no longer active), so I haven't checked in a year, but I think you'll be ok. It's a pretty simple module.1 point
-
I have turned it into a Page method: /* Returns the complete URL of a Page. * * @param int arguments[0] Pass false to exclude the pageNum of the Paginator * @return string */ $wire->addHookMethod('Page::siteCompleteUrl', function($event) { $withPageNum = isset($event->arguments[0]) ? $event->arguments[0] : true; $full_url = page()->url; if (input()->urlSegmentsStr) { $full_url .= input()->urlSegmentsStr; } if ($withPageNum && input()->pageNum > 1) { $full_url .= "/" . config()->pageNumUrlPrefix . input()->pageNum; } $event->return = $full_url; }); Small changes were applied to it: "/" is added in the second "if" to avoid double slashes on the pages of the paginator first argument can be used to exclude the page number of the paginator, defaults to true1 point
-
I am sending the NEW version of Simple Contact Form. This new version uses module MarkupGoogleRecaptcha in order to render the captcha. If you have downloaded Valitron validator through composer then add the following line to your _head.php: include(dirname(__FILE__) . "/../../vendor/autoload.php"); or include the old-way on the top of "partials/contact/_controller.php": require(dirname(__FILE__) . "/../../vendor/vlucas/valitron/src/Valitron/Validator.php"); There are 3 files currently: contact.php (template) partials/contact/_controller.php partials/contact/_email.php First file contact.php: <?php namespace ProcessWire; include('partials/contact/_controller.php'); ?> <h2><?php echo __('Contact Form') ?></h2> <?php if($session->flashMessage):?> <div class="alert <?php echo $session->sent ? 'alert-success' : 'alert-danger'?>" role="alert"> <?php echo $session->flashMessage;?> </div> <?php endif;?> <form id="contact-form" action="<?php echo $page->url;?>" method="post"> <div class="form-group <?php echo $v->errors('name') ? 'has-error' : ''?>"> <div class="input-group"> <span class="input-group-addon"><i class="fa fa-user"></i></span> <input required class="input-lg form-control" name="name" id="name" type="text" value="<?php echo $name?>" placeholder="<?php echo __('Name') ?>"> </div> </div> <div class="form-group <?php echo $v->errors('email') ? 'has-error' : ''?>"> <div class="input-group"> <span class="input-group-addon"><i class="fa fa-envelope"></i></span> <input required class="input-lg form-control" name="email" id="email" type="email" value="<?php echo $email?>" placeholder="<?php echo __('Email') ?>"> </div> </div> <div class="form-group <?php echo $v->errors('phone') ? 'has-error' : ''?>"> <div class="input-group"> <span class="input-group-addon"><i class="fa fa-phone"></i></span> <input class="input-lg form-control" name="phone" id="phone" type="tel" value="<?php echo $phone?>" placeholder="<?php echo __('Phone') ?>"> </div> <div class="field-notice" rel="phone"></div> </div> <div class="form-group <?php echo $v->errors('message') ? 'has-error' : ''?>"> <div class="input-group"> <span class="input-group-addon"><i class="fa fa fa-quote-left"></i></span> <textarea rows="7" class="input-lg form-control" placeholder="<?php echo __('Message') ?>" name="message" id="message"><?php echo $message?></textarea> </div> </div> <div class="form-group"> <?php echo $captcha->render()?> </div> <button id="contact_send" class="btn btn-default" type="submit"><i class="fa fa-paper-plane" aria-hidden="true"></i> <?php echo __('SEND') ?></button> </form> <?php $session->remove('flashMessage'); $session->sent = false; echo $captcha->getScript(); ?> Then file partials/contact/_controller.php: <?php namespace ProcessWire; use Valitron\Validator; $captcha = $modules->get("MarkupGoogleRecaptcha"); $name = $sanitizer->text($input->post->name); $email = $sanitizer->email($input->post->email); $phone = $sanitizer->text($input->post->phone); $message = $sanitizer->text($input->post->message); $v = new Validator([ 'name' => $name, 'email' => $email, 'message' => $message, ]); $v->rule('required', ['name', 'email', 'message']); $v->rule('email', 'email'); $contactFormRecipient = 'your@company.com'; if ($input->post->name) { if ($v->validate()) { if ($captcha->verifyResponse() === true) { $subject = 'Contact Form'; $messageHTML = include_once('partials/contact/_email.php'); $mail = wireMail() ->to($contactFormRecipient) ->header('Reply-To', $email) ->subject($subject) ->bodyHTML($messageHTML); if ($mail->send()) { $session->flashMessage = __('Thank you for your message! We will get back to you.'); $session->sent = true; $session->redirect($page->url); } else { $session->flashMessage = __('Mail not sent. Error occured.'); } } else { $session->flashMessage = __('Recaptcha Validation Error'); } } else { $session->flashMessage = __('Please correct the errors and try again.'); } } ?> And finally partials/contact/_email.php: <?php ob_start();?> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title><?php echo $subject; ?></title> </head> <body> <h1>New message from <i><?php echo $name; ?></i></h1> <p><?php echo $message; ?></p> <hr> <h3>Contact Info:</h3> <p><strong>Name:</strong> <?php echo $name; ?></p> <?php if(!empty($phone)):?> <p><strong>Phone:</strong> <?php echo $phone; ?></p> <?php endif;?> <p><strong>Email:</strong> <a href="mailto:<?php echo $email; ?>"><?php echo $email; ?></a></p> </body> </html> <?php return ob_get_clean();?> Hope you like it!1 point
-
I mostly try to use FormBuilder which does all the work However, if I need special forms in terms of styling or functionality, I'm using the ProcessWire form API to render and validate/process the input. You also get free CSRF protection. Here's an example how roughly works (written in browser, not copy paste ready): // Controller contact.php // ------------------------ $form = $modules->get('InputfieldForm'); $select = $modules->get('InputfieldSelect'); $select->required = 1; $select->setOptions(['blub', 'blub2']); $form->append($select); $csrf = $session->CSRF->renderInput(); $view->set('inputs', ['select' => $select', 'csrf' => $csrf]); // Validate and process the input if submitted if ($isSubmitted) { $form->processInput($input->post); // Handle errors and success } // View contact.twig // ------------------------------ <form action="{{ page.url}} " method="POST"> <label>Select</label> {{ form.select.render() }} {{ csrf }} <button type="submit">Submit me</button> </form> Cheers1 point
-
Hi, I just tried to install this module in ProcessWire 3.0.44 and everything works fine to the point where i have to log in with google and authenticate. Right after that step comes the following error: Could not fetch the accessToken Is this a problem with the current Version or did I miss something? I hope anyone can help to make this great addon working. Thanks in advance!1 point
-
Database migrations are "better" supported in other frameworks because they wouldn't function without them. A laravel framework doesn't have any databases beside those setup by migrations. But these are actually really bare bone in that they mostly update schema changes. For migrating db rows most use database seeding libraries, which then create the necessary models to be stored. Both can be done with the processwire api without actually needing to learn something new. This is basically what model factories do in frameworks (besides the autofill functionality). $pages->add('basic-page', $pages->get("/url/"), 'profile', array( "someField" => "value", "anotherField" => 202 ); And this may be what migrations do. // Create field $f = new Field(); $f->name = 'text'; $f->type = 'FieldtypeTextarea'; … $f->save(); // Add to template $bpt = $templates->get('basic-page'); $bpt->fields->add($f); $bpt->fields->save();1 point
-
Just in case someone else wants to do something like this, I just wanted to note that hooking before ProcessPageEdit::execute and foreaching through wire('breadcrumbs') works well. But for my particular needs I had problems with this approach and the Reno theme and so had to take another approach which worked: hooking after Process::breadcrumb Anyway, thought it might be useful info for others.1 point
-
Another option would be to add a small module that hooks Page::render and replaces the paths directly in the output... when running on localhost. It could replace it with something like this: public function init() { if(wire('config')->httpHost == 'localhost') { $this->addHook('Page::render', $this, 'hookAfterPageRender'); } } public function hookAfterPageRender($event) { $event->return = str_replace('/site/assets/files/', 'http://domain.com/site/assets/files/', $event->return); }1 point
-
Ok, gotcha. I did this on a site a while back. I'll dig up the code and get back to you in a minute. Actually I did this on the very first PW site I built, so there might be a better way, but this still works. Create a new datetime field called last_page_load and add it to the user template (remember to "Show System Templates" so you have access to it). Put this code somewhere that is included on every page of the site: if ($user->isLoggedin()) { $user->last_page_load = date('Y-m-d H:i:s'); $user->of(false); $user->save(); } Then you can do this: foreach($articles as $article){ if($user->isLoggedin() && $article->modified > $user->last_page_load){ // echo article here } } Let me know how you go with that. I don't think I have missed any components in making this work, and maybe there is a better approach, but one advantage of this over cookies is that it won't matter whether the user clears their cookies or not, or if they are visiting from different devices. EDIT: Thinking through this - in my case I had already limited $articles with a find. You may want to do the same as foreach'ing through 100's/1000's of articles won't be a good idea, so you could use something like this: $new_articles = $pages->find("template=articles,modified>{$user->last_page_load}");1 point