Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 05/01/2017 in all areas

  1. Hey guys, I've recently started a blog on web development and ProcessWire, and I'm slowly building content to make it a habit and not let it become a failed experiment. I also believe that a good way to keep producing new content is to build an audience that cares about what you write and what you offer. So, I've started writing a series on module development, from basic to complex, where I explore core modules and unearth the valuable documentation inside PW core and put them into practice. I'll be stripping core modules to their essentials like blueprints that people can build upon, and construct various modules that replicate popular ones on other platforms, or ones that bring some of their eye-caching features. I haven't implemented a commenting system on my blog, because I want to share and collect feedback and recommendations here, in this forum. Is there an etiquette on promoting my own content? Is it welcome, or how much is OK, or how much is too much? Thanks in advance, Abdus.
    10 points
  2. I can't speak 'officially' toward ProcessWire rules regarding promoting personal sites. However, I do think that your ideas, examples, prototypes if you will, are very welcome by me and other newbies. I found your ajax order tutorial very beneficial and I would welcome many others you may offer. I even have a few suggestions for topics if you are interested. I don't see anything wrong with putting a brief description (or outline) of each of your articles in the tutorial forum and include a link to the full article on your site. On a side note, having cross referenced links is good seo for you and ProcessWire both.
    5 points
  3. Without a backslash class names resolve to current namespace (ProcessWire), and since DateTime class is in global namespace, you should put \ before DateTime. $today = new \DateTime(); Also, I've realized that I am getting the same error, but $pages->find() is still returning a result (because when DateTime object is converted to string it turns into an empty string '', and the selector becomes "published>'' ", which returns all pages). You can fix the error using: $today = new \DateTime(); // use UNIX timestamps $aFewDaysAgo = $today->modify('-2 days')->getTimestamp(); $lastYear = $today->modify('-1 year')->getTimestamp(); // or convert to ISO format // $aFewDaysAgo = $today->modify('-2 days')->format('c'); // $lastYear = $today->modify('-1 year')->format('c'); $latestPages = $pages->find("published>=$aFewDaysAgo"); $pagesSinceLastYear = $pages->find("published>=$lastYear");
    5 points
  4. After this tutorial you'll have learned how to: Build a Process module Make an AJAX request to backend Serve JSON as response Let's say you want to display the latest orders in a dashboard that you can access from admin panel. And you want it to refresh its content with a button click. Most straightforward and proper way (that I know of) is to create a Process module, as they're built for this purpose. First, create a directory under /site/modules/, call it ProcessDashboard, and create a file named ProcessDashboard.module under that directory. Following is about the least amount of code you need to create a Process module. <?php namespace ProcessWire; class ProcessDashboard extends Process { public static function getModuleInfo() { return [ 'title' => 'Orders Dashboard', 'summary' => 'Shows latest orders', 'version' => '0.0.1', 'author' => 'abdus', 'autoload' => true, // to automatically create process page 'page' => [ 'name' => 'order-dashboard', 'title' => 'Orders', 'template' => 'admin' ] ]; } public function ___execute() { return 'hello'; } } Once you refresh module cache from Modules > Refresh, you'll see your module. Install it. It will create an admin page under admin (/processwire/) and will show up as a new item in top menu, and when you click on it, it will show the markup we've built in execute() function. All right, now let's make it do something useful. Let's add create a data list to display latest orders. We'll change execute() function to render a data table. public function ___execute() { /* @var $table MarkupAdminDataTable */ $table = $this->modules->MarkupAdminDataTable; $table->setID($this->className . 'Table'); // "#ProcessDashboardTable" $table->headerRow([ 'Product', 'Date', 'Total' ]); // fill the table foreach ($this->getLatest(10) as $order) { $table->row([ $order['title'], $order['date'], $order['total'] ]); } // to refresh items $refreshButton = $this->modules->InputfieldSubmit; $refreshButton->name = 'refresh'; $refreshButton->id = $this->className . 'Refresh'; // "#ProcessDashboardRefresh" $refreshButton->value = 'Refresh'; // label of the button return $table->render() . $refreshButton->render(); } where getLatest() function finds and returns the latest orders (with only title, date and total fields) protected function getLatest($limit = 5, $start = 0) { // find last $limit orders, starting from $start $orders = $this->pages->find("template=order, sort=-created, limit=$limit, start=$start"); // Only return what's necessary return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total ]; }); } When you refresh the page, you should see a table like this Now we'll make that Refresh button work. When the button is clicked, it will make an AJAX request to ./latest endpoint, which will return a JSON of latest orders. We need some JS to make AJAX request and render new values. Create a JS file ./assets/dashboard.js inside the module directory. window.addEventListener('DOMContentLoaded', function () { let refresh = document.querySelector('#ProcessDashboardRefresh'); let table = document.querySelector('#ProcessDashboardTable'); refresh.addEventListener('click', function (e) { // https://developer.mozilla.org/en/docs/Web/API/Event/preventDefault e.preventDefault(); // Send a GET request to ./latest // http://api.jquery.com/jquery.getjson/ $.getJSON('./latest', { limit: 10 }, function (data) { // check if data is how we want it // if (data.length) {} etc // it's good to go, update the table updateTable(data); }); }); function renderRow(row) { return `<tr> <td>${row.title}</td> <td>${row.date}</td> <td>${row.total}</td> </tr>`; } function updateTable(rows) { table.tBodies[0].innerHTML = rows.map(renderRow).join(''); } }); And we'll add this to list of JS that runs on backend inside init() function public function init() { $scriptUrl = $this->urls->$this . 'assets/dashboard.js'; $this->config->scripts->add($scriptUrl); } Requests to ./latest will be handled by ___executeLatest() function inside the module, just creating the function is enough, PW will do the routing. Here you should notice how we're getting query parameters that are sent with the request. // handles ./latest endpoint public function ___executeLatest() { // get limit from request, if not provided, default to 10 $limit = $this->sanitizer->int($this->input->get->limit) ?? 10; return json_encode($this->getRandom($limit)); } Here getRandom() returns random orders to make it look like there's new orders coming in. protected function getRandom($limit = 5) { $orders = $this->pages->find("template=order, sort=random, limit=$limit"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total ]; }); } And we're done. When refresh button is clicked, the table is refreshed with new data. Here it is in action: 2017-04-29_19-01-40.mp4 (227KB MP4, 0m4sec) Here's the source code: https://gist.github.com/abdusco/2bb649cd2fc181734a132b0e660f64a2 [Enhancement] Converting page titles to edit links If we checkout the source of MarkupAdminDataTable module, we can see we actually have several options on how columns are built. /** * Add a row to the table * * @param array $a Array of columns that will each be a `<td>`, where each element may be one of the following: * - `string`: converts to `<td>string</td>` * - `array('label' => 'url')`: converts to `<td><a href='url'>label</a></td>` * - `array('label', 'class')`: converts to `<td class='class'>label</td>` * @param array $options Optionally specify any one of the following: * - separator (bool): specify true to show a stronger visual separator above the column * - class (string): specify one or more class names to apply to the `<tr>` * - attrs (array): array of attr => value for attributes to add to the `<tr>` * @return $this * */ public function row(array $a, array $options = array()) {} This means, we can convert a column to link or add CSS classes to it. // (ProcessDashboard.module, inside ___execute() method) // fill the table foreach ($this->getLatest(10) as $order) { $table->row([ $order['title'] => $order['editUrl'], // associative -> becomes link $order['date'], // simple -> becomes text [$order['total'], 'some-class'] // array -> class is added ]); } Now, we need to get page edit urls. By changing getLatest() and getRandom() methods to return edit links in addition to previous fields protected function getLatest($limit = 5, $start = 0) { // find last $limit orders, starting from $offset $orders = $this->pages->find("template=order, sort=-created, limit=$limit, start=$start"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total, 'editUrl' => $order->editUrl ]; }); } protected function getRandom($limit = 5) { $orders = $this->pages->find("template=order, sort=random, limit=$limit"); return $orders->explode(function ($order) { return [ 'title' => $order->title, 'date' => date('Y-m-d h:i:s', $order->created), 'total' => $order->total, 'editUrl' => $order->editUrl ]; }); } and tweaking JS file to render first column as links function renderRow(row) { return `<tr> <td><a href="${row.editUrl}">${row.title}</a></td> <td>${row.date}</td> <td>${row.total}</td> </tr>`; } we get a much more practical dashboard.
    4 points
  5. Hey @abdus, very nice and considerate of you to ask. This would be a great addition to the tutorials section. I think a blogpost have more styling options than a long forum post.
    4 points
  6. You shouldn't put $page-> in there, just use the name of the field. And your sort field must be an existing field. $items = $pages->find("template=recipes-inner, published>=$start, published<=$end, sort=-published, limit=9"); Also you don't have to manually build the boundaries of today, you can just use today $items = $pages->find("template=recipes-inner, published>=today, sort=-published, limit=9"); In fact, you can use any valid PHP date that you can use with DateTime constructor. http://php.net/manual/en/class.datetime.php published>=today published>=1365436783 published>=2013-04-08 published>=4/8/2013 published>=8.4.2013 published>="April 8, 2013"
    4 points
  7. @Slav, another option is to set a redirect URL in the "Access" tab of the template that has restricted access. So if I user attempts to access a page with that template when they are not logged in (for any reason - either they logged out or their session expired) they are redirected to a page of your choosing.
    4 points
  8. Hi, Welcome to the forums. Sorry to hear you are having troubles saving pages. Can you post a screenshot of the template configuration (Tab: Basics) of the page you are trying to save? Also which version of PW, PHP and MySQL are you using?
    3 points
  9. v1.4.2 is up: new submodule: RestrictTreeDropdown (merging module of Robin S) new submodule: PageListIcons added "Force showing system templates" tweak to Misc (snippets and suggestion by Robin S) word-wrap issue fix for non-truncated file field names (suggested by Robin S) fix for templateEditLink on Page template selector if changing page template is disabled (see #40) fix for PageListThumbs not appearing if tree was initially collapsed I know that a few issues mentioned in this topic is remained, I'll check them later.
    3 points
  10. You can use DateTime class. http://php.net/manual/en/class.datetime.php $today = new \DateTime(); $aFewDaysAgo = $today->modify('-2 days'); $lastYear = $today->modify('-1 year'); $latestPages = $pages->find("published>=$aFewDaysAgo"); $pagesSinceLastYear = $pages->find("published>=$lastYear");
    3 points
  11. @szabesz, sreeb is concatenating not interpolating so either single or double quotes will work. @sreeb, you are using single quotes around the background URL path inside the single quotes of the style attribute: echo "<a href='".$block->url."' style='background: url('".$block->front_block_img->url."') no-repeat bottom center; background-size: cover;'>"; That effectively ends the style attribute prematurely. Either use escaped quotes around the background URL path... echo "<a href='".$block->url."' style='background: url(\"".$block->front_block_img->url."\") no-repeat bottom center; background-size: cover;'>"; ...or just leave out the quotes around the background URL - you only need those if there is a space in the path and that will never happen for a PW Pageimage as files are renamed on upload. echo "<a href='".$block->url."' style='background: url(".$block->front_block_img->url.") no-repeat bottom center; background-size: cover;'>";
    2 points
  12. There's no need. I'll provide an option to remove local copy right away on successful upload, and a maintenance function to remove mirrored files from local drive as well. The module doesn't even need to be a custom fieldtype, it hooks into built-in modules to get file paths then upload to cloud.
    2 points
  13. Yes, that must be a given. I'll be sure to create a detailed summary of the writing with a clear list of outcomes to ensure a good transition from forum->blog->forum. I'd like hear those . In fact, in the future if this initiative gets popular, I'll probably create a post where people can make requests. That's a really good bonus that I didnt think about.
    2 points
  14. Thanks @tpr&@Robin S Co. !
    2 points
  15. I have a PHP file that bootstraps PW and I'm calling it from a cron job. When the cron fires the script does run but I also get the following error notice: [01-May-2017 13:07:01 Pacific/Auckland] PHP Notice: Undefined index: argc in /home/myaccount/public_html/wire/core/ProcessWire.php on line 260 I've seen this reply to a similar problem... ...but I'm not sure that applies in my case. My site is on a shared host and I get the error when I call my cron job like this... php -q /home/myaccount/public_html/tm_import.php ...which is the recommended format according to my host's documentation. I tried this... /usr/bin/php -q /home/myaccount/public_html/tm_import.php ...and I get the same notice. Whereas based on @BitPoet's suggestion when I try... /usr/bin/php-cli -q /home/myaccount/public_html/tm_import.php ...my script doesn't run. Incidentally, line 260 of ProcessWire.php checks if php_sapi_name() == 'cli' and in my case php_sapi_name() is 'cgi-fcgi'. So I'm wondering if PW should be checking for some other variants of php_sapi_name() and I should file a GitHub issue for this, or if I'm just doing something wrong in my cron job. PHP 7.0, PW 3.0.55
    2 points
  16. I would say yes since php_sapi_name() will return 'cgi' or 'cgi-fcgi' even if cgi is called from the command line. Some other remarks: The content of $_SERVER differs depending on the call of php-cgi (server or command line) If register_argc_argv is disabled (php.ini) $_SERVER[argc] is not set. Could be checked with ini_get('register_argc_arg'); As far I can see the setting of $config->cli has not an effect inside PW.
    2 points
  17. Not 100% sure on this, but I think it will be because of the multi-instance support introduced in PW3. The example you gave from CommentFilterAkismet.module... $inputfields = $this->wire(new InputfieldWrapper()); ...if you check the PW2.x version of this file, before multi-instance support was added, it is simply... $inputfields = new InputfieldWrapper();
    2 points
  18. Metadata Exif Version 0.9.0 (currently a WIP) for PW 2.8.+ / 3.0+ This is a new attempt to add easy EXIF reading support to ProcessWire. It should be an easy and less error prone API task. Thats why the module internally uses lowercase key names for all EXIF keys and all of your requested key names. But you are not forced to use lowercase key names, you can use any mixed chars for it as you like. You will get back an array or object with keynames / properties named the same (casesensitive) way as you have passed them in with your request. API The module adds one hook to the Pageimage: getExif($options = null) Without any $options passed to the method, it returns all EXIF data in a RAW multidim array. Passing a simple list with requested key names as array to it returns an array with only the values of those requested keynames. If you want change the output from array to object, you can use the a boolean "toObject" set to true. Additionally to this and / or any other option, you need to put your requested keynames list to the option "keys". $rawArray = $image->getExif(); $options = array('Whitebalance', 'Flash', 'ISOSpeedRatings', 'FNumber', 'UserComment'); $array = $image->getExif($options); $options = array('toObject' => true, 'keys' => array('whitebalance', 'flash', 'isospeedratings', 'fnumber', 'usercomment')); $object = $image->getExif($options); Possible options are A working example $options = array( 'keys' => array('ISOSpeedRatings', 'FNumber', 'Flash') ); echo "<table><tr>"; foreach($options['keys'] as $key) echo "<th>{$key}</th>"; echo "</tr>"; foreach($page->images as $image) { $exif = $image->getExif($options); echo "<tr>"; foreach($exif as $value) echo "<td>$value</td>"; echo "</tr>"; } echo "</table>"; This will output something like: With the boolean option "unformatted" passed as true, the output would look like: For some keys, that only store something like integer values, what are not very meaningful to most of us, the module contain xyzFormatted methods. This way, it is easy to enhance and maintain the module in the future. Help wanted It would be nice if some of you test the module and report back if the intended API suites your needs. And you are very welcome to post suggestions for which keys you want to have a ...Formatted method. Thank you! https://github.com/horst-n/MetadataExif
    1 point
  19. This site was one of my first PW attempts a couple of years ago. I recently upgraded it to PW 3.0.60 and changed it to delayed output rather than header/body/footer format. URL: https://rockpoolpainting.com.au/ PW: v3.0.60 Modules: Admin Help Page Admin Help Setup Admin Help Tab Admin Save Actions Database Backups Email Obfuscator File (for FormBuilder) Form Builder Forms Jquery jQuery DataTables Plugin Json-LD Schema Markup Simple Navigation Markup Sitemap XML Mobile Detect Modules Manager Modules Manager Notification Page Delete ProCache ProFields: Table Template Editor Templates Reference (multiple) Features: To keep the home page content fresh, the list of services (repeater field) and 'Examples of our work' (Gallery page children, limit = 3) are randomly shuffled. Display changes subject to ProCache refresh. Testimonials are also randomly shuffled to keep content fresh Submission of the testimonial form adds the form data to an unpublished item in a pagetable on the testimonials admin page so the admin doesn't have to go to Setup->Forms->testimonial->Entries to view/edit/approve/delete the new submission (thanks netcarver ) Framework is Bootstrap although didn't use Bootstrap profile All pages validate on https://validator.w3.org/ Considering the number of images and the fact it's on a shared host, page speeds are acceptable on https://developers.google.com/speed/pagespeed/insights/ Photos of Tuggerah are my kitchen that Chris painted
    1 point
  20. I'm building a module using Comments field as reference. Inside Comments module and many others, there's always this expression which is used to inject dependencies (according to Wire class documentation) $fieldset = $this->wire(new InputfieldWrapper()); Is there a reason for injecting API variables inside objects? Because everything seems to work fine without.
    1 point
  21. Ryan just got back to my PM and this should now be fixed.
    1 point
  22. @arjen Thanks for the reply. I figured out the issue. I added a custom fieldset to basic-page template and that was causing the issue. Just removed it and now its working fine now.
    1 point
  23. Could this work? Seems like a good starting point: http://modules.processwire.com/modules/amazon-s3-cloudfront/
    1 point
  24. Hey @joer80, I've been developing a module that mirrors every (or ones you specify) upload to S3 bucket. I've started with S3 and Cloudfront support and I'm planning to support other CDNs in the future. Backblaze seems like a great provider to include as well. Once it is finished, it will be a near complete CDN solution. Right now basic uploads are working fine, but it's not ready for production yet. However, I hope to complete and release it in a few weeks. Is there any feature that you think would be nice? Some features I'm planning to include before releasing: Full Amazon S3 support Origin pull support Process module/page for managing buckets and objects, such as clearing buckets, force uploading files & directories, maintenance mode etc. Configuration page Caching uploaded file paths in DB to keep track of uploaded files and fallback to local to local urls to prevent 404 errors. Textformatter module for rewriting local urls to CDN urls. Optional custom domain support etc. Form integration with AJAX for quick upload/upload all/force upload/delete/delete all etc buttons for file/image fields while editing a page/image. Hooks for custom file uploads Extendable/hookable API for new services Future considerations Other providers. Backblaze, MaxCDN etc. Wireshell integration, (not sure how extensible WS is, but it'd be nice to have) CSS rewrites? (for imports, images etc)
    1 point
  25. @LostKobrakai I was intrigued by your comment: and have been tinkering with PW + git + heroku without having fully RTFM on heroku... I was weirded-out by my PW install almost working but eventually realized it was due to Heroku’s ephemeral filesystem": Actually, to give myself a bit of credit, I think heroku ought really to have written the above more 'honestly'; something like: > AWS S3, or similar storage services, are important when architecting applications that need any files to persist or for scale and are a perfect complement to Heroku’s ephemeral filesystem. Anyway, it looks, ATM, like I need to follow this Amazon-shaped rabbit hole... Off to do that plus asked their support people if it looks like I am doing the right thing there. I wonder, if this proves a little complex, might it be as easy/hard to simply emulate a minimal set of the steps PW's own sites have followed and forget heroku and just go Amazon for all aspects of trying to replace a traditional hosting env' (which is what I am researching)?
    1 point
  26. GitHub issue opened: https://github.com/processwire/processwire-issues/issues/255 Apparently it's quite easy to unknowingly call the php-cgi binary from a cron job on cPanel servers because the paths changed starting in EasyApache 4 (previously the /usr/bin/php path pointed to php-cli).
    1 point
  27. 1 point
  28. It's the same, just replace $page->custom_date_field with $page->created or $page->published
    1 point
  29. The code I have posted is taken from a frontend login template. The frontend has a complete suite of register, login and logout templates. I use core processes in the frontend. To prevent redirects to the backend in case of successfull login/ logout or an expired session I set login and logout URLs before I execute the Process. The customers are PW users with very limited permissions. I gave the admin area a very strange name (default: processwire) like '6gt0klw5a14' to hide it from guests or frontend users. You can additionally protect the login with a custom cookie or IP filter in your .htaccess file. Calling any target in the admin area without a valid session will cause render of the Login page instead. Its not recommended to change this.
    1 point
  30. Your textarea tag isn't valid. Instead of... <input type='textarea' id='comment' name='comment'> ...it should be... <textarea id='comment' name='comment'></textarea>
    1 point
  31. This is because the module extends Fieldtype Textarea but removes certain config inputfields (including contentType) that are not to be used with Fieldtype YAML. I'm guessing that the core Fieldtype Textarea has added an inputfield dependency for contentType since the Fieldtype YAML module was created, hence the warning. @owzim, maybe the module could set the unused config inputfields to hidden rather than not including them in the field config at all? foreach($parentInputfields as $inputfield) { if(in_array($inputfield->name, $forbidden)) { $inputfield->collapsed = Inputfield::collapsedHidden; } $inputfields->append($inputfield); }
    1 point
  32. Thanks @rick - I have pushed an update that fixes this. I also decided to show all the argument settings in the Selector Queries section, so it now looks like this:
    1 point
  33. A Followup This was an interesting conversation by all parties. What I didn't get was what actual syntax that was put in the /site/config.php file. While this may not matter to more experienced users, it makes a difference to others. My sites don't use Multi-Language and I was still confronted with the error that @EyeDentify noted in the original post when updating an existing site to ProcessWire 3.0.55 This is what I put in the /site/config.php file to make error go away /** * Installer: SetLocale Setting * * This is now needed since ProcessWire 3.0.53 to set local information * */ setlocale(LC_ALL, "en_US.utf8"); As @matjazp stated, it doesn't really matter where in the file this goes. What was confusing to me is that most other settings in the /site/config.php file started with $config and it wasn't clear whether this setting needed to follow that style. As I said, it may not be important to experienced PHP users, but knowing what to put when confronted with this warning is helpful. As an aside, it would also be helpful to know the exact syntax that is needed if using the init or ready files. Maybe I have missed something in reading all this that should have been clear to me and excuse me if I have. If what I have used is incorrect, I ask that someone provide the correct format (styling). Thanks.
    1 point
×
×
  • Create New...