Category Archives: Web Development

I come across random techniques while working and decided to start blogging them… For me to find later, for others who may need them and with the hope to get some feedback.

Using JSONP in WordPress Development. The Quick-and-Dirty QuickStart Guide

What IS JSONP and why should I care?

There’s been a lot of debate lately about whether to move your site to SLL (https) or not. It’s clear, though, that all your forms, at least the ones that handle sensitive data like logins, should go over a secure connection.

If your whole site is SSL then you won’t have any issues with making AJAX calls; however, if you DO go hybrid then Javascript will consider all the pages that are SSL as a separate domain and will block AJAX calls to them.

Use case: You want to secure your admin area and you add define(‘FORCE_SSL_ADMIN’, true) to your wp-config.php file. You’ll remember that to make AJAX calls in WordPress you need to call: “/wp-admin/admin-ajax.php.” But that will now be an SSL connection and violates Javascript’s “same-origin policy“.

This is where JSONP comes into the picture. As you probably know, you CAN include scripts from other sites using a script tag, that ability is the foundation of CDNs. JSONP takes advantage of that functionality.

Basically, instead of including a static script on your site hosted on a  CDN, you’re sending a GET request to the domain of choice, and asking it to send back a callback method including within it a JSON object with the data you want. In essence, you’re spoofing your browser to think that it’s not actually doing a cross-domain ajax call, but in practice, you are.

There’s a great discussion on StackOverflow about JSONP for further exploration.

How do I do it?

Below you’ll find two files, upload them to a folder in your WordPress plugins directory and activate. Note: you should probably run this on a test site. It won’t do anything bad to your site, but it’s never a good idea to play around on your production site.

To test it add the following to any page:

<input name="title" type="text" /> <input name="titleSubmit" type="button" />

The javascript looks for an input button, on ANY page of your site, with the name “titleSubmit” and does the JSONP call with the content of an input text field on that page with the name of “title”.

The most important pieces in the example that you’ll need to get your code running are:

In the js file:

Clearly without any of the code it won’t work properly; however, the above settings are not typical of most of your standard AJAX calls.

type: GET: By it’s very nature a JSONP call cannot be POST. You’re essentially embedding a script tag on your page with GET variables in the URL of the script.

cache: false: Removing this will not break your code, but for testing it’s best if you your browser doesn’t cache the responses.

dataType: “jsonp”: You’re telling jQuery that it’s a JSONP call. Duh.

crossDomain: true: Perhaps with the dataType set to JSONP you’d think the that this would be enabled, that’s the whole point, but you still need to set this.

In the PHP file:

content-type: text/javascript: You’re setting the response type being sent, application/javascript will work as well.

access-control-allow-origin: *: This is another security check, you need to enable any origin for JSONP to work.

$_GET[‘callback’]: Without the callback wrapping your JSON response jQuery will not recognize this as a valid response. It is set in place as a security measure similar to NONCEs.

Conclusion

Once you have the right recipe, JSONP with WordPress is not all that difficult. Use it well.

As always, questions? comments? Don’t hesitate to do so below.


    Why I went to the cloud…

    I was asked to write a blogpost about my server setup. Specifically, about “the best fixes for wrangling all of those crazy cables.” To which I responded: “Oh, I just use the cloud. Never used anything else.”

    Why don’t I host my own physical server? Cold hard cash. For the level of scale I need, a few hundred hits a day, it would be overkill to buy my own.

    That question got me thinking about why I do wrangle my own server, albeit in the cloud, instead of relying on hosted solutions. So here goes…

    I’ve used many of the popular shared, and not shared, hosting solutions. But I’ve always come across the same issues. As my development skills improve, I find that I can only do less and less with what I have, when I rely on others for the environment in which I run my site.

    Let’s say I need to increase the upload limit in your php.ini? NOPE!

    Do I want to install git? Wait, I can just install that from the command line?! What else does my unix user have permissions to do? …or other users for that matter…

    Wait, you aren’t running PHP 5? Ok, I get that it’s complicated to upgrade PHP for existing users because you don’t want to break their sites, but, c’mon! I’m a new user!!!

    Through a mixture of horror and frustration I decided to look into alternatives. But, to be honest, I was afraid of running and configuring a server of my very own.

    For my first attempt I tried SUSE linux; because that’s what was being used at the office at time. But I really couldn’t keep it running. It just kept restarting. I didn’t know enough then to be able to say even now why that happened.

    But I persisted experimenting, and with some shameless begging, we switched to CentOS in the office. I took that opportunity to build my own development environment locally. Around the time I started feeling comfortable with playing around with server configuration I discovered DigitalOcean.

    After playing with AWS for a bit, moving to DO felt like moving to Apple from Windows XP. I had to give up a few controls that I didn’t feel too strong about, in exchange I didn’t have to think too much about settings I don’t care about. The server just worked. No extra services were needed and I knew the cost.

    So to answer the question of why I wrangle my own server?

    First, I like understanding the technologies upon which I am relying. The more you understand, the better performance you can pull out of something — “full-stack development” should include the Linux/Apache (or Nginx) part of the stack.

    Second, I you’re building an plugin or theme for distribution you need to make sure it will be compatible, and you can’t really rely on the masses to be running any advanced server setups. So don’t bother with anonymous PHP functions. But if you’re building something for yourself, you should be able to enjoy the benefits of PHP 5.5 and more.

    I’m currently working on version 3 of my server. I just moved from CentOS with a traditional Apache, PHP setup to Ubuntu with Nginx and PHP-FPM.

    Why?

    Well, first of all, all the cool kids are doing it.

    More importantly, Apache slows down under heavy load, because of the need to spawn new processes, while Nginx was designed specifically to handle high traffic sites. In addition, PHP-FPM runs along side the webserver, instead of on top — like a traditional setup with apache — which reduces the recourses available.

    This is a great example of the first reason I stated above. I saw a relatively easy way to improve my site’s performance. Since I’m in control, I can do it.


      Setting up an Ubuntu Desktop LAMP development server

      So you’ve inherited a WordPress site and you want to start developing for it. But you don’t want to go commando on the production site. Smart.

      How do you set up a local development environment?

      In this tutorial I’ll walk you through setting up a simple dev environment on Ubuntu desktop.

      WordPress typically runs off a LAMP stack. Not always, but most sites do. So that’s what we’ll set up.

      LAMP stands for:
      L inux – the operating system. In this case Ubuntu.
      A pache – the web server. Sometimes Nginx is used.
      M ySQL – the database.
      P HP – the server-side scripting language.

      The first step is to make sure you’re completely up to date.

      So let’s open up the terminal and run:

      sudo apt-get update

      Now we install the lamp server, run:

      sudo apt-get install lamp-server^

      Since Ubuntu 10 there’s this. Pretty easy, right?

      Type your password. Agree to the packages it’ll install. This will install ALL of the modules and packages you’ll need to run a web server locally.

      You’ll get a prompt to set a password for the root user for MySQL. Even though this is local, it’s best to keep to best practices, so don’t just press enter.

      That was ridiculously easy. Let’s test that by going to http://localhost/ in the browser. You should get a success apache screen.

      Let’s test PHP:

      sudo touch /var/www/html/info.php

      sudo nano /var/www/html/info.php

      type:

      &lt;?php phpinfo();

      Ctrl C to exit.

      Then navigate to: http://localhost/info.php

      You should see the phpinfo screen.

      Also to keep with best practices, once that’s done run:

      sudo /usr/bin/mysql_secure_installation

      Remember that password we just set? You’ll need it now.
      No, you don’t need to change the password you just set.
      Say yes to the rest.

      Next we’ll need a database for WordPress to use. So type:

      mysql -u root -p

      Enter the password you set for root.

      Then create the database:

      create database wordpress;

      Then: exit

      Now we’re almost ready to install WordPress, we just have to deal with some permissions, so we can code comfortably.

      First, we’ll need to grant apache access to the html directory:

      sudo chown -R www-data:www-data /var/www/.

      Then we need to add ourselves to apache’s group:

      sudo usermod -a -G www-data YOURUSERNAME

      In order for that to take effect, you’ll have to log out and log back in.

      Now, we still can’t do anything in the html directory. That’s because we need to grant read/write/execute permissions for the apache group.

      sudo chmod -R 775 /var/www/.

      And Viola! WordPress time!

      Download: http://wordpress.org/latest.zip

      Let’s extract that to: /var/www/html/

      If you go to http://localhost/wordpress in your browser you should see the installation wizard.

      Click “Create a Configuration File” then “Let’s go!”

      The “Database Name” keep as ‘wordpress’, we just created that.
      “User Name” is ‘root’.
      “Password” is whatever you set for that.
      “Database Host” keep as ‘localhost’.

      Usually it’s not a good practice to keep the root user for running your applications, but for expediency we’ll keep it for now.

      “Submit”

      Aaannnd this: “Sorry, but I can’t write the wp-config.php file.”

      No problem. Click back in your browser, and go back to the terminal. Run:

      sudo chown -R www-data:www-data /var/www/.

      Again, and we’ll resubmit. Because we copied the WordPress files apache didn’t own that directory. You should be good to go now.

      “Run the Install”

      Fill out the form. And we can log in now!

      Congratulations! You now have your own development server running off your Ubuntu desktop.

      Let’s test that out… Yup, all seems to be working just fine.

      This site you can now develop for and break with impunity.


        Announcing IlanaCMyer.com

        ilanacmyer.com

        I’m proud to announce the launch of the author site of my talented wife: Ilana C. Myer.

        The art is by the very talented Galen Dara; the site design and development is my own. I wanted to take a few words and go a bit into my thoughts behind that design.

        The design: The primary purpose of the site is for Ilana to share updates and insights about her upcoming book.

        Towards that goal, it was important for me was to have a beautiful readable type. I used Calluna. It has an elegant feel. What struck me about it was the elegance of the serifs, and how it balanced that detail with a clean reading experience. 

        To balance out the serif body type I used Lato I believe that it was intended to be used as a thinner sans-serif. However, for contrast I used it’s thickest weight. I think it still works nicely.

        For Ilana’s name, at the top, we used Neue Hammer Unziale. It’s Celtic look was apropos due to the influences of the book. (You’ll just have to read it to find out more.)

        Having commissioned the art, we wanted to put it front-and-center. I felt it would immediately place the visitor into the world of the book. Thankfully, the visual trend now is leaning towards full screen hero images.

        The transparent heading before the posts on the homepage is to keep the visitor inside that world, always reminding them of the art. The art can be seen in full by scrolling all the way to the bottom.

        The page heading recluses itself as the visitor scrolls down to bring the visitor into the text, reducing the distraction of the art.

        The tech: I used underscores as a base theme and set up a grunt watch to compile my LESS and JS files. Nothing too fancy; not much needed for a blog.


          Demystifying the WordPress Plugin

          I asked one of the guys on my team to build a WordPress plugin, he balked. I realized that he just didn’t get how easy it is. So I thought I’d dedicate some time to dispelling the myth that plugins are complicated to write. “Plugin” sounds formidable. But really, a plugin is a file in the plugins directory with a comment. That’s it. To build your first plugin create a file, name it whatever you like, and place the following code at the top:

          <?php
          /**
          * Plugin Name: Name Of The Plugin
          * Plugin URI: http://URI_Of_Page_Describing_Plugin_and_Updates
          * Description: A brief description of the Plugin.
          * Version: The Plugin's Version Number, e.g.: 1.0
          * Author: Name Of The Plugin Author
          * Author URI: http://URI_Of_The_Plugin_Author
          * License: A "Slug" license name e.g. GPL2
          */
          

          Upload that file to "YOUR-SITE-ROOT/wp-content/plugins/" and voilà!

          Screenshot 2014-06-29 15.55.47

          Go to your plugins menu and you’ll see your shiny new plugin there.

          What do you put in there besides the comment? Typically, anything you’d put in your theme’s functions.php file. Unlike with the functions.php file which runs everything you put in it, where plugins are concerned you can decide whether to “activate” the code or not.

          Here’s a great post with 25+ potential plugins. Let’s try #3 (Don’t forget to change the plugin’s name):

          <?php
          /**
          * Plugin Name: Remove WordPress Version Number
          * Plugin URI: http://URI_Of_Page_Describing_Plugin_and_Updates
          * Description: A brief description of the Plugin.
          * Version: The Plugin&#8217;s Version Number, e.g.: 1.0
          * Author: Name Of The Plugin Author
          * Author URI: http://URI_Of_The_Plugin_Author
          * License: A &#8220;Slug&#8221; license name e.g. GPL2
          */
          function wpbeginner_remove_version() {
            return &#8221;;
          }
          add_filter(&#8216;the_generator&#8217;, &#8216;wpbeginner_remove_version&#8217;);
          

          See! Wasn’t that easy?

          Typically, you’ll want to add to your functions.php file code that is necessary for your theme to run, and make plugins for all functionality that is separate from the theme.

          There’s a lot more to say about plugins, the example above is rather simple, but it should get you on your way.

           


            Database Indices

            I recently had the pleasure of indexing our company’s sites’ tables. The  custom CMS I inherited has some really brilliant code, but it also has quite a large amount of idiotic code blocks as well. In this case, the database was not thought out as best it could have.

            I was able to reduce some ridiculously long queries from 10 seconds down to 2 seconds with a few well placed indices. While 2 seconds isn’t anything to brag about. It’s quite an improvement. A database index is a lookup table that is created to help query information more quickly.

            Before jumping in. One thing to note about indices is that there is an expense. If you go ahead and index your entire table it will likely take longer to run that query than a non-indexed table. So you have to be discerning about what you index.

            The rule of thumb is, anything that alters the query, typically a WHERE clause or ORDER or GROUP, is a potential candidate for an index. For me, the rest of the process was trial and error, I went through all the potential candidates in my longest queries and tried various combinations until I came up with the quickest time for each query. There may be an algorithm for calculating the best columns to index. But this worked.

            phpMyAdmin doesn’t make it easy to find your indices.

            ( Don’t get any funny ideas, this is just a typical WordPress install. )

            1) Open a table, click “Structure” at the top.

            where are you?

            2) At the bottom of the page is a tiny link that says “Indexes”. You can manage existing indices from here.

            oh there you are

            3) To add an index check to see if there’s a “More” menu on the column you’d like to index…

            and how do I do that?

            Enjoy!


              Leveling Up Your Development Skills with a Pinch of SysAdmin

               “If you’re developing properly, you shouldn’t have to worry about whether your site is running Apache, Nginx or anything else. Your code should just work.”
              - anon

              When developing a plugin or theme for use by the greater community this is certainly true. Anything you put out there for someone else, should absolutely not be dependent on the platform.

              However, if you already have a platform running, and your code is serving a purpose other than to be code for other people (a plugin or theme) to use on their sites — like if you are running a service yourself — then you want to make sure your code will run on your production server before pushing it there. That’s why I keep my servers running the same infrastructure. I have people who rely on my site running as expected.

              I have a Virtual Machine (VM) running on which I develop. VMs are great because you can control 100% what is running on it.

              Developing on a Mac is fun because it’s core is Unix-esk. Many developers will just use the PHP that is right there, or use MAMP. MAMP is a very good way to get started.

              A native setup like that works 99% percent of the time. But I found that in a few edge cases, if you’re not developing in the same environment that your production server runs on, it can make debugging complicated… and I HATE debugging my production server live. If you do that you might as well skip all other layers and code commando, straight on production.

              Another benefit of running your own VM is that you learn what goes on under the hood. Sometimes your code isn’t the only thing responsible for speed and performance. If you’re on a shared host, there’s a lot you can’t do. Once you practice on a local development environment, you might just find that you’ve built up enough gumption to run your own live server yourself.

              The lowest tier on Digital Ocean is certainly comparable in price to any shared hosting. The benefits are nice, though. Want to try out Nginx instead of Apache? Sure! Want to use fast-cgi instead of running PHP on top of Apache? Go for it? How about just a simple APC install? No arguing on the phone with customer support. Just do it!

              Doing this IS scary. The buck stops with YOU. Make sure you have proper fail safes, backups, etc. Digital Ocean has daily backups, which is nice. But if you’re hacked or you get a Reddit bump you need to handle it yourself.

              Doing this also means that you will be competent to spin up multiple environments for testing. Thus validating the quote at the beginning of the post.

              So how do I do that?

              • On my Mac I run VM Fusion. I found it far superior for this than Parallels for running a local server.
              • Digital Ocean has wonderful tutorials for spinning up servers. Try the LAMP stack. Want to run Nginx? No problem. I recommend trying several of these, a few times. The cool thing with a VM is that you can delete it and begin again, as many times as you need. Get to a good place? Take a snapshot and roll on.
              • I Deploy with git. Which basically consists of making sure your local server can SSH to your live server(s). Since most of the people who use the sites I manage, manage the content themselves I don’t have to worry about syncing databases, so I haven’t worked out a solution for that.
              • When I work with a team on a project I’ll typically have a staging server. It’s basically a clone of production, only without an easily accessible url. We coordinate with a central repository and test on the staging server. When code is ready to be shipped. It’s pushed to production.

              When I keep everything the same, I won’t have to worry about deploying. If it works locally, and it works in the staging area, I can be assured that it’ll work on production.

              I’d love to hear your thoughts. Discuss:

               


                Handling a PHP unserialize offset error… and why it happens

                I  discovered recently the importance of proper collation of database tables. I inherited a proprietary CMS to manage. The default collation was latin1_swedish_ci. Apparently it’s because “The bloke who wrote it was co-head of a Swedish company“. The problem occurred when a form we had on our site began getting submissions with foreign characters. The database collation couldn’t accept the characters and was saving them as question marks (?).

                Serialization is the process of translating data structures or object state into a format that can be stored.” For example the array:

                $returnValue = serialize(array('hello', 'world'));

                Will become:

                a:2:{i:0;s:5:"hello";i:1;s:5:"world";}

                This is what the above string means:

                • There is an array that is 2 in length. a:2.
                • The first item in the array has a key that is an integer with the value of 0. i:0.
                • The value for that item is a string that is 5 characters long, which is “hello”. s:5.
                • The second item in the array has a key that is an integer with the value of 1. i:1.
                • The value for that item is a string that is 5 characters long, which is “world”. s:5.

                An unserialize offset error can occur when the string count in the serialized data does not match the length of the string being saved. so in the above example that would look like this:

                a:2:{i:0;s:4:"hello";i:1;s:5:"world";}

                Notice the number ‘4’, while there are really 5 characters in the world ‘hello’.

                So the question is, why would the offset happen when a ? replaces a foreign character?

                To understand why, you need to dig into how UTF-8 works and things will become clear.

                The UTF-8 value of ‘?’ is ‘3f’, while the value for ‘Æ’ is ‘c3 86′. '?' translates into s:1:"?"; while 'Æ' translates into s:2:"Æ";. Notice the 2 replacing the 1 in the string length. So basically, what’s happening is that when php serializes the data it is storing the foreign character as a double the length but when it’s passed to MySQL, when the table isn’t formatted for UTF-8, the database converts the character to a ?, which is then stored as a single character. But the serialization length is not updated, so when you go and unserialize the data there is an offset error.

                How to resolve the problem

                There are several articles that provide solutions. The most popular is to use the base64_encode() function around the serialized data. This will prevent the data from getting corrupted since base64 converts the data to ASCII which any collation can take.

                //to safely serialize
                $safe_string_to_store = base64_encode(serialize($multidimensional_array));
                
                //to unserialize...
                $array_restored_from_db = unserialize(base64_decode($encoded_serialized_string));

                If you don’t have access to your database, or don’t want to fool with it, this is a great solution. You can also set your table collation to utf8_general_ci or utf8_general_ci and that should solve your problem as well (that’s what we did).

                But what if you already have bad data in your database, like we had, and you’re getting the horrid ‘Notice: unserialize() [function.unserialize]: Error at Offset’ error. When you get this notice, chances are you’re not getting all your data either…

                Here’s what you do:

                $fixed_serialized_data = preg_replace_callback ( '!s:(\d+):"(.*?)";!',
                    function($match) {
                        return ($match[1] == strlen($match[2])) ? $match[0] : 's:' . strlen($match[2]) . ':"' . $match[2] . '";';
                    },
                $error_serialized_data );
                

                This will search out the strings, recount the length, and replace the string length with the correct value. Unfortunately it cannot recover what the original foreign character was, but at least the rest of your data will load.

                I got the original code from StackOverflow, but since PHP 5.5 the /e modifier in preg_replace() has been deprecated completely and the original preg_match statement suggested will error out. So I rewrote it with preg_replace_callback().


                  Using a post-receive Git hook to mark a deployment in NewRelic

                  I recently started monitoring my systems with NewRelic. Fantastic tool.

                  One fun feature they provide is that you can mark in NewRelic’s dashboard when you’ve deployed new code. This way you can compare your site performance before and after the deploy.

                  curl -H "x-api-key:YOUR_API_KEY_HERE" -d "deployment[app_name]=iMyFace.ly Production" -d "deployment[description]=This deployment was sent using curl" -d "deployment[changelog]=many hands make light work" -d "deployment[user]=Joe User" https://api.newrelic.com/deployments.xml

                  Using Git’s post-receive hook is perfect for this, especially since I already use it to deploy my sites to the various servers.

                  The only question I had was, how would I get the various variables from the post-receive hook into the curl statement?

                  Well, here you go:

                  description=$(git log -1 --pretty=format:%s)
                  author=$(git log -1 --pretty=format:%cn)
                  revision=$(git log -1 --pretty=format:%T)

                  Now you can do this:

                  curl -H "x-api-key:YOUR_API_KEY_HERE" -d "deployment[app_name]=iMyFace.ly Production" -d "deployment[description]=$description" -d "deployment[user]=$author" -d"deployment[revision]=$revision" https://api.newrelic.com/deployments.xml

                    Introducing Assets Manager for WordPress

                    Note: if the links aren’t working properly, resave the pretty permalinks settings.

                    Download

                    Many of the companies which my current place of employment interacts with have a higher level of security on their firewall (they also tend to use IE7, such is life). Because of this we were having issues sharing files with our constituents using the current industry file sharing tools.

                    To solve this problem I was tasked with creating a custom version of the corporate file sharing webapps for internal use. This would solve the problems we were having. All the links would be hosted on our domain, so we wouldn’t have to worry about getting third parties’ domains whitelisted in other company’s firewalls.

                    I decided that WordPress would be the best tool to build this on. It already has wonderful custom post management abilities as well as built-in media management tools.

                    I’m proud of what I built, so I got permission to release it to the WordPress community as a white-labeled plugin. Special thanks to @binmind for his extensive QA testing of the company’s plugin, his testing was crucial for development of the proof of concept and making sure everything was working as it should.

                    Instead of releasing the plugin as-is,  I decided to rebuild it from scratch. I’ve learnt a lot since building the original assets manager  and wanted to harden up the code base before releasing it to the public. Here are the results of my efforts.

                    Features

                    features

                    Path Obfuscation:

                    When a file is uploaded to WordPress you usually access it by linking directly to the location of where the file is hosted on the server. Assets Manager creates a unique obfuscated link for the file instead. When a file is downloaded it will receive the name you supply.

                    This does two things:

                    1. You can’t figure out where the file is actually hosted, nor can you find other files based on some pattern. This is a security feature. Since the links to the files do not indicate anything about where the files are, or what they will be called when downloaded, you can’t guess where other files are stored.
                    2. Files are never linked to, they are read and served. This allows #1 to work. It also means that before the file is served, Assets Manager can check various things, like if the user is logged in or if the file has “expired”.

                    When should this file expire?

                    Because of #2 above, Assets Manager intercepts files before they are served to the user from the server. This means that you can decide when and how the file will be served. I’ve included the ability to set how long the file should last. If you see you’re running out of time, you can extend the expiration by as long as you wish. The expiration date of the file is displayed next to the expiration feature letting you know when the file will expire.

                    Enable this file?

                    Same as the above feature. If you send out the wrong link, you can easily edit the settings and uncheck “Enabled”.

                    Secure this file?

                    I can also  check to see if a user is logged in before serving them the file. It doesn’t actually make the file secure. If someone downloads it, they can send it anywhere. It only secures the link to the file.

                    Remove file

                    When a file is removed it is not deleted, it can still be found in the media library. It is just detached from that assets set. You can delete it via the media library if you wish.

                    Stats

                    A basic hit count is recorded per file.

                    Asset Set

                    Each asset set is a custom post type, the upload files are attached to this post. The URL for the asset set is obfuscated to protect it’s location. If it is linked to it will be indexed though. But bots can find it crawling the site.

                    You can upload a set of files, then only share the one link. That way if you decide to change the links around you can. Only available files will be listed there. So if a file is “secure” and the user isn’t logged in, they won’t see it, nor will anyone see expired and disabled files.

                    Future features I’m working on:

                    • Sha1: If you upload a file that already exists it will link that file to your post instead of keeping multiple versions of the file. I believe that WordPress should work this way in general, all filesystems for that matter. That’s a benefit of networks. Why keep doubles, unless you intentionally are backing up the information?
                    • File replacement: After uploading and even sharing a file you’ll be able to replace the file behind the active link with a file of the same MIME type. This way if you make a typo you can fix it quickly and replace the file without sending out a new link.
                    What do you think?
                     If you have ideas, discover bugs, let me know.