Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials - Web Development

1802 Articles
article-image-setting-plone-development-environment
Packt
21 Oct 2009
13 min read
Save for later

Setting up the Plone Development Environment

Packt
21 Oct 2009
13 min read
(For more resources on Plone, see here.) Prerequisites Pre-built packages for Zope and Plone are available for many operating systems. These can be tempting, but as developers it is normally better to configure the environment ourselves, in order to fully understand and control it. During development, we need write access to the Python code and configuration files. We may also need to run different versions of Zope and Plone in parallel. We will assume that you have at least the following as part of your regular development environment:   Python 2.4. We will assume that Python 2.4 gets invoked when you run python on the command line. Unfortunately, Zope, at version 2.10, does not yet support Python 2.5. Note that many Linux distributions have two packages for Python—one containing the binaries, and one containing header files (typically called python-dev or something similar). You will need both in order to compile Zope. PIL, the Python Imaging Library (http://www.pythonware.com/products/pil), should be installed for this Python interpreter. elementtree, an XML processing library, is required for Plone to start up. Most operating systems have packages for this. It can also be downloaded from http://effbot.org/zone/element-index.htm. A programmer's text editor. Preferably one with Python, XML/HTML and CSS syntax highlighting. You should set up your editor so that a tab/indent is output as four spaces. This makes Python development a lot more predictable. A shell. Most examples in this book will show a Bash interpreter shell, though we will cover Windows syntax when it differs significantly. Bear in mind that path separators on Windows are backslashes (), while other environments use forward slashes (/). Also, environment variables on Windows are referred to as %NAME%, while in most Unix shells, including Bash, variables are dereferenced with $NAME. A Subversion client. We will show the command line client syntax, but you can use a graphical client if you are more comfortable with that. Subversion can be obtained from http://subversion.tigris.org. A C compiler. You will need this to compile Zope. The venerable gcc is fine on UNIX-like systems. On Windows you probably want mingw32 (http://www.mingw.org). Alternatively, you can use a Zope Windows installer to get a binary Zope distribution.   Quick Start Understanding your development environment is an important step in becoming a productive developer. If you need to get up and running quickly, however, and you have the prerequisites outlined above in order, here are the key steps. We will assume you have Python 2.4 as your main Python interpreter. First download easy_install if you do not have it already, and use it to install ZopeSkel:   $ wget http://peak.telecommunity.com/dist/ez_setup.py   $ python ez_setup.py   $ easy_install ZopeSkel Note: If you do not have wget (e.g. because you are using Windows), you can just as easily download the ez_setup.py script using a web browser. When you run this script, it will install the easy_install binary to a directory that may not already be in your system PATH. Watch the ez_setup.py output to identify this directory. If it is not in your PATH, you should add it, allowing you to run easy_install as shown. Then, use paster, which was installed as a dependency of ZopeSkel, to create a new buildout. This folder holds our source code and dependencies, including the Zope application server:   $ paster create -t plone3_buildout myproject You can accept the defaults for all the questions, except for the password, which you must enter. Then, build the environment like so:   $ cd myproject   $ python bootstrap.py   $ ./bin/buildout Note: If you are using Windows, be sure to read the README.txt file that is generated in the myproject directory before running the buildout command. This last step may take some time, and you will need a live Internet connection.When it is complete, you can start Zope with:   $ ./bin/instance fg Go to http://localhost:8080/manage and you should see the Zope Management Interface. Use the drop-down box to add a Plone Site. If you call this mysite, it will beaccessible from http://localhost:8080/mysite. Glossary Let us now take a step back and consider our development environment in more detail. The table below summarizes the various terms and technologies that you will encounter in this chapter. It pays to be familiar with these names, because you will find them again not only throughout this book, but also in other Plone documentation. Term Definition Zope installation Zope consists of Python code, C extensions, configuration files, documentation, scripts, and utilities. Collectively, these are known as the Zope installation. Software home The part of the Zope installation that contains the main Zope runtime. This is found in the lib/python directory of the Zope installation. The full path is assigned to the $SOFTWARE_HOME environment variable when Zope is run. Zope instance The same Zope installation can be used to power multiple Zope servers, possibly running concurrently on different ports. Each instance has a directory containing a configuration file, instance-specific software components (e.g. an installation of Plone), and the local Zope database storage. Instance home When a Zope instance is running, the $INSTANCE_HOME environment variable refers to the directory where the instance is set up. Package A generic term for a distributable bundle of Python modules and supporting files. Product The traditional way to redistribute software for Zope 2 is in a "Product", which we will sometimes refer to as an "old-style Product". Products are placed in a special directory (Products/) and automatically discovered by Zope. The term "product" is also used more generally to refer to add-on components that extend Plone, even if they are not actually packaged as old-style Products. Egg A more generic and flexible alternative to products. Eggs are not specifi c to Zope, and Zope has only recently been made egg-aware. In addition to code, eggs contain metadata such as version, dependencies, and license information. Egg management tools can use this information to manage concurrent versions or automatically fetch dependencies, for example. $PYTHONPATH The $PYTHONPATH environment variable lists the directories containing Python packages that should be available at run time. It can also reference specific eggs. You should not have to set this manually. setuptools A Python library, which extends Python's built-in distutils package to support extended egg metadata and offers enhanced functionality when using software packaged as eggs. The Cheese Shop Also known as PyPI (Python Package Index). An online repository of Python eggs. Anyone can upload a package here. Egg-aware tools can use the Cheese Shop to locate dependencies and download them automatically when installing other eggs. easy_install A command-line tool, which searches the Cheese Shop for a given package, and downloads and installs it. Note that, by default, easy_install puts packages in the global site-packages folder for the Python interpreter that was used to install easy_install itself. Normally, we want our Plone packages to be local to a specific Zope instance, necessitating different tools. paster (Paste Script) paster, part of the Python Paste package, is a command runner. The paster create command invokes Paste Script templates, which are used to create skeleton packages based on command-line options and questions. ZopeSkel A collection of Paste Script templates for Zope and Plone development. We will use this to create new egg-ready packages, as well as the buildout that manages our development environment. Buildout (zc.buildout) A "buildout", using the zc.buildout toolset, is a self-contained environment that is controlled through a single configuration file (buildout.cfg). We will use it to download, install, and configure Zope, Plone, and other dependencies. Buildouts are "repeatable", meaning that they can be used to replicate a particular setup across multiple servers or developers' machines.     Understanding Eggs and Setuptools Python eggs are not specific to Zope or Plone. However, since Zope has only recently become egg-aware, they are new to many developers. Traditionally, almost all Zope add-on products, including Plone, have been distributed as Zope products. These are fairly easy to manage—you typically copy or symlink them into $INSTANCE_HOME/Products. Zope will scan this directory on startup, taking care of any product initialization and registration. However, code inside products is nearly impossible to re-use outside Zope because Zope does magic things with the Products.* namespace. Further, the namespace quickly becomes crowded, which deters developers from breaking up functionality into smaller, more re-usable and better-isolated packages. The Zope 3 philosophy is to be as close to "plain Python" as possible, and that means distributing code as such small packages. So long as its dependencies are in order, any package should be able to run in any environment. For example, the zope.interface package is used by the Twisted project, which is not otherwise dependent on Zope. This design goal has made it much easier to adopt Zope 3 packages in Zope2 and Plone. Starting with Plone 3, the Plone community has also embraced "plain Python" packages and uses them wherever possible. A number of packages, such as plone.memoize and plone.portlets are generic enough to work without any dependencies on the rest of Plone. Others are more specific to Plone and live in the plone.app namespace, such as plone.app.layout and plone.app.portlets, thelatter containing Plone-centric extensions to the generic plone.portlets package. All that is needed to use these packages is a sensible $PYTHONPATH. Thus, we can copy or link packages into $INSTANCE_HOME/lib/python/plone/memoize, lib/python/plone/portlets, lib/python/plone/app/portlets, and so forth for Zope to find them. This works, but it is pretty tedious when there are many packages, and it can become outright confusing when there are nested namespaces being used bymultiple packages. Luckily, other Python programmers have solved these problems, first creating distutils, then its successor setuptools and with setuptools, Python eggs. Note: For the setuptools documentation, see http://peak.telecommunity.com/DevCenter/setuptools. Installing Eggs When using setuptools, each project or package lives in a directory that has a toplevel setup.py file. This contains metadata about the package itself, and declares its current version as well as any dependencies. Dependencies can be specifieddown to particular versions (e.g. ">=0.2,<1.0" means "later than version 0.2 but before version 1.0"). When a package is installed, setuptools will attempt to fulfilldependencies by downloading and installing them if necessary. If you have a setuptools-enabled package, you can use setup.py to install it globally, by running:   $ python setup.py install This will copy the source code to the system-wide Python site-packages directory. Having to re-run this command each time you make a change can make development a little awkward, so while you are working on a particular package,you can install a development egg. This is essentially a link to the package's source code that ensures it is added to the $PYTHONPATH. To install a development egg, run:   $ python setup.py develop New packages can be released as binary eggs for distribution, which are just ZIP files of the package with some additional metadata. You can build an egg from within apackage by running:   $ python setup.py bdist_egg The new egg will be placed in the dist sub-directory, which will be created if necessary. Eggs can be uploaded to the Cheese Shop, also known as PyPI (the PythonPackage Index). This central repository makes it easy to fi nd packages. You canbrowse packages at http://cheeseshop.python.org/pypi. New packages can beuploaded via this website, or directly from the command line:   $ python setup.py egg_info -RDb "" sdist bdist_egg register upload You will be asked to specify or create a Cheese Shop account if this is the first time you run this command. A script called easy_install lets you search the Cheese Shop (or a similar index, if you specify a URL) for packages that it can download and install into the global Python environment. Dependencies will be included automatically. This is great for simple libraries and end-user applications, but less great when you are working on multiple Zope projects that may have different version requirements. This is why we tend to manage our eggs inside $INSTANCE_HOME or, as you will see in the next section, as part of a controlled buildout. Note: A tool called workingenv.py can create a mini-environment where global commands are restricted to a particular directory. We will not cover workingenv in this book, but you can download and read more about it at http://cheeseshop.python.org/pypi/workingenv.py. When eggs are activated (either explicitly, or implicitly by being unambiguouslyfound in the $PYTHONPATH), they can be discovered by other packages listening for plug-ins, using a mechanism called entry points (see http://peak.telecommunity.com/DevCenter/setuptools#dynamic-discovery-of-services-and-plugins). Zope does not yet directly use entry points, so we will not be covering them inany detail here. However, entry points are a very powerful system, and there areproposals to let Zope's discovery of packages use entry points instead of scanningmagic directories. With eggs, we therefore have the tools to manage multiple packages, from differentdevelopers and repositories, possibly across multiple versions. By using the packagemanagement tools that the rest of the Python community employs, we also make iteasier to re-use other libraries and share our own code with outside developers. Automating the Build Process with zc.buildout Creating a Zope instance and copying or linking packages into $INSTANCE_HOME/lib/python as is done in the manual process is not too difficult, but this approach has a few limitations. The process is manual and cumbersome to repeat across multiple environments. Multiple developers working on the same project may share the code in eggs and products by using a version control system such as Subversion. However, each developer would be responsible for setting up their development environment, and subtle differences may cause problems that are difficult to debug. Packages are installed manually, and so cannot benefit from setuptools' ability to manage dependencies and updates. Complex deployments that include other libraries, non-python code, or specific configurations will also need to be taken care of manually. Luckily, there are tools to make deployment easier. zc.buildout is one such tool, written largely by Zope founder Jim Fulton at Zope Corporation. It makes heavyuse of eggs and setuptools and is very flexible in supporting a wide range of deployment scenarios. Central to a buildout (i.e. what zc.buildout is managing for us) is a file called buildout.cfg. This specifies various options, including a list of parts, which will be executed when the buildout is run. Each part is associated with a recipe—a named egg, which will be called upon to parse the options provided, and perform a particular task, such as building a Zope instance or downloading Plone. A project-specific buildout directory can be checked into a version control system and shared among developers. It can also be used to replicate a particular environment across different servers with a high degree of predictability. By writing custom recipes, you can make zc.buildout do almost anything. Writing a recipe is not particularly hard, and there are plenty of examples and generic solutions available. However, we will not cover creating new recipes in this book, because all the recipes we need already exist.
Read more
  • 0
  • 0
  • 2318

article-image-testing-save-dialog-java-using-swing
Packt
21 Oct 2009
10 min read
Save for later

Testing a Save As Dialog in Java using Swing

Packt
21 Oct 2009
10 min read
In this article, we will use an example from our demonstration application, Ikon Do It. The code from this article is in the packages jet.ikonmaker and jet.ikonmaker.test. Click here to download the code. The Ikon Do It 'Save as' Dialog The 'Ikon Do It' application has a Save as function that allows the icon on which we are currently working to be saved with another name. Activating the Save as button displays a very simple dialog for entering the new name. The following figure shows the 'Ikon Do It' Save as dialog. Not all values are allowed as possible new names. Certain characters (such as '*') are prohibited, as are names that are already used. In order to make testing easy, we implemented the dialog as a public class called SaveAsDialog, rather than as an inner class of the main user interface component. We might normally balk at giving such a trivial component its own class, but it is easier to test when written this way and it makes a good example. Also, once a simple version of this dialog is working and tested, it is possible to think of enhancements that would definitely make it too complex to be an inner class. For example, there could be a small status area that explains why a name is not allowed (the current implementation just disables the Ok button when an illegal name is entered, which is not very user-friendly). The API for SaveAsDialog is as follows. Names of icons are represented by IkonName instances. A SaveAsDialog is created with a list of existing IkonNames. It is shown with a show() method that blocks until either Ok or Cancel is activated. If Ok is pressed, the value entered can be retrieved using the name() method. Here then are the public methods: public class SaveAsDialog { public SaveAsDialog( JFrame owningFrame, SortedSet<IkonName> existingNames ) { ... } /** * Show the dialog, blocking until ok or cancel is activated. */ public void show() { ... } /** * The most recently entered name. */ public IkonName name() { ... } /** * Returns true if the dialog was cancelled. */ public boolean wasCancelled() { ... }} Note that SaveAsDialog does not extend JDialog or JFrame, but will use delegation. Also note that the constructor of SaveAsDialog does not have parameters that would couple it to the rest of the system. This means a handler interface is not required in order to make this simple class testable. The main class uses SaveAsDialog as follows: private void saveAs() { SaveAsDialog sad = new SaveAsDialog( frame, store.storedIkonNames() ); sad.show(); if (!sad.wasCancelled()) { //Create a copy with the new name. IkonName newName = sad.name(); Ikon saveAsIkon = ikon.copy( newName ); //Save and then load the new ikon. store.saveNewIkon( saveAsIkon ); loadIkon( newName ); }} Outline of the Unit Test The things we want to test are: Initial settings: The text field is empty. The text field is a sensible size. The Ok button is disabled. The Cancel button is enabled. The dialog is a sensible size. Usability: The Escape key cancels the dialog. The Enter key activates the Ok button. The mnemonics for Ok and Cancel work. Correctness. The Ok button is disabled if the entered name: Contains characters such as '*', '', '/'. Is just white-space. Is one already being used. API test: unit tests for each of the public methods. As with most unit tests, our test class has an init() method for getting an object into a known state, and a cleanup() method called at the end of each test. The instance variables are: A JFrame and a set of IkonNames from which the SaveAsDialog can be constructed A SaveAsDialog, which is the object under test. A UserStrings and a UISaveAsDialog (listed later on) for manipulating the SaveAsDialog with keystrokes. A ShowerThread, which is a Thread for showing the SaveAsDialog. This is listed later on. The outline of the unit test is: public class SaveAsDialogTest { private JFrame frame; private SaveAsDialog sad; private IkonMakerUserStrings = IkonMakerUserStrings.instance(); private SortedSet<IkonName> names; private UISaveAsDialog ui; private Shower shower; ... private void init() { ... } private void cleanup() { ... } private class ShowerThread extends Thread { ... }} UI Helper Methods A lot of the work in this unit test will be done by the static methods in our helper class, UI. Some of these are isEnabled(), runInEventThread(), and findNamedComponent(). The new methods are listed now, according to their function. Dialogs If a dialog is showing, we can search for a dialog by name, get its size, and read its title: public final class UI { ... /** * Safely read the showing state of the given window. */ public static boolean isShowing( final Window window ) { final boolean[] resultHolder = new boolean[]{false}; runInEventThread( new Runnable() { public void run() { resultHolder[0] = window.isShowing(); } } ); return resultHolder[0]; } /** * The first found dialog that has the given name and * is showing (though the owning frame need not be showing). */ public static Dialog findNamedDialog( String name ) { Frame[] allFrames = Frame.getFrames(); for (Frame allFrame : allFrames) { Window[] subWindows = allFrame.getOwnedWindows(); for (Window subWindow : subWindows) { if (subWindow instanceof Dialog) { Dialog d = (Dialog) subWindow; if (name.equals( d.getName() ) && d.isShowing()) { return (Dialog) subWindow; } } } } return null; } /** * Safely read the size of the given component. */ public static Dimension getSize( final Component component ) { final Dimension[] resultHolder = new Dimension[]{null}; runInEventThread( new Runnable() { public void run() { resultHolder[0] = component.getSize(); } } ); return resultHolder[0]; } /** * Safely read the title of the given dialog. */ public static String getTitle( final Dialog dialog ) { final String[] resultHolder = new String[]{null}; runInEventThread( new Runnable() { public void run() { resultHolder[0] = dialog.getTitle(); } } ); return resultHolder[0]; } ...} Getting the Text of a Text Field The method is getText(), and there is a variant to retrieve just the selected text: //... from UI/** * Safely read the text of the given text component. */public static String getText( JTextComponent textComponent ) { return getTextImpl( textComponent, true );}/** * Safely read the selected text of the given text component. */public static String getSelectedText( JTextComponent textComponent ) { return getTextImpl( textComponent, false );}private static String getTextImpl( final JTextComponent textComponent, final boolean allText ) { final String[] resultHolder = new String[]{null}; runInEventThread( new Runnable() { public void run() { resultHolder[0] = allText ? textComponent.getText() : textComponent.getSelectedText(); } } ); return resultHolder[0];} Frame Disposal In a lot of our unit tests, we will want to dispose of any dialogs or frames that are still showing at the end of a test. This method is brutal but effective: //... from UIpublic static void disposeOfAllFrames() { Runnable runnable = new Runnable() { public void run() { Frame[] allFrames = Frame.getFrames(); for (Frame allFrame : allFrames) { allFrame.dispose(); } } }; runInEventThread( runnable );} Unit Test Infrastructure Having seen the broad outline of the test class and the UI methods needed, we can look closely at the implementation of the test. We'll start with the UI Wrapper class and the init() and cleanup() methods. The UISaveAsDialog Class UISaveAsDialog has methods for entering a name and for accessing the dialog, buttons, and text field. The data entry methods use a Cyborg, while the component accessor methods use UI: public class UISaveAsDialog { Cyborg robot = new Cyborg(); private IkonMakerUserStrings us = IkonMakerUserStrings.instance(); protected Dialog namedDialog; public UISaveAsDialog() { namedDialog = UI.findNamedDialog( SaveAsDialog.DIALOG_NAME ); Waiting.waitFor( new Waiting.ItHappened() { public boolean itHappened() { return nameField().hasFocus(); } }, 1000 ); } public JButton okButton() { return (JButton) UI.findNamedComponent( IkonMakerUserStrings.OK ); } public Dialog dialog() { return namedDialog; } public JButton cancelButton() { return (JButton) UI.findNamedComponent( IkonMakerUserStrings.CANCEL ); } public JTextField nameField() { return (JTextField) UI.findNamedComponent( IkonMakerUserStrings.NAME ); } public void saveAs( String newName ) { enterName( newName ); robot.enter(); } public void enterName( String newName ) { robot.selectAllText(); robot.type( newName ); } public void ok() { robot.altChar( us.mnemonic( IkonMakerUserStrings.OK ) ); } public void cancel() { robot.altChar( us.mnemonic( IkonMakerUserStrings.CANCEL ) ); }} A point to note here is the code in the constructor that waits for the name text field to have focus. This is necessary because the inner workings of Swing set the focus within a shown modal dialog as a separate event. That is, we can't assume that showing the dialog and setting the focus within it happen within a single atomic event. Apart from this wrinkle, all of the methods of UISaveDialog are straightforward applications of UI methods. The ShowerThread Class Since SaveAsDialog.show() blocks, we cannot call this from our main thread; instead we spawn a new thread. This thread could just be an anonymous inner class in the init() method: private void init() { //Not really what we do... //setup...then launch a thread to show the dialog. //Start a thread to show the dialog (it is modal). new Thread( "SaveAsDialogShower" ) { public void run() { sad = new SaveAsDialog( frame, names ); sad.show(); } }.start(); //Now wait for the dialog to show...} The problem with this approach is that it does not allow us to investigate the state of the Thread that called the show() method. We want to write tests that check that this thread is blocked while the dialog is showing. Our solution is a simple inner class: private class ShowerThread extends Thread { private boolean isAwakened; public ShowerThread() { super( "Shower" ); setDaemon( true ); } public void run() { Runnable runnable = new Runnable() { public void run() { sad.show(); } }; UI.runInEventThread( runnable ); isAwakened = true; } public boolean isAwakened() { return Waiting.waitFor( new Waiting.ItHappened() { public boolean itHappened() { return isAwakened; } }, 1000 ); }} The method of most interest here is isAwakened(), which waits for up to one second for the awake flag to have been set, this uses a class, Waiting. Another point of interest is that we've given our new thread a name (by the call super("Shower") in the constructor). It's really useful to give each thread we create a name. The init() Method The job of the init() method is to create and show the SaveAsDialog instance so that it can be tested: private void init() { //Note 1 names = new TreeSet<IkonName>(); names.add( new IkonName( "Albus" ) ); names.add( new IkonName( "Minerva" ) ); names.add( new IkonName( "Severus" ) ); names.add( new IkonName( "Alastair" ) ); //Note 2 Runnable creator = new Runnable() { public void run() { frame = new JFrame( "SaveAsDialogTest" ); frame.setVisible( true ); sad = new SaveAsDialog( frame, names ); } }; UI.runInEventThread( creator ); //Note 3 //Start a thread to show the dialog (it is modal). shower = new ShowerThread(); shower.start(); //Note 4 //Wait for the dialog to be showing. Waiting.waitFor( new Waiting.ItHappened() { public boolean itHappened() { return UI.findNamedFrame( SaveAsDialog.DIALOG_NAME ) != null; } }, 1000 ); //Note 5 ui = new UISaveAsDialog();} Now let's look at some of the key points in this code. Note 1: In this block of code we create a set of IkonNames with which our SaveAsDialog can be created. Note 2: It's convenient to create and show the owning frame and create the SaveAsDialog in a single Runnable. An alternative would be to create and show the frame with a UI call and use the Runnable just for creating the SaveAsDialog. Note 3: Here we start our Shower, which will call the blocking show() method of SaveAsDialog from the event thread. Note 4: Having called show() via the event dispatch thread from our Shower thread, we need to wait for the dialog to actually be showing on the screen. The way we do this is to search for a dialog that is on the screen and has the correct name. Note 5: Once the SaveAsDialog is showing, we can create our UI Wrapper for it. The cleanup() Method The cleanup() method closes all frames in a thread-safe manner: private void cleanup() { UI.disposeOfAllFrames();}
Read more
  • 0
  • 0
  • 4122

article-image-supporting-editorial-team-drupal-6
Packt
21 Oct 2009
16 min read
Save for later

Supporting an Editorial Team in Drupal 6

Packt
21 Oct 2009
16 min read
What you will do In this article, you will: Create a team Add Roles to support the team Define new Node Content types Configure permissions to support the Roles Handle a former (and disgruntled) team member The Creative team Let's take a quick look at Drupal's jargon regarding teams. Users—the logins of the individuals that make up a team Roles—the different 'job descriptions' based on a person's responsibilities Permissions—the granting of authorization to perform a Drupal function As the system administrator, you are authorized to perform any action within the Drupal environment, but you would not want every member of a team to have this absolute capability, or else you would soon have chaos. Let's first create a team. Then, we will look at assimilating that team into the Drupal environment. Our Creative team will be made up of individuals, each having one or more of the responsibilities mentioned below (Note: the titles are not Drupal terms): Copy Writers—are the writers of short articles Feature Writers—are the writers of long pieces, in which style matters a much as content Ad Writers—are the writers of internal and external advertising that will appear in blocks Proofreaders—are the reviewers who check pieces for spelling, grammar and usage errors Associate Editors—are the reviewers that are concerned with style, readability, and continuity Style Editors—are responsible for the formatting of content Graphic Artists—are the creators of the illustrations and images that are used as copy Senior Editor—is responsible for the quality of all of the above Moderator—manages postings by site visitors, such as comments and blog posts Blogger—creates blog entries Administrator—addresses the aspects of the site unrelated to content With our team assembled, let's move on to creating the roles in our site. Roles Drupal comes with three roles installed: creator (also known as userID1), authenticated user and anonymous user. Only the latter two are listed when assigning permissions, because the creator role can do everything, including things that you might not want the administrator to be able to do. It's best not to use the creator's login as the administrator login. A separate administrator role should be created and granted the appropriate permissions. So, looking at the list above, we will need to create roles for all of our team members. Creating roles in Drupal is a quick and easy process. Let's create them. Activity 1: Creating Roles The Name of the role is assigned as per the responsibilities of the team member. Login as the administrator. Select the User management option. Select the Roles option. Enter the name of the role in the text box, as shown in the following screenshot, and then click on the Add role button. We'll add the rest of the roles in the same way. After a couple of minutes, we have the entire team added, as seen in following screenshot. The edit role links are locked for anonymous user and authenticated user, because those roles should remain constant and never be edited or deleted. Node Content types The default installation of Drupal contains two Node Content types namely: Page and Story. Some modules, when activated, create additional Node Content types. One such example is the Blog entry, and another is an Event, which is used when using an event calendar. We're using the term Node Content to differentiate content nodes in Drupal, such as Pages and Stories, from other non-node types of content, such as Blocks, which is the generic term for anything on the page. What is the purpose of having different Node Content types? If we want a feature  writer to be able to create Features, then how do we accomplish that? Currently, we have Stories and Pages as our Node Content types. So, if we give the Feature writer the ability to create a Page, then what differentiates that Page from any other Page on our site? If we consider a Page as a Feature, then anyone who can create a Page has created a Feature, but that's not right, because not every Page is a Feature. Activity 2: Node Content for our Roles Because we have role types that we want to limit to working with their respective Node Content types, we will need to create those Node Content types. We will assign a Node Content type of Feature for Feature Writers, Ads for Ad Writers, and so on. Let's create them. From the admin menu, we'll select Content management. On the Content management page, we'll choose Content types. The Node Content types are listed, and from the top of the page we'll select Add content type. We're going to start with the Feature writer, so in the Name field we'll enter Feature. The next field, Type, determines the term that will be used to construct the default URL for this Node Content type. We'll enter feature as the text value for this field. In the Description field, we'll enter a short description, which will appear next to the Node Content type's link on the admin page, as follows: Next, we'll click on the Workflow settings link to display the additional workflow fields. When our Feature Writer completes a piece, it will not be published immediately. It will have to be proofread and undergo an editorial review. So, we'll deselect the Published and Promote to front page boxes. At this point we've configured the new Node Content type as per our needs, so we'll click on the Save button, and then we can see it listed, as shown in the screenshot below. We already have a Node Content type of Blog entry, which was created by the Blog module. The only other Role that requires its own Node Content type is the Ad Writer. This is because the other Roles defined will only edit existing Node Content,  as opposed to creating it. It is here that we run into trouble. The pieces that are 'grabbed' by Drupal to appear (usually) at the center of the screen, which we have been referring to as Node Content, are nodes, whether a Page, a Story, or now a Feature. The small blocks that appear around the sides, or on top, or at the bottom, are Blocks. Because they are placed in those positions, and are not available for selection as Node Content, they are not nodes. The Benefit of BlocksWhen looking at a typical web page of a CMS site, you will see a main body area with Node Content, such as articles, and also small blocks of information elsewhere on the page, such as in the left and right margins, or along the top or bottom. The main content, nodes, are limited, as to where they appear. However, each of the blocks can be configured to appear on any or every page of the site. That is why ads are best created as blocks, so that they can be placed where they will be the most effective. Nodes are created via the Create content function, and that function is available from the front page to anyone who is granted the permission. Using the admin menu is not necessary. On the other hand, blocks are created and edited from the Block page, which is an admin function. Although we can grant that capability to a user without granting any other admin capabilities, it would be much better if we could have an Ad Writer create ads in the same way that they create other Node Content. The reason for this is that with nodes, separate permission can be given to create a node and to administer a node. With  blocks, there is only one permission. You can create, edit, delete, and rearrange all of the blocks, or none. This opens the door to an accidental disaster. We don't want the Ad Writer doing anything but creating ad copy. So, in order to address this concern, we've added a module to our site: Node blocks. This module allows us designate a Node Content type (other than Page and Story) to be used as a Block. With that in mind, let's create our final Node Content type. Where can you find this module? This module, as well as other modules, can be found at http://drupal.org/project/modules. Activity 3—creating a Block Node Content type We'll start by repeating Steps 1 to 3 from the previous activity. In the Title field, we'll type in Ad. In the Type field, we'll type in ad. For the description, we'll enter Advertisement copy that will be used as blocks. We'll click on Workflow settings and deselect Published and Promoted to front page, as we did with the Feature. There is a new heading in this dialog, Available as Block, as seen in the following screenshot. This comes from the module that we've added. We'll select Enabled, which will make any piece created with this Node Content type available as a Block. That's all we need to do, so now we'll save our new Node Content type   Permissions The way that we enable one user to do something that the other cannot is by creating different user types (which we have done), different Node Content types—where necessary—(which again has been done), and then assign permissions to the user types (which we'll do now). The administrator will not be listed as a user type under Permissions, because if permissions were accidentally removed from the administrator, there might be no other user type that has the permissions to restore them. Activity 4: Granting Permissions Let's now assign to the members of the Creative team the Permissions that suit them best. From the admin menu we'll select User management. On the User management page we'll choose Permissions. The screenshot below shows us the upper portion of the screen. There are numerous permissions, and we now have numerous User types, so the resulting grid is very large. Rather than step-by-step illustrations, I'll simply list each Role and the Permissions that should be enabled in the form of Heading→Permission. Ad Writer node module→access content node module→create ad content node module→delete any ad content node module→delete own ad content node module→edit any ad content node module→edit own ad content node module→view revisions fckeditor module→access fckeditor Because of the number of Node Content types, each having several permissions as seen above, combined with the permissions being alphabetical by verb within the heading, instead of Content type, the necessary permissions are somewhat distant from each other and require scrolling to find them all. Feature Writer node module→access content node module→create feature content node module→delete any feature content node module→delete own feature content node module→edit any feature content node module→edit own feature content node module→view revisions fckeditor module→access fckeditor Blogger blog module→create blog entries blog module→delete own blog entries blog module→edit own blog entries node module→access content node module→view revisions fckeditor module→access fckeditor Associate Editor—The Associate Editor is concerned with content, which means editing it. The ability to create or delete content, to affect where the content appears, and so on, is not required for this Role. fckeditor module→access fckeditor node module→access content node module→edit any ad content node module→edit any feature content node module→edit any page content node module→edit any story content node module→revert revisions node module→view revisions path module→create URL aliases Copy Writer fckeditor module→access fckeditor node module→access content node module→create page content node module→create story content node module→delete own page content node module→delete own story content node module→edit own page content node module→edit own story content node module→view revisions Graphic Artist blog module→edit any blog entry fckeditor module→access fckeditor fckeditor module→allow fckeditor fle uploads node module→access content node module→edit any ad content node module→edit any feature content node module→edit any page content node module→edit any story content Moderator blog module→edit any blog entry comment module→access comments comment module→administer comments fckeditor module→access fckeditor node module→access content node module→edit any ad content node module→edit any feature content node module→edit any page content node module→edit any story content Proofreader blog module→edit any blog entry fckeditor module→access fckeditor node module→access content node module→edit any ad content node module→edit any feature content node module→edit any page content node module→edit any story content Style Editor block module→administer blocks fckeditor module→access fckeditor fckeditor module→allow fckeditor fle uploads node module→access content node module→edit any ad content node module→edit any feature content node module→edit any page content node module→edit any story content Senior Editor block module→administer blocks blog module→delete any blog entry blog module→edit any blog entry comment module→access comments comment module→administer comments fckeditor module→access fckeditor fckeditor module→allow fckeditor fle upload node module→access content node module→delete any ad content node module→delete any feature content node module→delete any page content node module→delete any story content node module→delete revisions node module→edit any ad content node module→edit any feature content node module→edit any page content node module→edit any story content node module→revert revisions node module→view revisions path module→create URL aliases view module→access all views view module→administer views With that, we have assigned the required permissions to all of our team members, which will allow them to do their jobs, but keep them out of trouble! However, what do you do when someone intentionally gets into trouble? The disgruntled team member So, we've been marching along as one big happy team, and then it happens. Someone gets let go, and that someone isn't happy about it, to say the least. Of course, we'll remove that person's login, but there is public access to our site as well, in the form of comments. Is there a way for us to stop this person from looking for ways to annoy us, or worse? Yes! Activity 5: Blocking Let's now perform the tasks necessary to keep disgruntled employees (and trouble-makers) at bay. From the admin menu, select User management. On the User management page, we'll select the Access rules option. We'll choose the Add rule option on the Access rules page. On the Add rule page, we have the option to deny access to a user, email address, or host. The username and email address options will block someone from registering, but will not affect someone already registered. The host name will stop anyone with that host name from accessing the system at all. Wild cards can be used: % will match any number of characters, and _ will match one character. Allow rules can be used to give access to someone who would otherwise be blocked by a host or wild card rule. In our case, let's say that the disgruntled former team member is spamming our comments from a host called spamalot.com, and is doing it from many emails. The first thing we want to do is create a 'deny' rule that will deny access to anyone from that host, as shown in the following figure, and then click on the Add rule button. We're also going to create an email deny rule for %@spamalot.com. We shouldn't have to (as we've already denied the host, which in turn would include all of the emails from that host), but we need to, because the rules testing logic ignores that hierarchy at this time. Let's also say that we've received an email from someone whose email address is its_not_me@spamalot.com, who would like to be a member of our site, and we verify that this person is not our former team member. In such a scenario, we will need to create an Allow rule, as shown in the following screenshot, so that this person can get past our previous Deny rule. Our rules now appear, as shown below, when we click on the List button, which is at the top of the page. It's always good to check and make certain that we've created the rule(s) correctly. If we don't do this, then we might inadvertently block the wrong users. Let's click on the Check rules tab at the top of the Access rules page. In the email box, we'll first try disgruntled@spamalot.com. Next, we'll try its_not_me@spamalot.com. In this last activity we have created some access rules. Drupal uses these access rules to determine who can and cannot access the site. In some cases, you may be having difficulty with a particular user adding comments to your site. Of course, if you set comments to require moderation, then the questionable ones won't appear, but it can still be a pain having to review a steady stream of them. In that case, you can block a specific user. You might be having difficulty with comments from more than one user at a given email domain. You can, if you like, block everyone from that location. On the other hand, your site might be meant for users of a particular domain, perhaps a university. In that case, you can allow users from that domain and only them. Summary In this article we learned about: Roles—defining types of users Permissions—defining capabilities for each role Node Content types—as they apply to Roles Access Rules—for those pesky, misbehaving users These features have been explained and learned with the help of activities where we have: Created a team Added Roles to enable the team Defined new Node Content types to suit the requirements of some team members Configured permissions to support the Roles and Node Content types Handled a former (and disgruntled) team member
Read more
  • 0
  • 0
  • 2529

article-image-deploying-your-dotnetnuke-portal
Packt
21 Oct 2009
7 min read
Save for later

Deploying Your DotNetNuke Portal

Packt
21 Oct 2009
7 min read
Acquiring a Domain Name One of the most exciting parts of starting a website is acquiring a domain name. When selecting the perfect name there are a few things that you need to keep in mind: Keep it brief: The more letters that a user has to type in to get to your site the more difficult it is going to be for them to remember your site. The name you select will help to brand your site. If it is catchy then people will remember it more readily. Have alternative names in mind: As time goes on, great domain names are becoming fewer and fewer. Make sure you have a few alternatives to choose from. The first domain name you had in mind may already be taken so having a backup plan will help when you decide to purchase a name. Consider buying additional top-level domain names: Say you've already bought www.DanielsDoughnuts.com. You might want to purchase www.DanielsDoughnuts.net as well to protect your name. Once you have decided on the name you want for your domain, you will need to register it. There are dozens of different sites that allow you to register your domain name as well as search to see if it is available. Some of the better-known domain-registration sites are Register.com and NetworkSolutions.com. Both of these have been around a long time and have good reputations. You can also look into some of the discount registers like BulkRegister (http://www.BulkRegister.com) or Enom (http://www.enom.com). After deciding on your domain name and having it registered, you will need to find a place to physically host your portal. Most registration services will also give the ability to host your site with them but it is best to search for a provider that fits your site's needs. Finding a Hosting Provider When deciding on a provider to host your portal, you will need to consider a few things: Cost: This is of course one of the most important things to look at when looking for a provider. There are usually a few plans to select from. The basic plan usually allows you a certain amount of disk space for a very small price but has you share the server with numerous other websites. Most providers also offer dedicated (you get the server all to yourself) and semi-dedicated (you share with a few others). It is usually best to start with the basic plan and move up if the traffic on your site requires it. Windows servers: The provider you select needs to have Windows Server 200/2003 running IIS (Internet Information Services). Some hosts run alternatives to Microsoft like Linux and Apache web server. .NET framework: The provider's servers need to have the .NET framework version 1.1 installed. Most hosts have installed the framework on their servers, but not all. Make sure this is available because DotNetNuke needs this to run. Database availability: You will need database server availability to run DotNetNuke and Microsoft SQL Server is the preferred back-end. It is possible to run your site off Microsoft Access or MySQL (with a purchased provider), but I would not suggest it. Access does not hold up well in a multi-user platform and will slow down considerably when your traffic increases. Also, since most module developers target MS SQL, MySQL, while able to handle multiple users, does not have the module support. FTP access: You will need a way to post your DotNetNuke portal files to your site and the easiest way is to use FTP. Make sure that your host provides this option. E-mail server: A great deal of functionality associated with the DotNetNuke portal relies on being able to send out e-mails to users. Make sure that you will have the availability of an e-mail server. Folder rights: The ASPNET or NetworkService Account (depending on server) will need to have full permissions to the root and subfolders for your DotNetNuke application to run correctly. Make sure that your host either provides you with the ability to set this or is willing to set this up for you. We will discuss the exact steps later in this article. The good news is that you will have plenty of hosting providers to choose from and it should not break the bank. Try to find one that fits all of your needs. There are even some hosts (www.WebHost4life.com) that will install DotNetNuke for you free of charge. They host many DotNetNuke sites and are familiar with the needs of the portal. Preparing Your Local Site Once you have your domain name and a provider to host your portal, you will need to get your local site ready to be uploaded to your remote server. This is not difficult, but make sure you cover all of the following steps for a smooth transition. Modify the compilation debug setting in the web.config file: You will need to modify your web.config file to match the configuration of the server to which you will be sending your files. The first item that needs to be changed is the debug configuration. This should be set to false. You should also rebuild your application in release mode before uploading. This will remove the debug tokens, perform optimizations in the code, and help the site to run faster: <!-- set debugmode to false for running application --> <compilation debug="false" /> Modify the data-provider information in the web.config file: You will need to change the information for connecting to the database so that it will now point to the server on your host. There are three things to look out for in this section (changes shown overleaf): First, if you are using MS SQL, make sure SqlDataProvider is set up as the default provider. Second, change the connection string to reflect the database server address, the database name (if not DotNetNuke), as well as the user ID and password for the database that you received from your provider. Third, if you will be using an existing database to run the DotNetNuke portal, add an objectQualifier. This will append whatever you place in the quotations to the beginning of all of the tables and procedures that are created for your database. <data defaultProvider=" SqlDataProvider" > <providers> <clear/> <add name = "SqlDataProvider" type = "DotNetNuke.Data.SqlDataProvider, DotNetNuke.SqlDataProvider" connectionStringname = "Server=MyServerIP;Database=DotNetNuke; uid=myID;pwd=myPWD;" providerPath = "~ProvidersDataProvidersSqlDataProvider" objectQualifier = "DE" databaseOwner = "dbo" upgradeConnectionString = "" />   Modify any custom changes in the web.config file: Since you set up YetAnotherForum for use on our site, we will need to make the modifications necessary to ensure that the forums connect to the hosted database. Change the to point to the database on the server: <yafnet> <dataprovider>yaf.MsSql,yaf</dataprovider> <connstr> user id=myID;password=myPwd;data source=myServerIP;initial catalog=DotNetNuke;timeout=90 </connstr> <root>/DotNetNuke/DesktopModules/YetAnotherForumDotNet/</root> <language>english.xml</language> <theme>standard.xml</theme> <uploaddir>/DotNetNuke/DesktopModules/yetanotherforum.net /upload/</uploaddir> <!--logtomail>email=;server=;user=;pass=;</logtomail--> </yafnet> Add your new domain name to your portal alias: Since DotNetNuke has the ability to run multiple portals we need to tell it which domain name is associated with our current portal. To do this we need to sign on as host (not admin) and navigate to Admin | Site Settings on the main menu. If signed on as host, you will see a Portal Aliases section on the bottom of the page. Click on the Add New HTTP Alias link:
Read more
  • 0
  • 0
  • 2724

article-image-access-control-php5-cms-part-1
Packt
21 Oct 2009
12 min read
Save for later

Access Control in PHP5 CMS - Part 1

Packt
21 Oct 2009
12 min read
The Problem We need to design and implement a role-based access control (RBAC) system, demonstrate its use, and ensure that the system can provide: a simple data structure a flexible code to provide a usable RBAC interface efficiency so that RBAC avoids heavy overheads Discussion and Considerations Computer systems have long needed controls on access. Early software commonly fell into the category that became known as access control lists (ACL). But these were typically applied at a fairly low level in systems, and referred to basic computer operations. Further development brought software designed to tackle more general issues, such as control of confidential documents. Much work was done on discretionary access control (DAC), and mandatory access control (MAC). A good deal of academic research has been devoted to the whole question of access controls. The culmination of this work is that the model most widely favored is the role-based access control system, such a mouthful that the acronym RBAC is used hereafter. Now although the academic analysis can be abstruse, we need a practical solution to the problem of managing access to services on a website. Fortunately, rather like the relational database, the concepts of RBAC are simple enough. RBAC involves some basic entities. Unfortunately, terminologies are not always consistent, so let us keep close to the mainstream, and define some that will be used to implement our solution: Subject: A subject is something that is controlled. It could be a whole web page, but might well be something much more specific such as a folder in a file repository system. This example points to the fact that a subject can often be split into two elements, a type, and an identifier. So the folders of a file repository count as a type of subject, and each individual folder has some kind of identifier. Action: An action arises because we typically need to do more than simply allow or deny access to RBAC subjects. In our example, we may place different restrictions on uploading files to a folder and downloading files from the folder. So our actions might therefore include 'upload', and 'download'. Accessor: The simplest example of an accessor is a user. The accessor is someone or something who wants to perform an action. It is unduly restrictive to assume that accessors are always users. We might want to consider other computer systems as accessors, or an accessor might be a particular piece of software. Accessors are like subjects in splitting into two parts. The first part is the kind of accessor, with website users being the most common kind. The second part is an identifier for the specific accessor, which might be a user identifying number. Permission: The combination of a subject and an action is a permission. So, for example, being able to download files from a particular folder in a file repository would be a permission. Assignment: In RBAC there is never a direct link between an accessor and permission to perform an action on a subject. Instead, accessors are allocated one or more roles. The linking of an accessor and role is an assignment. Role: A role is the bearer of permissions and is similar to the notion of a group. It is roles that are granted one or more permissions. It is easy to see that we can control what can be done by allocating roles to users, and then checking to see if any of a user's roles has a particular permission. Moreover, we can generalize this beyond users to other types of accessor as the need arises. The model built so far is known in the academic literature as RBAC0. Adding Hierarchy As RBAC can operate at a much more general level than ACL, it will often happen that one role embraces another. Suppose we think of the example of a hospital, the role of consultant might include the role of doctor. Not everyone who has the role of doctor would have the role of consultant. But all consultants are doctors. At present, Aliro implements hierarchy purely for backwards compatibility with the Mambo, and Joomla! schemes, where there is a strict hierarchy of roles for ACL. The ability to extend hierarchy more generally is feasible, given the Aliro implementation, and may be added at some point. The model with the addition of role hierarchies is known as RBAC1. Adding Constraints In general data processing, situations arise where RBAC is expected to implement constraints on the allocation of roles. A typical example would be that the same person is not permitted to have both purchasing and account manager roles. Restrictions of this kind derive from fairly obvious principles to limit scope for fraud. While constraints can be powerful additions to RBAC, they do not often arise in web applications, so Aliro does not presently provide any capability for constraints. The option is not precluded, since constraints are typically grafted on top of an RBAC system that does not have them. Adding constraints to the basic RBAC0 model creates an RBAC2 model, and if both hierarchy and constraints are provided, the model is called RBAC3. Avoiding Unnecessary Restrictions When it comes to design an implementation, it would be a pity to create obstacles that will be troublesome later. To achieve maximum flexibility, few restrictions are placed on the information that is stored by the RBAC system. Subjects and accessors have both types, and identifiers. The types can be strings, and there is no need for the RBAC system to limit what can be used in this respect. A moderate limitation on length is not unduly restrictive. It is up to the wider CMS to decide, for example, what kinds of subjects are needed. Our example for this article is the file repository, and the subjects it needs are known to the designer of the repository. All requests to the RBAC system from the file repository will take account of this knowledge. Identifiers will often be simple numbers, probably derived from an auto-increment primary key in the database. But it would be unduly restrictive to insist that identifiers must be numbers. It may be that control is needed over subjects that cannot be identified by a number. Maybe the subject can only be identified by a non-numeric key such as a URI, or maybe it needs more than one field to pick it out. For these reasons, it is better to implement the RBAC system with the identifiers as strings, possibly with quite generous length constraints. That way, the designers of software that makes use of the RBAC system have the maximum opportunity to construct identifiers that work in a particular context. Any number of schemes can be imagined that will combine multiple fields into a string; after all, the only thing we will do with the identifier in the RBAC system is to test for equality. Provided identifiers are unique, their precise structure does not matter. The only point to watch is making sure that whatever the original identifier may be, it is consistently converted into a string. Actions can be simple strings, since they are merely arbitrary labels. Again, their meaning is important only within the area that is applying RBAC, so the actual RBAC system does not need to impose any restrictions. Length need not be especially large. Roles are similar, although systems sometimes include a table of roles because extra information is held, such as a description of the role. But since this is not really a requirement of RBAC, the system built here will not demand descriptions for roles, and will permit a role to be any arbitrary string. While descriptions can be useful, it is easy to provide them as an optional extra. Avoiding making them a requirement keeps the system as flexible as possible, and makes it much easier to create roles on the fly, something that will often be needed. Some Special Roles Handling access controls can be made easier and more efficient by inventing some roles that have their own special properties. Aliro uses three of these: visitor, registered, and nobody. Everyone who comes to the site is counted as a visitor, and is therefore implicitly given the role visitor. If a right is granted to this role, it is assumed that it is granted to everybody. After all, it is illogical to give a right to a visitor, and deny it to a user who has logged in, since the user could gain the access right just by logging out. For the sake of efficient implementation of the visitor role, two things are done. One is that nothing is stored to associate particular users with the role, since everyone has it automatically. Second, since most sites offer quite a lot of access to visitors prior to login, the visitor role is given access to anything that has not been connected with some more specific role. This means, again, that nothing needs to be stored in relation to the visitor role. Almost as extensive is the role registered, which is automatically applied to anyone who has logged in, but excludes visitors who have not logged in. Again, nothing is stored to associate users with the role, since it applies to anyone who identifies themselves as a registered user. But in this case, rights can be granted to the registered role. Rather like the visitor role, logic dictates that if access is granted to all registered users, any more specific rights are redundant, and can be ignored. Finally, the role of "nobody" is useful because of the principle that where no specific access has been granted, a resource is available to everyone. Where all access is to be blocked, then access can be granted to "nobody" and no user is permitted to be "nobody". In fact, we can now see that no user can be allocated to any of the special roles since they are always linked to them automatically or not at all. Implementation Efficiency Clearly an RBAC system may have to handle a lot of data. More significantly, it may need to deal with a lot of requests in a short time. A page of output will often consist of multiple elements, any or all of which may involve decisions on access. A two pronged approach can be taken to this problem, using two different kinds of cache. Some RBAC data is general in nature, an obvious example being the role hierarchy. This applies equally to everyone, and is a relatively small amount of data. Information of this kind can be cached in the file system so as to be available to every request. Much RBAC information is linked to the particular user. If all such data were to be stored in the standard cache, it is likely that the cache would grow very large, with much of the data irrelevant to any particular request. A better approach is to store RBAC data that is specific to the user as session data. That way, it will be available for every request by the same user, but will not be cluttered up with data for other users. Since Aliro ensures that there is a live session for every user, including visitors who have not yet logged in, and also preserves the session data at login, this is a feasible approach. Where are the Real Difficulties? Maybe you think we already have enough problems to solve without looking for others? The sad fact is that we have not yet even considered the most difficult one! In my experience, the real difficulties arise in trying to design a user interface to deal with actual control requirements. The example used in this article is relatively simple. Controlling what users can do in a file repository extension does not immediately introduce much complexity. But this apparently simple situation is easily made more complex by the kind of requests that are often made for a more advanced repository. In the simple case, all we have to worry about is that we have control over areas of the repository, indicating who can upload, who can download, and who can edit the files. Those are the requirements that are covered by the examples below. Going beyond that, though, consider a situation that is often discussed as a possible requirement. The repository is extended so that some users have their own area, and can do what they like within it. A simple consequence of this is that we need to be able to grant those users the ability to create new folders in the file repository, as well as to upload and edit files in the existing folders. So far so good! But this scenario also introduces the idea that we may want the user who owns an area of the repository to be able to have control over certain areas, which other users may have access to. Now we need the additional ability to control which users have the right to give access to certain parts of the repository. If we want to go even further, we can raise the issue of whether a user in this position would be able to delegate the granting of access in their area to other users, so as to achieve a complete hierarchy of control. Handling the technical requirements here is not too difficult. What is difficult is designing user interfaces to deal with all the possibilities without creating an explosion of complexity. For an individual case it is feasible to find a solution. An attempt to create a general solution would probably result in a problem that would be extremely hard to solve. Summary In this part of the article we had a look at the highly flexible role-based access control system. We established the principles using standard notions of RBAC. We discussed about the specific details, such as the way accessors and subjects are identified are adapted to the particular situation of a CMS framework. In part 2 of the article, we will look at the database implementation and the code for administering RBAC. We will also consider in outline how questions about access can be answered.  
Read more
  • 0
  • 0
  • 2259

article-image-access-control-php5-cms-part-2
Packt
21 Oct 2009
17 min read
Save for later

Access Control in PHP5 CMS - Part 2

Packt
21 Oct 2009
17 min read
Framework Solution The implementation of access control falls into three classes. One is the class that is asked questions about who can do what. Closely associated with this is another class that caches general information applicable to all users. It is made a separate class to aid implementation of the split of cache between general and user specific. The third class handles administration operations. Before looking at the classes, though, let's figure out the database design. Database for RBAC All that is required to implement basic RBAC is two tables. A third table is required to extend to a hierarchical model. An optional extra table can be implemented to hold role descriptions. Thinking back to the design considerations, the first need is for a way to record the operations that can be done on the subjects, that is the permissions. They are the targets for our access control system. You'll recall that a permission consists of an action and a subject, where a subject is defined by a type, and an identifier. For ease of handling, a simple auto-increment ID number is added. But we also need a couple of other things. To make our RBAC system general, it is important to be able to control not only the actual permissions, but also who can grant those permissions, and whether they can grant that right to others. So an extra control field is added with one bit for each of those three possibilities. It therefore becomes possible to grant the right to access something with or without the ability to pass on that right. The other extra data item that is useful is a "system" flag. It is used to make some permissions incapable of deletion. Although not being a logical requirement, this is certainly a practical requirement. We want to give administrators a lot of power over the configuration of access rights, but at the same time, we want to avoid any catastrophes. The sort of thing that would be highly undesirable would be for the top level administrator to remove all of their own rights to the system. In practice, most systems will have a critical central structure of rights, which should not be altered even by the highest administrator. So now the permissions table can be seen to be as shown in the following screenshot: Note that the character strings for role, action, and subject_type are given generous lengths of 60, which should be more than adequate. The subject ID will often be quite short, but to avoid constraining generality, it is made a text field, so that the RBAC system can still handle very complex identifiers, if required. Of course, there will be some performance penalties if this field is very long, but it is better to have a design trade-off than a limitation. If we restricted the subject ID to being a number, then more complex identifiers would be a special case. This would destroy the generality of our scheme, and might ultimately reduce overall efficiency. In addition to the auto-increment primary key ID, two indices are created, as shown in the following screenshot. They involve overhead during update operations but are likely to speed access operations. Since far more accesses will typically be made than updates, this makes sense. If for some reason an index does not give a benefit, it is always possible to drop it. Note that the index on the subject ID has to be constrained in length to avoid breaking limits on key size. The value chosen is a compromise between efficiency through short keys, and efficiency through the use of fine grained keys. In a heavily used system, it would be worth reviewing the chosen figure carefully, and perhaps modifying it in the light of studies into actual data. The other main database table is even simpler, and holds information about assignment of accessors to roles. Again, an auto-increment ID is added for convenience. Apart from the ID, the only fields required are the role, the accessor type, and the accessor ID. This time a single index, additional to the primary key, is sufficient. The assignment table is shown in the following screenshot, and its index is shown in the screenshot after that: Adding hierarchy to RBAC requires only a very simple table, where each row contains two fields: a role, and an implied role. Both fields constitute the primary key, neither field on its own being necessarily unique. An index is not required for efficiency, since the volume of hierarchy information is assumed to be small, and whenever it is needed, the whole table is read. But it is still a good principle to have a primary key, and it also guarantees that there will not be redundant entries. For the example given earlier, a typical entry might have consultant as the role, and doctor as the implied role. At present, Aliro implements hierarchy only for backwards compatibility, but it is a relatively easy development to make hierarchical relationships generally available. Optionally, an extra table can be used to hold a description of the roles in use. This has no functional purpose, and is simply an option to aid administrators of the system. The table should have the role as its primary key. As it does not affect the functionality of the RBAC at all, no further detail is given here. With the database design settled, let's look at the classes. The simplest is the administration class, so we'll start there. Administering RBAC The administration of the system could be done by writing directly to the database, since that is what most of the operations involve. There are strong reasons not to do so. Although the operations are simple, it is vital that they be handled correctly. It is generally a poor principle to allow access to the mechanisms of a system rather than providing an interface through class methods. The latter approach ideally allows the creation of a robust interface that changes relatively infrequently, while details of implementation can be modified without affecting the rest of the system. The administration class is kept separate from the classes handling questions about access because for most CMS requests, administration will not be needed, and the administration class will not load at all. As a central service, the class is implemented as a standard singleton, but it is not cached because information generally needs to be written immediately to the database. In fact, the administration class frequently requests the authorization cache class to clear its cache so that the changes in the database can be effective immediately. The class starts off: class aliroAuthorisationAdmin { private static $instance = __CLASS__; private $handler = null; private $authoriser = null; private $database = null; private function __construct() { $this->handler =& aliroAuthoriserCache::getInstance(); $this->authoriser =& aliroAuthoriser::getInstance(); $this->database = aliroCoreDatabase::getInstance(); } private function __clone() { // Enforce singleton } public static function getInstance() { return is_object(self::$instance) ? self::$instance : (self::$instance = new self::$instance()); } private function doSQL($sql, $clear=false) { $this->database->doSQL($sql); if ($clear) $this->clearCache(); } private function clearCache() { $this->handler->clearCache(); } Apart from the instance property that is used to implement the singleton pattern, the other private properties are related objects that are acquired in the constructor to help other methods. Getting an instance operates in the usual fashion for a singleton, with the private constructor, and clone methods enforcing access solely via getInstance. The doSQL method also simplifies other methods by combining a call to the database with an optional clearing of cache through the class's clearCache method. Clearly the latter is simple enough that it could be eliminated. But it is better to have the method in place so that if changes were made to the implementation such that different actions were needed when any relevant cache is to be cleared, the changes would be isolated to the clearCache method. Next we have a couple of useful methods that simply refer to one of the other RBAC classes: public function getAllRoles($addSpecial=false) { return $this->authoriser->getAllRoles($addSpecial); }public function getTranslatedRole($role) { return $this->authoriser->getTranslatedRole($role); } Again, these are provided so as to simplify the future evolution of the code so that implementation details are concentrated in easily identified locations. The general idea of getAllRoles is obvious from the name, and the parameter determines whether the special roles such as visitor, registered, and nobody will be included. Since those roles are built into the system in English, it would be useful to be able to get local translations for them. So the method getTranslatedRole will return a translation for any of the special roles; for other roles it will return the parameter unchanged, since roles are created dynamically as text strings, and will therefore normally be in a local language from the outset. Now we are ready to look at the first meaty method: public function permittedRoles ($action, $subject_type, $subject_id) { $nonspecific = true; foreach ($this->permissionHolders ($subject_type, $subject_id) as $possible) { if ('*' == $possible->action OR $action == $possible->action) { $result[$possible->role] = $this->getTranslatedRole ($possible->role); if ('*' != $possible->subject_type AND '*' != $possible_subject_id) $nonspecific = false; } } if (!isset($result)) { if ($nonspecific) $result = array('Visitor' => $this->getTranslatedRole('Visitor')); else return array(); } return $result; }private function &permissionHolders ($subject_type, $subject_id) { $sql = "SELECT DISTINCT role, action, control, subject_type, subject_id FROM #__permissions"; if ($subject_type != '*') $where[] = "(subject_type='$subject_type' OR subject_type='*')"; if ($subject_id != '*') $where[] = "(subject_id='$subject_id' OR subject_id='*')"; if (isset($where)) $sql .= " WHERE ".implode(' AND ', $where); return $this->database->doSQLget($sql); } Any code that is providing an RBAC administration function for some part of the CMS is likely to want to know what roles already have a particular permission so as to show this to the administrator in preparation for any changes. The private method permissionHolders uses the parameters to create a SQL statement that will obtain the minimum relevant permission entries. This is complicated by the fact that in most contexts, asterisk can be used as a wild card. The public method permittedRoles uses the private method to obtain relevant database rows from the permissions table. These are checked against the action parameter to see which of them are relevant. If there are no results, or if none of the results refer specifically to the subject, without the use of wild cards, then it is assumed that all visitors can access the subject, so the special role of visitor is added to the results. When actual permission is to be granted we need the following methods: public function permit ($role, $control, $action, $subject_type, $subject_id) { $sql = $this->permitSQL($role, $control, $action, $subject_type, $subject_id); $this->doSQL($sql, true); }private function permitSQL ($role, $control, $action, $subject_type, $subject_id) { $this->database->setQuery("SELECT id FROM #__permissions WHERE role='$role' AND action='$action' AND subject_type='$subject_type' AND subject_id='$subject_id'"); $id = $this->database->loadResult(); if ($id) return "UPDATE #__permissions SET control=$control WHERE id=$id"; else return "INSERT INTO #__permissions (role, control, action, subject_type, subject_id) VALUES ('$role', '$control', '$action', '$subject_type', '$subject_id')"; } The public method permit grants permission to a role. The control bits are set in the parameter $control. The action is part of permission, and the subject of the action is identified by the subject type and identity parameters. Most of the work is done by the private method that generates the SQL; it is kept separate so that it can be used by other methods. Once the SQL is obtained, it can be passed to the database, and since it will normally result in changes, the option to clear the cache is set.   The SQL generated depends on whether there is already a permission with the same parameters, in which case only the control bits are updated. Otherwise an insertion occurs. The reason for having to do a SELECT first, and then decide on INSERT or UPDATE is that the index on the relevant fields is not guaranteed to be unique, and also because the subject ID is allowed to be much longer than can be included within an index. It is therefore not possible to use ON DUPLICATE KEY UPDATE. Wherever possible, it aids efficiency to use the MySQL option for ON DUPLICATE KEY UPDATE. This is added to the end of an INSERT statement, and if the INSERT fails by virtue of the key already existing in the table, then the alternative actions that follow ON DUPLICATE KEY UPDATE are carried out. They consist of one or more assignments, separated by commas, just as in an UPDATE statement. No WHERE is permitted since the condition for the assignments is already determined by the duplicate key situation. A simple method allows deletion of all permissions for a particular action and subject: public function dropPermissions ($action, $subject_type, $subject_id) { $sql = "DELETE FROM #__permissions WHERE action='$action' AND subject_type='$subject_type'AND subject_id='$subject_id' AND system=0"; $this->doSQL($sql, true); } The final set of methods relates to assigning accessors to roles. Two of them reflect the obvious need to be able to remove all roles from an accessor (possibly preparatory to assigning new roles) and the granting of a role to an accessor. Where the need is to assign a whole set of roles, it is better to have a method especially for the purpose. Partly this is convenient, but it also provides an extra operation, minimization of the set of roles. The method is: public function assign ($role, $access_type, $access_id, $clear=true) { if ($this->handler->barredRole($role)) return false; $this->database->setQuery("SELECT id FROM #__assignments WHERE role='$role' AND access_type='$access_type' AND access_id='$access_id'"); if ($this->database->loadResult()) return true; $sql = "INSERT INTO #__assignments (role, access_type, access_id) VALUES ('$role', '$access_type', '$access_id')"; $this->doSQL($sql, $clear); return true; }public function assignRoleSet ($roleset, $access_type, $access_id) { $this->dropAccess ($access_type, $access_id); $roleset = $this->authoriser->minimizeRoleSet($roleset); foreach ($roleset as $role) $this->assign ($role, $access_type, $access_id, false); $this->clearCache(); }public function dropAccess ($access_type, $access_id) { $sql = "DELETE FROM #__assignments WHERE access_type='$access_type' AND access_id='$access_id'"; $this->doSQL($sql, true); } The method assign links a role to an accessor. It checks for barred roles first, these are simply the special roles discussed earlier, which cannot be allocated to any accessor. As with the permitSQL method, it is not possible to use ON DUPLICATE KEY UPDATE because the full length of the accessor ID is not part of an index, so again the existence of an assignment is checked first. If the role assignment is already in the database, there is nothing to do. Otherwise a row is inserted, and the cache is cleared. Getting rid of all role assignments for an accessor is a simple database deletion, and is implemented in the dropAccess method. The higher level method assignRoleSet uses dropAccess to clear out any existing assignments. The call to the authorizer object to minimize the role set reflects the implementation of a hierarchical model. Once there is a hierarchy, it is possible for one role to imply another as consultant implied doctor in our earlier example. This means that a role set may contain redundancy. For example, someone who has been allocated the role of consultant does not need to be allocated the role of doctor. The minimizeRoleSet method weeds out any roles that are superfluous. Once that has been done, each role is dealt with using the assign method, with the clearing of the cache saved until the very end. The General RBAC Cache As outlined earlier, the information needed to deal with RBAC questions is cached in two ways. The file system cache is handled by the aliroAuthoriserCache singleton class, which inherits from the cachedSingleton class. This means that the data of the singleton object will be automatically stored in the file system whenever possible, with the usual provisions for timing out an old cache, or clearing the cache when an update has occurred. It is highly desirable to cache the data both to avoid database operations and to avoid repeating the processing needed in the constructor. So the intention is that the constructor method will run only infrequently. It contains this code: protected function __construct() { // Making private enforces singleton $database = aliroCoreDatabase::getInstance(); $database->setQuery("SELECT role, implied FROM #__role_link UNION SELECT DISTINCT role, role AS implied FROM #__assignments UNION SELECT DISTINCT role,role AS implied FROM #__permissions"); $links = $database->loadObjectList(); if ($links) foreach ($links as $link) { $this->all_roles[$link->role] = $link->role; $this->linked_roles[$link->role][$link->implied] = 1; foreach ($this->linked_roles as $role=>$impliedarray) { foreach ($impliedarray as $implied=>$marker) { if ($implied == $link->role OR $implied == $link->implied) { $this->linked_roles[$role][$link->implied] = 1; if (isset($this->linked_roles[$link->implied])) foreach ($this->linked_roles[$link->implied] as $more=>$marker) { $this->linked_roles[$role][$more] = 1; } } } } } $database->setQuery("SELECT role, access_id FROM #__assignments WHERE access_type = 'aUser' AND (access_id = '*' OR access_id = '0')"); $user_roles = $database->loadObjectList(); if ($user_roles) foreach ($user_roles as $role) $this- >user_roles[$role->access_id][$role->role] = 1; if (!isset($this->user_roles['0'])) $this->user_roles['0'] = array(); if (isset($this->user_roles['*'])) $this->user_roles['0'] = array_merge($this->user_roles['0'], $this->user_roles['*']); } All possible roles are derived by a UNION of selections from the permissions, assignments, and linked roles database tables. The union operation has overheads, so that alone is one reason for favoring the use of a cache. The processing of linked roles is also complex, and therefore worth running as infrequently as possible. Rather than working through the code in detail, it is more useful to describe what it is doing. The concept is much simpler than the detail! If we take an example from the backwards compatibility features of Aliro, there is a role hierarchy that includes the role Publisher, which implies membership of the role Editor. The role Editor also implies membership of the role Author. In the general case, it is unreasonable to expect the administrator to figure out the implied relationships. In this case, it is clear that the role Publisher must also imply membership of the role Editor. But these linked relationships can plainly become quite complex. The code in the constructor therefore assumes that only the least number of connections have been entered into the database, and it figures out all the implications. The other operation where the code is less than transparent is the setting of the user_roles property. The Aliro RBAC system permits the use of wild cards for specification of identities within accessor, or subject types. An asterisk indicates any identity. For accessors whose accessor type is user, another wild card available is zero. This means any user who is logged in, and is not an unregistered visitor. Given the relatively small number of role assignments of this kind, it saves a good deal of processing if all of them are cached. Hence the user_roles processing is done in the constructor. Other methods in the cache class are simple enough to be mentioned rather than given in detail. They include the actual implementation of the getTranslatedRole method, which provides local translations for the special roles. Other actual implementations are getAllRoles with the option to include the special roles, getTranslatedRole, which translates a role if it turns out to be one of the special ones and barredRole, which in turn, tests to see if the passed role is in the special group. It may therefore not be assigned to an accessor.
Read more
  • 0
  • 0
  • 2174
Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at $19.99/month. Cancel anytime
article-image-email-languages-and-jfile-joomla
Packt
21 Oct 2009
13 min read
Save for later

Email, Languages, and JFile with Joomla!

Packt
21 Oct 2009
13 min read
Sending emails Joomla!'s core content management has a built-in feature where visitors are able to send articles to their friends through email. A similar feature can be added to the "Restaurant Reviews" component. The component can be modified to display a link to a form where the email address of a friend can be entered along with a short message. We will create a new view to handle this form. Go to the /components/com_restaurants/views folder of your Joomla! component and create a new folder named email. In this folder, create a file called view.html.php, and load it with the following code: <?phpdefined( '_JEXEC' ) or die( 'Restricted access' );jimport( 'joomla.application.component.view');JTable::addIncludePath(JPATH_COMPONENT_ADMINISTRATOR . DS . 'tables');class RestaurantsViewEmail extends JView{ function display($tpl = null) { $id = JRequest::getInt('id', 0); $review =& JTable::getInstance('review', 'Table'); $review->load($id); $this->assignRef('review', $review); parent::display($tpl); }} This code first checks to make sure that the file is not being called directly, loads in the framework code for views, and adds /administrator/components/com_restaurants/tables to the include path list of JTable. After declaring RestaurantsViewEmail as an extension of JView, the display() function pulls in the restaurant review ID from the request. The getInstance() member function of JTable is used to get a reference to a table object for reviews. The review matching the ID is then loaded and assigned to the template using the assignRef() member function. Finally, JView's original display() member function is called. Although the code in view.html.php now loads the review that our visitors are trying to email, the form still needs to be added. Create a folder named tmpl in the existing /components/com_restaurants/views/email folder, and create a new file default.php inside it with the following code: <?php defined( '_JEXEC' ) or die( 'Restricted access' ); ?><form action="index.php" method="post"> <div class="contentheading">Email review</div> <p>&nbsp;</p> <p>Fill this form to send this review of <em> <?php echo htmlspecialchars($this->review->name) ?> </em> to someone you know who will find it useful:</p> <div> <strong>Your name:</strong> </div> <p> <input type="text" name="sender_name" value="" /> </p> <div> <strong>Your email address:</strong> </div> <p> <input type="text" name="sender_email" value="" /> </p> <div><strong>Recipient's email address:</strong></div> <p> <input type="text" name="recipient" value="" /> </p> <div><strong>Message:</strong></div> <p> <textarea name="message" rows="4" cols="40"></textarea> </p> <p> <input type="submit" value="Send Review" class="button" /> </p> <?php echo JHTML::_( 'form.token' ); ?> <input type="hidden" name="id" value= "<?php echo $this->review->id; ?>" /> <input type="hidden" name="task" value="sendemail" /> <input type="hidden" name="option" value= "<?php echo $option; ?>" /></form> Before any output occurs, the code checks to make sure that the request is coming from within Joomla! and is not being called directly. The file then outputs a brief message identifying the review by name, so that the visitors are sure of what they are sending. The form then continues with fields for the visitor's name and email address, the email address of their friend, and an optional message. Just after the submit button, there is a series of hidden fields. First, JHTML::_('form.token') is called to generate a token for the request. This is the same style of token as is used in the backend to thwart CSRF attacks, only here it is used to cut down on abuse. Next, the ID of the review being emailed is placed into the form. The task variable is set to sendemail, which is a function that we will add to the controller in a moment. Finally, option is set, so that Joomla! loads the com_restaurants component. Linking the form If you now load index.php?option=com_restaurants&view=email in your browser, you will see this screen: The message at the top of the screen is incomplete as we simply loaded the view without a review id. Although we could add id as a parameter onto the end of the URL, our visitors will not be doing this. They will need a link to follow from the review itself. To add this link, we need to make some small adjustments to the single view. This view first needs to generate URLs to the email view with the ID already included. Do this by making the following highlighted adjustment to the display() function in /components/com_restaurants/views/single/view.html.php: $date = JHTML::Date($review->review_date);$backlink = JRoute::_('index.php?option=com_restaurants');$emaillink = JRoute::_('index.php?option=com_restaurants&view=email&id=' . $id);$user =& JFactory::getUser();$comments =& $this->get('Comments');$this->assign('display_comments', $params->get('display_comments', '1'));$this->assignRef('review', $review);$this->assignRef('smoking', $smoking);$this->assignRef('date', $date);$this->assignRef('backlink', $backlink);$this->assignRef('emaillink', $emaillink);$this->assignRef('name', $user->name);$this->assignRef('comments', $comments);parent::display($tpl); With a URL to the email view now being generated, we now need to display it. Open /components/com_restaurants/views/single/tmpl/default.php and add the following highlighted code: <p><?php echo htmlspecialchars($this->review->review); ?></p><p><em>Notes:</em> <?php echo htmlspecialchars($this->review->notes); ?></p><p><a href="<?php echo htmlspecialchars($this->emaillink); ?>">Email this to a friend</a></p><a href="<?php echo htmlspecialchars($this->backlink); ?>">&lt; return to the reviews</a> After saving the files, navigate to one of the restaurant reviews in the frontend. Your screen should now have an Email this to a friend link, like the following screenshot: When you click on the Email this to a friend link, you will get a screen that looks like the following: Sending email With the form and the navigation in place, we can now focus on creating the function that creates the email and sends it to the correct place. Throughout the creation of this component, we have used member functions of JRequest to filter our input. We will do the same here, but go one step further by verifying that the mail addresses entered are valid. This extra step is necessary as malicious users can otherwise add invalid newline characters to your email fi elds, taking control of the message sending process. Once a remote user has control, the message can be sent anywhere with any text. This is known as an "Email Header Injection attack". If you fail to protect your website against this type of attack, your component could be hijacked and used to send thousands of spam messages without your knowledge. With this caution in mind, we will write the sendemail() function to process the form and send the review. Open /components/com_restaurants/restaurants.php and add this function to the controller class: function sendemail(){ JRequest::checkToken() or jexit( 'Invalid Token' ); JTable::addIncludePath(JPATH_COMPONENT_ADMINISTRATOR . DS . 'tables'); $sender_email = JRequest::getString('sender_email', ''); $recipient = JRequest::getString('recipient', ''); $sender_name = JRequest::getString('sender_name', ''); $message = JRequest::getString('message', ''); $id = JRequest::getInt('id', 0); jimport( 'joomla.mail.helper' ); if (!JMailHelper::isEmailAddress($sender_email) || !JMailHelper::isEmailAddress($recipient)) { JError::raiseError(500, 'One of the emails you entered is invalid. Please try again.'); } $review =& JTable::getInstance('review', 'Table'); $review->load($id); $link = JURI::base() . 'index.php?option=com_restaurants&view= single&id=' . $id; $subject = $sender_name . ' wants you to know about ' . $review->name; $body = "Here's a review of {$review->name}:nn"; $body .= "{$review->review}nn"; if ($message != '') { $body .= $sender_name . " also added this message:n"; $body .= '"' . $message . '"' . "nn"; } $body .= "For all of the details, follow this link: {$link}"; $sender_name = JMailHelper::cleanAddress($sender_name); $subject = JMailHelper::cleanSubject($subject); $body = JMailHelper::cleanBody($body); if (JUtility::sendMail($sender_email, $sender_name, $recipient, $subject, $body) !== true) { JError::raiseNotice( 500, 'Email failed.' ); } JRequest::setVar('view', 'email'); JRequest::setVar('layout', 'success'); $this->display();} Before even checking the variables, the checkToken() member function of JRequest is called to make sure that the user actually loaded the form. Although this will not prevent spammers from abusing your component, it will slow them down; they will need to load your form and extract the token for each message. Next, the path /administrator/components/com_restaurants/tables is added to the list of paths JTable will use to find table classes. This is necessary because we will be loading the review in a moment, in order to extract the summary and title. The email address of the sender, the address of the recipient, the name of the sender,any added message, and the review's ID are all extracted from the HTTP request.With the exception of the id field, all fields are filtered as strings. The id field is more stringently filtered to ensure that the value is also an integer. Joomla! has a library for handling email data, which we pull in by calling jimport( 'joomla.mail.helper' );. This is used immediately to ensure that the entered email addresses are in a valid format. Both the sender's address and the recipient's address are tested. If either one is in an invalid format or contains newlines, the raiseError() member function of JError is used to stop the script and display a message. The function continues by generating some review-specific data. The review is loaded from the database, and then a link back to the review is built using the review's ID. A subject line is built with the sender's name and the name of the restaurant. The body of the email starts with the name of the review, followed by the review itself. If the visitor added a personal message then this is added, along with their name. The link to the full review is added at the end. With all of the content generated, there is one step left before sending the message. The formats of the email addresses have already been validated, but the sender's name, subject, and body all contain user-supplied data. These must be filtered before they are sent off. The cleanAddress(), cleanSubject(), and cleanBody() member functions of JMailHelper strip out any attempts at email header injections. Finally, the sendMail() member function of JUtility is called to send the email with the sender's address, sender's name, recipient's email address, subject line, and body as the respective parameters. If this function fails for any reason, the raiseError() member function of JError is called and processing stops. Adding a success message When you perform an action that sends an email, most web applications will display an "email success" screen letting you know that the message went through. Our component will be no different. At the end of the sendemail() function, we set the view request variable to email, set the layout request variable to success, and then call the display() member function that defaults to JView::display(). Why aren't we calling $this->setRedirect()?Typically, $this->setRedirect() would be called to tell the controller to redirect the user to a specific page. This time, we have chosen to instead set the request variables and call the display() function directly. This prevents Joomla! from sending a redirect HTTP header to the browser, which ultimately saves another trip to the server. Because we want to display a message instead of going back to the review straight away, this makes sense. It may also be useful in cases where you have a client-side application that would otherwise be confused by a redirect. Instead of creating an entirely separate view to handle the success screen, we have opted instead to set the layout request variable and point back to the email view. This helps us to cut down on the number of views required, and allows us to reuse some of the view code. To add the markup for the success screen, we need to create a new file called success.php to the tmpl folder of the email view. Enter the code below in success.php: <?php defined( '_JEXEC' ) or die( 'Restricted access' ); ?><div class="componentheading">Success!</div><p>The review for <?php echo htmlspecialchars($this->review->name) ?> has been successfully emailed.</p><p><a href="<?php echo htmlspecialchars($this->reviewlink) ?>">Return to the review for <?php echo htmlspecialchars($this->review->name) ?>.</a></p> After checking to make sure that the request to success.php is coming from within Joomla!, a confirmation message, including the name, of the review is displayed. A link back to the review is also output. However, the URL for this link has not yet been generated. To do this, go to /components/com_restaurants/views/email/view.html.php and add the highlighted code to the display() function: $review->load($id);$reviewlink = JRoute::_('index.php?option=com_restaurants&view= single&id=' . $id);$this->assignRef('review', $review);$this->assign('reviewlink', $reviewlink);parent::display($tpl); Save all of your code, then load one of the reviews and click on the Email this to a friend link. Fill the form and click the Send Review button. If the email goes through correctly, you should see a screen like the following: If you sent the review to yourself, the email should look similar to the following: Here's a review of The Daily Dish: Chicken fried steak, meatloaf, potatoes, string beans and hot turkey sandwiches are all favorites from this venerable institution the locals swear by. Apple, pumpkin, and pecan pies round out the dessert selection.Dinner there won't break the bank, either. Ralph Elderman also added this message:"You should really try this place sometime. I take the family there every week!" For all of the details, follow this link: http://localhost/index.php?option=com_restaurants&view=single&id=2
Read more
  • 0
  • 0
  • 4833

article-image-comparing-asterisk-and-openser
Packt
21 Oct 2009
4 min read
Save for later

Comparing Asterisk and OpenSER

Packt
21 Oct 2009
4 min read
Introduction If you work with IP telephony, it's quite possible that you have not heard about OpenSER, but certainly you must have heard about Asterisk. Well, I love a polemic headline and I have seen this question asked in the forums many times.  So, I will dare to compare these two very popular softwares dedicated to the VoIP market.  The idea here is not to show you which one is the best, but mainly how they are different from each other. Below is a comparison topic by topic. Architecture Asterisk is a Back to Back User Agent (B2BUA), while OpenSER is a Session Initiation Protocol (SIP) Proxy.  This makes all the difference between them. The SIP proxy architecture is faster than a B2BUA because it deals only with signaling. On the other hand, the B2BUA architecture, even being slower, handles the media and it is capable of several services not available in a SIP proxy such as Codec Translation (that is G729<->G.711), Protocol Translation (SIP<->H323), and services related to media such as IVR, Queuing, Text to Speech, and Voice Recognition. Nat Traversal OpenSER deals a lot better with NAT traversal then Asterisk. You can send the media from your customer directly to the provider using OpenSER in most cases (non-symmetric NAT). Manipulating directly the SIP protocol allows you to handle special cases, such as, when you have two customers behind the same NAT device and want to send the media directly between them. Load Balancing OpenSER has specific load balancing algorithms with hash. So it can load balance by the "ruri", "username", "call-id", and some other properties. It can use redirected messages consuming very few resources from the load balancer machine. Failover is part of the solution, things you won't find on Asterisk, but are complementary. Low Level Access to SIP Header and Transactions OpenSER gives you low level access to the protocol. You can handle all the requests and responses. So it is possible, most times, to translate between two incompatible versions of SIP, handling directly the SIP headers, requests, and responses. This is an important feature when you have SIP implementations from different manufacturers, which can be incompatible between each other. Integration with Radius, Diameter, and LDAP OpenSER has built-in integration with LDAP, Radius, and Diameter. While this is also possible with Asterisk, the implementation on OpenSER is developed in C, integrated as a module, and is part of the OpenSER distribution (no perl, no python, no third-party modules). Carrier Class Routing The module CARRIERROUTE implements sophisticated algorithms to route calls to the PSTN. Some times VoIP providers have tables with more then 40.000 routes. In this case, you will absolutely need a specific routing module capable of failback, blacklists, and some other features specific to VoIP providers. Media Services OpenSER is a SIP Proxy and is not capable of any media related services. So it is not possible to create, using OpenSER, systems such as VoiceMail, IVR, TTS, and Voice Recognition. However, it is possible to integrate any of these services to the platform using a separate Media Server such as Asterisk, Yate, and FreeSwitch.  This is by design, and it is the way the SIP protocol is defined in the standards (RFC3261). Connectivity to the PSTN OpenSER always need a SIP gateway to connect to the PSTN. There is no possibility to install telephony cards in the server.  In several cases, Asterisk is used as the PSTN gateway for OpenSER. Conclusion I love this discussion, because Asterisk and OpenSER completes one another. OpenSER provides rock solid SIP services to VoIP providers, it is capable to handle large volumes of calls, to load balance SIP, to solve advanced NAT scenarios, and to deal with SIP signaling as no other. Asterisk is a B2BUA, very strong in the PBX market. It is simpler to configure and can handle low to medium volumes. Asterisk can be used as a "single box does it all", while OpenSER requires all the architectural components of SIP to work. OpenSER is a "hit" in the VoIP provider market and in Universities. Asterisk PBX is a success in the IP PBX market, and it is getting a piece of the small to medium VoIP providers. Usually you start using OpenSER when you have some special need, such as load balancing or when you have large volumes such as more than a thousand registered users. Choose wisely!   If you have read this article you may be interested to view : Using Asterisk as a PSTN Gateway for OpenSER Building the User Portal with SerMyAdmin for OpenSER
Read more
  • 0
  • 0
  • 4773

article-image-handler-and-phase-apache-axis2
Packt
21 Oct 2009
5 min read
Save for later

Handler and Phase in Apache Axis2

Packt
21 Oct 2009
5 min read
(For more resources on Axis2, see here.) Handler In any messaging system, the interceptor has its factual meaning in the context of messaging, where it intercepts the flow of messaging and does whatever task it is assigned to do. In fact, an interceptor is the smallest execution unit in a messaging system, and an Axis2 handler is also an interceptor. Handlers in Axis are stateless, that is, they do not keep their pass execution states in the memory. A handler can be considered as a logic invoker with the input for the logic evaluation taken from the MessageContext. A Handler has both read and write access permissions to MessageContext (MC) or to an incoming SOAP message. We can consider MessageContext as a property bag that keeps incoming or outgoing messages (maybe both) and other required parameters. It may also include properties to carry the message through the execution chain. On the other hand, we can access the whole system including the system runtime, global parameters, and property service operations via the MC. In most cases, a handler only touches the header block part of the SOAP message, which will either read a header (or headers), add a header(s), or remove a header(s). (This does not mean that the handler cannot touch the SOAP body, nor does it mean that it is not going to touch the SOAP body.) During reading, if a header is targeted to a handler and is not executing properly (the message might be faulty), then it should throw an exception, and the next driver in the chain (in Axis2, it is the Axis engine) would take the necessary action. A typical SOAP message with few headers is shown in the figure given below: Any handler in Axis2 has the capability to pause the message execution, which means that the handler can terminate the message flow if it cannot continue. Reliable messaging (RM) is a good example or use case for that scenario, when it needs to pause the flow depending on some of the preconditions and the postconditions as well and it works on a message sequence. If a service invocation consists of more than one message, and if the second message comes before the first one, then the RM handler will stop (or rather pause) the execution of the message invocation corresponding to the second message until it gets the first one. And when it gets, the first message is invoked, and thereafter it invokes or resumes the second message. Writing a Simple Handler Just learning the concepts will not help us in remembering what we have discussed. For that, we need to write a handler and see how it works. Writing a handler in Axis2 is very simple. If you want to write a handler, you either have to extend the AbstractHandler class or implement the Handler interface. A simple handler that extends the AbstractHandler class will appear as follows: public class SimpleHandler extends AbstractHandler{ public SimpleHandler() { }public InvocationResponse invoke(MessageContext msgContext) throws AxisFault { //Write the processing logic here // DO something return InvocationResponse.CONTINUE; }} Note the return value of the invoke method. We can have the following three values as the return value of the invoke method: Continue: The handler thinks that the message is ready to go forward. Suspend: The handler thinks that the message cannot be sent forward since some conditions are not satisfied; so the execution is suspended. Abort: The handler thinks that there is something wrong with the message, and cannot therefore allow the message to go forward. In most cases, handlers will return InvocationResponse.CONTINUE as the return value. When a message is received by the Axis engine, it calls the invoke method of each of the handlers by passing the argument to the corresponding MessageContext. As a result of this, we can implement all the processing logic inside that method. A handler author has full access to the SOAP message, and also has the required properties to process the message via the MessageContext. In addition, if the handler is not satisfied with the invocation of some precondition, the invocation can be paused as we have discussed earlier (Suspend). If some handler suspends the execution, then it is its responsibility to store the message context, and to forward the message when the conditions are satisfied. For example, the RM handler performs in a similar manner. Phase The concept of phase is introduced by Axis2, mainly to support the dynamic ordering of handlers. A phase can be defined in a number of ways: It can be considered a logical collection of handlers. It can be considered a specific time interval in the message execution. It can be considered a bucket into which to put a handler. One can consider a phase as a handler too. A flow or an execution chain can be considered as a collection of phases. Even though it was mentioned earlier that an Axis engine calls the invoke method of a handler, that is not totally correct. In fact, what the engine really does is call the invoke method of each phase in a given flow, and then the phase will sequentially invoke all the handlers in it (refer to the following figure). As we know, we can extend AbstractHandler and create a new handler; in the same way one can extend the Phase class and then create a new phase. But remember that we need not always extend the Phase class to create a new phase. We can do it by just adding an entry into axis2.xml (All the configuration that requires starting axis2 is obtained from axis2.xml). A phase has two important methods—precondition checking and postcondition checking. Therefore, if we are writing a custom phase, we need to consider the methods that have been mentioned. However, writing a phase is not a common case; you need to know how to write a handler.
Read more
  • 0
  • 0
  • 5045

article-image-multiple-templates-django
Packt
21 Oct 2009
13 min read
Save for later

Multiple Templates in Django

Packt
21 Oct 2009
13 min read
Considering the different approaches Though there are different approaches that can be taken to serve content in multiple formats, the best solution will be specific to your circumstances and implementation. Almost any approach you take will have maintenance overhead. You'll have multiple places to update when things change. As copies of your template files proliferate, a simple text change can become a large task. Some of the cases we'll look at don't require much consideration. Serving a printable version of a page, for example, is straightforward and easily accomplished. Putting a pumpkin in your site header at Halloween or using a heart background around Valentine's Day can make your site seem timely and relevant, especially if you are in a seasonal business. Other techniques, such as serving different templates to different browsers, devices, or user-agents might create serious debate among content authors. Since serving content to mobile devices is becoming a new standard of doing business, we'll make it the focus of this article. Serving mobile devices The Mobile Web will remind some old timers (like me!) of the early days of web design where we'd create different sites for Netscape and Internet Explorer. Hopefully, we take lessons from those days as we go forward and don't repeat our mistakes. Though we're not as apt to serve wholly different templates to different desktop browsers as we once were, the mobile device arena creates special challenges that require careful attention. One way to serve both desktop and mobile devices is a one-size-fits-all approach. Through carefully structured and semantically correct XHTML markup and CSS selectors identified to be applied to handheld output, you can do a reasonable job of making your content fit a variety of contexts and devices. However, this method has a couple of serious shortcomings. First, it does not take into account the limitations of devices for rich media presentation with Flash, JavaScript, DHTML, and AJAX as they are largely unsupported on all but the highest-end devices. If your site depends on any of these technologies, your users can get frustrated when trying to experience it on a mobile device. Also, it doesn't address the varying levels of CSS support by different mobile devices. What looks perfect on one device might look passable on another and completely unusable on a third because only some of the CSS rules were applied properly. It also does not take into account the potentially high bandwidth costs for large markup files and CSS for users who pay by the amount of data transferred. For example, putting display: none on an image doesn't stop a mobile device from downloading the file. It only prevents it from being shown. Finally, this approach doesn't tailor the experience to the user's circumstances. Users tend to be goal-oriented and have specific actions in mind when using the mobile web, and content designers should recognize that simply recreating the desktop experience on a smaller screen might not solve their needs. Limiting the information to what a mobile user is looking for and designing a simplified navigation can provide a better user experience. Adapting content You know your users best, and it is up to you to decide the best way to serve them. You may decide to pass on the one-size-fits-all approach and serve a separate mobile experience through content adaptation. The W3C's Mobile Web Initiative best practices guidelines suggest giving users the flexibility and freedom to choose their experience, and provide links between the desktop and mobile templates so that they can navigate between the two. It is generally not recommended to automatically redirect users on mobile devices to a mobile site unless you give them a way to access the full site. The dark side to this kind of content adaptation is that you will have a second set of template files to keep updated when you make site changes. It can also cause your visitors to search through different bookmarks to find the content they have saved. Before we get into multiple sites, let's start with some examples of showing alternative templates on our current site. Setting up our example Since we want to customize the output of our detail page based on the presence of a variable in the URL, we're going to use a view function instead of a generic view. Let us consider a press release application for a company website. The press release object will have a title, body, published date, and author name.In the root directory of your project (in the directory projects/mycompany), create the press application by using the startapp command: $ python manage.py startapp press This will create a press folder in your site. Edit the mycompany/press/models.py file: from django.db import models class PressRelease(models.Model): title = models.CharField(max_length=100) body = models.TextField() pub_date = models.DateTimeField() author = models.CharField(max_length=100) def __unicode__(self): return self.title Create a file called admin.py in the mycompany/press directory, adding these lines: from django.contrib import adminfrom mycompany.press.models import PressRelease admin.site.register(PressRelease) Add the press and admin applications to your INSTALLED_APPS variable in the settings.py file: INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'mycompany.press',) In the root directory of your project, run the syncdb command to add the new models to the database: $ python manage.py syncdb We will be prompted to create a superuser, go ahead and create it. We can access the admin site by browsing to http://localhost:8000/admin/ and add data. Create your mycompany/press/urls.py file as shown: urlpatterns = patterns('', (r'detail/(?P<pid>d+)/$', 'mycompany.press.views.detail'), (r'list/$','django.views.generic.list_detail.object_list', press_list_dict), (r'latest/$','mycompany.press.views.latest'), (r'$','django.views.generic.simple.redirect_to', {'url': '/press/list/'})) In your mycompany/press/views.py file, your detail view should look like this: from django.http import HttpResponsefrom django.shortcuts import get_object_or_404from django.template import loader, Contextfrom mycompany.press.models import PressRelease def detail(request, pid): ''' Accepts a press release ID and returns the detail page ''' p = get_object_or_404(PressRelease, id=pid) t = loader.get_template('press/detail.html') c = Context({'press': p}) return HttpResponse(t.render(c)) Let's jazz up our template a little more for the press release detail by adding some CSS to it. In mycompany/templates/press/detail.html, edit the file to look like this: <html><head><title>{{ press.title }}</title><style type="text/css">body { text-align: center;}#container { margin: 0 auto; width: 70%; text-align: left;}.header { background-color: #000; color: #fff;}</style></head><body><div id="container"><div class="header"><h1>MyCompany Press Releases</h1></div><div><h2>{{ press.title }}</h2><p>Author: {{ press.author }}<br/>Date: {{ press.pub_date }}<br/></p><p>{{ press.body }}</p></div></body></html> Start your development server and point your browser to the URL http://localhost:8000/press/detail/1/. You should see something like this, depending on what data you entered before when you created your press release: If your press release detail page is serving correctly, you're ready to continue. Remember that generic views can save us development time, but sometimes you'll need to use a regular view because you're doing something in a way that requires a view function customized to the task at hand. The exercise we're about to do is one of those circumstances, and after going through the exercise, you'll have a better idea of when to use one type of view over another. Serving printable pages One of the easiest approaches we will look at is serving an alternative version of a page based on the presence of a variable in the URL (aka a URL parameter). To serve a printable version of an article, for example, we can add ?printable to the end of the URL. To make it work, we'll add an extra step in our view to check the URL for this variable. If it exists, we'll load up a printer-friendly template file. If it doesn't exist, we'll load the normal template file. Start by adding the highlighted lines to the detail function in the mycompany/press/views.py file: def detail(request, pid): ''' Accepts a press release ID and returns the detail page ''' p = get_object_or_404(PressRelease, id=pid) if request.GET.has_key('printable'): template_file = 'press/detail_printable.html' else: template_file = 'press/detail.html' t = loader.get_template(template_file) c = Context({'press': p}) return HttpResponse(t.render(c)) We're looking at the request.GET object to see if a query string parameter of printable was present in the current request. If it was, we load the press/detail_printable.html file. If not, we load the press/detail.html file. We've also changed the loader.get_template function to look for the template_file variable. To test our changes, we'll need to create a simple version of our template that only has minimal formatting. Create a new file called detail_printable.html in the mycompany/templates/press/ directory and add these lines into it: <html><head><title>{{ press.title }}</title></head><body><h1>{{ press.title }}</h1><p>Author: {{ press.author }}<br/>Date: {{ press.pub_date }}<br/></p><p>{{ press.body }}</p></body></html> Now that we have both regular and printable templates, let's test our view.Point your browser to the URL http://localhost:8000/press/detail/1/, and you should see our original template as it was before. Change the URL to http://localhost:8000/press/detail/1/?printable and you should see our new printable template: Creating site themes Depending on the audience and focus of your site, you may want to temporarily change the look of your site for a season or holiday such as Halloween or Valentine's Day. This is easily accomplished by leveraging the power of the TEMPLATE_DIRS configuration setting. The TEMPLATE_DIRS variable in the settings.py file allows you to specify the location of the templates for your site. Also TEMPLATE_DIRS allows you to specify multiple locations for your template files. When you specify multiple paths for your template files, Django will look for a requested template file in the first path, and if it doesn't find it, it will keep searching through the remaining paths until the file is located. We can use this to our advantage by adding an override directory as the first element of the TEMPLATE_DIRS value. When we want to override a template with a special themed one, we'll add the file to the override directory. The next time the template loader tries to load the template, it will find it in the override directory and serve it. For example, let's say we want to override our press release page from the previous example. Recall that the view loaded the template like this (from mycompany/press/views.py): template_file = 'press/detail.html't = loader.get_template(template_file) When the template engine loads the press/detail.html template file, it gets itfrom the mycompany/templates/ directory as specified in the mycompany/settings.py file: TEMPLATE_DIRS = ( '/projects/mycompany/templates/',) If we add an additional directory to our TEMPLATE_DIRS setting, Django will look in the new directory first: TEMPLATE_DIRS = ( '/projects/mycompany/templates/override/’, '/projects/mycompany/templates/',) Now when the template is loaded, it will first check for the file /projects/mycompany/templates/override/press/detail.html. If that file doesn't exist, it will go on to the next directory and look for the file in /projects/mycompany/templates/press/detail.html. If you're using Windows, use the Windows-style file path c:/projects/mycompany/templates/ for these examples. Therein lies the beauty. If we want to override our press release template, we simply drop an alternative version with the same file name into the override directory. When we're done using it, we just remove it from the override directory and the original version will be served (or rename the file in the override directory to something other than detail.html). If you're concerned about the performance overhead of having a nearly empty override directory that is constantly checked for the existence of template files, we should consider caching techniques as a potential solution for this. Testing the template overrides Let's create a template override to test the concept we just learned. In your mycompany/settings.py file, edit the TEMPLATE_DIRS setting to look like this: TEMPLATE_DIRS = ( '/projects/mycompany/templates/override/', '/projects/mycompany/templates/',) Create a directory called override at mycompany/templates/ and another directory underneath that called press. You should now have these directories: /projects/mycompany/templates/override//projects/mycompany/templates/override/press/ Create a new file called detail.html in mycompany/templates/override/press/ and add these lines to the file: <html><head><title>{{ press.title }}</title></head><body><h1>Happy Holidays</h1><h2>{{ press.title }}</h2><p>Author: {{ press.author }}<br/>Date: {{ press.pub_date }}<br/></p><p>{{ press.body }}</p></body></html> You'll probably notice that this is just our printable detail template with an extra "Happy Holidays" line added to the top of it. Point your browser to the URL http://localhost:8000/press/detail/1/ and you should see something like this: By creating a new press release detail template and dropping it in the override directory, we caused Django to automatically pick up the new template and serve it without us having to change the view. To change it back, you can simply remove the file from the override directory (or rename it). One other thing to notice is that if you add ?printable to the end of the URL, it still serves the printable version of the file we created earlier. Delete the mycompany/templates/override/ directory and any files in it as we won't need them again.
Read more
  • 0
  • 0
  • 15561
article-image-installing-and-using-openfire
Packt
21 Oct 2009
6 min read
Save for later

Installing and Using Openfire

Packt
21 Oct 2009
6 min read
The Openfire instant messaging server is very easy to install. In fact, it's totally newbie-proof. So much so, that unlike other complex server software, even if you've never setup up Openfire before, you'll be able to get it up and running on your first try. If you're sceptical, by the time we are done with this short article, we'll have ourselves a fully-functional Openfire server that will register users and connect with clients. Preparing Your System Openfire is a cross-platform server and can be installed under Linux, Solaris, Mac, or Windows operating system environments. Openfire reserves its enormity for its users. When it comes to system requirements, Openfire is very suave and a perfect gentleman who has very moderate demands. You don't need to spend much time preparing your system for installing Openfire. Just pick out the environment you're comfortable with—Windows or one of the popular Linux distributions such as Fedora, Debian, or Ubuntu, and you're good to go. You don't have to run around getting obscure libraries or worry about mismatched versions. But like any hard-working gentleman, Openfire has a thing for caffeine, so make sure you have Java on your system. No need to run to the kitchen—this isn't the Java in the cupboard. Openfire is written in the Java programming language, so it'll need a Java Runtime Environment (JRE) installed on your system. A JRE creates a simple (breathable, so to say) environment for Java applications to live and function in. It's available as a free download and is very easy to install. If you're installing under Windows, just skip to the "Installing Under Windows" section later in the article. Linux Users Get Your Cuppa! Sun's Java Runtime Environment is available as a free download from Sun's website (http://www.java.com/en/download/linux_manual.jsp) or it can also be installed from your distribution's software management repositories. Users of RPM-based systems can safely skip this section because the Openfire installer for their distribution already includes a JRE. On the other hand, users of Debian-based systems such as Ubuntu will have to install the JRE before installing Openfire. Thanks to the popular apt-get package management system, there isn't much to installing the JRE. Because Sun's JRE isn't free and is also not an open source software, most Linux distributions make the JRE package available in their non-free tree. If the following command doesn't work, check out the detailed installation instructions for your specific distribution, at  https://jdk-distros.dev.java.net. Open a console and issue the following command: $ sudo apt-get install sun-java6-jre Now the apt-get system will automatically fetch, install, and activate the JRE for you! Meet The Protagonists This article is about making sure that you have no trouble installing one file. This one file is the Openfire installer and it is available in multiple flavors. The four flavors we're concerned with aren't as exotic as Baskin Robbins' 31 flavors but that doesn't make the decision any easier. The Openfire project releases several installers. The four flavors we're concerned with are: Openfire-3.5.2-1.i386.rpm: RPM package for Fedora Linux and other RPM-based variants Openfire_3.5.2_all.deb: DEB package for Debian, Ubuntu Linux and their derivates Openfire_3_5_2.tar.gz: Compressed "tarball" archive that'll work on any Linux distribution Openfire_3_5_2.exe: Openfire installer for Windows We'll cover installing Openfire from all of these files, so that you may use Openfire from your favorite Linux distribution or from within Windows. Just to reiterate here, the Windows installer and the RPM Linux installer both bundle the JRE, while the other other versions do not. The Actual Install-Bit Alright, so you have the Java JRE setup and you've downloaded the Openfire installer. In this section, we'll install Openfire server from the various versions we discussed in the last section. Let's first install from the source tarball. The first step when dealing with .tar.gz source archive is to extract the files. Let's extract ours under /tmp and then move the extracted directory under /opt. # tar zxvf openfire_3_5_2.tar.gz# mv openfire /opt Now we'll create a non-priviledged user and group for running Openfire. # groupadd openfire# useradd -d /opt/openfire -g openfire openfire Next, we'll change ownership of the openfire/directory to the newly-created user and group. # chown -R openfire:openfire /opt/openfire Believe it or not, that's it! You've just installed Openfire server. Surprised? Get ready for more. It gets even simpler if you install using the precompiled RPM or DEB binaries. In the case of RPM, Openfire is installed under /opt/openfire and in case of the DEB file, Openfire resides under /etc/openfire. On RPM-based systems such as Fedora and its derivates (as root), use: # rpm -ivh openfire-3.5.2-1.i386.rpm On DEB-based systems such as Debian, Ubuntu, and so on, use: $ sudo dpkg -i openfire_3.5.2_all.deb Voila! You're done. Now, who thought my "installing Openfire is totally newbie-proof" comment was an exaggeration? Running Openfire on Linux/Unix So, we now have Openfire on our favourite Linux distribution, whichever distribution this may be. Now it's time to fire it up and get going. Depending on how you installed Openfire, the procedure to start it varies a little. If you've installed Openfire from the RPM or DEB, you'll be pleased to know that the Openfire developers have already done most of the hard work for you. These binaries contain some custom handling for the RedHat/Debian-like environments. You can start and stop Openfire just like any other service on your system: # /etc/init.d/openfire startStarting Openfire: You can also view the other options available: # /etc/init.d/openfireUsage /etc/init.d/Openfire {start|stop|restart|status|condrestart|reload} On the other hand, if you've installed Openfire using the .tar.gz archive, you can start and stop Openfire using the bin/openfire script in your Openfire installation directory. First, change to the user that owns the /opt/openfire directory: # su - openfire# cd /opt/openfire/bin/# ./openfire startStarting Openfire And now you have Openfire up and running! If you are using a firewall, which you most probably are, make sure to forward traffic on ports 5222 and 5223 (for SSL) which clients use for connecting with the Openfire server. Also forward traffic on port 7777 for file transfer. Linux users can skip the next section on installing Openfire under Windows and move directly to the section that discusses the preliminary Openfire setup.
Read more
  • 0
  • 0
  • 5933

article-image-ajax-dynamic-content-and-interactive-forms
Packt
21 Oct 2009
7 min read
Save for later

AJAX / Dynamic Content and Interactive Forms

Packt
21 Oct 2009
7 min read
Essentially, AJAX is an acronym for Asynchronous JavaScript and XML, and it is the technique of using JavaScript and XML to send and receive data between a web browser and a web server. The biggest advantage this technique has is that you can dynamically update a piece of content on your web page or web form with data from the server (preferably formatted in XML), without forcing the entire page to reload. The implementation of this technique has made it obvious to many web developers that they can start making advanced web applications (sometimes called RIAs—Rich Interface Applications) that work and feel more like software applications, instead of like web pages. Keep in mind that the word AJAX is starting to have its own meaning (as you'll also note its occasional use here as well as all over the web as a proper noun, rather than an all-cap acronym). For example, a Microsoft web developer may use VBScript instead of JavaScript to serve up Access Database data that is transformed into JSON (not XML) using a .NET server-side script. Today, that guy's site would still be considered an AJAX site, rather than an AVAJ site (yep, AJAX just sounds cooler). In fact, it's getting to the point where just about anything on a website (that isn't in Flash) that slides, moves, fades, or pops up without rendering a new browser window is considered an 'Ajaxy' site. In truth, a large portion of these sites don't truly qualify as using AJAX, they're just using straight-up JavaScripting. Generally, if you use cool JavaScripts in your WordPress site, it will probably be considered 'Ajaxy', despite not being asynchronous or using any XML. Want more info on this AJAX business? The w3schools site has an excellent introduction to AJAX, explaining it in straight-forward, simple terms. They even have a couple of great tutorials that are fun and easy to accomplish, even if you only have a little HTML, JavaScript, and server-side script (PHP or ASP) experience (no XML experience required) (http://w3schools.com/ajax/). You Still Want AJAX on Your Site? OK! You're here and reading this article because you want AJAX in your WordPress site. I only ask you take the just discussed into consideration and do one or more of the following to prepare. Help your client assess their site's target users first. If everyone is web 2.0 aware, using newer browsers, and are fully mouse-able, then you'll have no problems, AJAX away. But if any of your users are inexperienced with RIA (Rich Interface Application) sites or have accessibility requirements, take some extra care. Again, it's not that you can't or shouldn't use AJAX techniques, just be sure to make allowances for these users. You can easily adjust your site's user expectations upfront, by explaining how to expect the interface to act. Again, you can also offer alternative solutions and themes for people with disabilities or browsers that can't accommodate the AJAX techniques. Remember to check in with Don't Make Me Think, that Steve Krug book I for help with any interface usability questions you may run into. Also, if you're really interested in taking on some AJAX programming yourself, I highly recommend AJAX and PHP by Cristian Darie, Bogdan Brinzarea, Filip Chereches-Tosa, and Mihai Bucica. In it, you'll learn the ins and outs of AJAX development, including handling security issues. You'll also do some very cool stuff like make your own Google-style auto-suggest form and a drag-and-drop sortable list (and that's just two of the many fun things to learn in the book). So, that said, you're now all equally warned and armed with the knowledgeable resources I can think to throw at you. Let's get to it; how exactly do you go about getting something 'Ajaxy' into your WordPress site? Plug-ins and Widgets In these next few sections we're going to cover plug-ins and widgets. Plug-ins and widgets are not a part of your theme. They are additional files with WordPress compatible PHP code that are installed separately into their own directories in your WordPress installation (again, not in your theme directory). Once installed, they are available to be used with any theme that is also installed in your WordPress installation. Even though plug-ins and widgets are not the part of your theme, you might have to prepare your theme to be compatible with them. Let's review a bit about plug-ins and widgets first. Plug-ins WordPress has been built to be a lean, no frills publishing platform. Its simplicity means that with a little coding and PHP know-how, you can easily expand WordPress's capabilities to tailor to your site's specific needs. Plug-ins were developed so that even without a little coding and PHP know-how, users could add extra features and functionality to their WordPress site painlessly, via the Administration Panel. These extra features can be just about anything—from enhancing the experience of your content and forms with AJAX, to adding self-updating 'listening/watching now' lists, Flickr feeds, Google Map info and Events Calendars; you name it, someone has probably written a WordPress plug-in for it. Take a look at the WordPress Plug-in page to see what's available: http://wordpress.org/extend/plugins/ Widgets Widgets are basically just another plug-in! The widget plug-in was developed by AUTOMATTIC (http://automattic.com/code/widgets/), and it allows you to add many more kinds of self-updating content bits and other useful 'do-dads' to your WordPress site. Widgets are intended to be smaller and a little more contained than a full, stand-alone plug-in, and they usually display within the side bar of your theme (or wherever you want; don't panic if you're designing a theme without a sidebar). If you're using WordPress version 2.2 and up, the widget plug-in has become a part of WordPress itself, so you no longer need to install it before installing widgets. Just look through the widget library on WordPress's widget blog and see what you'd like! (http://widgets.wordpress.com/) Trying to download Widgets but the links keep taking you to Plug-in download pages? You'll find that many WordPress Widgets 'piggyback' on WordPress Plug-ins, meaning you'll need the full plug-in installed in order for the widget to work or the widget is an additional feature of the plug-in. So don't be confused when searching for widgets and all of a sudden you're directed to a plug-in page. WordPress Widgets are intended to perform much the same way Mac OS's Dashboard Widgets and Windows Vista Gadgets work. They're there to offer you a quick overview of content or data and maybe let you access a small piece of often used functionality from within a full application or website, without having to take the time to launch the application or navigate to the website directly. In a nutshell, widgets can be very powerful, while at the same time, just don't expect too much. Getting Your Theme Ready for Plug-ins and Widgets In this article, we'll take a look at what needs to be done to prepare your theme for plugins and widgets. Plug-in Preparations Most WordPress Plug-ins can be installed and will work just fine with your theme, with no extra effort on your part. You'll generally upload the plug-in into your wp_content/plugins directory and activate it in your Administration Panel. Here are a few quick tips for getting a plug-in displaying well in your theme: When getting ready to work with a plug-in, read all the documentation provided with the plug-in before installing it and follow the developer's instructions for installing it (don't assume just because you've installed one plug-in, they all get installed the same way). Occasionally, a developer may mention the plug-in was made to work best with a specific theme, and/or the plug-in may generate content with XHTML markup containing a specific CSS id or class rule. In order to have maximum control over the plug-in's display, you might want to make sure your theme's stylesheet accommodates any id or class rules the plug-in outputs. If the developer mentions the plug-in works with say, the Kubrick theme, then, when you install the plug-in, view it using the Kubrick theme (or any other theme they say it works with), so you can see how the plug-in author intended the plug-in to display and work within the theme. You'll then be able to duplicate the appropriate appearance in your theme.
Read more
  • 0
  • 0
  • 2348

article-image-customizing-plugins-joomla-15x-part-2
Packt
21 Oct 2009
8 min read
Save for later

Customizing Plugins in Joomla! 1.5x (Part 2)

Packt
21 Oct 2009
8 min read
Step 2: Plan out our changes Just like with our module, we are going to be systematic about our customization. This keeps us organized and reduces the chances for mistakes. Really, these changes are so simple we could probably just dive in and do them, but we want to build good habits for when we want to customize more complex extensions. Step 2.1: Decide on our changes Our plugin is going to be essentially the same, hiding or showing parts of our content depending on a particular condition. Only we want to change it so the condition we use is user's subscription and not their user group. We will need to put in some code to search the database for the visitor's subscription information. We also want to clean out any code we don't need, such as the description HTML page and images. We will go a little bit further and rename our extension and functions. One day we may want to distribute this plugin to get some traffic to our site, and help other developers like ourselves. Also, seeing as we are going to rebuild most of this plugin, let's put a short description in to remind us what it is for, or in case we hire another developer to help with our site later, they can see what it does. Step 2.2: Mark out our changes Remember that before we actually make our changes, we want to go through the code and mark them with comments first. This way we are forced to think the whole process through from start to finish before we write any code, and we can see any potential problems before they happen. This beats finding them after we have spent a few hours writing code, and wasting that time going back to repair them. en-GB.plg_content_njaccess.ini First, we are going to edit our language file, en-GB.plg_content_njaccess.ini. If we were making a complex component, we would usually keep the language file open the entire time, and add new entries to it every time we wanted to put some text onto the screen. But our plugin is pretty much a 'behind the scenes' plugin so we don't need much text. So what text do we need? Well, as we discussed above, when we hide some content from a user, we probably want to display a message that tells them that it has been hidden, and that they should get a subscription to read it. We also want to remove the current rich description and replace it with simpler, normal text. So let's add a note to our current code, NINJACONTENT=<IFRAME SRC="../plugins/content/njaccess/njaccess_desc.html" WIDTH=600 HEIGHT=600 FRAMEBORDER=0 SCROLLING=yes></IFRAME> that tells us to delete it completely. Then we will add a note to write our description and message in its place. # TODO-Remove thisNINJACONTENT=<IFRAME SRC="../plugins/content/njaccess/njaccess_desc.html" WIDTH=600 HEIGHT=600 FRAMEBORDER=0 SCROLLING=yes></IFRAME># TODO-Add plain text description# TODO-Add message for hidden text Wait a minute! What are these hashes? We haven't seen them before. Up until now we were told that comments were either double slashes (//), enclosing slash asterisks (/* … */), or for HTML some long tags (<!-- … -->). Well, .ini files are different from our .php files, and are processed differently. As a result, they use a different symbol to indicate for comments. So now, we can add # to our list of comment symbols, but for .ini (language) files only. njaccess.php Next, open up njaccess.php. As we are basically re-writing this plugin, we might as well change the name of this file and all the functions to something more relevant. // TODO-Rename this file// Ensure this file is being included by a parent file. defined('_JEXEC') or die( "Direct Access Is Not Allowed" );jimport('joomla.eventplugin');// TODO- Rename the class to match our new filenameclass plgContentNJaccess extends JPlugin {// TODO- Rename this constructorfunction plgContentNJaccess( &$subject ){... We don't have any parameters, so we can remove the parameter loading from the constructor. ...parent::__construct( $subject );// TODO-Remove these as we have no need for parameters$this->_plugin = JPluginHelper::getPlugin( 'Content','ninjaacess' );$this->_params = new JParameter( $this->_plugin->params );} We are renaming everything, so we should rename our regex tags and the function call via preg_replace_callback as well. function onPrepareContent(&$article, &$params, $limitstart) {// TODO- Adjust our regex to look for a shorter tag// and one collector function between the tags$regex = "#{njaccess(.*?)}(.*?){/njaccess}#s";// TODO- Rename the function call$article->text = preg_replace_callback($regex,array($this,"njaccess"), $article->text);return true;}// TODO- Rename the functionfunction njaccess(&$matches) { We also want to remove any references to the ACL. We do want to continue to load the user information though, as we need their user id (if logged in) to compare it to the subscriptions in the AEC tables. $user = &JFactory::getUser();// TODO- Remove the next 3 lines as we don't need ACL$acl = &JFactory::getACL();$myRealGid = intval( $acl->get_group_id( $user->usertype ) );$accessLevels = ''; We are only going to have one collector pattern now, so only one set of information, the text to be shown/hidden, needs to be collected. To do this, we need to change all the references to $matches[2] into $matches[1] and remove the old $matches[1] checks. // TODO-change this to matches[1] as we only have// one collector now$output= $matches[2];// TODO-Remove thisif (@$matches[1]) {$accessLevels = explode(",", trim($matches[1]));} Lastly, we need to replace the main processing with a query to check our visitor's user id against the AEC subscription tables for an active paying subscription. // TODO-Replace this with a query searching for the// user's id in the subscriptions table, searching// for a paying subscriptionif (in_array($myRealGid,$accessLevels))return $output;// TODO- Get the visitor's id if available.// If a guest (id = 0) then skip this and display// the please subscribe message// TODO- Look for the id in the AEC subscriptions// table, and check if they have a valid, paid// subscription. If so, return the text// if not, skip it and return the message// TODO- Instead of blank, return our message from our// language filereturn "";}} njaccess.xml Finally, we come to our njaccess.xml file. Comments can be made into XML files in the same way as HTML <!-- … -->. For our XML manifest, we have a few things to do. At first, we want to rename everything from njaccess, including the manifest itself. <?xml version="1.0" encoding="utf-8"?><install version="1.5" type="plugin" group="content"><!-- TODO- Rename this file and plugin --><name>Ninja Access</name><author>Daniel Chapman</author><creationDate>February 2008</creationDate><copyright>(C) 2008 Ninja Forge</copyright><license>http://www.gnu.org/copyleft/gpl.html GNU/GPL</license><authorEmail>daniel@ninjaforge.com</authorEmail><authorUrl>www.ninjaforge.com</authorUrl> Also, let's change the version number of our new plugin to 1.0. Then change the description as well, to suit what we put into our language file. <!-- TODO- Change to 1.0 --><version>1.1</version><!-- TODO- Change to match our language file --><description>NINJACONTENT</description> Then, we want to remove all the unnecessary files from the description <!-- TODO- Remove unneeded files --><files><filename plugin="njaccess">njaccess.php</filename><filename>njaccess/njaccess_desc.html</filename><filename>njaccess/js/ninja.js</filename><filename>njaccess/images/logo.jpg</filename><filename>njaccess/images/ninjoomla.png</filename><filename>njaccess/images/firefox2.gif</filename><filename>njaccess/images/jcda.png</filename><filename>njaccess/images/validation_xhtml.png</filename><filename>njaccess/images/validation_css.png</filename><filename>njaccess/images/info.png</filename><filename>njaccess/images/change.png</filename><filename>njaccess/images/inst.png</filename><filename>njaccess/images/tabbg.gif</filename><filename>njaccess/images/tab2.png</filename><filename>njaccess/images/gnugpl.png</filename></files> Finally, rename the reference to our language file to suit the new filename: <params></params><!-- TODO- Rename the language file --><languages><language tag="en-GB">en-GB.plg_content_njaccess.ini</language></languages></install>
Read more
  • 0
  • 0
  • 1662
article-image-social-bookmarking-mediawiki
Packt
21 Oct 2009
4 min read
Save for later

Social Bookmarking - MediaWiki

Packt
21 Oct 2009
4 min read
Traditionally, bookmarking was done through Internet browsing software, such as Internet Explorer, Safari, Firefox, or Opera. With social bookmarking, your bookmarks are not confined to one browser, but are stored online. The following two options are available for enabling social bookmarking on your website: Link to each individual social bookmarking service that you wish to use Use a social bookmarking aggregator such as Socializer or AddThis Even if you do not have links to allow visitors to bookmark your website, many services allow their users to install toolbars in their browser, which allows your website to be added anyway. Adding these links will help to spread your wiki and its new skin very fast. Individual Social Bookmarking Services There are huge numbers of social bookmarking services on the Internet, and quite a number of these have become reasonably popular. We will look at some of the more popular bookmarking services such as: Mister Wong Furl Facebook Your Wiki's Audience One thing to consider before adding social bookmarking service links to your wiki is your audience. For instance, if your wiki is technology-related, you may find it better to use Digg than Facebook, as Digg is more popular than Facebook for your wiki's intended audience. If your wiki's visitors are primarily from Germany, you may find Mister Wong more useful than Furl, because Mister Wong is more popular with the German users. There are many other social bookmarking services available, including Del.icio.us (http://del.icio.us) and StumbleUpon (http://stumbleupon.com), which you can use after asking your wiki's visitors by means of a poll, or following simple experimentation. Example of Audience durhamStudent (http://durhamStudent.co.uk) is a niche website aimed at students of Durham University in the UK: The durhamStudent website uses both the methods of social bookmarking discussed earlier, providing links for eKstreme's Socializer and a link to an individual bookmarking service, Facebook: Facebook was linked individually here because it is incredibly popular among the students at Durham University (indeed, the Durham network is only open to those with a university email address). Although Facebook's bookmarking service is accessible through Socializer, it is also linked separately as the website's target audience is more likely to use that service than any other. Mister Wong Mister Wong, http://www.mister-wong.com, is popular with German and other European users (though not so much in Great Britain), and allows the users to store their bookmarks while maintaining their privacy, with the ability to set bookmarks as "public" or "private". Generally, the social bookmarking services ask for two pieces of information when creating a link from your website to them: the URL (address) of the page or website you want to add, and the title of that page or website, as you wish it to be posted. Linking to Mister Wong To link to Mister Wong, create a link where you want your social bookmark to be shown in your MediaWiki template in the following format: http://www.mister-wong.com/index.php?action=addurl&bm_url=www.example. com &bm_description=Your+Website Spaces are automatically escaped with a "+" sign by the majority of social bookmarking services. You don't have to worry about properly escaping spaces in the title of your link when linking to bookmarking services. To use these services with MediaWiki, we will be required to use some PHP. In particular, we will need the following: The page's title, which we can get with <?php $this->text('pagetitle') ?>. The website's address, retrievable with <?php $this->urlencode('serverurl') ?>. For simplicity, we will assume that our visitors will always want to bookmark JazzMeet's homepage, http://www.jazzmeet.com. Thus, the code in our MediaWiki template would appear as follows: <a href= "http://www.mister-wong.com/index.php?action=addurl&bm_url=www.jazzmeet.com &bm_description=JazzMeet" title="Bookmark JazzMeet with Mister Wong">Bookmark JazzMeet with Mister Wong</a> What Mister Wong Users See If a visitor to your wiki decides to bookmark your wiki with Mister Wong, they will be greeted with a screen similar to the following, with fields for the address of the website (URL), title, related keywords ("tags"), and a comment about the website: They are also given the option to bookmark as either "public", allowing other Mister Wong users to see it, or "private", which restricts the bookmarked website to their account only.
Read more
  • 0
  • 0
  • 2078

article-image-customizing-plugins-joomla-15x-part-1
Packt
21 Oct 2009
9 min read
Save for later

Customizing Plugins in Joomla! 1.5x (Part 1)

Packt
21 Oct 2009
9 min read
The code used in this article can be downloaded from here. Plugin composition and operation     Like a module, in its simplest form, a plugin can consist of only two files, a PHP file with the actual code for the plugin, and an XML manifest that tells Joomla! what to do with the plugin. Despite this apparent simplicity, plugins are very powerful, and more difficult to understand than modules and components. Plugins are designed to run at certain times during the execution of our site, and they perform actions that can only be done at these times. For example, in our sample site we want to hide some of our content from guests, and only show it to paid subscribers. This action can only be performed when we are actually preparing the content to be displayed, because we need to wait until we identify if the viewer is a guest or subscriber, and then make the changes to the content dynamically. In a different example, checking if a subscriber's subscription is valid is something that only needs to be done when they try to login, and not on every page load. Plugin types Plugins are divided into eight types, as follows: Authentication Content Editors Editors-XTD Search System User XML-RPC Authentication Authentication plugins are designed to allow the site to check the user's authentication against a variety of sources. The default is to check the user's authentication against a username and password stored in the Joomla! database, which, as of Joomla! 1.5, will be the username and password fields in the #__user table (#__ is the table prefix we chose when setting up Joomla!). However, any source with a public API can be used to verify someone's authentication details. Common uses are LDAP, OpenID, a Google account, a subscription, community component, and more. On our site, for example, we are already using an authentication plugin to verify the subscriptions of users when they attempt to login. Content Possibly the most commonly used of all plugins, content plugins allow content items to be modified or have additional features added to them. We could, for example, use content plugins to cloak email addresses, embed audio or video into our pages, or do text replacements. We can even embed components and modules into our pages via plugins. We will later look at a content plugin that we will use to hide and show content depending on a user's subscription. Editors Editors plugins add WYSIWYG editors that we can use when editing our content. We installed JCE on our site earlier, which is the most popular Joomla! editor plugin as of this publication according to Joomla.org. Editors-XTD Editors-XTD (extended) plugins allow us to add additional buttons to the editors. The Image, Read more, and Pagebreak buttons on the default Joomla! WYSIWYG editor, for example, are actually plugins. Search Search plugins allow us to search through the data from different components. By default, Joomla! comes with plugins that search through content articles and the Contacts and Weblinks components. These can be expanded upon by creating or installing search plugins for other extensions. System System plugins are arguably the most powerful and most flexible types of plugins for Joomla!, as they can execute at several different pre-defined points during the execution of a Joomla! page plugin. They can be used to perform a vast array of functions, such as loading extra scripts or CSS into the header of a web page, redirecting people away from pages, logging statistics, and more. User User plugins allow us to perform actions at different times with respect to users. Such times include logging in and out, and also when saving changes to a user's profile. User plugins are often used to create a "bridge" between Joomla! and other web applications (such as the phpBB forum or the osCommerce e-commerce platform.). XML-RPC XML-RPC plugins are for communicating between our Joomla! site and other external applications, such as a desktop application or a different web site. Plugin events As a Joomla! site loads a page, it steps through a series of events as part of that process. The events it steps through are determined by the type of page it is loading. Plugins are always tied to one or more of these events, and are executed during those events as required. When loading a page of content, for example, we would step through a mix of the system and some of the content events. When loading the same page for editing, we will step through the system events, different content events, and also possibly editor events. The events triggered in Joomla! are: Authentication onAuthenticate Content onPrepareContent onAfterDisplayTitle onBeforeDisplayContent onBeforeContentSave onAfterContentSave Editors onInit onGetContent onSetContent onSave onDisplay onGetInsertMethod Editors XTD (Extended) onDisplay Search onSearch onSearchAreas System onAfterInitialise onAfterRoute onAfterDispatch onAfterRender User onLoginUsexr onLoginFailure onLogoutUser onLogoutFailure onBeforeStoreUser onAfterStoreUser onBeforeDeleteUser onAfterDeleteUser XML-RPC onGetWebServices Most of these events are easy to understand from their name, but just in case, more information can be found on the Joomla! documentation wiki at http://docs.joomla.org/CategoryPlugins. Some events are only activated at specific times, such as onAuthenticate, which is only activated when someone logs into their account. Others are activated on every page load. Content events are activated on all content pages and only on content pages, not on pages with components other than com_content. Content plugins are also only executed on the main body content itself and don't have access to the template or other module data. So a text replacement content plugin, for example, wouldn't change any text in modules or the template, only in the main content itself. It is actually possible for modules and components to manually activate plugin events with clever programming, but this is not the default Joomla! behavior. It is usually done when a developer wants to apply content plugin restrictions/changes to a module. Plugin order Aside from the events and types, there is a third important factor to consider when setting up our plugins. That is the order in which the plugins of a particular type are executed. This order is best observed on the Plugin Manager screen that can be found under the Extensions menu. The order in which the plugins execute is something not many people think about, but is really quite powerful and useful. This is because the plugins which execute later, can then use the output or effects of the earlier executing plugins as input. For example, imagine we have a plugin that displays different text for different user types, and we have another plugin that reads certain text values and replaces them with embedded video or audio. If we wanted to be able to show different videos to different groups, then we could use the first plugin to generate the different command strings for the second plugin, and have it generate them based on the user type. The second plugin, our media embedding plugin, doesn't even know that the first plugin exists. All it knows is which videos it needs to display based on what is in the content item. If the media plugin executes first, then it will generate both videos regardless of the user type. As another example, imagine we have some sort of external application and we log users into it after they authenticate via an authentication plugin. We need to make sure that this plugin is executed after all of our other authentication plugins that may check a user's credentials or account status. Otherwise, someone may get logged into our external application even though they were prevented from login into our Joomla! site. So a hacker, for example, could get access to our external application without needing to even successfully get into our main site. This was all because we had the order of our plugins wrong. So when we install and activate plugins, it is well worth taking the time to double check that everything happens in the order it is meant to be in. If one of our plugins is not behaving how it should, it might be worth checking the order to see if another plugin is conflicting with it. Customizing a Plugin Now that we have a better understanding of how our plugins work and fit together, we are going to try our hand at customizing one for our site. This will hopefully give us the understanding and confidence to make any other customizations we need in the future. As with modules, it is often easier to find a plugin that does most of what we want it to do and then make changes to it so that it meets our needs more completely. Looking back over our goals, one that requires a plugin is that we want to limit access to certain parts of our content to only our paying subscribers. This effect is going to be best achieved via content plugin, so we chose the Ninja Access plugin to fill this need. To use Ninja Access we first need to mark the content we want to restrict with special tags and indicate the user groups we want to see what is contained within the tags. When the page is produced, the plugin reads the visitor's user group and then compares it to the ones in the list provided by the tag. If the user groups match, then the content is displayed, if not, then it is hidden. For example: {njaccess 0}shows only to guest users{/njaccess}{njaccess 18,19,20,21,23,24,25}shows to all users who are not a guest{/njaccess} The numbers in the examples above indicate the default Joomla! user group ids. The most important ones are: 0 = Guests 18 = Registered 24 = Administrators 25 = Super Administrators We could use this as it is, but as we don't have a component installed to create new access groups, it won't be very flexible. We could get into trouble in the future if we decide to let people register without getting a subscription, or create a free subscription. In this instance, we will have paying and free subscribers all in the number 18 user group. Also, as we are always going to be restricting the same groups, we don't really need to type the parameters in every single time. Making our plugins always restrict the same groups automatically will save us some time and reduce mistakes. Lastly, do we really need to type njaccess every time? Let's shorten it to something like soc—subscriber only content. For our first dilemma, a better idea than groups might be to associate the access to certain AEC subscriptions that are currently active. That way if people's subscriptions expire, or they get a free account, the content is still properly controlled regardless of their user groups.
Read more
  • 0
  • 0
  • 2042
Modal Close icon
Modal Close icon