ClassicPress Plugin Development: Format of a Plugin Header

ClassicPress PluginsThis post is part of the ClassicPress Plugin Development series in which I am going to look at both best practice for developing plugins and how I approach some requirements as well as some of the functions I commonly use.

As with the readme.txt file, the main plugin file needs a header containing information related to the plugin. The bare minimum which is needed in the header is the plugin name:

/**
 * Plugin Name: {plugin name}
 */

The fields which can be used are:

  • Plugin Name: The name of your plugin, which will be displayed in the Plugins list in the ClassicPress admin dashboard.
  • Plugin URI: The unique home page of the plugin
  • Description: A short description of the plugin which will be displayed in the Plugins section in the admin dashboard. It should be shorter than 140 characters.
  • Version: The current version number of the plugin, such as 1.0.0 or 1.0.3.
  • Requires at least: The lowest ClassicPress version that the plugin will work on.
  • Requires PHP: The minimum required PHP version.
  • Author: The name of the plugin author (separate multiple authors with commas).
  • Author URI: The author’s website or profile on another website.
  • License: The short name (slug) of the plugin’s license (e.g. GPLv2).
  • License URI: A link to the full text of the license (e.g. https://www.gnu.org/licenses/gpl-2.0.html).
  • Text Domain: The gettext text domain of the plugin. More information can be found in the Text Domain section of the How to Internationalize your Plugin page.
  • Domain Path: The domain path tells ClassicPress where to find the translations.

The below example is what I use for my plugins:

/**
 * ------------------------------------------------------------------------------
 * Plugin Name: {plugin name}
 * Description: {short description}
 * Version: {version}
 * Author: {author}
 * Author URI: {author url}
 * Plugin URI: {plugin url}
 * Text Domain: {text domain}
 * Domain Path: {domain path}
 * ------------------------------------------------------------------------------
 * This is free software released under the terms of the General Public License,
 * version 2, or later. It is distributed WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Full
 * text of the license is available at https://www.gnu.org/licenses/gpl-2.0.html.
 * ------------------------------------------------------------------------------
 */

In the above example, the text in braces {} would be replaced with the required text.

Click to show/hide the ClassicPress Plugin Development Series Index

Error Running Fastpath hybrid Grant script

FastpathIn yesterdays post on encountering an error deploying the Fastpath Audit Trails to a new company, there was a problem which came up, but I forgot to mention.

When the minimum permissions script (also know as the hybrid grant script) from Fastpath was run, an error in SSMS was produced which was not seen when implementing:

Msg 3729, Level 16, State 1, Procedure sp_revokedbaccess, Line 51 [Batch Start Line 18]
Cannot drop schema 'Fastpathsql' because it is being referenced by object 'GP_DR_AT_BankDetailChanges'.

The problem here was because we had created a report via the portal and this had the owner of the SQL login configured for use by Audit Trails. The solution is to change the database owner to dbo.

This can be done a few ways, but the “safest” is to use the sp_changeobjectowner stored procedure to alter the owner to dbo:

EXEC dbo.sp_changeobjectowner @objname = 'Audit Trails SQL Login.SQL view name', @newowner = 'dbo'

The two highlighted sections need to be replaced; the first with the current owner of the view, the second is the view name.

Error Deploying Fastpath Audit Trails to a New Company

FastpathI implemented Audit Trails from Fastpath for a client a while ago. They created the required triggers in a few company databases and all worked well.

Recently they deployed the triggers to another company, but none of the data was flowing through to the portal.

I did some investigation and found the triggers were working correctly as the audit tables in the company database were being populated, but the data collect wasn’t moving them to the audit table in the FPAUDIT database. Further investigation and a quick suggestion from the Fastpath support team had me checking permissions on the databases; the database triggers were being added to did not have the required permissions for the Fastpath SQL login.

When I checked with the user, this was a company created after the initial deployment of Audit Trails and so the user had never had permissions to this database. We re-ran the minimum permissions script and the data collect was then able to run successfully and collect the audit changes from the new company.

SQL Script: Get First Email from Semi-colon delimited string

Microsoft SQL ServerI had a request to produce a SQl view for a client recently which extracted the first email address from the EmailToAddress field in the Address Electronic Funds Transfer Master (SY06000) table linked to a creditor record in Microsoft Dynamics GP. This field generally stores a single email, but sometimes stores multiple email addresses separated with a semi-colon.

The below script will extract the first email address from the field if it is delimited with a semi-colon or the entire content of the field if there is no semi-colon.

/*
Created by Ian Grieve of azurecurve | Ramblings of an IT Professional (http://www.azurecurve.co.uk) This code is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0 Int). */
SELECT CASE WHEN SUBSTRING(EmailToAddress, 0, CHARINDEX(';', EmailToAddress)) = '' THEN EmailToAddress ELSE SUBSTRING(EmailToAddress, 0, CHARINDEX(';', EmailToAddress)) END AS Email FROM SY01200 -- Address Electronic Funds Transfer Master (SY06000)

ClassicPress Plugin Development: Format of a Plugin readme.txt

ClassicPress PluginsThis post is part of the ClassicPress Plugin Development series in which I am going to look at both best practice for developing plugins and how I approach some requirements as well as some of the functions I commonly use.

A ClassicPress plugin should have a readme.txt file. A readme file is where you “sell” your plugin, it’s features and benefits and why users would want to use it over the competition. Being clear and concise in your explanations will be of benefit.

This file explains what the plugin does, what features it has and is used to present this and other information to the users when they view the plugin details page through their ClassicPress site. This is an example of the plugin details for my SMTP plugin:

Plugin details page

As you can see the user first sees the description, but there are a number of other tabs available as well, which are configured in the readme file which are created using a form of Markdown which allows the file to be human readable no matter how it is viewed (unlike HTML markup).

At the basic level, a plugin file does not need to contain much information. Below is an example of the template used by the Update Manager I use for updating my plugins (while the ClassicPress Directory is in development:

=== Plugin Name Here ===

Version:           1.0.0
Requires:          1.0.0
Download link:     https://

== Description ==

This text displays in the modal windows; it is required. Write something!

Continue reading “ClassicPress Plugin Development: Format of a Plugin readme.txt”

ClassicPress Plugin Development: Structure of a Plugin

ClassicPress PluginsThis post is part of the ClassicPress Plugin Development series in which I am going to look at both best practice for developing plugins and how I approach some requirements as well as some of the functions I commonly use.

Before you start developing a plugin, I’d recommend deciding on the plugin structure you want to use. At the simplest level, a plugin only actually requires the file which holds enough code for the plugin and the folder it sits within, but in reality you will have other files which are needed as well, such as style sheets, language files, images and so on.

Planning a structure for the plugin will ensure your plugin files are well organised which will make it easier to work with both now and again in future.

John Alarcon of Code Potent, did a blog post on this subject a while ago. I use a structure fairly similar to the one he described, and have used the same format for showing the structure I use.

Continue reading “ClassicPress Plugin Development: Structure of a Plugin”

ClassicPress Plugin Development: Using Namespaces

ClassicPress PluginsThis post is part of the ClassicPress Plugin Development series in which I am going to look at both best practice for developing plugins and how I approach some requirements as well as some of the functions I commonly use.

In the last post in this series, on whether to use namespaces, I discussed whether they should be used or not and noted that I do not currently use them, but am debating whether I should.

At present, the azrcrv_tt_post_tweet function in my To Twitter plugin is called from a few other plugins in order to send a tweet; the function calls looks like this:

$tweet_result = azrcrv_tt_post_tweet($parameters);

This calls this function:

function azrcrv_tt_post_tweet($parameters){

If I was to update my plugins to use namespaces, in the To Twitter plugin a namespace would be added to the top of the PHP file (only the opening PHP tags and any comments should be before the namespace declaration). If I do make this change, I would use a developer and plugin specific namespace:

namespace azurecurve\ToTwitter;

The function to post the tweet, and all other functions, could then be renamed to remove the current developer and plugin specific prefixes thusly:

function post_tweet($parameters){

The other plugins which call this function, would need the function call to be amended to include the namespace:

$tweet_result = \azurecurve\ToTwitter\post_tweet($parameters);

With namespaces, it is possible to use a function name which matches that in the global namespace. For example, the get_option function is a standard ClassicPress function used to get the options for a plugin. I can create a function with the same name in a plugin without a conflict.

Calling the below will call the function in the plugin:

$options = get_option('azrcrv-tt');

To call the standard ClassicPress version in the global namespace I would prefix the function call with a \:

$options = \get_option('azrcrv-tt');

The final point to handle, is if you are using a ClassicPress hook such as add_action you are passing a string which will be executed in the global namespace so you need to pass the namespace of your plugin as part of the hook:

add_action('admin_menu', 'azurecurve\ToTwitter\create_admin_menu');

There is a predefined PHP constant available which you can use to avoid putting your namespace in many parts of your plugin; this can be useful in future if you need to change your namespace, as you then ony need to change the declaration at the top of the plugin:

add_action('admin_menu', __NAMESPACE__.'\create_admin_menu');

The same principles would apply PHP classes as well, but as I said in the coding paradigms blog post, I am not developing using object oriented programming and so am not covering classes.

Click to show/hide the ClassicPress Plugin Development Series Index

SQL View to Return Budgets with Account User-Defined Fields

Microsoft Dynamics 365 Business CentralI was helping a client create a budget report recently where they wanted to have the same information available in more than one reporting too. While queries could be written and embedded there is scope for them to then diverge over time; the solution to this is to create a SQL view which all of the reporting tools can then select to make sure they always have the same data.

The view uses data from the following tables:

-- drop view if it exists
IF OBJECT_ID(N'uv_AZRCRV_Budgets', N'V') IS NOT NULL
	DROP VIEW uv_AZRCRV_Budgets
GO
-- create view
CREATE VIEW uv_AZRCRV_Budgets AS
/*
Created by Ian Grieve of azurecurve | Ramblings of an IT Professional (http://www.azurecurve.co.uk) This code is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0 Int). */
SELECT ['Budget Master'].BUDGETID ,['Budget Master'].YEAR1 ,['Budget Summary Master'].PERIODID ,['Account Master'].ACTINDX ,['Account Master'].ACTNUMBR_1 ,['Account Master'].ACTNUMBR_2 ,['Account Master'].ACTNUMBR_3 ,['Account Master'].ACTNUMBR_4 ,['Account Master'].ACTNUMBR_5 ,['Account Master'].ACTNUMBR_6 ,['Account Master'].ACTNUMBR_7 ,['Account Master'].ACTNUMBR_8 ,['Account Master'].ACTNUMBR_9 ,['Account Master'].ACTNUMBR_10 ,['Account Index Master'].ACTNUMST ,['Account Master'].ACTDESCR ,['Account Category Master'].ACCATDSC ,['Account Master'].USERDEF1 ,['Account Master'].USERDEF2 ,['Account Master'].USRDEFS1 ,['Account Master'].USRDEFS2 ,['Budget Summary Master'].BUDGETAMT FROM GL00200 AS ['Budget Master'] INNER JOIN GL00201 AS ['Budget Summary Master'] ON ['Budget Summary Master'].BUDGETID = ['Budget Master'].BUDGETID INNER JOIN GL00100 AS ['Account Master'] ON ['Account Master'].ACTINDX = ['Budget Summary Master'].ACTINDX INNER JOIN GL00105 AS ['Account Index Master'] ON ['Account Index Master'].ACTINDX = ['Budget Summary Master'].ACTINDX INNER JOIN GL00102 AS ['Account Category Master'] ON ['Account Category Master'].ACCATNUM = ['Account Master'].ACCATNUM GO GRANT SELECT ON uv_AZRCRV_Budgets TO DYNGRP GO