Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 11/30/2021 in all areas

  1. I'm a little late to the game on this but wanted to throw out what we use on our PW sites. We keep all credentials in .env files in the root directory and then loaded using a package called phpdotenv. The .env file is loaded and then the credentials can be accessed at runtime via the $_ENV global. Some further details on this setup: .env files are automatically protected system files in Apache and will not be served (unless someone actively overrides this, but that would be bad). These files are not added to the Git repository and never stored. We have .env files for local, staging, and production. The contents of each file are stored in whole as a secure note in our password manager. The dotenv library is loaded once in config.php and the values are available globally. We also store credentials for external APIs and services. This allows us to store not only credentials, but any values that differ between local/staging/production. We store all of our config.php variable values in .env so that the config.php file is environment agnostic and we can always be sure that the .env is the single source of truth for the entire CMS configuration. We use Git to deploy to staging/production servers so using .env files allows us to push all of our code while knowing that sensitive information and data that changes between environments never gets mixed up. Also makes it very clear to anyone looking at the code that these values are stored in a dedicated system dot file. Here's the package, can be installed with composer https://github.com/vlucas/phpdotenv This is what a config.php file looks like with it in use: <?php namespace ProcessWire; // Load env variables from .env in root directory $dotenv = \Dotenv\Dotenv::createImmutable(__DIR__ . '/../'); $dotenv->load(); $config->debug = filter_var($_ENV['CMS_DEBUG'], FILTER_VALIDATE_BOOLEAN); $config->usePageClasses = filter_var($_ENV['CMS_USE_PAGE_CLASSES'], FILTER_VALIDATE_BOOLEAN); $config->useFunctionsAPI = filter_var($_ENV['CMS_USE_FUNCTIONS_API'], FILTER_VALIDATE_BOOLEAN); /** * Database Configuration */ $config->dbHost = $_ENV['CMS_DB_HOST']; $config->dbName = $_ENV['CMS_DB_NAME']; $config->dbUser = $_ENV['CMS_DB_USER']; $config->dbPass = $_ENV['CMS_DB_PASS']; $config->dbPort = $_ENV['CMS_DB_PORT']; $config->dbEngine = $_ENV['CMS_DB_ENGINE']; // Etc... Hope this might be useful to someone!
    5 points
  2. Hi all, we're a public research institute and in the future we cannot have CMS clients anymore (there can be legal issues in the future). That's why we'd like to transfer our client to a Processwire developer. We could transfer you the whole virtual machine (linux, apache). The client is super nice and on the technical site it is like 5 minor issues (making a new form, place a button here and there). Facts: 1. Client based in Germany 2. Processwire is used (3.0.123) 3. One content manager takes care of all the texts, which she gets from the client. That's a couple of hours a week. 4. Certain users (there is no registration) have an account where they can download certain pdfs. So this is an "exclusive area". 5. Users can forget passwords and re-create it on their own. 6. Bootstrap is used If you have any questions, let me know. Best, Marcel
    2 points
  3. It's not exactly the case. {variable} will return anything matching the route and will populate the value $e->variable accordingly, whereas (variable:value) will make sure `variable` is equal to `value` (which can be a regex) before making it accessible as $e->variable. There's an example where there is /route/(variable1|variable2|variable3) but in this case it will be accessible through $e->arguments(1) and will have to match either of the three values. At first this confused me as well but re-reading I think we misunderstood what Ryan meant: he's not talking about the regex part of (variable:regex) but rather making the whole hook a regex, as in: $wire->addHook("#/(event|person)/([[:alnum:]]{15})#", function($e) { $type = $e->arguments(1); $id = $e->arguments(2); return "Looking up data for ID $id of type $type"; }); That's why he mentioned that "...PW converts your match path to a regular expression (if it isn't one already)..."
    2 points
  4. if the DocumentRoot is now set to /var/www/html/pwire, there's no need to set the RewriteBase to the subdirectory anymore. Either leave them all commented, or uncomment only the first one: "RewriteBase /"
    2 points
  5. Your regex looks strangely complicated for what you're trying to achieve. I got it working using: <?php namespace ProcessWire; $wire->addHook('/api/v1/offline-events/(eventId:[[:alnum:]]{15})', function($e) { $e->return = "Hello $e->eventId"; }); As for the 404s, if you're not returning anything it will result in a pageNotFound() call you could hook after to check the url and execute code accordingly: $wire->addHookAfter('ProcessPageView::pageNotFound', function($e) { $url = $e->arguments(1); if(strpos($url, "/api") === 0) { $e->return = "API 404"; } });
    2 points
  6. Not sure if you read my last post and why I asked what I asked but nontheless I think you could try the following code on ready.php. Have in mind, the following function declared in the saved() hook executes after the page is saved, which would be my first approach in getting the gecoded location without doing all the pages in one request . So this approach sort of "throttles" geocoding the address, doing it only when the user adds the address. $wire->addHookAfter('Pages::saved', function(HookEvent $event) { $page = $event->arguments(0); // Skip if not the template we are interested in.. if($page->template != 'customer_project') return; $geo = new \OpenCage\Geocoder\Geocoder('API-KEY'); //This is the field that contains the address in the pages with customer_project template $address = $page->address_field; if ($page->customer_project_address === 1 && $address){ // I guess the first param is the actual address? $address assigned above? $result = $geo->geocode("Another location", ['language' => 'Sweden', 'countrycode' => '+46']); $first = $result['results'][0]; $coordinates = $first['geometry']['lat'] . ',' . $first['geometry']['lng']; //here the field gets saved, have in mind that this is different than saving //the whole page, which would get is into an infinite loop in this scenario $page->setAndSave('my_location_field', $coordinates); } });
    2 points
  7. Use this site, to generate all needed favicons and also the needed HTML code: Favicon Generator for perfect icons on all browsers (realfavicongenerator.net)
    2 points
  8. 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;
    1 point
  9. @monollonom Good observations on that. I think the regex section could use an example like the others on that page. I just wasn't able to visualize that. Many many thanks for your assistance an insight on this!
    1 point
  10. @dotnetic I picked it up after developing our API in Slim and looking at some best practices. It would be really great if this was the ProcessWire default.
    1 point
  11. I think this should be the default for credentials in ProcessWire. Many other CMS or Frameworks like Laravel or Statamic (which is an Laravel application) use this method.
    1 point
  12. 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
  13. sorry for getting back to you guys this late and thanks for all your input. Using ImageSizer directly sounds pretty handy - although I have to tell you, that for my particular case, I have decided to just create a hidden page which acts as a container. So exactly the opposite what I was looking for, but in the end this approach turned out to be the most efficient one 😉 Anyways: Thanks a lot!
    1 point
  14. (re: this, I'm no master and just religiously use https://regex101.com/ or stackoverflow in desperate cases)
    1 point
  15. @monollonom HECK YEAH! If there's anything that demonstrates my complete lack of regex abilities... it's this post. Excellent 404 fix. First time using the URL hooks and didn't know if there was a "URL Hook way" of handling.
    1 point
  16. Hi @Juergen Try to set current user language to the langauge that are set for user to which you are sending mail like protected function sendActivationLinkEmail(User $user): bool { $savedLanguage = $this->wire('user')->language; $this->wire('user')->language = $user->language; // send an email with the activation link to the user // but check first if activation code (fl_activation) exists inside the database if($user->fl_activation){ $m = wireMail(); $m->to($user->email); $m->from($this->input_email); $m->subject($this->_('Action required to activate your account')); $m->body($this->createActivationLinkEmail($user)); $m->mailTemplate($this->input_emailTemplate); return $m->send(); } $this->wire('user')->language = $savedLanguage; return false; }
    1 point
  17. The main PW .htaccess file is the one in your /pwire/ directory. The .htaccess file in /pwire/site/ is, as I understand it, just there to protect certain files if the main .htaccess is missing – and though I haven't tried, I would guess the PW installation would work without it.
    1 point
  18. And it seems to work perfectly using $event->cancelHooks! Thanks @Robin S for the pointer and for clarifying about replace, reading again from the doc it does say that it replaces the hookable function but it doesn't mean it will stop the functions hooked after.
    1 point
  19. Your default apache config file (000-default.conf) should look similar to this... <VirtualHost *:80> #ServerName www.example.com ServerAdmin webmaster@localhost DocumentRoot /var/www <Directory /var/www/> Options -Indexes +FollowSymLinks -MultiViews AllowOverride All Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> The file you posted is missing elements and has elements assigned incorrectly. The DocumentRoot value should be the location where PW was installed (/var/www/html/pwire). Note there is no slash at the end. Your file is missing the <Directory> element. This value should be the location where PW was installed -- Including the trailing slash. Read through this... apache docs
    1 point
  20. I don't think $event->replace = true is supposed to have any effect on whether an "after" hook fires for a method - it just means that the code inside the hooked method will not execute. If you're trying to stop subsequent hooks from firing then have a look at $event->cancelHooks (https://processwire.com/api/ref/hook-event/)
    1 point
  21. That should be happening if you put it next to ProcessWire’s index.php unless the server refuses access or something. Look at the browser console. It probably shows an error trying to load https://www.example.com/favicon.ico. But you can also put it anywhere and add a link tag in your head, such as: <link rel="icon" type="image/x-icon" href="<?php echo $config->urls->templates?>style/favicon.ico"/>
    1 point
  22. Would it work if after one of the following events: User visits page (literally opens up the browser in the page) Page save (whoever saves the address, hits Save in the admin, or the page is saved through the api) The location lat, lng would get added and saved to the page's field through the API?
    1 point
  23. Hi @Bike There is Textformatter type of modules that are applicable in such cases. You can take a look at this module https://github.com/Toutouwai/TextformatterProcessImages from @Robin S Probably it can suit your needs or at least you can use it as a staring point.
    1 point
  24. ProcessWire is an excellent framework for building just about anything, with great tools for permissions, input fields, and creating data structures. With the Combo and Table profields, it's possible to handle just about any data. These fieldtypes show the potential of ProcessWire to handle SQL tables in a more 'traditional' SQL table format, which leads to one area I wonder might have potential for a new feature. In addition to ProcessWire, I also do some work with ASP.Net Core, and one of the nice features it has is the ability to work with existing SQL databases, so that you can either build your models in the framework, or use existing SQL Tables. While working with any SQL data is possible in ProcessWire, it's a lot more hands on, with less built-in support. InputFields and FieldTypes are separate, and there's no reason why an inputField can't be used for data input for any kind of data, and indeed there are some modules that use inputFields to edit custom table data. What would be an amazingly useful addition to ProcessWire would be a module that allows mapping of inputFields to fields in an arbitrary SQL table without modifying the structure of the table itself. This is something the open source Directus project does by using metadata tables to store data about to display input for existing SQL tables, without touching their structure, but documentation isn't great for that project. I think ProcessWire has all the plumbing in place to enable this kind of functionality, and the Combo fieldtype gets pretty close, but still requires the combo table records to be associated with a ProcessWire page. Obviously, existing SQL tables won't automatically map to pages, which means directly accessing them on the front-end via URL automatically isn't possible, but often this isn't required as they may be for backend use only, or for consumption within a ProcessWire page. The recent support for URL/Path hooks though, means that if there is a need to directly access existing SQL on the front end via URL, it should be possible to create a hook to do so. I think adding the ability to create backend data entry for existing SQL tables would fully complete the ProcessWire philosophy of getting out of your way and not making assumptions about your content. It's currently already best of class in terms of doing this regard to front end presentation, but it's still a bit opinionated about data structures at the back end. This is absolutely fine if you're building a web app from scratch, and works really well in most cases, but there are times where being able to quickly incorporate existing data structures would be useful. To be clear, this isn't a replacement for the existing pages model, as that would be a huge and unecessary task to completely re-engineer ProcessWire, but rather an enhancement that can sit alongside all the good stuff that's already in ProcessWire so that it's possible to get ProcessWire to handle existing SQL data tables on the backend as neatly as it already does with its own data.
    1 point
  25. This is a holiday week here in the US, at least for my kids (school break). With kids home I've been off work this week so don't have any ProcessWire updates to report, but I'm looking forward to getting back to work next week and will have more updates to report then. Yesterday was Thanksgiving here in the the US and that kind of marks the beginning of the holiday season here. So today the Christmas tree went up, the lights are coming out and the holiday music has taken over the radio (or Alexa or Google or whatever we call it). Cheers and Happy Holidays!
    1 point
  26. Confirming that the expressions of interest is now closed. I will collate the responses and get back to all who registered. I am working on a getting started frontend that will help with the testing. I am also working on a minimal API documentation to help you put it all together. Thanks to all who have responded and/or have an intention to support this work in the future 🙂.
    1 point
  27. You can set your own path to your data directory 😉 https://processwire.com/api/ref/paths/ or you can use the root directory: $urls->root Site root: / (or subdirectory if site not at domain root)
    1 point
  28. Template file contains: $quantity = 'testing'; $data = include_once '/Users/ajones/Sites/ecoreportcard/test.php'; $this->session->set($quantity . '_data', $data); and test.php contains: return array( ///////Units Of Temperature/////// "dimension" => 'dfg', 'base' => 'Kelvin', 'units' => array( "Kelvin" => array("shortLabel" => "K", "conversion" => 1, "plural" => "Kelvin"), //Kelvin - base unit for temperature "Celsius" => array("shortLabel" => "degC", "conversion" => function ($val, $tofrom) { return $tofrom ? $val - 273.15 : $val + 273.15; }, "plural" => "Celsius"), "Fahrenheit" => array("shortLabel" => "degF", "conversion" => function ($val, $tofrom) { return $tofrom ? ($val * 9 / 5 - 459.67) : (($val + 459.67) * 5 / 9); }, "plural" => "Fahrenheit"), ) ); and the result when viewing a page with that template is: Fatal error: Uncaught Exception: Serialization of 'Closure' is not allowed in [no active file]:0 Stack trace: #0 {main} thrown in [no active file] on line 0 I think it really is a simple matter of what the error says. You aren't allowed to serialize closures (and there are two of them in test.php) and saving to $_SESSION serializes the data. There are lots of topics discussing it: https://www.google.com/search?q=php+session+"Serialization+of+'Closure'+is+not+allowed"
    1 point
×
×
  • Create New...