Jan Thielemann https://janthielemann.de/ Developer, Designer, Teacher Mon, 02 Apr 2018 10:50:14 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.5 Symbolic links under Windows: The ln -s equivalent https://janthielemann.de/random-stuff/symbolic-links-under-windows-the-ln-s-equivalent/?utm_source=rss&utm_medium=rss&utm_campaign=symbolic-links-under-windows-the-ln-s-equivalent https://janthielemann.de/random-stuff/symbolic-links-under-windows-the-ln-s-equivalent/#respond Mon, 02 Apr 2018 10:50:14 +0000 https://janthielemann.de/?p=25990 After a long long time I got to work with Windows again. Naturally I set up all my development environments as similar as I am used to on my Mac. One thin in particular did not work out though. I run a local web server using MAMP for my WordPress development. My themes and plugins […]

Der Beitrag Symbolic links under Windows: The ln -s equivalent erschien zuerst auf Jan Thielemann.

]]>
After a long long time I got to work with Windows again. Naturally I set up all my development environments as similar as I am used to on my Mac. One thin in particular did not work out though. I run a local web server using MAMP for my WordPress development. My themes and plugins reside in my Google Drive (as well as on Bitbucket) but I like to share the code across my computers without needing to push and pull every time I switch the device. That is why I used symbolic links on my Mac to link the themes/plugins folder to the wp-content folder in my web server.

At first I thought on Windows I could simply create a shortcut to the folder and throw it in the wp-content themes or plugins folder but that did not work out as expected. Instead I had to use the command line to create the link. The programm on Windows is called “mklink” and it works as the following:

mklink /d destination source

As expected you can either use full paths or go straight into the source or destination directory and use “.\”. Here I already cd’ed into the MAMP\htdocs\wordpress\wp-content\plugins folder on my C: drive to link a plugin from my Google Drive folder which resides on my D: drive:

mklink /d .\jt-my-plugin D:\GoogleDrive\wordpress\plugins\jt-my-plugin

Pretty straight forward once you know that the “ln -s” equivalent under windows is “mklink /d”. Just keep in mind that under Windows, the path separator is a backslack, not a forward slash as under most UNIX systems.

Der Beitrag Symbolic links under Windows: The ln -s equivalent erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/random-stuff/symbolic-links-under-windows-the-ln-s-equivalent/feed/ 0
How to fix Spotlight not showing apps https://janthielemann.de/random-stuff/fix-spotlight-not-showing-apps/?utm_source=rss&utm_medium=rss&utm_campaign=fix-spotlight-not-showing-apps https://janthielemann.de/random-stuff/fix-spotlight-not-showing-apps/#respond Thu, 02 Nov 2017 06:40:38 +0000 https://janthielemann.de/?p=25896 I use Apples Spotlight all the time. The shortcut CMD + Spacebar is so convenient that I use it exclusively to open apps, do currency conversion or simple calculations. Recently, though, Xcode disappeared from the Spotlight search. Actually at the moment it happens each time there is an Xcode update. After a short search, I […]

Der Beitrag How to fix Spotlight not showing apps erschien zuerst auf Jan Thielemann.

]]>
I use Apples Spotlight all the time. The shortcut CMD + Spacebar is so convenient that I use it exclusively to open apps, do currency conversion or simple calculations. Recently, though, Xcode disappeared from the Spotlight search. Actually at the moment it happens each time there is an Xcode update. After a short search, I found a solution to this problem. Simply re-activate the spotlight indexing by entering the following command in the terminal:

sudo mdutil -i on /

Simple yet effective, I can now continue to use my beloved Spotlight to open my apps. I hope this little tip might save you one day from having a bad time. Cheers ✌️

Der Beitrag How to fix Spotlight not showing apps erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/random-stuff/fix-spotlight-not-showing-apps/feed/ 0
In Japanese https://janthielemann.de/video/the-musical-ghost/?utm_source=rss&utm_medium=rss&utm_campaign=the-musical-ghost https://janthielemann.de/video/the-musical-ghost/#respond Tue, 24 Oct 2017 01:32:32 +0000 https://janthielemann.de/?p=25856 Don’t mind me. I am just a demo for https://janthielemann.de/filterable-grid-for-divi/#gallery

Der Beitrag In Japanese erschien zuerst auf Jan Thielemann.

]]>

Don’t mind me. I am just a demo for https://janthielemann.de/filterable-grid-for-divi/#gallery

Der Beitrag In Japanese erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/video/the-musical-ghost/feed/ 0
Original https://janthielemann.de/video/original/?utm_source=rss&utm_medium=rss&utm_campaign=original https://janthielemann.de/video/original/#respond Tue, 24 Oct 2017 01:31:46 +0000 https://janthielemann.de/?p=25855 Don’t mind me. I am just a demo for https://janthielemann.de/filterable-grid-for-divi/#gallery

Der Beitrag Original erschien zuerst auf Jan Thielemann.

]]>

Don’t mind me. I am just a demo for https://janthielemann.de/filterable-grid-for-divi/#gallery

Der Beitrag Original erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/video/original/feed/ 0
The Living Tombstone https://janthielemann.de/video/the-living-tombstone/?utm_source=rss&utm_medium=rss&utm_campaign=the-living-tombstone https://janthielemann.de/video/the-living-tombstone/#respond Tue, 24 Oct 2017 01:30:43 +0000 https://janthielemann.de/?p=25854 Don’t mind me. I am just a demo for https://janthielemann.de/filterable-grid-for-divi/#gallery

Der Beitrag The Living Tombstone erschien zuerst auf Jan Thielemann.

]]>

Don’t mind me. I am just a demo for https://janthielemann.de/filterable-grid-for-divi/#gallery

Der Beitrag The Living Tombstone erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/video/the-living-tombstone/feed/ 0
Word: How to put the chapter heading in the page header https://janthielemann.de/random-stuff/word-put-chapter-heading-page-header/?utm_source=rss&utm_medium=rss&utm_campaign=word-put-chapter-heading-page-header https://janthielemann.de/random-stuff/word-put-chapter-heading-page-header/#respond Wed, 30 Aug 2017 15:14:12 +0000 https://janthielemann.de/?p=25661 I was working on a eBook layout for a customer today, when he came up with a wish I have never done before. He wanted the current chapters heading 1 to appear in the all the header of all pages of that chapter. The solution is quiet simple and was easy to find. All you […]

Der Beitrag Word: How to put the chapter heading in the page header erschien zuerst auf Jan Thielemann.

]]>
I was working on a eBook layout for a customer today, when he came up with a wish I have never done before. He wanted the current chapters heading 1 to appear in the all the header of all pages of that chapter. The solution is quiet simple and was easy to find. All you have to do is to add the appropriate field to the header.

A little bit more tricky was to style the text because the customer wanted a different font and text style. The solution is to use the following field function. Then you can simply apply whatever font style you want to the field and you are good to go:

{ STYLEREF "Heading 1" \* CHARFORMAT }

Depending on your locale, you might need to change “Heading 1” to you local name. In Germany that would for example be “Überschrift 1”. I encountered one problem while doing this. My Word is using the german localization and if I wanted the field to be correctly used inside the doc, I had to use “Überschrift 1”. However, when I wanted to export a PDF from the doc, I had to switch to the english term. The reason might be that I am using a Mac and for me it seems that the PDF generation is done on a remote server.

Thats it for today, I hope it helps somebody. Cheers ✌️

Der Beitrag Word: How to put the chapter heading in the page header erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/random-stuff/word-put-chapter-heading-page-header/feed/ 0
Building a self-sizing card layout like list in iOS using UITableView https://janthielemann.de/random-stuff/building-self-sizing-card-layout-like-list-ios-using-uitableview/?utm_source=rss&utm_medium=rss&utm_campaign=building-self-sizing-card-layout-like-list-ios-using-uitableview https://janthielemann.de/random-stuff/building-self-sizing-card-layout-like-list-ios-using-uitableview/#comments Mon, 31 Jul 2017 15:54:44 +0000 https://janthielemann.de/?p=25371 In one of the Android apps I work on, I display content in a list. So far nothing fancy. Except that it is a list of cards. In Android I can simply use a CardView for this. It looks beautiful to have these cool cards with their elevation effect. But when I tried to implement […]

Der Beitrag Building a self-sizing card layout like list in iOS using UITableView erschien zuerst auf Jan Thielemann.

]]>
In one of the Android apps I work on, I display content in a list. So far nothing fancy. Except that it is a list of cards. In Android I can simply use a CardView for this. It looks beautiful to have these cool cards with their elevation effect. But when I tried to implement a similar layout in iOS, I encountered some problems which I want to talk about in this post.

So here is the layout from the Android app. Quiet simple, isn’t it? In Android this kind of layout is achieved using a RecyclerView where each item is a CardView. Did I ever mention that I really like how easy you can create layouts in Android? The whole XML system has some big advantages over iOS AutoLayout if you ask me. But we are not here to complain about whats worse in one or the other OS, are we?

My first intuition to recreate this on iOS was to use a UICollectionView where each item would be a card. I then used the technique I described here to create the self-sizing of the items. Then I added some layer magic to the UICollectionViewCell to add a drop shadow and I was good to go. The solution worked so why were I not happy? Well because you might be familiar with what they say: the best code is the code you don’t write.

Using a UICollectionView forced me to write a custom layout class, use a sizing cell and do all the measurements myself. Also my client wanted only one row so why did I even bother using a UICollectionView in the first place when there is a perfect UI component for lists: UITableView? The answer is: because I thought it was better to have multiple columns so I developed the UI so you could easily have more than one column by flipping a switch in the settings.

After some discussions, the client made the decision that there will never be more than one column so I thought now would be a good time to clean up the code and do some refactoring. A good chance to reduce and simplify the amount of code which could possibly contain errors.

First I started by replacing the UICollectionView with a UITableView and removing the custom layout completely. The next step was the custom UITableViewCell. Here is what it looks like in the xib:

The trick here is to add your UI not directly to the Content View of the UITableViewCell but add another container which you can use as your “card”. I’m not 100% sure if I couldn’t simply have used the StackView but I went with a plain UIView as my “Container View”. The StackView is pinned to top, bottom, left and right of the Container View (respecting the default margin). The Container View is pinned to top, bottom, left and right of the Content View without any margins. This is because I later will set the margin in code.

The next step was to implement the custom code for the cell. Heres what it looks like:

class StatusTableViewCell: UITableViewCell {

    @IBOutlet weak var containerView: UIView!
    @IBOutlet weak var stackView: UIStackView!
    @IBOutlet weak var iconImageView: UIImageView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var descriptionLabel: UILabel!
    @IBOutlet weak var statusValueLabel: UILabel!
    @IBOutlet weak var levelImageView: UIImageView!
    @IBOutlet weak var alertCountLabel: BadgeView!
    
    @IBOutlet weak var levelImageWidthConstraint: NSLayoutConstraint!
    @IBOutlet weak var iconImageWidthConstraint: NSLayoutConstraint!
    
    @IBOutlet weak var containerViewTopConstraint: NSLayoutConstraint!
    @IBOutlet weak var containerViewRightConstraint: NSLayoutConstraint!
    @IBOutlet weak var containerViewBottomConstraint: NSLayoutConstraint!
    @IBOutlet weak var containerViewLeftConstraint: NSLayoutConstraint!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        alertCountLabel.badgeColor = .batteryRed
        alertCountLabel.textColor = .white
        levelImageWidthConstraint.constant = CGFloat(AppConfig.levelIconSize)
        iconImageWidthConstraint.constant = CGFloat(AppConfig.iconSize)
                
        prepareForReuse()
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        iconImageView.image = nil
        titleLabel.text = nil
        dateLabel.text = nil
        descriptionLabel.text = nil
        statusValueLabel.text = nil
        levelImageView.image = nil
        alertCountLabel.text = nil
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        //Set containerView drop shadow
        if AppConfig.displayStatesAsCards {
            containerView.layer.borderWidth = 1.0
            containerView.layer.borderColor = UIColor.white.cgColor
            containerView.layer.shadowColor = UIColor.lightGray.cgColor
            containerView.layer.shadowRadius = 2.0
            containerView.layer.shadowOpacity = 1.0
            containerView.layer.shadowOffset = CGSize(width:0, height: 2)
            containerView.layer.shadowPath = UIBezierPath(rect: containerView.bounds).cgPath
        }
    }
    
}

As you see, I awakeFromNib I do some styling for some of the components depending on the current settings. The more interesting part is the draw function. Here I add the drop shadow to the containerView via it’s layer. This allows for smooth scrolling. Thats it for the cell. If you would just run the code now, you wouldn’t see any cards though but just a normal UITableView.

The trick to display the entries as cards is now to set the constants of the constraints. This is done in the UIViewController which contains the UITableView. To get the self sizing cells, we have to activate automatic dimensions first. There are two good places to do this. In viewDidLoad() or as I prefer, in the didSet of the tableView property. Since I always use plain UIViewController over UITableViewController, I add a outlet to my UITableView and do the setup there like this:

    @IBOutlet weak var tableView: UITableView! {
        didSet {
            //Register cell
            tableView.registerCellWithNib(StatusTableViewCell.self)
            
            //Setup pull to refresh
            tableView.refreshControl = UIRefreshControl()
            tableView.refreshControl?.addTarget(self, action: #selector(reloadDataSource), for: .valueChanged)
        
            //Configure TableView
            tableView.rowHeight = UITableViewAutomaticDimension
            tableView.estimatedRowHeight = 44.0
            tableView.separatorStyle = .none
        }
    }

The registerCellWithNib is a extension which works in conjunction with a protocol and a default implementation. Maybe I’ll write another article on this in the future (I got the idea from here). Basically it allows you to easily register and dequeue cells without the need to have strings as reuse identifiers. Anyways, the interesting part is to set the rowHeight of the tableView to automatic dimension and give the tableView a estimatedRowHeight to work with. Also set the separator style to none.

Thats it. Now the UITableView will automatically calculate the correct height for each row based on the auto layout constraints you setup. But that still doesn’t give you a card like layout. For this, we need to do the final step which is setting up the margin for the Container View. We will do this in cellForRowAt:indexPath: like so:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(for: indexPath) as StatusTableViewCell
        
        let viewModel = diffCalc.rows[indexPath.row]
        cell.titleLabel.text = viewModel.title
        cell.dateLabel.text = viewModel.date
        cell.descriptionLabel.text = viewModel.description
        cell.iconImageView.image = viewModel.iconImage
        cell.iconImageView.tintColor = viewModel.iconColor
        cell.statusValueLabel.text = viewModel.statusValue
        cell.alertCountLabel.text = viewModel.alertCount
        cell.alertCountLabel.isHidden = viewModel.alertCount == nil
        cell.levelImageView.image = viewModel.levelImage
        cell.levelImageView.tintColor = viewModel.levelColor
        cell.selectionStyle = viewModel.selectionStyle
        
        if AppConfig.displayStatesAsCards {
            //Disable cell clipping
            cell.clipsToBounds = false
            
            //Set containerView padding
            cell.containerViewTopConstraint.constant = indexPath.row == 0 ? 8.0 : 4.0
            cell.containerViewBottomConstraint.constant = indexPath.row == diffCalc.rows.count - 1 ? 8.0 : 4.0
            cell.containerViewRightConstraint.constant = 8.0
            cell.containerViewLeftConstraint.constant = 8.0
            
            //Make cell selection invisible
            cell.selectionStyle = .none
            
        } else {
            cell.containerViewTopConstraint.constant = 0
            cell.containerViewBottomConstraint.constant = 0
            cell.containerViewRightConstraint.constant = 0
            cell.containerViewLeftConstraint.constant = 0
        }
        
        return cell
    }

The interesting part here is setting the cell.containerViewXXXConstraint based on the row. I wanted a 8px space between each “card” so that meant that I couldn’t simply add 8px top and bottom constraint or it would result in a 16px margin between each row. Instead, I check if the current indexPath.row is the first or the last row and only in this case add a 8px margin. Otherwise I add 4px margins to get the 8px in total.

The final result looks like this:

So the result looks exactly the same as it did with the UICollectionView and I also have self-sizing cells but the amount of code was reduced drastically. The collection view layout class was removed completely as well as the sizing cell. I didn’t had to do any sizing of my own anymore and all I had to do was setup correct layout constraints on my UITableViewCell.

I saw a post on StackOverflow the other day where somebody asked how he could get a self-sizing single column UICollectionView with full width items. Here you have it. If you only want one column, don’t bother using UICollectionView. Though the latest version comes with options for fully automatic sizing of items but if you need full width items, you are better off using a good old UITableView. It is very well designed and versatile in it’s layout. You can do a lot of things with it.

Don’t get fooled by thinking you need the more complex UICollectionView. In a lot of scenarios you can get away with the simpler tableview, saving you code, time, headache and even make your app faster. Automatic sizing of tableviews is done by Apple and they (most of the time) know what they do. In my tests, the self-sizing tableview was a lot faster than my self-sizing collectionview.

I hope this article helps you create cool card layouts and improve your code. Cheers ✌️

Der Beitrag Building a self-sizing card layout like list in iOS using UITableView erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/random-stuff/building-self-sizing-card-layout-like-list-ios-using-uitableview/feed/ 4
Implementing Easy Digital Downloads Product Updates in WordPress Plugins for Elegant Marketplace https://janthielemann.de/divi/implementing-easy-digital-downloads-product-updates/?utm_source=rss&utm_medium=rss&utm_campaign=implementing-easy-digital-downloads-product-updates https://janthielemann.de/divi/implementing-easy-digital-downloads-product-updates/#respond Wed, 21 Jun 2017 18:32:28 +0000 https://janthielemann.de/?p=25128 Another day another challenge. This time I was working on a WordPress Plugin. As you might know, I use the Divi Theme a lot. I think it’s a really cool Theme with a powerful page builder and all sorts of cool modules. There is even a marketplace where you can buy additional modules and child themes. […]

Der Beitrag Implementing Easy Digital Downloads Product Updates in WordPress Plugins for Elegant Marketplace erschien zuerst auf Jan Thielemann.

]]>
Another day another challenge. This time I was working on a WordPress Plugin. As you might know, I use the Divi Theme a lot. I think it’s a really cool Theme with a powerful page builder and all sorts of cool modules. There is even a marketplace where you can buy additional modules and child themes.

About two month ago, I was in desperate needs for a module for my german travel blog which didn’t exist in the Divi Page Builder. I also did not find anything close to what I needed on the web or in the marketplace. So as the programmer I am, I decided that it was time to learn another language (PHP) and dive a little bit into WordPress development.

I developed a module called Date Counter for Divi for the Divi Page Builder and I was really happy with it. On Facebook I asked in some Divi groups if people would like to test it – my plan was to sell it via Gumroad on this site. A fellow german developer pointed me to the Elegant Marketplace and told me that I should start selling my plugins there.

I checked the page out, created a account and after one or two days, I already had my first sale. Awesome. But then I created a update because I fixed a small issue with a wrong text. Now how should I give the update to my customers? I mean sure, they can just download the plugin, remove the old one from their WordPress and install the new version but we live in 2017, not 1600 bc – amirite?

Fortunately the Elegant Marketplace works with a tool called Easy Digital Downloads – a suite for selling digital products via your own WordPress installation. It comes with all sorts of extensions and one of the – at least I think so – coolest functions is the possibility to use WordPress native updating mechanism to provide updates for your plugins and themes.

Implementation is quiet simple too. At least if you know what to do. Elegant Marketplace provides a small coding sample in the Vendor Dashboard as well as a sample plugin and a sample theme but I still got confused and in the end, even after I did everything right according to them, it still did not work till I did a small change by myself they were not aware of by themselves.

So let me explain what you actually have to do to get licensing and updating via EDD for your products on the Elegant Marketplace.

Step 1: Preparing your Plugin

The first thing you have to do is to prepare your plugin. Thats really, really really simple. All you have to do is to copy over the EDD_SL_Plugin_Updater.php file to your plugin folder. It really doesn’t matter where you put it. I for example put it in the root directory because my plugins are only a few files and I don’t see a need for folders.

I saw that the popular Woo Layout Injector plugin puts it in a folder called includes but thats just personal preference I guess. You can get the file from the edd-sample-plugin-1 on the vendor dashboard of Elegant Marketplace or directly from the Easy Digital Downloads Github Repo.

Step 2: Copy over the sample code

The second step is to copy over the sample code from the edd-sample-plugin.php to your plugin file. If you are an advances developer, you of course can put this code in a separate php file and include it in your plugin file instead of stuffing your plugin file with all that code. Anyways, lets go through the code. I’ll explain to you step by step what you have to change and what to keep.

// this is the URL our updater / license checker pings. This should be the URL of the site with EDD installed
define( 'EDD_SAMPLE_STORE_URL', 'http://easydigitaldownloads.com' ); // you should use your own CONSTANT name, and be sure to replace it throughout this file

// the name of your product. This should match the download name in EDD exactly
define( 'EDD_SAMPLE_ITEM_NAME', 'Sample Plugin' ); // you should use your own CONSTANT name, and be sure to replace it throughout this file

// the name of the settings page for the license input to be displayed
define( 'EDD_SAMPLE_PLUGIN_LICENSE_PAGE', 'pluginname-license' );

if( !class_exists( 'EDD_SL_Plugin_Updater' ) ) {
	// load our custom updater
	include( dirname( __FILE__ ) . '/EDD_SL_Plugin_Updater.php' );
}

This is the first part. Here we define the EDD store url, the plugin name, the page where the license field is displayed and we also include the EDD_SL_Plugin_Updater.php if it wasn’t installed by another plugin already. Here are the changes you have to do:

  • Rename EDD_SAMPLE_STORE_URL to something meaningful for you
  • Change the value of EDD_SAMPLE_STORE_URL to https://elegantmarketplace.com
  • Rename EDD_SAMPLE_ITEM_NAME to something meaningful for you
  • Change the value of EDD_SAMPLE_ITEM_NAME to the display name of your plugin or theme on Elegant Marketplace. A good example is my “Date Counter for Divi” Module or the “Woo Layout Injector”
  • Rename EDD_SAMPLE_PLUGIN_LICENSE_PAGE to something meaningful for you
  • Change the value of EDD_SAMPLE_PLUGIN_LICENSE_PAGE to the slug of your license page. If you want to include the license field on a existing settings page, this should match it
  • Finally change the path to the EDD_SL_Plugin_Updater.php to match whatever path you put it in. If your EDD_SL_Plugin_Updater.php is in the root directory of your plugin, you don’t need to change the path

Ready for the next part? Here we go:

function edd_sl_sample_plugin_updater() {
    // retrieve our license key from the DB
    $license_key = trim( get_option( 'edd_sample_license_key' ) );

    // setup the updater
    $edd_updater = new EDD_SL_Plugin_Updater( EDD_SAMPLE_STORE_URL, __FILE__, array(
        'version' => '1.0', // current version number
        'license' => $license_key, // license key (used get_option above to retrieve from DB)
        'item_name' => EDD_SAMPLE_ITEM_NAME, // name of this plugin
        'author' => 'Pippin Williamson', // author of this plugin
        'beta' => false
    ));
}
add_action( 'admin_init', 'edd_sl_sample_plugin_updater', 0 );

This function sets up the updating mechanism. Here I had the most trouble when setting up my plugin for the first time. It took me a few days to figure out what I was doing wrong. I ended up debugging the EDD_SL_Plugin_Updater class and recreating its API calls to see whats wrong. So let me tell you what you have to change here:

  • Change edd_sample_license_key to something meaningful for you. It’s a good idea to use your vendor prefix (I use jt_ for my plugins) followed by your plugins name followed by license_key. Ideally you use search and replace to replace all occurrences of this string throughout the rest of the code.
  • Match the version field to your plugins current version.
  • Change EDD_SAMPLE_ITEM_NAME to match the field you changed in the last section.
  • Remove the author field completely. This is what in the end prevented my plugin from getting updates but I only found out about this after recreating the API call from the EDD_SL_Plugin_Updater with a REST tool (I use Postman by the way). As far as I’m concerned, the author field is not necessary when checking for updates so you can safely omit it
  • Finally rename edd_sl_sample_plugin_updater to something meaningful for you. Again, using your vendor prefix and plugin name followed by _updater is kind of a good idea I guess

The next part is simplest as well if you take your time and look through the code:

function edd_sample_license_menu() {
	add_plugins_page( 'Plugin License', 'Plugin License', 'manage_options', EDD_SAMPLE_PLUGIN_LICENSE_PAGE, 'edd_sample_license_page' );
}
add_action('admin_menu', 'edd_sample_license_menu');

function edd_sample_license_page() {
	$license = get_option( 'edd_sample_license_key' );
	$status  = get_option( 'edd_sample_license_status' );
	?>
	<div class="wrap">
		<h2><?php _e('Plugin License Options'); ?></h2>
		<form method="post" action="options.php">

			<?php settings_fields('edd_sample_license'); ?>

			<table class="form-table">
				<tbody>
					<tr valign="top">
						<th scope="row" valign="top">
							<?php _e('License Key'); ?>
						</th>
						<td>
							<input id="edd_sample_license_key" name="edd_sample_license_key" type="text" class="regular-text" value="<?php esc_attr_e( $license ); ?>" />
							<label class="description" for="edd_sample_license_key"><?php _e('Enter your license key'); ?></label>
						</td>
					</tr>
					<?php if( false !== $license ) { ?>
						<tr valign="top">
							<th scope="row" valign="top">
								<?php _e('Activate License'); ?>
							</th>
							<td>
								<?php if( $status !== false && $status == 'valid' ) { ?>
									<span style="color:green;"><?php _e('active'); ?></span>
									<?php wp_nonce_field( 'edd_sample_nonce', 'edd_sample_nonce' ); ?>
									<input type="submit" class="button-secondary" name="edd_license_deactivate" value="<?php _e('Deactivate License'); ?>"/>
								<?php } else {
									wp_nonce_field( 'edd_sample_nonce', 'edd_sample_nonce' ); ?>
									<input type="submit" class="button-secondary" name="edd_license_activate" value="<?php _e('Activate License'); ?>"/>
								<?php } ?>
							</td>
						</tr>
					<?php } ?>
				</tbody>
			</table>
			<?php submit_button(); ?>

		</form>
	<?php
}

function edd_sample_register_option() {
	// creates our settings in the options table
	register_setting('edd_sample_license', 'edd_sample_license_key', 'edd_sanitize_license' );
}
add_action('admin_init', 'edd_sample_register_option');

function edd_sanitize_license( $new ) {
	$old = get_option( 'edd_sample_license_key' );
	if( $old && $old != $new ) {
		delete_option( 'edd_sample_license_status' ); // new license has been entered, so must reactivate
	}
	return $new;
}

Here we add the settings page with the field to enter the license for updating. Of course you could also use get_option( ‘edd_sample_license_status’ ) in your plugin itself to check if the license is activated and only if so you would do something. However, I’m not doing that because first of all, I offer lifetime licenses for my customers but even if you offer only let’s say a year of updates, you still want your plugin to function. You just don’t give support.

The things we have to do here are:

  • Rename the functions to follow the previous naming convention <vendor>_<plugin>_<descriptive function name>
  • Rename Plugin License in the add_plugins_page() function to something meaningful. Remember, that page will show up under Plugins in your WordPress installation. Of course you can put this on a existing settings page of your plugin instead of adding a page under Plugins if you like.
  • Well basically you have to rename all the “edd_…” stuff and match it to whatever you renamed in the previous sections (e. g. EDD_SAMPLE_PLUGIN_LICENSE_PAGE which is the settings page slug)

Ready for the last parts? We are almost done.

function edd_sample_activate_license() {

	// listen for our activate button to be clicked
	if( isset( $_POST['edd_license_activate'] ) ) {

		// run a quick security check
	 	if( ! check_admin_referer( 'edd_sample_nonce', 'edd_sample_nonce' ) )
			return; // get out if we didn't click the Activate button

		// retrieve the license from the database
		$license = trim( get_option( 'edd_sample_license_key' ) );


		// data to send in our API request
		$api_params = array(
			'edd_action' => 'activate_license',
			'license'    => $license,
			'item_name'  => urlencode( EDD_SAMPLE_ITEM_NAME ), // the name of our product in EDD
			'url'        => home_url()
		);

		// Call the custom API.
		$response = wp_remote_post( EDD_SAMPLE_STORE_URL, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );

		// make sure the response came back okay
		if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {

			if ( is_wp_error( $response ) ) {
				$message = $response->get_error_message();
			} else {
				$message = __( 'An error occurred, please try again.' );
			}

		} else {

			$license_data = json_decode( wp_remote_retrieve_body( $response ) );

			if ( false === $license_data->success ) {

				switch( $license_data->error ) {

					case 'expired' :

						$message = sprintf(
							__( 'Your license key expired on %s.' ),
							date_i18n( get_option( 'date_format' ), strtotime( $license_data->expires, current_time( 'timestamp' ) ) )
						);
						break;

					case 'revoked' :

						$message = __( 'Your license key has been disabled.' );
						break;

					case 'missing' :

						$message = __( 'Invalid license.' );
						break;

					case 'invalid' :
					case 'site_inactive' :

						$message = __( 'Your license is not active for this URL.' );
						break;

					case 'item_name_mismatch' :

						$message = sprintf( __( 'This appears to be an invalid license key for %s.' ), EDD_SAMPLE_ITEM_NAME );
						break;

					case 'no_activations_left':

						$message = __( 'Your license key has reached its activation limit.' );
						break;

					default :

						$message = __( 'An error occurred, please try again.' );
						break;
				}

			}

		}

		// Check if anything passed on a message constituting a failure
		if ( ! empty( $message ) ) {
			$base_url = admin_url( 'plugins.php?page=' . EDD_SAMPLE_PLUGIN_LICENSE_PAGE );
			$redirect = add_query_arg( array( 'sl_activation' => 'false', 'message' => urlencode( $message ) ), $base_url );

			wp_redirect( $redirect );
			exit();
		}

		// $license_data->license will be either "valid" or "invalid"

		update_option( 'edd_sample_license_status', $license_data->license );
		wp_redirect( admin_url( 'plugins.php?page=' . EDD_SAMPLE_PLUGIN_LICENSE_PAGE ) );
		exit();
	}
}
add_action('admin_init', 'edd_sample_activate_license');


function edd_sample_deactivate_license() {

	// listen for our activate button to be clicked
	if( isset( $_POST['edd_license_deactivate'] ) ) {

		// run a quick security check
	 	if( ! check_admin_referer( 'edd_sample_nonce', 'edd_sample_nonce' ) )
			return; // get out if we didn't click the Activate button

		// retrieve the license from the database
		$license = trim( get_option( 'edd_sample_license_key' ) );


		// data to send in our API request
		$api_params = array(
			'edd_action' => 'deactivate_license',
			'license'    => $license,
			'item_name'  => urlencode( EDD_SAMPLE_ITEM_NAME ), // the name of our product in EDD
			'url'        => home_url()
		);

		// Call the custom API.
		$response = wp_remote_post( EDD_SAMPLE_STORE_URL, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );

		// make sure the response came back okay
		if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {

			if ( is_wp_error( $response ) ) {
				$message = $response->get_error_message();
			} else {
				$message = __( 'An error occurred, please try again.' );
			}

			$base_url = admin_url( 'plugins.php?page=' . EDD_SAMPLE_PLUGIN_LICENSE_PAGE );
			$redirect = add_query_arg( array( 'sl_activation' => 'false', 'message' => urlencode( $message ) ), $base_url );

			wp_redirect( $redirect );
			exit();
		}

		// decode the license data
		$license_data = json_decode( wp_remote_retrieve_body( $response ) );

		// $license_data->license will be either "deactivated" or "failed"
		if( $license_data->license == 'deactivated' ) {
			delete_option( 'edd_sample_license_status' );
		}

		wp_redirect( admin_url( 'plugins.php?page=' . EDD_SAMPLE_PLUGIN_LICENSE_PAGE ) );
		exit();

	}
}
add_action('admin_init', 'edd_sample_deactivate_license');

Here we do almost exactly the same as in the previous section. We change every occurrence of edd to our own vendor prefix, rename samle to our own plugin name and match the constants we have defined. If you used search and replace, you should have almost nothing to do.

Did I say “every occurence of edd”? Well that is not completely true. Notice that the $api_params array has a field called edd_action. This field must keep the name edd_action! This is one of the rare occurrences where you don’t rename edd and it costed me half a day to figure this out. Blindly following the tutorial on Elegant Marketplace I first used a search and replace to replace every edd_ throughout the whole code and then the search for errors started.

So be aware and don’t change the edd_action to your plugin prefix!

Okay, now to the last part:

function edd_sample_admin_notices() {
	if ( isset( $_GET['sl_activation'] ) && ! empty( $_GET['message'] ) ) {

		switch( $_GET['sl_activation'] ) {

			case 'false':
				$message = urldecode( $_GET['message'] );
				?>
				<div class="error">
					<p><?php echo $message; ?></p>
				</div>
				<?php
				break;

			case 'true':
			default:
				// Developers can put a custom success message here for when activation is successful if they way.
				break;

		}
	}
}
add_action( 'admin_notices', 'edd_sample_admin_notices' );

This function takes care of displaying error messages if something goes wrong during the activation process of the license. Here again we just have to rename the functions to match our vendor prefix and plugin name.

Thats it. Your licensing / updating should now work. For me the two issues which caused the most trouble were the fact that I renamed the edd_action parameter and that I had to remove the vendor field from the call to the updater class. If this tutorial was helpful for your, I’d love to hear about it in the comments section. Cheers ✌️

 

 

Der Beitrag Implementing Easy Digital Downloads Product Updates in WordPress Plugins for Elegant Marketplace erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/divi/implementing-easy-digital-downloads-product-updates/feed/ 0
How I got rid of Strings and made i18n as easy as pie 🍰 https://janthielemann.de/ios-development/got-rid-strings-made-i18n-easy-pie/?utm_source=rss&utm_medium=rss&utm_campaign=got-rid-strings-made-i18n-easy-pie https://janthielemann.de/ios-development/got-rid-strings-made-i18n-easy-pie/#respond Fri, 26 May 2017 17:42:17 +0000 https://janthielemann.de/?p=25064 Have you ever found yourself in the situation that you worked on an app for quiet a while and the project evolved more and more from what was once more of a prototype? Have you forgotten or just haven’t had the time to plan every part in advance? Have you or your client then noticed, […]

Der Beitrag How I got rid of Strings and made i18n as easy as pie 🍰 erschien zuerst auf Jan Thielemann.

]]>
Have you ever found yourself in the situation that you worked on an app for quiet a while and the project evolved more and more from what was once more of a prototype? Have you forgotten or just haven’t had the time to plan every part in advance? Have you or your client then noticed, that it would be nice to publish your app in various languages?

Have you then spent hours searching for all the strings in your project and replace them with localized strings? Well, if you have (just like me in my last project) or even if not, this article might be interesting for your (especially if you are just about to start a new app).

Strings are bad

Let me tell you the story about how I got rid of almost every string in my project while at the same time make localization as easy as pie. But first, why are strings bad? Well, thats an easy question to answer: because you have no way to check whatever is inside the string.

This is especially bad in some cases, where you have to rely on that whatever is in a string is actually correct and when you use a certain string in multiple locations. Its easy to create typos when writing the same text over and over again. Like when using strings as segue or cell identifiers. Raise your hand if your app crashed at least once because a identifier had a typo. ✋

It is easy to hard code a string here and there but at some point, when you are writing the same text over and over again, it’s time to refactor. Then we put the string in a constant somewhere in our class and get autocompletion. This is okay for stuff like identifiers even though there are better ways.

But what if we need some text which gets displayed to the user in multiple locations in our app? Its a bad idea to have the same string constant hanging around in multiple classes.

String extension to the rescue

The logical consequence is to create some sort of constants file which is globally valid. I actually don’t like putting my strings in such a file. I rather use an extension on String itself. This way, it’s even easier to use my localized string (more about localization/i18n in a minute).

I mean, lets compare two examples. A typical app constants file version vs a String extension:

//First off with the constants. Everybody has created or seen a struct like this, right? 
struct MyConstants {
    static let myString = "My String"
}

//And heres the better choice, a simple String extension
extension String {
    static let myString = "My String"
}

Sure, you could replace struct with enum so it’s not instantiable but then you’d also had to have at least one case in the enum. But that is absolutely not my point. My point is the type inheritance of the Swift compiler. Lets look at an example which makes the advantage of the String extension pretty obvious:

let myConstantString: String = MyConstants.myString
let myStringString: String = .myString

Woa, we can assign the string without explicitly calling the struct/enum/class or whatever. Isn’t that awesome? I like how short and clean my code suddenly becomes. I constantly assign meaningful default values to labels. I define them in one place and can be sure to never make typos again. Well except in the String extension of course but at least I now only have to look in one place.

Making your strings localizable and supporting i18n

At some point, internationalization becomes a topic of almost every app. With our strings all in one place, its easier than never before to support i18n. Want to see how easy? Let me show you! First we define another nice extension on String to get localized strings:

var localized: String {
    return NSLocalizedString(self, tableName: "Strings", bundle: Bundle.main, value: "", comment: "")
}

Next, just go through your String extension file and add .localized to each of the strings:

extension String {
    static let myString = "My String".localized
}

Can it be easier? I don’t know but if you do, I’d really love to learn about it as well so feel free to leave a comment. Cheers ✌️

Der Beitrag How I got rid of Strings and made i18n as easy as pie 🍰 erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/ios-development/got-rid-strings-made-i18n-easy-pie/feed/ 0
Fixing invisible UITabBarItem Icon when using UISplitViewController inside UITabBarController in Storyboard https://janthielemann.de/ios-development/fixing-invisible-uitabbaritem-icon-using-uisplitviewcontroller-inside-uitabbarcontroller-storyboard/?utm_source=rss&utm_medium=rss&utm_campaign=fixing-invisible-uitabbaritem-icon-using-uisplitviewcontroller-inside-uitabbarcontroller-storyboard https://janthielemann.de/ios-development/fixing-invisible-uitabbaritem-icon-using-uisplitviewcontroller-inside-uitabbarcontroller-storyboard/#comments Thu, 25 May 2017 21:17:19 +0000 https://janthielemann.de/?p=25048 I like Apples container view controllers. They are familiar for the users since they are used everywhere in the system and they give you a lot of possibilities to create convenient UIs without writing a lot of code. They are subclassable and they have useful protocols which you can implement so often there is not […]

Der Beitrag Fixing invisible UITabBarItem Icon when using UISplitViewController inside UITabBarController in Storyboard erschien zuerst auf Jan Thielemann.

]]>
I like Apples container view controllers. They are familiar for the users since they are used everywhere in the system and they give you a lot of possibilities to create convenient UIs without writing a lot of code. They are subclassable and they have useful protocols which you can implement so often there is not even the need to subclass.

Though they sometimes have their culprits. For example, when you try to embed a UISplitViewController from another Storyboard inside a UITabBarController.

Personayll I like to keep my Storyboards clean and I don’t like to overcrowd them. I think of a Storyboard as a part of my UI which is a closed user story. Like a combined login/sign up screen or a master-detail flow.

Especially when it comes to UITabBarControllers on the applications top level, I often create a separate Storyboard for each tab. And sometimes I end up putting a UISplitViewController in one of those tabs.

And here comes the problem. If the UISplitViewController is the initial view controller of your Storyboard, you won’t be able to set it’s tab bar item icon and title via the Storyboard. It was kind of frustrating and in the beginning, I would subclass UITabBarController and setup all titles and icon there.

Fortunately I just found a better way. Simply place a UITabBarController inside the Storyboard where the UISplitViewController is the initial view controller and make the UISplitViewController one of the UITabBarControllers view controllers by dragging the appropriate Segue.

Now you can set the tab bar icon and title directly on the UISplitViewController and you don’t even need to make the UITabBarController the initial view controller. All you have to do is give it an identifier to get rid of the warning that it is not reachable and you are good to go.

For me this is definitely good enough for now and I hope it will save you some headache as well some day. Cheers ✌️

Der Beitrag Fixing invisible UITabBarItem Icon when using UISplitViewController inside UITabBarController in Storyboard erschien zuerst auf Jan Thielemann.

]]>
https://janthielemann.de/ios-development/fixing-invisible-uitabbaritem-icon-using-uisplitviewcontroller-inside-uitabbarcontroller-storyboard/feed/ 3