Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 12/04/2021 in all areas

  1. I agree wit wbmnfktr, however, I also tend to agree with OP that the installer could be made more fool-proof. We’ve had a lot of threads over the years with people getting 500s trying to access the Admin (or anything other than the front page) right after installing PW. OP’s case may be especially niche, but .htaccess adjustments to get PW running aren’t really. It might be a good idea for the installer to ping the chosen admin URL and display a warning with some instructions if it doesn’t get a 200. Also, from a UI perspective, looking at this screenshot I just see a bunch of tick marks. We all know users don’t read anything you put in front of them, and this only encourages us to believe no further action is needed. IMHO, the “Complete & Secure Your Installation” section shouldn’t have check marks. Maybe it should even be a different color to emphasise its to-do-list nature and differentiate it from the mere success messages. “Get Started” is mostly to-do as well, albeit more optional.
    5 points
  2. ProcessWire 3.0.190 has 15 commits relative to 3.0.189 and contains a mixture of issue resolutions and feature additions. We’ll review these below, in addition to updates for the PageAutosave and ProFields Table modules— https://processwire.com/blog/posts/pw-3.0.190/
    5 points
  3. Fieldtype modules Not really a tutorial, but a bunch of stuff I learnt in building a fieldtype module (FieldtypeMeasurement). That module is used as a starting point for many of the examples. Happy to take corrections and improvements ? Basics For a (full-featured) module, you actually need two module files: FieldtypeModuleName.module; and InputfieldModuleName.module The Fieldtype module defines the general settings for the fieldtype (how it appears in the setup->field page), together with how it interacts with the database, while the Inputfield module defines how the field appears when editing it in a page. In addition, for complex fields, you can define a class to hold your field values in an object. This allows you to provide custom methods for use in the API. Otherwise you can store field values as any existing type or ProcessWire object The chart below summarises the interactions of these elements and the subsequent sections describe the methods in more detail. Fieldtype Module The important methods are described below. __construct() Generally not much is required here (apart from parent::__construct();). If you have a php script with the field object then include it (require_once() ). SQL database interaction getDatabaseSchema(Field $field) This is essential. It states what data will be saved to the SQL database (via the sleepValue function - see below). An example is: public function getDatabaseSchema(Field $field) { $schema = parent::getDatabaseSchema($field); $schema['data'] = 'double NOT NULL'; // value in base units $schema['magnitude'] = 'varchar(64)'; // value in current units - needs to be text to store composite values $schema['unit'] = 'text NOT NULL'; $schema['quantity'] = 'text NOT NULL'; return $schema; } 'data' is required and is a primary key field which means that 'text' cannot be used, although varchar(64) is OK. Often (as here) it would be a numeric field of some type. Other items can be defined as required. ___sleepValue(Page $page, Field $field, $value) This determines how the ProcesssWire field object is mapped to the SQL schema. You need to return an array where the keys are the elements defined in the schema, e.g.: $sleepValue = array( 'data' => $data, 'magnitude' => $magnitude, 'unit' => $value->unit, 'quantity' => $value->quantity ); return $sleepValue; ___wakeupValue(Page $page, Field $field, $value) This is basically the inverse of sleepValue - mapping the array from the database into the field object. In the example below, the field object (Measurement) extends WireData. Properties in WireData objects can be stored in the 'data' property via 'get' and 'set' methods. getBlankValue() (see next section) performs the initial setting of these - config values for the field (see below) can be set, but otherwise the settings are just placeholders. Properties set to a WireData object can also be set or accessed directly, like $item->property or using array access like $item[$property] public function ___wakeupValue(Page $page, Field $field, $value) { // if for some reason we already get a valid value, then just return it if($value instanceof Measurement) return $value; // start a blank value to be populated $measurement = $this->getBlankValue($page, $field); // if we were given a blank value, then we've got nothing to do: just return a blank Measurement object if(empty($value) || !is_array($value)) return $measurement; // create new Measurement object $measurement->quantity = $value['quantity']; // ... custom processing ... $measurement->baseMagnitude = $value['data']; if($value['unit']) { $measurement->unit = $value['unit']; $units = $measurement->getUnits(); if(array_key_exists($value['unit'], $units) && isset($value['magnitude'])) { $measurement->magnitude = explode('|', $value['magnitude']); } else { $measurement->magnitude = $measurement->baseMagnitude; $measurement->unit = $measurement->units->base; $this->error('... error msg ...'); } } else { //... } if(!is_array($measurement->magnitude)) $measurement->magnitude = [$measurement->magnitude]; return $measurement; } getBlankValue(Page $page, Field $field) This should return an empty item of the appropriate type. For instance, if your field object is an array, just return array(); If the field type is an object then you will need to return a 'new ObjectClassName()'. Pre-fill any config values from the Fieldtype settings but leave blank those which are set in the Inputfield, In the above example, the field object data was set as follows: public function getBlankValue(Page $page, Field $field) { /* @var $field FieldtypeMeasurement */ $measurement = new Measurement($field->quantity, null, []); if ($field->quantity) $measurement->set('quantity', $field->quantity); $measurement->set('magnitude', []); $measurement->set('shortLabel', null); $measurement->set('plural', null); return $measurement; } If your object has configurable fields that can be modified according to context (as defined in getConfigAllowContext() - see below), then you will need to deal with this in getBlankValue too, e.g. : public function getBlankValue(Page $page, Field $field): Measurement { //NB Field details may differ between templates so we need to get the field in context $context = ($page && $page->id) ? $field->getContext($page->template) : $field; ... $measurement = new Measurement($context->quantity, null, []); ... return $measurement; } But note that this does not completely deal with the situation where the field is in repeater matrix items where the types might have different contexts - there you might need: public function getBlankValue(Page $page, Field $field): Measurement { //NB Field details may differ between templates so we need to get the field in context $context = ($page && $page->id) ? $field->getContext($page->template) : $field; ... // if($page instanceof RepeaterMatrixPage) { // This does not always work - see edit note if($page->template->pageClass == 'RepeaterMatrixPage') { if($page->getField($field->name)) { $context = $page->fieldgroup->getFieldContext($field, "matrix$page->repeater_matrix_type"); } } ... $measurement = new Measurement($context->quantity, null, []); ... return $measurement; } See this post for more details. Configuration ___getConfigInputfields(Field $field) This defines how the Details tab in the field setup page will look. The best thing to do here is to find a fieldtype module that is similar to the one you want if you are uncertain. Broadly the process is: define the config object - $inputfields = parent::___getConfigInputfields($field); for each config item, use the appropriate input field, e.g . $f = $this->modules->get("InputfieldSelect"); assign the relevant attributes. $f->name = $f_name; is important as it enables the item to be subsequently referred to as $field->f_name in, for example, getBlankValue(). append each item ($inputfields->append($f);) and return $inputfields; ___getConfigAllowContext(Field $field) This determines if the above input fields are allowed to have unique values per Fieldgroup assignment enabling the user to configure them independently per template in the admin, rather than sharing the same setting globally. E.g. public function ___getConfigAllowContext(Field $field) { $a = array('quantity', 'units', 'hide_quantity', 'show_update'); return array_merge(parent::___getConfigAllowContext($field), $a); } In this example the settings 'quantity', 'units', 'hide_quantity' and 'show_update' can be varied in different template contexts. Link with the Inputfield module This is done with getInputfield(Page $page, Field $field) e.g.: public function getInputfield(Page $page, Field $field) { $inputfield = $this->wire('modules')->get('InputfieldMeasurement'); $inputfield->setField($field); return $inputfield; } If you want to reference the current page in the inputfield, you will also need to include $inputfield->setPage($page); If your fieldtype is an object and you want full context flexibility including for different repeater matrix item types, then you may need to use this: public function getInputfield(Page $page, Field $field): Inputfield { $inputfield = $this->wire('modules')->get('InputfieldMeasurement'); if($page->template->pageClass == 'RepeaterMatrixPage') { if($page->getField($field->name)) { $field_in_context = $page->fieldgroup->getFieldContext($field, "matrix$page->repeater_matrix_type"); if($field_in_context) { $field = $field_in_context; } } } $inputfield->setField($field); $inputfield->setPage($page); return $inputfield; } Inputfield Module __construct() Generally not much is required here (apart from parent::__construct();). If you have a php script with the field object then include it (require_once() ). Configuration ___getConfigInputfields() This is pretty much exactly the same construction as the similar method in the Fieldtype class. The only difference is that these settings will appear in the 'input' tab of the fieldtype settings, rather than the 'details' tab. ___getConfigAllowContext(Field $field) This is the equivalent to the Fieldtype::getConfigAllowContext() method, but for the "Input" tab rather than the "Details" tab. Input and output The key methods for this module are to render it from the fieldtype and database and to process the user inputs. ___render() $field = $this->field will have the field config settings from the fieldtype module. $this->attr('value') will have the current values for the field. If there is no current values then, if using a field object, you will need to create a new object, e.g.: if($this->attr('value')) $value = $this->attr('value'); // Measurement object else { $value = new Measurement(); } You can then use $field and $value to display the inputfield (which might be a fieldset) as required using the appropriate pre-existing inputfield modules. (Again, find an existing module that is similar, if you are uncertain). renderValue() This is required where the field is locked (not editable) and therefore render() does not apply. Get the value with $value = $this->attr('value'); and then apply the required formatting, returning the output string. ___processInput(WireInputData $input) Here you take the inputs and update the field values. As in render(), set $value = $this->attr('value') ; and then modify $value for the inputs. For example, set $name = $this->attr('name'); and then assign the inputs thus: $input_names = array( 'magnitude' => "{$name}_magnitude", 'unit' => "{$name}_unit", 'quantity' => "{$name}_quantity", 'update' => "{$name}_update" ); You can then loop through the inputs and carry out the required updates. The example below is slightly convoluted but illustrates this: foreach($input_names as $key => $name) { if(isset($input->$name) && $value->$key != $input->$name) { if($key == 'magnitude') { $input->$name = trim($input->$name); if(!is_numeric($input->$name)) { $magnitude = explode('|', $input->$name); $magnitude = array_filter($magnitude, 'is_numeric'); $value->set($key, $magnitude); } else { $value->set($key, [$input->$name]); } } else { $value->set($key, $input->$name); } $this->trackChange('value'); } } When all is done, return $this; Custom classes As mentioned earlier, for complex field types it may be useful to set up custom classes to hold the data. Typically a custom class would extend WireData, which is ProcessWire's class designed for runtime data storage. It provides this primarily through the built-in get() and set() methods for getting and setting named properties to WireData objects. The most common example of a WireData object is Page, the type used for all pages in ProcessWire. Properties set to a WireData object can also be set or accessed directly, like $item->property or using array access like $item[$property]. If you foreach() a WireData object, the default behaviour is to iterate all of the properties/values present within it. Do not declare any such properties in your class (or declare properties with the same name) otherwise you will end up with two properties, one in the 'data' array and one outside it and endless confusion will result. It is advisable to put any such classes in your own namespace. In that case, you will need to include 'use' statements in your script - e.g. use ProcessWire\{FieldtypeMeasurement, WireData}; use function ProcessWire\{wire, __}; and also include use statements in your module scripts, e.g. use MetaTunes\MeasurementClasses\Measurement;
    2 points
  4. ProcessWire Dashboard Download You can find the latest release on Github. Documentation Check out the documentation to get started. This is where you'll find information about included panel types and configuration options. Custom Panels The goal was to make it simple to create custom panels. The easiest way to do that is to use the panel type template and have it render a file in your templates folder. This might be enough for 80% of all use cases. For anything more complex (FormBuilder submissions? Comments? Live chat?), you can add new panel types by creating modules that extend the DashboardPanel base class. Check out the documentation on custom panels or take a look at the HelloWorld panel to get started. I'm happy to merge any user-created modules into the main repo if they might be useful to more than a few people. Roadmap Panel types Google Analytics Draft At a glance / Page counter 404s Layout options Render multiple tabs per panel Chart panel load chart data from JS file (currently passed as PHP array)
    1 point
  5. PageMjmlToHtml Github: https://github.com/eprcstudio/PageMjmlToHtml Modules directory: https://processwire.com/modules/page-mjml-to-html/ A module allowing you to write your Processwire template using MJML and get a converted HTML output using MJML API. About Created by Mailjet, MJML is a markup language making it a breeze to create newsletters displayed consistently across all email clients. Write your template using MJML combined with Processwire’s API and this module will automatically convert your code into a working newsletter thanks to their free-to-use Rest API. Prerequisite For this module to work you will need to get an API key and paste it in the module’s configuration. Usage Once your credentials are validated, select the template(s) in which you’re using the MJML syntax, save and go visualize your page(s) to see if everything’s good. You will either get error/warning messages or your email properly formatted and ready-to-go. From there you can copy/paste the raw generated code in an external mailing service or distribute your newsletter using ProMailer. Features The MJML output is cached to avoid repetitive API calls Not cached if there are errors/warnings Cleared if the page is saved Cleared if the template file has been modified A simple (dumb?) code viewer highlights lines with errors/warnings A button is added to quickly copy the raw code of the generated newsletter Not added if the page is rendered outside of a PageView Only visible to users with the page’s edit permission A shortcut is also added under “View” in the edit page to open the raw code in a new tab Multi-languages support Notes The code viewer is only shown to superusers. If there’s an error the page will display: Only its title for guests Its title and a message inviting to contact the administrator for editors If you are using the markup regions output strategy, it might be best to not append files to preserve your MJML markup before calling the MJML API. This option is available in the module’s settings. If your layout looks weird somehow, try disabling the minification in the options.
    1 point
  6. Like last week, this week, updates continued on the core and matrix repeater fields. Repeater and matrix fields can now be configured to use fewer pages. When set, it won't create placeholder pages for repeater items until at least one repeater item exists for a given page and field. This can drastically reduce the number of pages consumed by repeaters in your system, and even more so if you are nesting repeaters. Eventually, this will become the default setting, but for now we are playing it safe and making it optional with a new toggle that you'll find on the Details tab when editing a repeater or matrix field: After enabling the "Use fewer pages..." Setting, the "Find an optionally delete unnecessary pages" checkbox will take care of cleaning up anything that isn't necessary for existing repeaters already in the database. If you have a large site with a lot of repeaters, this could be deleting a lot of now irrelevant stuff, so just be aware of that and backup ahead of time to be safe. Thanks to @Jonathan Lahijani for the idea/suggestion. Also new this week is the ability to copy and paste repeater items, as well as to clone above or below existing items. It handles this by replacing the existing "clone" icon action with a dialog that now lets you choose among various related actions. Among them is the ability to copy/paste from the same page or between different pages. The only requirement is that the repeater (or matrix) items are from the same field. See the video below for an example of how this works: This works with either Repeater or Repeater Matrix fields. But if you want this feature in Repeater Matrix, you'll want to upgrade to ProcessWire 3.0.188 and download the new version posted today (v8 beta) in the ProFields download thread. The ability to copy/paste repeater items was an idea originally from @David Karich and a module he developed called Repeater Matrix Item Duplicator. Thanks for reading and have a great weekend!
    1 point
  7. @bernhard I am not using or maintaining this module anymore, so I really don't know if it works. Best way to be sure is to try it out. Sureley Hybrid Auth should be updated to latest version.
    1 point
  8. Thank you so much for this post. This should be considered for the official documentation. It would have saved me a lot of time and headache when developing my first custom fieldtype
    1 point
  9. I totally agree and therefore looked through threads but... no chance so far. Don't know if it's pwlink (CKEditor module from PW) or CKEditor itself that needs to be changed... maybe @ryan can clarify and give us an update. I guess and bet Ryan could clarify this or can somehow resolve this and the issue @teppo mentioned.
    1 point
  10. This whole discussion points to a larger issue: There is no way to visually select a page you want to link to in another language (without diving into the source code or manually editing the link), is there? A simple use case is for a client to be able to link to a page in another language from a CKeditor field. I have to admit that I've never had the need so far in the 9+ years I've been using PW, but it seems like it should be an option, even if it's hidden by default. What do you guys think?
    1 point
  11. This is a great addition and idea! @Jan Romero Fool-proof or advanced settings to make it more secure and more awesome. A point I didn't see due to so many instances I installed and configured. Maybe we could even open a thread to collect solutions based on hosting companies to get some idea about how different providers need different settings within .htaccess or whatever. Just an idea right now but I know that I had to tinker around with things in STRATO (DE, hosting company) a lot a few years back and even some hostings (I won't name names here) I moved away from because it didn't work out at all.
    1 point
  12. The link abstraction part sounds a lot like https://github.com/processwire/processwire-issues/issues/1244 to me. Just cross-linking for reference.
    1 point
  13. Right, so… We have a couple of different problems here. The template label thing is just a simple programming mistake. I’m sure you can figure it out by looking at the last three lines again. Hints: 1. on $templates->get() 2. on API access in general (you can’t use $templates inside a function. This is an unfortunate ProcessWire quirk that makes things unnecessarily difficult for beginners. You just have to be aware of it OR simply use the functions API everywhere) Now the ProcessPageAdd::buildForm hook. Let’s look again at what we’re hooking and what we want to accomplish. Hooking after ProcessPageAdd::buildForm means that the hook code is being executed right after the Page Add Screen is generated. That is to say, the form that asks the user for the template they wish to instantiate under the given parent, if you will. This means that that code cannot know the user’s template selection. It can however know the parent page, i.e. the page of which the new page is going to be a child. So anything you do in the hook either happens in EVERY Page Add Screen, or you can make it depend on that parent page (which I called $parent in my example). It looks to me like you’re trying to skip the hook depending on the child template – this isn’t possible because that template has not been determined yet. If I understand the requirements right, you may want to do this: $this->addHookAfter("ProcessPageAdd::buildForm", function(HookEvent $event) { $form = $event->return; $parentField = $form->getChildByName('parent_id'); $parent = pages()->get($parentField->value); if ($parent->template->name === 'dashboard_customer') return; $nameField = $form->getChildByName('_pw_page_name'); $nameField->wrapClass = 'InputfieldHidden'; $nameField->value = 'Newpage'; }); Notice how I’m not using matching(). That function takes a selector string, so if you want to use it to test for a page’s template, you can probably do $page->matches('template=dashboard_customer'), but not $page->matches('dashboard_customer')! Now, if you must make the default page name depend on the selected template and also want to hide the name field from users, you have several options: Make the name field hidden the way we’ve been doing it and inject some Javascript to change its value whenever the selected template changes. Here you will still want to run the hooks only for some parent page where it makes sense. Adding the Javascript in ProcessPageAdd::buildForm could look like this: $form->appendMarkup .= '<script> const templateSelect = document.getElementById("template"); templateSelect.addEventListener("change", (e) => { const nameInput = document.getElementById("Inputfield__pw_page_name"); nameInput.value = e.target.options[e.target.selectedIndex].innerHTML; //set the name to the selected template’s label }); templateSelect.dispatchEvent(new Event("change")); //run the above function immediataly to capture the default value </script>'; Alternatively, hook ProcessPageAdd::processInput and determine the page name there. That’s the method that runs when you click „Save“ on the Page Add Screen, so at that point you’re committed to the selected template and parent. Still need to hook ProcessPageAdd::buildForm to make the name field disappear, though.
    1 point
  14. Fully agree on this.
    1 point
  15. As of today's latest commit to the dev branch, the solution to the original question of sorting results by a specific order of page IDs is now possible. // get page 2, then 1, then 3 in that specific order using the new 'id.sort'; you can also use "limit" for pagination without any downsides $pages->find("id.sort=2|1|3"); More info: https://github.com/processwire/processwire-issues/issues/1477#issuecomment-982928688
    1 point
  16. Finally I got my local setup switched today. PHP 8 which needed a new apache version too, and then SSL needs to be updated, etc., etc., ... Now I need to upgrade my IDE for PHP 8 support too. ? After all I could debug and correct the WireMail-SMTP module: https://github.com/horst-n/WireMailSmtp
    1 point
  17. Hi Louis, 1- In the form, change the name of the input : <!-- from --> <input type="file" id="preview-name" name="preview-name"> <!-- to --> <input type="file" id="preview_name" name="preview_name"> 2- In your JS code, use FormData() and add the contentType prop to the $.ajax call (that's the key here) : $('#submit-preview').click(function(e) { e.preventDefault(); title = $("#preview").val(); image = $('input[name=preview_name]').prop('files')[0]; // modified form_data = new FormData(); // added form_data.append('preview_name', image); // added form_data.append('title', title); // added console.log(title); console.log(image); $.ajax({ type: 'POST', data: form_data, url: '/development/upload-preview/', contentType : false, // added processData : false, // added success: function(data) { console.log("Woo"); }, error: function(xhr, ajaxOptions, thrownError) { alert(xhr.responseText); } }); }); 3- The ajax template file look good. The main reason that WireUpload doesn't work here, is because it couldn't find the right field name (form input name) : $u = new WireUpload('preview_image');
    1 point
  18. Announcement: I've created a new branch on github with the requested feature by @felix. Please can you and / or anybody else try this out and give some feedback. If everything is working as expected, I will push this to the master branch. Short introduction: You can specify how many and what ever params you want into an array that is called $config->wirmailsmtp. Every valid key of this array will override the key of the stored modules config setting. // example entry in site/config.php $config->wiremailsmtp = array( "smtp_host" => "smtp.example.com", "smtp_port" => 587, "smtp_ssl" => 0, "smtp_start_tls" => 1, "smtp_user" => "yourusersname", "smtp_password" => "youruserspassword", "extra_headers" => array("Organization" => "Horst Nogajski - Fotografie & Webdesign", "X-Header" => "Some Content") ); To see your resulting (merged) settings you can var_dump the output of the method getSettings(): // debug example in a template file $mail = wireMail(); echo "<pre>"; var_dump($mail->getSettings()); I tested it here myself and it seems to work fine. EDIT: I forgott to mention that I removed the required flags from the modules config settings for smtp_host and smtp_port. This way, both settings now may stay empty in the config screen, but can be set via the $config->wiremailsmtp array. The downside would be that the modules config screen isn't that robust anymore in regard of misconfiguration. Is this acceptable, or should there also be a required setting in the modules config screen? This is open for discussion. ?
    1 point
  19. Done too! Its updated to 0.2.7. All in all we only used 7 minutes to react on Charles issue report! Cool!
    1 point
×
×
  • Create New...