BitPoet

Members
  • Content count

    502
  • Joined

  • Last visited

  • Days Won

    19

BitPoet last won the day on December 31 2016

BitPoet had the most liked content!

Community Reputation

1,075 Excellent

About BitPoet

  • Rank
    Hero Member

Profile Information

  • Gender
    Male
  • Location
    Near Munich / Germany
  • Interests
    programming, writing, scuba diving

Recent Profile Visitors

3,518 profile views
  1. <?php $pagearr = $pages->newPageArray()->add($page->myfield);
  2. You can hook before ProcessLogin::executeLogout and set the desired url. <?php wire()->addHookBefore("ProcessLogin::executeLogout", null, "setRedirect"); function setRedirect(HookEvent $event) { $event->object->setLogoutURL($pages->get('/')->httpUrl); }
  3. Since I am the one guilty of adding UTF-8 support to WireMail, I could take an educated guess WireMailSmtp uses some external libraries that have evolved over the years and have their own routines to handle character encoding in address headers. I'll take a look at what is done differently there (I have an idea, but I'll have to run some tests to be sure) . In the meantime, if using wireMailSmtp is an option, I'd go with that.
  4. Could you try and shorten the fromName and toName (or replace it with plain ascii ones) for testing and see if that fixes it? It might be a line wrapping issue in one of those two.
  5. Is the first line complete? The missing from: header start and quoted-printable preamble is the only thing I see that's incorrect. If it's not, can you make sure that there are no line breaks in the fromName? setLocale shouldn't have any influence on WireMail's behavior.
  6. So the fields are named Tab1...Tab9 resp. TabContent1...TabContent9? In that case, two possibilities immediately come to mind, but both require you to assemble the name outside of the quotes (there might be a way inside the strings, but I can't think of one right now). <?php //... $tabname = "Tab" . $i; echo "<label for='tab{$i}' class='labeltab'>{$page->$tabname}</label>\n"; Or, in one go: <?php //... echo "<label for='tab{$i}' class='labeltab'>" . $page->get("Tab" . $i) . "</label>\n";
  7. Does that happen for all users or only when you're logged in as superuser?
  8. Since I'm doing a lot of detailed logging in our internal PW-based systems, that has become a bit of a bottleneck under heavy load and I was missing a centralized view with the growing number of separate PW instances. So I dug into the core a bit, namely WireLog.php and FileLog.php as well as ProcessWire.php. I managed to whip up my own WireLogDatabase and DbLog classes mimicking the behaviour of the regular logging classes, but not without a little bit of tweaking to the ProcessWire class itself to replace the regular logger. Now I'm logging to a MySQL server instead of plain files and ProcessLogger works smoothly with it without tweaking. I thought it would be shame to keep this all to myself, but a release-worthy version would need or could benefit from: a bit of polishing in regards to error handling and proper treatment of conflicting concurrent operations without too much lock overhead (drop table vs. insert especially) more source code documentation a little more abstraction so all csv operations are deprecated in favor of database columns where avaible last but not least, an approved way to configure the substitute logger and load it early on, which means touching the core ProcessWire class Before I invest too much into that, I'd love to hear all thoughts on this, especially if you think such a module may fit your requirements, and I would be especially happy to hear from @ryan - could you see such a mechanism in the core?
  9. Have you tried linking s3fs to assets/files instead of the whole assets folder, just to see what difference that makes? Also, I could imagine that it's not only the cache folder that may slow things down at some point but also session and log, both of which could benefit from something with less round trips (and that handles concurrent writes better) than s3fs. Sessions could (and should) thus probably be switched to SessionHandlerDB. I think goofys is at a disadvantage since it doesn't have local caching. The scenarios where it overtakes s3fs (e.g. creating many files at once) fall short when s3fs can use its cache (that's why the benchmarks on the goofys page only compare uncached operations).
  10. It should be possible to do this by specifying a base: <base href="<?= $config->urls->templates ?>myAppDirectory" /> in the header of the page. That means that relative link behavior changes as any url that doesn't start with a slash will be searched inside myAppDirectory.
  11. Hook after ProcessLogin::afterLogin and there, if $this->user->roles has a match, do a $this->session->redirect().
  12. Just because it's Monday: <?php /** * ProcessWire module. * * Allow depending modules to be updated from non-standard repositories. * * In absence of this module, your module will continue to work but updates * will point at the regular repository. If you want to avoid that, add * a "requires" entry in your module * * To trigger the custom update routines, add an array "extendedUpdate" to * your getModuleInfo array. It can either contain a "url" entry with the * URL to the JSON data that describes the available update, or a "customMethod" * entry with the name of a public method in your module that returns that * URL. * * If you go the "url" way, the value there is run through wirePopulateStringTags. * You can add placeholders for all of the modules moduleInfo values, the module's * configuration values, the "moduleServiceKey" (PW base version, i.e. pw300, pw280 etc.) * and the module's name as, yeah, you guessed it, "name". * * If you use customMethod, it gets passed an associative array with all these values. * * A simple example for a module using custom updates: * * class TestModule extends WireData implements Module { * public static function getModuleInfo() { * return array( * "title" => _("Test Module"), * "summary" => _("Test for a module with a custom update location"), * "version" => "0.0.1", * "requires" => array("ModuleUpdateCustomUrl"), * "extendedUpdate" => array( * "url" => "https://bitpoet.ddns.net/update/{name}.json?apiVersion={moduleServiceKey}", * // Alternative way to generate the URL through a method in your module: * //"customMethod" => "generateUrl" * ) * ); * } * * public function generateUrl($data) { * return "https://bitpoet.ddns.net/update/" . $data['name'] . ".json?apiVersion=" . $data['moduleServiceKey']; * } * * } * * * A somewhat minimal JSON example response for the test module above: * { * "status":"success", * "class_name":"TestModule", * "module_version":"0.0.2", * "authors":[ * { * "title":"BitPoet" * }, * { * "title":"Anonymous" * } * ], * "pw_versions":[ * { * "name":"2.7" * }, * { * "name":"2.8" * }, * { * "name":"3.0" * } * ], * "download_url":"https://bitpoet.ddns.net/update/TestModule_0.0.2.zip", * "release_state":{ * "title":"Beta" * }, * "summary":"Test for a module with a custom update location", * "module_home":"https://bitpoet.ddns.org/modules/TestModule" * } * * For a complete example, see the live data from the official repo, e.g. * http://modules.processwire.com/export-json/Helloworld/?apikey=pw300 * * The response must either return a property "success" with a true value * or populate an "error" property with a description of what went wrong. * */ class ModuleUpdateCustomUrl extends WireData implements Module { public static function getModuleInfo() { return array( "title" => _("Module Update from Custom Url"), "summary" => _("Extension module that allows modules to be updated from non-standard repos."), "version" => "0.0.4", "autoload" => true, ); } public function init() { $this->addHookBefore("ProcessModule::execute", $this, "hookProcessModuleExecute_customUpdate"); } public function hookProcessModuleExecute_customUpdate(HookEvent $event) { if($this->input->get->update) { $name = $this->sanitizer->name($this->input->get->update); $info = $this->modules->getModuleInfo($name); if(! $info["extendedUpdate"]) return; if(! isset($info["extendedUpdate"]["url"]) && !isset($info["extendedUpdate"]["customMethod"])) { $this->error($this->_('Neither URL nor custom method set in extendedUpdate configuration in module info')); return; } $event->return = $this->downloadDialog($name, $info); $event->replace = true; } } public function downloadDialog($name, $info) { $redirectURL = "./edit?name=$name"; $className = $name; $cfgdata = $this->modules->getModuleConfigData($name); $params = array_merge($info, $cfgdata); $params["moduleServiceKey"] = $this->wire('sanitizer')->name($this->wire('config')->moduleServiceKey); $params["name"] = $name; if(isset($info["extendedUpdate"]["customMethod"])) { $method = $info["extendedUpdate"]["customMethod"]; $module = $this->modules->get($name); $url = $module->$method($params); } else { $url = trim(wirePopulateStringTags($info["extendedUpdate"]["url"], $params)); } $http = $this->wire(new WireHttp()); $data = $http->get($url); if(empty($data)) { $this->error($this->_('Error retrieving data from web service URL') . ' - ' . $http->getError()); return $this->session->redirect($redirectURL); } $data = json_decode($data, true); if(empty($data)) { $this->error($this->_('Error decoding JSON from web service')); return $this->session->redirect($redirectURL); } if($data['status'] !== 'success') { $this->error($this->_('Error reported by web service:') . ' ' . $this->wire('sanitizer')->entities($data['error'])); return $this->session->redirect($redirectURL); } $form = $this->buildDownloadConfirmForm($data); return $form->render(); } public function buildDownloadConfirmForm($data) { $warnings = array(); $authors = ''; foreach($data['authors'] as $author) $authors .= $author['title'] . ", "; $authors = rtrim($authors, ", "); $compat = ''; $isCompat = false; $myVersion = substr($this->wire('config')->version, 0, 3); foreach($data['pw_versions'] as $v) { $compat .= $v['name'] . ", "; if(version_compare($v['name'], $myVersion) >= 0) $isCompat = true; } $compat = trim($compat, ", "); if(!$isCompat) $warnings[] = $this->_('This module does not indicate compatibility with this version of ProcessWire. It may still work, but you may want to check with the module author.'); $form = $this->wire('modules')->get('InputfieldForm'); $form->attr('action', './download/'); $form->attr('method', 'post'); $form->attr('id', 'ModuleInfo'); $markup = $this->wire('modules')->get('InputfieldMarkup'); $form->add($markup); $installed = $this->modules->isInstalled($data['class_name']) ? $this->modules->getModuleInfoVerbose($data['class_name']) : null; $moduleVersionNote = ''; if($installed) { $installedVersion = $this->formatVersion($installed['version']); if($installedVersion == $data['module_version']) { $note = $this->_('Current installed version is already up-to-date'); $installedVersion .= ' - ' . $note; $this->message($note); $this->session->redirect("./edit?name=$data[class_name]"); } else { if(version_compare($installedVersion, $data['module_version']) < 0) { $this->message($this->_('An update to this module is available!')); } else { $moduleVersionNote = " <span class='ui-state-error-text'>(" . $this->_('older than the one you already have installed!') . ")</span>"; } } } else { $installedVersion = $this->_x('Not yet', 'install-table'); } $table = $this->wire('modules')->get('MarkupAdminDataTable'); $table->setEncodeEntities(false); $table->row(array($this->_x('Class', 'install-table'), $this->wire('sanitizer')->entities($data['class_name']))); $table->row(array($this->_x('Version', 'install-table'), $this->wire('sanitizer')->entities($data['module_version']) . $moduleVersionNote)); $table->row(array($this->_x('Installed?', 'install-table'), $installedVersion)); $table->row(array($this->_x('Authors', 'install-table'), $this->wire('sanitizer')->entities($authors))); $table->row(array($this->_x('Summary', 'install-table'), $this->wire('sanitizer')->entities($data['summary']))); $table->row(array($this->_x('Release State', 'install-table'), $this->wire('sanitizer')->entities($data['release_state']['title']))); $table->row(array($this->_x('Compatibility', 'install-table'), $this->wire('sanitizer')->entities($compat))); // $this->message("<pre>" . print_r($data, true) . "</pre>", Notice::allowMarkup); $installable = true; if(!empty($data['requires_versions'])) { $requiresVersions = array(); foreach($data['requires_versions'] as $name => $requires) { list($op, $ver) = $requires; $label = $ver ? $this->sanitizer->entities("$name $op $ver") : $this->sanitizer->entities($name); if($this->modules->isInstalled("$name$op$ver") || in_array($name, $data['installs'])) { // installed $requiresVersions[] = "$label <i class='fa fa-fw fa-thumbs-up'></i>"; } else if($this->modules->isInstalled($name)) { // installed, but version isn't adequate $installable = false; $info = $this->modules->getModuleInfo($name); $requiresVersions[] = $this->sanitizer->entities($name) . " " . $this->modules->formatVersion($info['version']) . " " . "<span class='ui-state-error-text'>" . $this->sanitizer->entities("$op $ver") . " " . "<i class='fa fa-fw fa-thumbs-down'></i></span>"; } else { // not installed at all $requiresVersions[] = "<span class='ui-state-error-text'>$label <i class='fa fa-fw fa-thumbs-down'></i></span>"; $installable = false; } } $table->row(array($this->_("Requires"), implode('<br />', $requiresVersions))); if(!$installable) $this->error("Module is not installable because not all required dependencies are currently met."); } if(!empty($data['installs'])) { $installs = $this->sanitizer->entities(implode("\n", $data['installs'])); $table->row(array($this->_("Installs"), nl2br($installs))); } $links = array(); $moduleName = $this->wire('sanitizer')->entities1($data['name']); if($data['module_home']) { $moduleURL = $this->wire('sanitizer')->entities($data['forum_url']); $links[] = "<a target='_blank' href='$moduleURL'>" . $this->_('More Information') . "</a>"; } if($data['project_url']) { $projectURL = $this->wire('sanitizer')->entities($data['project_url']); $links[] = "<a target='_blank' href='$projectURL'>" . $this->_('Project Page') . "</a>"; } if($data['forum_url']) { $forumURL = $this->wire('sanitizer')->entities($data['forum_url']); $links[] = "<a target='_blank' href='$forumURL'>" . $this->_('Support Page') . "</a>"; } if(count($links)) $table->row(array($this->_x('Links', 'install-table'), implode(' / ', $links))); if($data['download_url']) { $downloadURL = $this->wire('sanitizer')->entities($data['download_url']); $table->row(array($this->_x('ZIP file', 'install-table'), $downloadURL)); $warnings[] = $this->_('Ensure that you trust the source of the ZIP file above before continuing!'); } else { $warnings[] = $this->_('This module has no download URL specified and must be installed manually.'); } foreach($warnings as $warning) { $table->row(array($this->_x('Please Note', 'install-table'), "<strong class='ui-state-error-text'> $warning</strong>")); } $markup->value = $table->render(); if($installable && $data['download_url']) { $btn = $this->wire('modules')->get('InputfieldSubmit'); $btn->attr('id+name', 'godownload'); $btn->value = $this->_("Download and install"); $btn->icon = 'cloud-download'; $btn->value .= " ($data[module_version])"; $form->add($btn); $this->session->ProcessModuleDownloadURL = $data['download_url']; $this->session->ProcessModuleClassName = $data['class_name']; } else { $this->session->remove('ProcessModuleDownloadURL'); $this->session->remove('ProcessModuleClassName'); } $btn = $this->wire('modules')->get('InputfieldButton'); $btn->attr('name', 'cancel'); $btn->href ="./edit?name=$data[class_name]"; $btn->value = $this->_("Cancel"); $btn->icon = 'times-circle'; $btn->class .= ' ui-priority-secondary'; $form->add($btn); $form->description = $this->wire('sanitizer')->entities($data['title']); return $form; } /** * Format a module version number from 999 to 9.9.9 * * @param string $version * @return string * */ protected function formatVersion($version) { return $this->wire('modules')->formatVersion($version); } } Created with massive theft of code from ProcessModule.module I'm going to toy around with this in our local environment and see if I can stitch together a nice workflow with the local git repo and a post-receive hook (once I find the time).
  13. That is what happens in the backend (you're logging in to the admin), but you can create your own frontend login procedure as well (completely from scratch or with the FrontendUser module). In that case, you control where the user gets redirected to.
  14. I can't reproduce it here, so something in your installation has to be different. Which versions of PHP and MySQL are you running? Did you choose any non-default options when installing PW (like InnoDB storage engine)?
  15. You can use status!=hidden in your selector, so in "Custom selector..." you'd input something like: parent=[id of Content Blocks page], status!=hidden