Leaderboard
Popular Content
Showing content with the highest reputation on 03/06/2019 in all areas
-
News Update - 6 March 2019 Hi all. How time flies! It's been a while since I gave you an update on progress. Taxes Most of the current work has focused on Taxes. Although the last mini update talked about the GUI, the main work has gone into taxes. I went back and forth on this one but I think I've finally arrived at something I am satisfied with. Looking at other systems, I liked both the approaches of Shopify and Woocommerce, but especially the former. The approach we'll take will marry the best of both worlds. In a nutshell, taxes will involve/feature the following: You will first need to set up countries that you will be shipping to (this is done when setting up shipping zones). An overview of the shops tax settings. Tax Overrides: Use this feature to override the base tax rates and taxes on shipping per country and/or country state/province. You can create a product category (aka collection) to which a specific tax override will apply. Tax Exemptions: At each product level, you will be able to specify whether a product should be charged tax or not. However, you will also be able to exempt some customers from paying taxes on purchases. Currently, email addresses will be used to identify such customers. Option to choose if taxes should be charged on shipping rates or not. In cases where more than one tax can be applied, choose whether taxes are compounded (e.g. a country + province tax) or one used instead of the other. Specify whether digital products should be charged tax (e.g. EU VAT) or not. Evidence of buyers location for EU VAT. Specify if taxes are calculated based on origin (where you are shipping from/your shop location) or destination (where you are shipping to)[default]. Specify if stated/displayed prices include taxes already or not. Print out tax reports (how much tax you've charged over a given period). Taxes in the United States are a bit tricky to determine. We'll do our best to accommodate the needs of Padloper users in this country. Please note that although Padloper 2 will ship with base rate taxes for most/many territories of the world, including their states/provinces where applicable, and whilst we'll endeavour to regularly update tax rates, the responsibility to ensure that a shop/business is charging and remitting the correct taxes lies solely with the shop owner. Shop owners can use overrides to update their taxes in cases where Padloper 2 base tax rates are not up to date. We will not accept any responsibility for any wrongful tax deductions due to incorrect base rates and/or overrides. GUI i was hoping to have something for you to look at by now but I needed to sort out tax related logic first. I wanted the logic to guide the GUI design and not the other way around. So, you'll have to take a rain check on this one. Files We had a bit of a think on how to handle files (downloadable/digital products, site-wise assets, etc). No final decision has been made but the goal is to be as versatile as possible. Notifications We've started drafting plans on how this will work. More on this later but this will involve email notification to yourself, customers and shop staff regarding their orders. Some notifications will be (semi-) automated, e.g. sending order confirmation, order status change, etc. That's it for now. By next update, I'm hoping to have something for you to look at, however crude :-). Thanks!3 points
-
3 points
-
https://github.com/rolandtoth/AdminOnSteroids/wiki/FieldAndTemplateEditLinks3 points
-
2 points
-
@kongondo Awesome! Taxes are a ball ache. No mean feat, so can totally appreciate everything you're saying. Looking forward to hearing more. ?2 points
-
Thanks for the module @kongondoI'm playing with this and when URL is /processwire/dashboard-notes/view/ (without note id) I get an error: Call to a member function count() on null in ProcessWire\ProcessDashboardNotes->renderReplyMarkup(), line 1228, $note->dashboard_note is null. Check for urlsegment2 is probably needed. Also when you pass nonexisting id an error is thrown: Argument 1 passed to ProcessWire\WireData::setArray() must be of the type array, null given. I guess there is no support for comments that need to be approved? But as you said, this is in lazy development (nice wording) and good starting point for those who need it.2 points
-
It's not a new panel ? That Page Files panel shows all files on a page and highlights orphaned and missing files. The Temp status column is the only thing that's new.2 points
-
We are off to the races.. Alpha version for testing and comments now available at repo. Please note (see what I did there? ??) This thing is in lazy development. Don't expect any more non-critical changes soon. Main issues for me CSS: When note sizes are small, the note titles are still large and look horrible. I'm not sure whether to truncate or reduce font size, or? Truncating long titles + long text in dashboard Note visibility can be limited by permissions, users and roles: Should we filter out system permissions? Continuing from #3, should we NOT display superuser names (in the dropdown of lists of users who can view the note, if one wants to set that) Continuing from #4, what about frontend users only; should we remove their names from the list since they can't view the backend anyway? I'm not sure how code this Continuing from #3, currently we check if a user title exists and use that value rather than name. Maybe make the field to check configurable? Notes layout? Currently, one can reply to several responses in the same note simultaneously. Is this OK? I think that's it. If I think of anything else I'll add it here. Thanks!2 points
-
Thanks, that's what I assumed. Though it would be cool if ProcessWire did this under the hood, since having a start/offset but no limit is a realistic scenario / requirement in this case for example. Thanks, that's exactly what I was looking for! For some reason I thought I needed two different queries since I need different fields from the two sections ^^ I need to look into the WireArray methods again, some really useful stuff there ... ?2 points
-
Hi @fmgoodman Please post the Apache and the PHP error logs. Also, what is the ownership of /var/www/lib/php/session|cookie ? You can also try to troubleshoot by changing the session.save_path and session.cookie_path of your php.ini file from /var/www/lib/php/session|cookie to /tmp If you can't get it working, I offer you a ninja session on your server ?2 points
-
Hello Frank, and welcome to the forum. I'm going to assume that both those session paths are writable. Are other session settings set correctly, eg, referer_check, etc.? Is your /site/config.php readable? Is apache running as the correct user? Other more knowledgeable people will chime in as they get here, so don't fret. Glad to have you on board.2 points
-
@Robin S - actually, I think you might be correct in this case. That was my initial thought, but some recent changes I made to Tracy should have actually prevented that error, so it threw me off. So I think it might be a combination of an older version of Tracy and the disabled in modal option. I agree that the defaults should be for Tracy to be on in all those modals/panels/iframes - I have made this change in the latest version.2 points
-
You would need to exclude by ID withing the children selector or else the first page of $news_archive would be an unequal quantity (or empty depending on the value of feed_count_full) because pages would be filtered out after they are found. $news_archive = $page->children("template=news, id!=$news_full, limit=50"); If all of the results are appearing on the same page (no pagination is used) then you can achieve it without needing a second DB query... $news = $page->children("template=news"); $news_full = $news->slice(0, $page->feed_count_full); $news_archive = $news->slice($page->feed_count_full);2 points
-
This module enables you to send push notifications and receive information about sent notifications on your HTTPS ProcessWire website. It enables you to: Create a field of type FieldtypePushAlert that you can add to a page template. This is a multi-input field widget that enables you to send notifications from your page in the admin Page Edit and monitor statistics - Attempted, Delivered, Clicked, etc Send notifications from a page template directly using the API, eg to PW users who have a specific role and have subscribed to notifications Capture subscriptions on your website front end All kudos to the great support team at PushAlert and to all the ProcessWire developers who've helped me with this project. Download from the Modules directory at: https://modules.processwire.com/modules/push-alert/ or from GitHub at: https://github.com/clipmagic/PushAlert Full instructions for use are in the module README.md file. Enjoy!1 point
-
What @szabesz said above. My thinking was that this is used in the admin, meaning existing users with access to the module (permission dashboard-notes) are already trusted users. Yeah, I had "fun" with these ones. i went from using input strings to urlSegments and I must have forgotten to catch such errors. PR please ?. Thanks for testing!1 point
-
If this tool is used by site editors and superusers only (as a collaboration tool) of trusted users, then there is no need for this feature. It is used in the admin after all, so it should be ok to publish messages right away. Am I missing something?1 point
-
This MySQL uft8 collations comparison may help you: http://mysql.rjweb.org/utf8_collations.html Took it from here: https://stackoverflow.com/questions/2344118/utf-8-general-bin-unicode1 point
-
Hah, traced the error to the /wire/modules/LanguageSupport/ProcessLanguageTranslator.module where line 671 contains: if(!$dirIterator) return array(); Played around a little and noticed when I change the statement to this: if($dirIterator != false) return array(); The error doesn't show up, but the list of translatable files is still empty. I googled this strange behavior and found out it's a bug in PHP. Which was fixed in version 5.6.17. Which reminded me I am not really sure which PHP version I am using atm. It was 5.4 ? (but PW requirements state required PHP version is 5.4 or newer...?) Tested on 7.2, works ? Lesson learned (again): when something is fishy, first check the basics ?1 point
-
Yeah, it was a combination of the older version and the disabled modals. in newer versions of Tracy, that error wouldn't appear, although the debug bar still wouldn't have appeared with the dump because it was disabled, but the error threw me and made me think the cause of the problem was different. Anyway, glad it's working for you now.1 point
-
Thanks again for all the effort! I will find some time this week (probably on the weekend) to check it out and comment, and I'll see what else I can contribute.1 point
-
I found this tip originally for WordPress. function filter_ptags_on_images($content){ return preg_replace('/<p>\\s*?(<a .*?><img.*?><\\/a>|<img.*?>)?\\s*<\\/p>/s', '\1', $content); } $text = filter_ptags_on_images($page->text); echo $text;1 point
-
Thanks guys. I have it working now and see the relevant php docs That's cool. So we put $count++; at the end of the echo because when it loops back around, it uses this new value as the starting number. Good to know.1 point
-
OT: I'm really missing that ninja-emoji from the old invision forum.1 point
-
1 point
-
https://processwire.com/api/ref/wire-array/eq/ It says: "Returns the item at the given index..." if you do not know the index, you cannot get it this way. In other words, you already have the reference to the object, you need an index, so you are facing some sort of opposite scenario (sure, the page ID and your index are not the same, btw). Otherwise just do what @dragan has suggested ? I do not recommend starting form -1 if you increment it at the last line in the loop as in this case the first index will be -1 and Peter needs it to be 0.1 point
-
Indeed. Nonetheless, DeepL is of awesome help to translate my website content. I use it as a basis and tweak the result. It saves tons of time. The only missing thing would be more comfort to save me from this copy from Processwire, paste to Deepl, copy from Deepl, paste to Processwire.1 point
-
Thanks @psy, all template files are 644 - not run in to permissions problems before. Not to worry re this one. With thanks.1 point
-
@szabesz the invision board software has eaten your link! Please add it again and add a new line after it.1 point
-
There are some quirks regarding this issue, please read this report, particularly the last two comments of mine: https://github.com/processwire/processwire-issues/issues/668 EDIT: I posted this too early by accident.1 point
-
Well I tweaked through all the different values of fingerprint including disabling them and none of them fixed the issue, but thanks for the suggestions! F.1 point
-
@fmgoodman, you could experiment with different values for $config->sessionFingerprint (add the line to /site/config.php) to try and track down the issue. See the $config docs and the code comments for Session::getFingerprint().1 point
-
If the page you are loading in the iframe is on the same domain as your site you can get the title by finding it within jQuery contents() - google it for details. If the page is not on the same domain then you cannot get the iframe contents (and therefore the title) via Javascript. You could get the page contents via PHP (e.g. PW WireHttp) and use DOMDocument or similar to find the title, but the simplest thing might be to use an external API like http://textance.herokuapp.com/index.html Would be smart to cache the title for a while with WireCache.1 point
-
$c = 0; foreach($item->images as $photo){ echo "<li uk-slideshow-item='$c'><a href='#'><img src='{$photo->size(150,100)->url}' alt=''></a></li>"; $c++; } just use a good old counter var instead ?1 point
-
Thanks a lot for this solution @mscore! I've just switched successfully my default language from English to German by this. The only remaining step seemed to be moving "German" to the first position of "Languages" in the page tree, then I also get the "German" tab at first position when editing a text. I really can't understand why this is no PW default feature yet -- this feature was essential for finishing my site migration to PW, and my fear of wrecking my content by a dirty workaround delayed my project probably by several months!1 point
-
@bluellyr, your screenshot shows a modal. Make sure you do not have the Tracy debug bar disabled for modals in the module config: @adrian, I wonder if it would be better to not have Tracy disabled for modals by default, and people can disable it if they are finding the debug bar annoying. And perhaps spell it out that no debug bar = errors if you try and use a dump method.1 point
-
Both of these are the same and they work like this: $files->send($page->file->filename); And you can force the download for file types that might otherwise open in the browser like this: $files->send($page->file->filename, ['forceDownload' => true]);1 point
-
Use $item->images instead of $product_gallery->images in your inner foreach loop.1 point
-
Hi there! Backup your database and try changing database table collations to utf8_swedish_ci. In addition this solves some sorting issues with words starting with Å, Ä or Ö.1 point
-
Wouldn't it be better to do something like: $news_full = $page->children("template=news, limit={$page->feed_count_full}"); $news_archive = $page->children("template=news, limit=50")->filter("id!=$news_full"); so you can keep a reasonable limit to the archive query, 50 for instance, instead of loading all the children at once?1 point
-
That's currently the way it is implemented. If there's no limit given, then PW ignores the offset (for the curios, it's implemented in PageFinder::getQueryStartLimit). It makes sense from a database point of view since MySQL's LIMIT keyword always needs a row count. So your approach is the correct one. You could perhaps use a reasonably high number (like MySQL's maximum unsigned integer, 18446744073709551615) for your limit. If you don't want to have to remember the value, just put it intosite/config.php like "$config->maxDbLimit = 18446744073709551615;" and then use "limit={$config->maxDbLimit}" in your selector.1 point
-
Sorry @bluellyr - my apologies - I didn't initially realize what you meant by "template context". Using Tracy to debug the PW core is hit and miss. It loads before all site modules, but for the core and core modules it often isn't available depending on exactly what you are trying to debug. Unfortunately there isn't anything I can do about this. Typically for this, using $log->save('testing', json_encode($page)); or something like that is your best bet.1 point
-
Are you sure you did everything correct? I also installed a new latest master PW3.0.123, and added 2 multisites to test. Works fine here. Can you show the config or tell more about what you did? The allowed httpHosts should be entered in the config as well. And the multisite config is as shown in the readme. The default 404 page has always been the basic-page template in PW, and the ID is what you enter in the multisite config. This works fine.1 point
-
Hey @Tom. Thanks for the nice words and your feedback. I agree, it would be neat to have an image field for clients. Could you open a feature request on GitHub? I would like to discuss the implementation with you, or anyone interested in this. I guess there are may ways to achieve this, and I'm not sure which one's the best ?1 point
-
1 point
-
1 point
-
Here is a new created version to track changes which works without any problems and you dont have to take care about the deletion of input values if the page was not saved successfully (like in the version before) Put this little piece of code inside your ready.php. //Compare before and after values and output a warning message $pages->addHookAfter('Pages::saveReady', function($event) { $page = $event->arguments('page'); $page->of(false); //configuration: change it to your needs $templates = ['event_businessvacations', 'event_dates', 'event_events', 'event_specialbusinesshours']; //array of templates where this hook should run $fields = ['summary', 'body']; //array of fields which should be checked //configuration end if(in_array($page->template->name, $templates)){ $changedfields = []; foreach($fields as $fieldname){ if ($page->isChanged($fieldname)) { // Page as it is in the DB $oldPage = wire('pages')->getById($page->id, array( 'cache' => false, // don't let it write to cache 'getFromCache' => false, // don't let it read from cache 'getOne' => true, // return a Page instead of a PageArray )); $changedfields[] = $oldPage->fields->$fieldname->label; } } $changedfields = implode(", ", $changedfields); if(!empty($changedfields)){ $this->warning(__("The following fields have been changed: {$changedfields}")); } } }); Change the configuration block to your needs (template names, field names). This little code snippet outputs only a warning message which fields have been changed - not more or less, but you can also run some other logics - its up to you. Note: Works also with repeaterfields, but you can only check the repeaterfields for changes in general. It is not possible to check for specific fields inside the repeater.1 point
-
Hi @pandaman For one of my customers I made some basic experiments to connect the both systems together. Your arguments are valid. I also told him to combine the best of two worlds and let each system do what it's best for. What I can say right now, it is possible, but there might be pitfalls, that I didn't discover yet. One problem was the session management which caused conflicts so you have to modify it. Right now I have the following working solution (PW 3 and Magento 2.1): I installed Magento 2.1 in a "store" directory that lies right beside site and wire directories. I made a magento2-bridge.php in site/templates/ <?php namespace ProcessWire; require $_SERVER['DOCUMENT_ROOT'] . '/store/app/bootstrap.php'; use Magento\Framework\App\Bootstrap; class StoreApp { private static $instance; public static function get_instance() { if ( ! isset(self::$instance) && ! (self::$instance instanceof StoreApp) ) { self::$instance = new StoreApp(); } return self::$instance; } public $helper; public $quote; public $session; public $cart; public $customer; private function __construct() { $bootstrap = Bootstrap::create(BP, $_SERVER); $obj = $bootstrap->getObjectManager(); $state = $obj->get('Magento\Framework\App\State'); $state->setAreaCode('frontend'); $this->customer = $obj->get('Magento\Customer\Model\Session')->getCustomer(); $this->quote = $obj->get('Magento\Checkout\Model\Session')->getQuote(); $this->helper = $obj->get('\Magento\Checkout\Helper\Cart'); $this->session = $obj->get('Magento\Checkout\Model\Session'); $this->cart = $obj->get('\Magento\Checkout\Model\Cart'); } } I had to modify the session management in Magento, so it stores its session variables in ProcessWires directory. I hope I remember this correctly: Change the Cookie Path in Magento @ Stores -> Configuration -> Web -> Defaut Cookie Settings to "/" (without the quotes) Change the save_path for sessions under store/app/etc/env.php 'session' => array ( 'save' => 'files', 'save_path' => $_SERVER['DOCUMENT_ROOT'].'/site/assets/sessions', At some point I changed the sessionAllow parameter of ProcessWire, but I don´t know if this is needed anymore. But for completeness, here is the code I used: $config->sessionAllow = function () { if (strpos($_SERVER['REQUEST_URI'], '/processwire/') === 0) { return true; } return false; }; Then in my site/templates/home.php I have the following code, to get data from Magento (like Customer data, what is in the cart, product information): require_once __DIR__ .'/magento2-bridge.php'; $store = StoreApp::get_instance(); $quote = $store->helper->getQuote(); $quoteitems = $quote->getAllItems(); $customer = $store->customer; bd($customer->getName()); foreach ($quoteitems as $item) { // Code to get contents per product bd($item->getName()); bd($item->getQty()); } If you are wondering what bd() means. It is a debugging output from Tracy Debugger for ProcessWire (recommended install). Why do I share this information here although it was very time-consuming and expensive to figure this out? Because I had great support from the PW community and want to give something back. If you make any progress with this, please try to do the same and share your findings with our lovely community.1 point
-
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.1 point
-
Try this count($entry->comment) or maybe this will work too $entry->comment->count(); $entry->comment.count definately doesn't, and returns the toString method of the object which is the id's comma separated I would guess.1 point
-
The first part of doing this would be to determine what you want your starting date to be. You could do this by setting a cookie on every pageview to track the time they last viewed it: Setting and getting the date and time of the last visit <?php // see when their last visit was from a cookie that we set if(isset($_COOKIE['lastVisit'])) { // get the last visit time from the cookie $lastVisit = (int) $_COOKIE['lastVisit']; // if it was more than 30 days ago, then just assume 30 days $minLastVisit = strtotime("-30 days"); if($lastVisit < $minLastVisit) $lastVisit = $minLastVisit; } else { // no last visit, so assume 7 days ago $lastVisit = strtotime("-7 days"); } // make a nice formatted date we can use for output later, i.e. October 1, 2011 5:00 am $lastVisitStr = date("F j, Y H:i a", $lastVisit); // set a cookie with the time of this request, and the cookie expires in 30 days setcookie('lastVisit', time(), strtotime('+30 days')); OR, if you wanted to just use a specific date, then you could just do this instead: <?php $lastVisit = strtotime("October 1, 2011 5:00 am"); $lastVisitStr = date("F j, Y H:i a", $lastVisit); // formatted version for output Displaying pages modified since the last visit Once you know your starting date/time (lastVisit), then you can use that to find pages that have been modified since that time: <?php // find the pages that have been modified, and sort by that $modifiedPages = $pages->find("modified>=$lastVisit, sort=-modified, limit=50"); if(count($modifiedPages)) echo "<h2>Pages modified since $lastVisitStr</h2>"; // loop through the pages and display them foreach($modifiedPages as $p) { $modified = date('F j, Y H:i a', $p->modified); // i.e. October 1, 2011 5:00 am echo "<p><strong><a href='{$p->url}'>{$p->path}</a></strong> last modified $modified</p>"; } If you instead wanted to use the created date rather than the modified date, then you would just replace every instance of the word 'modified' with 'created' above. And likewise you can substitute any other date fields you might add to your page. Displaying new comments since the last visit When it comes to comments, we'll use a similar approach. But we first find the pages that have new comments, and then find the new comments on those pages. Since this is more of a specific purpose, we're not using PW's built in comments render functions and instead doing it ourselves here. Also, we'll assume that you already have that $lastVisit variable set from the first code example. <?php // first find the pages that have new comments on them, sort by the comment created date $commentPages = $pages->find("comments.created>=$lastVisit, sort=-comments.created, limit=50"); // output a header that uses the $lastVisitStr var we set in the previous code example if(count($commentPages)) echo "<h2>Comments posted since $lastVisitStr</h2>"; // loop through the pages that have new comments foreach($commentPages as $p) { // find the new comments on this page that have an approved status // status=1 means approved (i.e. not spam or pending approval) $comments = $p->comments->find("created>=$lastVisit, sort=-created, status=1"); // find out how many there are. if none, then move to next in loop $total = count($comments); if(!$total) continue; // output the page that had the comments with a link to it echo "<h3><a href='{$p->url}'>$total new comment(s) found at {$p->path}</a></h3>"; // display the comments foreach($comments as $c) { $cite = htmlentities($c->cite); // prep what we need for output $text = nl2br(htmlentities($c->text, ENT_QUOTES, "UTF-8")); $created = date('F j, Y H:i a', $c->created); echo "<p class='comment'>"; echo "<strong>Posted by $cite on $created:</strong>"; echo "<blockquote>$text</blockquote>"; echo "</p>"; } }1 point