Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by Sergio

  1. https://cloudinary.com/ does this for you and it has an API already. ? You can save the returned image on PW server after it's processed as well.
  2. As @LostKobrakai pointed out on the thread below: You can disable it though.
  3. AFAIK, only English is installed by default (at least on Digital Ocean servers) so I always went up "sshing" and installing my Portuguese ones ? Check in your shared server panel docs if they have more locales installed.
  4. This module looks very cool, thank you! I've built a similar solution (although simpler in the config side) where I run a cronjob daily to update the count on a field on each page. It works like a charm, but of course, there are other ways.
  5. https://developers.google.com/youtube/player_parameters#release_notes_08_23_2018
  6. I will check on that flag, @horst ! Thank you for clarifying and to offer your help. ?
  7. Hi everyone, Yesterday I began working on a module to create a filesystem abstraction for files and images using the Flysytem library (http://flysystem.thephpleague.com), out of the necessity of having the images copied (and probably linked) on Amazon S3 and other places like Dropbox. There two reasons why I decided to tackle this: 1 - When I work on the project in my machine, I need a way to automatically sync the images from the server and/or the external storage. When an image is added on the server and the database is imported on my local env, PW shows a blank thumbnail of it. The idea for the module if to check if the page image has a width == 0 and if it exists on the server, add it to the local filesystem. 2 - In the past, I had to move a large website to a different server in a hurry and a lot of images were left behind (it was a mess). So I'm planning for a possible future worst-case scenario of the server exploding ? The code I quickly wrote is below (please bear with me that it's pretty raw at the moment). One thing I had to figure it out is why PW fires the Pageimage::size hook wherever a page is loaded in the admin, even though the thumbnails are already created. I was planning to save the image variations on S3 as well. Can someone clarify? I know that @LostKobrakai was working on a similar project, and so I would like to ask him and everyone else if you think I (and who may help) should evolve this idea into a full-featured module where the user can select which server (adapter) to use (AWS, Digital Ocean spaces, Dropbox etc.) <?php namespace ProcessWire; use Aws\S3\S3Client; use League\Flysystem\AwsS3v3\AwsS3Adapter; use League\Flysystem\Filesystem; use League\Flysystem\Adapter\Local; use Spatie\Dropbox\Client; use Spatie\FlysystemDropbox\DropboxAdapter; class ProcessFly extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return array( 'title' => 'ProcessWire Flysystem Integration', 'version' => 001, 'summary' => 'Synchronize all the page assets uploaded through PW to a specified bucket in Amazon S3 and...', 'author' => 'Sérgio Jardim', 'singular' => true, 'autoload' => true, 'icon' => 'image' ); } public function init() { $this->client = S3Client::factory([ 'credentials' => [ 'key' => '', 'secret' => '', ], 'region' => 'sa-east-1', 'version' => 'latest', ]); $this->bucket_name = ""; $this->dir_name = "images"; $this->s3_adapter = new AwsS3Adapter($this->client, $this->bucket_name, $this->dir_name); $this->s3_filesystem = new Filesystem($this->s3_adapter); // DROPBOX $this->authorizationToken = ""; $this->dropbox_client = new Client($this->authorizationToken); $this->adapter_dropbox = new DropboxAdapter($this->dropbox_client); $this->dropbox_filesystem = new Filesystem($this->adapter_dropbox); $this->addHookAfter('Pages::saved', $this, 'checkImageS3'); // Download images that are not in local filesystem for whatever reason but are available in the remove one. $this->addHookAfter('InputfieldFile::fileAdded', $this, 'uploadImageS3'); // Fired when a file/image is added to a page in the admin // $this->addHookAfter('Pageimage::size', $this, 'uploadImageS3'); // Fired when a file is resized via API. Note: this hook is also called when the page is loaded in Admin. Need to know why. $this->addHookAfter('Pageimage::url', $this, 'redirectImageURL'); //Replace image URL for the S3 path } public function redirectImageURL($event){ if($event->page->template == 'admin') return; else $event->return = "https://s3-sa-east-1.amazonaws.com/[bucket name]/images/" . $event->object->page . "/" . $event->object->name; } // UPLOAD public function uploadImageS3($event){ if(count($event->return)) { //if it is a image resize event get image variation data $file = $event->return; } else { $file = $event->arguments(0); } $filename = $file->name; $filename_original = $file->url; $pathToFile = $file->page . "/" . $filename; $system_path = $file->filename(); try{ $file_on_s3 = $this->s3_filesystem->has($pathToFile); //check if file exists on AWS if(!$file_on_s3) { $contents = file_get_contents($system_path); $this->s3_filesystem->put($pathToFile, $contents, array( 'visibility' => 'public', )); //upload file with the same folder structure as in assets/files: page_id/file_name //Also add a copy to Dropbox (OPTIONAL) $this->dropbox_filesystem->put($pathToFile, $contents); } } catch (Exception $e){ throw new WireException("Error: Image not Added to S3: {$e->getMessage()}"); } } public function checkImageS3($event){ $page = $event->arguments(0); if(count($page->images)) { foreach($page->images as $file) { if($file->width == 0) { return $this->downloadImageS3($page, $file); }; } } } public function downloadImageS3($page, $file){ $pathToFile = $page->id . "/" . $file->name; $file_on_s3 = $this->s3_filesystem->has($pathToFile); //check if file exists on AWS if($file_on_s3) { $image = "https://s3-sa-east-1.amazonaws.com/[bucket name]/images/" . $pathToFile; // $page->images->remove($image); $page->images->add($image); $page->save(); } else { throw new WireException("Error: Image not found on S3: {$file->name}"); } } }
  8. Thanks! It was as it's a pretty simple extension (source below). I followed the VS Code official docs: https://code.visualstudio.com/docs/extensions/example-hello-world and looked at the examples they gave. ? // The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below const vscode = require('vscode'); // this method is called when your extension is activated // your extension is activated the very first time the command is executed function activate(context) { // Use the console to output diagnostic information (console.log) and errors (console.error) // This line of code will only be executed once when your extension is activated console.log('Congratulations, your extension "whichthemeisthat" is now active!'); // The command has been defined in the package.json file // Now provide the implementation of the command with registerCommand // The commandId parameter must match the command field in package.json let disposable = vscode.commands.registerCommand('extension.whichThemeIsThat', function () { // The code you place here will be executed every time your command is executed // context.subscriptions.push(disposable); // create a new status bar item that we can now manage myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); context.subscriptions.push(myStatusBarItem); myStatusBarItem.show(); updateStatusBarItem(); // register some listener that make sure the status bar // item always up-to-date context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(updateStatusBarItem)); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(updateStatusBarItem)); }); } function updateStatusBarItem() { const theme = vscode.workspace.getConfiguration().get('workbench.colorTheme'); const font = vscode.workspace.getConfiguration().get('editor.fontFamily'); myStatusBarItem.text = '$(eye) Theme: ' + theme + ' + ' + 'Font: ' + font; } exports.activate = activate; // this method is called when your extension is deactivated function deactivate() { } exports.deactivate = deactivate;
  9. Which Theme/Font is That? Tired of hearing that question when you share a screenshot or a tutorial on YouTube? Your problems are over! ? Introducing "Which Theme is That" Visual Studio Code extension. This extension will show on the status bar your workspace's current theme name and font. Install Grab it here: https://marketplace.visualstudio.com/items?itemName=sjardim.whichthemeisthat How to Run Install the extension and run the Command Palette Ctrl+Shift+P or Cmd+Shift+P on Mac. Search for "Which Theme is That?" and hit Enter. Screenshot Enjoy!
  10. Welcome to the forum, @MateThemes ! As @Gideon So already pointed out, to accomplish this you will need to implement a condition on your template code depending on your business needs, but it can also be achieved without any custom code if it's just a matter of hiding a portion of a content from a page that has both languages. If you could explain a little bit more about what you want to accomplish, we can help. ?
  11. I'm getting a 404 on your website due to Chrome's blocking scripts on http as insecure. See screenshot. Version 70.0.3538.110 (Official Build) (64-bit) on Windows 10
  12. Olá, Guy! ? I'm hosting the app on Laravel Forge with FastCGI enabled. I've based my config on this @u-nikos post: Here's my version: server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com; root /home/forge/example.com/public; index index.html index.htm index.php; charset utf-8; # ----------------------------------------------------------------------------------------------- # Access Restrictions: Protect ProcessWire system files # ----------------------------------------------------------------------------------------------- # Block access to ProcessWire system files location ~ \.(inc|info|module|sh|sql)$ { deny all; } # Block access to any file or directory that begins with a period location ~ /\. { deny all; } # Block access to protected assets directories location ~ ^/(site|site-[^/]+)/assets/(cache|logs|backups|sessions|config|install|tmp)($|/.*$) { deny all; } # Block acceess to the /site/install/ directory location ~ ^/(site|site-[^/]+)/install($|/.*$) { deny all; } # Block dirs in /site/assets/ dirs that start with a hyphen location ~ ^/(site|site-[^/]+)/assets.*/-.+/.* { deny all; } # Block access to /wire/config.php, /site/config.php, /site/config-dev.php, and /wire/index.config.php location ~ ^/(wire|site|site-[^/]+)/(config|index\.config|config-dev)\.php$ { deny all; } # Block access to any PHP-based files in /templates-admin/ location ~ ^/(wire|site|site-[^/]+)/templates-admin($|/|/.*\.(php|html?|tpl|inc))$ { deny all; } # Block access to any PHP or markup files in /site/templates/ location ~ ^/(site|site-[^/]+)/templates($|/|/.*\.(php|html?|tpl|inc))$ { deny all; } # Block access to any PHP files in /site/assets/ location ~ ^/(site|site-[^/]+)/assets($|/|/.*\.php)$ { deny all; } # Block access to any PHP files in core or core module directories location ~ ^/wire/(core|modules)/.*\.(php|inc|tpl|module)$ { deny all; } # Block access to any PHP files in /site/modules/ location ~ ^/(site|site-[^/]+)/modules/.*\.(php|inc|tpl|module)$ { deny all; } # Block access to any software identifying txt files location ~ ^/(COPYRIGHT|INSTALL|README|htaccess)\.(txt|md)$ { deny all; } # Block all http access to the default/uninstalled site-default directory location ~ ^/site-default/ { deny all; } #Amplify dashboard location /nginx_status { stub_status on; allow; deny all; } # ----------------------------------------------------------------------------------------------- # If the request is for a static file, then set expires header and disable logging. # Give control to ProcessWire if the requested file or directory is non-existing. # ----------------------------------------------------------------------------------------------- location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|eot|woff|ttf)$ { expires 15d; log_not_found off; access_log off; try_files $uri $uri/ /index.php?it=$uri&$query_string; } # ----------------------------------------------------------------------------------------------- # ProCache Rules # ----------------------------------------------------------------------------------------------- set $cache_uri $request_uri; if ($request_method = POST) { set $cache_uri 'nocache'; } if ($http_cookie ~* "wires_challenge") { set $cache_uri 'nocache'; } if ($http_cookie ~* "persist") { set $cache_uri 'nocache'; } # ----------------------------------------------------------------------------------------------- # This location processes all other requests. If the request is for a file or directory that # physically exists on the server, then load the file. Else give control to ProcessWire. # ----------------------------------------------------------------------------------------------- location / { expires -1; try_files /site/assets/ProCache-b3d534d...d/$cache_uri/index.html $uri $uri/ /index.php?it=$uri&$args; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } access_log off; error_log /var/log/nginx/example.com-error.log error; error_page 404 /index.php; location ~ \.php$ { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; fastcgi_index index.php; include fastcgi_params; } location ~ /\.ht { deny all; } }
  13. Hey @Guy Verville, excellent write-up!! Thank you for that! I've been using ProCache for about 3 years now in Nginx without any issues. So, go ahead! ? PS: I'd like to suggest you post in on Medium, etc. as well to increase reach. If you decide so, I also suggest to add to Ryans role: creator and maintainer of Processwire... instead of just designer, as people may only read "graphic/web designer" when they see that.
  14. Two examples I've used. //Page "intro" property that can be accessed via $page->intro: wire()->addHookProperty('Page::intro', function($event) { $page = $event->object; if ($page->summary) { //some pages on the admin do not have a summary field, only body $intro = strip_tags($page->summary); } else { $body = strip_tags($page->body); $intro = $body; $length = 450; if (strlen($body) > $length) { $intro = preg_replace("/^(.{1,$length})(\s.*|$)/s", '\\1...', $intro); } } $event->return = $intro; }); // I have a computed page_views field. If the page is a podcast, I added the views from Soundcloud to the page_total_views hook. wire()->addHookProperty('Page::page_total_views', function($event) { $page = $event->object; $views = 0; if ($page->soundcloud_playback !== "") { //if page has a soundcloud field, it's a podcast page $page->of(false); $en = wire('languages')->get("default"); $soundcloud_playback_en = intval($page->getLanguageValue($en, 'soundcloud_playback')); // get the unformatted value in English $pt = wire('languages')->get("pt"); $soundcloud_playback_pt = intval($page->getLanguageValue($pt, 'soundcloud_playback')); // get the unformatted value in Portuguese $views = intval($page->page_views) + $soundcloud_playback_en + $soundcloud_playback_pt; } else { $views = $page->page_views; } $event->return = $views; });
  15. WOW!! This looks awesome, @joshuag!! Seems very useful, especially for speed up the creation of complex templates!!
  16. This is very impressive! Excellent job @bernhard!! Could you detail more about the PDF generation? Library used, how the custom design was made, how you control widows / orphans etc. Thank you!
  17. I use the same as you do on Mac and Win. Laravel Valet on Linux. And I only recommend moving to Docker if you need to work on a team AND need to replicate a server setup in all machines. And if so, I recommend trying Laravel Homestead.
  18. You may find great insights on this thread below, on how to do it by hand. A bit old, but probably everything works even today.
  19. You may need a hook to get it done, take a look on this thread:
  20. Sendy is great if your budget is small AND you need to send emails to a lot of people (10k+). Mailchimp is incredible, but the price for small business is not so good, especially when a dollar is almost 4 times your currency (Brazilian Real) ? Just keep an eye on your AWS SES reputation dashboard, for complain and bounce rates.
  21. I added the source code on this post:
  22. The module source is below. Example usage: a checkbox on a contact form (using Form Builder) for the user to subscribe. It's used on https://ricardo-vargas.com/contact/ EXAMPLE A method on _hooks.php. If you don't use Form Builder, use this code on your form page. $forms->addHookBefore('FormBuilderProcessor::emailForm', function($event) { $processor = $event->object; if ($processor->formName == 'contact-form') { $formData = $event->arguments(1); $contact_name = $event->sanitizer->text($formData['contact_name']); $contact_name = substr($contact_name, 0, 30); // limit length further $contact_name = $event->sanitizer->emailHeader($contact_name); $contact_email = $event->sanitizer->text($formData['contact_email']); $contact_email = $event->sanitizer->emailHeader($contact_email); $processor->emailFrom = $contact_email; //reply to $processor->emailSubject = 'Message from '.$contact_name; $form = $event->object->getInputfieldsForm(); $subscribe = $form->get('receive_updates'); $list_id = $form->get('sendy_list_id')->attr('value'); // check to see if they subscribed if ($subscribe->attr('checked')) { $success_url = '/contact'; // $fail_url = '/contact?error=1'; $ProcessSendyAPI = wire('modules')->getModule('ProcessSendyAPI'); $ProcessSendyAPI->subscribeInSendy($contact_name, $contact_email, $list_id, $success_url); } } }); MODULE https://gist.github.com/sjardim/2d834ebb0bd66d4da1ac16072f4075cd <?php namespace ProcessWire; class ProcessSendyAPI extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return array( 'title' => __('Process Sendy API'), 'summary' => __('Handle API calls to a Sendy installation'), 'author' => 'Sérgio Jardim', 'version' => '001', 'singular' => true, 'autoload' => false, 'icon' => 'envelope' ); } /** * Data as used by the get/set functions * */ protected $data = array(); /** * Default configuration for module * */ static public function getDefaultData() { return array( "sendy_api_key" => '', "sendy_installation_url" => 'http://www.example.com/sendy' ); } /** * Populate the default config data * */ public function __construct() { foreach(self::getDefaultData() as $key => $value) { $this->$key = $value; } } public static function getModuleConfigInputfields(array $data) { $data = array_merge(self::getDefaultData(), $data); $wrapper = new InputfieldWrapper(); $f = wire('modules')->get('InputfieldText'); $f->attr('name', 'sendy_api_key'); $f->label = __('Sendy API Key', __FILE__); $f->description = __('Further instructions at https://sendy.co/api', __FILE__); $f->notes = __('Get your key at http://your_sendy_installation/settings.', __FILE__); $f->value = $data['sendy_api_key']; $wrapper->add($f); $f = wire('modules')->get('InputfieldURL'); $f->attr('name', 'sendy_installation_url'); $f->label = __('Sendy instalation URL', __FILE__); $f->description = __('Your Sendy installation URL without a trailing slash', __FILE__); $f->notes = 'http://www.example.com/sendy'; $f->value = $data['sendy_installation_url']; $wrapper->add($f); return $wrapper; } /** * [subscribeUserOrGuest description] * @param [type] $name [description] * @param [type] $email [description] * @param [type] $list_id [description] * @param [type] $success_url [description] * @param [type] $fail_url [description] * @return [type] [description] */ public function subscribeInSendy($name, $email, $list_id, $success_url = null, $fail_url = null) { $api_key = $this->data['sendy_api_key']; $sendy_url = $this->data['sendy_installation_url']; $postdata = http_build_query( array( 'name' => $name, 'email' => $email, 'list' => $list_id, 'boolean' => 'true' //set this to "true" so that you'll get a plain text response ) ); $opts = array('http' => array('method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => $postdata)); $context = stream_context_create($opts); $result = file_get_contents($sendy_url.'/subscribe', false, $context); //check result and redirect if($result) { $this->wire('log')->save("newsletter", 'A new user subscribed to the site mailing list: '.$email); if($success_url) { header("Location: $success_url"); } } else { $this->wire('log')->save("error", 'Error occurred on subscribing '.$email); if($fail_url) { header("Location: $fail_url"); } } } }
  23. Hi @php ! I'd like to recommend to you a free PHP course by Jeffrey Way, the guy behind Laracasts: https://laracasts.com/series/php-for-beginners I think you're going to love it.
  24. I think you can take a look at the CSS "object-fit" rule. See this codepen: https://codepen.io/akb20/pen/EPoPpx
  • Create New...