Leaderboard
Popular Content
Showing content with the highest reputation on 03/22/2018 in all areas
-
Not sure where the best place for this is, but I felt like sharing a little snippet for site/ready.php I wrote that creates a PageArray::groupBy method: <?php /** * * Adds a groupBy method to all PageArray instances. * * Returns a nested array, with the values of the page properties whose names * were passed as arguments as the keys. * * Usage: * ====== * * $grouped = $mypagearray->groupBy(field1 [, field2 ...] [, mutator_function]); * or * $grouped = $mypagearray->groupBy(mutator_function); * * Example: * ======== * * $mypagearray = $pages->find("template=blog-post, sort=year, sort=month"); * $grouped = $mypagearray->groupBy("year", "month"); * * foreach($grouped as $year => $monthgroup) { * echo "<div class='year'><h2>$year</h2>" . PHP_EOL; * echo "\t<ul class='month'>" . PHP_EOL; * * foreach($monthgroup as $month => $mypages) { * echo "\t\t<li><h3>$month</h3>" . PHP_EOL; * * echo "\t\t\t<ul class='post'>" . PHP_EOL; * * foreach($mypages as $post) { * echo "\t\t\t\t<li><a href='{$post->url}'>{$post->title}</a></li>" . PHP_EOL; * } * * echo "\t\t\t</ul>\n" . PHP_EOL; * echo "\t\t</li>\n" . PHP_EOL; * } * * echo "\t</li>" . PHP_EOL; * echo "</ul>" . PHP_EOL; * } * * Example Output: * =============== * * <div class='year'><h2>2016</h2> * <ul class='month'> * <li><h3>1</h3> * <ul class='post'> * <li><a href='/grouptest/grouptest-10/grouptest-10-10/'>Group Test 10 10</a></li> * <li><a href='/grouptest/grouptest-6/grouptest-6-10/'>Group Test 6 10</a></li> * <li><a href='/grouptest/grouptest-10/grouptest-10-12/'>Group Test 10 12</a></li> * <li><a href='/grouptest/grouptest-1/grouptest-1-12/'>Group Test 1 12</a></li> * <li><a href='/grouptest/grouptest-5/grouptest-5-3/'>Group Test 5 3</a></li> * <li><a href='/grouptest/grouptest-10/grouptest-10-4/'>Group Test 10 4</a></li> * <li><a href='/grouptest/grouptest-3/'>Group Test 3</a></li> * <li><a href='/grouptest/grouptest-6/grouptest-6-4/'>Group Test 6 4</a></li> * <li><a href='/grouptest/grouptest-10/grouptest-10-7/'>Group Test 10 7</a></li> * </ul> * * </li> * * <li><h3>2</h3> * <ul class='post'> * <li><a href='/grouptest/grouptest-5/grouptest-5-10/'>Group Test 5 10</a></li> * <li><a href='/grouptest/grouptest-3/grouptest-3-7/'>Group Test 3 7</a></li> * <li><a href='/grouptest/grouptest-9/grouptest-9-5/'>Group Test 9 5</a></li> * <li><a href='/grouptest/grouptest-7/grouptest-7-12/'>Group Test 7 12</a></li> * <li><a href='/grouptest/grouptest-3/grouptest-3-11/'>Group Test 3 11</a></li> * <li><a href='/grouptest/grouptest-9/grouptest-9-11/'>Group Test 9 11</a></li> * </ul> * * </li> * * <li><h3>3</h3> * <ul class='post'> * <li><a href='/grouptest/grouptest-7/grouptest-7-10/'>Group Test 7 10</a></li> * <li><a href='/grouptest/grouptest-12/grouptest-12-12/'>Group Test 12 12</a></li> * <li><a href='/grouptest/grouptest-11/grouptest-11-5/'>Group Test 11 5</a></li> * </ul> * * </li> * * <li><h3>4</h3> * <ul class='post'> * <li><a href='/grouptest/grouptest-8/grouptest-8-3/'>Group Test 8 3</a></li> * <li><a href='/grouptest/grouptest-12/grouptest-12-6/'>Group Test 12 6</a></li> * </ul> * * </li> * * </li> * </ul> * * IMPORTANT! * ========== * * Your PageArray needs to be sorted by the fields you group by, or your return array will be an * unorderly mess (the grouping is still correct, but the order of keys is arbitrary). * * Mutator Function: * ================= * * Instead of just reading properties from the page, you can also pass a mutator function * to groupBy. It gets passed the page object as its first arguments and any optional property * names after that. * * This might come in handy if you want to group by year and month but only have a single * DateTime field. You can then use a grouping function like this: * * $blogposts = $pages->find("template=blog-post, sort=created"); * $grouped = $blogposts->groupBy(function($pg) { * return array(strftime('%Y', $pg->created), strftime('%m', $pg->created)); * }); * */ wire()->addHook("PageArray::groupBy", function(HookEvent $event) { $out = array(); $args = $event->arguments(); if(count($args) == 0) throw new InvalidArgumentException("Missing arguments for function PageArray::groupBy, at least 1 required!"); $last = count($args) - 1; $fnc = is_string($args[$last]) ? FALSE : array_pop($args); foreach($event->object as $pg) { if($fnc) { $props = call_user_func_array($fnc, array_merge(array($pg), $args)); } else { $props = array_map(function($propname) use($pg) { return $pg->{$propname}; }, $args); } $cur = &$out; foreach($props as $prop) { if(!isset($cur[$prop])) $cur[$prop] = array(); $cur = &$cur[$prop]; } $cur[] = $pg; } $event->return = $out; });8 points
-
Pages Export This module is for specifically exporting ProcessWire 2.x sites for later importing into ProcessWire 3.x. Github: Project Page Modules Directory: https://modules.processwire.com/modules/process-pages-export/ Credits: Ryan Cramer Background As I make my modules ProcessWire 3.x-compatible only, I've had the need to re-create/mirror their ProcessWire 2.x test sites in respective ProcessWire 3.x sites. These ProcessWire 3.x sites (one for each module) were already in place and I didn't feel like re-doing them by exporting/importing site profiles. I also like working with JSON rather than other export formats. So, I decided to write a custom pages export/import script for moving the ProcessWire 2.x sites to their respective ProcessWire 3.x counterpart sites. I'd just finished the export side of things when I came across a post in the forums that reminded me that ProcessWire 3.x already boasts a pages export/import feature, although some of it is still in development. Great! I like its API (PagesExportImport.php) and GUI (ProcessPagesExportImport.module) so no need to re-invent the wheel. I still had the small problem of making sure my JSON export would be compatible with the JSON input that the ProcessWire 3.x import expects. No need to re-invent the wheel, again! I ditched my custom script and instead ported the export functionalities of ProcessWire 3.x Pages Export/Import for use in ProcessWire 2.2 - 2.7, specifically to help migrate older sites to ProcessWire 3.x. Compatibility The module and class have been tested and work in ProcessWire 2.2, 2.3, 2.4, 2,5, 2.6 and 2.7. The module is currently tagged as 'in development' until Pages Import feature of ProcessWire 3.x is released as stable. Nonetheless, I have not encountered any issues so far in either the export or the ProcessWire 3.x import. I think Ryan is waiting until he can support more complex field types before tagging the ProcessWire 3.x Pages Export/Import as production-ready. This is not a ProcessWire 3.x module and will never be. It has no such need . Just in case you forget and try to install it in a ProcessWire 3.x site, the module will throw a WireException(). I will also not be porting the ProcessWire 3.x import functionality for use in ProcessWire 2.x. That will defeat the purpose here; to move sites to ProcessWire 3.x and not the other way round. Supported Fields All non-complex fields such as integer, text, textarea, etc Page fields Repeaters File and Image fields I think these cover most needs. Note: not yet tested with Multilingual fields. Technical To ensure exports will be compatible with ProcessWire 3.x Pages Import, where necessary, the module borrows (and amends as needed) methods from ProcessWire 3.x for use in ProcessWire 2.x. For instance, ProcessWire 3.x Pages Export/Import uses the new(-ish) $file functions found in WireFileTools. Rather than copy and include such files, the module only borrowed and amended needed methods. These are listed below. PagesExport.php From /wire/core/Functions.php: wireInstanceOf(), wireClassName() and wireClassParents() From /wire/core/Fieldtype.php: getImportValueOptions() and getDatabaseSchema() From /wire/core/WireFileTools.php: zip(), chmod() and mkdir() From /wire/core/WireHttp.php: sendFile From /wire/modules/Fieldtype/FieldtypeFile.module: exportValue() and exportDescription() From /wire/modules/Fieldtype/FieldtypeImage.module: exportValue() From /wire/modules/Fieldtype/FieldtypePage.module: exportValue() and exportValuePage() From /wire/modules/Fieldtype/FieldtypeRepeater.module: exportValue() From /wire/core/Fieldtype/WireTempDir.php: create(), createName() and getTempDir() All the export methods from the /wire/core/PagesExportImport.php class ProcessPagesExport.module All the export methods from /wire/modules/process/ProcessPagesExportImport/ProcessPagesExportImport.module Newer methods such as $this->wire() will gracefully degrade to the older wire() function, ensuring smooth and uniform operation in ProcessWire 2.2 - 2.7. Use This module and class is for supersusers only and has only 1 aim; to export ProcessWire 2.x sites ready for importing into ProcessWire 3.x sites. You can either install (like any other module) and use the process module (ProcessPagesExport.module) or skip the install and just include and use the class (PagesExport.php) to export your sites. Both the module (Export GUI) and API require that you are logged in as a supersuser before you can use them. The PagesExport class has a gateway method and option not found in the original class (PagesExportImport). The method export() allows access to the three export methods in the original class, i.e. pagesToArray(), exportJSON() and exportZip(). See example usage below. GUI/Process Module On install, the module will create a new admin page Export Pages. Please note that unlike the original code, this page is created directly under /admin/ and not /admin/pages/. Click on Export Pages to start. Nothing much has changed from the original ProcessPagesExportImport. In older ProcessWire versions where InputfieldSelector was not available, the module will instead present you with a text input to specify a valid (for that version of ProcessWire!) selector for finding pages. The other two methods for adding pages (add pages manually or add by parent) are still available. Custom JS ensures older installs without showIf functionality still get the inputfield dependency treatment. API export($items, $options) PageArray $items: The PageArray to export. Array $options: In addition to the options in the original class that you can pass to pagesToArray(), there are two more options here. mode to specify the export type (array or json or zip) and fieldNamesExclude, to specify fields to exclude from the export. Note that the original class option fieldNames still works. It's for specifying the fields to include in the export. // API USAGE // get and include PagesExport class /* @note: you'll need to include the path differently if you are using the class directly without installing the Process module */ $path = $config->paths->ProcessPagesExport . 'PagesExport.php'; require_once($path); // create new instance of the class $siteExport = new PagesExport(); // find items to export /* a. export whole site! (minus admin and children) careful! in some cases, better to export in batches */ //$items = $pages->get('/')->find('has_parent!=2'); // export a batch of pages $items = $pages->find('template=basic-page|computer'); /* you could also use these methods directly #$data = $siteExport->pagesToArray($items); #$data = $siteExport->exportJSON($items); #$data = $siteExport->exportZIP($items); */ $options = array( // @kongondo addition: export to screen as 'array' or 'json' OR export to zip // zip will be saved in /site/assets/backups/PagesExport/ 'mode' => 'array',// array or json or ZIP // export only these field names, when specified 'fieldNames' => array('images', 'files', 'multi_pages'), // @kongondo addition: exclude fields from export. Here we exclude 'body' field 'fieldNamesExclude' => array('body'), ); // get the export $data = $siteExport->export($items, $options); if(is_array($data)) { echo '<pre> EXPORTED SITE USING pagesToArray '; print_r($data); echo '</pre>'; } // JSON export else echo $data; Screenshots See also the links to Ryan's blog posts above. ProcessWire 2.2 ProcessWire 2.4 ProcessWire 2.5 ProcessWire 2.7 Video Demo (Sorry, long and boring) Demo shows various exports from ProcessWire 2.x and their importing into ProcessWire 3.x. Remember the old Skyscrapers site profile? See how a whole Skyscrapers site gets exported from a ProcessWire 2.7.3 site and imported into a ProcessWire 3.x starting from here.6 points
-
Thanks @BitPoet! I think there might be some typos in the mutator example - maybe you meant: $blogposts = $pages->find("template=blog-post, sort=created"); $grouped = $blogposts->groupBy(function($pg) { return array(strftime('%Y', $pg->created), strftime('%m', $pg->created)); });4 points
-
@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.4 points
-
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. Thanks3 points
-
Definitely. Thank for pointing that out, I have ammended my post.3 points
-
@bernhard, you are of course free to put whatever you like into your modules. However, having your modules "phone home" like this discloses data that users may not want shared. At the minimum the website URL is disclosed. Not every site that exists on the internet is intended for public access. There are things like staging servers, and probably a whole range of other sensitive sites. My personal opinion is: 1. I think there should be a very clear and prominent statement so that users know that the module will send data to a third party, and what data will be sent. And I'm not sure that only a notice in the module readme would be satisfactory because users might download via the PW admin without seeing the readme. And if you start adding this to existing modules that didn't have it before it could pass by unnoticed when the module is updated. Basically there needs to be informed consent before data is sent, and probably also a privacy policy. 2. I think this crosses a line that shouldn't be crossed, and sets a dangerous precedent that if followed by others could be harmful to the PW project/ecosystem. There is a huge amount of trust placed in third-party modules by users. Modules can potentially access any website data. The very notion that a module phones home erodes trust. We don't want to create a sense of unease where every time a user contemplates installing a module they feel like they ought to comb through the source code to see what the module might be disclosing to a third party. In the bigger picture, I'd like to see PW get a reputation as being a system suitable for large institutional and government clients. Here in New Zealand, SilverStripe is in the enviable position of being seen as the go-to system for government websites. I think PW should be competing in this space. But government and institutional clients won't touch PW with a pole if there is a perception that there are privacy issues with modules in the PW ecosystem. I think the right way to track software popularity is by counting downloads, not phoning home. Sadly GitHub and GitLab don't provide useful tools for this, but that is another discussion.3 points
-
Sorry for bumping and old topic... but it seems there is still no language-module features for swapping languages with the default? I had this issue and found that dealing with PW databases was the fastest route for me: To swap language strings repeat this SQL for each multilanguage field: UPDATE field_FIELDNAME s1, field_FIELDNAME s2 SET s1.data = s1.data123, s1.data123 = s2.data WHERE s1.pages_id = s2.pages_id; FIELDNAME is your fieldname in PW and data123 is the column name for the language your are swapping the default for. Just check any multilanguage field table to see the correct column name. Finally to swap page names run this SQL: UPDATE pages s1, pages s2 SET s1.name = s1.name123, s1.name123 = s2.name WHERE s1.id = s2.id AND s1.name123 IS NOT NULL Obviously it is risky to tamper with databases, but this approach saved me lots of work with a big site with reasonably few fields. The language-module should have this sort of swappery built-in.2 points
-
Interesting discussion here I agree that the PW really needs to track module installs / uninstalls to give module authors some idea of module usage. I feel like it should be as simple as a call from the install() and uninstall() methods. These numbers should be displayed in modules directory - easy and transparent! As for the approach that @bernhard has taken. I have to admit that I thought about the same approach a little while ago, but thought it might illicit a response like @Robin S's. I am certain that in Bernhard's case it's all above board, but I guess you never know what module authors could potentially start tracking. Something to think about is that the PW core unintentionally does something vaguely similar already - take the links to Page, PageArray and NullPage (when editing a Page Reference field) as just one example. These links are to processwire.com which means that if you click on these from your site, the google analytics for processwire.com will show the domain of your site in the "Acquisition" section. This will include local dev domains, staging domains, etc, all because of http_referer. I realize that in this example you have to actively click on a link to trigger the call, but it's something to be aware of if you're really concerned about keeping a domain name private. Cheers!2 points
-
That's not actually true - it's the class name that has to match the file name.2 points
-
I'm not using twig but latte but the main advantages are sophisticated template inheritence, filters, reusable blocks/partials, etc, and after all, more readable and maintainable code. Backdraws are the small overhead and some extra complexity, of course the latter disappears when you get used to it. Markup regions is in my opinion a very simple templating implementation. This works fine if you don't need any of those extra features, and in case of template engines you can more easily find help if you are stuck. Speaking of myself, I made only my first PW project with pure PHP about 3 years ago, then all with latte.2 points
-
I am compiling this list and the contributions of @szabesz - thanks a lot. To make contributions to the documentation based on the code, it seems best to contribute to the code on https://github.com/processwire/processwire and send a pull request to @ryan To make contributions to the rest of the documentation, it seems best to send them to @ryan for example by pm. PW Documentation API Reference : Gives an overview of the API variables and detailed information about the class functionality. This is directly generated from the source code (see API Explorer module) based on a special markup code included in the PHP comments. Tutorials : As the name suggests, giving insights into a number of topics. API Cheatsheet : A comprehensive reference of ProcessWire API variables, methods and properties – all on one page. The intention is to pull this information also directly from the code using the API Explorer module (see this post - What about the cheatsheet - is this actually implemented by now?) API Documentation : Learn more about the API including the concepts behind it, how to work with template files, and how to develop modules. Also labeled as: DEVELOPING A SITE IN PROCESSWIRE. Covers additionally topics like Concept, Template Files, Selectors, Include&Bootstrap, Syntax, Hooks, Plugin Modules, Users&Access, Arrays, Fieldtypes, Multi-Language Support and Coding Style Guide. Directory Structure : This section outlines ProcessWire’s default directory structure, highlighting the locations where your site’s files go. Template Files : Every time a page is loaded on your site, ProcessWire looks at what template is assigned to the page, loads it, gives it several API variables, and then runs it as a PHP script. This section covers the use of template files and serves as an introduction to using ProcessWire’s API with examples. This can be accessed also by way of "API Documentation" Selectors : A selector allows you to specify fields for finding a page or group of pages that match your criteria. Selectors in ProcessWire are loosely based around the idea and syntax of attribute selectors in jQuery. This can be accessed also by way of "API Documentation" Multi-Language Support : ProcessWire makes multi-language support easy with multi-language fields, URLs (page names) and code internationalization. Security : Security is our number one priority with ProcessWire. Make it your number one priority too. In this section we attempt to cover some of the more important aspects in maintaining a secure installation. Install & Upgrade : Covers installation of ProcessWire, troubleshooting and upgrades. More on how different ways how documentation is generated or updated in these posts: https://processwire.com/blog/posts/processwire-3.x-api-reference/ https://processwire.com/blog/posts/processwire-3.0.16-continues-expanding-documentation-and-more/ https://processwire.com/blog/posts/processwire-3.0.41-and-a-look-at-api-explorer/2 points
-
NOTE: This thread originally started in the Pub section of the forum. Since we moved it into the Plugin/Modules section I edited this post to meet the guidelines but also left the original content so that the replies can make sense. ProcessGraphQL ProcessGraphQL seamlessly integrates to your ProcessWire web app and allows you to serve the GraphQL api of your existing content. You don't need to apply changes to your content or it's structure. Just choose what you want to serve via GraphQL and your API is ready. Warning: The module supports PHP version >= 5.5 and ProcessWire version >= 3. Links: Zip Download Github Repo ScreenCast PW modules Page Please refer to the Readme to learn more about how to use the module. Original post starts here... Hi Everyone! I became very interested in this GraphQL thing lately and decided to learn a bit about it. And what is the better way of learning a new thing than making a ProcessWire module out of it! For those who are wondering what GraphQL is, in short, it is an alternative to REST. I couldn't find the thread but I remember that Ryan was not very happy with the REST and did not see much value in it. He offered his own AJAX API instead, but it doesn't seem to be supported much by him, and was never published to official modules directory. While ProcessWire's API is already amazing and allows you to quickly serve your content in any format with less than ten lines of code, I think it might be convenient to install a module and have JSON access to all of your content instantly. Especially this could be useful for developers that use ProcessWire as a framework instead of CMS. GraphQL is much more flexible than REST. In fact you can build queries in GraphQL with the same patterns you do with ProcessWire API. Ok, Ok. Enough talk. Here is what the module does after just installing it into skyscrapers profile. It supports filtering via ProcessWire selectors and complex fields like FieldtypeImage or FieldtypePage. See more demo here The module is ready to be used, but there are lots of things could be added to it. Like supporting any type of fields via third party modules, authentication, permissions on field level, optimization and so on. I would love to continue to develop it further if I would only know that there is an interest in it. It would be great to hear some feedback from you. I did not open a thread in modules section of the forum because I wanted to be sure there is interest in it first. You can install and learn about it more from it's repository. It should work with PHP >=5.5 and ProcessWire 3.x.x. The support for 2.x.x version is not planned yet. Please open an issue if you find bugs or you want some features added in issue tracker. Or you can share your experience with the module here in this thread.1 point
-
TextformatterTypographer A ProcessWire wrapper for the awesome PHP Typography class, originally authored by KINGdesk LLC and enhanced by Peter Putzer in wp-Typography. Like Smartypants, it supercharges text fields with enhanced typography and typesetting, such as smart quotations, hyphenation in 59 languages, ellipses, copyright-, trade-, and service-marks, math symbols, and more. It's based on the PHP-Typography library found over at wp-Typography, which is more frequently updated and feature rich that its original by KINGdesk LLC. The module itself is fully configurable. I haven't done extensive testing, but there is nothing complex about this, and so I only envisage a typographical bug here and there, if any.1 point
-
If you're looking to do an elaborate a page builder with ProcessWire and don't want to pull your hair out, I highly recommended viewing this video: A couple notes: with the css grid specification, you can assign multiple blocks to the same grid-area but they will overlap each other. I've "overcome" this by combining multiple blocks into a parent div and assigning that instead. pretty easy to do. i didn't demonstrate it, if your blocks have a grid structure within them (like built with flexbox), you can still assign that block to a grid-area. so if your blocks themselves have a grid structure, that's ok. for example, if your css grid layout is 6 columns, but you have a block that has a grid inside of it (built with like uikit's grid that's 5 columns), you can assign that block to the grid-area. with the css grid specification, the flow of the blocks does not have to match the flow of the grid-areas. this is insanely powerful. Enjoy.1 point
-
@gmclelland I noticed the same problem on a project of mine. I tried to fix this with the latest release of the TemplateEngineFactory module (1.1.2). If you throw a Wire404Exception() the module now should set the correct 404 page for twig. So the redirect should not be necessary now.1 point
-
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
-
The original roadmap has been closed, in favour of a project board. As already mentioned, I'll be resuming work on Jumplinks in the next few weeks. (Super sorry it's taken so long...)1 point
-
I'd like to get some general feedback from people using the module. If you have a minute, please share your experiences or issues (even those that are self-resolved, if any). If it's looking stable enough, I'd like to bump to stable v1. If there are issues, please also feel free to let me know. Thanks!1 point
-
Hi @zeka, thank you, So sorry but not work. I added it this way: <?php namespace ProcessWire; class CustomPrices extends WireData implements Module { public static function getModuleInfo() { return array( 'title' => 'Custom Prices', 'version' => 1, 'summary' => 'Example module for modifying prices in Padloper.', 'singular' => true, 'autoload' => true, 'icon' => 'money', ); } But I Found the error! The name of 'title' => 'Custom Prices' need be the exactly the same of the file name in this case: CustomPrices.module Sorry I didn't know this. Now the module is installed. I correct my answer: The name of class CustomPrices need be the exactly the same of the file name in this case: CustomPrices.module1 point
-
I just looked at Latte and it's very clean to read, it looks so similar to Twig, I was using Smarty before but Twig is 100% OOP and extending it is cleaner compared to Smarty, I will give Natte Latte a shot later on.1 point
-
Just cross-referencing: I've ported the export functionality into a module and class for exporting ProcessWire 2.x sites in a format compatible with importing into ProcessWire 3.x using PagesExport/Import.1 point
-
I think its better if you started a new thread under general support. Please also add more details about the error. I can't see the error itself in your screenshot above, just a pointer to where it occurs. Thanks.1 point
-
Thanks szabesz, I support your request and liked + commented it on github1 point
-
Yep, lots of us have been waiting for something like this for quite a long time: So this "changelog" feature could be boosted with these new ideas of yours, too. (like "user could check a box to agree to data submission", etc...)1 point
-
updated and I can confirm the issue is resolved thanks. On a side note, I keep getting mixed up with the save buttons all over the place in the modal and the pages. Clicking save twice will take some getting used to.1 point
-
Hi @neosin - thanks for reporting - looks like it was a multi-language site bug that crept in at some point. Can you please try the latest version and let me know if everything looks ok now? Thanks!1 point
-
I found a definite bug (latest module+pw 3.0.96) If you go to a page and click children, choose batch edit, choose "edit" mode (only one i tested) as the option and then in the list; edit some children in the modal and save those - the children titles in the parent will have no value set for "title" and in the batch list it now shows empty values where the titles used to be. It appears that the variable for title is not saving or updating properly. I was editing other fields but for some reason it made my titles blank when I didn't edit the titles... very odd. also the title in the actual children themselves is now empty (in English, French is fine)1 point
-
More on this: https://processwire.com/blog/posts/processwire-3.x-api-reference/ https://processwire.com/blog/posts/processwire-3.0.16-continues-expanding-documentation-and-more/ https://processwire.com/blog/posts/processwire-3.0.41-and-a-look-at-api-explorer/ Write one then send a pm to Ryan Probably, see: https://processwire.com/blog/posts/processwire-3.0.60-core-updates-and-more/ "...With the API reference now up-to-date, we'll soon be looking at the cheatsheet and hopefully updating that to pull data from the API Explorer module as well." Also search for the word cheatsheet in these: https://processwire.com/blog/posts/processwire-3.x-api-reference/ https://processwire.com/blog/posts/growing-processwire-in-2016/#cheatsheet-and-documentation Same as the tutorials Yes, there is. It's too late here for me to compile a good list but I will try to do it in the following days if others do not provide something faster than me...1 point
-
Thanks for looking into this - right now I am going to have to either build up the JSON, or wait for you to implement an API method. I think the API method is essential because I am sure others will also need to import dates from existing sites. I'd appreciate knowing whether you might have time to implement this relatively quickly, or if you think it might take some time to get to - not pressuring, just needing to know if I need to take the JSON approach to meet my deadlines. Thanks for your time!1 point
-
I don’t have a method to add dates via the api, but that is a good idea. If you do add dates by populating the json manually, I could see some complications arising depending on how you have chosen the repeat pattern in the Recurme Field. Because the dates are generated based on the rrule via JavaScript. I will give this some thought and see how we could make this a no-brainer. Thanks for the great idea.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
-
Hey @joshuag - not sure if I am just missing it, but is there a method for adding new dates via the API? I have a LOT of events to import. Or, do I have to manually build up the JSON eg: {"startDate":1518778800,"endDate":1518940740,"allDay":false,"timeEnd":"12:00 pm","dates":["W7/16/Fri/Feb/2018","W7/17/Sat/Feb/2018"],"excluded":[],"active":true,"showResults":true,"rrule":"WKST=MO;FREQ=DAILY;DTSTART=20180216T030000Z;INTERVAL=1;UNTIL=20180217T235900Z"} and populate with $page->event->date = $jsonStr; Thanks!1 point
-
Here is the answer I've been searching for <?php foreach ($page->files as $file) { echo "<a href='{$file->url}' download>download {$file->name}</a><br>"; } ?> Thank you for the help1 point
-
I am doing something very similar to this and am just using pages. I use two templates: 'newsletters_parent' and 'newsletters_child'. The 'newsletters_parent' is the container and onyl has a title, but its settings on the "Family" tab are: Can have children: YES Can be used for new pages: ONE Allowed template(s) for children: newsletter_child Name format for children: Y/m/d H:i:s (the page creation date/time -- this will get overwritten at a later stage) Sort settings for children: pub_date (a field that will be added to the newsletter_child page) Reverse sort direction: CHECKED (yes) The 'newsletters_child' template has the following fields: title (default and required by all templates) pub_date (a datepicker field set to YYYY-MM-DD -- the publication date might not necessarily be the same as the date the file was uploaded!) pdf_file (file-upload field, single file only, and also set to only allow PDF files) The settings ("Family" tab) for the child template are: Can have children: NO Can be used for new pages: YES Allowed template(s) for parents: newsletter_parent Show in the add-page shortcut menu: YES To make the process of adding a new newsletter as easy as possible for users -- all they have to do is select the file to upload and the pick the pub date -- I use a couple of hooks hook in the site's ready.php file: The first hook adds dummy text to the page title field on page creation so that the user doesn't have to enter anything (if it is left blank, it will throw an error) -- the proper title, along with the page name will be created from the pub_date field when the page is saved. The second hook also creates the actual page title and the sets page name based on the supplied 'pub_date'. [NOTE: the newsletters only get published once a month, so we only use the Year/Month in page names/titles.] /** * 1. Set a dummy title on page save at creation, to prevent the required field showing an error if it is blank. * (Requires the parent template option 'Name format for children' to be set) * 2. Modify newsletter title and page name from pub_date before saving. */ $pages->addHookAfter('saveReady', function(HookEvent $event) { $page = $event->arguments(0); // Only on newsletter_child templates where the title is blank (on page creation) if ($page->template->name == 'newsletter_child' && $page->title == '') { $page->title = 'Newsletter'; // a dummy title, will be replaced at a later stage } // Only on newsletter_child templates with a pub_date if ($page->template->name == 'newsletter_child' && $page->pub_date) { $newName = wire('sanitizer')->pageName('newsletter-' . date('Y-M', $page->pub_date), true); $newTitle = date('F Y', $page->pub_date); if ($page->title != $newTitle) { $page->title = $newTitle; } if ($page->name != $newName) { $page->name = $newName; } } }); Thiis then makes it easy to search the system for whatever you want: // All newsletter files (no limit applied) $allNews = $pages->find('template=newsletter_child, sort=pub_date'); // ascending order (earliest first) $allNews = $pages->find('template=newsletter_child, sort=-pub_date'); // descending order (most recent first) // All newsletters from a particular year (e.g., 2017): $yearStart = strtotime('2017-01-01 00:00:00'); $yearEnd = strtotime('2018-01-01 00:00:00'); $yearNews = $pages->find("template=newsletter_child, pub_date>={$yearStart}, pub_date<{$yearEnd}, sort=-pub_date"); // The most recent newsletter: $mostRecent = $pages->findOne('template=newsletter_child, sort=-pub_date'); // Accessing the file itself (using $mostRecent) if ($mostRecent->pdf_file) { $file = $mostRecent->pdf_file; $downloadLink = "<a href='{$file->url}'>Download the newsletter: {$mostRecent->title}</a> ({$file->filesizeStr})"; } That's all there is, really. There is lots you can do with the system.1 point
-
Wow o_O @dadish I am humbled by your excellent work here. I stumbled on this thread researching PWAs and then was transfixed for the full 37 minutes of your excellent video intro. Thank you very much indeed for all of this, I am yet again thankful for the ProcessWire community's generous and excellent work. I've had Vue.js noted down as "something I want to use", along with using ProcessWire as/via an API, and most recently, PWAs. I am hoping, if I've not misunderstood, now I may be able to use all these together for a project. Brilliant! \o/1 point
-
1 point
-
@adrian I know Jumplinks. I use it, I like it and I recommend it. Somebody else using my Process404Logger module had problems interacting with ProcessRedirects. To test and fix this I needed to fix the bugs of ProcessRedirects first.1 point
-
Hey @kixe - curious if there is a reason you chose to fix this rather than switch to jumplinks? http://modules.processwire.com/modules/process-jumplinks/ Is there something you prefer about this module, or just that you didn't know about the new one?1 point
-
Actually, what do you guys @netcarver @FlorianA @bernhard think about putting 00:00:00 in the placeholder attribute - that way editors know what format to enter, but the value is still blank.1 point
-
Can't we use upgrade() function inside modules to achieve this? It's not as eloquent as having the changelog available before downloading new version, but developers can use it to display certain info, and request confirmation during/abort if possible. Something like this: <?php namespace ProcessWire; class MyModule extends Module { // ... // (module implementation) // ... public function upgrade($from, $to) { // TODO: remove after v2.0 // upgrading to v2.0, show warning about breaking changes if(strpos($from, '1.') === 0 && strpos($to, '2.') === 0) { $this->warning("New version includes <strong>breaking</strong> changes. See module info page for details.", Notice::allowMarkup); // create some info page, display compiled output of README.md // not sure how to display info on Process pages if (!$accepted) { throw new WireException('Cannot install without confirmation'); } } return $to; } }1 point
-
You are completely right. I can't argue that "enabled-by-default" approach can lead to lots of security issues. That's why I am limiting the exposable pages only to selected templates. While the selector option is quite simple to implement I don't want to enable this kind of option because I believe it should not be this module's concern. The way I see it, if this module stays consistent and retrieves data only through $pages->find() api (or it's equivalent like $page->children(), $page->siblings() etc) that should give the user any type of control with the security. For example what you suggest could be achieved with a single hook. Say this is your template file where you expose your GraphQL api (something like /site/templates/graphql.php). <?php echo $modules->get('ProcessGraphQL')->executeGraphQL(); What you suggest could be achieved like this. <?php wire()->addHookAfter('Pages::find', function($event) { $event->return = $event->return->filter($mySecuritySelector); }); echo $modules->get('ProcessGraphQL')->executeGraphQL(); I would prefer users to approach security this way. This strategy to security gives full control for the user while allowing me to stick to a single rule when concerned about security and makes the code of the module much easier to reason about. I do realize that I could just insert the above code in the module and that's basically an implementation of what you suggest. But I don't want to encourage the user to solve security problems via module settings because no matter how hard I try, I won't be able to make this module dummy proof without limiting it's capabilities. Another thing I wanted to mention is that I see this module as a GraphQL representation of ProcessWire api. Like @Ivan Gretsky mentioned, if done right, this could allow us to build lot's of useful developer tools on top of this module. Even a mobile app that gives you limited site administration capabilities. But only if module is consistent with how ProcessWire behaves. And that includes the security of course. Oh no sir, not at all. I value your opinion very much. That's exactly what I wanted to hear from the community, opinions. I am thankful to you for mentioning this aspect of the module in it's early stage, before I started to implement other features that depend on it, like authentication or others that I might not think of right now.1 point
-
In light of the above, I've been working on a port of Typset from JS to PHP: https://github.com/mikerockett/php-typeset It doesn't include hyphenation, but does add features not found in the original JS version. It's currently in alpha, and I'll make a Textformatter for it when it's stable. I haven't done benchmarking between Typography and Typeset, but I can already feel that Typeset is faster. Sensible (common) defaults are set. If you're keen to have a look, please let me know what you think. ?1 point
-
I've been calling it a framework instead of a CMS. I find that's a great starting point to explain how much different it is from a CMS such as WordPress, how it makes zero assumptions and allows you to build whatever you want without having to hack the S#!7 out of it or depend on a bunch of plugins. And so far so good. Clients have been trusting my arguments and accepting PW. Agencies... not so much. They're like christians, and accept only the holy word of WordPress (or whatever they use). They also laugh at me when I say I don't use Bootstrap or CSS preprocessors. Apparently it's a flaw to know CSS enough to not need a crutch to make a responsive column based layout. I'm drifting. Sorry.1 point
-
When a client asks for "a website" with "easy management", explaining that ProcessWire is "an awesome framework for building just about anything" feels plain wrong and is unlikely to yield desired results. On the other hand, when client (or fellow developer) does mention the need for "an application", "framework" or "software platform", it's good to be able to match those needs too. Personally I really like the fact that ProcessWire has the ability to be both and would like to avoid emphasizing either *too much* over the other. If I needed a CMS, there would be a huge amount of other options available, and if I simply wanted to build an application I could (for an example) turn to Zend Framework and get the job done.. and so on. What ProcessWire brings into this equation is a well-balanced combination of features from both worlds. Each part supports the other, eventually making it more than the sum of it's parts. I especially love how easy and fast built-in admin makes mocking up data structures -- in fact it's such a smooth experience that the times I've built mock data manually for HTML wireframes etc. have been almost nonexistent lately. I find it faster to make fully functional features using ProcessWire than writing dummy directly with editor. And that, in my opinion, is why it's so damn good. Not that I really needed to explain it to anyone here, but my point is mostly that I don't see ProcessWire simply as a CMF or CMS or application framework or whatever. It's all of these combined.. and that's a good thing. I hope we don't get stuck discussing which one it is when that question can't (and shouldn't) have one single correct answer1 point
-
My thoughts about pw: If you just started with pw, maybe you created your First Page and saw how pw Works and handles data you think uh yummi nice CMS. Well thats fine and correct,pw acts, feels and looks like a CMS. You then dig deeper into the API, you understand how the different fieldtypes are working and you get a good overview. You probably think at this point: could I use pw like a Database? You Set your first page relation, count the arrays and you Play with the Data and you think: WTF!? Is this still a CMS? The longer you use pw, the deeper understanding of the API, you finally realise which great,strong and powerfull toolset Ryan just handed right into your hands. At this particullar point the term CMS would not come to your mind anymore. PW could be used like phpMyAdmin, just create tables,rows and data and work with them, but a Way more easier without even knowig what MySQL really is. Some of you might know my Intranet Application i'm working on, this is all made with pw and some little template php scripting. The next days I will Show a Little preview of an App i'm also working on, and yes everything PW driven.1 point
-
I feel like for the first two lines this is at least a little more logical/readable $ancestor = $page->children->first(); $find = $ancestor->find("template=subcategory"); From there I'm a bit confused as to the exact goal so I would just be guessing.1 point
-
I'm not sure I got your question right, but that doesn't seem very unefficient to me. The first two lines could be replaced by this though: $find = $pages->find("has_parent=1134, template=subcategory"); So no need to get the ancestor first but go straight to find any page it's parent or grandparent of. Maybe this was what you were after?1 point
-
Couldn't the $page->parents() be used to do this? $found = $page->parents->find("template=overview"); $theone = $page->parents->find("template=overview")->first();1 point