Modification Writing Basics: Difference between revisions From Online Manual

Jump to: navigation, search
m (Bot: Automated text replacement (-  + ))
m (Bot: Automated text replacement (-< +<))
Line 10: Line 10:
'''2. Version Unspecific Mods'''
'''2. Version Unspecific Mods'''
Using the package manager it is possible to define different installation methods dependant on the version of SMF someone is running. This makes it possible to have a mod which would install on (for example) SMF 1.0 and also install on SMF 1.1 Beta 2, even though the actual changes are different. This is achieved by adding a "for" attribute to the package-info.xml file included with all package manager mods. So, to define a set of install actions for SMF 1.0 you would use the install tag as:
Using the package manager it is possible to define different installation methods dependant on the version of SMF someone is running. This makes it possible to have a mod which would install on (for example) SMF 1.0 and also install on SMF 1.1 Beta 2, even though the actual changes are different. This is achieved by adding a "for" attribute to the package-info.xml file included with all package manager mods. So, to define a set of install actions for SMF 1.0 you would use the install tag as:
<pre>&lt;install for="SMF 1.0"&gt;</pre>
<pre><install for="SMF 1.0"&gt;</pre>
If an install tag is encountered without the "for" attribute then it will be followed regardless of the current version. SMF follows install tags like an if/else statement, it will look at each set of installation tags in turn until it finds one it matches. The last set of install tags in a package should always be left without a "for" attribute set. This ensures that if your package is used on a future version of SMF, then some installation actions will exist. This is best shown with an example, a basic example of a possible package structure, with most tags missing is:
If an install tag is encountered without the "for" attribute then it will be followed regardless of the current version. SMF follows install tags like an if/else statement, it will look at each set of installation tags in turn until it finds one it matches. The last set of install tags in a package should always be left without a "for" attribute set. This ensures that if your package is used on a future version of SMF, then some installation actions will exist. This is best shown with an example, a basic example of a possible package structure, with most tags missing is:
<pre>&lt;install for="SMF 1.0 Beta 6, SMF 1.0 Beta 6 Public"&gt;
<pre><install for="SMF 1.0 Beta 6, SMF 1.0 Beta 6 Public"&gt;
   &lt;modification file="mod_actions_b6.xml" /&gt;
   <modification file="mod_actions_b6.xml" /&gt;
&lt;/install&gt;
</install&gt;
&lt;install for="SMF 1.0"&gt;
<install for="SMF 1.0"&gt;
   &lt;modification file="mod_actions_1_0.xml" /&gt;
   <modification file="mod_actions_1_0.xml" /&gt;
&lt;/install&gt;
</install&gt;
&lt;install&gt;
<install&gt;
   &lt;modification file="mod_actions_1_1.xml" /&gt;
   <modification file="mod_actions_1_1.xml" /&gt;
&lt;/install&gt;</pre>
</install&gt;</pre>
With a package like above, SMF will go through each set of tags, trying to find a set which match the current version. If by the last set of tags no version has been found, it will attempt to run the actions in the final set (Here mod_actions_1_1.xml). In this example that means anyone running SMF 1.1, 2.0 or 5.2 could still attempt to install the modification. Note exactly the same method is true for uninstall actions.
With a package like above, SMF will go through each set of tags, trying to find a set which match the current version. If by the last set of tags no version has been found, it will attempt to run the actions in the final set (Here mod_actions_1_1.xml). In this example that means anyone running SMF 1.1, 2.0 or 5.2 could still attempt to install the modification. Note exactly the same method is true for uninstall actions.


Line 43: Line 43:
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";</pre>
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";</pre>
And I need my mod to change this query to also retrieve a new column I added to the members table called &#039;custom&#039;. I could do a search on the whole query as below:
And I need my mod to change this query to also retrieve a new column I added to the members table called &#039;custom&#039;. I could do a search on the whole query as below:
<pre>&lt;search for&gt;
<pre><search for&gt;
       $select_columns = "
       $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
Line 61: Line 61:
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
&lt;/seach for&gt;
</seach for&gt;
&lt;replace&gt;
<replace&gt;
       $select_columns = "
       $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
Line 80: Line 80:
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
&lt;/replace&gt;</pre>
</replace&gt;</pre>
However, this has two problems. Firstly, if this query ever gets modified even slightly in the future then the change will fail, and in a big query like this change is inevitably going to occur - also if another modification adds a variable before me, then my search will fail. So alternatively, how about searching for the smallest unique string possible, and just replacing that?
However, this has two problems. Firstly, if this query ever gets modified even slightly in the future then the change will fail, and in a big query like this change is inevitably going to occur - also if another modification adds a variable before me, then my search will fail. So alternatively, how about searching for the smallest unique string possible, and just replacing that?
<pre>&lt;search for&gt;
<pre><search for&gt;
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
&lt;/search for&gt;
</search for&gt;
&lt;replace&gt;
<replace&gt;
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce, mem.custom,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce, mem.custom,
&lt;/replace&gt;</pre>
</replace&gt;</pre>
That&#039;s better, there&#039;s much less chance of this failing in the future as SMF grows, but what would make this even better is if instead of replacing code we added code on a seperate line, like so:
That&#039;s better, there&#039;s much less chance of this failing in the future as SMF grows, but what would make this even better is if instead of replacing code we added code on a seperate line, like so:
<pre>&lt;search for&gt;
<pre><search for&gt;
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
&lt;/search for&gt;
</search for&gt;
&lt;add after&gt;
<add after&gt;
         mem.custom,
         mem.custom,
&lt;/add after&gt;</pre>
</add after&gt;</pre>
Although not as nice to look at, the fact that this has not changed the original line means any other mod that wants to change this query can do the same thing, and this will never fail. Generally, using add before or add after will mean your modifications has less chance of going "Out of date".
Although not as nice to look at, the fact that this has not changed the original line means any other mod that wants to change this query can do the same thing, and this will never fail. Generally, using add before or add after will mean your modifications has less chance of going "Out of date".


Line 111: Line 111:
'''2. Version Inspecific Mods'''
'''2. Version Inspecific Mods'''
Using the package manager it is possible to define different installation methods dependant on the version of SMF someone is running. This makes it possible to have a mod which would install on (for example) SMF 1.0 and also install on SMF 1.1 Beta 2, even though the actual changes are different. This is achieved by adding a "for" attribute to the package-info.xml file included with all package manager mods. So, to define a set of install actions for SMF 1.0 you would use the install tag as:
Using the package manager it is possible to define different installation methods dependant on the version of SMF someone is running. This makes it possible to have a mod which would install on (for example) SMF 1.0 and also install on SMF 1.1 Beta 2, even though the actual changes are different. This is achieved by adding a "for" attribute to the package-info.xml file included with all package manager mods. So, to define a set of install actions for SMF 1.0 you would use the install tag as:
{{code|1=&lt;install for="1.0"&gt;}}
{{code|1=<install for="1.0"&gt;}}
If an install tag is encountered without the "for" attribute then it will be followed regardless of the current version. SMF follows install tags like an if/else statement, it will look at each set of installation tags in turn until it finds one it matches. The last set of install tags in a package should always be left without a "for" attribute set. This ensures that if your package is used on a future version of SMF, then some installation actions will exist. This is best shown with an example, a basic example of a possible package structure, with most tags missing is:
If an install tag is encountered without the "for" attribute then it will be followed regardless of the current version. SMF follows install tags like an if/else statement, it will look at each set of installation tags in turn until it finds one it matches. The last set of install tags in a package should always be left without a "for" attribute set. This ensures that if your package is used on a future version of SMF, then some installation actions will exist. This is best shown with an example, a basic example of a possible package structure, with most tags missing is:
{{code|1=&lt;install for="1.1"&gt;
{{code|1=<install for="1.1"&gt;
   &lt;modification&gt;modifications11.xml&lt/modification&gt;
   <modification&gt;modifications11.xml&lt/modification&gt;
&lt;/install&gt;
</install&gt;


&lt;install for="2.0"&gt;
<install for="2.0"&gt;
   &lt;modification&gt;modifications20.xml&lt/modification&gt;
   <modification&gt;modifications20.xml&lt/modification&gt;
&lt;/install&gt;
</install&gt;


&lt;install&gt;
<install&gt;
   &lt;modification&gt;modifications.xml&lt/modification&gt;
   <modification&gt;modifications.xml&lt/modification&gt;
&lt;/install&gt;}}
</install&gt;}}
With a package like above, SMF will go through each set of tags, trying to find a set which match the current version. If by the last set of tags no version has been found, it will attempt to run the actions in the final set (Here modifications.xml). In this example that means anyone running SMF 1.1, 2.0 or 5.2 could still attempt to install the modification. Note exactly the same method is true for uninstall actions.
With a package like above, SMF will go through each set of tags, trying to find a set which match the current version. If by the last set of tags no version has been found, it will attempt to run the actions in the final set (Here modifications.xml). In this example that means anyone running SMF 1.1, 2.0 or 5.2 could still attempt to install the modification. Note exactly the same method is true for uninstall actions.


Line 146: Line 146:
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";}}
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";}}
And I need my mod to change this query to also retrieve a new column I added to the members table called &#039;custom&#039;. I could do a search on the whole query as below:
And I need my mod to change this query to also retrieve a new column I added to the members table called &#039;custom&#039;. I could do a search on the whole query as below:
{{code|1=&lt;search position="replace"&gt;
{{code|1=<search position="replace"&gt;
       $select_columns = "
       $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
Line 164: Line 164:
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
&lt;/seach&gt;
</seach&gt;
&lt;add&gt;
<add&gt;
       $select_columns = "
       $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
Line 183: Line 183:
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
&lt;/add&gt;}}
</add&gt;}}
However, this has two problems. Firstly, if this query ever gets modified even slightly in the future then the change will fail, and in a big query like this change is inevitably going to occur - also if another modification adds a variable before me, then my search will fail. So alternatively, how about searching for the smallest unique string possible, and just replacing that?
However, this has two problems. Firstly, if this query ever gets modified even slightly in the future then the change will fail, and in a big query like this change is inevitably going to occur - also if another modification adds a variable before me, then my search will fail. So alternatively, how about searching for the smallest unique string possible, and just replacing that?
{{code|1=&lt;search position="replace"&gt;
{{code|1=<search position="replace"&gt;
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
&lt;/search&gt;
</search&gt;
&lt;add&gt;
<add&gt;
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce, mem.custom,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce, mem.custom,
&lt;/add&gt;}}
</add&gt;}}
That is better. There is much less chance of this failing in the future as SMF grows, but what would make this even better is if instead of replacing code we added code on a seperate line, like so:
That is better. There is much less chance of this failing in the future as SMF grows, but what would make this even better is if instead of replacing code we added code on a seperate line, like so:
{{code|1=&lt;search position="before"&gt;
{{code|1=<search position="before"&gt;
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
&lt;/search&gt;
</search&gt;
&lt;add&gt;
<add&gt;
         mem.custom,
         mem.custom,
&lt;/add&gt;}}
</add&gt;}}
Although not as nice to look at, the fact that this has not changed the original line means any other mod that wants to change this query can do the same thing, and this will never fail. Generally, using add before or add after will mean your modifications has less chance of going "Out of date".
Although not as nice to look at, the fact that this has not changed the original line means any other mod that wants to change this query can do the same thing, and this will never fail. Generally, using add before or add after will mean your modifications has less chance of going "Out of date".



Revision as of 23:34, 22 March 2011

Overview to the Coding Guidelines:

[" class="bbc_link">Layout Related Guidelines]|[" class="bbc_link">Code Related Guidelines]|[" class="bbc_link">Mod(ification) Writing Basics]

----

1. SQL Changes As a general rule of thumb mods should not alter the name, size or index of any table which is created as part of an SMF default installation - unless of course it is a mod whose aim is to enhance performance in some way. Instead it is assumed that mods will generally only add new tables, or otherwise add new columns to existing tables. If it is essential for a modification to alter the default SMF structure then it must provide, as part of the uninstall script, a means to put the tables back to their original form. The reason behind this is to ensure that as the user upgrades SMF in the future, that upgrades do not fail as a result of your modification.

If you have added your own tables and columns it is probably inappropriate to remove this structure upon uninstallation of the mod - as all collected data will be lost. The reason behind this is simply that many users may uninstall a modification due to an upgrade, and be upset at the loss of data. If you are determined to remove the structure upon uninstallation then please do so providing a separate package so the user is fully aware of what is being carried out in their database.

2. Version Unspecific Mods Using the package manager it is possible to define different installation methods dependant on the version of SMF someone is running. This makes it possible to have a mod which would install on (for example) SMF 1.0 and also install on SMF 1.1 Beta 2, even though the actual changes are different. This is achieved by adding a "for" attribute to the package-info.xml file included with all package manager mods. So, to define a set of install actions for SMF 1.0 you would use the install tag as:

<install for="SMF 1.0">

If an install tag is encountered without the "for" attribute then it will be followed regardless of the current version. SMF follows install tags like an if/else statement, it will look at each set of installation tags in turn until it finds one it matches. The last set of install tags in a package should always be left without a "for" attribute set. This ensures that if your package is used on a future version of SMF, then some installation actions will exist. This is best shown with an example, a basic example of a possible package structure, with most tags missing is:

<install for="SMF 1.0 Beta 6, SMF 1.0 Beta 6 Public">
  <modification file="mod_actions_b6.xml" />
</install>
<install for="SMF 1.0">
  <modification file="mod_actions_1_0.xml" />
</install>
<install>
  <modification file="mod_actions_1_1.xml" />
</install>

With a package like above, SMF will go through each set of tags, trying to find a set which match the current version. If by the last set of tags no version has been found, it will attempt to run the actions in the final set (Here mod_actions_1_1.xml). In this example that means anyone running SMF 1.1, 2.0 or 5.2 could still attempt to install the modification. Note exactly the same method is true for uninstall actions.

3. Minimizing Risk of Failure Obviously as SMF continues to develop code will change, and mods will after time need to be updated to install correctly. However, if care is taken when defining what a modifications to look for, the chance of failure can be reduced - saving time for both yourself as new versions come out - and for users who find mods no longer install after an upgrade. The simplest way to reduce the risk of failure is to make the search string only as long as is needed. Say for example I have this query below:

      $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         mem.personalText, mem.location, mem.gender, mem.avatar, mem.ID_MEMBER, mem.memberName, mem.realName,
         mem.emailAddress, mem.hideEmail, mem.dateRegistered, mem.websiteTitle, mem.websiteUrl, mem.birthdate,
         mem.location, mem.ICQ, mem.AIM, mem.YIM, mem.MSN, mem.posts, mem.lastLogin, mem.karmaGood, mem.karmaBad,
         mem.memberIP, mem.lngfile, mem.ID_GROUP, mem.ID_THEME, mem.buddy_list, mem.im_ignore_list, mem.im_email_notify,
         mem.timeOffset" . (!empty($modSettings['titlesEnable']) ? ', mem.usertitle' : '') . ", mem.timeFormat,
         mem.secretQuestion, mem.is_activated, mem.additionalGroups, mem.smileySet, mem.showOnline,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
         mg.onlineColor AS member_group_color, IFNULL(mg.groupName, '') AS member_group,
         pg.onlineColor AS post_group_color, IFNULL(pg.groupName, '') AS post_group,
         IF((mem.ID_GROUP = 0 OR mg.stars = ''), pg.stars, mg.stars) AS stars";
      $select_tables = "
         LEFT JOIN {$db_prefix}log_online AS lo ON (lo.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}attachments AS a ON (a.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";

And I need my mod to change this query to also retrieve a new column I added to the members table called 'custom'. I could do a search on the whole query as below:

<search for>
      $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         mem.personalText, mem.location, mem.gender, mem.avatar, mem.ID_MEMBER, mem.memberName, mem.realName,
         mem.emailAddress, mem.hideEmail, mem.dateRegistered, mem.websiteTitle, mem.websiteUrl, mem.birthdate,
         mem.location, mem.ICQ, mem.AIM, mem.YIM, mem.MSN, mem.posts, mem.lastLogin, mem.karmaGood, mem.karmaBad,
         mem.memberIP, mem.lngfile, mem.ID_GROUP, mem.ID_THEME, mem.buddy_list, mem.im_ignore_list, mem.im_email_notify,
         mem.timeOffset" . (!empty($modSettings['titlesEnable']) ? ', mem.usertitle' : '') . ", mem.timeFormat,
         mem.secretQuestion, mem.is_activated, mem.additionalGroups, mem.smileySet, mem.showOnline,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
         mg.onlineColor AS member_group_color, IFNULL(mg.groupName, '') AS member_group,
         pg.onlineColor AS post_group_color, IFNULL(pg.groupName, '') AS post_group,
         IF((mem.ID_GROUP = 0 OR mg.stars = ''), pg.stars, mg.stars) AS stars";
      $select_tables = "
         LEFT JOIN {$db_prefix}log_online AS lo ON (lo.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}attachments AS a ON (a.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
</seach for>
<replace>
      $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         mem.personalText, mem.location, mem.gender, mem.avatar, mem.ID_MEMBER, mem.memberName, mem.realName,
         mem.emailAddress, mem.hideEmail, mem.dateRegistered, mem.websiteTitle, mem.websiteUrl, mem.birthdate,
         mem.location, mem.ICQ, mem.AIM, mem.YIM, mem.MSN, mem.posts, mem.lastLogin, mem.karmaGood, mem.karmaBad,
         mem.memberIP, mem.lngfile, mem.ID_GROUP, mem.ID_THEME, mem.buddy_list, mem.im_ignore_list, mem.im_email_notify,
         mem.timeOffset" . (!empty($modSettings['titlesEnable']) ? ', mem.usertitle' : '') . ", mem.timeFormat,
         mem.secretQuestion, mem.is_activated, mem.additionalGroups, mem.smileySet, mem.showOnline,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce, mem.custom,
         mg.onlineColor AS member_group_color, IFNULL(mg.groupName, '') AS member_group,
         pg.onlineColor AS post_group_color, IFNULL(pg.groupName, '') AS post_group,
         IF((mem.ID_GROUP = 0 OR mg.stars = ''), pg.stars, mg.stars) AS stars";
      $select_tables = "
         LEFT JOIN {$db_prefix}log_online AS lo ON (lo.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}attachments AS a ON (a.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
</replace>

However, this has two problems. Firstly, if this query ever gets modified even slightly in the future then the change will fail, and in a big query like this change is inevitably going to occur - also if another modification adds a variable before me, then my search will fail. So alternatively, how about searching for the smallest unique string possible, and just replacing that?

<search for>
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
</search for>
<replace>
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce, mem.custom,
</replace>

That's better, there's much less chance of this failing in the future as SMF grows, but what would make this even better is if instead of replacing code we added code on a seperate line, like so:

<search for>
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
</search for>
<add after>
         mem.custom,
</add after>

Although not as nice to look at, the fact that this has not changed the original line means any other mod that wants to change this query can do the same thing, and this will never fail. Generally, using add before or add after will mean your modifications has less chance of going "Out of date".

Finally, whereever possible searching for anything version specific should definetly be avoided. If you want to add a string to Modifications.english.php, don't search for:

// Version: 1.1 Beta 1; Modifications

Instead either use the xml attribute to add your entry to the end of the file, or otherwise search for:

?>

And add your entries before it. This will avoid your modification being locked to that version number - and mean you don't have to edit your modification very time a new version comes out!

Overview to the Coding Guidelines

1. SQL Changes As a general rule of thumb mods should not alter the name, size or index of any table which is created as part of an SMF default installation - unless of course it is a mod whose aim is to enhance performance in some way. Instead it is assumed that mods will generally only add new tables, or otherwise add new columns to existing tables. If it is essential for a modification to alter the default SMF structure then it must provide, as part of the uninstall script, a means to put the tables back to their original form. The reason behind this is to ensure that as the user upgrades SMF in the future, that upgrades do not fail as a result of your modification.

If you have added your own tables and columns it is probably inappropriate to remove this structure upon uninstallation of the mod - as all collected data will be lost. The reason behind this is simply that many users may uninstall a modification due to an upgrade, and be upset at the loss of data. If you are determined to remove the structure upon uninstallation then please do so providing a separate package so the user is fully aware of what is being carried out in their database.

2. Version Inspecific Mods Using the package manager it is possible to define different installation methods dependant on the version of SMF someone is running. This makes it possible to have a mod which would install on (for example) SMF 1.0 and also install on SMF 1.1 Beta 2, even though the actual changes are different. This is achieved by adding a "for" attribute to the package-info.xml file included with all package manager mods. So, to define a set of install actions for SMF 1.0 you would use the install tag as:

<install for="1.0">

If an install tag is encountered without the "for" attribute then it will be followed regardless of the current version. SMF follows install tags like an if/else statement, it will look at each set of installation tags in turn until it finds one it matches. The last set of install tags in a package should always be left without a "for" attribute set. This ensures that if your package is used on a future version of SMF, then some installation actions will exist. This is best shown with an example, a basic example of a possible package structure, with most tags missing is:

<install for="1.1">
  <modification>modifications11.xml&lt/modification>
</install>

<install for="2.0">
  <modification>modifications20.xml&lt/modification>
</install>

<install>
  <modification>modifications.xml&lt/modification>
</install>

With a package like above, SMF will go through each set of tags, trying to find a set which match the current version. If by the last set of tags no version has been found, it will attempt to run the actions in the final set (Here modifications.xml). In this example that means anyone running SMF 1.1, 2.0 or 5.2 could still attempt to install the modification. Note exactly the same method is true for uninstall actions.

3. Minimizing Risk of Failure Obviously as SMF continues to develop code will change, and mods will after time need to be updated to install correctly. However, if care is taken when defining what a modifications to look for, the chance of failure can be reduced - saving time for both yourself as new versions come out - and for users who find mods no longer install after an upgrade. The simplest way to reduce the risk of failure is to make the search string only as long as is needed. Say for example I have this query below:

$select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         mem.personalText, mem.location, mem.gender, mem.avatar, mem.ID_MEMBER, mem.memberName, mem.realName,
         mem.emailAddress, mem.hideEmail, mem.dateRegistered, mem.websiteTitle, mem.websiteUrl, mem.birthdate,
         mem.location, mem.ICQ, mem.AIM, mem.YIM, mem.MSN, mem.posts, mem.lastLogin, mem.karmaGood, mem.karmaBad,
         mem.memberIP, mem.lngfile, mem.ID_GROUP, mem.ID_THEME, mem.buddy_list, mem.im_ignore_list, mem.im_email_notify,
         mem.timeOffset" . (!empty($modSettings['titlesEnable']) ? ', mem.usertitle' : '') . ", mem.timeFormat,
         mem.secretQuestion, mem.is_activated, mem.additionalGroups, mem.smileySet, mem.showOnline,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
         mg.onlineColor AS member_group_color, IFNULL(mg.groupName, '') AS member_group,
         pg.onlineColor AS post_group_color, IFNULL(pg.groupName, '') AS post_group,
         IF((mem.ID_GROUP = 0 OR mg.stars = ''), pg.stars, mg.stars) AS stars";
      $select_tables = "
         LEFT JOIN {$db_prefix}log_online AS lo ON (lo.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}attachments AS a ON (a.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";

And I need my mod to change this query to also retrieve a new column I added to the members table called 'custom'. I could do a search on the whole query as below:

<search position="replace">
      $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         mem.personalText, mem.location, mem.gender, mem.avatar, mem.ID_MEMBER, mem.memberName, mem.realName,
         mem.emailAddress, mem.hideEmail, mem.dateRegistered, mem.websiteTitle, mem.websiteUrl, mem.birthdate,
         mem.location, mem.ICQ, mem.AIM, mem.YIM, mem.MSN, mem.posts, mem.lastLogin, mem.karmaGood, mem.karmaBad,
         mem.memberIP, mem.lngfile, mem.ID_GROUP, mem.ID_THEME, mem.buddy_list, mem.im_ignore_list, mem.im_email_notify,
         mem.timeOffset" . (!empty($modSettings['titlesEnable']) ? ', mem.usertitle' : '') . ", mem.timeFormat,
         mem.secretQuestion, mem.is_activated, mem.additionalGroups, mem.smileySet, mem.showOnline,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
         mg.onlineColor AS member_group_color, IFNULL(mg.groupName, '') AS member_group,
         pg.onlineColor AS post_group_color, IFNULL(pg.groupName, '') AS post_group,
         IF((mem.ID_GROUP = 0 OR mg.stars = ''), pg.stars, mg.stars) AS stars";
      $select_tables = "
         LEFT JOIN {$db_prefix}log_online AS lo ON (lo.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}attachments AS a ON (a.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
</seach>
<add>
      $select_columns = "
         IFNULL(lo.logTime, 0) AS isOnline, IFNULL(a.ID_ATTACH, 0) AS ID_ATTACH, a.filename, mem.signature,
         mem.personalText, mem.location, mem.gender, mem.avatar, mem.ID_MEMBER, mem.memberName, mem.realName,
         mem.emailAddress, mem.hideEmail, mem.dateRegistered, mem.websiteTitle, mem.websiteUrl, mem.birthdate,
         mem.location, mem.ICQ, mem.AIM, mem.YIM, mem.MSN, mem.posts, mem.lastLogin, mem.karmaGood, mem.karmaBad,
         mem.memberIP, mem.lngfile, mem.ID_GROUP, mem.ID_THEME, mem.buddy_list, mem.im_ignore_list, mem.im_email_notify,
         mem.timeOffset" . (!empty($modSettings['titlesEnable']) ? ', mem.usertitle' : '') . ", mem.timeFormat,
         mem.secretQuestion, mem.is_activated, mem.additionalGroups, mem.smileySet, mem.showOnline,
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce, mem.custom,
         mg.onlineColor AS member_group_color, IFNULL(mg.groupName, '') AS member_group,
         pg.onlineColor AS post_group_color, IFNULL(pg.groupName, '') AS post_group,
         IF((mem.ID_GROUP = 0 OR mg.stars = ''), pg.stars, mg.stars) AS stars";
      $select_tables = "
         LEFT JOIN {$db_prefix}log_online AS lo ON (lo.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}attachments AS a ON (a.ID_MEMBER = mem.ID_MEMBER)
         LEFT JOIN {$db_prefix}membergroups AS pg ON (pg.ID_GROUP = mem.ID_POST_GROUP)
         LEFT JOIN {$db_prefix}membergroups AS mg ON (mg.ID_GROUP = mem.ID_GROUP)";
</add>

However, this has two problems. Firstly, if this query ever gets modified even slightly in the future then the change will fail, and in a big query like this change is inevitably going to occur - also if another modification adds a variable before me, then my search will fail. So alternatively, how about searching for the smallest unique string possible, and just replacing that?

<search position="replace">
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
</search>
<add>
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce, mem.custom,
</add>

That is better. There is much less chance of this failing in the future as SMF grows, but what would make this even better is if instead of replacing code we added code on a seperate line, like so:

<search position="before">
         mem.totalTimeLoggedIn, mem.ID_POST_GROUP, mem.notifyAnnouncements, mem.notifyOnce,
</search>
<add>
         mem.custom,
</add>

Although not as nice to look at, the fact that this has not changed the original line means any other mod that wants to change this query can do the same thing, and this will never fail. Generally, using add before or add after will mean your modifications has less chance of going "Out of date".

Finally, whereever possible searching for anything version specific should definetly be avoided. If you want to add a string to Modifications.english.php, don't search for:

// Version: 1.1 Beta 1; Modifications

Instead either use the xml attribute to add your entry to the end of the file, or otherwise search for:

?>

And add your entries before it. This will avoid your modification being locked to that version number - and mean you don't have to edit your modification very time a new version comes out!



Advertisement: