Jump to content

MoritzLost

Members
  • Posts

    364
  • Joined

  • Last visited

  • Days Won

    18

Everything posted by MoritzLost

  1. The class files should always be named the same as the class by convention, yes. So either use ProductsPagesType -> ProductsPagesType.php or Products -> Products.php. This is in accordance with PSR-4, but it's also just for your sanity. If you want to find something when you come back to this project after some time, having a direct mapping between the class namespace & classname and the folders / files they're located in will be a huge timesaver in finding what you're looking for. Especially if you start to use sub-namespaces. If you want to use Products as your classname, why not just name the file Products.php as well? I don't completely understand what classLoader->addSuffix does by it's documentation, but you could just go the easy route and add your classes directory to the autoloader for the ProcessWire namespace: wire('classLoader')->addNamespace('ProcessWire', '/path/to/classes/'); This way you don't have to load classes manually and can add as many as you want. I don't know how this all interacts with the page classes support in 3.0.152, but the autoloader stuff should work in any case. By the way, the PagesType is an older feature, the new classes directory is for custom Page types, not PagesType classes as far as I understood it ... maybe that's why ProcessWire is not loading your classes correctly. Hope this helps!
  2. Is your init.php in the ProcessWire namespace? The error seems to indicate that you're trying to load the Products class in the global namespace instead of ProcessWire\Products. In this case, you can fix this by adding the ProcessWire namespace to your init.php, importing the Products class or using the fully-qualified class name: $products = new \ProcessWire\Products();
  3. Slightly off-topic: There's not really an ideal way to include vendor libraries in ProcessWire modules. You can of course just check the entire vendor folder into your repository, but then you're replicating the ENTIRE source code of ALL vendor libraries in your repository, which feels wrong. Also, in this case you have to check the license of that project; if it has a share-alike clause, you'd have to use the same license for your entire module ... Honestly, I'd avoid the hassle and just include instructions on how to install the third-party libraries to use with your module. You could even include a composer.json in your module with all dependencies, so the users of your module just have to do a composer install. @bernhard talked about a similar approach here; though in this case, the entire vendor folder created by Composer is included in the repository, which again is a somewhat heavy overhead. If you are including the repository in your vendor folder, you should be able to load them with the WireClassLoader. Though most PHP libraries are built to work with Composer's autoloader. In theory, every PSR-4 compliant autoloader should work – but I'm not sure how many edge cases there are. I'm also not sure if the WireClassLoader is fully PSR-4 compliant. A good starting point is the repository's composer.json. Check the autoload information, there you can see that the module follows PSR-4 and maps the Ibericode\Vat\ namespace to the src/ directory. Modify your namespace mapping accordingly and it should (could / might ?) work: wire('classLoader')->addNamespace('Ibericode\Vat', __DIR__ . '/vendor/vat/src'); Again, I'm not sure if this is the best solution. I'd love to see some core support for Composer dependencies in ProcessWire. My dream solution would be to be able to use git submodules for dependencies, but not even Github really supports those, oh well ?
  4. @mel47 Thanks for the report! I have just pushed a fix for the issue in release 1.0.1. The title and body arguments to TrelloWireApi::createCard() are now nullable, and the body defaults to an empty string if not set. This also means that the Hook for TrelloWireCard::setBody now gets called even if the body is empty, which it wouldn't previously. I have also added a Changelog. Let me know if you find any more bugs!
  5. I see three different approaches for this: You can already build footnotes using superscript, anchors and links inside the CK Editor without any additional programming or plugins. You need to write the footnotes manually (possibly in a seperate CK Editor field if you want to output them in a different place than the main content), create anchors on them, and then link to those anchors from within your text. For the link inside the text you can just use a numbers in superscript. This becomes very tedious to maintain though, especially if you have lots of footnotes. So this only works well for a couple of footnotes here and there. You could do this with Hanna Code, which is very similar to WordPress's shortcodes, only you create shortcuts through the GUI instead of code. One [[ref]] shortcode that gets replaced with a link to the footnotes and collects all footnotes in a global variable, and another [[footnotes]] shortcode that outputs the collected list of footnotes. You can get very fancy with additional parameters for different lists of footnotes etc. Write your own Textformatter module that works in a similar way to the shortcode approach mentioned above. Might be more flexible in the long run, though it's definitely more work than using Hanna Code. You can look at the source code of Hanna Code which you can adapt to your specific use-case. If you want a more current example of a textformatter, take a look at my TextformatterPageTitleLinks module which is written for PHP 7 exclusively. Not completely sure what the script does, but this is something that I would never do in a JavaScript. You can probably do the same thing or something better in CSS with flex or grid.
  6. Yes, in general all autoloaders work this way. Composer just generates a file that registers an autoloader function through spl_autoload_register. This function is then called whenever a class is used that is not loaded yet, as a last chance to load the class before an error is thrown. Composer has a few different ways of locating the class files though. You can even generate an optimized autoloader that includes a class map which will speed up autoloading a bit. You can also test if a class is loaded already or autoloaded with class_exists: // using class_exists with false as the second argument, the autoloader will not be called // this return false for all classes that have not been manually included or autoloaded yet var_dump(class_exists('Some\Vendor\Class', false)); // this will trigger the autoloader, so even if the previous call returned false this will return true if the class exists var_dump(class_exists('Some\Vendor\Class')); I have a bit more information on Composer usage in my Composer for ProcessWire tutorial ? Personal preference! I do believe that the .module files themselves need to be inside the ProcessWire namespace in order for ProcessWire to correctly load them in all situations. But ProcessWire doesn't know about the files in the src directory, so I could use any namespace I wanted (for Composer libraries, you'd usually use the developer / organization name as the top-level namespace, so I might have used something like MoritzLost\TrelloWire). It just felt wrong to me to have the main module files inside the ProcessWire namespace and the API in an entirely different namespace, this is why I used ProcessWire\TrelloWire.
  7. I use the classLoader in my TrelloWire module, you can look at the source code here. As you can see, this adds the src directory inside the module files to the WireClassLoader as the location to look for classes inside the namespace ProcessWire\TrelloWire. Almost; it doesn't preload the classes, but only on demand. This way, once I instantiate the class ProcessWire\TrelloWire\TrelloWireApi, the WireClassLoader looks for the PHP file for this class inside the src directory (the one registered for that namespace through addNamespace). If it couldn't find it, an error would be thrown. (If you're wondering why I don't use the fully qualified namespace, note the use statement at the top). Two main advantages: Using an autoloader, you can map classes to directories. The exact mapping (how namespaces are resolved to file paths) is defined in the PSR-4 standard. As far as I know, the WireClassLoader loosely complies with that. Composer mostly does the same thing (although a bit more elaborate, with optimizations for production usage etc.). Basically, you have a root directory mapped to a root namespace (in this case ProcessWire\TrelloWire -> src/ directory), and from there resolve sub-namespaces to sub-directories and the class name to a file name (so ProcessWire\TrelloWire\TrelloWireApi -> src/TrelloWireApi.php). The advantage is that you add namespaces wholesale. So you only need to add the mapping for the root namespace, and can then add as many classes and nested namespaces and classes without having to worry about including any class files manually because they will be autoloaded. Because class files are only included on-demand, only the classes you actually need for a request will be loaded. This of course cuts down on processing time, since otherwise you'd usually include all classes at the top for every request. Hope this clears up some of the confusion!
  8. @Tyssen I've run into that error a couple of times, I think it's specific to the Selectable Option field. I think it's related to how Twig tries different ways to get a property value from an object. ProcessWire uses magic __get methods all over the place, which are checked quite late by Twig. So Twig first tries to call hasValue on the object, which exists but requires additional arguments, so it breaks ... A workaround is using more explicit properties, either of those should work: {{ matrixBlock.fieldName.getValue }} {{ matrixBlock.fieldName.first.value }}
  9. The repeater field is an instance of RepeaterPageArray which inherits from WireArray, so you should be able to use WireArray::sort. This should work for sorting by the ingredientType: $recipeIngredients = $page->ingredients; $ingredientsSorted = $recipeIngredients->sort('ingredientType'); // if ingredientType is a page reference field itself, you may need to sort by it's title instead $ingredientsSorted = $recipeIngredients->sort('ingredientType.title');
  10. If I understand correctly, your "ingredient" field inside the repeater of a recipe is a Page reference field pointing to one "ingredient" page, correct? And the ingredientType is a field of the "ingredient" page? In this case, you don't want to have the ingredientType in your repeater, because the ingredientType is already defined as a property in the ingredient page. You can access this property through the page reference: // assuming the repeater field "ingredients" foreach ($page->ingredients as $repeaterItem) { // assuming the page reference field "ingredient" $ingredientPage = $repeaterItem->ingredient; // "ingredientPage" is the page object referenced by the page reference field, so you can access it's fields $ingredientTitle = $ingredientPage->title; $ingredientType = $ingredientPage->ingredientType; } You can than use the ingredient type to sort the ingredients, display them alongside the title or whatever you want to do.
  11. @LAPS Because it's plaintext, and ProcessWire needs to be able to parse log lines back into structured information (user, url, timestamp, message) for $log->getEntries() to work. Take a look at the source of lineToEntry: This method splits a log line on tabs and returns an associative array. Now if the "message" part of the line could include an arbitrary amount of tabs, the method couldn't really do that, because you wouldn't know how many parts the explode method would yield for that line. You could make the argument that it only needs to parse the first few array elements and join the rest with tabs again, but since the log may or may not contain the URL and the user, you don't know exactly how many array entries are bits of meta data, and which belong to the message. I'm not saying it's perfect design by the way (though I do think it's an elegant solution despite this edge-case), just trying to explain why I think it was build this way ? In this case I'd go with the approach I mentioned above, using a different delimiter and optionally changing it back to a tab in your output.
  12. Currently this is not possible because the ProcessWire logs save additional information for each message you save to a log (timestamp, current user et c.). Since the logs are stored as plaintext files, this information is stored as tab-delimited lines. This can only work if all linebreaks and tabs are stripped from each message, which is exactly what WireLog does. Interestingly enough, there is a "delimiter" option to change the delimiter used to seperate those bits of information. But it doesn't change the fact that all tabs are stripped out from the message. Maybe that would warrant a feature request? That is, if a custom delimiter is passed strip that from the message itself and keep tabs in. Though I'm not sure how much work that would be. Why do you need tabs inside log messages anyway? If you want to display the log messages in a structured format, maybe using regular pages and fields would be better suited. Or just use something else as a delimiter, like a pipe |. You could even str_replace the pipes back to tabs in your output ...
  13. @mel47 Great ? Though I think you have mixed up boards and lists; The method TrelloWireApi::moveCard only moves a card to a different list (within the same or a different board). So your constant ADDED_BOARD should probably be named ADDED_LIST instead to avoid confusion!
  14. A very similar problem was recently discussed here. Basically, you want to trigger a download by sending the following headers, then stream the file contents directly to the user and then kill the request: header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.$page->name.'.csv"'); See the thread linked above for the complete example code. To stream the actual content you have to do a little bit of work since you are using fputcsv which expects a file handle. I see three approaches: Write the file like you are already doing, then use readfile to output it to the user, and then delete the file before closing the request (not so efficient). Just output the CSV string manually (might have to handle escaping the delimiters inside the CSV string yourself though). Open the file handle to "php://output", see here for details, or this comment on the fputcsv documentation. Cheers!
  15. You need to know the ID of the list you want to move the card to. To get all lists of a board, you need to know the ID of that board first. In general, you can check the Trello API docs and perform the query you need using the TrelloWireApi class included in the module. Take note that all the API methods are defined on the TrelloWireApi class, not TrelloWire itself. So you need to use $TrelloWire->api()->moveCard()! For this particular case, the TrelloWireApi class has a helper method to retrieve both boards and lists associated with your account: // get the module and the api helper class $TrelloWire = wire('modules')->get('TrelloWire'); $TrelloWireApi = $TrelloWire->api(); // this will return an array of boards (with name and id) $boards = $TrelloWireApi->boards(); // alternatively, you can use the target board defined through the module configuration $boardId = $TrelloWire->TargetBoard; // this returns an array of lists on the board $lists = $TrelloWireApi->lists($boardId); Once of you have the array of lists, take note of it's ID, this is what you need to move a card to it. You can do this dynamically by searching through the array and search for the correct name or whatever, but I'd just dump the array of lists, grab the ID you need and put it in a constant somewhere ? This also saves an API call each time, and those actually have a noticable impact on performance because of network overhead. $targetId = 'some-id'; // get this ID from the lists() method above! $TrelloWireApi->moveCard($card->id, $targetId); Let me know if something isn't working for you!
  16. Your code can't work because interpolation for double quoted strings allows only variables or some variable expressions (like methods calls or object property access), see the documentation on string parsing. foreach loops are language constructs and can't be used inside strings. For this to work, move your foreach loop outside the echo statement and only echo the actual HTML code. Then it should work: foreach($item->repeater_logos_links as $logo) { echo "<img src='{$logo->single_image->url}' alt='{$logo->single_image->description}' width='400'>"; }
  17. Hi @mel47, I'm happy to hear that! Yes, you can! You can use this to output fields from the page to the Trello card. For example, by default the card body contains a link to the page: {httpUrl}, this will be the page's url after parsing. You can use both custom fields as well as system fields. For example: **Page title: {title}** Page content: {body} Page was created at {created} This approach is somewhat limited. For example, the {created} placeholder will output the raw timestamp, and as far as I'm aware there's no way to change that to a proper date format. But that's why all methods are hookable. For example, if you want to append the (properly formatted) creation date to the body of all cards (when they are created or updated), you can hook TrelloWireCard::setBody: <?php // site/ready.php namespace ProcessWire; wire()->addHookBefore('TrelloWireCard::setBody', function (HookEvent $e) { $body = $e->arguments(0); $card = $e->object; if ($card->page) { $body .= sprintf("\n\nCreated at: %s", date('Y-m-d', $card->page->created)); $e->arguments(0, $body); } }); You can hook different methods of TrelloWire or TrelloWireCard depending on when you want to modify the cards, there's a complete list of hookable methods in the README. Currently the only things you can do through the module configuration when a page is published is (1) create a new card for this page or (b) restore the card associated with this page if it was previously archived. It's also possible to update the card whenever the page is saved, though that doesn't differentiate between status changes. If you want to perform a specific action after a page is published (like, for example, tick one of the checklist items), you can do that through hooks and use the TrelloWireApi class to perform the action through the Trello API. If you need some help with that let me know what you want to do! Yes, you can create cards manually in your template files. In that case, I would first deactivate the automatic card creation through the configuration. Then you can create cards manually: // replace with a check for your button if ($buttonWasClicked) { $TrelloWire = $modules->get('TrelloWire'); $page = wire('page'); $card = $TrelloWire->buildCardData($page); // dont create cards for pages that are already associated with a card if (!$card->id) { $TrelloWire->createCardForPage($card, $page); } } Let me know if it's working for you! Something on my To-Do list for this module is to add some "recipes" for stuff like that ? I think both methods are equivalent, wirePopulateStringTags just passes through to the populatePlaceholders method. Some of those methods are really well hidden in the documentation, those are super handy and should definitely be more visible!
  18. Thanks for the update @d'Hinnisdaël, the new version fixes the issue ? As for the warnings with the Textareas field, that was indeed a bug with the Textareas module. I fact, I encountered this bug a year ago and Ryan fixed it already. Now if only I'd remembered to actually use the updated module ... ? Thanks for your help!
  19. @d'Hinnisdaël Great, thanks for your support! I'll install the new version tomorrow, and I'll try your suggestions for the Textareas field ?
  20. Thanks! I was hoping the screenshot would do that ? Maybe I can give the screencast a go this weekend ... though I'm just now realizing I don't even have a headset any more ? Lockdown because of Covid-19 mostly ? No but really, the README on Github has two examples, and the first one is basically the client project that started this off. This project is a lead generation platform with multiple contact / data collection forms. Our client's team manages all requests on a Trello board. This works really well once everybody is used to it, they use many custom labels and lists to organize all requests / leads. Currently all the new leads are entered manually, but after the relaunch every request will create both a ProcessWire page and a Trello card for the lead. After additional data is entered through one of the forms, the cards are updates with more information and appropriate labels. This is what the status change handling options are for; if the ProcessWire page is trashed, the Trello card can be archived and so on ...
  21. TrelloWire This is a module that allows you to automatically create Trello cards for ProcessWire pages and update them when the pages are updated. This allows you to setup connected workflows. Card properties and change handling behaviour can be customized through the extensive module configuration. Every action the module performs is hookable, so you can modify when and how cards are created as much as you need to. The module also contains an API-component that makes it easy to make requests to the Trello API and build your own connected ProcessWire-Trello workflows. Warning: This module requires ProcessWire 3.0.167 which is above the current stable master release at the moment. Features All the things the module can do for you without any custom code: Create a new card on Trello whenever a page is added or published (you can select applicable templates). Configure the target board, target list, name and description for new cards. Add default labels and checklists to the card. Update the card whenever the page is updated (optional). When the status of the card changes (published / unpublished, hidden / unhidden, trashed / restored or deleted), move the card to a different list or archive or delete it (configurable). You can extend this through hooks in many ways: Modifiy when and how cards are created. Modify the card properties (Target board & list, title, description, et c.) before they are sent to Trello. Create your own workflows by utilizing an API helper class with many convenient utility methods to access the Trello API directly. Feedback & Future Plans Let me know what you think! In particular: If you find any bugs report them here or on Github, I'll try to fix them. This module was born out of a use-case for a client project where we manage new form submissions through Trello. I'm not sure how many use-cases there are for this module. If you do use it, tell me about it! The Trello API is pretty extensive, I'll try to add some more helper methods to the TrelloWireApi class (let me know if you need anything in particular). I'll think about how the module can support different workflows that include Twig – talk to me if you have a use-case! Next steps could be a dashboard to manage pages that are connected to a Trello card, or a new section in the settings tab to manage the Trello connection. But it depends on whether there is any interest in this ? Links Repository on Github Complete module documentation (getting started, configuration & API documentation) TrelloWire in the modules directory Module configuration
  22. Hi @d'Hinnisdaël , thanks for this module! I'm trying it out now and collecting a couple of suggestions. I have encountered two small-ish bugs so far: On Firefox, I get a horizontal overflow in Firefox with the 'Shortcuts' panel in 'list' mode and 'small' size. Doesn't happen in Chrome. The following CSS fixes it for me (even though I haven't checked if it has other unintended consequences yet): .Dashboard__panel { max-width: 100%; } I'm also getting two warnings with the 'Collections' panel when I try to output some text from a Textareas field. Here's the input for the collection panel: 'columns' => [ 'title' => wire('fields')->get('title')->getLabel(), 'c_campaign_parameters.utm_source' => 'Campaign source' ], This results in warnings (even though the output still works): 1× PHP Warning: class_parents(): Class InputfieldText does not exist and could not be loaded in .../FieldtypeTextareas.module:1071 1× PHP Warning: in_array() expects parameter 2 to be array, bool given in .../FieldtypeTextareas.module:1072 I'm not sure if this is a bug in your module or in the Textareas field ? Can you take a look? Thanks!
  23. @Tyssen Stuff like that happens to me all the time ? Glad you got it working!
  24. If the class can't be found, the autoloader probably hasn't been included for some reason. Where are you including the autoloader? Make sure it's loaded before using the Twig classes: var_dump(class_exists('Twig\Loader\FilesystemLoader')); If it doesn't, the autoloader likely hasn't been included yet, or Twig is not installed. Which version Twig are you using? If you're using an older version of Twig 1, the namespaced classes may not exist in your version yet. In that case try to update to the latest 2.X or 3.X version. Have you made sure that the composer.json on your live server actually includes the Twig requirement?
  25. Probably not. If you look at the source code of WireHttp::send (wireSendFile is basically an alias for this), it sends the file by sending the correct HTTP headers for file transfers (Content-Disposition: attachment) and then streaming the raw binary data. JavaScript can trigger downloads but it's a bit more involved and doesn't work in all browsers. It will be way easier to use a normal form submission without AJAX, then it should work as intended.
×
×
  • Create New...