Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Events
Videos
Audiobooks
Packt Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds

How-To Tutorials

7019 Articles
article-image-working-report-builder-microsoft-sql-server-2008-part-1
Packt
28 Oct 2009
16 min read
Save for later

Working with the Report Builder in Microsoft SQL Server 2008: Part 1

Packt
28 Oct 2009
16 min read
The Microsoft SQL Server 2008 Reporting Services Report Builder 2.0 tool can be installed from a standalone installer available at this Microsoft site, http://download.microsoft.com/download/a/f/6/af64f194-8b7e-4118-b040-4c515a7dbc46/ReportBuilder.msi. The same file is also available from a collection of download files when you access the Microsoft SQL Server 2008 Feature Pack, October 2008 at http://www.microsoft.com/downloads/details.aspx?FamilyId=228DE03F-3B5A-428A-923F-58A033D316E1&displaylang=en. Report Builder overview In the present version of SQL Server 2008 [Enterprise Evaluation edition] there  are two Report Builders available. Report Builder 1.0, which has remained as a program that can be launched from the Report Manager, and the new Report Builder 2.0, which is a stand alone report authoring tool that needs to be independently launched. Although Report Builder 1.0 can access Report Models built with Visual Studio 2008 and the Report Manager, it cannot be used to create reports using those models. It also does not work with Reports generated by Visual Studio 2008/BIDS/Report Builder 2.0. The errors can be summarized as follows: When you try to access the Report Server 2008 from the link provided on the Report Builder 1.0 interface you get the following error message: Specifying credentials in a URL is not supported When you try to open a report created using VS2008/BIDS/ReportBuilder2.0 using the Open Report… and Open File… navigational items in Report Builder 1.0 you get the following error message: System.IO.StreamReader: The Report element was not found Report Builder 1.0 allows you to access Report Models created with VS2008/BIDS/Report Manager and even allows you create a report in design view but this report cannot be processed on the Report Server. If you try to do so, you get the following error message: MemoryStream length must be non-negative and less than 2^31-1-origin. Parameter name: offset; Remote GDI stream version: ?. Expected version: 11.0.1 In this article the Report Builder 2.0 interface will be described along with the new features that are incorporated into this version. Report Builder 2.0 is admirably suited to address all items in the Report Definition Language of 2008. One of the important features of Report Builder 2.0 is the empowerment it provides business users to create ad hoc reports using the Report Models built on the databases they use. In this article you will be learning mostly about the Report Builder 2.0  interface details and working with it to create reports or modify them. It may be noted that Report Builder generates 2008 compliant RDL files as described in http://download.microsoft.com/download/6/5/7/6575f1c8-4607-48d2-941d-c69622e11c32/RDL_spec_08.pdf and therefore, cannot work with reports generated using 2005 technology. Report Builder 2.0 user interface description Report Builder is a report authoring tool and the basic procedure for authoring a report consists of the following steps: Report planning Connecting to a source of data Extracting a dataset from source Designing the report and data binding Previewing the report Although deploying the report is not included in the above, Report Builder can deploy the report as well. It is not always necessary to deploy a completed report, as any part of a report definition file can be deployed. This makes modifying a report on the server very flexible. In the following sections, the various parts of the Report Builder interface will be described starting at the very top and going to the bottom of the interface The menu for file operations Report Builder 2.0 can be accessed from Start | All Programs | Microsoft SQL Server 2008 Report Builder | Report Builder 2.0. This brings up the Report Builder Interface 2.0 as shown with the design area containing two icons: Table or Matrix and Chart. Each of these will launch a  related wizard which will step you through the various tasks. The Report Builder 2.0 interface is very similar to Office 2007. More than one instance of Report Builder can be launched. At the very top of the following screen shown you have the undo and redo controls as well as a save icon. When you click on the save icon the Save as Report window gets displayed as shown. Here you provide a name for the report. The default save extension is  *.rdl and it will be saved to the report server. It may also be persisted to a folder on your machine. Clicking on the Office Button (top left) opens a drop-down window shown in the following screenshot: In this window, you can carry out a number of tasks such as creating a new report, opening an existing report, saving a report, and saving a report with a different name. The Save button saves it to the default location seen earlier and Save as invokes the same window to save the report with a different name as seen earlier displying the report server instance as the Save to location. The Recent Documents pane shows the more recent reports created with this tool. New allows you to create a new report. When you click on Open, the following Open Report window gets displayed with the default location http://Hodentek2:8080/ReportServer_SANGAM/My Reports. You will also notice the message: This folder is not available because the My Reports feature is not enabled on the computer. Also the Open Reports window allows you look for reports with the extension .rdl. Therefore, unless the My Reports feature is enabled, this window is unusable. This is supposed to be possible from Report Manager but there are no controls in Report Manager that would do this. An alternative was suggested by one of the MSDN forum moderators (see http://social.msdn.microsoft.com/forums/en-US/sqlreportingservices/thread/6c695160-29e8-4185-be6d-5fe027a6975c/). Hands-on exercise (Part 2) will describe how you may enable My Reports. The idea of My Reports is similar to My Documents where each user can keep his reports. When the Options button (in the previous screenshot) is clicked it opens the window Report Builder Options window with two tabbed pages Settings and Resource shown as follows: Here you can view, as well as modify, Report Builder settings. The defaults are more than adequate to work with the examples in this book. Clicking on the Resources button brings up this interesting window which enables you to interact with Microsoft regarding SSRS activities, concerns, community, and so on. If you are serious about Reporting Services, these are very valuable links. The About button when clicked can provide you with Report Builder version information. The ribbon The main menu consists of Home, Insert, and View menu items which are part of the "ribbon". The ribbon introduced by Microsoft in Office 2007 is actually a container for other toolbar items. The ribbon is the replacement for the classic menus, toolbars, and is supposed to be more efficient and discoverable by the user. In fact you see a lot more on the "ribbon" than in the classic menu. Home The next figure shows the Home menu with its toolbar arranged from left to right and divided into sections. The Run toolbar item with the title Views when clicked would run the report open in the design view (in fact, even without a report open in the design view, the report can be run. The result would be the current date and time getting displayed in the center of the screen of an untitled report which has just ExecutionTime as the only item in the report). The Font, Paragraph, Border, and Number toolbar sections become enabled if parts of a report need editing. The formatting of textboxes in the report, the formatting of numbers in the report, and the alignment of components in the layout can all be independently managed using these toolbar items. Insert When you click on the Insert menu item on the "ribbon", the tabbed page for this item is displayed as shown in the following screenshot: It has four sections: Data Regions, Report Items, Subreports, and Header & Footer. These are all the normal items that are used either individually or together to make up a report. There can be more than one data region in a report. Data Regions In the Data Regions section you have both the Tablix (Table, Matrix, and List) and the graphic controls that can be bound to data—the Chart and the Gauge. Gauge is new in SQL Server Reporting Services 2008. Chart and gauge implementations are the off shoot of collaboration with Dundas (http://www.dundas.com/). Report Builder is built in such a way that the dataset must be defined before any of the data regions are added to the report body. For the purpose of describing the various data regions in this section, it is assumed (in order to get the screen shots shown here) that a dataset has been defined and the default wizards on the design surface have been removed. Table The Table is meant for displaying data retrieved from a database either all data detailed in groups or a combination (some grouped and some detailed) of both. It has a fixed number of columns which can be adjusted at design time. The table length expands to accommodate the rows. Data can be grouped by a single field or by multiple fields. Expression designer can be used in grouping as well. The grouping is carried out by creating row groups. Static rows can be added for row headings (labels) and totals. Aggregates for groups can be added. Both detailed data as well as grouped data can be hidden initially and the user can interactively reveal the data needed by drill downs. When you click on Insert | Table | Insert Table and then click on the design surface you can add a table to the design area. The table appears as shown with handles to adjust its dimensions. The table can be dragged to any other location on the design surface (the body of the report) as well. After placing the table, which by default has three columns and two rows, when you click on any other part of the design area you will see the table as shown. When you hover over the cell marked Data on the table you will see a little icon. This icon is a minimized version of the dataset fields. The grayed out feature that surrounds the table indicate the position of the rows and columns of the table. It also shows such other features as whether it is a detail, or whether it is a group. In the case of group, within a group the feature would indicate the nesting schematically as well. When you want to increase the size of a column or a row you can drag the double headed arrow that gets displayed when your cursor is placed between two columns or between two cells as shown. When you click on the dataset icon in the cell Data you get a drop-down list containing the fields in the dataset as shown. You can choose any of the fields to occupy the cell you clicked and the corresponding header will be added to the table. In this particular dataset there are nine fields and you can choose any of them to occupy the cell. When you right-click on a cell, a drop-down menu will be available. It can be used for the following: Work with the highlighted textbox (each cell of the table is a textbox) including to copy, cut, delete, and paste contents. Work with the properties of the Textbox. Populate the textbox with an expression using the expression builder. The expression builder gets displayed when fx Expression is clicked. Use Select to select the body or the Tablix. Insert a new column or a new row. Columns can be added to the right or the left of the clicked cell and rows can be added above or below the clicked cell. Delete columns and rows. Add a group. Both row and column groups can be added. When you click on the properties of the textbox, the Text Box Properties window is displayed. The textbox has several properties which are arranged on the left as a list with each item having its own page as shown. The Help button on any of the pages will take you directly to the definition of the properties and is extremely useful. In the General page, you can make changes to the elements in the Name, Value, and Sizing options page as shown. The Value is one which you choose among the column values (from the drop-down) from the dataset. You may also add a text for the ToolTip, which will display this text when the report is generated and this cell is accessed by hovering over it in the report. Alternatively you can set the Value and Tooltip using fx—the button that brings up the Expression window. In the Number page you can set the number and date data type formatting options for the cell that contains a number or a date. This is what you normally would find in most Microsoft products such as Excel and Access. In the Alignment page you can choose the vertical and horizontal alignments as well as the padding of the textbox content from the edges of the cell. Similarly the Font and Border properties are the same ones you find in most Microsoft products. The Fill property lets you add or change background color to the report as well as add a graphic element. The graphic element can be embedded, external, or originate from a database (being one of the fields accessed). Expressions can be developed to set a desired color for the Fill. The Visibility of the textbox can be any of Show, Hide, Show or Hide based on an expression. In each of these cases the visibility can be toggled when another table cell is clicked (which can be chosen). This page also gives access to the Expression window which is similar to the MS Access expression builder. The Interactive Sorting page allows you to define interactive sorting options on  the textbox. Matrix Matrix provides a similar functionality (roughly speaking rows against columns) to cross-tab reports in MS Access (http://aspalliance.com/1041_Creating_a_Crosstab_Report_in_Visual_Studio_2005_Using_Crystal_Reports.all) and Pivot Table dynamic views (http://www.aspfree.com/c/a/MS-SQL-Server/On-Accessing-Data-From-An-OLAP-Server-Using-MS-Excel/3/). The matrix should have at least one row group and one column group. The matrix can expand both ways to accommodate the data, horizontally for column groups and vertically for row groups. The matrix cells (intersection of rows and columns) display summary information (aggregates). When you click on Insert Matrix in the Insert menu and drop it on the design area of Report Builder 2.0, it gets displayed as shown in the following figure: Now if you click inside the boundary of the (2x2) empty matrix you will see more features of the matrix as shown in the following screenshot. The basic elements are the ColumnGroup (Column Groups), the RowGroup (Row Groups), and the Data. The group information is also displayed as shown by overlaid lines pointing to them. There needs to be a minimum of one group and one column for the matrix and there could be a hierarchy of column and row groups. The row and column group cells have their own properties which can be displayed when you right-click on them as shown in the next screenshot for the row group. When you right-click on the cell marked Rows, the following drop-down menu  pops up. In addition to the properties that you can set for the textbox in that cell, you have additional submenu items that work with the grouping and totaling. These are part of representing data in a matrix. Each of the Tablix for the Rows and Columns has the additional submenu items which are shown here for the Rows. Similar ones apply for the Columns as well. These are useful when you want to create nested groups. With the Matrix design interface in SQL Server 2005 this would not have been possible. Add Group Row Group Parent Group... Child Group... -------------------- Adjacent Above Adjacent Below Row Group Delete Group Group Properties Add Total Before After In addition to the above, each of the items Rows and Columns cells has the following items as well. These specify how new columns and rows are inserted with reference to the current cell as shown. The differences are due to the geometrical positions that are allowed for the new columns or rows as shown. For the "Columns" cell: Insert Column Inside Group-Left Inside Group-Right ------------------ Outside Group-Left Outside Group-Right Insert Row Inside Group-Above Inside Group-Below ------------------ Outside Group_Above For the "Rows" cell: Insert Column Inside Group-Left Inside Group-Right ------------------ Outside Group-Left Insert Row Inside Group-Above Inside Group-Below ------------------ Outside Group_Above Outside Group_Below Besides using a cell as a starting point, one could also use the rows as a whole or column as a whole to add further structure as shown in the next figure. Of course you need to use the proper submenu option to arrive at a particular matrix structure. Clicking at the indicated points would let you choose the structure you want for your matrix. If you click at the location shown for the Tablix you could choose to the delete the whole matrix. The Tablix graphical arrangement gives you the maximum flexibility in extending the matrix in 2-dimensions. List The list data region repeats for each row of data. List element provides a single container for the data which can be used to generate what are called Free Form Reports. In this kind of report there is no rigid structure such as a table for the data. You can also place a list inside another list or even a chart inside a list. You can drag a column from a dataset and drop it into the list. You can work with the list using the properties of the Rectangle it contains as well as its Tablix properties. As described earlier, the design interface is very flexible and you can leverage all features provided by the Tablix structure like displaying details and adding groups either independent, or nested. The properties pages described earlier allow you to sort and filter grouped data. When you drop a List on the design surface you will see just a single cell as shown. You can change its dimensions to suit your needs. When you click on the List you can access its handles as shown: When you add a List, there is one column and one row (just one cell). This can be extended in both directions by choosing the appropriate submenu items. These can be displayed by right-clicking on the handles as shown:
Read more
  • 0
  • 0
  • 7158

article-image-creating-dialplan-asterisk-16-part-1
Packt
27 Oct 2009
12 min read
Save for later

Creating a Dialplan in Asterisk 1.6: Part 1

Packt
27 Oct 2009
12 min read
When calls come into the switch, we tell Asterisk step-by-step how to handle the call. Steps can be as simple as playing a sound file to running a customized script. We are limited mostly by our imaginations at this point. We define all the steps we want Asterisk to perform in our extensions.conf file, in the customary location /etc/asterisk. Before we begin, we need to set priorityjumping=yes in the [general] section of extensions.conf. This will allow the tips and tricks in this article to work with Asterisk 1.6.x. Creating a context What is a context? Simply said, a context is a group of extensions. Each extension must exist within a context. There is more to contexts than grouping extensions though. In our extensions.conf file (or any included files), a context is denoted by square brackets "[ ]", as shown: [mycontext]. . . So, if a context is a group of extensions, why do we need more than one? Let's think for a minute. Not all employees should be able to dial every phone. Would you trust your 16-year-old intern with the ability to dial international calls? I wouldn't. Also, do you want your president to be bothered by customers in the waiting room who use a courtesy phone and misdial? We could find that hazardous to our continued employment. Certain extensions are hidden or made inaccessible from other extensions by context. This gives us some level of security. It also allows us to host multiple phone systems on a single server. Imagine you have two businesses on the same phone system, each with only two handsets. It'd be a pain to have each dial four digits to reach the other handset. We can use contexts to treat each company as if it were on a separate server. Something very important about contexts is we can include other contexts through the use of the include directive. This means all extensions in an included context are available. The value of this may not be immediately apparent, but soon we will see the full power of this tool. Suppose we have some context named bob. If we wanted bob to include default, then we would have the following in our extensions.conf: [bob]include => default This single line placed in any context gives that context the ability to dial any extension in the default context, as well as all contexts included in the default context. This means that if the default context included the foo context, then anybody in the bob context could dial extensions in the foo context. Suppose we had the following in our extensions.conf file: [foo]exten => 1,1,Playback(tt-monkeys)include => bar[bar]exten => 1,1,Playback(tt-weasels) Now I know that we haven't yet discussed the definition of extensions. That's OK. All we need to know is that extension 1 in foo will play back a file that sounds like monkeys, and extension 1 in bar will play back a file that says, "weasels have taken over our phone system". If we are in context foo and press 1, which file will play? This shows us the danger of include. We should be careful not to include multiple matches for the same extension. If we do include multiple contexts, the first included context with a match will win. Consider the following file: [foobar1]include => fooinclude => bar[foobar2]include => barinclude => foo If we are in context foobar1 and press 1, we will hear monkeys, while if we are in context foobar2 and press 1, we will hear weasels. While this can trip the unwary, we will use it to our advantage later on. Creating an extension We all have a good idea about what an extension is. On our legacy PBX, each handset was an extension. Pretty simple, right? While conceptually simple, there is a little wrinkle. If all we want to do is provide a few handsets, then there's one extension per phone. But Asterisk can do much more! We need to think of an extension as a group of commands that tells Asterisk to do some things. As amorphous as that may be, it's true. An extension can be tied to one handset, a queue, groups of handsets, or voicemail. An extension can be attributed to many different areas of the system. If you're familiar with programming terms, perhaps you could say that extensions are polymorphic. To go further, extensions can be used to provide access to other applications, sound files, or other services of Asterisk. Extensions are important to the magic of Asterisk. Now that we know why we create extensions, let's think about how we create them. Again, they are in the extensions.conf file, or any files that you include from there. We may decide to break up files such as extensions.conf into multiple configuration files. A common example of this is when we create large groups of extensions and choose to give each its own file. This also applies to the other configuration files we use. The general format for a line in the extensions.conf file is: exten => extensionnum,priority,action Let's take a closer look. Each line begins with the command exten. This is a directive inside Asterisk. You do not change this for each extension. Next, we have the extension number. Each extension has a unique number. This number is how Asterisk knows which set of commands to run. This extension can be detected in three major ways. First, the phone company may send it in with the calls, as is the case with DID numbers. Users can enter an extension using their touch-tone keys. Finally, there are a few special extensions defined. Some of these are: s: start extension. If no other extension number is entered, then this is the extension to execute. t: timeout extension. If a user is required to give input, but does not do so quickly enough, this is the extension that will be executed. i: invalid extension . If a user enters an extension that is not valid, this is the extension that will be executed. fax: fax calls. If Asterisk detects a fax, the call will be rerouted to this extension. Then we have the priority. Asterisk will start at priority 1 by default, complete the requested command, and then proceed to priority n+1. Some commands can force Asterisk to jump to priority n+101, allowing us to route based on decisions, such as if the phone is busy. Finally, we have the action. This is where we tell Asterisk what we want to do. Some of the more common actions we may want to perform are: Answer: This accepts the call. Many applications require that the call be answered before they can run as expected. Playback(filename): This command plays a file in .wav or .gsm format. It is important to note that the call must be answered before playing. Background(filename): This command is like Playback, except that it listens for input from the user. It too requires that the call be answered first. Goto(context,extension,priority): Here, we send the call to the specified context, extension, and priority. While useful, this can be a bad style, as it can be very confusing to us if something goes wrong. However, it can be a good style if it keeps us from duplicating extension definitions, as moves, adds, or changes would only have to be updated in one place. Queue(queuename|options): This command does what it seems like it should. It places the current call in the queue, which we should have already defined in the queues.conf file. Voicemail(extension): This transfers the current call to the voicemail application. There are some special options as well. If we preceed the extension with the letter s, it skips the greeting. When we place the letter u before the extension, it uses the unavailable greeting, and b uses the busy greeting. VoicemailMain: This application allows users to listen to their messages, and also record their greetings and name, and set other configuration options. Dial(technology/id,options,timeout): This is where we tell Asterisk to make the phone ring, and when the line is answered, to bridge the call. Common options include: t: Allow the called user to transfer the call by pressing the # key. T: Allow the calling user to transfer the call by pressing the # key. r: Indicate ringing to the calling party. m: Provide music on hold to the calling party. H: Allow the calling party to hang up by pressing the * key. g: Go on in the context if the destination hangs up. While this list is not exhaustive, it should be enough to get us started. Suppose we just want to make a DAHDI phone ring, which is on interface 1, and we are going to work completely in the default context. Our extensions.conf file would look like: [default]exten => s,1,Dial(dahdi/1) Pretty simple, right? Now, imagine we want to transfer to the voicemail of user 100 if someone is on the phone. As Dial sends you to priority n+101 when the line is busy or not available, all we have to do is define what we want to do. Our dialplan would look like: [default]exten => s,1,Dial(dahdi/1)exten => s,102,Voicemail(b100) Great! We have some of the functionality that users have come to expect. But are you happy yet? The problem is that a phone could ring for years before someone picks it up. So, for our next exercise, suppose we want to transfer the call to voicemail when the phone is not answered in 30 seconds. So, obviously, we're going to have to use the option in Dial to define a time-out. Our dialplan would have something like: [default]exten => s,1,Dial(dahdi/1|30)exten => s,2,Voicemail(u100)exten => s,102,Voicemail(b100) All we're doing is telling Asterisk how to handle the call, in a step-by-step way. It is important to think about all scenarios that a call can go through, and plan for them. Just to reiterate a point I made earlier, planning ahead will save us hours of debugging later. Suppose we want to send anyone who is in a place where they shouldn't be to user 0's voicemail, which will be checked periodically by the receptionist. [default]exten => s,1,Dial(dahdi/1|30)exten => s,2,Voicemail(u100)exten => s,102,Voicemail(b100)exten => i,1,Voicemail(s0)exten => t,1,Voicemail(s0) All right, we're getting somewhere now! At least we know each call will be handled in some way. What about faxes? Suppose we have only one fax machine (or a centralized fax server) on DAHDI interface 2, then our dialplan should look similar to: [default]exten => s,1,Dial(dahdi/1|30)exten => s,2,Voicemail(u100)exten => s,102,Voicemail(b100)exten => i,1,Voicemail(s0)exten => t,1,Voicemail(s0)exten => fax,1,Dial(dahdi/2) Congratulations! We now have a working phone system. May be not the most interesting yet, but we're making great progress. Don't worry, our phone system will grow in features. Now, to create a list of useful extensions, we need to define a set of commands for each handset we have. Suppose we have three SIP phone users—1001-1003, with extensions 1001-1003. Our default context would look like: [default]exten => 1001,1,Dial(SIP/1001|30)exten => 1001,2,Voicemail(u1001)exten => 1001,102,Voicemail(b1001)exten => 1002,1,Dial(SIP/1002|30)exten => 1002,2,Voicemail(u1002)exten => 1002,102,Voicemail(b1002)exten => 1003,1,Dial(SIP/1003|30)exten => 1003,2,Voicemail(u1003)exten => 1003,102,Voicemail(b1003)exten => i,1,Voicemail(s0)exten => t,1,Voicemail(s0)exten => fax,1,Dial(dahdi/2) For every extension we add, the length of extensions.conf will grow by four lines (three lines of code, and one line of whitespace). This is not very easy to read, and it is very easy to make mistakes. There has to be a better way, right? Of course there is! We can use macros to define common actions. We will create a special macro context. The name of these contexts always starts with macro-. Suppose we want to call this one macro-normal. We would have: [macro-normal]exten => s,1,Dial(${ARG2}|30)exten => s,2,Voicemail(u${ARG1})exten => s,102,Voicemail(b${ARG1}) Now, to create the same three extensions, we would have: exten => 1001,1,Macro(normal|1001|SIP/1001)exten => 1002,1,Macro(normal|1002|SIP/1002)exten => 1003,1,Macro(normal|1003|SIP/1003) So now, each extension we add requires only one extra line in extensions.conf. This is much more efficient and less prone to errors. But what if we knew that any four-digit extension beginning with a 1 would be a normal, SIP extension? Here it is time for us to discuss Asterisk's powerful pattern-matching capabilities. We can define extensions with certain special wildcards in them, and Asterisk will match any extension that fits the description. Using the underscore (_) character warns Asterisk that the extension number will include pattern matching. When matching patterns, the X character represents any number (0 to 9), the Z character will match the numbers 1 to 9, the N character represents numbers 2 to 9, and the period (.) represents a string of any number of digits. Also, we can use certain variables in our dialplan. One such variable is ${EXTEN}, which represents the extension that was used. So, for this example, we could use the following definition: exten => _1XXX,1,Macro(normal|${EXTEN}|SIP/${EXTEN}) This one line of code has now defined 1000 extensions, from 1000 to 1999. All we have to do is ensure that our voicemail user, extension, and SIP user are all the same number. Pretty cool, huh? Note that if we wish to modify the behavior of all extensions, all we have to do is modify the macro. This should help us quite a bit as we tweak Asterisk to fit our business needs.
Read more
  • 0
  • 0
  • 6453

article-image-podcasting-and-images-drupal
Packt
27 Oct 2009
8 min read
Save for later

Podcasting and Images in Drupal

Packt
27 Oct 2009
8 min read
Getting Started with Podcasts To create a podcast, you will need: A mp3 file A place to store the mp3 file At the risk of stating the obvious, a good podcast requires thought and planning before you make the actual recording. Later in the article, we will discuss some of these general mechanics. But, from a technical perspective, once you have your audio file, you can upload it to your Drupal site, and you will have published a podcast. Audio Module The Audio module supports the playback of audio files that have been uploaded to your site. To install this module, we will also need to install two helper modules required by the Audio module: the getID3() and Token modules. In this section, we will cover installing the Audio module, as well as the getID3() and Token modules. Install the getID3() Module Download the getID3() module from http://drupal.org/project/getid3, and upload it to your sites/all/modules directory . Do not, however, enable the module, as we need to install an additional piece of code described as follows. Install the getID3() Libraries The getID3() libraries are a tool that automatically extract information about audio files. These libraries don't require you to do any additional work; rather, they detect information that can be used by the Audio module. Download the getID3() libraries from http://getid3.sourceforge.net/. Unzip these libraries onto your hard drive. As shown in the preceding screenshot, the libraries include some demo and helper files, in addition to the readme and license files. The only files we need are contained in the getid3 directory. The getid3 directory is the only directory that you need to upload to your website. Then, use your FTP client to connect to your web server, and navigate to sites/all/modules/getid3. Upload the getid3 directory into sites/all/modules/getid3 as shown in the following screenshot: Once the module and the libraries have been uploaded to your site, enable the getID3() module by clicking the Administration | Site building | Modules link, or by navigating to admin/build/modules. Following these instructions the path to your getID3() library is sites/all/modules/getid3/getid3. If needed, this path can be adjusted at Administer | Site configuration | getID3(), or admin/settings/getid3. Install the Token Module Download the Token module from http://drupal.org/project/token, and install it. Once this module has been uploaded to your site, enable it by clicking the Administration | Site building | Modules link, or by navigating to admin/build/modules. The Token module is a helper module, and its functionality will be largely invisible to the end user. The Token module supplies pieces of text, or tokens, which can be used by other modules. The Audio module relies on the Token module and the getID3() module to help automatically generate titles and other information for audio files. Install and Enable the Audio Module Download the audio module from http://drupal.org/project/audio. Upload the module to your sites/all/modules directory, and enable it by clicking the Administer | Site building | Modules link or by navigating to admin/build/modules. Select the Audio and the Audio getID3 modules. Click the Save configuration button to submit the form and enable the modules. Configure the Audio Module Now that we have installed the Audio module and its helper modules, we need to configure the audio module to support our needs. Click the Administer | Site Configuration | Audio link, or navigate to admin/settings/audio. As pictured in the following screenshot, you will see three tabs across the top of the page: Audio, Metadata tags, and Players. The Audio Tab The options on the Audio tab, pictured in the preceding screenshot, allow you to set some default values that are used when audio posts are uploaded. The values here can be created automatically, which can be useful if you are working with songs. For most cases, however, you will want to delete the option for the Default node title format, and leave the other default values intact. When you have adjusted the settings, click the Save configuration button at the bottom of the page. To save your settings, you must click the Save configuration button before moving on to the next tab. A Brief Explanation of Tokens In the preceding screenshot, there is a collapsible fieldset titled List of available tokens. Click on the link to expand the fieldset. A portion of the tokens available are shown in the following screenshot: As suggested by the preceding screenshot, tokens expose pieces of information about content created within a site. Tokens can only be used when a module has been written to work with the tokens. Because the Audio module has been written to depend on the Token module, we have the option of using tokens if we wish. For example, we could set the title of audio nodes to automatically incorporate the username and the creation date. To make this work, we would set the Default node title format (as shown in the Audio settings screenshot) to Created by [author-name] on [yyyy]-[mon]-[date]. In most cases tokens run invisibly in the background without requiring any adjustments by the end user. The Metadata Tags Tab The options in this section will be useful if you are setting up podcasts as part of a music or radio station, but will be less useful in other environments. By reducing the number of required options, you can simplify the form for uploading podcasts. The settings pictured in the following screenshot are all you need to get started publishing audio on the web. The Players Tab The Audio module comes with several different players that can be used to play your audio files. You can use the settings on this page to choose your preferred player. As you can see in the following figure, you can specify a different player for each type of audio file. The "best" player will largely be determined by your aesthetic preference; all of the players do a great job playing audio stored on your site. After you have chosen a player, click the Save configuration button to save your preference. Assign Rights to the Audio Module Now that we have installed, enabled, and configured the audio module, we need to assign rights to it. Click the Administer | User management | Roles link, or navigate to admin/user/roles. The possible rights that can be assigned are shown in the following figure: We will need to assign rights for the teacher role, the student role, the authenticated user role, and possibly the anonymous user role. For the authenticated user role, assign rights to download audio and play audio. For the student role, assign rights to create audio and edit own audio. For the teacher role, assign rights to create audio, edit own audio, and view download stats. For the anonymous user role, assign the rights you think are appropriate. In most cases, if you are allowing anonymous users to see content, allowing them the rights to download audio and play audio is appropriate. Each time you assign rights to an individual roles, click the Save permissions button to save the rights for the role. Adjust Existing Views Currently, three views are being used to display student and teacher-created content. We will need to edit these views so that they return any audio nodes created within the site. To edit these views, click the Administer | Site building | Views link, or navigate to admin/build/views. We need to edit three views: the teacher_blog view, the student_blog and conversation views. As shown in the following screenshot, these views can be edited by using the Edit link on the main Views administration page. Editing the student_blog View Click the Edit link as shown in the preceding screenshot. Then, in the Defaults display, under Filters, click on the Node: Type link, as shown by Item 1 in the following screenshot: As shown by Item 2 in the preceding screenshot, add Audio to the node types returned in this view. Click the Update button to store this change, and then click the Save button (not pictured in the preceding screenshot) to save the view. Editing the conversations View Click the Edit link for the conversations view. Then, in the Defaults display, under Arguments, click on the Search:Links to link, as shown by Item 1 in the following figure: As shown by Item 2 in the preceding screenshot, add Audio to the list of node types where this view will be validated. Click the Update button to store this change, and then click the Save button to save the view. As we add additional content types into the site, we will need to update these views to account for the newly-added content types.
Read more
  • 0
  • 0
  • 1849

article-image-building-user-portal-sermyadmin-openser
Packt
27 Oct 2009
8 min read
Save for later

Building the User Portal with SerMyAdmin for OpenSER

Packt
27 Oct 2009
8 min read
SerMyAdmin Originally, this material was written for SerWeb. SerWeb was originally developed for the SER project. Unfortunately, SerWeb became incompatible with newer versions of OpenSER. Another important aspect of SerWeb to be considered is its vulnerabilities. There are very few options for web interfaces to OpenSER. One of the tools we have found is OpenSER administrator. This tool is being developed using Ruby on Rails. While it seems to be a very good tool to administer an OpenSER server, it does not permit to provisioning users in the same way that SerWeb did and it lacks multi-domain support. OpenSER administrator can be found at http://sourceforge.net/projects/openseradmin. Since a tool to build an OpenSER portal was not available , we decided to build our own tool named SerMyAdmin using Java. After a slow start, it is now ready and we are using it to build a book. It is licensed according to GPLv2 and developed in Grails (Groovy on rails). It can be downloaded at http://sourceforge.net/projects/sermyadmin. What you are seeing here is the standalone tool. In our roadmap, we intend to integrate SerMyAdmin into the Liferay portal. Using a content management system such as Liferay (www.liferay.com) will make your task of building a portal much easier than it is today. The SerMyAdmin project can be found at sermyadmin.sourceforge.net. The idea is to facilitate the administration of the OpenSER database. SerMyAdmin is licensed under the GPLv2. Lab—Installing SerMyAdmin SerMyAdmin uses the Grails framework, so it needs an application server. You can choose from many application servers, such as IBM WebSphere, JBoss, Jetty, Tomcat, and so on. In this article we will use Apache Tomcat, because it's free and easy to install. Because we use some Java 1.5 features, we'll need Sun's Java JDK, not the free alternative GCJ. Step 1: Create an administrator for SerMyAdmin: mysql –u rootuse openserINSERT INTO 'subscriber' ( 'id' , 'username' , 'domain' , 'password' , 'first_name' , 'last_name' , 'email_address' , 'datetime_created' , 'ha1' , 'ha1b' , 'timezone' , 'rpid' , 'version' , 'password_hash' , 'auth_username' , 'class' , 'domain_id' , 'role_id' )VALUES (NULL , 'admin', 'openser.org', 'senha', 'Admin', 'Admin', 'admin@openser.org', '0000-00-00 00:00:00', '1', '1', '1', '1', '1', NULL , 'admin@openser.org', NULL , '1', '3'); Step 2: The next step we will take is to update our source's list to use the contrib repository and non-free packages. Our /etc/apt/sources.list, should look like below: # /etc/apt/souces.listdeb http://ftp.br.debian.org/debian/ etch main contrib non-freedeb-src http://ftp.br.debian.org/debian/ etch main contrib non-freedeb http://security.debian.org/ etch/updates main contrib non-freedeb-src http://security.debian.org/ etch/updates main contrib non-free/etc/apt/sources.list Notice that we have added only the keywords contrib and non-free after our repository definitions. Step 3: Update the package listing using the following command: openser:~# apt-get update Step 4: Install Sun's Java 1.5, running the command below: openser:~# apt-get install sun-java5-jdk Step 5: Make sure you are using Sun's Java. Please, run the command below to tell Debian that you want to use Sun's Java as your default Java implementation. openser:~# update-java-alternatives -s java-1.5.0-sun Step 6: If everything has gone well so far, you should run the following command and get a similar output. openser:~# java -version java version "1.5.0_14" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_14-b03) Java HotSpot(TM) Client VM (build 1.5.0_14-b03, mixed mode, sharing) Step 7: Install Tomcat. You can obtain Tomcat at: http://tomcat.apache.org/download-60.cgi. To install Tomcat, just run the commands below: openser:/usr/local/etc/openser# cd /usr/localopenser:/usr/local# wget http://mirrors.uol.com.br/pub/apache/tomcat/tomcat-6/v6.0.16/bin/apache-tomcat-6.0.16.tar.gzopenser:/usr/local# tar zxvf apache-tomcat-6.0.16.tar.gzopenser:/usr/local# ln -s apache-tomcat-6.0.16 tomcat6 Step 8: To start Tomcat on your server initialization, please copy the following script to /etc/init.d/tomcat6. #! /bin/bash –e#### BEGIN INIT INFO# Provides: Apache’s Tomcat 6.0# Required-Start: $local_fs $remote_fs $network# Required-Stop: $local_fs $remote_fs $network# Default-Start: 2 3 4 5# Default-Stop: S 0 1 6# Short-Description: Tomcat 6.0 Servlet engine# Description: Apache’s Tomcat Servlet Engine### END INIT INFO## Author: Guilherme Loch Góes <glwgoes@gmail.com>#set -ePATH=/bin:/usr/bin:/sbin:/usr/sbin:CATALINA_HOME=/usr/local/tomcat6CATALINA_BIN=$CATALINA_HOME/bintest -x $DAEMON || exit 0. /lib/lsb/init-functionscase "$1" in start) echo "Starting Tomcat 6" "Tomcat6" $CATALINA_BIN/startup.sh log_end_msg $? ;; stop) echo "Stopping Tomcat6" "Tomcat6" $CATALINA_BIN/shutdown.sh log_end_msg $? ;; force-reload|restart) $0 stop $0 start ;; *) echo "Usage: /etc/init.d/tomcat6 {start|stop|restart}" exit 1 ;;esacexit 0 Step 9: Instruct Debian to run your script on startup; we do this with the command below. openser: chmod 755 /etc/init.d/tomcat6 openser:/etc/init.d# update-rc.d tomcat6 defaults 99 Step 10: To make sure everything is running correctly, reboot the server and try to open in your browser the URL http://localhost:8080; if everything is OK you'll be greeted with Tomcat's start page. Step 11: Install the MySQL driver for Tomcat, so that SerMyAdmin can access your database. This driver can be found at http://dev.mysql.com/downloads/connector/j/5.1.html. You should download the driver and unpack it, then copy the connector to Tomcat's shared library directory, as follows. openser:/usr/src# tar zxf mysql-connector-java-5.1.5.tar.gz openser:/usr/src# cp mysql-connector-java-5.1.5/mysql-connector-java-5.1.5-bin.jar /usr/local/tomcat6/lib Step 12: Declare the data source for SerMyAdmin to connect to OpenSER's database. You can do this in an XML file found at /usr/local/tomcat6/conf/context.xml. The file should look as below: <?xml version="1.0" encoding="UTF-8"?><Context path="/serMyAdmin"> <Resource auth="Container" driverClassName="com.mysql.jdbc.Driver" maxActive="20" maxIdle="10" maxWait="-1" name="jdbc/openser_MySQL" type="javax.sql.DataSource" url="jdbc:mysql://localhost:3306/openser" username="sermyadmin" password="secret"/></Context> In the file above, please change the highlighted parameters according to your scenario. SerMyAdmin can be installed in a different server than the one that holds the database. Do this for better scalability when possible. The default MySQL installation on Debian only accepts requests from localhost, so you should edit the file /etc/mysql/my.cnf, for MySQL to accept requests from external hosts. Step 13: Create a user to be referenced in the file context.xml. This user will have the required access to the database. Please, run the commands below: openser:/var/lib/tomcat5.5/conf# mysql -u root –p Enter password: Welcome to the MySQL monitor. Commands end with ; or g. Your MySQL connection id is 14 Server version: 5.0.32-Debian_7etch5-log Debian etch distribution Type 'help;' or 'h' for help. Type 'c' to clear the buffer. mysql> grant all privileges on openser.* to sermyadmin@'%' identified by 'secret'; Query OK, 0 rows affected (0.00 sec) Step 14: We're almost there. The next step is to deploy the SerMyAdmin WAR file. Please, download and copy the file serMyAdmin.war to Tomcat's webapps directory. Restart it, to activate the changes. openser:/usr/src# cp serMyAdmin-0.4.war /usr/local/tomcat6/webapps/serMyAdmin.war openser:/usr/src# invoke-rc.d tomcat6 restart Don't worry about database modifications; SerMyAdmin will automatically handle that for you. Step 15: Configure Debian's MTA (Message Transfer Agent) to allow SerMyAdmin to send a confirmation email to new users. Run the command below to configure Exim4 (default MTA for Debian). Ask your company's email administrator. openser:/# apt-get install exim4 openser:/# dpkg-reconfigure exim4-config You will be greeted with a dialog-based configuration menu; on this menu it's import to pay attention to two options: General type of mail configuration, which should be set to Internet Site so that we can send and receive mails directly using SMTP, and Domains to relay mail for, which should be set to the domain from which you want the emails from SerMyAdmin to appear to come. Step 16: Customize the file /usr/local/apache-tomcat-6.0.16/webapps/serMyAdmin-0.3/WEB-INF/spring/resource.xml, which contains the parameters that specify which email server is used to send mails and from whom these emails should appear to come from. The following is an example of this file: <?xml version="1.0" encoding="UTF-8"?><beans xsi_schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl"> <property name="host"><value>localhost</value></property> </bean> <!-- You can set default email bean properties here, eg: from/to/subject --> <bean id="mailMessage" class="org.springframework.mail.SimpleMailMessage"> <property name="from"><value>admin@sermyadmin.org</value></property> </bean></beans> The first parameter to change is the server that we will use to send emails. The second is the parameter specifying from whom those emails will appear to come. Restart Tomcat again and we're ready to go. When you point your browser to http://<server address>:8080/serMyAdmin you should be greeted with the login page, the same as we have shown at the start on this article.
Read more
  • 0
  • 0
  • 1851

article-image-implementing-basic-helloworld-wcf-windows-communication-foundation-service
Packt
27 Oct 2009
7 min read
Save for later

Implementing a Basic HelloWorld WCF (Windows Communication Foundation) Service

Packt
27 Oct 2009
7 min read
We will build a HelloWorld WCF service by carrying out the following steps: Create the solution and project Create the WCF service contract interface Implement the WCF service Host the WCF service in the ASP.NET Development Server Create a client application to consume this WCF service Creating the HelloWorld solution and project Before we can build the WCF service, we need to create a solution for our service projects. We also need a directory in which to save all the files. Throughout this article, we will save our project source codes in the D:SOAwithWCFandLINQProjects directory. We will have a subfolder for each solution we create, and under this solution folder, we will have one subfolder for each project. For this HelloWorld solution, the final directory structure is shown in the following image: You don't need to manually create these directories via Windows Explorer; Visual Studio will create them automatically when you create the solutions and projects. Now, follow these steps to create our first solution and the HelloWorld project: Start Visual Studio 2008. If the Open Project dialog box pops up, click Cancel to close it. Go to menu File | New | Project. The New Project dialog window will appear. From the left-hand side of the window (Project types), expand Other Project Types and then select Visual Studio Solutions as the project type. From the right-hand side of the window (Templates), select Blank Solution as the template. At the bottom of the window, type HelloWorld as the Name, and D:SOAwithWCFandLINQProjects as the Location. Note that you should not enter HelloWorld within the location, because Visual Studio will automatically create a folder for a new solution. Click the OK button to close this window and your screen should look like the following image, with an empty solution. Depending on your settings, the layout may be different. But you should still have an empty solution in your Solution Explorer. If you don't see Solution Explorer, go to menu View | Solution Explorer, or press Ctrl+Alt+L to bring it up. In the Solution Explorer, right-click on the solution, and select Add | New Project… from the context menu. You can also go to menu File | Add | New Project… to get the same result. The following image shows the context menu for adding a new project. The Add New Project window should now appear on your screen. In the left-hand side of this window (Project types), select Visual C# as the project type, and on the right-hand side of the window (Templates), select Class Library as the template. At the bottom of the window, type HelloWorldService as the Name. Leave D:SOAwithWCFandLINQProjectsHelloWorld as the Location. Again, don't add HelloWorldService to the location, as Visual Studio will create a subfolder for this new project (Visual Studio will use the solution folder as the default base folder for all the new projects added to the solution). You may have noticed that there is already a template for WCF Service Application in Visual Studio 2008. For the very first example, we will not use this template. Instead, we will create everything by ourselves so you know what the purpose of each template is. This is an excellent way for you to understand and master this new technology. Now, you can click the OK button to close this window. Once you click the OK button, Visual Studio will create several files for you. The first file is the project file. This is an XML file under the project directory, and it is called HelloWorldService.csproj. Visual Studio also creates an empty class file, called Class1.cs. Later, we will change this default name to a more meaningful one, and change its namespace to our own one. Three directories are created automatically under the project folder—one to hold the binary files, another to hold the object files, and a third one for the properties files of the project. The window on your screen should now look like the following image: We now have a new solution and project created. Next, we will develop and build this service. But before we go any further, we need to do two things to this project: Click the Show All Files button on the Solution Explorer toolbar. It is the second button from the left, just above the word Solution inside the Solution Explorer. If you allow your mouse to hover above this button, you will see the hint Show All Files, as shown in above diagram. Clicking this button will show all files and directories in your hard disk under the project folder-rven those items that are not included in the project. Make sure that you don't have the solution item selected. Otherwise, you can't see the Show All Files button. Change the default namespace of the project. From the Solution Explorer, right-click on the HelloWorldService project, select Properties from the context menu, or go to menu item Project | HelloWorldService Properties…. You will see the project properties dialog window. On the Application tab, change the Default namespace to MyWCFServices. Lastly, in order to develop a WCF service, we need to add a reference to the ServiceModel namespace. On the Solution Explorer window, right-click on the HelloWorldService project, and select Add Reference… from the context menu. You can also go to the menu item Project | Add Reference… to do this. The Add Reference dialog window should appear on your screen. Select System.ServiceModel from the .NET tab, and click OK. Now, on the Solution Explorer, if you expand the references of the HelloWorldService project, you will see that System.ServiceModel has been added. Also note that System.Xml.Linq is added by default. We will use this later when we query a database. Creating the HelloWorldService service contract interface In the previous section, we created the solution and the project for the HelloWorld WCF Service. From this section on, we will start building the HelloWorld WCF service. First, we need to create the service contract interface. In the Solution Explorer, right-click on the HelloWorldService project, and select Add | New Item…. from the context menu. The following Add New Item - HelloWorldService dialog window should appear on your screen. On the left-hand side of the window (Categories), select Visual C# Items as the category, and on the right-hand side of the window (Templates), select Interface as the template. At the bottom of the window, change the Name from Interface1.cs to IHelloWorldService.cs. Click the Add button. Now, an empty service interface file has been added to the project. Follow the steps below to customize it. Add a using statement: using System.ServiceModel; Add a ServiceContract attribute to the interface. This will designate the interface as a WCF service contract interface. [ServiceContract] Add a GetMessage method to the interface. This method will take a string as the input, and return another string as the result. It also has an attribute, OperationContract. [OperationContract] String GetMessage(String name); Change the interface to public. The final content of the file IHelloWorldService.cs should look like the following: using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.ServiceModel;namespace MyWCFServices{[ServiceContract]public interface IHelloWorldService{[OperationContract]String GetMessage(String name);}}
Read more
  • 0
  • 0
  • 2748

article-image-python-data-persistence-using-mysql-part-ii-moving-data-processing-data
Packt
27 Oct 2009
8 min read
Save for later

Python Data Persistence using MySQL Part II: Moving Data Processing to the Data

Packt
27 Oct 2009
8 min read
To move data processing to the data, you can use stored procedures, stored functions, and triggers. All these components are implemented inside the underlying database, and can significantly improve performance of your application due to reducing network overhead associated with multiple calls to the database. It is important to realize, though, the decision to move any piece of processing logic into the database should be taken with care. In some situations, this may be simply inefficient. For example, if you decide to move some logic dealing with the data stored in a custom Python list into the database, while still keeping that list implemented in your Python code, this can be inefficient in such a case, since it only increases the number of calls to the underlying database, thus causing significant network overhead. To fix this situation, you could move the list from Python into the database as well, implementing it as a table. Starting with version 5.0, MySQL supports stored procedures, stored functions, and triggers, making it possible for you to enjoy programming on the underlying database side. In this article, you will look at triggers in action. Stored procedures and functions can be used similarly. Planning Changes for the Sample Application Assuming you have followed the instructions in Python Data Persistence using MySQL, you should already have the application structure to be reorganized here. To recap, what you should already have is: tags nested list of tags used to describe the posts obtained from the Packt Book Feed page. obtainPost function obtains the information about the most recent post on the Packt Book Feed page. determineTags function determines tags appropriate to the latest post obtained from the Packt Book Feed page. insertPost function inserts the information about the obtained post into the underlying database tables: posts and posttags. execPr function brings together the functionality of the described above functions. That’s what you should already have on the Python side. And on the database side, you should have the following components: posts table contains records representing posts obtained from the Packt Book Feed page. posttags table contains records each of which represents a tag associated with a certain post stored in the posts table. Let’s figure out how we can refactor the above structure, moving some data processing inside the database. The first thing you might want to do is to move the tags list from Python into the database, creating a new table tags for that. Then, you can move the logic implemented with the determineTags function inside the database, defining the AFTER INSERT trigger on the posts table. From within this trigger, you will also insert rows into the posttags table, thus eliminating the need to do it from within the insertPost function. Once you’ve done all that, you can refactor the Python code implemented in the appsample module. To summarize, here are the steps you need to perform in order to refactor the sample application discussed in the earlier article: Create tags table and populate it with the data currently stored in the  tags list implemented in Python. Define the AFTER INSERT trigger on the posts table. Refactor the insertPost function in the appsample.py module. Remove the tags list from the appsample.py module. Remove the determineTags function from the appsample.py module. Refactor the execPr function in the appsample.py module. Refactoring the Underlying Database To keep things simple, the tags table might contain a single column tag with the primary key constraint defined on it. So, you can create the tags table as follows: CREATE TABLE tags ( tag VARCHAR(20) PRIMARY KEY ) ENGINE = InnoDB; Then, you might want to modify the posttags table, adding a foreign key constraint to its tag column. Before you can do that, though, you will need to delete all the rows from this table. This can be done with the following query: DELETE FROM posttags; Now you can move on and alter posttags as follows: ALTER TABLE posttags ADD FOREIGN KEY (tag) REFERENCES tags(tag); The next step is to populate the tags table. You can automate this process with the help of the following Python script: >>> import MySQLdb >>> import appsample >>> db=MySQLdb.connect(host="localhost",user="usrsample",passwd="pswd",db=">>> dbsample") >>> c=db.cursor() >>> c.executemany("""INSERT INTO tags VALUES(%s)""", appsample.tags) >>> db.commit() >>> db.close() As a result, you should have the tags table populated with the data taken from the tags list discussed in Python Data Persistence using MySQL. To make sure it has done so, you can turn back to the mysql prompt and issue the following query against the tags table: SELECT * FROM tags; The above should output the list of tags you have in the tags list. Of course, you can always extend this list, adding new tags with the INSERT statement. For example, you could issue the following statement to add the Visual Studio tag: INSERT INTO tags VALUES('Visual Studio'); Now you can move on and define the AFTER INSERT trigger on the posts table: delimiter // CREATE TRIGGER insertPost AFTER INSERT ON posts FOR EACH ROW BEGIN INSERT INTO posttags(title, tag) SELECT NEW.title as title, tag FROM tags WHERE LOCATE(tag, NEW.title)>0; END // delimiter ; As you can see, the posttags table will be automatically populated with appropriate tags just after a new row is inserted into the posts table. Notice the use of the INSERT … SELECT statement in the body of the trigger. Using this syntax lets you insert several rows into the posttags table at once, without having to use an explicit loop. In the WHERE clause of SELECT, you use standard MySQL string function LOCATE returning the position of the first occurrence of the substring, passed in as the first argument, in the string, passed in as the second argument. In this particular example, though, you are not really interested in obtaining the position of an occurrence of the substring in the string. All you need to find out here is whether the substring appears in the string or not. If it is, it should appear in the posttags table as a separate row associated with the row just inserted into the posts table. Refactoring the Sample’s Python Code Now that you have moved some data and data processing from Python into the underlying database, it’s time to reorganize the appsample custom Python module created as discussed in Python Data Persistence using MySQL. As mentioned earlier, you need to rewrite the insertPost and execPr functions and remove the determineTags function and the tags list. This is what the appsample module should look like after revising: import MySQLdb import urllib2 import xml.dom.minidom def obtainPost(): addr = "http://feeds.feedburner.com/packtpub/sDsa?format=xml" xmldoc = xml.dom.minidom.parseString(urllib2.urlopen(addr).read()) item = xmldoc.getElementsByTagName("item")[0] title = item.getElementsByTagName("title")[0].firstChild.data guid = item.getElementsByTagName("guid")[0].firstChild.data pubDate = item.getElementsByTagName("pubDate")[0].firstChild.data post ={"title": title, "guid": guid, "pubDate": pubDate} return post def insertPost(title, guid, pubDate): db=MySQLdb.connect(host="localhost",user="usrsample",passwd="pswd",db="dbsample") c=db.cursor() c.execute("""INSERT INTO posts (title, guid, pubDate) VALUES(%s,%s,%s)""", (title, guid, pubDate)) db.commit() db.close() def execPr(): p = obtainPost() insertPost(p["title"], p["guid"], p["pubDate"]) If you compare it with appsample discussed in Part 1, you should notice that the revision is much shorter. It’s important to note, however, that nothing has changed from the user standpoint. So, if you now start the execPr function in your Python session: >>>import appsample >>>appsample.execPr() This should insert a new record into the posts table, inserting automatically corresponding tags records into the posttags table, if any. The difference lies in the way it’s going on behind the scenes. Now the Python code is responsible only for obtaining the latest post from the Packt Book Feed page and then inserting a record into the posts table. Dealing with tags is now responsibility of the logic implemented inside the database. In particular, the AFTER INSERT trigger defined on the posts table should take care of inserting the rows into the posttags table. To make sure that everything has worked smoothly, you can now check out the content of the posts and posttags tables. To look at the latest post stored in the posts table, you could issue the following query: SELECT title, str_to_date(pubDate,'%a, %e %b %Y') lastdate FROM posts ORDER BY lastdate DESC LIMIT 1; Then, you might want to look at the related tags stored in the posttags tables, by issuing the following query: SELECT p.title, t.tag, str_to_date(p.pubDate,'%a, %e %b %Y') lastdate FROM posts p, posttags t WHERE p.title=t.title ORDER BY lastdate DESC LIMIT 1; Conclusion In this article, you looked at how some business logic of a Python/MySQL application can be moved from Python into MySQL. For that, you continued with the sample application originally discussed in Python Data Persistence using MySQL.
Read more
  • 0
  • 0
  • 4620
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 €18.99/month. Cancel anytime
article-image-creating-shopping-cart-using-zend-framework-part-2
Packt
27 Oct 2009
15 min read
Save for later

Creating a Shopping Cart using Zend Framework: Part 2

Packt
27 Oct 2009
15 min read
Creating the Cart Views and Forms Now that we have our Model and Controller created, we can now start putting everything together and get the cart working. Cart forms The Cart will use two forms Storefront_Form_Cart_Add and Storefront_Form_Cart_Table. The add form is displayed next to the products so users can add items to the Cart, and the table form is used to display all the items in the cart so users can edit them. Add form The add form can be used by customers browsing the store to quickly add items to their shopping cart. This form will look like the one shown in the screenshot below when it is rendered: Let's add the code to create the add form now. application/modules/storefront/forms/Cart/Add.php class Storefront_Form_Cart_Add extends SF_Form_Abstract { public function init() { $this->setDisableLoadDefaultDecorators(true); $this->setMethod('post'); $this->setAction(''); $this->setDecorators(array( 'FormElements', 'Form' )); $this->addElement('text', 'qty', array( 'decorators' => array( 'ViewHelper' ), 'style' => 'width: 20px;', 'value' => 1 )); $this->addElement('submit', 'buy-item', array( 'decorators' => array( 'ViewHelper' ), 'label' => 'Add to cart' )); $this->addElement('hidden', 'productId', array( 'decorators' => array( 'ViewHelper' ), )); $this->addElement('hidden', 'returnto', array( 'decorators' => array( 'ViewHelper' ), )); } } The add form contains four elements—qty, buy-item, productId, and returnto. We can see that it is much like the other forms we have created previously. The only major difference here is that we use the setDisableLoadDefaultDecorators() method to disable the default decorators for the form (not the elements). We do this because we do not want the form to contain the default definition list markup (<dl>). We also only use the ViewHelper decorator on each element so that the <dt> and <dd> tags are omitted Table form The table form is going to form the customer shopping cart. Customers will use this form to view, update, and remove items from their cart. This form will look similar to the one showed below when it is rendered: Let's add the code for the table form now: application/modules/storefront/forms/Cart/Table.php class Storefront_Form_Cart_Table extends SF_Form_Abstract { public function init() { $this->setDisableLoadDefaultDecorators(true); $this->setDecorators(array( array( 'ViewScript', array('viewScript' => 'cart/_cart.phtml') ), 'Form' )); $this->setMethod('post'); $this->setAction(''); $this->addElement('submit', 'update-cart', array( 'decorators' => array( 'ViewHelper' ), 'label' => 'Update' )); } } Th e table form is highly specialized. Therefore, we have chosen to use a ViewScript decorator. To do this, we fi rst disable the default decorators using the setDisableLoadDefaultDecorators(). We then need to configure the forms decorators. We will only have two decorators for the form, ViewScript and Form. This means that if we render the form, the update-cart element will not be rendered because we have not included the FormElements decorator. This is where the ViewScript decorator comes in. We can use this decorator to render a View script, in this case cart/_cart.phtml. We then have access to all the elements within the form inside this View script, meaning we can create highly specialized markup without needing to use lots of complicated decorators. Also, the table form will need to have fi elds dynamically added to it as we need a form element for each cart item. We will look at this shortly when we create the View Helper and Views for the Cart. The ViewScript decorator uses a View Partial to render its view script. This has an overhead as it clones the view instance. Generally, partials should be avoided in large numbers so do not over use them or the ViewScript decorator. SF_Form_Abstract You may have noticed that our forms did not subclass Zend_Form as in our previous examples. Also, this time we have extended from the SF_Form_Abstract class. This is because we have done some minor refactoring to the SF library so that we can inject the Model into the form. library/SF/Form/Abstract.php class SF_Form_Abstract extends Zend_Form { protected $_model; public function setModel(SF_Model_Interface $model) { $this->_model = $model; } public function getModel() { return $this->_model; } } The new SF_Form_Abstract class subclasses Zend_Form and adds two new methods, setModel() and getModel(). These simply set, and get, the protected $_model property. This then means that when we instantiate the form, we can pass in the model inside the options array. $form = new SF_Form_Abstract(array('model' => new myModel())); Here we are taking advantage of the fact that the setOptions() method will look for setters that match elements in the options array. In our case, the setOptions() class will find the setModel() method, call it, and pass in the model. This type of functionality is very common in Zend Framework components. It is always worth checking the setOptions() methods on components to see if you can extend them in this way. To get the model injected on instantiation, we also need to make a minor change to the SF_Model_Abstract. library/SF/Model/Abstract.php public function getForm($name) { if (!isset($this->_forms[$name])) { $class = join('_', array( $this->_getNamespace(), 'Form', $this->_getInflected($name) )); $this->_forms[$name] = new $class( array('model' => $this) ); } return $this->_forms[$name]; } He re, we simply pass in an array containing the model ($this) when we first instantiate the form class. We now have access to our Model from within our forms. Cart View Helper Th e Cart View Helper is responsible for creating many of the display elements for the cart. Therefore, we will break it down and look at each method in turn. application/modules/storefront/views/helpers/Cart.php class Zend_View_Helper_Cart extends Zend_View_Helper_Abstract { public $cartModel; public function Cart() { $this->cartModel = new Storefront_Model_Cart(); return $this; } The main Cart() method instantiates a new Cart Model and then returns a reference to itself so that we can chain calls to the other methods. application/modules/storefront/views/helpers/Cart.php public function getSummary() { $currency = new Zend_Currency(); $itemCount = count($this->cartModel); if (0 == $itemCount) { return '<p>No Items</p>'; } $html = '<p>Items: ' . $itemCount; $html .= ' | Total: '.$currency->toCurrency ($this->cartModel->getSubTotal()); $html .= '<br /><a href="'; $html .= $this->view->url(array( 'controller' => 'cart', 'action' => 'view', 'module' => 'storefront' ), 'default', true ); $html .= '">View Cart</a></p>'; return $html; } The getSummary() method creates the HTML that will be used to display a summary of the cart items and subtotal to the user. This will be displayed below the main category menus. application/modules/storefront/views/helpers/Cart.php public function addForm(Storefront_Resource_Product_Item$product) { $form = $this->cartModel->getForm('cartAdd'); $form->populate(array( 'productId' => $product->productId, 'returnto' => $this->view->url() )); $form->setAction($this->view->url(array( 'controller' => 'cart', 'action' => 'add', 'module' => 'storefront' ), 'default', true )); return $form; } The addForm() method will return a form for adding a single product to the cart. This method accepts one parameter $product that must be an instance of Storefront_Resource_Product_Item. We will use this to render individual add to cart forms for each product. application/modules/storefront/views/helpers/Cart.php public function cartTable() { $cartTable = $this->cartModel->getForm('cartTable'); $cartTable->setAction($this->view->url(array( 'controller' => 'cart' , 'action' => 'update' ), 'default' )); $qtys = new Zend_Form_SubForm(); foreach($this->cartModel as $item) { $qtys->addElement('text', (string) $item->productId, array( 'value' => $item->qty, 'belongsTo' => 'quantity', 'style' => 'width: 20px;', 'decorators' => array( 'ViewHelper' ), ) ); } $cartTable->addSubForm($qtys, 'qtys'); // add shipping options $cartTable->addElement('select', 'shipping', array( 'decorators' => array( 'ViewHelper' ), 'MultiOptions' => $this->_getShippingMultiOptions(), 'onChange' => 'this.form.submit();', 'value' => $this->cartModel->getShippingCost() )); return $cartTable; } The cartTable() method will return the table containing all our cart items, their costs, and totals. This will be used to update items in the cart. We create a subform to dynamically add the cart items quantity elements at runtime. The reason we use a subform is so we can easily get the whole set of quantity fi elds from the form, and later iterate over them in the View script. The form will need to contain an array of quantity text elements so that we can iterate over them in the updateAction in the controller. To create this array, we pass the belongsTo option to the addElement() method, which will tell the form that these elements are an array with the name quantity. We also set the value of the element to the qty held in the cart item. We also need a way of passing the productId for each cart item. To do this, we set the element name to the productId of the item. This also helps us by providing a unique name for each element (we have to cast this to a string). It will create a set of text form elements like: <input type="text" style="width: 20px;" value="1" id="quantity-21"name="quantity[21]"/><input type="text" style="width: 20px;" value="5" id="quantity-10"name="quantity[10]"/> Once we have all the quantity elements in the subform, we then add the whole subform to the main table form using the addSubForm() method. We give this the name of qtys, which we will use in the View script later to retrieve the elements. We also add the shipping options to the main table form. Here, we use the _getShippingMultiOptions() method to populate the select elements options and set the value to the currently selected shipping option of the cart. application/modules/storefront/views/helpers/Cart.php public function formatAmount($amount) { $currency = new Zend_Currency(); return $currency->toCurrency($amount); } The formatAmount() method is a little helper method we use to display amounts from the Cart. This may not be necessary in the future as there is a proposal for a currency View Helper that we would use instead. application/modules/storefront/views/helpers/Cart.php private function _getShippingMultiOptions() { $currency = new Zend_Currency(); $shipping = new Storefront_Model_Shipping(); $options = array(0 => 'Please Select'); foreach($shipping->getShippingOptions() as $key => $value) { $options["$value"] = $key . ' - ' . $currency->toCurrency($value); } return $options; } } Our final method is the private _getShippingMultiOptions() method. This is used internally by the cartTable() method to populate the shipping select element's options. This method gets the shipping options from the Shipping Model and creates an array suitable for the multiOptions option. Cart View scripts Now that we have all the tools created that we will need to build our cart, we can start creating the user interface. Cart view.phtml The view.phtml is the View that is rendered by the viewAction of the CartController. This View includes a title and renders the cartTable form application/modules/storefront/views/scripts/cart/view.phtml <h3>shopping <span>cart</span></h3> <?=$this->Cart()->cartTable();?> Cart _cart.phtml The ViewScript decorator attached to the table form will render the _cart.phtml View. When it renders, the ViewScript decorator will create a view partial and pass in the form as the element property for this View script. application/modules/storefront/views/scripts/cart/_cart.phtml <div style="padding: 8px;"> <table style="width: 100%;"> <tbody> <? $i = 0; foreach($this->element->getModel() as $item): ?> <tr <? if($i % 2){ echo 'class="odd"';};?>> <td><?=$this->Escape($item->name); ?></td> <td><?=$this->element->qtys->getElement ($item->productId); ?></td> <td class="rt"><?=$this->Cart()->formatAmount ($item->getLineCost()); ?></td> </tr> <? ++$i; endforeach; ?> <tr> <td colspan="2" class="rt">SubTotal:</td> <td class="rt colRight"><?=$this->Cart() ->formatAmount($this->element->getModel() ->getSubTotal()); ?></td> </tr> <tr> <td colspan="2" class="rt">Shipping: <?=$this->element ->getElement('shipping');?></td> <td class="rt colRight"><?=$this->Cart() ->formatAmount($this->element->getModel() ->getShippingCost()); ?></td> </tr> <tr> <td colspan="2" class="rt">Total:</td> <td class="rt"><?=$this->Cart()->formatAmount($this ->element->getModel()->getTotal()); ?></td> </tr> </tbody></table><?=$this->element->getElement('update-cart'); ?></div> The HTML produced by this script will look similar to the following screenshot: The main aspect here is the line items. We need to iterate over the cart and display each product line item. <?$i = 0;foreach($this->element->getModel() as $item):?> <tr <? if($i % 2){ echo 'class="odd"';};?>> <td><?=$this->Escape($item->name); ?></td> <td><?=$this->element->qtys->getElement($item->productId); ?> </td> <td class="rt"><?=$this->Cart()->formatAmount($item->getLineCost()); ?> </td> </tr><?++$i;endforeach;?> Here, we get the Cart Model from the form using our new getModel() method that we created earlier in the SF_Form_Abstract and iterate over it. As we iterate over the Cart Model, we display all the products and line costs. We also get the quantity form elements. To retrieve the correct quantity form element for each product, we access the qtys subform and use the getElement() method. We pass in the items productId as we named our quantity form elements using the productId earlier. All of the other form data is rendered in a similar way. We either get data from the Cart Model, or get elements from the form itself. By using the ViewScript decorator, we can see that it is much easier to mix form and non-form elements. Layout main.phtml application/layouts/scripts/main.phtml <div class="left categorylist"> <?= $this->layout()->categoryMain; ?> <? if (0 < count($this->subCategories)):?> <div class="sub-nav"> <h3>in this <span>category</span></h3> <ul> <? foreach ($this->subCategories as $category): ?> <li><a href="<?=$this->url(array('categoryIdent' => $category->ident), 'catalog_category', true );?>"><?=$category->name; ?></a></li> <? endforeach; ?> </ul> </div> <? endif; ?> <div> <h3>in your <span>cart</span></h3> <?= $this->Cart()->getSummary(); ?> </div> </div> We need to display the cart summary to the users so that they can see a brief overview of the items in their cart. To do this, we will use the Cart View Helper and the getSummary() method that looks similar to the following screenshot: Catalog index.phtml application/modules/storefront/view/scripts/catalog/index.phtml <p><?=$this->productPrice($product); ?></p> <?=$this->Cart()->addForm($product); ?> When displaying a list of products, we want the user to be able to add the product to their cart at that point. To do this, we render the cart add form under the price. This will make our catalog listing look like the one shown below: Catalog view.phtml application/modules/storefront/view/scripts/catalog/view.phtml <p><?=$this->productPrice($this->product); ?></p> <?=$this->Cart()->addForm($this->product); ?> Just like the index.phtml, we need to render the cart add form after the product price. This will make our details page look like this: Summary In this two-part article series, we learnt about: Creating Models that do not use a database as a data source Using Zend_Session_Namespace Implementing the Cart Views and Controllers More Forms, View Helpers, and so on If you have read this article you may be interested to view : Creating a Shopping Cart using Zend Framework: Part 1
Read more
  • 0
  • 0
  • 3897

article-image-overview-cherrypy-web-application-server-part1
Packt
27 Oct 2009
6 min read
Save for later

Overview of CherryPy - A Web Application Server (Part1)

Packt
27 Oct 2009
6 min read
Vocabulary In order to avoid misunderstandings, we need to define a few key words that will be used. Keyword Definition Web server A web server is the interface dealing with the HTTP protocol. Its goal is to transform incoming HTTP requests into entities that are then passed to the application server and also transform information from the application server back into HTTP responses. Application An application is a piece of software that takes a unit of information, applies business logic to it, and returns a processed unit of information. Application server An application server is the component hosting one or more applications. Web application server A web application server is simply the aggregation of a web server and an application server into a single component. CherryPy is a web application server. Basic Example To illustrate the CherryPy library we will go through a very basic web application allowing a user to leave a note on the main page through an HTML form. The notes will be stacked and be rendered in a reverse order of their creation date. We will use a session object to store the name of the author of the note. Each note will have a URI attached to itself, of the form /note/id. Create a blank file named note.py and copy the following source code. #!/usr/bin/python# -*- coding: utf-8 -*# Python standard library importsimport os.pathimport time################################################################The unique module to be imported to use cherrypy###############################################################import cherrypy# CherryPy needs an absolute path when dealing with static data_curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))################################################################ We will keep our notes into a global list# Please not that it is hazardous to use a simple list here# since we will run the application in a multi-threaded environment# which will not protect the access to this list# In a more realistic application we would need either to use a# thread safe object or to manually protect from concurrent access# to this list###############################################################_notes = []################################################################ A few HTML templates###############################################################_header = """<html><head><title>Random notes</<title><link rel="stylesheet" type="text/css" href="/style.css"></link></head><body><div class="container">"""_footer = """</div></body></html>"""_note_form = """<div class="form"><form method="post" action="post" class="form"><input type="text" value="Your note here..." name="text"size="60"></input><input type="submit" value="Add"></input></form></div>"""_author_form = """<div class="form"><form method="post" action="set"><input type="text" name="name"></input><input type="submit" value="Switch"></input></form></div>"""_note_view = """<br /><div>%s<div class="info">%s - %s <a href="/note/%d">(%d)</a></div></div>"""################################################################ Our only domain object (sometimes referred as to a Model)###############################################################class Note(object):def __init__(self, author, note):self.id = Noneself.author = authorself.note = noteself.timestamp = time.gmtime(time.time())def __str__(self):return self.note################################################################ The main entry point of the Note application###############################################################class NoteApp:"""The base application which will be hosted by CherryPy"""# Here we tell CherryPy we will enable the session# from this level of the tree of published objects# as well as its sub-levels_cp_config = { 'tools.sessions.on': True }def _render_note(self, note):"""Helper to render a note into HTML"""return _note_view % (note, note.author,time.strftime("%a, %d %b %Y %H:%M:%S",note.timestamp),note.id, note.id)@cherrypy.exposedef index(self):# Retrieve the author stored in the current session# None if not definedauthor = cherrypy.session.get('author', None)page = [_header]if author:page.append("""<div><span>Hello %s, please leave us a note.<a href="author">Switch identity</a>.</span></div>"""%(author,))page.append(_note_form)else:page.append("""<div><a href="author">Set youridentity</a></span></div>""")notes = _notes[:]notes.reverse()for note in notes:page.append(self._render_note(note))page.append(_footer)# Returns to the CherryPy server the page to renderreturn page@cherrypy.exposedef note(self, id):# Retrieve the note attached to the given idtry:note = _notes[int(id)]except:# If the ID was not valid, let's tell the# client we did not find itraise cherrypy.NotFoundreturn [_header, self._render_note(note), _footer]@cherrypy.exposedef post(self, text):author = cherrypy.session.get('author', None)# Here if the author was not in the session# we redirect the client to the author formif not author:raise cherrypy.HTTPRedirect('/author')note = Note(author, text)_notes.append(note)note.id = _notes.index(note)raise cherrypy.HTTPRedirect('/')class Author(object):@cherrypy.exposedef index(self):return [_header, _author_form, _footer]@cherrypy.exposedef set(self, name):cherrypy.session['author'] = namereturn [_header, """Hi %s. You can now leave <a href="/" title="Home">notes</a>.""" % (name,), _footer]if __name__ == '__main__':# Define the global configuration settings of CherryPyglobal_conf = {'global': { 'engine.autoreload.on': False,'server.socket_host': 'localhost','server.socket_port': 8080,}}application_conf = {'/style.css': {'tools.staticfile.on': True,'tools.staticfile.filename': os.path.join(_curdir,'style.css'),}}# Update the global CherryPy configurationcherrypy.config.update(global_conf)# Create an instance of the applicationnote_app = NoteApp()# attach an instance of the Author class to the main applicationnote_app.author = Author()# mount the application on the '/' base pathcherrypy.tree.mount(note_app, '/', config = application_conf)# Start the CherryPy HTTP servercherrypy.server.quickstart()# Start the CherryPy enginecherrypy.engine.start() Following is the CSS which should be saved in a file named style.css and stored in the same directory as note.py. html, body {background-color: #DEDEDE;padding: 0px;marging: 0px;height: 100%;}.container {border-color: #A1A1A1;border-style: solid;border-width: 1px;background-color: #FFF;margin: 10px 150px 10px 150px;height: 100%;}a:link {text-decoration: none;color: #A1A1A1;}a:visited {text-decoration: none;color: #A1A1A1;}a:hover {text-decoration: underline;}input {border: 1px solid #A1A1A1;}.form {margin: 5px 5px 5px 5px;}.info {font-size: 70%;color: #A1A1A1;} In the rest of this article we will refer to the application to explain CherryPy's design.
Read more
  • 0
  • 0
  • 3751

article-image-creating-dialplan-asterisk-16-part-2
Packt
27 Oct 2009
14 min read
Save for later

Creating a Dialplan in Asterisk 1.6: Part 2

Packt
27 Oct 2009
14 min read
Advanced Call Distribution What exactly is Advanced Call Distribution ? Many phone systems tout this feature, but most do not adequately define what it means. Basically, it refers to using call queues, parking calls for another user to answer, and Direct Inward Dialing (DID). So that we keep our focus, we will look at each of these elements individually. Call queues We have already configured call queues through the /etc/asterisk/queues.conf file. As we go through how we're going to use our queues, we may decide we want to change the way our queues are configured. There is absolutely no problem with changing the configuration so that it more accurately reflects our needs. Just remember that we need to issue a reload on the Asterisk console, or type #asterisk –r –x reload at the command line. The power and flexibility of other ACD systems can be matched or exceeded by Asterisk. As we evaluate our needs, we should remember that configuring a single aspect of Asterisk sometimes requires changes to more than one file. For example, queues will be configured both in the queues.conf file and the extensions.conf file. We will discuss how to set up extensions.conf to give us the desired result. When dealing with call queues, we need to think about the two types of users we have. First, we have the caller who calls in and waits in the queue for the next agent. We can think of this person as our customer. Next, we have the agents who work the queue. We can think of these people as our users. As a business, we have to decide what we want our customers' experience to be. Our call queue can make it sound like a phone is ringing. Or we can use music on hold while the customer waits. We can also announce call position and estimated wait time if we want to. When we place customers in a queue, we use the Queue application. To place a caller in the queue named bob, we would use something like: exten => 1000,1,Queue(bob) Suppose we have an operator's extension. As Ollie the operator may have more than one call at a time, we decide to give him a call queue. His calls are always about a minute long. The customers waiting for him are going to be there because they got lost in a system of menus. His queue will be named operator. In this instance, we will choose to have the customer hear the ring, so they will believe they are about to be helped. The sound of ringing should not last more than about a minute. We will not announce call queue length because our customer should not know that he or she is in a queue. The entry for this queue would be: exten => 0,1,Queue(operator|tr) Notice our use of options. Options for the queue application include: t: Allow the user to transfer the customer. T: Allow the customer to transfer the user. d: This is a data-quality call. H: Allow the customer to hang up by hitting *. n: Do not retry on timeout. The next step in the dialplan will be executed. r: Give the customer the ringing sound instead of music on hold. Thus, we told the Queue application to make the customer hear the ring, and the user (Ollie) the ability to transfer calls (as he's the operator). Now, suppose we have Rebecca, the receptionist at SIP phone 1006. When Ollie goes to the bathroom, we want our poor lost customers to be routed to her. So we could use the following in our extensions.conf file: exten => 0,1,Queue(operator|trn)exten => 0,2,Dial(SIP/1006) Now, Rebecca had better answer this. Until she does, the phone will continue to ring. Notice that this call will never end up in Rebecca's voicemail, as it is not transferred to her extension, but instead dials her phone directly. We have adequately addressed the customer's experience. But now we need to look at how our users will join and leave the queue. Previously, we discussed the power and flexibility of using agents in queues. As with most things in Asterisk, there are many ways we can associate members to queues. The three main ways are—statically, dynamically, and by using agents. Our first option is to have members statically assigned to the queue. In order to do this, we use the member directive in the queues.conf file. This is most helpful when we have a queue with fixed members, such as a switchboard queue. Our second option is to allow members to log in dynamically. We do this through the AddQueueMember application. An example of this would be: exten => 8101,1,AddQueueMember(myqueue|SIP/1001) Whenever anybody dials extension 8101, the telephone handset SIP/1001 would be added to the queue named myqueue. All that we would have to do is define a login extension for every member of every queue. What happens when this member no longer wishes to be in the queue? We use the RemoveQueueMember application, like this: exten => 8201,1,RemoveQueueMember(myqueue|SIP/1001) With this configuration, whenever anybody dials extension 8201, the telephone handset at SIP/1001 is removed. Again, we would have to define a logout extension for each member of the queue. Suppose we did not wish to define a login and logout extension for each member. We have the option of leaving off the interface (SIP/1001 in the previous example) and having Asterisk use our current extension. While this is very useful, Asterisk does not always use the right value. However, if it works for all extensions that need to be in the queue, we would only have to define one login and one logout per queue. The code would look like: exten => 8101,1,AddQueueMember(myqueue)exten => 8201,1,RemoveQueueMember(myqueue) This is better than having to define a login and logout for each member of each queue, but sometimes users are not good at remembering multiple extensions to dial. The AddQueueMember application will jump to priority n+101 if that interface is already a member of the queue. Therefore, we could define an extension like: exten => 8101,1,Answerexten => 8101,2,AddQueueMember(myqueue)exten => 8101,3,Playback(agent-loginok)exten => 8101,4,Hangupexten => 8101,103,RemoveQueueMember(myqueue)exten => 8101,102,Playback(agent-loggedoff)exten => 8101,105,Hangup When we define it this way, a user dialing extension 8101 is logged in if not already a member of the queue, or logged out if in the queue. Also, we added a confirmation to the action, so that the user can know if they are now in or out of the queue. Notice that before we could use the Playback application, we had to answer the call. If we have a lot of these, we could define a macro extension, like: [macro-queueloginout]exten => s,1,Answerexten => s,2,AddQueueMember(${ARG1})exten => s,3,Playback(agent-loginok)exten => s,4,Hangupexten => s,103,RemoveQueueMember(${ARG1})exten => s,104,Playback(agent-loggedoff)exten => s,105,Hangup. . .[default]exten => 8101,1,Macro(queueloginout|queue1)exten => 8102,1,Macro(queueloginout|queue2)exten => 8103,1,Macro(queueloginout|queue3) And thus we see that using a macro will save us five lines in our extensions.conf for every queue after the first. This is how we can add queue members dynamically. Our final option for adding queue members is by using Asterisk's agent settings. We were able to define agents in /etc/asterisk/agents.conf. We create an agent by defining an ID and a password, and listing the agent's name. In the queues.conf, we could define agents as members of queues. Calls will not be sent to agents unless they are logged in. In this way, queues can be both dynamic and static—they are static when we do not change the members of the queues, but dynamic when calls will go to different handsets based upon which agents are logged in. There are two main types of agents in this world. There are the archetypical large call center agents who work with a headset and never hear rings, and there are the lower-volume agents whose phone rings each time a call comes in. Asterisk has the flexibility to handle both types of agents, even in the same queue. First, imagine a huge call center that takes millions of phone calls per day. Each agent is in multiple queues, and we have set each queue to use an announcement at the beginning of calls to let the agent know which queue the call is coming in from. As employees arrive for their shift, they sit down at an empty station, plug in their headset, and log in. Each employee will hear music in between calls, and then hear a beep, and the call will be connected. To accomplish this, we use the line: exten => 8001,1,AgentLogin Through the normal login, the call is kept active the whole time. The agents will logout by hanging up the phone. This allows large call centers to be quieter, as the distraction of ringing phones will be removed. It also allows for more efficient answering of lines, as the time required to pick up the phone is eliminated. When our users arrive at work and wish to log in, they call extension 8001, where they are prompted for their agent ID, password, and then an extension number at which they will take calls. This is how Asterisk knows how to reach them. Our agents can log out when using AgentCallbackLogin by going through the same procedure as for login, with the exception that when they are prompted for their extension, they press the # key. It may be a good idea for us to review agents.conf. If we defined autologoff, then after the specified number of seconds of ringing, the agent will be automatically logged off. If we set ackcall to yes, then agents must press the # key to accept calls. If we created a wrapuptime (defined in milliseconds), then Asterisk will wait that many milliseconds before sending another call to the agent. These options can help us make our phone system as user friendly as we want it to be. Through the use of call queues, we can distribute our incoming calls efficiently and effectively. We have plenty of options, and can mix and match these three ways of joining users to queues. Call parking In many businesses across the United States, an operator can be heard announcing "John, you have a call on line 3. John, line 3." In Asterisk, we don't really have lines the way analog PBXs do. Our users are accustomed to not having to transfer calls, especially when they may not know exactly where John is. Asterisk uses a feature known as call parking to accomplish this same goal. Our users will transfer calls to a special extension, which will then tell them what extension to call in order to retrieve the call. Then our users can direct the intended recipient to dial that extension and connect to the call. In order to be able to use this feature, we must define our parking lot. This is done in the /etc/asterisk/parking.conf file. In this file, there are only a few options that we will need to configure. First, we must create the extension that people are to dial in order to park calls. This can be whatever extension is convenient for us. Then we will define a list of extensions on which to place parked calls. These extensions will be what users dial to retrieve a parked call. Next, we will define what context we want our parked calls to be in. Finally, we will define how many seconds a call remains parked before ringing back to the user who parked it. Here is an example: [general]parkext => 8100parkpos => 8101-8199context => parkedcallsparkingtime => 120 These settings would mean that we can park calls by dialing 8100, and the call will be placed in extensions 8101 through 8199, giving us the ability to have up to 99 parked calls at any given time. The calls will be in the context called parkedcalls, which means we should be careful to include it in any context where users should be able to park and retrieve calls. When our users transfer a call to extension 8100, they will hear Asterisk read out the extension that the call has been placed on. They can now make a note of it and notify the appropriate co-worker of the extension to reach the calling customer on. If the call is not picked up within the given parkingtime, then the call will ring back to the user who parked the call. By using call parking, we can help our users by providing a feature similar to that of previous generations of PBXs. This also allows users to collaborate and redirect callers to other users who are better equipped to handle our customers' needs. Direct Inward Dialing (DID) Suppose we work at a healthcare company with over 100 employees. We have two PRI lines coming in, and only three switchboard agents to handle incoming calls. As a healthcare company, we schedule many appointments, answer questions about prescriptions, and help patients with billing questions. These three agents are always busy. Now suppose the IT guy's wife calls in to ask if he wants sprouts or mash with his dinner. Do we want our switchboard agents to have to answer the call, find out who it is and what they want, and then transfer the call, or would we rather want the IT guy's wife to call her husband directly? This is where Direct Inward Dialing (DID) comes in handy. DID is a service provided by phone companies where they send an agreed-upon set of digits, depending on the number the customer dialed. For most phone companies, the sent digits will be the full ten-digit number (in the United States). But this can be as small as the last digit. All right, so the phone company is sending digits. What are we going to do with them? Imagine you have a PRI coming in to your office, and only ten phone numbers—a block from (850) 555-5550 to 5559. Your phone company has agreed to send you only the last digit dialed, which will be from 0 to 9, because you are guaranteed for this to be unique. Asterisk can route calls based on this DID information. If we have our PRI line's channels defined to go into a context called incoming, this context could look like: [incoming]s,1,Goto(default,s,1)i,1,Goto(default,s,1)t,1,Goto(default,s,1)0,1,Goto(default,1234,1)1,1,Goto(default,2345,1)2,1,Goto(default,3456,1)3,1,Goto(default,4567,1)4,1,Goto(default,5678,1)5,1,Goto(default,6789,1)6,1,Goto(default,7890,1)7,1,Goto(default,1111,1)8,1,Goto(default,1111,1)9,1,Goto(default,1111,1) There are a few things we should notice about this. First, we handled the error cases. What if a glitch at the phone company results in four digits being sent? We cannot allow a simple mistake on their end to interrupt our ability to receive phone calls. Secondly, we are using Goto statements. We've briefly discussed how they can be both good and bad. In this case, if a user moves from one extension to another by using Goto, we have to update it only in the default context. Finally, we are allowed to send multiple incoming DIDs to the same extension, if we so desire, as in the last three lines shown in the previous code. This might be useful if extension 1111 is the operator, and we do not yet have the number 7, 8, or 9 assigned to a user. Of course, in real life this is going to get much more complicated, as phone numbers will probably come in with the full ten digits. But the concept is the same—we can define extensions based upon information that the phone company sends when the call is established. By using DIDs, we can cut down on bottlenecks and give direct access to certain extensions. This tool of Asterisk helps make our phone system fast, efficient, and friendly to our users and customers.  
Read more
  • 0
  • 0
  • 3261

article-image-aspnet-social-networks-blogs-fisharoo
Packt
27 Oct 2009
8 min read
Save for later

ASP.NET Social Networks—Blogs in Fisharoo

Packt
27 Oct 2009
8 min read
Problem This article, as stated in Introduction, is all about adding the Blogging feature to our site. This will handle creating and managing a post. It will also handle sending alerts to your friends' filter page. And finally we will handle creating a friendly URL for your blog posts. Here we are making our first post to our blog: Once our post is created, we will then see it on the Blogs homepage and the My Posts section. From here we can edit the post or delete it. Also, we can click into the post to view what we have seen so far. The following screenshot shows what one will see when he/she clicks on the post: I have the blog post set up to show the poster's avatar. This is a feature that you can easily add to or remove. Most of your users want to be able to see who the author is that they are currently reading! Also, we will add a friendly URL to our blog post's pages. Design The design of this application is actually quite simple. We will only need one table to hold our blog posts. After that we need to hook our blog system into our existing infrastructure. Blogs In order for us to store our blog, we will need one simple table. This table will handle all the standard attributes of a normal blog post to include the title, subject, page name, and the post itself. It has only one relationship out to the Accounts table so that we know who owns the post down the road. That's it! Solution Let's take a look at the solution for these set of features. Implementing the database Let's take a look at the tables required by our solution. Blogs The blogs table is super simple. We discussed most of this under the Blogs section. The one thing that is interesting here is the Post column. Notice that I have this set to a varchar(MAX) field. This may be too big for your community, so feel free to change it down the road. For my community I am not overly worried. I can always add a UI restriction down the road without impacting my database design using a validation control. After that we will look at the IsPublished flag. This flag tells the system whether or not to show the post in the public domain. Next to that we will also be interested in the PageName column. This column is what we will display in the browser's address bar. As it will be displayed in the address bar, we need to make sure that the input is clean so that we don't have parsing issues (responsible for causing data type exceptions) down the road. We will handle that on the input side in our presenter later. Creating the relationships Once all the tables are created, we can then create all the relationships. For this set of tables we have relationships between the following tables: Blogs and Accounts Setting up the data access layer To set up the data access layer follow the steps mentioned next: Open the Fisharoo.dbml file. Open up your Server Explorer window. Expand your Fisharoo connection. Expand your tables. If you don't see your new tables try hitting the Refresh icon or right-clicking on tables and clicking Refresh. Then drag your new tables onto the design surface. Hit Save and you should now have the following domain objects to work with! Keep in mind that we are not letting LINQ track our relationships, so go ahead and delete them from the design surface. Your design surface should have all the same items as you see in the screenshot (though perhaps in a different arrangement!). Building repositories With the addition of new tables will come the addition of new repositories so that we can get at the data stored in those tables. We will be creating the following repository to support our needs. BlogRepository Our repository will generally have a method for select by ID, select all by parent ID, save, and delete. We will start with a method that will allow us to get at a blog by its page name that we can capture from the browser's address bar. public Blog GetBlogByPageName(string PageName, Int32 AccountID){Blog result = new Blog();using(FisharooDataContext dc = _conn.GetContext()){result = dc.Blogs.Where(b => b.PageName == PageName &&b.AccountID == AccountID).FirstOrDefault();}return result;} Notice that for this system to work we can only have one blog with one unique page name. If we forced our entire community to use unique page names across the community, we would eventually have some upset users. We want to make sure to enforce unique page names across users only for this purpose. To do this, we require that an AccountID be passed in with the page name, which gives our users more flexibility with their page name overlaps! I will show you how we get the AccountID later. Other than that we are performing a simple lambda expression to select the appropriate blog out of the collection of blogs in the data context. Next, we will discuss a method to get all the latest blog posts via the GetLatestBlogs() method. This method will also get and attach the appropriate Account for each blog. Before we dive into this method, we will need to extend the Blog class to have an Account property. To extend the Blog class we will need to create a public partial class in the Domain folder. using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Fisharoo.FisharooCore.Core.Domain{ public partial class Blog { public Account Account { get; set; } }} Now we can look at the GetLatestBlogs() method. public List<Blog> GetLatestBlogs(){ List<Blog> result = new List<Blog>(); using(FisharooDataContext dc = _conn.GetContext()) { IEnumerable<Blog> blogs = (from b in dc.Blogs where b.IsPublished orderby b.UpdateDate descending select b).Take(30); IEnumerable<Account> accounts = dc.Accounts.Where(a => blogs.Select(b => b.AccountID).Distinct().Contains(a.AccountID)); foreach (Blog blog in blogs) { blog.Account = accounts.Where(a => a.AccountID == blog.AccountID).FirstOrDefault(); } result = blogs.ToList(); result.Reverse(); } return result;} The first expression in this method gets the top N blogs ordered by their UpdateDate in descending order. This gets us the newest entries. We then add a where clause looking for only blogs that are published. We then move to getting a list of Accounts that are associated with our previously selected blogs. We do this by selecting a list of AccountIDs from our blog list and then doing a Contains search against our Accounts table. This gives us a list of accounts that belong to all the blogs that we have in hand. With these two collections in hand we can iterate through our list of blogs and attach the appropriate Account to each blog. This gives us a full listing of blogs with accounts. As we discussed earlier, it is very important for us to make sure that we keep the page names unique on a per user basis. To do this we need to have a method that allows our UI to determine if a page name is unique or not. To do this we will have the CheckPageNameIsUnique() method. public bool CheckPageNameIsUnique(Blog blog){ blog = CleanPageName(blog); bool result = true; using(FisharooDataContext dc = _conn.GetContext()) { int count = dc.Blogs.Where(b => b.PageName == blog.PageName && b.AccountID == blog.AccountID).Count(); if(count > 0) result = false; } return result;} This method looks at all the blog entries except itself to determine if there are other blog posts with the same page name that are also by the same Account. This allows us to effectively lock down our users from creating duplicate page names. This will be important down the road when we start to discuss our pretty URLs. Next, we will look at a private method that will help us clean up these page name inputs. Keep in mind that these page names will be displayed in the browser's address bar and therefore need not have any characters in them that the browser would want to encode. While we can decode the URL easily, this conversation is more about keeping the URL pretty so that the user and search engine spiders can easily read where they are at. When we have characters in the URL that are encoded, we will end up with something like %20 where %20 is the equivalent to a space. But to read my%20blog%20post is not that easy. It is much easier to ready my-blog-post. So we will strip out all of our so called special characters and replace all spaces with hyphens. This method will be the CleanPageName() method. private Blog CleanPageName(Blog blog){ blog.PageName = blog.PageName.Replace(" ", "-").Replace("!", "") .Replace("&", "").Replace("?", "").Replace(",", ""); return blog;} You can add to this as many filters as you like. For the time being I am replacing the handful of special characters that we have just seen in the code. Next, we will get into the service layers that we will use to handle our interactions with the system.
Read more
  • 0
  • 0
  • 4476
article-image-real-content-php5-cms-part-3
Packt
27 Oct 2009
8 min read
Save for later

Real Content in PHP5 CMS: Part 3

Packt
27 Oct 2009
8 min read
Administering text items—viewer Generating the XHTML is handled in a separate class, thus implementing the principles of the MVC pattern. The viewer class constructor establishes strings for translation in a way that will allow them to be picked up by gettext, as well as invoking the constructor in the parent class basicAdminHTML, which will provide useful methods and also transfer information such as the page navigation object from the controller object passed as a parameter: class listTextHTML extends basicAdminHTML { public function __construct ($controller) { parent::__construct($controller); $lang_strings = array(T_('Simple Text'),T_('Title'), T_('Byline'),T_('Version'), T_('Publishing'),T_('Published'), T_('Start date'),T_('End date'), T_('Article text'),T_('Metadata'), T_('Keys'),T_('Description'), T_('Hits'),T_('ID')); $this->translations = array_combine( $lang_strings, $lang_strings); } The actual display of a list of text items is then quite simple, involving the creation of a heading first, followed by a loop through the text items, and then some final XHTML including hidden fields that allow for effective navigation. Note that the parent class will have set up $this->optionurl and $this->optionline to help in the construction of links within the component and a hidden variable to identify the component respectively. public function view ($rows) { $mainhtml = $this->listview($rows); echo <<<ALL_HTML $mainhtml <div> <input type="hidden" name="task" value="" /> $this->optionline <input type="hidden" name="boxchecked" value="0" /> <input type="hidden" name="hidemainmenu" value="0" /> </div>ALL_HTML; } The view method does very little, relying on the listview method for most of the work, and only adding hidden fields needed to ensure that navigation and the toolbar will work correctly. Note that the parent class helps us by setting $this->optionline with a hidden input field for the critical option variable needed to ensure the correct component is invoked when the form is submitted. Actual XHTML form tags are created by the CMS framework so that every administrator page is a form. The reason for splitting the page creation in this way will become apparent later, when we look at menu creation. So, moving on to the listview method, we find quite a lot of simple code, which is mainly just a definition of the page in XHTML. The second and third parameters will be set differently from their default values when we come to menu creation. public function listview ($rows, $showlinks=true, $subhead='') { $rowcount = count($rows); $html = <<<ADMIN_HEADER {$this->header($subhead)} <table class="adminlist" width="100%"> <thead> <tr> <th width="3%" class="title"> <input type="checkbox" name="toggle" value="" onclick="checkAll($rowcount);" /> </th> <th> {$this->T_('ID')} </th> <th width="50%" class="title"> {$this->T_('Title')} </th> <th> {$this->T_('Byline')} </th> <th> {$this->T_('Hits')} </th> <th align="left"> {$this->T_('Published')} </th> </tr> </thead> <tbody>ADMIN_HEADER; $i = $k = 0; foreach ($rows as $i=>$row) { if ($showlinks) $title = <<<LINK_TITLE <a href="{$this->optionurl}&amp;task=edit&amp; id=$row->id">$row->title</a>LINK_TITLE; else $title = $row->title; $html .= <<<END_OF_BODY_HTML <tr class="row$k"> <td> {$this->html('idBox', $i, $row->id)} </td> <td align="center"> $row->id </td> <td> $title </td> <td> $row->byline </td> <td align="center"> $row->hits </td> <td align="center"> {$this->html('publishedProcessing', $row, $i )} </td> </tr>END_OF_BODY_HTML; $i++; $k = 1 - $k; } if (0 == $rowcount) $html .= <<<NO_ITEMS_HTML <tr><td colspan="6" class="center"> {$this->T_('No items')} </td></tr>NO_ITEMS_HTML; $html .= <<<END_OF_FINAL_HTML </tbody> </table> {$this->pageNav->getListFooter()}END_OF_FINAL_HTML; return $html; } When it comes to adding a new item or editing an existing one, no looping is required, and the WYSIWYG editor is activated to provide a helpful interface for the administrator who is editing a text item. Note that the use of PHP heredoc allows the XHTML to be written out quite plainly, with the PHP insertions unobtrusive but effective. Actual text for translation is shown in its correct place (in the base language) by using the T_ method that is inherited from aliroBasicHTML via basicAdminHTML. public function edit ($text) { $subhead = $text->id ? 'ID='.$text->id : T_('New'); $editor = aliroEditor::getInstance(); echo <<<EDIT_HTML {$this->header($subhead)} <div id="simpletext1"> <div> <label for="title">{$this->T_('Title')}</label><br /> <input type="text" name="title" id="title" size="80" value="$text->title" /> </div> <div> <label for="byline">{$this->T_('Byline')}</label><br /> <input type="text" name="byline" id="byline" size="80" value="$text->byline" /> </div> <div> <label for="version">{$this->T_('Version')}</label><br /> <input type="text" name="version" id="version" size="80" value="$text->version" /> </div> <div> <label for="article">{$this->T_('Article text')}</label><br /> {$editor->editorAreaText( 'article', $text->article, 'article', 500, 200, 80, 15 )} </div> </div> <div id="simpletext2"> <fieldset> <legend>{$this->T_('Publishing')}</legend> <div> <label for="published">{$this->T_('Published')}</label><br /> <input type="checkbox" name="published" id="published" value="1" {$this->checkedIfTrue($text->published)} /> </div> <div> <label for="publishstart">{$this->T_('Start date')}</label><br /> <input type="text" name="publish_start" id="publishstart" size="20" value="$text->publish_start" /> </div> <div> <label for="publishend">{$this->T_('End date')}</label><br /> <input type="text" name="publish_end" id="publishend" size="20" value="$text->publish_end" /> </div> </fieldset> <fieldset> <legend>{$this->T_('Metadata')}</legend> <div> <label for="metakey">{$this->T_('Keys')}</label><br /> <textarea name="metakey" id="metakey" rows="4" cols="40">$text->metakey</textarea> </div> <div> <label for="metadesc">{$this->T_('Description')}</label><br /> <textarea name="metadesc" id="metadesc" rows="4" cols="40">$text->metadesc</textarea> </div> </fieldset> <input type="hidden" name="task" value="" /> $this->optionline </div> <div id="simpletext3"> <input type="hidden" name="id" value="$text->id" /> <input type="hidden" name="boxchecked" value="0" /> <input type="hidden" name="hidemainmenu" value="0" /> </div>EDIT_HTML; } Finally, there is a common method to deal with the creation of the heading. It uses the addCSS method provided by the parent class to link to a small amount of CSS that is held in a separate file. Although the list of text items defined in the XHTML above is perfectly legitimate as a table, since it really is a tabular structure, the heading would be better built out of other XHTML elements. The only reason for using a table here is that it is one of the features retained from earlier systems for the sake of backwards compatibility: private function header ($subhead='') { $this->addCSS(_ALIRO_ADMIN_DIR.'/components /com_text/admin.text.css'); if ($subhead) $subhead = "<small>[$subhead]</small>"; return <<<HEAD_HTML <table class="adminheading"> <tr> <th class="user"> {$this->T_('Simple Text')} $subhead </th> </tr> </table>HEAD_HTML; } }
Read more
  • 0
  • 0
  • 2309

article-image-adding-pages-image-gallery-and-plugins-wordpress-blog
Packt
27 Oct 2009
10 min read
Save for later

Adding Pages, Image Gallery, and Plugins to a WordPress Blog

Packt
27 Oct 2009
10 min read
Pages At first glance, pages look very similar to posts. They also have a title and a content area in which we can write extended text. However, pages are handled quite differently from posts. Pages don't have a timestamp, categories, or tags. Posts belong to your blog, which is meant to be a part of an ongoing expanding section of your website, and are added regularly. Pages are more static, and the regular parts of your site that stand alone in a separate part of the site. When you installed WordPress, a page was automatically created for you (along with the first post and first comment). You can see it by clicking on the About link under Pages in the sidebar: Adding a page To add a new page, go to your WP Admin and navigate to Pages | Add New, or use the drop-down menu in the top grey menu by clicking on the arrow next to New Post and choosing New Page. This will take you to the Add New Page page: The minimum you need to do to create a new page is type in a title and some content. Then click on the blue Publish button, just as you would for a post, and your new page will appear linked in the sidebar of your website. You'll recognize most of the fields on this page from the Add New Post page, and they work the same for pages as they do for posts. Let's talk about the one new section, the box called Attributes Parent WordPress allows you to structure your pages hierarchically. This way, you can organize your website's pages into main pages and subpages, which is useful if you're going to have a lot of pages on your site. For example, if I was writing this blog along with three other authors, we would each have one page about us on the site, but they'd be subpages of the main About page. If I was adding one of these pages, I'd choose About as the parent page for this new page. Template Theme designers often offer alternate templates that can be used for special pages. The default WordPress theme comes with two templates: Archives and Links. Let's try using the Archives template. Just give your new page a title (for example, Blog Archives) and some content (for example, Let's experiment with the archives template). Then choose Archives from the Template pull-down menu and publish your page. When you go to your site and click on the Blog Archives link in the sidebar, you'll see this: As you can see, your title and content both do not appear, which makes this different from pages that use the default template (such as the About page). The sidebar is also missing. What does appear are the search box, a list of blog archives organized by month, and a list of archives organized by subject, that is, Categories. This particular template doesn't appear useful because all of its information is currently in the sidebar of the rest of the site. However, this shows you the power of a template. If you're designing a theme for your own website, you can create any number of templates that have special content. The Links template creates a similar page, but it lists all of your links. Order By default, the pages in your page list on the sidebar of your blog will be in alphabetical order. If you want them in some other order, you can specify it by entering numbers in the Order box for all of your pages. Pages with lower numbers (0) will be listed before pages with higher numbers (5). As the WordPress developers acknowledge right on this page, this method of ordering pages is quite clunky. Luckily, there is a plugin that makes ordering pages much easier. You can download this from http://wordpress.org/extend/plugins/pagemash/. Managing pages  To see a list of all the pages on your website in the WP Admin, navigate to Pages | Edit in the main menu. You'll see the Edit Pages page: By now this list format should begin to look familiar to you. You've got your list of pages, and in each row are a number of useful links allowing you to Edit, Quick Edit, Delete, or View the page. You can click on an author's name to filter the list by that author. You can use the two links at the top, All and Published, to filter the pages by status. And you can check boxes and mass-edit pages by using the Bulk Actions menu at the top and bottom of the list. You can also search your pages with the search box at the top. Links Word Press gives you a very powerful way of organizing external links or bookmarks on your site. This is a way to link other related blogs—websites you like, websites that you think your visitors will find useful, or just any category of link you want—to your blog. Speaking of categories, you can create and manage link categories that are separate from your blog categories. When you installed WordPress, it created the link category Blogroll along with a number of links in that category. You can see them in your blog's sidebar as follows: Adding a new link Let's add a new link to the Blogroll category. In your WP Admin, navigate to Links | Add New. This will take you to the Add New Link page, which has a number of boxes in which you can add information about your new link. Let's look at the first three here: Of all the fields on this page, it's the top two that are the most important. You need to give your link a Name, which is the text people will see and can click on. You also need to give a Web Address, which is the URL of the website that is linked to your blog. You can add a description, which will show up when visitors hover over the link. (Alternatively, you can also choose to have the description show up on the page below the link.) Now let's look at the next two boxes in the following screenshot: The first box in the screenshot above should look familiar because it's very similar to the Categories selection box for posts. Keep in mind that link categories are separate from post categories. On this page, you will only see link categories. You can assign a category to the new link that you're adding or create a brand new category by clicking on the + Add New Category link. Your links will be organized by the categories on your website. The second box lets you choose whether your visitors will be taken to a new window, or a new tab, when they click on the link. I generally recommend always using _blank when sending people to an external website. The other boxes on this page are used less commonly. You can use the two new boxes to specify XFN (XHTML Friends Network) relationships between you and any individuals you link to. Learning moreIf you want to learn more about XFN, take a look at this website: http://gmpg.org/xfn/. The final box at the bottom of this page will allow you to specify: An image that belongs with this link (for example, the logo of the company whose site you are linking to) The RSS feed for the website you're linking to Any notes you have about the site, beyond what you entered into the Description box A rating for the site from 0 to 9 To make use of any of these pieces of information, you need to have a theme that recognizes and makes use of them. At the top right of the page is a Save box with a checkbox that you can check if you want to keep the link private, that is, if you don't want it to show up on your site to anyone but you. Click on the Add Link button in that box to save your new link. I added a link for a recipe and food website using this form. I filled in only the first three boxes as seen in this screenshot: Now when I save and then re-load my website, I see my new link here: Managing links and categories You can manage your links just as you manage posts and pages. Navigate to Links and you will see this: From here, you can click on the name of a link to edit it, click on the URL to visit it, and see which categories you've chosen for it. Using the View all Categories pull-down menu, you can filter links by categories, change the order, and do bulk deletes. Just as with post categories, you can manage and add new link categories on the Link Categories page. You can access this page by navigating to Links | Link Categories: From this page, you can both add a new category using the form at the left and also manage your existing categories using the table at the right. Media library The media library is where WordPress stores all of your uploaded files—images, PDFs, music, video, and so on. To see your media library, navigate to Media in the main menu: This is the now-familiar management table. My media library has only one photo that I uploaded when I posted about the butternut squash soup recipe. As you can see from this table, it shows me the following: A thumbnail of the image. If this were another type of media, I'd see an icon representing the type of media. The title that I gave the file when I uploaded it, along with the format extension. The author. Information about which post or page the file is attached to. This will be important when it comes to making an image gallery. The uploaded file will be attached to the post or page that you are editing while uploading a file. The number of comments waiting on the attached post or page. The date when the file was uploaded. If you hover over the row with your mouse, links for Edit, Delete, and View will appear. You can click on the file's title or the Edit link to edit the Title, Caption, and Description. You cannot edit anything else about uploaded files. You can also add a new file to your media library. Navigate to Media | Add New to get a page similar to the upload media page that you got while uploading a file for a post. When you click on the Select Files button and select the file to be uploaded, it will upload it and then give you the options shown in this screenshot: Enter a title, caption, and description if you want, and click on the Save all changes button. Your new item will appear in the media library, which will be unattached to any post or page. However, you'll still be able to use what you just uploaded in any post or page. To do that, click on the Upload/Insert button as you did before. But instead of choosing a file From Computer, click on the Media Library tab on the top of the box: When you click on the Show link that is next to the image you want to use, you'll get the same set of options you got after uploading an image. Now you can click on the Insert into Post button.
Read more
  • 0
  • 0
  • 4334

article-image-real-content-php5-cms-part-1
Packt
27 Oct 2009
14 min read
Save for later

Real Content in PHP5 CMS: Part 1

Packt
27 Oct 2009
14 min read
The problem There are some common features in providing website content, but also many differences. Applications easily become complex as they tackle real world problems, and there has been much real innovation in web systems. So the areas to look at in this article are: Major areas for content development A review of minor yet important areas How a simple text manager is built An outline of a complex content delivery extension Discussion and considerations Now, we will work through the major areas of website content, devoting a section to each one. A round up of some less important aspects of content completes the discussion, leaving us ready to move on to details of implementation. Articles, blogs, magazines, and FAQ The most basic requirement is for text and pictures, and the simplest scheme needs little more than the standard database and a WYSIWYG editor. An extension that works at this level is illustrated later in the article. It is pretty much essential to have an ability to create items of this kind in an unpublished state so that they can be revised until ready for use. The state is then changed to published. Almost immediately, a further requirement arises to specify a range of publication dates, so that material aimed at a specific event can be automatically published at the appropriate time. Likewise, it is desirable to have an automatic mechanism for removing information that is no longer current, for example because it refers to a coming event in terms that will be irrelevant once the event has passed. A website that carries plainly obsolete articles is unlikely to be popular! There are many ways to organize textual material. One is to place it into some kind of tree structure, rather akin to the classification schemes used in libraries. Ideally, such a scheme has no particular constraints on the depth of the tree structure. A concern with this approach is that it can quickly lead to a conflict between two alternative uses—classification according to subject and classification according to reader permissions. An option that can be used in conjunction with a tree structure is to use some form of tagging. This introduces much greater flexibility in some respects, as it is easy to apply multiple tags to a single item of content, which can therefore be classified in a wide variety of ways, and can appear under multiple headings. A blog is an example of a system that might work best with a combination of a classification tree and a tagging scheme. Where there are several people creating blogs, the different authors fit well with a tree structure, since there is no question of an item belonging to more than one author. On the other hand, items are often tagged according to their subject matter, and several tags may be applicable to an individual article. If authors create more than one blog and there are questions about which visitors are able to see which blog, then careful thought needs to be given as to whether the split of blogs is best handled by the classification tree or by tagging. Using a tree achieves rigid separation, and is easily amenable to imposing access controls. But if the same item appears in more than one blog, then tagging works better as the item is ideally stored only once but has multiple tags. Blogs also frequently provide for comments, discussed in the next section. A magazine is typically a collection of articles. For a simple case, it might be adequate for the articles of the magazine to be equated to website pages, but a more sophisticated magazine would want to avoid restrictions of that kind. The basic unit of content would still need to be an individual article, but website pages then require some kind of template to build a page from multiple items. One popular application for quite simple content is the compilation of frequently asked questions (FAQ's). Advanced implementations might be described more grandly as knowledge bases. Again, both a classification tree and tagging can be relevant, but a useful FAQ (and especially one that wants to be a knowledge base) also needs effective search facilities so that information can be easily found. In all of these cases, added complexity arises if facilities like versioning are needed. Another similar issue is the need for workflow and differing roles, such as authors and editors. Mention of roles suggests a RBAC mechanism. It seems unlikely that one single model will ever meet every requirement in areas such as versioning and workflow. Version control can become extremely complex, and usually requires the allocation of roles that involve access rights and functional capabilities. Workflow is much the same. In both cases, though, simple and rigid schemes are liable to create problems. For example, the same person is quite likely to be an author in some situations, and an editor or publisher in others. A flexible and an efficient RBAC system is a pre-requisite for handling these problems, but as discussed earlier, the technical provision of RBAC is only a start. Applying it to particular systems and creating an appropriate user interface is a considerable challenge. Comments and reviews One of the successful innovations brought about by widespread use of the Web has been feedback through comments and reviews. Amazon is only one of many sites that now include reviews by customers of the products on sale. It could be said that this is a form of social networking, as the more sophisticated sites maintain profiles of reviewers and encourage them to achieve their own identity. Regular readers in particular areas of interest can get to know reviewers and form an opinion on the reliability of their views. There are two main problems with implementing comments and reviews. One is the question of how to generalize the facility, so as to avoid implementing it repeatedly in different applications. The other is how to deal with the ever present threat of spam. From the point of view of a developer, handling comments raises much the same issues regardless of what may be the subject of the comments. So blogs, selections of products, image galleries, and so on are all capable of having comments added to their items using similar mechanisms. This suggests a structure something like the scheme where the coarse grained structure is the component, but its display is achieved through the use of a template and a number of modules. Comments can thus be generated by a module that knows relatively little about the application, only enough to keep its comments separate from those for other applications and to relate a set of comments to a particular item, whether it is a blog item, product, gallery image, or whatever. That deals with the display of existing comments, which still leaves a requirement for a general interface that allows new comments to be added. The comment facility can easily enough handle the acceptance of a new comment, although it may need help if the page that accepts comments is to also show the object to which the comment applies. The comment facility also needs to know where to hand control once a new comment has been completed. Some moderately tricky detailed design is involved in providing an implementation of the full scheme. The other big problem with any facility that permits visitors to a site to enter information for display is that it attracts spammers. Usually, they arrive not in person but in the form of automated bots that can become very sophisticated. There are bots that know how to obtain an account, and log in to a range of systems. There are even bots that can handle CAPTCHAs (those messed up images out of which you are supposed to decipher letters or numbers). Some of the bots can handle CAPTCHAs better than some humans, which makes for accessibility problems. Fortunately, much link spamming is for the purpose of promoting websites, and so the spammer has to give away some information in the form of the link to the site being promoted. A reasonably effective defense against this kind of spamming is a collaborative scheme for blacklisting sites. Even that is not totally effective, as spammers find ways to create new sites quickly and cheaply, so that the threat is constantly changing. As with most forms of attack, there is unlikely to be any conclusion to this battle. Forums Forums are a very popular Web feature, providing a structured means for public or private discussion. Developing a forum is a major undertaking, and most people will prefer to choose from existing software products. Forum software usually provides for visitors to contribute messages, either starting a new topic or replying to an existing one. There is often a hierarchical structure to the messages so that a number of different areas of interest can be covered in a convenient way. Advanced systems include sophisticated user management, including support for a variety of different groups, which provides a means to decide who has access to which topics. Unwanted messages are a constant threat, and most active forums need moderators to weed them out. Development of a new forum will clearly need a number of the framework features discussed earlier. Robust user control is essential, and if different users are granted different access rights, a good system of RBAC is a requirement. A forum is highly amenable to the use of cache, since pages are likely to be constructed out of a number of database records, but the records are updated relatively infrequently. To be responsive, the cache needs to have a degree of intelligence so that pages with new contributions are refreshed quickly. Mail services are likely to be employed so that subscribers can receive notification of new contributions to topics in which they have registered an interest. Another approach is to seek a degree of integration between off the shelf forum software and the CMS. The most popular area for integration is user login. Obviously it is necessary to obtain some information about the way in which the forum software is implemented. Provided that can be found, then it is a relatively simple matter to integrate with a CMS that has been built with plentiful plug in triggers around the area of user authentication. From the point of view of visual integration, the amount of screen space needed by a forum is such that it is often difficult to build it within the framework of a typical CMS. Often a better approach is to build a custom theme for the forum that includes links back to the main site, so as to avoid completely losing continuity of navigation. Galleries, repositories, and streaming Although they have come from different requirements, galleries, and file repositories have a lot in common. Both start out simple and rapidly become complex. The general idea of a gallery is to build a collection of images, typically organized into categories and accessible via small versions of the images (thumbnails). File repositories have long been popular since the days of bulletin boards, where collections of files (often programs) were made available for download. Ideally the organization into categories (or folders or containers) is flexible with no particular limit on the depth to which subcategories can go. Some basic requirements relate to security. It is obviously essential to avoid hosting files that could contain malicious PHP code. This includes avoiding uploads of image files that contain PHP code embedded within actual image data. Simple checks can be fooled by this technique, but a block on the .php extension prevents the code being interpreted. Another potentially major security issue is bandwidth theft. If files or images are too easily accessed, then other sites may choose to use them without acknowledgment, transferring the bandwidth costs to the site hosting the material. As applications broaden, access control becomes an issue. Files are to be made available only to a restricted group, and uploads may be restricted more tightly again. There may be administrator oversight, with uploads needing approval. Once again, we are seeing a demand for an effective access control system, preferably role-based. In fact demands on systems of this kind can easily become very sophisticated, such as allowing users to have personal upload areas over which they have complete control to determine who is able to gain access. An RBAC system that is technically capable of handling this can be built relatively easily, although creating a good user interface is a challenge. Whether the system is a gallery or file repository, the use of thumbnail images is increasingly prevalent. File uploads may, therefore, be accompanied by one or more image files that are used to enhance the display of the files available. Information about the system is likely to be needed, such as which are the most recent additions to the collection, which items are most popular, who has accessed what, and who has uploaded what. Information of this kind can also contribute to security by providing an audit trail of what has been happening to the system. Streaming of files is a demand now often placed on a file repository, as the files can be audio or video files made available for immediate access. Streaming is simply a mode of file processing whereby the information is delivered to the user at a speed adequate for consumption in real time. Clearly video tends to place greater demands on the system than audio. The problems are both hardware and software related, although with steadily improving technology it is increasingly feasible to overcome both. E-commerce and payments Everyone is aware of the huge growth of commercial transactions on the Web. The kind of transaction involved can vary widely across simple fixed price retail sales, auctions of various kinds, and reverse auctions for procurement. For retail transactions immediate settlement is usually required, whereas larger scale business to business transactions are usually handled through relatively traditional invoicing methods. Even those are tending to be altered towards paperless billing and payment schemes that cut transaction costs to a minimum. Systems for e-commerce vary enormously in their sophistication from simple requests for payment using a PayPal button to highly sophisticated Web operations such as Amazon and eBay. Open source PHP software exists to cover a significant part of this spectrum, some of it in the form of extensions to CMS frameworks. PayPal has achieved a very high profile, especially with smaller operators, by offering easy access for merchants combined with technology that is relatively simple to implement. This includes the ability to complete a transaction with online confirmation in a way that is suitable for the sale of electronically deliverable goods such as software. Clearly, robust authentication of users is essential for e-commerce. For all but the simplest transactions, some kind of shopping cart is highly desirable. These requirements imply a need for good session handling, preferably taking effect as soon as a visitor arrives at a site. Nearly every shopping site will allow a visitor to accumulate items in a shopping cart prior to any kind of login. There is a plethora of payment systems, some of them suitable mainly for large volume uses, but others that can be applied on a small scale. A particular CMS framework might adopt some standard payment mechanisms that are then integral to the CMS and can be used whenever needed. Security is obviously paramount, as loss of data is both financially damaging and extremely bad for the site's reputation. E-commerce sites also often use a number of the features described in other sections here. A popular addition is the ability for customers to review the items they have purchased. This kind of facility may lead to further requirements to distinguish categories of users so as to give incentives to people who regularly write reviews.
Read more
  • 0
  • 0
  • 1689
article-image-managing-student-work-using-moodle-part-2
Packt
27 Oct 2009
5 min read
Save for later

Managing Student Work using Moodle: Part 2

Packt
27 Oct 2009
5 min read
How Assignments Look to a Student I've logged out and then logged back in as student John Smith. As far as offline assignments are concerned, they are carried out in the real world. In that instance, Moodle is used to manage grades and notes. If I click on my Offline assignment, I just see a description of the assignment: My second assignment requires students to upload a file. In the next section, we experience a little of what life is like as a Moodle student when we try uploading a project submission to Moodle. Taking the Student's Point of View—Uploading a Project File It is a very good idea to see what we are expecting our students to do when we ask them to upload their project work to us online. At the very least, when we ask students to upload their project work to Moodle, we need to know what we are talking about in case they have any questions. If you don't have a student login or you are still logged in as yourself and have asked a colleague to check that your assignment is working correctly, it's a good idea to take a good look over their shoulder while they are running through the following steps. Together, let's run though what a student must do to upload a file to us... Time for Action – Uploading a File to an Assignment I only have one computer to work from, so the first thing to do is for me to log out and log back in as my pretend student "John Smith". If you have the luxury of having two computers next to each other then you can log in as yourself on one and your pretend student on the other at the same time. You might have two different browsers (e.g. Firefox and Internet Explorer) installed on the same computer. If so you can log into one as a teacher and the other as a student. Don't try to log in as two different people on the same computer using the same browser—it doesn't work. Now that you are logged in as a student... Return to the course main page and click on the Advanced uploading of files assignment you added earlier. You will be presented with the following page: The top half of the page is our description of the assignment. The second half allows us to upload a file and, because I configured the activity such that students could include comments with their submission, has an area allowing us to add a note. Students can browse for files and upload them in exactly the same way as we upload our teaching materials to the course files area. If they want to add a note, then they need to press on the Edit button (at the bottom of the previous screenshot). Click on the Browse... button now. The File upload dialog is displayed. This allows us to select a file to upload. You can choose any for now, just to prove the point. I've quickly created a text file using Notepad called example_submission.txt. Select the file you want to upload and press the Open button. The name of the file is now displayed in the box: Press the Upload this file button. You will now see the file listed in the Submission draft box: Repeat this process for your other project files. To add a note to go along with the submission, I can press the Edit button at the bottom of the page. Try leaving a note now. (If your assignment has been configured so that students are prevented from leaving a note, you won't have this option.) If I am happy that this is the final version of the project and I want to send it for marking, then I can press the Send for marking button at the bottom of the page. Pressing this stops me from uploading any more files: That's it. We're done: What Just Happened? It was easy for us to convert our assignments to Moodle. Now, we've seen how easy it is for students to convert to using Moodle to hand in their assignment submissions. Now, we've actually got a piece of work to mark (albeit a pretend piece), I am ready to start marking. Before moving on to the next section, make sure you are logged in as yourself rather than as a student. Marking Assignments Managing student grades and the paperwork associated with student submissions is one of my biggest headaches. By converting to Moodle, I can avoid all of these problems. Let's see how easy it is to mark assignments in Moodle. Marking Offline Assignments My Offline assignment, the poster project, is being carried out in the real world. Currently, I take a digital photograph of the poster and record my comments and grades on separate pieces of paper. Let's see how I can convert this to Moodle... Time for Action – Mark an Offline Assignment From the course front page, click on your Offline assignment. Click on the No attempts have been made on this assignment/View 0 submitted assignments link in the top right-hand corner of the page. You are now taken to the Submissions page. I've only got one student enrolled on my course—the pretend student my admin put on my course for me—so this is what I see: To grade John Smith's work, I need to click on the Grade link, found in the Status column. The Feedback dialog is displayed: I can use this dialog to comment on a student's work. At this point, I could include a photograph of the poster in the comment, if I wanted to (or I could get the students to take photographs of their posters and then to upload the images as part of an online submission).
Read more
  • 0
  • 0
  • 1611

article-image-managing-student-work-using-moodle-part-3
Packt
27 Oct 2009
5 min read
Save for later

Managing Student Work using Moodle: Part 3

Packt
27 Oct 2009
5 min read
Specifying Custom Grades Currently, I'm marking my projects out of 100 but, as I mentioned previously, that's not how they are graded. According to the syllabus, I can only give students one of four grades: Distinction, Merit, Pass, and Referral. So how do you specify your own grades? Let's learn how to do that now. Time for Action – Create a Custom Grade Scale Return to your course front page and look for Grades in the Administration block: Click on Grades and you'll be taken to the Grader report page. We are now in the Moodle grade book. I'm not going to worry too much about all of the features in the grade book for the moment—but while you are there you might like to spend a little time having a look. As with anything else in Moodle, you can't do any damage by doing something by mistake. At the top left of the page, you will find a list of view options: From the list select Scales. You're now taken to the scales page. We need to add a new scale, so press the Add a new scale button in the center of the page. On the following page, give your new scale a name and in the Scale box you can specify the possible grades contained in your new scale. Separate the grades with commas—no spaces. Make sure you specify the grades in order of increasing value: You don't have to worry about a description. Are the grades you are specifying here used for grading in other courses? If you tick the Standard scale box then your scale will be made available to teachers on all courses. When you are done, press the Save changes button. Your new scale is listed on the scales page. Because I didn't make my new scale a standard scale, it's listed as a custom scale: What Just Happened? I don't give students a numerical grade for the Backyard Ballistics projects. The syllabus requires a qualitative grade, but luckily the system makes it easy to import my own custom grade scales. All I need to do now is modify my two assignment activities to use the new scale. That only involves a few clicks, so let's do that now... Time for Action – Grading Using a Custom Scale Return to your course front page and click on the update icon next to the assignment you want to change to use your new custom scale. The Editing assignment page is displayed. Scroll down to the Grade drop-down list. Click on the list. Scroll up if you need to, because the custom scale we want to use will be towards the very top: With the new grading scale selected, scroll down to the bottom of the page and press the Save and return to course button. That's it. You will now be able to grade your project using your new scale. What Just Happened? We've just modified the assignment to use our new grading scale. All that remains now is to demonstrate how you use it. Now that we are back at the course front page, click on the link to the assignment itself to display the assignment's main page (displaying the description of the task we've set). Click on the View submitted assignments link in the top right-hand corner of the page to take you to the Submissions page. Choose a student and down in the Status column click on the Grade link. If you've already marked that student then the link will say Update: Click on the link to open the Feedback dialog. Click on the Grade list at the top right-hand corner of the page to display the grades you can give to this piece of work. The grades listed are the ones from our new custom grade scale: More Uses for Moodle Assignments We aren't limited to using the four assignment activities just for major projects. Here are some more ideas on using the assignment activity to convert your current teaching over to Moodle... Include an online text assignment for discursive tasks, for example writing a short story or for short essay homework tasks. If you're able to display the submissions page of a single file assignment to the class during teaching time, keep refreshing the page as homework is submitted. You'll quickly find that there'll be a race on to be the first to hand their homework in. You could easily turn that into a game for younger students. Use an Offline activity to manage the grades of any task you set for your students—homework handed in on paper, for example. You don't have to confine yourself to just projects. On that last point, there is another way of managing grades directly. We've already been briefly into the Moodle Grader report when we set up our custom scale. Let's revisit that page to see how we can set up custom grading items. Grading Students on Core Competencies Often, as educators, we need to grade assignments on core competencies, otherwise known as key skills or goals. That certainly applies to my syllabus: A percentage of the final grade for my course includes marks for numeracy, literacy, and the use of ICT. Because we are converting to Moodle, and in Moodle-speak, the competencies that I am grading are called "outcomes", in this final section, we learn how to specify the core competencies we need to grade, and how we can then grade students on them. There are pros and cons of converting to Moodle, specifically: I can choose to enable outcomes on a per assignment basis, but you can't use the default numeric grading scale to grade outcomes, only standard and custom grading scales (like my custom Backyard Ballistics scale that I created in Time for action – Create a Custom Grade Scale).
Read more
  • 0
  • 0
  • 1537
Modal Close icon
Modal Close icon