Jump to content

Leaderboard

Popular Content

Showing content with the highest reputation on 11/21/2016 in all areas

  1. This is our latest site using PW. Most of the grunt work is done with core modules but ProCache makes sure everything is super fast and the core image resize function was deployed everywhere an image is used to make sure everything stays looking good. The site uses a premium theme for it's graphical layout with colours and some typography changed to match the companies in-house style. Page templates themselves are hybrids using bespoke collections of the content blocks in the theme. A 'site settings' page is was added to the admin which collects and allows edits to site wide details such as phone number, email, GA code and main menu items etc. Comments welcome! http://www.brownhensolutions.com/
    4 points
  2. Image & File Dependencies Allows Image and File fields to be used in inputfield dependency selectors. Note: prefix field names with an underscore in your dependency selectors. Usage Install the ImageFileDependencies module. You can now create an inputfield dependency based on the number of images or files added to a Image or File field. When you create an inputfield dependency for a Image or File field, prefix your field name with an underscore. Example for a field named 'images': _images>3 Note that you do not include a '.count' subfield in the selector. https://github.com/Toutouwai/ImageFileDependencies Demo
    4 points
  3. @Robin S you're my hero again for this module! Seriously every time I think of something I need in PW you're already on it.
    4 points
  4. @adrian thanks, I will check it. Here is a feature for the Misc submodule: change page title field case:
    4 points
  5. Module: Auto Smush PDF https://github.com/matjazpotocnik/AutoSmushPDF Compress PDF files automatically on upload, manually by clicking the link for each file and in bulk mode for all PDF files. In Automatic mode PDF files that are uploaded are automatically compressed. In Manual mode "Compress" link will be present. This allows manual compression of the individual PDF file. In Bulk mode all PDF files can be compressed in one click. Will process PDF files sitewide, use with caution! Using https://labstack.com/, free (at the moment) online web service that provides compressing of PDF files. There is no limit in file size and no limit on number of uploaded PDF's. No privacy policy available. EDIT April 30 2017: This module is not working anymore as Labstack removed support for pdf compression.
    3 points
  6. Pocketgrid is so flexible, you can make and define your own column classes with it Example how pocketgrid lets you do it: <div id="some id" class="w20 block"> Blocks are semantic in pocketgrid so here I named it simply w20. The w stands for width and the 20 for 20%. It doesnt have to be w20, you can use any semantic value or description you want, I used w20 because it makes sense and works for me. Then you simply define these values in your main css file like in my example: .w20 {width: 20%; min-height: 1px;} The min-height: 1px is necessary to avoid columns from starting to float when they are empty (no output in a column) Isnt pocketgrid just wonderful
    3 points
  7. https://seld.be/notes/php-versions-stats-2016-2-edition Data comes from packagist. New versions are being adopted faster and faster. Something to consider, when thinking of PW minimum requirements. PW is still saying 5.3.8 is the minimum. May 2016 All versions Grouped PHP 5.5.9 11.87% PHP 5.6 39.67% PHP 7.0.6 10.39% PHP 5.5 29.56% PHP 5.6.20 8.41% PHP 7.0 20.24% PHP 5.6.21 7.69% PHP 5.4 7.64% PHP 5.6.19 4.71% PHP 5.3 2.43% November 2016 All versions Grouped PHP 7.0.12 8.58% PHP 5.6 37.46% PHP 5.5.9 8.25% PHP 7.0 35.01% PHP 7.0.11 7.62% PHP 5.5 18.93% PHP 7.0.8 6.92% PHP 5.4 5.40% PHP 5.6.26 6.12% PHP 5.3 1.60% PHP 5.6.27 4.49% PHP 7.1 1.36%
    3 points
  8. The use cases are the same as those for any inputfield dependency: you want to show or require one field based on the state of another. This module just adds Image and File fields to the types of fields you can use in an inputfield dependency selector. Off the top of my head: you have an images field for an optional gallery and you want to remind the site editor to add a summary/introduction for the gallery if images are added. Also, there was this request for help. @szabesz is right - the screen recorder tool is LICEcap. I discovered it via this post by @bernhard.
    3 points
  9. All your fields will be added directly to your template - Page fields and other fields. For the Page fields, it is the selectable options for the field that you will create as individual pages elsewhere in the tree. You could put these 'option' pages and their parents directly under the Home page, but I like to group them under a 'Selects' page to separate them from the rest of the site pages. Home Selects Make Toyota Volkswagen ... Color ... A couple of easy ways to create these pages: 1. Use adrian's great Page Field Select Creator module. This is good for when you want to include the ability to add pages (i.e. options for the select) via Pages > Add New because it will create dedicated templates for the option page and the parent page. The module creates the Page field for you. 2. When I don't need the use of the Add New feature and want to avoid unnecessary extra templates I use ImportPagesCSV with the "Paste in CSV Data" option. If all you need is the title then for example you would enter: title Toyota Volkswagen ... With this option you would create the Page field yourself and choose selectable pages, etc.
    3 points
  10. Ok well. I separated the assertions in their own class. And created a simple file for using the command line https://github.com/NinjasCL/ProcessServerAssert/blob/master/Assert.php just use php Assert.php in your server (or execute that file in the browser) and you can know if ProcessWire can run in the server
    2 points
  11. instead of trying to figure out elsewhere if a class is loaded (file is included) or not, i only do it inside the file that should be included. if(!class_exists("myClassName")) { class myClassName { // ... } } This way, one can include it multiple times in any order from everywhere without worrying.
    2 points
  12. Yes. May be I will do a merge request to wireshell for including this functionality as a command line app. The use case is when you do an installation of a previously configured site. So maybe the server could run ProcessWire, but some modules may be not present (like gd) and you discover that later in the development of the site.
    2 points
  13. ConnectPageFields Allows the connecting of two related Page fields so that changing one updates the other. Purpose of module An example: suppose your website is about movies. You have a template "movie" with Page field "actors". For each movie you add the actors that appear in the movie. All good, but what if you want to find results like... the 10 actors who have appeared in the most movies actors who haven't appeared in any movies since 1990 You cannot retrieve these pages with a single efficient $pages->find() query, and must load a large PageArray into memory in order to iterate or filter it. For the sake of making these types of queries more efficient you could structure your templates/fields so that movies are added to actors instead, but this may be a less comfortable workflow and can run into equivalent problems (e.g. "find the 10 movies with the largest cast"). The solution is to have a two-way relationship so that movie pages have an "actors" Page field and actor pages have a "movies" Page field. This module will keep these two Page fields in sync so that adding "Ryan Gosling" to "Drive" automatically adds "Drive" to "Ryan Gosling". Also, you can select the same Page field in both Page field A and Page field B. For example, create a "Related" Page field for related pages. Choose "Related" for both fields in a pair in the module config. Now when you add "Orange buffoon" to Related for "Donald Trump", "Donald Trump" is automatically added to Related for "Orange buffoon". Usage Install the ConnectPageFields module. If you haven't already done so, create the two Page fields you want to connect and add them to templates. In the module config select the two Page fields in a "Connected field pair" row as Page field A and Page field B. You can add rows as needed using the "Add another row" button. Troubleshooting Make sure you have set the "Selectable Pages" settings for each Page field correctly: The settings for Page field A should allow pages using the template(s) that Page field B has been added to. The settings for Page field B should allow pages using the template(s) that Page field A has been added to. http://modules.processwire.com/modules/connect-page-fields/ https://github.com/Toutouwai/ConnectPageFields Module config: Demo showing how changing one Page field updates the other:
    1 point
  14. @ethfun, if I understand right you would like to validate the page name during the Add Page process. This ought to be possible with a hook to ProcessPageAdd::processInput or InputfieldPageName::processInput, but stuffed if I can get PW to give a field error and return to Add Page; the error is displayed but the page is still added. I thought you could set an error to the inputfield... $my_field->error('That value is not allowed'); ...and the form wouldn't validate but I can't get that work for Add Page. Hopefully someone knows how to do this. BTW, members without a Form Builder license will not be able to read the thread you linked to with the background info. Might be good to include the details here.
    1 point
  15. Nice looking site. Only thing I noticed is, that your robots.txt contains some PHP: Sitemap: <?php echo "http://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"] ?>sitemap.xml And you could further more improve page speed by enabling compression, if your server supports it. Look in the ProCache settings under GZIP+More for more instructions. It is propably a typo, but it should be $config->urls->templates.
    1 point
  16. Take a look at what I did here: https://github.com/adrianbj/AutoContent/blob/24ec931771d8e80f1e2b7fbd57bd774fedbddd0f/AutoContent.module#L305-L307 $pageimages = $this->getBlankValue($p, $field); $this->fakerImage = new Pageimage($pageimages, $imagePath); which makes use of this: protected function getBlankValue(Page $p, Field $field) { $pageimages = new Pageimages($p); $pageimages->setField($field); $pageimages->setTrackChanges(true); return $pageimages; } Obviously a bit of a hack, but it seems to work fine. Not sure if this would work for your needs or not. You will of course need a $field object for file/image field. EDIT: Sorry, that still needs a page: $p - I didn't notice that when I posted.
    1 point
  17. Hello, This is a very simple module that helps you asserting if the server has all the requirements for running ProcessWire. Helpful if you are developing in local and then have to upload to a production server. https://github.com/NinjasCL/ProcessServerAssert
    1 point
  18. Of course Presto based I am a Presto worshipper since the early days and cannot let it go. I didn't know there is another one like me...
    1 point
  19. @klenkes -- Wait a min... which dragonfly are you talking about? the presto based or chrome? I loved presto based, they were awesome. I loved Firefox dev tools, unfortunately there were so many inaccuracies, there weren't much updates on FF dev tools lately. @AndZyk, yes I am aware about disabling cache in dev tools. It didn't really work most of time. Well, I guess Ctrl+Shift+Del or getting chrome apps called clear cache is the solution for now.
    1 point
  20. Maybe it should be mentioned in the tutorials, how to do this.
    1 point
  21. I love the case changer - awesome for when you've pasted in a title from elsewhere - thank you!
    1 point
  22. Bug: Missing figure tags if image has no alignment class has been corrected.
    1 point
  23. Well, the hoster I had that issue with wouldn't even be listed in such a overview. It's a small hosting company of probably <5 people. My client liked the personal support there, but there where only running of the shelf debian with the stock 5.3 just a year ago.
    1 point
  24. I found the problem, I had my parent under wich the children were saved under the Admin section, the admin is a hidden page. Seems that if one of his ancestors has a hidden status the PageTable won't list it's children. Might be better that it would only check it's parent instead of it's ancestors. But for now I just moved my categories page.
    1 point
  25. It's not only the users, it's also lazy hosters. In shared hosting environments some hosters only support a single php version per server. So the hoster cannot update because of potential incompatibilities for the users and the users cannot (easily) change the php version, because they're supposed to move their site to a whole different server. In the worst case both don't really care.
    1 point
  26. Well, it has me stumped. I haven't had or heard of this issue before. I doubt the GitHub issue szabesz linked to is related as that is specific to PageTables added to the user template.
    1 point
  27. $my_files_field->add('/path/to/file.pdf'); Same for Pageimages, which inherits the methods and properties of Pagefiles. https://processwire.com/api/ref/pagefiles/ https://processwire.com/api/ref/pagefiles/add/
    1 point
  28. PostCSS looks indeed interesting, especially the Autoprefixer plugin. I am thinking about switching my current workflow (CodeKit + Sass + Compass) to Gulp + Sass + PostCSS, but have to evaluate if it is worth the hustle.
    1 point
  29. This is the first time that I'm going to post anything under the showcase forum, although I have a few ProcessWire powered websites under my belt. This one is special because I think the use case is somewhat unique. Caltex Loyalty Club - initial the situation Caltex Loyalty Club is a customer loyalty and rewards program of Caltex Palawan aimed to provide exciting promos and incentives their customers' continuous patronage. Caltex Palawan originally bought their system from a large software company that also provides card-based solutions. The previous system consist of the following: two high-end Dell servers that are located in two separate Caltex' offices traditional EFTPOS devices for reading loyalty cards MIfare RFID cards as loyalty cards Running within one of the Dell servers was a crude and barely usable loyalty/rewards manager application that runs the whole thing. Here's what I mean: You're free to imagine what the rest of the application looks like. The other dell server acts as a VPN server through which all the EFTPOS devices and the application server connect to. To cut the long story short, this setup was running smoothly for a total of 3 weeks before problems started popping up, mostly because VPN server did not have a static IP address and the EFTPOS devices could not connect. The developers refused to provide service to the system because Caltex Palawan does not want to shoulder the travel and boarding expenses of the developers (since it was just working 3 weeks). We were called in at this point (we being a local tech solutions provider in Puerto Princesa), and we made the VPN server work, but other problems soon surfaced. The whole system was barely working for about 3 years when they finally decided they were fed up with the customer complaints they keep getting everyday. Our Solution We studied the existing system for quite some time to examine where the problems we're coming from. The most obvious one we found was that because the servers were located inside Caltex Palawan's offices, the servers were prone to downtimes caused by power outages and internet connectivity problems. It was obvious that the better way would be a web-based solution that would always be online. We also wanted to replace the bulky and outdated EFTPOS devices with android based NFC device that are more portable, and easier to work with. We retained the cards that originally came with the system since there are thousands left unused, but eventually replaced them with NTAG stickers, and newer cards. To sum it up: A web-based application to manage the loyalty / rewards system Android based NFC device to read the loyalty cards NTAG stickers to act as loyalty cards ProcessWire to the Rescue Having used ProcessWire before, it was our best choice, after considering using Laravel, and other frameworks, because of ProcessWire's key strengths: all custom fields - no unnecessary bloat powerful api scalability Although this straight up looks like it's been lifted from processwire.com's front page, but this really is the case, and these are all we need from a web-application framework to do almost anything! All custom fields Since we are working with a unique dataset, it's impossible for us to find anything out there that would have anything even remotely close to what we need. In the beginning we were planning to use custom database tables that we would deploy alongside ProcessWire. I even posted one time in the forum, asking if there's any way to integrate an ORM library with ProcessWire, and the answer I got was that it would be redundant, and almost surely unnecessary. And this turned out exactly the case! When we designed our database (using ERDs), we we're delighted at how closely we were able to replicate our relational design using templates, fields, and pages. This is mainly due to FieldtypePage. We were able to create meaningful relationships within our templates. We ended up with ~60 templates and ~70 fields. Powerful API About 70% of our code are API calls. The rest are just control structures and simple computations. That's how powerful the ProcessWire API is. So powerful that we did not even need to write a single sql query to complete the whole project. We needed to handle user login, API has it. Session handling, API has it. Selecting and manipulating huge amount of interrelated data, the API has it! Scalability At the onset of the project, the existing CLC program already has some 20 thousand members with hundreds of thousands of transactions. Right now, our current system has around 500,000 pages and counting and were not experiencing any slowdown. How we used ProcessWire The first thing that we decided that our client would not be able to see the processwire admin page. We wanted to present them with a simplified UI that does not present them with anything they don't need. We created a whole frontend UI for the client to use. We had custom forms for all the pages that they can create, and all the actions that they can do. We, on the otherhand, use the admin backend thoroughly in continuously developing, and supporting the system. What the clients see vs what we see: Our setup ProcessWire 2.8 running on a LEMP stack DigitalOcean droplet AjentiV VPS manager Modules we used We used a very minimal amount of modules for the project: ImportPagesCSV - to import the data migrated from the old system FieldtypeDecimal - for all our decimal values Modified RestHelper - from clsource for all the communication between ProcessWire and our android POS app Modified PagesSum from esrch ProcessSelectorTest - for quickly checking some selections Things we had to do on our own Frontend user login Frontend password recovery Fine-grained permission handling for users Cron jobs for scheduled tasks. The rest are API calls, and business logic. The Result Our Caltex Loyalty Club web application is now running on its sixth month without so much as a hiccup! Compared to the previous system that was averaging 200 transactions per day, we are now getting 800 and it's growing. We are running 10 campaigns and promos simultaneously across 18 Caltex Stations all over Palawan, with 30 android NFC terminals communicating to a single ProcessWire installation. We are very happy with ProcessWire in terms of performance, ease of use, and most importantly it's simple and yet very powerful functionality as a web-application framework even though it doesn't primarily market itself as one. PS. Also one of the best things we experienced while doing this project is the awesome, unparalleled community support. Throughout the project, although we had a lot of moments when we just couldn't figure out what to do, never once did we have to ask a question in the forum. A quick search here and there, and someone already helped someone else that faced the same problem we were having. It's almost as if people are being paid to diligently answer all the questions! We most of the time just had to like an answer that was already there! Screenshots and photos Android POS terminals and CSR training
    1 point
  30. Thanks for your responses. Yes @adrian, the idea to record and send search requests is unusual, but this will not be a high traffic site and there will be additional filters, who can access the search function. To summarize your input: 1. Search: As @Robin S mentioned, can I use a simple GET request to handle the search terms from the form and then process input in a single search template (search.php)? I.e. Get search terms -> sanitize input -> save input to variables -> create PHPMailer object and send input variables -> Show search results on the front-end ? 2. Database Design: I had a closer look at the car example of kongondo's post on "Approaches to categorizing site content", i.e. simple multiple categories. So the objective is to have typical search requests like this: "Show me all red, second-hand vans from Toyota with Diesel engine built in 2012 with a price below 15.000 EUR." Instead of adding the required fields directly to my car-template.php, I would create pages with sub-pages as attribute categories and sub-categories in the tree, which will then be referenced in my car-template.php as Page Fieldtypes, correct? I assume this keeps the attributes more manageable if further sub-categories need to be added in the future? So I would probably have a tree view similar like this: - Cars (car-template.php) -> this is where new vehicles will be added - Make -- Toyota -- Volkswagen -- BMW ... - Year -- 2000 -- 2001 -- 2002 -- 2003 ... - Color -- Red -- White -- Blue ... - Fuel -- Gas -- Diesel -- Electric ... - Condition -- New -- Used - Gear -- Automatic -- Manual -- Semi-automatic - Body - Compact - Convertible - Coupe - Off-Road - Van ... - etc. ... Now, there will probably be a couple of fields that won't be directly categories, such as price, mileage, description, images. Price and mileage, however, should be searchable. I assume those can be added directly to car-template.php instead of creating individual pages? Would appreciate your feedback if this makes sense.
    1 point
  31. Sorry for inconvenience! I just realised that the whole directory was missing on github. I fixed that and also upgraded AS to v1.0.2 that add support for CroppableImage3.
    1 point
  32. @thomas, I created a module that allows image and file fields to be used in inputfield dependencies.
    1 point
  33. This is working for me (PW 3.0.40)... /site/config.php $config->imageSizes = [ "foo" => ["width" => 200, "height" => 200], "bar" => ["width" => 400, "height" => 0], ]; /site/ready.php $this->addHookAfter('InputfieldFile::fileAdded', function($event) { $inputfield = $event->object; if($inputfield->name != 'images') return; // needs to match the name of your images field // alternatively, to use for all image fields... // if($inputfield->class != 'FieldtypeImage') return; $image = $event->argumentsByName("pagefile"); $sizes = isset($this->config->imageSizes) ? $this->config->imageSizes : []; if(count($sizes)) { foreach($sizes as $size) { $image->size($size["width"], $size["height"]); } } }); You won't see the variations immediately inside PageEdit, but you can see them after you save the page.
    1 point
  34. Another tip today: a filter to easily get pages in templates Usage: // homepage url (1: page ID) <a href="{(1|getPage)->url}" class="logo"> // custom selector <a href="{('name=about'|getPage)->url}" class="logo"> Filter (add to ready.php): $view->_filters['getPage'] = function ($selector) { return isset($selector) ? wire('pages')->get($selector) : false; }; I wouldn't recommend to use it with selectors but it's there if needed. It's better to use with variables set in template php files.
    1 point
  35. Here are two macros that I started to use recently to avoid passing full page objects and make things easier. I usually set "common" pages in ready.php like this: // Pages $view->homepage = $pages->get(1); $view->contactPage = $pages->get(1033); // PageArrays $view->menuItems = $pages->find('id=1010|1020|1030,sort=sort'); But instead of this I set only ID (selector): // Pages $view->homepage = 1 $view->contactPage = 1033; // PageArrays $view->menuItems = 'id=1010|1020|1030,sort=sort'; Now in latte template files I use the macro "n:page" or "n:pages", which automatically sets the "$p" variable ("$pArr" in case of n:pages). Basically it sets the context: <a n:page="$contactPage" class="contact" href="{$p->url}">{$p->title}</a> <ul n:pages="$menuItems" n:inner-foreach="$pArr as $p"> <li class="{$p|activeClass|noescape}"> <a href="{$p->url}">{$p->title|noescape}</a> </li> </ul> You can use this if you prefer "normal" macros instead "n:macros": {page $contactPage} <p>{$p->httpUrl}</p> {/page} And here are the macros (put it in site/ready.php): $view->_addMacro['page'] = array( 'page', '$p = \ProcessWire\wire("pages")->get(%node.word)', ';' ); $view->_addMacro['pages'] = array( 'pages', '$pArr = \ProcessWire\wire("pages")->find(%node.word)', ';' );
    1 point
  36. The simplest answer is that classnames cannot be shortened, but namespaces can. The ProcessWire namespace is not the best example here, because it's still not to long, therefore I'll use some namespaces from the flysystem package. use Aws\S3\S3Client; use League\Flysystem\AwsS3v3\AwsS3Adapter; use League\Flysystem\Filesystem; $client = S3Client::factory($credentials); $adapter = new AwsS3Adapter($client, 'your-bucket-name', 'optional-prefix'); $filesystem = new Filesystem($adapter); […] if(!$filesystem instanceof Filesystem) die(); // vs. $client = Aws\S3\S3Client::factory($credentials); $adapter = new League\Flysystem\AwsS3v3\AwsS3Adapter($client, 'your-bucket-name', 'optional-prefix'); $filesystem = new League\Flysystem\Filesystem($adapter); […] if(!$filesystem instanceof League\Flysystem\Filesystem) die(); Now imagine you'd need to use these classes 15 times in a file. You'd have to write them 15 times in their full glory without namespaces. With them you can always use the shortname of a class in your code, which keeps it readable and even if the shortname would collide you can do this to use an alternative short name for one of those in the file. (Alternativ naming does even work if classes don't clash.) <?php namespace kobrakai; use ProcessWire\User as PwUser; class User extends PwUser {} The use statement is super useful because you hardly need conflicting classes in a single file. Therefore you declare once which class you want to use in a file and never care about it later on. With longer classnames you need to constantly care about it trying to remember that one special long class name. The next benefit is the "folder" structure namespaces give yourself. As long as classes are in the same namespace you don't actually need to do anything besides the namespace declaration once. Only accessing classes outside the current namespace does need you to specify the namespace in any way. > kobrakai > Form > ImageGallery > … <?php namespace kobrakai; use DateTime; use ProcessWire\PageArray; class Form { public function ImageGalleryButtons(PageArray $options) { // Namespace needed $g = new ImageGallery(); // No namespace needed […] $date = new DateTime(); // DateTime is a core class, therefore also another namespace (root) } } Like you've seen above namespaces are great to extend classes. If I want to change a already existing file, which does use ProcessWire\User to use my own implementations (with maybe some additional business logic) I just change the use statement and I'm done. The file is still as readable as before. There were 100% no accidents in trying to search/replace classnames. We are still talking about users. <?php use kobrakai\User; // from: use ProcessWire\User; $u = new User; $u->name = 'my-user'; … if($another_u instance of User) $u->friend->add($another_u); …
    1 point
  37. You might try something like this for replacing an image. You add the new image (which places it at the end) then grab it and insert it before or after the old image. Then you delete the old image. $oldFile = 'myimage.jpg'; $newFile = '/some/path/or/url/myimage2.jpg'; $oldItem = $page->images->get($oldFile); $page->images->add($newFile); $newItem = $page->images->last(); // get the item just added $page->images->insertAfter($newItem, $oldItem); $page->images->delete($oldItem); $page->save();
    1 point
  38. This inputfield uses the ProcessPageSearch AJAX API, which is accessible to logged-in users only that have administrative access. Meaning, it can't be used on the front-end of a site, unless for an administrative user. However, this Inputfield is mainly just a front-end to the jQuery UI autocomplete, with some tweaks to make it consistent with PW's admin style and offer some configurable options. So one could very easily implement the same sort of functionality on the front-end of a site by creating a page with it's own template to handle the ajax calls, translate them to a $pages->find() operation, and return the matches. It doesn't need to do any more than this: /site/templates/search.php <?php $resultsArray = array(); if($input->get->q) { $q = $sanitizer->selectorValue($input->get->q); $results = $pages->find("title*=$q, limit=50"); foreach($results as $p) $resultsArray[$p->id] = $p->title; } echo json_encode($resultsArray); From there, the rest is just jQuery UI autocomplete: http://jqueryui.com/demos/autocomplete/#multiple-remote
    1 point
×
×
  • Create New...