Jump to content

BitPoet

Members
  • Posts

    1,331
  • Joined

  • Last visited

  • Days Won

    61

Everything posted by BitPoet

  1. Hook after ProcessLogin::afterLogin and there, if $this->user->roles has a match, do a $this->session->redirect().
  2. 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).
  3. 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.
  4. 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)?
  5. 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
  6. The dd.mm.yy format is what PW outputs, MySQL itself expects a standard datetime literal. Therefore, if you want to get all pages with a date in the current year, you need to search for a minimum date (and perhaps maximum if there are dates in future years present). <?php $startdate = date('Y') . "-01-01"; $enddate = date('Y') . "-12-31"; $zaehler = $page->children("einsatzdatum>=$startdate, einsatzdatum<=$enddate"); Or you could be daring and try out my (still beta) DatetimeAdvanced module that makes use of MySQL's builtin date and time functions. With it, you could say: <?php $year = date("Y"); $zaehler = $page->children("einsatzdatum.year=$year");
  7. There is. Use wire('languagues') instead of $languages in function scope. wire() makes all wired variables available to you this way ($page, $templates, $log etc.).
  8. I wasn't aware that the field settings are inherited in the ckeditor dialog, but I should have suspected it. What should work though is adding another image field at the bottom of the template, then going to Modules -> Configure ->ProcessPageEditImageSelect and adding the name of your gallery field where it reads "Field names to skip for selection".
  9. That's just the way ProcessWire works, by adding any uploaded image to a page. You could another collapsed/hidden images field to your template though, then you can select that field in CKEditor's image dialog.
  10. I'd probably use a repeater with fields "quantity", "unit" and "ingredient". This way, you can also do searches for individual ingredients and reorder them easily. But you can of course go the textarea way and simply split its content on newlines. <ul class='ingredients'> <?php foreach(explode("\n", $pages->ingredients) as $ingredient) { echo "<li>$ingredient</li>\n" } ?> </ul>
  11. Never done that (I usually use responsive CSS frameworks like UIkit to allow smooth scaling with a single set of templates), but you might have a look at @justb3a's MobileDetect module. With it, you should be able to adapt the paths (with code in site/init.php, not config.php, since the module needs to be initialized first) like in this recipe.
  12. Here's a short snippet for site/ready.php that hooks after ProcessPageEdit::buildForm and moves a regular field (named "testfield here") from the Content tab to Settings. The methods used are from the InputfieldWrapper class. <?php wire()->addHookAfter("ProcessPageEdit::buildForm", null, "moveFieldToSettings"); function moveFieldToSettings(HookEvent $event) { $form = $event->return; $field = $form->find("name=testfield")->first(); if($field) { $settings = $form->find("id=ProcessPageEditSettings")->first(); // Alternatively, find a specific field to insert before/after: // $settings = $form->find("name=template")->first(); if($settings) { $form->remove($field); $settings->append($field); // In the alternative, insert before or after the found field: // $form->insertBefore($field, $settings); } } }
  13. What does phpinfo() output in that regard (ini file location and whether ImageMagick is enabled)? Is the file you edited the correct one (and, if you're using MAMP Pro, could it be that it was overwritten by its template mechanism)?
  14. Properties like $this->session and $this->input are only available in Wire-derived class, so you need either use wire() syntax throughout or extend Wire in your class declaration.
  15. Another thought: give the clients a (hidden) field with the year of first revenue, populate it once with existing data and hook into whatever template determines that year to update the field (or update it in a nightly batch if a small delay isn't that much of an issue). This gives you the opportunity to use a simple selector and, most importantly since you're already thinking of growth, scales a lot better than any view.
  16. Any guess I could come up with would be stabbing in the dark, so I'd probably look for the usual suspects, like full tmp drive, sql_mode settings, file/directory permissions and PHP memory limits.
  17. Switching to 2.8 would naturally be the easiest way to avoid namespace errors. It depends on how certain you are that you can find and correct these. Usually, getting things running with namespaces isn't that much work (just adding a namespace declaration here and there or enabling template compilation), but that of course depends on the complexity of they include file layout.
  18. Repeater items are stored under the admin tree in ProcessWire, thus they are only visible in find() calls for users with admin permissions. You can circumvent that by adding "check_access=0" to your selector.
  19. Looks like the problem arises in the profileImport section. Try to ramp up logging in php.ini and/or look in the server error log to see what's going wrong.
  20. There's a typo in the source. In the line in question, "$button" is missing one "t" (and $buton is of course undefined). This has already been fixed in the development version. Meanwhile, you could add the missing "t" by hand and not worry about updates since the fix will be in the next stable release.
  21. You can distribute your templates with your module and copy them into the templates folder in the install routine. The Blog module does it this way. You will need to think about how you handle updates and reinstalls (overwrite, err out, copy with version prefix?).
  22. I concur that some kind of slow down is to be expected when complexity rises. Before touching template cache, markup cache or thinking about APC or Varnish though, the first step should IMHO always be to cache the raw PHP by using opcache or wincache (the latter should only be enabled after installing PW). Having said that, I can see a few specific things that might impact performance in 3.x: Namespaces. Yes, they don't impact hard, but any call to a global function in namespace context makes PHP look for an identically named function in the current namespace. It might be worth it to go through the code and qualify all global calls, like e.g. \explode() instead of explode(). I'll see if I can whip up a tool to do that. File system access / file compiler. Not much to do about directly it if you need it, besides faster disks. Memory footprint. More functionality, more properties, more memory, but should only be a real issue on slow or overloaded servers. Old PHP versions. Some PHP features needed a few release steps to reach optimal performance. It's always worth it to compare performance under e.g. 5.5 and 7.1. MySQL. Often, production systems using a distribution's default installation have performance schema enabled unnecessarily or are logging every tiny query to disk. Though this shouldn't impact in whole percentages unless the system's at its limits anyway.
  23. Probably an update of MySQL to 5.6. Disable STRICT_TRANS_TABLE and the message should disappear.
  24. The message just says the user or password is invalid, and that the client did provide a password. Invalid user may also mean that the configured user account is only valid for '%', which means any remote client, not localhost. You did create the fubar@localhost user, didn't you?
  25. Does chown'ing the htdocs folder including the PW installation files to the webserver user and group (I think that used to be apache:apache in standard EC2 instances) before starting the installer help?
×
×
  • Create New...