Author: Joe Meyer
Date: April 19, 2014

Goal: To update Concrete5 using the Concrete5 built in automated updater

Difficulty: Easy

Prerequisites: Your PHP settings must support CURL requests. This guide assumes that you are an administrator of the concrete5 website you are trying to update. While no FTP access is directly required for the upgrade, in the event your upgrade fails you will need access to directly modify files. 

Note: These screenshots were taking from an instance of concrete5 running on version 5.5. If you are using Concrete5.4 or older the administration interface may appear drastically different and may require you to go through other upgrade steps before being able to update your site. Also, please note that recovering from a failed backup is not included in this tutorial, however it does prepare you for that scenario.

Introduction

One question we never hear people stop asking is "Should I upgrade my Concrete5 website to the latest version?" Our answer is almost always "It depends". We typically recommend that if it is feasible, you stay on the most current release that has been available for at least 3 weeks. Why this 3 week waiting period? Unfortunately, nobody is perfect, and this includes the developers working on the Concrete5 core. There have been countless times where the core has become corrupted or something was broken due to a human programming error, and that can cause your site to break. Waiting for a release to become a little more stable than bleeding edge is generally what we recommend to those clients of ours who don't have programming expertise to fix a bug themselves when it occurs. Typically, if a release has been available for 2-3 weeks and no new release has followed, I would consider that pretty stable. On the other hand, if you are having issues with your current version of Concrete5 that you know was fixed in a release, it may be beneficial for you to upgrade right away.

Regardless, we do think that keeping your site up to date and doing at least annual upgrades is a good idea. This helps to ensure that going forward you won't have to go through several iterations of upgrades in one shot. This is a bad thing to do because then if something does break, and you don't discover it right away, you have to try and deduce what actually caused the issue, was it the first upgrade? the second? the third? So there is a bit of a balancing act here with waiting and upgrading. If you have questions you can certainly reach out to our development staff or the Concrete5 community for expert advice or opinions.

Step One: Backing Up Your Concrete5 Website

I cannot stress enough how important it is to back up your Concrete5 website prior to doing any updates (including when you do add-on updates). While 99% of the time Concrete5 upgrades without any problems, there's always that 1% of the time where you find out that the database server crashed during the upgrade, or PHP ran out of memory halfway through, or perhaps you had a developer who thought they knew what they were doing and ended up modifying the Concrete5 core files, breaking any upgrade ability you might have had. Several of these reasons often happen on shared hosting due to lack of resources or conservative settings.

  1. The first step of backing up your Concrete5 website is to access the system section of the dashboard. This can be done by going to http://www.example.com/index.php/dashboard/system/. It can also be done by hovering over the "Dashboard" icon in the toolbar, then selecting the "System & Settings".
    C5 Dashboard Systems & Settings

  2. From the Systems & Settings page we now want to look under the "Backup & Restore" section, and select "Backup Database". You can access the backup database by using the following URL directly: http://www.example.com/index.php/dashboard/system/backup_restore/backup/
    Concrete5 Dashboard Systems & Settings - Backup Database

  3. Next click the "Run Backup" button.
    Concrete5 Systems & Settings Run Backup
    Note: As stated int he important information section, you should not keep these backups any longer than necessary. Having .sql backup files in your web root is generally a bad idea in general due to possible security implications. Also, these files can take up a lot of disk space depending on the size of your website, adding to your site backup times and possibly exhausting your disk quota with your web host.

  4. Once this completes you should now have your backup listed in the "Existing Backups" section.

Previously I stated that you do not really need to back up your file system, this is because Concrete5 doesn't overwrite any "core" files when the upgrade happens. However, it does modify your files in the /config/ folder on the root of your site. It may not be a bad idea to back these files up, since in the event you need to revert your site you would essentially just need to restore your database and the files in that directory. That said, if you don't it's usually not overly difficult to rebuild those configuration files assuming they weren't completely deleted.

Step Two: Downloading the Next Version

  1. Back on the Dashboard "Systems & Settings" page, you should select the "Update Concrete5" link. This can be accessed via http://www.example.com/index.php/dashboard/system/backup_restore/update/.
    Concrete5 Systems & Settings Update Concrete5

  2. This page may look slightly different to everyone depending on what version you are on, and what future versions are available. However, you should have a "Check for Updates" button and a "Download" button if updates are available. In this case we will select the "Download" button.
    Concrete5 Systems & Settings Download Update
    Note: Downloading is not the same as upgrading, you can download your files and come back later to do the upgrade. This is often a good idea if you don't have time to commit immediately to doing testing right after you do the upgrade, especially if your web server does not have a high amount of bandwidth available or you are testing from your home connection. If you do wait for a period of time after downloading the update, please make a new database backup just in case something changed.

    Also, on this page there are often release note highlights indicating what has changed and a link to more detailed notes. These are often a good thing to read through if you are considering moving to the most current version of Concrete5.

Step Three: Running the Updater

  1. Finally, we're to the updating part of things. After you downloaded the update, it should have redirected you to http://www.example.com/index.php/dashboard/system/backup_restore/update/. Which looks like the screenshot below. If not you can use the link posted above, or go back into the Systems & Settings page, and click on "Update Concrete5" again.
    Concrete5 Install Local Update

  2. Next, click the "Update" button showing on screen. Afterwards you should be presented with a screen saying that the update is completed. At this point, you should review your website to make sure everything is working as expected.
    Concrete5 Upgrade Complete

    Note: In the event that you received a PHP memory error or other odd error, you can re-run the update by going to http://www.example.com/index.php/tools/required/upgrade?force=1.

Step Four: Cleanup

Please make sure that post-upgrade you clear your cache. 

  1. This can be done by going to the "Systems & Settings" section of the Dashboard and clicking on "Clear Cache" option under the optimization.
    Concrete5 Dashboard Clear Cache

  2. Next you will be presented with a simple interface screen. Click the "Clear Cache" button and you're all set to start using your newly updated Concrete5.

  3. Once you are comfortable your install is working properly, go back to the "System & Settings" page and into the "Backup Database" page. Here you should make certain that you DELETE your backup. Alternatively, if you really want to keep this around, you should download the file, save it into some nice safe spot on your computer, and then delete it off the server.

Need Additional Help?

In the event that you need additional help with your update there are several resources available to you.

  • First and foremost the Concrete5 community. Create an account on www.concrete5.org and post to the forums with any problems you might run across. This helps alert the developers of problems people are having so that the product can be made better or more user friendly in the future.
  • Secondly, contact your web host. Often times the installer may error due to php not being configured with enough memory or perhaps it's not running the right version. These are things that your web host should be able to help you address.
  • Thirdly, please feel free to contact ExchangeCore's development team. I list us as the last option because while we know what we're doing, but we also come at a price and we hate to see you throw money away when there are other options. If you do need our help, we can be reached by visiting our Development Contact Page.

Author: Joe Meyer
Date: April 15, 2014

Goal: To install Concrete5 Manually on my cPanel web hosting account

Difficulty: Easy

Prerequisites: This guide shows you how to add files and databases using cPanel. If you are not on a hosting account where cPanel is available to you, you may have other alternatives such as FTP for file transfers and other database creation utilities.

Concrete5 is a great user friendly content management system. Installing Concrete5 can be a bit intimidating if you don't have any experience installing websites and the hope is that by following this step by step guide, even someone who is completely unfamiliar with what a database is, or how to unzip a file, you will be able to get Concrete5 installed and off to working on your latest site. So let's get started...

Just one more quick note. Throughout this guide you see me using "c5help" in many areas. This variable simply happens to be the sub-domain I am installing to. You do not need to have any part of this when you set up your site.

Step One: Setting Up a Database

Concrete5 uses a MySQL database in order to store the dynamic content on your website. This means that you are going to need a MySQL database, a MySQL username, and a MySQL password. Here's how you will set those up if you are using a host that offers cPanel. Note: Your screen may look slightly different based on the cPanel theme you are using, the examples below were all taken from the x3 theme.

  1. Log in to your cPanel account. Usually this can be done by going to www.example.com/cpanel (where example.com is your domain name).
  2. Find the "Databases" group and click on "MySQL Databases"
    cPanel MySQL Databases

  3. Next create a short and meaningful database name. Usually, I try to make my database names resemble my website names in some way. This way it is easy to identify which database belongs to which site later on if I decide to add another to my account. Do this by entering the database name and clicking "Create Database".
    cPanel Create Database

  4. Next, you will want to create a MySQL user. I strongly recommend using the password generator and here is my reasoning: 1) The password generator will ensure you have a very strong password and 2) The password generator will help ensure that you are not re-using a password you used before. This second part is important since this password will be stored in clear text in your Concrete5 configuration file. This means that any developer or anyone with FTP access to your site has access to this username and password, and it would be horrible for them to have a password that you used elsewhere. Also, you only ever have to type this password when you install Concrete5 in a later step so there's no sense in making this password something memorable. Click "Create User" once you are finished with this section.
    cPanel create MySQL user

  5. Finally, we want to assign the MySQL user we just created to our MySQL database we just created. This is a pretty straightforward process of picking them from the select lists and clicking "Add".
    cPanel add mysql user to database
    Then we click the "All Privileges" checkbox and finally the "Make Changes" button.
    cPanel MySQL Account Maintenance

Step Two: Preparing Concrete5 Files for Installation

 This next section is all about setting up the necessary files and file security for Concrete5 to work. These files will work in tandem with the database once installed to give you the powerful CMS of Concrete5.

  1. The first step to this is to download a zip of the latest version of Concrete5. You can obtain a copy of this from http://www.concrete5.org/developers/downloads/. Then look for the heading of "Latest Stable Version" and click the download link.
    Concrete5 Download Page

  2. Once you have that downloaded go back to your cPanel home page and find the "File Manager". We will be using this to upload the zip file we just downloaded from Concrete5's website.
    cPanel File Manager

  3. At the Directory Selection prompt pick the "Document Root for:" and your website name there. Also, make sure that you have the "Show Hidden Files (dotfiles)." checkbox selected.
    cPanel File Manager Directory Selection

  4. Next, pick the "Upload" button from the upper left hand corner of the file manager.
    cPanel Upload File with File Manager

  5. A new window will open, select the "Choose File" button and pick your Concrete5 zip file you downloaded from their website. (Odds are this is probably in your downloads folder).
    cPanel File Uploader Choose File
    Once you have picked the file to be uploaded it will start uploading immediately and you will see an uploading indicator at the bottom of your browser window. Wait until this has finished to move on.
    cPanel File Uploader Status
  6. Once the status is finished (and it doesn't appear like the page is doing any more loading) you can close this browser window and go back to the file manager.
  7. Next, we need to extract the files using the file manager. Select the zip file, then click the "Extract" button in the upper right corner.
    cPanel File Manager Extract
    Choose to extract these files to the root of your website (this should be the default location if you picked the document root as instructed in the previous step). Once it's extracted it will give you some results screens and other stuff you can ignore.

  8. At this point you should now have an unzipped set of files. (You should have a folder named similar to your zip file, if so you can at this point delete the zip file).

  9. Go into the unzipped folder by either using the tree view on the left side of the file manager, or by double clicking on the folder. Inside it should appear to be the root of what a Concrete5 website looks like. (See screenshot below). Click the "Select all" checkbox at the top of the file list.
    Concrete5 File capture

  10. After you have selected all the files, we now need to move them up to the parent directory so that they are sitting at the root of your site. Do this by clicking the "Move File" option in the upper left hand corner.
    cPanel File Manager Move File

    Then remove the concrete folder from the path you want to move them to. In my case I changed the path from
    /public_html/c5help/concrete5.5.2.1
    to be the root of my site:
    /public_html/c5help

    cPanel File Manager Move File2

  11. Now change back to your site root directory (using the tree on the left or the Up One Level icon) and delete the old concrete5.x.x.x folder (DO NOT REMOVE THE "concrete" FOLDER)
  12. Next we need to change the permissions of the config folder and the files folder to make them writable. First click on the config folder, then select "Change Permissions" on the top menu bar (make sure only config is selected)
    cPanel File Manager Change Permissions Config
    For the config folder ensure the permissions are set to 755 (Shown below)
    cPanel File Manager Chmod Config

    Next do the same thing for the "files" directory except this time use 777 for permissions. (shown below)
    cPanel File Manager Chmod Files

  13. Next we need to create a .htaccess file for the site. To do this use the "New File" button in the upper left hand corner of the File Manager. 
    cPanel File Manager New File

    The new file .htaccess file should be placed on the root of your site.
    cPanel File Manager New .htaccess File

  14. Now we need to edit the contents of this .htaccess file. Do this by selecting the file, then selecting the "Edit" button near the top of your screen. Note: If you did not check the box to show hidden files when you opened the file manager, you will not see the .htaccess file appear even though it exists.

    Once you have the editor open please populate it with the contents below and save changes:

    DirectoryIndex index.php
    
    RewriteEngine On
    RewriteBase /
    
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    
    RewriteRule ^(.*)$ index.php/$1 [L]
    
    cPanel File Manager Edit .htaccess

  15. Now we are done setting up the Concrete5 files. The file manager can be closed.

 Step Three: The Install

Now we are finally ready to run the Concrete5 installer.

  1. Visit the path you installed your concrete5 site to. In this example I have put everything on the root of my subdomain so I will simply use that: http://c5help.exchangecore.com/ but you may have put yours on the root of your domain or possibly even in a sub folder such as www.example.com/concrete/. When you visit you should immediately be presented with a requirements page. If you have any requirements not met, you should contact your web host to find out if they are available to you.
    Concrete5 Requirements Check

  2. Click the "Continue to Installation" button.
  3. You are now on a page asking you for some information. Here's some info about each field for you:
    Site Name -  you should pick something that is less than 60 characters, since this will be used as part of the title on all of your pages and can have SEO implications.

    Administrator Information - This information is for setting up the super admin to the site. The password is something you can make up that should be easy for you to remember, since this is the account you will most likely use for making changes to the site.

    Database Information
    Server
    : Usually this will be "localhost" unless your web host is using a remote MySQL server. You will have to contact them if this is the case to get the appropriate MySQL server host name or IP address.

    MySQL Username: If you recall, way long ago back under the first section we created a MySQL user. That is what this should be. In my example this is exchange_c5help

    MySQL Password: Again, something we created back in the first section. This is the one and only time you really need to know that password you generated or created for the MySQL user. Once this step is done you can clear that notepad file or toss that post-it note.

    Database Name: Again, set up back in the first section. It just so happens that I named mine the same as my MySQL user, exchange_c5help. 

    Sample Content
    This part is entirely up to you. If you know what you're doing a blank site should suit you well. Otherwise, per C5's recommendation, I'll also recommend you start with the sample site.

    Here's a screenshot of what my final install page looks like:
    Concrete5 Install Page

  4. Click the "Install concrete5" button and wait for the installer to finish. Once it has (hopefully without error) you should be logged in as the admin and you can now start building your website!

Need Additional Help?

In the event that you need additional help with your installation there are several resources available to you.

  • First and foremost the Concrete5 community. Create an account on www.concrete5.org and post to the forums with any problems you might run across. This helps alert the developers of problems people are having so that the product can be made better or more user friendly in the future.
  • Secondly, contact your web host. Often times the installer may error due to php not being configured with enough memory or perhaps it's not running the right version. These are things that your web host should be able to help you address.
  • Thirdly, please feel free to contact ExchangeCore's development team. I list us as the last option because while we know what we're doing, we also come at a price and we hate to see you throw money away when there are other options. If you do need our help, we can be reached by visiting our Development Contact Page.

Author: Joe Meyer
Date: April 14, 2014

I had the need in one of my applications to take associative PHP arrays and generate CSV files for the users to download from them. More specifically, I was dumping data from my database to CSV files. However, another thing I wanted to avoid doing was that I did not want to save a file to the web server then have to delete it when the request was done.

This function takes in an array of associative arrays (associative arrays must contain all the same keys) and outputs it to a CSV file using the first row's key values as the headers for the CSV file.

The function looks something like this: 

/**
 * Takes in a filename and an array associative data array and outputs a csv file
 * @param string $fileName
 * @param array $assocDataArray     
 */
public function outputCsv($fileName, $assocDataArray)
{
    ob_clean();
    header( 'Content-Type: text/csv' );
    header( 'Content-Disposition: attachment;filename='.$fileName);
    header( 'Pragma: no-cache' );
    header( 'Expires: 0' );
    if(isset($assocDataArray['0'])){
        $fp = fopen('php://output', 'w');
        fputcsv($fp, array_keys($assocDataArray['0']));
        foreach($assocDataArray AS $values){
            fputcsv($fp, $values);
        }
        fclose($fp);
    }
    ob_flush();
}

And here's an example of the function call in use:

$data = array(
    array( 'item' => 'Server', 'cost' => 10000, 'approved by' => 'Joe'),
    array( 'item' => 'Mt Dew', 'cost' => 1.25, 'approved by' => 'John')
    array( 'item' => 'IntelliJ IDEA', 'cost' => 500, 'approved by' => 'James'),
);

outputCsv('expenses.csv', $data);

Hope you guys find this helpful. I know it has become one of my most universal scripts that I put in my applications and it also doubles as an excellent debugging tool for var dumping large arrays into an easy format to read, search, and sort.

Author: Joe Meyer
Date: January 22, 2014

Goal: Create a Login page that works with Active Directory using the Yii Framework

Difficulty: Medium

Prerequisites: Yii Framework, Basic understanding of Yii Configurations, Basic PHP Knowledge is helpful

 

Yii is a very powerful and extendable framework. In a many corporate environments where Active Directory is used as the primary means of authentication, it makes sense to integrate that same authentication form into your website. This helps to eliminate the need to maintain another username/password set, makes enforcing password policies a breeze, and makes life a whole lot easier in general.

The scripts below are relatively simple, and yet relatively robust for the small amount of time it takes to implement them. This method of integrating active directory with your Yii php instance supports restricting login to a specific organizational units or domains. It allows you to specify multiple OUs or Domains in case you want to allow users from one domain in OU Users to join and users from another domain. This PHP script also supports naming multiple domain controllers such that if one goes offline it will fail over to the next one in the list.

So lets get started implementing. The first thing we'll do is to create our UserIdentity file. This is how you do authentication logic for logging in with Yii. This file you can copy and paste and you should not need to make any changes to this file directly. This is where all of the LDAP stuff actually happens, so for those of you finding this just looking for a way to authenticate in PHP this file is probably the only one you really care about (and maybe a bit of the configuration file).

/protected/components/UserIdentity.php

<?php

/**
 * UserIdentity represents the data needed to identity a user.
 * It contains the authentication method that checks if the provided
 * data can identity the user.
 */
class UserIdentity extends CUserIdentity
{

    const ERROR_NO_DOMAIN_CONTROLLER_AVAILABLE = 1001; // could not bind anonymously to any domain controllers
    const ERROR_INVALID_CREDENTIALS = 1002; // could not bind with user's credentials
    const ERROR_NOT_PERMITTED = 1003; //user was not found in search criteria

    private $_options;

    private $_domain;
    private $_email;
    private $_firstName;
    private $_lastName;
    private $_securityGroups;

    private $_loginEmail = false;


    public function __construct($username = null,$password = null)
    {
        $this->_options = Yii::app()->params['ldap'];

        $this->username = $username;
        $this->password = $password;

        if(strpos($username,'@') !== false){
            $this->_loginEmail = $username;
            $exploded = explode('@',$username);
            $this->username = $exploded[0];
        }

        $slashPos = strpos($this->username, "\\");
        if($slashPos !== false){
            $this->username = substr($this->username, $slashPos+1);
            $this->_domain = substr($this->username, 0, $slashPos);
        }else{
            $this->_domain = $this->_options['defaultDomain'];
        }
    }

	public function authenticate()
	{
        $this->errorCode = self::ERROR_NONE;
        if($this->username != '' && $this->password != ''){

            $bind = false;
            $connected = false;
            $ldap = false;

            //connect to the first available domain controller in our list
            foreach($this->_options['servers'] AS $server){
                $ldap = ldap_connect($server);
                if($ldap !== false){
                    ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
                    ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
                    $connected = @ldap_bind($ldap); //test if we can connect to ldap using anonymous bind
                    if($connected){
                        if($this->_loginEmail === false){
                            $ldaprdn = $this->_domain . "\\" . $this->username;
                        }else{
                            $ldaprdn = $this->_loginEmail;
                            var_dump($ldaprdn);
                        }
                        $bind = @ldap_bind($ldap, $ldaprdn, $this->password);
                        break; //we connected to one successfully
                    }
                }
            }

            //were we able to connect to any domain controller?
            if(!$connected){
                $this->errorCode = self::ERROR_NO_DOMAIN_CONTROLLER_AVAILABLE;
            }else{
                //were we able to authenticate to a domain controller as our user?
                if ($bind) {
                    //if we can bind to active directory we must have valid AD credentials
                    $filter='(sAMAccountName='.$this->username.')';

                    $conn=array();
                    for($i = 0; $i < count($this->_options['search']); $i++){
                        $conn[] = $ldap;
                    }
                    $results = ldap_search($conn,$this->_options['search'],$filter);
                    $foundInSearch = false;
                    foreach($results AS $result){
                        $info = ldap_get_entries($ldap, $result);
                        if($info['count'] > 0){
                            $this->_firstName = (isset($info['0']['givenname']['0']))?($info['0']['givenname']['0']):('');
                            $this->_lastName = (isset($info['0']['sn']['0']))?($info['0']['sn']['0']):('');
                            $this->_email = (isset($info['0']['mail']['0']))?($info['0']['mail']['0']):('');

                            $this->_securityGroups = array();
                            foreach($info['0']['memberof'] AS $sg){
                                preg_match('/CN=(.*?),/',$sg, $matches);
                                if(isset($matches[1])){
                                    $this->_securityGroups[] = $matches['1'];
                                }
                            }
                            sort($this->_securityGroups);

                            $foundInSearch = true;
                            break;
                        }
                    }

                    if(!$foundInSearch){
                        $this->errorCode = self::ERROR_NOT_PERMITTED;
                    }
                }else{
                    //if we can't bind to active directory it means that the username / password was invalid
                    $this->errorCode = self::ERROR_INVALID_CREDENTIALS;
                }
            }
        }else{
            //if username or password is blank don't even try to authenticate
            $this->errorCode = self::ERROR_INVALID_CREDENTIALS;
        }

        switch($this->errorCode){
            case self::ERROR_INVALID_CREDENTIALS :
                $this->errorMessage = 'Invalid Credentials.';
                break;
            case self::ERROR_NO_DOMAIN_CONTROLLER_AVAILABLE :
                $this->errorMessage = 'No domain controller available.';
                break;
            case self::ERROR_NOT_PERMITTED:
                $this->errorMessage = 'Not permitted in application.';
                break;
            case self::ERROR_NONE :
                $this->setState('firstName', $this->_firstName);
                $this->setState('lastName', $this->_lastName);
                $this->setState('email', $this->_email);
                $this->setState('adSecurityGroups', $this->_securityGroups);
                break;
            default : $this->errorMessage = 'Unable to Authenticate';
        }


		return !$this->errorCode;
	}

    public function getName(){
        return $this->_firstName.' '.$this->_lastName;
    }
}

Now we need to create the validation logic for the forms. In Yii we can do some pretty fancy stuff using the models so I'll use that here. Keep in mind though that you could just as easily leave out the model and build your form manually with the view files and put your logic in your controller.

/protected/models/LoginForm.php

<?php

/**
 * LoginForm class.
 * LoginForm is the data structure for keeping
 * user login form data. It is used by the 'login' action of 'SiteController'.
 */
class LoginForm extends CFormModel
{
	public $username;
	public $password;
	public $rememberMe;

	private $_identity;

	/**
	 * Declares the validation rules.
	 * The rules state that username and password are required,
	 * and password needs to be authenticated.
	 */
	public function rules()
	{
		return array(
			// username and password are required
			array('username, password', 'required'),
			// rememberMe needs to be a boolean
			array('rememberMe', 'boolean'),
			// password needs to be authenticated
			array('password', 'authenticate'),
		);
	}

	/**
	 * Declares attribute labels.
	 */
	public function attributeLabels()
	{
		return array(
			'rememberMe'=>'Remember me next time',
		);
	}

	/**
	 * Authenticates the password.
	 * This is the 'authenticate' validator as declared in rules().
	 */
	public function authenticate($attribute,$params)
	{
		if(!$this->hasErrors())
		{
			$this->_identity=new UserIdentity($this->username,$this->password);
			if(!$this->_identity->authenticate())
				$this->addError('password',$this->_identity->errorMessage);
		}
	}

	/**
	 * Logs in the user using the given username and password in the model.
	 * @return boolean whether login is successful
	 */
	public function login()
	{
		if($this->_identity===null)
		{
			$this->_identity=new UserIdentity($this->username,$this->password);
			$this->_identity->authenticate();
		}
		if($this->_identity->errorCode===UserIdentity::ERROR_NONE)
		{
			$duration=$this->rememberMe ? 3600*24*30 : 0; // 30 days
			Yii::app()->user->login($this->_identity,$duration);
			return true;
		}
		else
			return false;
	}
}

Next we'll set up our action for the login page. I put this in my site controller. Again it should just be a straight copy and paste, no edits necessary.

/protected/controllers/SiteController.php segment


	/**
	 * Displays the login page
	 */
	public function actionLogin()
	{
		$model=new LoginForm;

		// if it is ajax validation request
		if(isset($_POST['ajax']) && $_POST['ajax']==='login-form')
		{
			echo CActiveForm::validate($model);
			Yii::app()->end();
		}

		// collect user input data
		if(isset($_POST['LoginForm']))
		{
			$model->attributes=$_POST['LoginForm'];
			// validate user input and redirect to the previous page if valid
			if($model->validate() && $model->login())
				$this->redirect('/');
		}
		// display the login form
		$this->render('login',array('model'=>$model));
	}

Now we need to create the view file for our form. Again, no changes required here though you can style it to your own liking. When I set this up I used the default Yii Classic template.

/protected/views/site/login.php

<?php
/* @var $this SiteController */
/* @var $model LoginForm */
/* @var $form CActiveForm  */

$this->pageTitle=Yii::app()->name . ' - Login';
$this->breadcrumbs=array(
	'Login',
);
?>

<h1>Login</h1>

<p>Please fill out the following form with your login credentials:</p>

<div class="form">
<?php $form=$this->beginWidget('CActiveForm', array(
	'id'=>'login-form',
	'enableClientValidation'=>true,
	'clientOptions'=>array(
		'validateOnSubmit'=>true,
	),
)); ?>

	<p class="note">Fields with <span class="required">*</span> are required.</p>

	<div class="row">
		<?php echo $form->labelEx($model,'username'); ?>
		<?php echo $form->textField($model,'username'); ?>
		<?php echo $form->error($model,'username'); ?>
	</div>

	<div class="row">
		<?php echo $form->labelEx($model,'password'); ?>
		<?php echo $form->passwordField($model,'password'); ?>
		<?php echo $form->error($model,'password'); ?>
		<p class="hint">
			Hint: You may login with <kbd>demo</kbd>/<kbd>demo</kbd> or <kbd>admin</kbd>/<kbd>admin</kbd>.
		</p>
	</div>

	<div class="row rememberMe">
		<?php echo $form->checkBox($model,'rememberMe'); ?>
		<?php echo $form->label($model,'rememberMe'); ?>
		<?php echo $form->error($model,'rememberMe'); ?>
	</div>

	<div class="row buttons">
		<?php echo CHtml::submitButton('Login'); ?>
	</div>

<?php $this->endWidget(); ?>
</div><!-- form -->

Finally, we get around to configuring our LDAP configurations. I have set up all the configuration options as part of a ldap parameter. You should add these parameters to your Yii configuration file and tweak as necessary. Here is an outline of what each config option is used for:

servers - a list of IP addresses or DNS names which you want to try and authenticate against. The first one in the list will always be tried first, it will only move on to the next one if it fails to bind to the ldap port.

defaultDomain - a default domain which you want to authenticate to if the user does not try logging in using an email address or using the DOMAIN\USERNAME format.

search - an array of LDAP filters which the user authenticating will be searched for in. If the user does not belong to any of the filters applied, they will receive an access denied message when they try to authenticate. In my example I am only including the Faculty and Staff OUs so if someone in the students OU tries logging in they will be denied access.

Sample Params in Config File: /protected/config/main.php segment

'params'=>array(		
        'ldap' => array (
            'servers' => array(
                'dc1.example.com',
                'dc2.example.com',
                '10.1.0.3',
            ),
            'defaultDomain' => 'EXAMPLE',
            'search'=>array(
                'ou=Students,dc=EXAMPLE,dc=net',
                'ou=Faculty,dc=EXAMPLE,dc=net',
            ),
        ),
),

For those of you who want a quick start I have attached a zip file containing the necessary files to make this work with a stock Yii setup. Just drop the files in, tweak the config, and you're ready to authenticate against your active directory servers!

Yii_Active_Directory_LDAP.zip

Author: Joe Meyer
Date: January 2, 2014

Goal: To allow .php files to run on a windows IIS 7.5 web server

Difficulty: Medium

Prerequisites: IIS 7.5 Webserver

With PHP being such a popular programming language for the web, it is important to understand how we can leverage our windows web server to run php. Below I will demonstrate how to install PHP 5.4 onto my brand new IIS7 web server. This server is running Windows 2008 r2 standard edition. A couple of disclaimers before I get started with the basic guide: You will need to adjust the paths in this guide suitable to your environment, I am using C: for my default website and S: (Secondary) drive for my program files (aka php installation). You will also need to make sure that you have the appropriate C++ runtime environment on your operating system. This is a fairly standard requirement so it is not included in this guide. Lastly, if you run into any problems or have question please feel free to reply to this thread or create your own and I will be more than happy to help you figure out what's going on (in my experience if you follow this guide and something isn't working it's a permission problem, or a php.ini configuration problem)

  1. The first step to making sure you can use PHP on your IIS server it to make sure you have CGI enabled. This can be done from the Server Manager under the Roles section.
    IIS7.5 with PHP 1.png
  2. Next leats head out to http://php.net/downloads.php and select the Windows {Version} binaries and source link.
    IIS7.5 with PHP 2.png
  3. Next make sure you download the "Non Thread Safe" zip file of the version you want. You may also notice that it says VC9 x86, this indicates that you will need the x86 version of the c++ 2008 package on your machine in order for php to run.
    IIS7.5 with PHP 3.png
  4. You will then be presented with some sort of download menu, if your using a browser that supports it just go ahead and open the zip file.
    IIS7.5 with PHP 4.png
  5. After you have the zip file opened go ahead and select "Extract all files" near the top left corner of the window.
    IIS7.5 with PHP 5.png
  6. Now extract these files to wherever you want your php installation to be located. For me I have this on a secondary drive under Program Files. Following the extraction go ahead and open up that folder as we have some work to do in that directory before we're done.
    IIS7.5 with PHP 6.png
  7. In your PHP installation directory copy your php.ini-production (or development depending on what kind of sites you are putting up on this box).
    IIS7.5 with PHP 7.png
  8. Then paste this file back in and rename it to be "php.ini".
    IIS7.5 with PHP 8.png
  9. In your php.ini file make sure you have the following set:
    open_basedir = {YOUR IIS WEBSITES PATH HERE}
    cgi.fix_pathinfo=1
    fastcgi.impersonate=1
    IIS7.5 with PHP 9.png
  10. After this set up your extensions you wish to have enabled and configure the rest of your php.ini file to your liking. Save and close the file.
    IIS7.5 with PHP 10.png
  11. Next Open up the Internet Information Services (IIS) Manager.
    IIS7.5 with PHP 11.png
  12. Select teh "Handler Mappings" for your server
    IIS7.5 with PHP 12.png
  13. Then click the "Add Module Mapping..." found in the top right corner of the Handler Mappings screen.
    IIS7.5 with PHP 13.png
  14. Then fill in the following information and click OK:
    Request Path:  *.php
    Module: FastCgiModule
    Executable (optional): [Enter the location of your php installation]\php-cgi.exe
    Name: PHP Via FastCGI
    IIS7.5 with PHP 14.png
  15. After you click OK you will be prompted: "Do you want to create a FastCGI application for this executable? Click 'Yes' to add the entry to the FastCGI collection and to enable this executable to run as a FastCGI application." to which you should click "Yes".
    IIS7.5 with PHP 15.png
  16. At this point you should have php running on your IIS server. To test, create a file called phpinfo.php in your default website directory (default is C:\inetpub\wwwroot\) and put the following code in the file and save it.
    <?php
    phpinfo();
    ?>
    IIS7.5 with PHP 16.png
  17. After you have added that file open a web browser on the server and go to http://localhost/phpinfo.php and you should see your php information if php is working properly. If not revisit the steps above and try and find what you might have missed.
    IIS7.5 with PHP17.png

Author: Joe Meyer
Date: January 2, 2014

Often times the question comes up as to what purpose reverse DNS really does for us. To be honestly, I haven't found anything that ptr records MUST be set up for. However, here are some uses that you may encounter them being used for:

  • Network Troubleshooting using tools such as traceroute, ping, or the "Received" header field in SMTP emails. Many web sites use tools such as these to help track users as a reverse DNS name will often mean a lot more to a webmaster than an IP will when trying to figure out if someone is the same user, so ISP's having rDNS set up when they hand out IPs is useful.
  • Along the same lines of network troubleshooting is System logs or Monitoring tools. These often receive the IP address and will try to do a reverse lookup to put in the logs. Again quite useful for system admins or network admins as an IP might not mean much to them.
  • Email anti-spam techniques often use domain names for the rDNS record to see if these domains have rDNS records of something that doesn't match the domain of the sender, or if the record looks like it's coming from a dynamic IP address. According to spamhaus, a global email blacklist database, these are things that could raise the spam score of an email.

Microsoft DNS servers by default add reverse dns lookup. This is a very useful tool for internal debugging as said above, however when it boils down to setting up this record for your webhosting domains, if you aren't planning on sending email from that IP it's probably not worth your effort to add the records.

Another common issue is that many webhosts do not support ptr record setups. If however you have a dedicated IP then your webhost may be able to have the ptr record added. There is no restriction on the number of ptr records for a domain.

If you wish to look up a rdns (pointer) record for an IP address you can use the nslookup command and use the following commands:

nslookup
set type=ptr
[enter ip address and hit enter]

Author: Joe Meyer
Date: January 2, 2014

Goal: Output Directories a specified number of directories deep with their directory permissions.

Difficulty: Medium

Prerequisites: Visual Studios or other C# Compiler.

Attached is a simple script that will find all directories n levels deep on the directory specified to search, and output the permissions associated with those directories. This is a C# Console application. You just need to build it. I make use of the args variable in the main method so that I can run this application and then send it to a file using >. This program also catches any directories it does not have access to and outputs "Could not access <Directory>".

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.AccessControl;
namespace FolderPermissions
{
    class Program
    {
        static void GetDirectorySecurity(string dir, int levels)
        {
            int curLevel = 1;
            string[] dirs = Directory.GetDirectories(dir);
            foreach (string directory in dirs)
            {
                Console.WriteLine("---------------------------------------------------------");
                Console.WriteLine(directory);
                try
                {
                    string tabs = "\t";
                    DirectoryInfo dInfo = new DirectoryInfo(directory);
                    DirectorySecurity dSecurity = dInfo.GetAccessControl();
                    AuthorizationRuleCollection acl = dSecurity.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount));
                    foreach (FileSystemAccessRule ace in acl)
                    {
                        Console.WriteLine("{0}Account: {1}", tabs, ace.IdentityReference.Value);
                        Console.WriteLine("{0}Type: {1}", tabs, ace.AccessControlType);
                        Console.WriteLine("{0}Rights: {1}", tabs, ace.FileSystemRights);
                        Console.WriteLine("{0}Inherited: {1}", tabs, ace.IsInherited);
                        Console.WriteLine();
                    }
                    if (curLevel < levels)
                        GetDirectorySecurity(@directory, curLevel + 1, levels);
                }
                catch
                {
                    Console.WriteLine("Could not access {0}", directory);
                }
            }
        }
        static void GetDirectorySecurity(string dir, int curLevel, int levels)
        {
            string[] dirs = Directory.GetDirectories(@dir);
            string tabs = "";
            for (int i = 0; i < curLevel; i++)
                tabs += "\t";
            foreach (string directory in dirs)
            {
                Console.WriteLine(tabs.Substring(0, tabs.Length - 1) + "---------------------------------------------------------");
                Console.WriteLine(tabs.Substring(0, tabs.Length - 1) + directory);
                try
                {
                    DirectoryInfo dInfo = new DirectoryInfo(directory);
                    DirectorySecurity dSecurity = dInfo.GetAccessControl();
                    AuthorizationRuleCollection acl = dSecurity.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount));
                    foreach (FileSystemAccessRule ace in acl)
                    {
                        Console.WriteLine("{0}Account: {1}", tabs, ace.IdentityReference.Value);
                        Console.WriteLine("{0}Type: {1}", tabs, ace.AccessControlType);
                        Console.WriteLine("{0}Rights: {1}", tabs, ace.FileSystemRights);
                        Console.WriteLine("{0}Inherited: {1}", tabs, ace.IsInherited);
                        Console.WriteLine();
                    }
                    if (curLevel < levels)
                        GetDirectorySecurity(@directory, curLevel + 1, levels);
                }
                catch
                {
                    Console.WriteLine("Could not access {0}", directory);
                }
            }
        }
        static void Main(string[] args)
        {
            try
            {
                if (args[0] != null && args[1] != null)
                    GetDirectorySecurity(@args[0], int.Parse(args[1]));
                else
                {
                    Console.WriteLine("This program requires the input of a starting directory path");
                    Console.WriteLine("including the letter drive followed by an integer specifying");
                    Console.WriteLine("how many directories deep to recursively scan");
                    Console.WriteLine();
                    Console.WriteLine("Example: programname C:\\shared\\ 2");
                }
            }
            catch
            {
                {
                    Console.WriteLine("This program requires the input of a starting directory path");
                    Console.WriteLine("including the letter drive followed by an integer specifying");
                    Console.WriteLine("how many directories deep to recursively scan");
                    Console.WriteLine();
                    Console.WriteLine("Example: FolderPermissions C:\\shared\\ 2");
                    Console.WriteLine("\tThis will scan 2 levels deep on the shared folder on C:");
                }
            }
        }
    }
}

Attached is my program solution built in visual studios 2010 for those of you who wish to use that instead of copying and pasting the code into a new project.

FolderPermissionsFinder.zip