Leaderboard
Popular Content
Showing content with the highest reputation on 01/26/2024 in all areas
-
I hope you all are having a great week! I don't have much to report in terms of core updates this week, just a few commits to the dev branch, but with more on the way next week. I'm in the process of wrapping up a client project this and next week, so have had to dedicate more time to that work this week. Though I'll be back to more major core updates and Pro module updates after next week. Have a great weekend!6 points
-
@overoon The bug as I described it above is addressed in the latest commit to the dev branch on Github. Please download and confirm this solves the issue! Alternate temporary solution: I was able to prevent this issue from happening by changing "Repeater dynamic loading (AJAX) in editor" to "New items only" in the settings for the field. This is a temporary fix while I work on this so you don't have to downgrade. Let me know if that helps.3 points
-
I received a suggestion from @monollonom that it would make more sense if the module appeared under the Access menu rather than the Setup menu. I agree so I've changed to this in v0.1.5. If you've installed < v0.1.5 and you'd like to have the module under Access then there a couple of ways to do this. First update to v0.1.5 and then either... Uninstall the module, and then reinstall the module so the page will be automatically created under Access during install. Or move the "Template Access" page within the Admin branch from under Setup to be under Access.3 points
-
Fluency v1.0.7 is now available. Recommended for all users. New features, bugfixes, and all of the features mentioned above are in this release: Translation buttons can be switched between the original translate from the default language, translate to all languages (from any language), or both Casting the result of a translation to a string or echoing it directly will output the first translated value (or only value if you are only translating one string) Improvements to the standalone translation tool Fixes a PHP 8.2 compatibility issue Fixes translating content containing links to ProcessWire pages Fixes an issue where API usage may be above normal in some instances (sorry) Fixes translating page URLs when creating new pages Early release Fluency API feature where you can translate any file, or multiple files, in site/ or wire/ in one method call. This will be updated with a new feature in the future, subject to change but useful now! Bunch of other stuff that makes Fluency run better but you don't have to worry about Big thanks to @bernhard and @ryangorley for their feedback and bug reports, release notes on Github credit their contributions!3 points
-
I would advice all you that the geocoder API is no longer accessible in the format of the current module. I have an error geocoding address and the return message from nominatim.openstreetmap is: Using the URL /search/ and /reverse/ (with slashes) is no longer supported. Change url from /search/?q=Berlin in /search?q=Berlin Also complete addresses are changed, see below: File not found: API no longer accessible via this URL Using the URL /search/ and /reverse/ (with slashes) is no longer supported. Please use URLs as given in the documentation. Examples how to change the URL: You use: https://nominatim.openstreetmap.org/search/?q=Berlin Change to: https://nominatim.openstreetmap.org/search?q=Berlin You use: https://nominatim.openstreetmap.org/search/US/Texas/Huston Change to: https://nominatim.openstreetmap.org/search?q=Huston, Texas, US See github issue #3134 for more details. See github issue #3134 for more details. Maybe editing ControlGeocoder.js fixes partially the problem? :343 L.Control.Geocoder.jsonp(this.options.serviceUrl + 'search/', L.extend({ change in L.Control.Geocoder.jsonp(this.options.serviceUrl + 'search', L.extend({ :370 L.Control.Geocoder.jsonp(this.options.serviceUrl + 'reverse/', L.extend({ change in L.Control.Geocoder.jsonp(this.options.serviceUrl + 'reverse', L.extend({ I have tested with simple addresses (italian street, number and city) ad it works fine again, both search and reverse. Posted a commit on Github: https://github.com/FriendsOfProcessWire/FieldtypeLeafletMapMarker/commit/63254dffea9bd504eb45b124d64fd5832e38f0133 points
-
Template Access A Process module that provides an editable overview of roles that can access each template. The module makes it quick and easy to see which templates have access control enabled (personally I like to ensure I've enabled it for every template) and to set the appropriate access per template. Usage The Template Access page under the Access menu shows access information for all non-system templates in a table. You can filter the table rows by template name if needed. Click an icon in the table to toggle its state. The changes are applied once you click the "Save" button. Sometimes icons cannot be toggled because of logical rules described below. When an icon cannot be toggled the cursor changes to "not-allowed" when the icon is hovered and if the icon is clicked an explanatory alert appears. A role must have edit access before it can be granted create access. If the guest role has view access for a template then all roles have view access for that template. https://github.com/Toutouwai/ProcessTemplateAccess https://processwire.com/modules/process-template-access/2 points
-
Hi @FireWire and thanks for the blazingly fast reply! I can confirm all of your findings and also: the fix on your dev branch resolves the issue ?2 points
-
View at: https://okeowoaderemi.com I switched to Processwire 10 years ago and it's being my goto framework for maintaining my website and also developing for clients. Development I used the moduleTemplateEngineFactory and TemplateEngineTwig, Twig has always been my favourite templating engine, because of how easy it is, to use reusable code and have an easy way to inject content into the layout, it's almost like MasterPages in .NET (For the Oldies)1 point
-
FieldtypeGrapick The FieldtypeGrapick module for ProcessWire wraps the Grapick vanilla javascript gradient creation UI and extends the feature set to include settings beyond what the original library allowed for. The original javascript library was written by Artur Arseniev. Aside from requiring ProcessWire 3, the requirements are: PHP >= 7.2 or >= PHP 8.0 This module makes use of the Spectrum colorpicker library. Repeater and RepeaterMatrix items are supported. There is a gremlin in RepeaterPageArray iteration that causes warnings. I've created an issue for it. It does not impact performance. CssGradient Object The FieldtypeGrapick field value is a CssGradient object. $gradient = new CssGradient( $options=[] ); where $options is a list of properties: $options = [ 'style' => 'linear', 'stops' => 'FFFFFFFF^0|FF000000^100', 'angle' => '180', 'origin' => '', 'size' => '', ]; Properties The CssGradient style by default is linear, 180deg, with a white opaque stop at 0% and a black opaque stop at 100%. style $gradient->style: gives you the dropdown value of the style of gradient. Setting this automatically uses the correct settings for the css function and shape parameter as required. Possible values: 'linear' = Linear 'radial-circle' = Radial Circle 'radial-ellipse' = Radial Ellipse 'repeating-linear' = Repeating Linear 'repeating-radial-circle' = Repeating Circle 'repeating-radial-ellipse' = Repeating Ellipse 'conical' = Conical 'repeating-conical' = Repeating Conical Any other value defaults to linear. Depending on the type of gradient selected, origin, angle, and/or size will come into play. The stops are always used to determine the order of colors and their relative locations according to the limitations of each style. origin $gradient->origin: gives you the dropdown value of the origin of the gradient as it applies to radial and conical gradients. The format is X%_Y% if for some reason you want to set a custom X/Y origin. The dropdown values are typically what I find useful for most applications, but I am open to adding other presets. '-100%_-100%' = Far Top Left '50%_-100%' = Far Top Center '200%_-100%' = Far Top Right '-50%_-50%' = Near Top Left '50%_-50%' = Near Top Center '150%_-50%' = Near Top Right 'top_left' = Top Left 'top_center' = Top Center 'top_right' = Top Right '-100%_50%' = Far Middle Left '-50%_50%' = Near Middle Left 'center_left' = Middle Left 'center_center' = Center 'center_right' = Middle Right '150%_50%' = Near Middle Right '200%_50%' = Far Middle Right 'bottom_left' = Bottom Left 'bottom_center' = Bottom Center 'bottom_right' = Bottom Right '-50%_150%' = Near Bottom Left '50%_150%' = Near Bottom Center '150%_150%' = Near Bottom Right '-100%_200%' = Far Bottom Left '50%_200%' = Far Bottom Center '200%_200%' = Far Bottom Right angle $gradient->angle: gives you the angle in degrees of the gradient as it applies to conical and linear gradients. Should be a value between -360 and 360. Measured in degrees. size $gradient->size: gives you the size - of what depends on the type of gradient. For radial ellipse gradients, at applies a size of XX% YY% using the value. So 25 would represent a size of 25% width, 25% height of the container. For repeating linear, conical and radial gradients, the repeating gradient will apply the percentage stops as a percentage of this value. In the case of repeating linear gradients, if you have your stops at 0%, 10%, 50% and 100% and your size is 200, the stops in the calculated rule will be at 0px, 20px, 100px and 200px. For repeating ellipse and conical gradients, a similar calculation is performed, but the units are %, not px. You can get some crazy tartan backgrounds out of this if you stack your gradients up and are creative with transparencies. stops $gradient->stops: gives you the current stop settings in a AARRGGBB^%% format, with each stop separated by a '|' character on a single line. You can role your own gradient ruleset by modiying this property prior to getting the rule. When using the UI you can also reveal the Stops inputfield and change the stops manually, however you will need to save to see the changes in the UI. rule $gradient->rule: gives you the stored rule that is calculated prior to the field value being saved to the database from the UI. Of course, if you are ignoring the UI altogether and just using the class, you will probably ALWAYS want to call getRule() rather than use this property. If you render the field, it will return the rule as a string. Methods The CssGradient has a single public method that is mostly useful when manipulating an instance of the CssGradient class. getRule(string $delimiter) $gradient->getRule(): calculates the rule based on the properties or options you have set for the field. This automatically runs if you have set an $options array and populate the rule property, but if you decide later that you need to change the properties of the object, you'll want to manually call it again to recalculate it. For example, if you programmatically change the stops, you will want to run the getRule() method rather than just grab the rule property. If you pass a string, the first character will be used as an ending delimiter. If you pass an empty string, no ending delimited will appear. By default, the rule is output with a semicolon. Grapick UI The Grapick UI is relatively straightforward. Clicking the (x) handle above a gradient stop removes the stop from the gradient calculation. If you remove all the stops, the bar is transparent. Clicking on the gradient bar sets a stop conveniently set to the color you click on. Clicking on the colorpicker box below the stop line allows you to select the color and transparency of the stop. Click and drag the stop line itself to modify the gradient. Making changes to any of the controls on the field will update the preview and the calculated rule in real-time. You can always cut and paste this rule and use it in your designs elsewhere if you want. Likewise, if you open the Stops inputfield area (which is collapsed by default) you can directly alter the colors and code using a color in an AARRGGBB format and adjust the stop with a number from 0-100. It's fun to play with - experiment with hard and soft lines, size and origin - many interesting effects are possible. Do not forget that the alpha slider is also available. Examples $grk = new CssGradient($options=[ 'style' => 'linear', 'origin' => '', 'angle' => 270, 'stops' => 'FF8345E4^0|FF5A08DB^25|FF2C046B^97|FF000000^100', 'size' => '', ]); echo $grk->getRule(); will give you: linear-gradient(270deg, rgba(131, 69, 228, 1) 0%, rgba(90, 8, 219, 1) 25%, rgba(44, 4, 107, 1) 97%, rgba(0, 0, 0, 1) 100%); while echo $grk->getRule(''); will give you linear-gradient(270deg, rgba(131, 69, 228, 1) 0%, rgba(90, 8, 219, 1) 25%, rgba(44, 4, 107, 1) 97%, rgba(0, 0, 0, 1) 100%) and echo $grk->gerRule(','); will give you linear-gradient(270deg, rgba(131, 69, 228, 1) 0%, rgba(90, 8, 219, 1) 25%, rgba(44, 4, 107, 1) 97%, rgba(0, 0, 0, 1) 100%),1 point
-
I think that's a lot clearer1 point
-
I'm working on improving the RockPageBuilder config page and added toggle buttons instead of checkboxes as they are a lot more intuitive (especially if you have default=yes toggles, where you'd need to use a negative meaning with checkboxes, eg "dont use the widgets feature" vs. "use widgets feature yes/no" The default toggle looks like this in ProcessWire: There it is not really obvious which value is selected. Is the grey one the active item or the shiny white one? I think this is much better: So I'm wondering if that should be a global default for toggles in AdminStyleRock - what do you think?1 point
-
Hi, i'm running into an issue when working with repeater matrix (pro fields), which wasnt a problem using the "original" fluency. i didnt find a related issue, so i thought i might as well report as it also persists in 1.0.7 ? Textareas (body field) within a repeater matrix do not receive the controls for translating, whilst regular inputfields still do. The console log reads: Uncaught TypeError: this.getEditorInstanceForLanguage(...) is undefined here a screenshot of the interface; the error is thrown when the repeater matrix expand is triggered/the field initialized. textarea fields outside of a repeater matrix are fully functional as intended. i wasnt really able to dig into it yet, as i will probably resort to the old version to get the site live, but let me know if i can be of any further assistance ?1 point
-
First of all: Great module, I really like to have an overview, because I got many templates (20) with access roles Improvement suggestion: I would like it even more if it just modifies the standard template overview instead of adding a new one. What do you think of that?1 point
-
Ok... AI did indeed help with this problem again! I told cursor "@[link to this topic] can you help bernhard?" and it gave me instructions what I can do and check. So it told me to place a file in /.well-known/foo.txt to check if my rules work. That was a great "idea" because I was able to check the rules without issuing a cert all the time. I also had a look at PW's .htaccess file and looked how it is done there. Mixing everything together with some trial&error brought me to the working solution: RewriteEngine on # don't proxy requests to the .well-known folder RewriteCond %{REQUEST_URI} ^/\.well-known/.* RewriteRule ^ - [L] # send other requests to uptime kuma RewriteCond %{HTTP:Upgrade} =websocket RewriteRule /(.*) ws://localhost:3001/$1 [P,L] RewriteCond %{HTTP:Upgrade} !=websocket RewriteRule /(.*) http://localhost:3001/$1 [P,L] Maybe it helps someone else ?1 point
-
This is my approach to combining CSS files for improved page speed. The implementation is straightforward; you only need to add one PHP file to your server and make a few tweaks. My setup: -site/ -templates/ - inc/ - styles/ _init.php Include the CombinedCSS.php file at the top of my _init.php: include_once('./inc/CombinedCSS.php'); _main.php Add the following to the <head> section: <?php $cssFiles = [ paths()->templates . 'styles/uikit.min.css', paths()->templates . 'styles/uikit.ext.css', paths()->templates . 'styles/main.css' ]; echo CombinedCSS::CSS($cssFiles); ?> CombinedCSS.php <?php namespace ProcessWire; /** * Import the RuntimeException class for throwing runtime exceptions. */ use RuntimeException; /** * Class CombinedCSS * * A class for combining and minifying CSS files and generating a link tag for the combined CSS. */ class CombinedCSS { /** * @var string|null The output directory for saving the combined CSS file. */ private static $outputDirectory; /** * @var string|null Hash of the concatenated content of CSS files. */ private static $filesHash; /** * Initializes the output directory if it is not already set. */ private static function initOutputDirectory() { if (!isset(self::$outputDirectory)) { self::$outputDirectory = paths()->templates . 'styles/'; } } /** * Checks if a directory is writable. * * @param string $directory The directory path to check. * @return bool True if the directory is writable, false otherwise. */ private static function isWritableDirectory($directory) { // Check if the directory exists if (!is_dir($directory)) { return false; } // Check if the directory is writable if (!is_writable($directory)) { return false; } return true; } /** * Combines and minifies an array of CSS files. * * @param array $files An array of CSS file paths. * @return string The combined and minified CSS content. */ private static function combineAndMinifyCSS(array $files): string { $combinedCss = ''; foreach ($files as $file) { // Read the content of each CSS file $cssContent = file_get_contents($file); // Minify the CSS content (you can replace this with your preferred minification logic) $minifiedCss = self::minifyCSS($cssContent); // Append the minified CSS to the combined CSS content $combinedCss .= $minifiedCss; } return $combinedCss; } /** * Minifies CSS content. * * @param string $css The CSS content to be minified. * @return string The minified CSS content. */ private static function minifyCSS(string $css): string { // Replace this with your preferred CSS minification logic // Example: removing comments, extra whitespaces, etc. $minifiedCss = preg_replace('/\/\*.*?\*\//s', '', $css); $minifiedCss = preg_replace('/\s+/', ' ', $minifiedCss); return $minifiedCss; } /** * Generates a unique filename for the combined CSS file based on the provided file paths. * * @param array $files An array of CSS file paths. * @return string The generated combined CSS filename. */ private static function generateCombinedFilename(array $files): string { return md5(implode('', $files)) . '.css'; } /** * Saves the combined CSS content to a file. * * @param string $filename The filename for the combined CSS file. * @param string $content The combined CSS content to be saved. * * @throws RuntimeException If there is an error writing the file to disk. */ private static function saveToFile(string $filename, string $content): void { // Save the combined CSS content to the specified file $filePath = self::$outputDirectory . DIRECTORY_SEPARATOR . $filename; if (file_put_contents($filePath, $content) === false) { $error = error_get_last(); $errorMessage = isset($error['message']) ? $error['message'] : 'Unknown error'; throw new RuntimeException('Failed to write the combined CSS file to disk. Error: ' . $errorMessage); } } /** * Generates a link tag for the combined CSS file based on the provided file paths. * * @param array $files An array of CSS file paths. * @return string The link tag for the combined CSS file. * @throws RuntimeException If the output directory is not writable. */ public static function CSS(array $files): string { // Initialize the output directory self::initOutputDirectory(); // Ensure the output directory is writable if (!self::isWritableDirectory(self::$outputDirectory)) { throw new RuntimeException('The output directory is not writable.'); } // Combine and minify CSS files $combinedCss = self::combineAndMinifyCSS($files); // Check if the files have changed by comparing their hash $currentFilesHash = md5($combinedCss); $combinedFilename = ''; if ($currentFilesHash !== self::$filesHash) { // If the files have changed, generate a unique filename for the combined CSS file $combinedFilename = self::generateCombinedFilename($files); // Save the combined CSS content to a file self::saveToFile($combinedFilename, $combinedCss); // Update the files hash self::$filesHash = $currentFilesHash; } else { // If the files have not changed, check if the file exists on disk $combinedFilename = self::generateCombinedFilename($files); $filePath = self::$outputDirectory . DIRECTORY_SEPARATOR . $combinedFilename; if (!file_exists($filePath)) { // If the file doesn't exist, save the combined CSS content to a file self::saveToFile($combinedFilename, $combinedCss); } } // Return the link tag return '<link rel="stylesheet" href="' . urls()->templates . 'styles/' . $combinedFilename . '">'; } } Notes: Ensure that you have writing permissions for the $outputDirectory location. The combined CSS file will be generated and saved only if the files have changed or if the file doesn't exist on disk. The order of the combined file will adhere to the sequence specified in the array. Modify the hardcoded paths based on your requirements. Any suggestions for improvements are welcome! Cheers!1 point
-
Sounds like the session variables do not survive, maybe cookies disabled?1 point
-
@ryan looks like this was added in 4.22.0 and can be disabled: https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-versionCheck1 point
-
According to https://github.com/ckeditor/ckeditor4/issues/5510 it was indeed added to check if the installed version is up to date, but that's still unexpected and — in my opinion — just plain nasty. There should be absolutely no reason for CKEditor to "call home", except perhaps for those subscribing to their commercial LTS version. And if that feature was added just to push paid subscriptions to existing users once security issues are inevitably found from the free version, that doesn't make it any better. Thought about posting a question about that in the issue, but look like they've disabled commenting; doesn't seem like they're interested in open discussion. Migration to TinyMCE feels more and more like the right move ?1 point
-
@Robin S THIS IS A BLAST! I just testet it on a image-heavy site and it works perfectly. Thanks a lot!!! This definitely belongs to the core of PW in a newer version.1 point
-
@Jim Bailie In addition to the options you mentioned, if you put $myObject = new MyObject(); in your /site/templates/_init.php, then $myObject will also be available to all the page templates and the _main.php file, i.e. echo $myObject->value; As a benefit, it'll be automatically excluded from template files that don't use _init.php, such as the admin.php template file. If it's something that you might need on every page/template, but not necessarily, you may want to use it as a function in /site/ready.php that constructs it on first access, and then returns the same object on any later accesses to the function: function myObject() { static $myObject = null; if($myObject === null) $myObject = new MyObject(); return $myObject; } In this case, you'd use it like as a function with () appended rather than $ prepended, i.e. echo myObject()->value; This also has the benefit of using the same object (rather than creating another) if you happen to be calling $page->render(); on other pages in your template file(s). Another option: The way that ProcessWire does it is to use API variables, and you can add your own if you want. If you go that route, put in your /site/ready.php file: $wire->wire('myObject', new MyObject()); Then $myObject is available in any template files. The downside here is that it'll also be available to template files where you might not need it, such as the admin.php template file. So you may want to create it conditionally: if($page->template->name != 'admin') { $wire->wire('myObject', new MyObject()); }1 point
-
An SQL "expert" with records with duplicate keys? That starts alarm bells ringing for me. Generally any SQL table should have a primary key which by definition must be unique for each record. Of course it's possible they have some sort of composite key based on multiple fields that needs combining to correspond to a unique page name in ProcessWire. There are certainly cases where I've build SQL tables with composite keys, with one scenario being many-many relationships, and that's something that Processwire doesn't handle too well, although there is a module that makes many-many type relationships possible, although it doesn't compare to what you can do with pure SQL. Processwire does handle One-Many relationships fine via page reference fields or Pagetable fields. If you really must have a direct relationship between SQL commands and table structure and your CMS, I actually wonder whether ProcessWire is your best option. I've been doing a bit of investigation of Directus which looks promising, although it's headless, so no templates for output like ProcessWire, just a REST API, from what I can see, no full text indexing, and being more of a direct SQL - CMS mapping, it also lacks the hierarchical parent-child structure that ProcessWire handles so well, as it doesn't make any assumptions about what sort of data structures you have, whereas ProcessWire, while generally very un-opinionated does treat everything as a page in a page hierarchy. For websites, that's generally a pretty reasonable assumption, but if you really don't want that, and just want pure SQL tables then there are alternatives. While something like Directus will give you a direct SQL to CMS mapping, it won't fix bad SQL data, so if you've already started down the ProcessWire path, you want to be really sure it's worth the effort to change. I come from a pure SQL background myself, and it only took me about 20 minutes of reading the ProcessWire documentation to understand how it works, so I don't think it should be hard for someone from an SQL background to adapt. Maybe there's room for a blog post or tutorial showing how to "do it this way in SQL" and ProcessWire equivalent along with what's different.1 point
-
One more addition to this: The API is, in fact, one of the biggest advantages of ProcessWire. While the preferred term is "content management framework", one may as well think of it as an ORM of sorts. ProcessWire was never designed for direct database access, and that's the whole point: it's meant to (mostly) abstract the database layer away so that both developers and content editors can (as much as possible) avoid the complexities and inherent risks of dealing with SQL queries. I must say that I don't agree at all with your point about ProcessWire's advantages taking a backseat once the website is finished. I get where you're coming with this, but I'd assume that we can both still agree that it's extremely rare for a client to actually prefer SQL over a GUI — let alone them being competent and meticulous enough to produce data that fulfils all expectations we as developers might have. Obviously we come from different backgrounds and thus have different expectations. If I would've suggested to any of the clients I've ever worked with a platform that requires them to use SQL, that would've been a disaster ?1 point
-
@fruid I don't really understand your use-case. You say your client is going to use SQL to update the database, does that mean they are going to write raw SQL queries in front of a terminal? That seems like a really inefficient way to go about content creation, especially since you have to make sure your data fits within the constraints defined in the template and field settings. Despite that, you can still do that, you just have to add a couple of JOINs and be careful with your WHERE clauses. Each field table comes with a foreign key corresponding to the page ID. If your client is really some kind of SQL wizard who prefers the power of raw SQL for data migration over the limited interfaces puny mortals have to use, what's stopping them? Or do you mean "the client is going to log into phpMyAdmin / Adminer / MySQL Workbench / ... and insert data through that interface"? In that case, you're not really using SQL anyway, you're just using an interface that's closer to the database, that *may* be slightly more efficient to use for batch creation / updates if you really know what you're doing. If that's what your client wants – well, that's an interface you can build for them! Something like a batch update module that lists pages and allows you to edit them inline. In fact, such a module exists already: Lister Pro comes with inline editing for multiple fields at once. This gives you a convenient interface to update many pages at once and still stay within one backend and have all the input / constraint validation apply to your edits. Best of both worlds ? By the way, if you've ever tried to manually find something in a Drupal 8 database, you will like ProcessWire's database structure MUCH more ...1 point
-
That was the way they did it in other CMSs I used in former times - before I luckily found ProcessWire! It was like a breeze of fresh air after a long stuffy time... The dilemma you are in is to incline to habits you are familiar with. Try to understand the way PW works, and you will soon be convinced. (And as a next step: Try to convince the client...) All the best!1 point
-
This is not the whole picture. Simple fields might only have a single `data` column, but complex ones might have many more. Also multi-language support does work by adding `data_{lang}` columns to those field tables. Though I can see the argument for having template (or more correctly fieldgroup) based tables, where columns of fields would be merged into an single table.1 point
-
Each field may belong to more than one template, and each template has a different set of fields. Current structure works well with that concept, makes it possible to connect (or disconnect) fields with/from templates with ease, makes it unlikely for a single table to grow to giant proportions (thus making all queries against it slower), and also allows fetching/searching/saving the exact data that ProcessWire needs to fulfil a specific request. So yes — there are advantages to current structure. It's also a very fundamental part of ProcessWire, so changing it is not possible without major changes to the core ? It has been. If you'd like to read a bit more on it, I'd suggest doing a google search for something like "processwire database structure". You'll find a lot of existing content on this topic ? Your point of view is not unheard of for newcomers but trust me, there are valid reasons why the database architecture is what it is. Much of it is due to the fact that ProcessWire — unlike some competing platforms, I might add — was designed with custom data structures (custom fields) in mind from the ground-up. Some other systems (WordPress, for one) have a much simpler database structure, but that's because they weren't originally intended for the same kind of use as ProcessWire. In the context of ProcessWire this would be a bad idea: First of all the Admin is a ready-to-use tool for managing content, and I highly doubt that anyone will really have easier time managing the content with raw SQL. It can be fun and/or if you've had to do that a lot in the past you may be used to it, but still: ask them to give the Admin a try and I bet that this idea will go away in no time. If you manually update the rows in the database, most of what ProcessWire's fields do will be completely skipped. This includes validation, filtering, and sanitization; things that are there to help you build sites with valid and well formed data. Without these features you will run into trouble eventually, it's just a matter of time. ProcessWire does internal cleanups and such using hooks, and those will not get triggered if you update the data manually in the database. This means that you'll likely be left with broken data, missing pieces here and there, and so on. Some fields (take Repeaters for an example) will also be very difficult to update manually via database. Finally, while we're on the topic of hooks: they are a major feature in ProcessWire, used by both core and third party (module) code — and, once you get used to it, probably your own code as well — and again if you don't go through the "official channels" (API or Admin) you'll loose this benefit altogether.1 point
-
Over time we have often had discussions about the nature of Pages and the fact that the term 'Pages', in context of ProcessWire(PW), can be misleading. Pages can represent a lot of things and serve multiple goals. (fun read: https://processwire.com/talk/topic/2296-confused-by-pages/ ) You are right that Pages can be seen as (URL accessible) data objects. What data a particular Page can hold is defined via the page his Template and the Fields added to that template. So a Template can, amongst other things, be seen as a data model. ProcessWire has its own way of organizing and relating data. While the underlying database structure might be different than most MVC-ish systems, the Page tree and the Page fieldtype allow for really flexible ways of achieving most, if not all, of the traditional model associations like in Rails' Active Record. Finding and working with pages/data couldn't be easier thanks to the great API, so i don't think there is a need for an 'ORM' because in a way PW already has this.1 point
-
I had a realization recently about the PW data-model that I would like to share. The PW model is actually very akin to that of the Entity/Component model. Pages can be seen as Entities, and Fields can be seen as Components of those Entities. Templates can be seen as Component Types. One important difference from the E/C model, in terms of implementation, is the shape of the resulting entities - Fields are mapped directly onto Pages; or in other words, Component properties are mapped directly onto Entities, which means the Components themselves are limited to a single property at the model-level, even though some Field types are backed by tables with more than one property value, e.g. more than one data-column. I can't help thinking the resulting model is really close, but one step removed from being anywhere near as powerful as a real E/C data-model. To explain why I feel this way, here's a very simple case example. Let's say you have a shop with different types of products, many of which have at least one thing in common - they have a unit price, product title, and manufacturer name. With the PW model, I will add these three fields individually to every Template that describes a product type, and I will address these as, say, $page->price, $page->title and $page->manufacturer. Fetching these three fields requires three joins to separate tables. Conceptually, these three fields are meaningless on their own - they belong to a single component that defines the necessary properties required for something to "be" a product in our system. The, let's say, shopping cart service, for example, is dependent on the presence of all of these three fields and doesn't function if one of them is missing. With the Entity/Component model, instead of adding three individual fields, you define a component type that defines these three fields. You would address them as, say, $page->product->price, $page->product->title and $page->product->manufacturer. In other words, these three fields, which belong together, are exposed in the model as a single, integral component. Fetching these three fields requires a single join to one table. The ability to group together related properties in components has performance advantages (fewer joins/queries) but more importantly, it has practical advantages in terms of programming. You wouldn't need to group together related fields by prefixing them with "name_" anymore, which seems like an unnatural way to create a kind of "pseudo component". Adding/removing properties to a component property would naturally propagate that change to every entity that uses that component, rather than having to run around and update them manually. Controllers/services could define their requirements in terms of component shape. I realize it's a pretty substantial departure from the PW data-model, but perhaps something to think about for an eventual future 3.0. Or perhaps just an idea to consider for a different CMF in the future. Or perhaps just something interesting to think about and do nothing1 point