From 732cb11e0cd987574e597d9b04def0ff02c3bf03 Mon Sep 17 00:00:00 2001
From: Mark Baker <mark@lange.demon.co.uk>
Date: Sun, 17 Nov 2013 00:11:45 +0000
Subject: [PATCH] Feature: (LWol) Work Item GH-252 - Adding support for macros,
 Ribbon in Excel 2007

---
 Classes/PHPExcel.php                          | 253 +++++++++++++++++-
 Classes/PHPExcel/Reader/Excel2007.php         |  71 ++++-
 Classes/PHPExcel/Worksheet.php                | 102 +++++++
 Classes/PHPExcel/Writer/Excel2007.php         |  31 +++
 .../Writer/Excel2007/ContentTypes.php         |  34 ++-
 Classes/PHPExcel/Writer/Excel2007/Rels.php    |  20 ++
 .../PHPExcel/Writer/Excel2007/RelsRibbon.php  |  77 ++++++
 Classes/PHPExcel/Writer/Excel2007/RelsVBA.php |  72 +++++
 .../PHPExcel/Writer/Excel2007/Worksheet.php   |   6 +
 changelog.txt                                 |   1 +
 10 files changed, 652 insertions(+), 15 deletions(-)
 create mode 100644 Classes/PHPExcel/Writer/Excel2007/RelsRibbon.php
 create mode 100644 Classes/PHPExcel/Writer/Excel2007/RelsVBA.php

diff --git a/Classes/PHPExcel.php b/Classes/PHPExcel.php
index 7bd0243..c71ee5f 100644
--- a/Classes/PHPExcel.php
+++ b/Classes/PHPExcel.php
@@ -112,18 +112,257 @@ class PHPExcel
      */
     private $_cellStyleXfCollection = array();
 
-    /**
-     * Create a new PHPExcel with one Worksheet
+	/**
+	* _hasMacros : this workbook have macros ?
+	*
+	* @var bool
+	*/
+	private $_hasMacros = FALSE;
+
+	/**
+	* _macrosCode : all macros code (the vbaProject.bin file, this include form, code,  etc.), NULL if no macro
+	*
+	* @var binary
+	*/
+	private $_macrosCode=NULL;
+	/**
+	* _macrosCertificate : if macros are signed, contains vbaProjectSignature.bin file, NULL if not signed
+	*
+	* @var binary
+	*/
+	private $_macrosCertificate=NULL;
+
+	/**
+	* _ribbonXMLData : NULL if workbook is'nt Excel 2007 or not contain a customized UI
+	*
+	* @var NULL|string
+	*/
+	private $_ribbonXMLData=NULL;
+
+	/**
+	* _ribbonBinObjects : NULL if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
+	* ignored if $_ribbonXMLData is null
+	*
+	* @var NULL|array
+	*/
+	private $_ribbonBinObjects=NULL;
+
+	/**
+	* The workbook has macros ?
+	*
+	* @return true if workbook has macros, false if not
+	*/
+	public function hasMacros(){
+		return $this->_hasMacros;
+	}
+
+	/**
+	* Define if a workbook has macros
+	*
+	* @param true|false
+	*/
+	public function setHasMacros($hasMacros=false){
+		$this->_hasMacros=(bool)$hasMacros;
+	}
+
+	/**
+	* Set the macros code
+	*
+	* @param binary string|null
+	*/
+	public function setMacrosCode($MacrosCode){
+		$this->_macrosCode=$MacrosCode;
+		$this->setHasMacros(!is_null($MacrosCode));
+	}
+
+	/**
+	* Return the macros code
+	*
+	* @return binary|null
+	*/
+	public function getMacrosCode(){
+		return $this->_macrosCode;
+	}
+
+	/**
+	* Set the macros certificate
+	*
+	* @param binary|null
+	*/
+	public function setMacrosCertificate($Certificate=NULL){
+		$this->_macrosCertificate=$Certificate;
+	}
+
+	/**
+	* Is the project signed ?
+	*
+	* @return true|false
+	*/
+	public function hasMacrosCertificate(){
+		return !is_null($this->_macrosCertificate);
+	}
+
+	/**
+	* Return the macros certificate
+	*
+	* @return binary|null
+	*/
+	public function getMacrosCertificate(){
+		return $this->_macrosCertificate;
+	}
+
+	/**
+	* Remove all macros, certificate from spreadsheet
+	*
+	* @param none
+	* @return void
+	*/
+	public function discardMacros(){
+		$this->_hasMacros=false;
+		$this->_macrosCode=NULL;
+		$this->_macrosCertificate=NULL;
+	}
+
+	/**
+	* set ribbon XML data
+	*
+	*/
+	public function setRibbonXMLData($Target=NULL, $XMLData=NULL){
+		if(!is_null($Target) && !is_null($XMLData)){
+			$this->_ribbonXMLData=array('target'=>$Target, 'data'=>$XMLData);
+		}else{
+			$this->_ribbonXMLData=NULL;
+		}
+	}
+
+	/**
+	* retrieve ribbon XML Data
+	*
+	* return string|null|array
+	*/
+	public function getRibbonXMLData($What='all'){//we need some constants here...
+		$ReturnData=NULL;
+		$What=strtolower($What);
+		switch($What){
+		case 'all':
+			$ReturnData=$this->_ribbonXMLData;
+			break;
+		case 'target':
+		case 'data':
+			if(is_array($this->_ribbonXMLData) && array_key_exists($What,$this->_ribbonXMLData)){
+				$ReturnData=$this->_ribbonXMLData[$What];
+			}//else $ReturnData stay at null
+			break;
+		}//default: $ReturnData at null
+		return $ReturnData;
+	}
+
+	/**
+	* store binaries ribbon objects (pictures)
+	*
+	*/
+	public function setRibbonBinObjects($BinObjectsNames=NULL, $BinObjectsData=NULL){
+		if(!is_null($BinObjectsNames) && !is_null($BinObjectsData)){
+			$this->_ribbonBinObjects=array('names'=>$BinObjectsNames, 'data'=>$BinObjectsData);
+		}else{
+			$this->_ribbonBinObjects=NULL;
+		}
+	}
+	/**
+	* return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function)
+	*
+	*/
+	private function _getExtensionOnly($ThePath){
+		return pathinfo($ThePath, PATHINFO_EXTENSION);
+	}
+
+	/**
+	* retrieve Binaries Ribbon Objects
+	*
+	*/
+	public function getRibbonBinObjects($What='all'){
+		$ReturnData=NULL;
+		$What=strtolower($What);
+		switch($What){
+		case 'all':
+			return $this->_ribbonBinObjects;
+			break;
+		case 'names':
+		case 'data':
+			if(is_array($this->_ribbonBinObjects) && array_key_exists($What, $this->_ribbonBinObjects)){
+				$ReturnData=$this->_ribbonBinObjects[$What];
+			}
+			break;
+		case 'types':
+			if(is_array($this->_ribbonBinObjects) && array_key_exists('data', $this->_ribbonBinObjects) && is_array($this->_ribbonBinObjects['data'])){
+				$tmpTypes=array_keys($this->_ribbonBinObjects['data']);
+				$ReturnData=array_unique(array_map(array($this,'_getExtensionOnly'), $tmpTypes));
+			}else
+				$ReturnData=array();//the caller want an array... not null if empty
+			break;
+		}
+		return $ReturnData;
+	}
+
+	/**
+	* This workbook have a custom UI ?
+	*
+	* @return true|false
+	*/
+	public function hasRibbon(){
+		return !is_null($this->_ribbonXMLData);
+	}
+
+	/**
+	* This workbook have additionnal object for the ribbon ?
+	*
+	* @return true|false
+	*/
+	public function hasRibbonBinObjects(){
+		return !is_null($this->_ribbonBinObjects);
+	}
+
+	/**
+     * Check if a sheet with a specified code name already exists
+     *
+     * @param string $pSheetCodeName  Name of the worksheet to check
+     * @return boolean
      */
-    public function __construct()
+    public function sheetCodeNameExists($pSheetCodeName)
     {
+		return ($this->getSheetByCodeName($pSheetCodeName) !== NULL);
+    }
+
+	/**
+	 * Get sheet by code name. Warning : sheet don't have always a code name !
+	 *
+	 * @param string $pName Sheet name
+	 * @return PHPExcel_Worksheet
+	 */
+	public function getSheetByCodeName($pName = '')
+	{
+		$worksheetCount = count($this->_workSheetCollection);
+		for ($i = 0; $i < $worksheetCount; ++$i) {
+			if ($this->_workSheetCollection[$i]->getCodeName() == $pName) {
+				return $this->_workSheetCollection[$i];
+			}
+		}
+
+		return null;
+	}
+
+	 /**
+	 * Create a new PHPExcel with one Worksheet
+	 */
+	public function __construct()
+	{
 		$this->_uniqueID = uniqid();
 		$this->_calculationEngine	= PHPExcel_Calculation::getInstance($this);
 
-        // Initialise worksheet collection and add one worksheet
-        $this->_workSheetCollection = array();
-        $this->_workSheetCollection[] = new PHPExcel_Worksheet($this);
-        $this->_activeSheetIndex = 0;
+		// Initialise worksheet collection and add one worksheet
+		$this->_workSheetCollection = array();
+		$this->_workSheetCollection[] = new PHPExcel_Worksheet($this);
+		$this->_activeSheetIndex = 0;
 
         // Create document properties
         $this->_properties = new PHPExcel_DocumentProperties();
diff --git a/Classes/PHPExcel/Reader/Excel2007.php b/Classes/PHPExcel/Reader/Excel2007.php
index 7ae0a99..8f58279 100644
--- a/Classes/PHPExcel/Reader/Excel2007.php
+++ b/Classes/PHPExcel/Reader/Excel2007.php
@@ -440,7 +440,13 @@ class PHPExcel_Reader_Excel2007 extends PHPExcel_Reader_Abstract implements PHPE
 						}
 					}
 				break;
-
+				//Ribbon
+				case "http://schemas.microsoft.com/office/2006/relationships/ui/extensibility":
+					$customUI = $rel['Target'];
+					if(!is_null($customUI)){
+						$this->_readRibbon($excel, $customUI, $zip);
+					}
+				break;
 				case "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument":
 					$dir = dirname($rel["Target"]);
 					$relsWorkbook = simplexml_load_string($this->_getFromZipArchive($zip, "$dir/_rels/" . basename($rel["Target"]) . ".rels"));  //~ http://schemas.openxmlformats.org/package/2006/relationships");
@@ -460,12 +466,30 @@ class PHPExcel_Reader_Excel2007 extends PHPExcel_Reader_Abstract implements PHPE
 					}
 
 					$worksheets = array();
+                    $macros = $customUI = NULL;
 					foreach ($relsWorkbook->Relationship as $ele) {
-						if ($ele["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet") {
+						switch($ele['Type']){
+						case "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet": 
 							$worksheets[(string) $ele["Id"]] = $ele["Target"];
+							break;
+						// a vbaProject ? (: some macros)
+						case "http://schemas.microsoft.com/office/2006/relationships/vbaProject":
+							$macros = $ele["Target"];
+							break;
 						}
 					}
 
+					if(!is_null($macros)){
+						$macrosCode = $this->_getFromZipArchive($zip, 'xl/vbaProject.bin');//vbaProject.bin always in 'xl' dir and always named vbaProject.bin
+						if($macrosCode !== false){
+							$excel->setMacrosCode($macrosCode);
+							$excel->setHasMacros(true);
+							//short-circuit : not reading vbaProject.bin.rel to get Signature =>allways vbaProjectSignature.bin in 'xl' dir
+							$Certificate = $this->_getFromZipArchive($zip, 'xl/vbaProjectSignature.bin');
+							if($Certificate !== false)
+								$excel->setMacrosCertificate($Certificate);
+						}
+					}
 					$styles 	= array();
 					$cellStyles = array();
 					$xpath = self::array_item($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']"));
@@ -684,7 +708,9 @@ class PHPExcel_Reader_Excel2007 extends PHPExcel_Reader_Abstract implements PHPE
 									$docSheet->getTabColor()->setARGB( (string)$xmlSheet->sheetPr->tabColor['rgb'] );
 								}
 							}
-
+							if (isset($xmlSheet->sheetPr) && isset($xmlSheet->sheetPr['codeName'])) {
+								$docSheet->setCodeName((string) $xmlSheet->sheetPr['codeName']);
+							}
 							if (isset($xmlSheet->sheetPr) && isset($xmlSheet->sheetPr->outlinePr)) {
 								if (isset($xmlSheet->sheetPr->outlinePr['summaryRight']) &&
 									!self::boolean((string) $xmlSheet->sheetPr->outlinePr['summaryRight'])) {
@@ -1942,7 +1968,44 @@ class PHPExcel_Reader_Excel2007 extends PHPExcel_Reader_Abstract implements PHPE
 		return $value;
 	}
 
-
+	private function _readRibbon($excel, $customUITarget, $zip)
+    {
+		$baseDir = dirname($customUITarget);
+		$nameCustomUI = basename($customUITarget);
+        // get the xml file (ribbon)
+		$localRibbon = $this->_getFromZipArchive($zip, $customUITarget);
+		$customUIImagesNames = array(); 
+        $customUIImagesBinaries = array();
+        // something like customUI/_rels/customUI.xml.rels
+		$pathRels = $baseDir . '/_rels/' . $nameCustomUI . '.rels';
+		$dataRels = $this->_getFromZipArchive($zip, $pathRels);
+		if ($dataRels) {
+            // exists and not empty if the ribbon have some pictures (other than internal MSO)
+			$UIRels = simplexml_load_string($dataRels);
+			if ($UIRels) {
+				// we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image
+				foreach ($UIRels->Relationship as $ele) {
+					if ($ele["Type"] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') {
+                        // an image ?
+						$customUIImagesNames[(string) $ele['Id']] = (string)$ele['Target'];
+						$customUIImagesBinaries[(string)$ele['Target']] = $this->_getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']);
+					}
+				}
+			}
+		}
+		if ($localRibbon) {
+			$excel->setRibbonXMLData($customUITarget, $localRibbon);
+			if (count($customUIImagesNames) > 0 && count($customUIImagesBinaries) > 0) {
+				$excel->setRibbonBinObjects($customUIImagesNames, $customUIImagesBinaries);
+			} else {
+				$excel->setRibbonBinObjects(NULL);
+			}
+		} else {
+			$excel->setRibbonXMLData(NULL);
+			$excel->setRibbonBinObjects(NULL);
+		}
+	}
+
 	private static function array_item($array, $key = 0) {
 		return (isset($array[$key]) ? $array[$key] : null);
 	}
diff --git a/Classes/PHPExcel/Worksheet.php b/Classes/PHPExcel/Worksheet.php
index 9d7a500..3fecc16 100644
--- a/Classes/PHPExcel/Worksheet.php
+++ b/Classes/PHPExcel/Worksheet.php
@@ -326,6 +326,13 @@ class PHPExcel_Worksheet implements PHPExcel_IComparable
     private $_hash    = null;
 
     /**
+    * CodeName
+    *
+    * @var string
+    */
+    private $_codeName = null;
+
+	/**
      * Create a new worksheet
      *
      * @param PHPExcel        $pParent
@@ -336,6 +343,8 @@ class PHPExcel_Worksheet implements PHPExcel_IComparable
         // Set parent and title
         $this->_parent = $pParent;
         $this->setTitle($pTitle, FALSE);
+        // setTitle can change $pTitle
+	    $this->setCodeName($this->getTitle());
         $this->setSheetState(PHPExcel_Worksheet::SHEETSTATE_VISIBLE);
 
         $this->_cellCollection        = PHPExcel_CachedObjectStorageFactory::getInstance($this);
@@ -417,6 +426,34 @@ class PHPExcel_Worksheet implements PHPExcel_IComparable
     }
 
     /**
+     * Check sheet code name for valid Excel syntax
+     *
+     * @param string $pValue The string to check
+     * @return string The valid string
+     * @throws Exception
+     */
+    private static function _checkSheetCodeName($pValue)
+    {
+        $CharCount = PHPExcel_Shared_String::CountCharacters($pValue);
+        if ($CharCount == 0) {
+            throw new PHPExcel_Exception('Sheet code name cannot be empty.');
+        }
+        // Some of the printable ASCII characters are invalid:  * : / \ ? [ ] and  first and last characters cannot be a "'"
+        if ((str_replace(self::$_invalidCharacters, '', $pValue) !== $pValue) || 
+            (PHPExcel_Shared_String::Substring($pValue,-1,1)=='\'') || 
+            (PHPExcel_Shared_String::Substring($pValue,0,1)=='\'')) {
+            throw new PHPExcel_Exception('Invalid character found in sheet code name');
+        }
+ 
+        // Maximum 31 characters allowed for sheet title
+        if ($CharCount > 31) {
+            throw new PHPExcel_Exception('Maximum 31 characters allowed in sheet code name.');
+        }
+ 
+        return $pValue;
+    }
+
+   /**
      * Check sheet title for valid Excel syntax
      *
      * @param string $pValue The string to check
@@ -2802,4 +2839,69 @@ class PHPExcel_Worksheet implements PHPExcel_IComparable
             }
         }
     }
+/**
+	 * Define the code name of the sheet
+	 *
+	 * @param null|string Same rule as Title minus space not allowed (but, like Excel, change silently space to underscore)
+	 * @return objWorksheet
+	 * @throws PHPExcel_Exception
+	*/
+	public function setCodeName($pValue=null){
+		// Is this a 'rename' or not?
+		if ($this->getCodeName() == $pValue) {
+			return $this;
+		}
+		$pValue = str_replace(' ', '_', $pValue);//Excel does this automatically without flinching, we are doing the same
+		// Syntax check
+        // throw an exception if not valid
+		self::_checkSheetCodeName($pValue);
+
+		// We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
+		
+        if ($this->getParent()) {
+			// Is there already such sheet name?
+			if ($this->getParent()->sheetCodeNameExists($pValue)) {
+				// Use name, but append with lowest possible integer
+
+				if (PHPExcel_Shared_String::CountCharacters($pValue) > 29) {
+					$pValue = PHPExcel_Shared_String::Substring($pValue,0,29);
+				}
+				$i = 1;
+				while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) {
+					++$i;
+					if ($i == 10) {
+						if (PHPExcel_Shared_String::CountCharacters($pValue) > 28) {
+							$pValue = PHPExcel_Shared_String::Substring($pValue,0,28);
+						}
+					} elseif ($i == 100) {
+						if (PHPExcel_Shared_String::CountCharacters($pValue) > 27) {
+							$pValue = PHPExcel_Shared_String::Substring($pValue,0,27);
+						}
+					}
+				}
+
+				$pValue = $pValue . '_' . $i;// ok, we have a valid name
+				//codeName is'nt used in formula : no need to call for an update
+				//return $this->setTitle($altTitle,$updateFormulaCellReferences);
+			}
+		}
+
+		$this->_codeName=$pValue;
+		return $this;
+	}
+	/**
+	 * Return the code name of the sheet
+	 *
+	 * @return null|string
+	*/
+	public function getCodeName(){
+		return $this->_codeName;
+	}
+	/**
+	 * Sheet has a code name ?
+	 * @return boolean
+	*/
+	public function hasCodeName(){
+		return !(is_null($this->_codeName));
+	}
 }
diff --git a/Classes/PHPExcel/Writer/Excel2007.php b/Classes/PHPExcel/Writer/Excel2007.php
index 4e93201..5ff98f2 100644
--- a/Classes/PHPExcel/Writer/Excel2007.php
+++ b/Classes/PHPExcel/Writer/Excel2007.php
@@ -144,6 +144,8 @@ class PHPExcel_Writer_Excel2007 extends PHPExcel_Writer_Abstract implements PHPE
 									'drawing' 		=> 'PHPExcel_Writer_Excel2007_Drawing',
 									'comments' 		=> 'PHPExcel_Writer_Excel2007_Comments',
 									'chart'			=> 'PHPExcel_Writer_Excel2007_Chart',
+									'relsvba'		=> 'PHPExcel_Writer_Excel2007_RelsVBA',
+									'relsribbonobjects' => 'PHPExcel_Writer_Excel2007_RelsRibbon'
 								 );
 
     	//	Initialise writer parts
@@ -243,6 +245,35 @@ class PHPExcel_Writer_Excel2007 extends PHPExcel_Writer_Abstract implements PHPE
 			// Add [Content_Types].xml to ZIP file
 			$objZip->addFromString('[Content_Types].xml', 			$this->getWriterPart('ContentTypes')->writeContentTypes($this->_spreadSheet, $this->_includeCharts));
 
+			//if hasMacros, add the vbaProject.bin file, Certificate file(if exists)
+			if($this->_spreadSheet->hasMacros()){
+				$macrosCode=$this->_spreadSheet->getMacrosCode();
+				if(!is_null($macrosCode)){// we have the code ?
+					$objZip->addFromString('xl/vbaProject.bin', $macrosCode);//allways in 'xl', allways named vbaProject.bin
+					if($this->_spreadSheet->hasMacrosCertificate()){//signed macros ?
+						// Yes : add the certificate file and the related rels file
+						$objZip->addFromString('xl/vbaProjectSignature.bin', $this->_spreadSheet->getMacrosCertificate());
+						$objZip->addFromString('xl/_rels/vbaProject.bin.rels',
+							$this->getWriterPart('RelsVBA')->writeVBARelationships($this->_spreadSheet));
+					}
+				}
+			}
+			//a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels)
+			if($this->_spreadSheet->hasRibbon()){
+				$tmpRibbonTarget=$this->_spreadSheet->getRibbonXMLData('target');
+				$objZip->addFromString($tmpRibbonTarget, $this->_spreadSheet->getRibbonXMLData('data'));
+				if($this->_spreadSheet->hasRibbonBinObjects()){
+					$tmpRootPath=dirname($tmpRibbonTarget).'/';
+					$ribbonBinObjects=$this->_spreadSheet->getRibbonBinObjects('data');//the files to write
+					foreach($ribbonBinObjects as $aPath=>$aContent){
+						$objZip->addFromString($tmpRootPath.$aPath, $aContent);
+					}
+					//the rels for files
+					$objZip->addFromString($tmpRootPath.'_rels/'.basename($tmpRibbonTarget).'.rels',
+						$this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->_spreadSheet));
+				}
+			}
+			
 			// Add relationships to ZIP file
 			$objZip->addFromString('_rels/.rels', 					$this->getWriterPart('Rels')->writeRelationships($this->_spreadSheet));
 			$objZip->addFromString('xl/_rels/workbook.xml.rels', 	$this->getWriterPart('Rels')->writeWorkbookRelationships($this->_spreadSheet));
diff --git a/Classes/PHPExcel/Writer/Excel2007/ContentTypes.php b/Classes/PHPExcel/Writer/Excel2007/ContentTypes.php
index 9fdc9dd..b19b4cd 100644
--- a/Classes/PHPExcel/Writer/Excel2007/ContentTypes.php
+++ b/Classes/PHPExcel/Writer/Excel2007/ContentTypes.php
@@ -86,9 +86,26 @@ class PHPExcel_Writer_Excel2007_ContentTypes extends PHPExcel_Writer_Excel2007_W
 			);
 
 			// Workbook
-			$this->_writeOverrideContentType(
-				$objWriter, '/xl/workbook.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'
-			);
+			if($pPHPExcel->hasMacros()){ //Macros in workbook ?
+				// Yes : not standard content but "macroEnabled"
+				$this->_writeOverrideContentType(
+					$objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml'
+				);
+				//... and define a new type for the VBA project
+				$this->_writeDefaultContentType(
+							$objWriter, 'bin', 'application/vnd.ms-office.vbaProject'
+						);
+				if($pPHPExcel->hasMacrosCertificate()){// signed macros ?
+					// Yes : add needed information
+					$this->_writeOverrideContentType(
+						$objWriter, '/xl/vbaProjectSignature.bin', 'application/vnd.ms-office.vbaProjectSignature'
+				);
+				}
+			}else{// no macros in workbook, so standard type
+				$this->_writeOverrideContentType(
+					$objWriter, '/xl/workbook.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'
+				);
+			}
 
 			// DocProps
 			$this->_writeOverrideContentType(
@@ -178,7 +195,16 @@ class PHPExcel_Writer_Excel2007_ContentTypes extends PHPExcel_Writer_Excel2007_W
 						);
 				}
 			}
-
+			if($pPHPExcel->hasRibbonBinObjects()){//Some additional objects in the ribbon ?
+				//we need to write "Extension" but not already write for media content
+				$tabRibbonTypes=array_diff($pPHPExcel->getRibbonBinObjects('types'), array_keys($aMediaContentTypes));
+				foreach($tabRibbonTypes as $aRibbonType){
+					$mimeType='image/.'.$aRibbonType;//we wrote $mimeType like customUI Editor
+					$this->_writeDefaultContentType(
+						$objWriter, $aRibbonType, $mimeType
+					);
+				}	
+			}
 			$sheetCount = $pPHPExcel->getSheetCount();
 			for ($i = 0; $i < $sheetCount; ++$i) {
 				if (count($pPHPExcel->getSheet()->getHeaderFooter()->getImages()) > 0) {
diff --git a/Classes/PHPExcel/Writer/Excel2007/Rels.php b/Classes/PHPExcel/Writer/Excel2007/Rels.php
index fa225ee..87403b6 100644
--- a/Classes/PHPExcel/Writer/Excel2007/Rels.php
+++ b/Classes/PHPExcel/Writer/Excel2007/Rels.php
@@ -94,6 +94,15 @@ class PHPExcel_Writer_Excel2007_Rels extends PHPExcel_Writer_Excel2007_WriterPar
 				'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
 				'xl/workbook.xml'
 			);
+			// a custom UI in workbook ?
+			if($pPHPExcel->hasRibbon()){
+				$this->_writeRelationShip(
+					$objWriter,
+					5,
+					'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility',
+					$pPHPExcel->getRibbonXMLData('target')
+				);
+			}
 
 		$objWriter->endElement();
 
@@ -159,6 +168,17 @@ class PHPExcel_Writer_Excel2007_Rels extends PHPExcel_Writer_Excel2007_WriterPar
 					'worksheets/sheet' . ($i + 1) . '.xml'
 				);
 			}
+			// Relationships for vbaProject if needed
+			// id : just after the last sheet
+			if($pPHPExcel->hasMacros()){
+				$this->_writeRelationShip(
+					$objWriter,
+					($i + 1 + 3),
+					'http://schemas.microsoft.com/office/2006/relationships/vbaProject',
+					'vbaProject.bin'
+				);
+				++$i;//increment i if needed for an another relation
+			}
 
 		$objWriter->endElement();
 
diff --git a/Classes/PHPExcel/Writer/Excel2007/RelsRibbon.php b/Classes/PHPExcel/Writer/Excel2007/RelsRibbon.php
new file mode 100644
index 0000000..47b75a3
--- /dev/null
+++ b/Classes/PHPExcel/Writer/Excel2007/RelsRibbon.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * PHPExcel
+ *
+ * Copyright (c) 2006 - 2013 PHPExcel
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category   PHPExcel
+ * @package    PHPExcel_Writer_Excel2007
+ * @copyright  Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt	LGPL
+ * @version     ##VERSION##, ##DATE##
+ */
+
+
+/**
+ * PHPExcel_Writer_Excel2007_RelsRibbon
+ *
+ * @category   PHPExcel
+ * @package    PHPExcel_Writer_Excel2007
+ * @copyright  Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
+ */
+class PHPExcel_Writer_Excel2007_RelsRibbon extends PHPExcel_Writer_Excel2007_WriterPart
+{
+	/**
+	 * Write relationships for additional objects of custom UI (ribbon)
+	 *
+	 * @param 	PHPExcel	$pPHPExcel
+	 * @return 	string 		XML Output
+	 * @throws 	PHPExcel_Writer_Exception
+	 */
+	public function writeRibbonRelationships(PHPExcel $pPHPExcel = null){
+		// Create XML writer
+		$objWriter = null;
+		if ($this->getParentWriter()->getUseDiskCaching()) {
+			$objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+		} else {
+			$objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_MEMORY);
+		}
+
+		// XML header
+		$objWriter->startDocument('1.0','UTF-8','yes');
+
+		// Relationships
+		$objWriter->startElement('Relationships');
+		$objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+		$localRels=$pPHPExcel->getRibbonBinObjects('names');
+		if(is_array($localRels)){
+			foreach($localRels as $aId=>$aTarget){
+				$objWriter->startElement('Relationship');
+				$objWriter->writeAttribute('Id', $aId);
+				$objWriter->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image');
+				$objWriter->writeAttribute('Target', $aTarget);
+				$objWriter->endElement();//Relationship
+			}
+		}
+		$objWriter->endElement();//Relationships
+
+		// Return
+		return $objWriter->getData();
+
+	}
+
+}
diff --git a/Classes/PHPExcel/Writer/Excel2007/RelsVBA.php b/Classes/PHPExcel/Writer/Excel2007/RelsVBA.php
new file mode 100644
index 0000000..60833e1
--- /dev/null
+++ b/Classes/PHPExcel/Writer/Excel2007/RelsVBA.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * PHPExcel
+ *
+ * Copyright (c) 2006 - 2013 PHPExcel
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * @category   PHPExcel
+ * @package    PHPExcel_Writer_Excel2007
+ * @copyright  Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt	LGPL
+ * @version     ##VERSION##, ##DATE##
+ */
+
+
+/**
+ * PHPExcel_Writer_Excel2007_RelsVBA
+ *
+ * @category   PHPExcel
+ * @package    PHPExcel_Writer_Excel2007
+ * @copyright  Copyright (c) 2006 - 2013 PHPExcel (http://www.codeplex.com/PHPExcel)
+ */
+class PHPExcel_Writer_Excel2007_RelsVBA extends PHPExcel_Writer_Excel2007_WriterPart
+{
+	/**
+	 * Write relationships for a signed VBA Project
+	 *
+	 * @param 	PHPExcel	$pPHPExcel
+	 * @return 	string 		XML Output
+	 * @throws 	PHPExcel_Writer_Exception
+	 */
+	public function writeVBARelationships(PHPExcel $pPHPExcel = null){
+		// Create XML writer
+		$objWriter = null;
+		if ($this->getParentWriter()->getUseDiskCaching()) {
+			$objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+		} else {
+			$objWriter = new PHPExcel_Shared_XMLWriter(PHPExcel_Shared_XMLWriter::STORAGE_MEMORY);
+		}
+
+		// XML header
+		$objWriter->startDocument('1.0','UTF-8','yes');
+
+		// Relationships
+		$objWriter->startElement('Relationships');
+		$objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+		$objWriter->startElement('Relationship');
+		$objWriter->writeAttribute('Id', 'rId1');
+		$objWriter->writeAttribute('Type', 'http://schemas.microsoft.com/office/2006/relationships/vbaProjectSignature');
+		$objWriter->writeAttribute('Target', 'vbaProjectSignature.bin');
+		$objWriter->endElement();//Relationship
+		$objWriter->endElement();//Relationships
+
+		// Return
+		return $objWriter->getData();
+
+	}
+
+}
diff --git a/Classes/PHPExcel/Writer/Excel2007/Worksheet.php b/Classes/PHPExcel/Writer/Excel2007/Worksheet.php
index f70d4fe..8ca33aa 100644
--- a/Classes/PHPExcel/Writer/Excel2007/Worksheet.php
+++ b/Classes/PHPExcel/Writer/Excel2007/Worksheet.php
@@ -148,6 +148,12 @@ class PHPExcel_Writer_Excel2007_Worksheet extends PHPExcel_Writer_Excel2007_Writ
 		// sheetPr
 		$objWriter->startElement('sheetPr');
 		//$objWriter->writeAttribute('codeName',		$pSheet->getTitle());
+		if($pSheet->getParent()->hasMacros()){//if the workbook have macros, we need to have codeName for the sheet
+			if($pSheet->hasCodeName()==false){
+				$pSheet->setCodeName($pSheet->getTitle());
+			}
+			$objWriter->writeAttribute('codeName',		$pSheet->getCodeName());
+		}
 			$autoFilterRange = $pSheet->getAutoFilter()->getRange();
 			if (!empty($autoFilterRange)) {
 				$objWriter->writeAttribute('filterMode', 1);
diff --git a/changelog.txt b/changelog.txt
index 2f5bb06..deeb03b 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -38,6 +38,7 @@ Fixed in develop branch for release v1.8.0:
 - Feature:  (MBaker)                            - Added "Quote Prefix" to style settings (Excel2007 Reader and Writer only)
 - Feature:  (MBaker)                            - Added Horizontal FILL alignment for Excel5 and Excel2007 Readers/Writers, and Horizontal DISTRIBUTED alignment for Excel2007 Reader/Writer
 - Feature:  (trvrnrth)                          - Add support for reading protected (RC4 encrypted) .xls files (64-bit Linux only)
+- Feature:  (LWol)            Work Item GH-252  - Adding support for macros, Ribbon in Excel 2007
 - General:  (cdhutch)         Work item 20055   - Remove array_shift in ReferenceHelper::insertNewBefore improves column or row delete speed
 - General:  (MBaker)                            - Improve stock chart handling and rendering, with help from Swashata Ghosh
 - General:  (MBaker)                            - Fix to calculation properties for Excel2007 so that the opening application will only recalculate on load if it's actually required