
abdus
Members-
Posts
743 -
Joined
-
Last visited
-
Days Won
42
Everything posted by abdus
-
Protip: you can filter empty categories and specify sort in your selector, and simplify your code like this <?php namespace ProcessWire; // get categories that have been referenced by a blog post $categories = $pages('template=category, blogPost.count>0, sort=name'); // $categories = $pages('parent=/categories/, blogPosts.count>0'); ?> <?php if ($categories->count): ?> <ul class="list-unstyled"> <?php foreach ($categories as $category): ?> <li class="category"> <a href="<?= $category->url ?>"><?= $category->title ?> (<?= $category->blogPosts->count ?>)</a> </li> <?php endforeach; ?> </ul> <?php else: ?> <p>No categories yet.</p> <?php endif; ?> And using API methods, your post items can be simplified to: <article class="post"> <h1 class="post__title">...</h1> <p class="post__summary">...</p> <p>Posted in: <?= $post->categories->implode('/', function($c){return "<a href='{$c->url}'>{$c->title}</a>";}) ?></p> </article> https://processwire.com/api/ref/wire-array/#pwapi-methods-retrieval https://processwire.com/api/ref/wire-array/implode/
-
Ah, ok I was directly copying from the field settings. It's filled with correct field names and everything
-
That's right. (you were putting boundaries using selectors though, so, not "all" pages) You're not searching through all pages to check if their template and category matches with your criteria. With page reference fields, all relevant pages are known beforehand (check out field_blogPosts table in your db), and fetched from DB all at once once you call $cat->blogPosts.
-
It works fine inside repeaters too $wire->addHookAfter('InputfieldPage::getSelectablePages', function($event) { if($event->object->hasField == 'pageRef') { $event->return = $event->pages->find('template=repeater_testRepeater, include=all'); } });
-
Yes, you're on the right track. Nothing to worry about
-
-
I tried using asmSelect, it works fine on my setup. Maybe you need include=all (repeater pages are hidden by default)? $wire->addHookAfter('InputfieldPage::getSelectablePages', function($event) { if($event->object->hasField == 'tags') { $event->return = $event->pages->find('template=repeater_seo, include=all'); } });
-
InputfieldPage::getSelectablePages doesnt work with all input types protected static $defaultInputfieldClasses = array( 'InputfieldSelect', // works 'InputfieldSelectMultiple', // works 'InputfieldCheckboxes', // works 'InputfieldRadios', // works 'InputfieldAsmSelect', // works 'InputfieldPageListSelect', // doesnt work 'InputfieldPageAutocomplete', // doesnt work ); Because it's called only if inputfield has addOption() method // InputfieldPage.module /** * Get delegate Inputfield for page selection * * @return Inputfield|null * @throws WireException * */ public function getInputfield() { // ... if(method_exists($inputfield, 'addOption')) { $children = $this->getSelectablePages($page); if($children) { foreach($children as $child) { $label = $this->getPageLabel($child); $inputfield->addOption($child->id, $label); } } } // ... }
-
Ah, I totally missed that. There's a config field that lets you define session timeout. /** * Session expiration seconds * * How many seconds of inactivity before session expires * * @var int * */ $config->sessionExpireSeconds = 86400; That would be tricky to implement and probably not be secure at all. But using $input->whitelist you can keep sending and receiving a session id which can be used to identify a session across browsers. Google "cookieless session php". https://stackoverflow.com/questions/5882401/cookie-less-sessions-in-php https://stackoverflow.com/questions/14229193/sharing-session-between-different-browsers I tried playing with $config->sessionFingerprint, but I couldn't make it work as you asked /** * Use session fingerprint? * * Should login sessions be tied to IP and user agent? * IP fingerprinting may be problematic on dynamic IPs. * Below are the possible values: * * 0 or false: Fingerprint off * 1 or true: Fingerprint on with default/recommended setting (currently 10). * 2: Fingerprint only the remote IP * 4: Fingerprint only the forwarded/client IP (can be spoofed) * 8: Fingerprint only the useragent * 10: Fingerprint the remote IP and useragent (default) * 12: Fingerprint the forwarded/client IP and useragent * 14: Fingerprint the remote IP, forwarded/client IP and useragent (all). * * If using fingerprint in an environment where the user’s * IP address may change during the session, you should * fingerprint only the useragent, or disable fingerprinting. * * If using fingerprint with an AWS load balancer, you should * use one of the options that uses the “client IP” rather than * the “remote IP”, fingerprint only the useragent, or disable * fingerprinting. * * @var int * */ $config->sessionFingerprint = 1;
-
Ah, good'ol brute force solution. Glad you got it working though.
-
Is it a managed server? Or is there a cPanel or a similar controller? I have very little experience with cPanel but it might have changed/intercepted logs into another file. http://www.inmotionhosting.com/support/website/getting-started-guides/cpanel-logs-for-access-apache-email-error-ftp-mysql-whm
-
What do error logs for Apache say? There should be some entries on this issue. See this SO answer for error log locations
-
What do you mean by "logged in at two places at the same time"? How can user login again if it's already logged in? Am I missing something?
-
Fieldtype not found after installation of site profile
abdus replied to Robin S's topic in General Support
Hmm, you're right. // create, but no data $options['excludeExportTables'] = array( 'field_roles', 'field_permissions', 'field_email', 'field_pass', 'caches', 'session_login_throttle', 'page_path_history', ); -
Fieldtype not found after installation of site profile
abdus replied to Robin S's topic in General Support
You might be right. initFields() is called from FieldtypeRepeater::init() method, which fires after PW finishes loading modules in boot sequence. You can try deleting module cache directly from the database using the config prepared in the previous step, before PW is bootstrapping. This forces Modules class to recreate caches <?php $db = new \ProcessWire\Database( '127.0.0.1', 'dbUser', 'dbPass', 'dbName', 1234 ); // OR (not tested) $config = new \stdClass(); require_once __DIR__ . '/site/config.php'; // OR $config = \ProcessWire\ProcessWire::buildConfig(__DIR__); $db = new \ProcessWire\Database($config); $db->query("DELETE FROM caches WHERE name = 'Modules.site/modules/'"); But a better solution would be excluding caches table from the exported profile. -
Splitting field content to parts and showing them in divs
abdus replied to MilenKo's topic in Getting Started
You can use an array and join rows with the separator <?php $rows = []; $i = 0; foreach ($order as $o) { $item = $nutrition->get("name%=$o"); // skip if item or its name or value not populated if (!$item || !$item->name || !$item->amount) continue; $row = ''; // increase the counter and assign left or right $class = ($i % 2 === 0) ? 'left-box' : 'right-box'; // to place two nutrition values in a wrapper, // on every even item open up wrapper if ($i % 2 === 0) $row .= "<div class='nutrition-detail'>"; // markup for each nutritional value $row .= " <div class='$class'> {$item->name}<br> {$item->amount} {$item->unit} </div> "; // on every odd item, close the wrapper again if ($i % 2 === 1) { $row .= "</div>"; $rows[] = $row; } $i++; } $seperator = "<div class='separator-post'></div>"; echo join($seperator, $rows); -
Fieldtype not found after installation of site profile
abdus replied to Robin S's topic in General Support
I'm not sure around which version but almost all caches were migrated to DB from filesystem. I presume it's faster to access DB as it's cached in the memory than accessing a file on the disk. But inside install.php you can flush module cache by adding $wire->modules->refresh() before the last screen when PW is ready to be bootstrapped. /** * Execution controller * */ public function execute() { // ... if(isset($_POST['step'])) switch($_POST['step']) { case 0: $this->initProfile(); break; case 1: $this->compatibilityCheck(); break; case 2: $this->dbConfig(); break; case 4: $this->dbSaveConfig(); break; case 5: require("./index.php"); /** @var ProcessWire $wire */ // Refresh module cache $wire->modules->refresh(); $this->adminAccountSave($wire); break; default: $this->welcome(); } else $this->welcome(); // ... } -
Do you want something like this? Moving a page before Admin > Pages? Then try using $pages->insertBefore(), no need to change page status at all. $anchor = $pages(3); // `Pages` page $sortee = $pages(1354); // page to move before `Pages` $pages->insertBefore($sortee, $anchor); <?php /** * Sort one page before another (for pages using manual sort) * * Note that if given $sibling parent is different from `$page` parent, then the `$pages->save()` * method will also be called to perform that movement. * * @param Page $page Page to move/sort * @param Page $sibling Sibling that page will be moved/sorted before * @param bool $after Specify true to make $page move after $sibling instead of before (default=false) * @throws WireException When conditions don't allow page insertions * */ public function insertBefore(Page $page, Page $sibling, $after = false) { /* ... */ } EDIT: Moving before Tree works too $anchor = $pages(8); // `Tree` page $sortee = $pages(1354); // page to move before `Tree` $pages->insertBefore($sortee, $anchor);
-
When exporting a FieldtypeFieldsetPage using Setup > Fields > Export, repeater fields appears empty. Other repeater types (at least RepeaterMatrix) seem to export fine { "seo": { "id": 99, "type": "FieldtypeFieldsetPage", "flags": 0, // ... "repeaterFields": "", // empty "icon": "search", // ... } }
-
For more tips, check out this blog post: https://processwire.com/blog/posts/making-efficient-use-of-fields-in-processwire/ Edit: I've written a tutorial about this topic
-
No reason it would break at all. It's quite robust and a lot of corner cases are handled in the module source. But, regardless of your setup or page count, make frequent backups of your DB. http://modules.processwire.com/modules/cronjob-database-backup/
-
Splitting field content to parts and showing them in divs
abdus replied to MilenKo's topic in Getting Started
Here you go <?php $nutritionMarkup = ''; $i = 0; foreach ($order as $o) { $item = $nutrition->get("name%=$o"); // skip if item or its name or value not populated if (!$item || !$item->name || !$item->value) continue; // increase the counter and assign left or right $class = (($i++) % 2 == 0) ? 'left-box' : 'right-box'; // to place two nutrition values in a wrapper, // open up wrapper before every even item if ($i % 2 === 0) $nutritionMarkup .= "<div class='nutrition-detail'>"; // markup for each nutritional value $nutritionMarkup .= " <div class='$class'> {$item->name}<br> {$item->amount} {$item->unit} </div> "; // close the wrapper after every odd item if ($i % 2 === 1) $nutritionMarkup .= "</div>"; } echo $nutritionMarkup; -
Repeater items are actually pages, and stored under /admin/repeaters/. If a template includes a repeater field, and you create a page using that template, a new repeater parent page is created under /admin/repeaters to house new repeater items for that specific page and a specific field. Changing the page's name or parent later on doesn't have an effect, because repeater parents use page and field ids for referencing which repeater items belong to which page and which field. <?php // FieldtypeRepeater.module /** * Return the repeater parent used by $field, i.e. /processwire/repeaters/for-field-name/ * * Auto generate a repeater parent page named 'for-field-[id]', if it doesn't already exist * * @param Field $field * @return Page * @throws WireException * */ protected function getRepeaterParent(Field $field) { $parentID = (int) $field->get('parent_id'); if($parentID) { $parent = $this->wire('pages')->get($parentID); if($parent->id) return $parent; } $repeatersRootPage = $this->wire('pages')->get((int) $this->repeatersRootPageID); $parentName = self::fieldPageNamePrefix . $field->id; // we call this just to ensure it exists, so template is created if it doesn't exist yet if(!$field->get('template_id')) $this->getRepeaterTemplate($field); $parent = $repeatersRootPage->child("name=$parentName, include=all"); if(!$parent->id) { $parent = $this->wire('pages')->newPage(array('template' => $repeatersRootPage->template)); $parent->parent = $repeatersRootPage; $parent->name = $parentName; $parent->title = $field->name; $parent->addStatus(Page::statusSystem); $parent->save(); $this->message("Created '$field' parent: $parent->path", Notice::debug); } if($parent->id) { if(!$field->get('parent_id')) { // parent_id setting not yet in field $field->set('parent_id', $parent->id); $field->save(); } } else { throw new WireException("Unable to create parent {$repeatersRootPage->path}$parentName"); } return $parent; } If you change the template and the new template does not have the same repeater field, then the repeater parent and all its items are deleted /** * Delete the given Field from the given Page * * @param Page $page * @param Field $field Field object * @return bool True on success, false on DB delete failure. * */ public function ___deletePageField(Page $page, Field $field) { $result = parent::___deletePageField($page, $field); $this->deletePageField = $field->get('parent_id'); $fieldParent = $this->wire('pages')->get((int) $field->get('parent_id')); // confirm that this field parent page is still part of the pages we manage if($fieldParent->parent_id == $this->repeatersRootPageID) { // locate the repeater page parent $parent = $fieldParent->child('name=' . self::repeaterPageNamePrefix . $page->id . ', include=all'); if($parent->id) { // remove system status from repeater page parent $parent->addStatus(Page::statusSystemOverride); $parent->removeStatus(Page::statusSystem); $this->message("Deleted {$parent->path}", Notice::debug); // delete the repeater page parent and all the repeater pages in it $this->wire('pages')->delete($parent, true); } } return $result; } As for your second question, I wouldn't worry as long as everything works fine
-
Local dev server running slow? This may be of help.
abdus replied to MikeB's topic in General Support
MySQL usually listens to loopback interface (127.0.0.1) and only responds to requests coming from the machine itself, like PHP or NodeJS etc (unless you're using a convoluted multi-server DB architecture and have configured PW to connect another machine on the network). So you dont have to change 127.0.0.1 to localhost. In fact leaving it that way will eliminate a (non-zero but very small) delay caused resolving host names to their IP addresses. -
When you save a new page with $p->save(), page is saved to the database and last insert id is assigned to the page. <?php // /wire/core/PagesEditor.php protected function savePageQuery(Page $page, array $options) { // ... // save the page to the database // ... if($result && ($isNew || !$page->id)) $page->id = $database->lastInsertId(); if($options['forceID']) $page->id = (int) $options['forceID']; return $result; } Since fields and pages are uncoupled (referencing takes place by page ids), once the page is saved, its ID becomes available and its fields are ready to save as well. <?php // /wire/core/PagesEditor.php /** * Save individual Page fields and supporting actions * * triggers hooks: saved, added, moved, renamed, templateChanged * * @param Page $page * @param bool $isNew * @param array $options * @return bool * */ protected function savePageFinish(Page $page, $isNew, array $options) { // ... // save each individual Fieldtype data in the fields_* tables foreach($page->fieldgroup as $field) { $name = $field->name; if($options['noFields'] || isset($corruptedFields[$name]) || !$field->type || !$page->hasField($field)) { unset($changes[$name]); unset($changesValues[$name]); } else { try { $field->type->savePageField($page, $field); } catch(\Exception $e) { $error = sprintf($this->_('Error saving field "%s"'), $name) . ' - ' . $e->getMessage(); $this->trackException($e, true, $error); } } } // ... } There are a lot of missing parts, but feel free to read PagesEditor class, the source is quite readable. Also, you don't have to explicitly call $page->referenceField->save(), saving page already takes care of that. <?php namespace ProcessWire; $p = new Page(); $p->template = 'page_template'; $p->parent = $parentPageOrItsId; // parent id is enough $p->addStatus(Page::statusUnpublished); $p->field = $someValue; $p->reference_field = $somePage; // $p->reference_field->save(); // not necessary $p->save();