I recently had a scenario on a Drupal 6 site that I am developing where I wanted to refresh some content every 10 seconds with new data from the Drupal database. Usually to do something like this I do the following:

  • Use an AJAX call to a PHP script on my site.
  • PHP script queries database and sends back the response.

You can’t do this though if you want to use Drupal’s API since Drupal needs to go through it’s entire boot process before any of the API is avaible. Luckily there is an easy way to get around this; create a module with a menu hook.

To create the module first create the .info file:

  1. ;custom_ajax.info
  2. name = Custom Ajax Calls
  3. description = Allows AJAX to use Drupal API
  4. core = 6.x
  5. project = "custom_ajax"

Next create the module file with the hook_menu() and call back.

  1. // custom_ajax.module
  2.  
  3. // menu hook call
  4. function custom_ajax_menu()
  5. {
  6.      // List of navagation links to your ajax functions.
  7.      $items = array();
  8.      
  9.      // Creates a link yoursite/?q=my/ajax/function that will call the page callback function (custom_ajax_my_ajax_function())
  10.      $items["my/ajax/function"] = array(
  11.           "title"          => t("title"),
  12.           "description"    => t("provides access to a php function that can be requested by an ajax call"),
  13.           "page callback"  => "custom_ajax_my_ajax_function",
  14.           "access arguments" => array("access content"),
  15.           "type"           => MENU_CALLBACK
  16.       );
  17. }
  18.  
  19. // The function that actually does what you need for the ajax request
  20. function custom_ajax_my_ajax_function()
  21. {
  22.      $q = "";
  23.      db_query($q); //drupal api
  24.      echo json_encode($data);
  25.      exit;  // Don’t render the page.
  26. }

Now in your JavaScript code, you just make the AJAX request to the callback path

  1. $.ajax({
  2.      url:‘/?q=my/ajax/function’,
  3.      success: function(data, success, jqXHR){
  4.      }
  5. });
 

All of the list attributes (columns) can be found in the ows_MetaInfo attribute. This is just a huge string packed with information that you can parse to extract. So far I’ve been able to gather the following about this data:

  1. Keys that start with “vti_*****” are predefined list attributes (e.g. vti_title, vti_author, vti_modifiedby, etc)
  2. Keys that start with “_dlc_****” contain data about the resource itself (e.g _dlc_DocIdUrl, _dlc_DocIdItemGuid, etc)
  3. Keys that are the exact name of an attribute are columns that you added to the list after creation. (e.g. My Custom Cost)

To extract any of this data I use the following to regular expressions functions:

  1. // First get the key / value you want.  In this instance I want the custom "title" column I added to the list.
  2. preg_match(‘/(?=vti_title).*/’, $result->getAttribute("ows_MetaInfo"), $match);  // Returns "vti_title:RW|my title is this: what?"
  3.  
  4. // Extract the value
  5. $title = preg_replace(‘/\w*\:\w{2}\|/’, , $match[0]);  // Returns "my title is this: what?"
 

Objective

Create a PHP based web page that creates a list of all documents in a SharePoint 2010 Document Library. Each item in this list is a link to the actual document. When a user clicks the link, the document is downloaded from SharePoint’s database and displayed to the user.

Background

Sharepoint 2010 is fantastic. If you don’t use it, look into it. (Drupal isn’t bad either). While i’ve been migrating a custom coded CMS to Sharepoint 2010, I keep running across interesting issues. My latest was that I needed to let users submit PDF documents to the system and then have those documents display on the website.

With a traditional PHP website, piece of cake: Save document to server, list link to file, user clicks link.

SharePoint however stores all of the documents into a database that is only understandable by SharePoint…

Prep

Make sure you have done the following:

  1. Enabled Basic Authentication on the SharePoint server
  2. Enabled the openssl, curl and soap PHP extensions on your web server
  3. Downloaded a copy of the Lists services to your web site. (Go to https://sharepointsite/subsite_if_one/_vti_bin/Lists.asmx?WSDL)

Now on to the coding!

Steps

Here is my main loop that fetches the document list and creates links to the document on the page:

  1. $results = getDocumentList({GUID});
  2. $load = false;
  3. $out = ‘<ul>’;
  4. //Fetching the elements values. Specify more attributes as necessary
  5. foreach($results as $result){  
  6.  
  7.         // Get a custom column in my list.
  8.         $title = getMetaItem($result, ‘vti_title’);
  9.        
  10.         // Get the document url and guid of document
  11.         $docURL = getMetaItem($result, ‘_dlc_DocIdUrl’);
  12.         $docURL = preg_split(‘/\w*\|/’, $docURL);
  13.         $docURL = explode(",", $docURL[0]);             // Parse the full url, doc_id
  14.        
  15.         $out .= "<li><a href=\"/sptest.php?id=".trim($docURL[1])."\">{$title}</a><li/>";
  16.                                                
  17.         if(isset($_GET[‘id’]) && trim($docURL[1]) == $_GET[‘id’]){
  18.                 $load = true;
  19.                 $url = "https://usportal.aaalliance.ucf.edu/" . rawurlencode(preg_replace(‘/(^[0-9]*).{2}/’, , $result->getAttribute("ows_FileRef")));
  20.         }                              
  21. }
  22. $out .= ‘</ul>’;
  23.  
  24. // If requesting document
  25. //preg_match(‘/(\d\w)*-(\d\w)*-(\d\w)/’, $_GET['id'])){
  26. if($load){
  27.         openDocument($url);
  28. }
  29. else echo $out;

My function to get all of the documents in a specified library:

  1. /*
  2.  * @param $listName string this is the GUID of your library.  To find it go to your library through the sharepoint website. Then click on "library settings".  Once there look at the URL.  It should be: https://sharepointsite/subsite/_layouts/listedit.aspx?List={E2EAE561-D43R-4F8B-AC4D-146BEB66DBD4}
  3.  
  4. Use what List= is (including the {})
  5. */
  6. function getDocumentList($listName, $config = array())
  7. {      
  8.         $authParams = getAuth(); // returns username:password
  9.        
  10.         /* A string that contains either the display name or the GUID for the list.
  11.          * It is recommended that you use the GUID, which must be surrounded by curly
  12.         * braces ({}).
  13.         */     
  14.         $rowLimit = ’150′;
  15.        
  16.         /* Local path to the Lists.asmx WSDL file (localhost). You must first download
  17.          * it manually from your SharePoint site (which should be available at
  18.                         * yoursharepointsite.com/subsite/_vti_bin/Lists.asmx?WSDL)
  19.         */
  20.         $wsdl = $_SERVER[‘DOCUMENT_ROOT’]."/Lists.xml";
  21.        
  22.         //Creating the SOAP client and initializing the GetListItems method parameters
  23.         $soapClient = new SoapClient($wsdl, $authParams);
  24.         $params = array(‘listName’ => $listName,
  25.                         ‘rowLimit’ => $rowLimit,
  26.                         ‘viewFields’    => "<ViewFields Properties=’True’ />"
  27.         );
  28.        
  29.         //Calling the GetListItems Web Service
  30.         $rawXMLresponse = null;
  31.         try{
  32.                 $rawXMLresponse = $soapClient->GetListItems($params)->GetListItemsResult->any;
  33.         }
  34.         catch(SoapFault $fault){
  35.                 //var_dump($fault);
  36.                 echo ‘Fault code: ‘.$fault->faultcode;
  37.                 echo ‘Fault string: ‘.$fault->faultstring;
  38.         }
  39.        
  40.         //Loading the XML result into parsable DOM elements
  41.         $dom = new DOMDocument();
  42.         $dom->loadXML($rawXMLresponse);
  43.        
  44.         $results = $dom->getElementsByTagNameNS("#RowsetSchema", "*");
  45.         unset($soapClient);
  46.        
  47.         return $results;
  48. }

My function to parse ows_MetaInfo

  1. /*
  2.  * Parse ows_MetaInfo for a value
  3.  */
  4. function getMetaItem($result, $key)
  5. {
  6.         $match = array();
  7.         preg_match(‘/(?=’.$key.‘).*/’, $result->getAttribute("ows_MetaInfo"), $match);
  8.         return preg_replace(‘/\w*\:\w{2}\|/’, , $match[0]);
  9. }

Finally, open the document with by it’s URL

  1. function openDocument($url)
  2. {              
  3.         $authParams = getAuth();
  4.                
  5.         // Get the file name.
  6.         $filename = substr(strrchr(rawurldecode($url), ‘/’), 1);
  7.         header("Content-Disposition: attachment; filename=".str_replace(‘ ‘, , (urlencode($filename))));
  8.         header("Content-type: application/octet-stream");
  9.         header("Content-Transfer-Encoding: binary");
  10.                
  11.         $ch = curl_init();
  12.                
  13.         curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
  14.         curl_setopt($ch, CURLOPT_HEADER, 0);
  15.         curl_setopt($ch, CURLOPT_URL, $url);
  16.         curl_setopt($ch, CURLOPT_USERPWD,  $authParams[‘login’] . ":" . $authParams[‘password’]);
  17.         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  18.         curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  19.         //curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // Not working for me
  20.         //curl_setopt($ch, CURLOPT_CAINFO, getcwd() . "/../data/CAcerts/my.pem");
  21.                
  22.         $response = curl_exec($ch);
  23.         echo $response;
  24.         curl_close($ch);       
  25. }

Oh how I love making systems talk to systems…

Thank you to http://davidsit.wordpress.com/2010/02/23/reading-a-sharepoint-list-with-php/ for getting me started on how to read sharepoint lists.

 

The WPShower WordPress theme comes with a slideshow that allows you to add posts with a featured image to it. You can’t however add pages with featured images to it. To do this you need to do the following:

1) Modify sight_add_box()

  1. // functions.php
  2. function sight_add_box() {
  3.     global $meta_box;
  4.  
  5.     add_meta_box($meta_box[‘id’], $meta_box[‘title’], ‘sight_show_box’, $meta_box[‘page’], $meta_box[‘context’], $meta_box[‘priority’]);
  6.  
  7.     /* The above function adds the box to any "edit post" page.  By changing the $context argument of add_meta_box() to ‘page’ we are saying added this Slideshow Options widget to "edit page" page. */
  8.     add_meta_box($meta_box[‘id’], $meta_box[‘title’], ‘sight_show_box’, ‘page’, $meta_box[‘context’], $meta_box[‘priority’]);
  9. }

2) Tell the slideshow grab pages along with posts

  1. // slideshow.php
  2. // $slides = get_posts($args); // Before
  3. $slides = array_merge(get_posts($args), get_pages($args));
 

You can’t.

Yeah, sucky, but… there are ways around that. If you Google for a while you’ll see some theories, I’ve got my own though that I’ve actually implemented. (This won’t work for all use cases.).

Ok, so basically I needed to do the following:

1) Create a PHP form that users could submit an application through.
2) Display this information on a SharePoint site with a “Pending” state.
3) Allow users with access to this information to change the state of the application in SharePoint.
4) Depending on what the state changes to, email a certain user.

Most of this is pretty straight forward, the only real hurdles were a) providing a selection of state options and b) starting the “workflow”

(Later I’ll try to add some actual screen shots of what I did)

Providing a select of state options

Since you aren’t using SP workflows, you need to have a column in your database table to hold the state of the workflow. Since columns can only contain one value, you need to create a way of letting a user choose among a variety of predefined options to save to the column. Use InfoPath!

InfoPath allows you to attached a DropDown or Radio to a field. So just add your predefined options to the attached DropDown or Radio and then set your field to read only. Now when a user goes to edit the application, they have a list of “states” to choose from.

Starting the “Workflow”

Now all you need to do is listen for the state change with a database update trigger! This isn’t the most elegant way, but there’s nothing elegant about external list workflows in SharePoint! On the bright side, it’s separated from SP so it can be used with other systems.

My trigger just listens for updates and checks to see what the “state” column is going to be changed to. Various emails are sent based on the new “states” value.

And that is how I do External List Workflows in SharePoint 2010.

 

I actually think there is a view helper for this, but just for my information:

  1. <?php // Display a list of all errors (no association to the element) ?>
  2. <ul>
  3.     <?php foreach($this->form->getMessages() as $el => $errors):?>
  4.         <?php foreach($errors as $type => $message):?>
  5.         <li><?php echo $message; ?></li>
  6.         <?php endforeach ?>
  7.     <?php endforeach ?>
  8. </ul>
 

I had a client that recently wanted to start backing up all of the data from his websites to an external hard drive. I was like, “Ok, that’s easy. GoDaddy creates daily backups of your files that you can zip up from your account and download.” Wrong.

Godaddy does create daily backs, which you can zip up and download, but not if your trying anything over 10 MB or so. Also, it DOES NOT create backups of your databases. But that’s ok, cron jobs to the rescue.

To create the necessary backups I needed to do the following:

1) Write a script that would generate backups of all the databases and store them in a private zone
2) Write a script that would create a zip file of the entire site
3) Add both of these as cron jobs

1) backupmydatabases

  1. #!/bin/sh
  2.  
  3. // Just create a sql dump of the database.
  4. mysqldump -uMYUSERNAME -pMYPASSWORD –opt -hMYHOSTNAME MYDATABASENAME > ~/private/access/MYDATABASENAME.sql
  5. // Do this for each database that you need to backup

2) backupmyfiles

  1. #!/bin/sh
  2.  
  3. // Remove the previous backup file.
  4. rm -f ~/private/access/fullsite_backup.tar.gz
  5.  
  6. // Create a tarball file of everything in your pocket of the server
  7. // Dont include the actual tarball!!
  8. tar –exclude=~/private/access/fullsite_backup.tar.gz -czf ~/private/access/fullsite_backup.tar.gz ~/*

3) Godaddy Crons

Now you just go to your Godaddy account and setup 2 cron jobs to run each of those scripts. Make sure you run the database backup script first and allow enough time for it to finish before you start the file backup script!

All you have to do now to get a backup of your entire webserver is just download the fullsite_backup.tar.gz file.

 

If you don’t want the context menu to show up in your list

You need to modify the XSLT template (site/lists/list_name/read_list_name.aspx). Before I could view the XSLT content I had to drag part of the grid… and remove the following

  1. <xsl:if test="@ClassInfo=’Menu’ or @ListItemMenu=’TRUE’">
  2.         <xsl:attribute name="height">100%</xsl:attribute>
  3.         <xsl:attribute name="onmouseover">OnChildItem(this)</xsl:attribute>
  4. </xsl:if>
 

This is going to show you how to create a listing of related database content in SharePoint. Two great resources that help me do this are:

First things first: Create a SQL Server View of Related Content

The database that I will be working with has 3 tables; Person, Jobs and Education.

Populated with

  1. INSERT INTO Playground.dbo.Person (first_name, last_name) VALUES(‘brian’, ‘strickland’);
  2. INSERT INTO Playground.dbo.Person (first_name, last_name) VALUES(‘joe’, ‘smith’);
  3. INSERT INTO Playground.dbo.Person (first_name, last_name) VALUES(‘jane’, ‘doe’);
  4.  
  5. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(3, ‘Babysitter’, ’1/1/2002′, ’3/23/2002′);
  6. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(3, ‘Cook’, ’1/1/2003′, ’2/3/2004′);
  7. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(3, ‘Host’, ’2/4/2004′, ’7/2/2005′);
  8. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(3, ‘Dancer’, ’1/1/2002′, ’3/23/2002′);
  9. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(3, ‘Singer’, ’1/1/2002′, ’3/23/2002′);
  10.  
  11. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(2, ‘Researcher’, ’1/1/2002′, ’3/23/2003′);
  12. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(2, ‘Unemployed’, ’1/1/2004′, ’3/23/2004′);
  13. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(2, ‘Student’, ’1/1/2005′, ’3/23/2005′);
  14. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(2, ‘Unemployed’, ’1/1/2006′, ’3/23/2007′);
  15.  
  16. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(1, ‘Bum’, ’1/1/2003′, ’3/23/2011′);
  17. INSERT INTO Playground.dbo.Jobs (person, title, start_date, end_date) VALUES(1, ‘Homeless’, ’2/29/2012′, ’4/1/2012′);
  18.  
  19. INSERT INTO Playground.dbo.Education (person, name, degree) VALUES(1, ‘BCC’, ‘AA’);
  20. INSERT INTO Playground.dbo.Education (person, name, degree) VALUES(1, ‘UCF’, ‘CS’);
  21. INSERT INTO Playground.dbo.Education (person, name, degree) VALUES(2, ‘BCC’, ‘AA’);
  22. INSERT INTO Playground.dbo.Education (person, name, degree) VALUES(3, ‘BCC’, ‘AA’);
  23. INSERT INTO Playground.dbo.Education (person, name, degree) VALUES(3, ‘UCF’, ‘Art’);

We will create the view so that returns a list of all rows in Person with each of it’s related Education and Jobs data by choosing the following columns.

The problem with this, is that I don’t want to display 17 rows to my SharePoint users.  I only need to show them the 3 employees and merge the name, degree, etc fields.  To do this I need to write a SQL function for each of my related tables that will combined the related row data into a single column.

The way I go about doing this is that I create a function for each related table that I need to concatenate into one column and replace that select column in my sql view to use that function.  For example the default sql view is

  1. SELECT dbo.Person.id, dbo.Person.first_name, dbo.Person.last_name, dbo.Education.name, dbo.Education.degree, dbo.Jobs.start_date, dbo.Jobs.title
  2. FROM dbo.Person
  3. INNER JOIN dbo.Education ON dbo.Person.id = dbo.Education.person
  4. INNER JOIN dbo.Jobs ON dbo.Person.id = dbo.Jobs.person

So what I want to do is replace all of the dbo.Education.* with a function call that will concatenate all of the Education data for a single person and a function call that will concatenate all of the dbo.Jobs.* data for a single person.

To create a single string for the Education data create the following scalar function:

  1. CREATE FUNCTION [dbo].[fnGetEducation] (@person INT)
  2.  
  3. RETURNS VARCHAR(8000)
  4. AS
  5. BEGIN
  6.  
  7. DECLARE @list VARCHAR(8000)
  8. SELECT @list =(
  9.  
  10. SELECT name + ‘, ‘ + degree + ‘; ‘
  11. FROM Education
  12. WHERE person = @person
  13. AND name IS NOT NULL – only care about the name
  14. ORDER BY name
  15. FOR XML PATH()
  16. )
  17.  
  18. RETURN LEFT(@list,(LEN(@list) -1))
  19. END

To create a single string for the Jobs data create the following scalar function:

  1. CREATE FUNCTION [dbo].[fnGetJobs] (@person INT)
  2.      
  3. RETURNS VARCHAR(8000)
  4. AS
  5. BEGIN
  6.            
  7. DECLARE @list VARCHAR(8000)
  8. SELECT @list =(
  9.      
  10.     SELECT  title + ‘, ‘ + CONVERT(nvarchar(30), [start_date], 126) + ‘ – ‘ + CONVERT(nvarchar(30), end_date, 126) + ‘; ‘
  11.     FROM    Jobs
  12.     WHERE person = @person
  13.     AND title IS NOT NULL    
  14.     ORDER BY title
  15.     FOR XML PATH()
  16. )
  17.      
  18. RETURN LEFT(@list,(LEN(@list) -1))
  19. END

Once you’ve done that ALTER the view by replacing

  1. dbo.Education.name, dbo.Education.degree

with

  1. (SELECT dbo.fnGetEducation(dbo.Person.id)) AS [Education]

and

  1. dbo.Jobs.start_date, dbo.Jobs.title, dbo.Jobs.end_date

with

  1. (SELECT dbo.fnGetJobs(dbo.Person.id)) AS Jobs

Now if you execute your view query you’ll get

Now you have a view that SharePoint can build external lists from.  In Part II I will describe how to do this.

 

I had a friend running Windows Vista and whenever she tried to close the gadgets, her computer would freeze. The fix to this was pretty simple:

1) Go to “%localappdata%\Microsoft\Windows Sidebar”
2) Copy “Settings.ini” to somewhere else
3) Delete “Settings.ini”
4) Log off and log back on.

© 2012 Strick's Modern Life Suffusion theme by Sayontan Sinha