Location: PHPKode > projects > AModules3 > amodules-3.0.1/mini-apps/03-DocAssistant/page/GetStructure.php
<?php
class page_GetStructure extends Page{
	private $structure;
	private $project_id;
	private $version_id=null;
	private $in_comment=false;
	private $comment;
	private $lines_from_comment;
	private $lines_from_declaration;
	/**
	 * Whether or not to collect a description after class declaration
	 */
	private $desc_class_after = false;
	/**
	 * Whether or not to collect a description after method declaration
	 */
	private $desc_method_after = false;
	/**
	 * Whether or not to parse functions that are not belonging to any class
	 */
	private $parse_non_class = false;
	
	function init(){
		parent::init();
		$this->project_id=$_GET['id'];
		$this->desc_class_after=$_REQUEST['desc_class_after']=='Y'||$_GET['desc_class_after']=='Y';
		$this->desc_method_after=$_REQUEST['desc_method_after']=='Y'||$_GET['desc_method_after']=='Y';;
		$this->parse_non_class=$_REQUEST['parse_non_class']=='Y'||$_GET['parse_non_class']=='Y';;
		$this->template->set('projectname', $this->api->getProjectName($this->project_id));
		$progress=$this->add('Text', null, 'Content');
		$progress->set("<div id=state>Parsing of application source code is in progress." .
			"Please be patient.</div><img src=templates/pixel.gif onLoad=\"aasn('progress', '".
			$this->api->getDestinationURL(null,array('cut_object'=>$progress->name, 
				'desc_class_after'=>$_REQUEST['desc_class_after'],
				'desc_method_after'=>$_REQUEST['desc_method_after'],
				'parse_non_class'=>$_REQUEST['parse_non_class'],
				'id'=>$this->project_id,
				'do_parse'=>1))."');spinner_on('progress')\">" .
			"<div id=progress width=50%>Here should be a progress info</div>")
		;
		$this->add('Ajax')->useProgressIndicator('progress');
		if($_GET['do_parse']){
			echo "<h3>Parse report:</h3>";
			//parsing all the files recoursively
			$this->parseAll($this->api->getProjectPath($this->project_id));
			//building class structure
			$this->buildClassStructure();
			//redirecting to projects
			//$this->api->redirect('Projects');
		}
	}
	function defaultTemplate(){
		return array('getstructure', '_top');
	}
	function buildClassStructure(){
		/**
		 * This method changes classes parents depending on their names
		 */
		$classes=$this->api->db->getAllHash("select * from member where project_id=$this->project_id" .
				" and type='class' and version_id=".$this->getVersionId());
		foreach($classes as $class){
			$parent_id=$this->api->db->getOne
				("select id from member where name='".$class['extends']."' and project_id=$this->project_id");
			if($parent_id!='')$this->api->db->query("update member set parent_id=$parent_id where id=".$class['id']);
		}
	}
	function parseAll($path){
		$omit_dirs=$this->api->getConfig('parsing/omit_dirs', array('.', '..', '.svn'));
		//echo "Entering parseAll<br>";
		//if($this->parse_non_class)echo "Parsing all the content<br>";
		if ($handle=opendir($path)) {
			if(substr($path, -1)==DIRECTORY_SEPARATOR)$path=substr($path, 0, -1);
			//echo $path;
			while(($file=readdir($handle))!==false){
				if(array_search($file, $omit_dirs)!==false)continue;
				$file=$path.DIRECTORY_SEPARATOR.$file;
				if(is_dir($file))$this->parseAll($file);
				if(strpos($file, '.php', strlen($file)-5)!==false){
					//echo "parsing $file...<br>";
					echo "<p>$file</p>";
					$this->parseFile($file);
					//for testing purposes stopping after the first file
					//if($this->structure)return;
				}
			}
			closedir($handle);
		}
	}
	function parseFile($filename){
		$lines=file($filename);
		$class_id=null;
		$func_id=null;
		$class_index="";
		$prev_item=null;//a pointer just added item; for comment-after-declaration set
		$pcount=0; //'{}'-bracket count
		foreach($lines as $no=>$orig_line){
			$item=array();
			//we need to discover the line type...
			$line=$this->stripComments($orig_line);
			if($this->in_comment){
				//collecting comments, they could be useful
			}else{
				//if there is trigger_error - remainder of the file is obsolete
				if($class_id==null&&$func_id==null&&strpos($line, 'trigger_error')!==false)return;
				/*
				 * setting comment that is AFTER member declaration
				 * such comments are only for classes and functions and are set if new member haven't
				 * been matched
				 */
				if($prev_item!=null&&$prev_item['type']!='property'&&$this->comment!=''){
					$this->setItemDescription($prev_item);
				}
				$item['declaration']=$this->stripSyntax($line);
				$item['file']=$filename;
				$item['line']=$no+1;
				//////////////// Class parsing //////////////// 
				if(strpos($line, 'class ')!==false&&$class_id==null){
					$this->lines_from_declaration=0;
					//setting a comment if it was collected
					if($this->comment!='')$this->setItemDescription($item);
					//parsing a class
					$first_space=strpos($line, ' ', strpos($line, 'class')+1)+1;
					$second_space=strpos($line, ' ', $first_space);
					$second_space=$second_space==0?strlen($line):$second_space;
					$item['name']=substr($line, $first_space, $second_space-$first_space);
					$item['type']='class';
					$item['visibility']=$this->getVisibility($line);
					$extends=strpos($line, 'extends');
					if($extends!==false){
						$extends=trim(substr($line, strpos($line, ' ', $extends+1)));
						$item['extends']=$this->stripSyntax($extends);
					}
					$class_index=$item['name'];
					$class_id=$this->dbAddMember($item);
					$this->structure[$class_index]=$item;
					$prev_item=&$this->structure[$class_index];
				}
				//////////////// Method parsing //////////////// 
				elseif(strpos($line, 'function')!==false&&$func_id==null&&
				($class_id!=null||$this->parse_non_class)){
					$this->lines_from_declaration=0;
					//setting a comment if it was collected
					if($this->comment!='')$this->setItemDescription($item, $this->comment);
					//parsing a method
					$first_space=strpos($line, ' ', strpos($line, 'function')+1)+1;
					$second_space=strpos($line, '(');
					if($second_space!==false)$item['name']=substr($line, $first_space, $second_space-$first_space);
					$item['type']='method';
					$item['visibility']=$this->getVisibility($line);
					$func_id=$this->dbAddMember($item, $class_id);
					$this->structure[$class_index]['methods'][$item['name']]=$item;
					$prev_item=&$this->structure[$class_index]['methods'][$item['name']];
				}
				//////////////// Property parsing //////////////// 
				elseif($func_id==null&&(strpos($line, 'private')!==false||strpos($line, 'public')!==false||
				strpos($line, 'protected')!==false||strpos($line, 'var')!==false)&&
				($class_id!=null||$this->parse_non_class)){
					$this->lines_from_declaration=0;
					//property's comment is always before it
					if($this->comment!='')$this->setItemDescription($item);
					$first_space=strpos($line, ' ')+1;
					//parsing a property
					$item['name']=trim(substr($line, $first_space, strpos($line, ';')-$first_space));
					$item['type']='property';
					$item['visibility']=$this->getVisibility($line);
					$this->dbAddMember($item, $class_id);
					$this->structure[$class_index]['properties'][$item['name']]=$item;
					$prev_item=&$this->structure[$class_index]['properties'][$item['name']];
				}
				$this->lines_from_declaration++;
				if(strpos($line, '{')!==false){
					$pcount++;
				}
				if(strpos($line, '}')!==false){
					//closing opened item
					if($pcount>0)$pcount--;
					if($pcount==1){
						$func_id=null;
						//unsetting prev_item if it was a function
						if($prev_item['type']=='method')$prev_item=null;
					}
					if($pcount==0){
						$class_id=null;
						$class_index=null;
						//unsetting prev_item cuase all the comments will be for the next class
						$prev_item=null;
					}
					//else throw new BaseException("Something goes wrong");
				}
				if($this->lines_from_comment>$this->api->getConfig('parsing/comment_range'))$this->comment='';
				else $this->lines_from_comment++;
			}
			//echo "{$item['name']}: $class_id / $func_id / {$item['description']} / $no : $pcount<br>";
		}
	}
	private function setItemDescription(&$item){
		if($this->lines_from_declaration <= $this->api->getConfig('parsing/comment_range')){
			if(!isset($item['id'])||(($item['type']=='class'&&$this->desc_class_after)||
			($item['type']=='method'&&$this->desc_method_after))){
				if($item['description']=='')$item['description']=trim($this->comment);
				$this->comment='';
			}
			//updating only if it is an existing item
			if($item['id']!='')$this->dbUpdateMember($item);
		}
		if($item['id']!='')$item=null;
	}
	private function setInComment($in_comment){
		//erasing comment if reached a new one
		if($in_comment)$this->comment="";
		else $this->lines_from_comment=0;
		$this->in_comment=$in_comment;
	}
	/**
	 * Strips brackets, commas, semicolons, etc
	 */
	private function stripSyntax($line){
		$bracket_start=strpos($line, '{');
		$bracket_end=strpos($line, '}');
		$code=$bracket_start!==false?
			substr($line, $bracket_start, $bracket_end===false?strlen($line):$bracket_end):
			'';
		if($code!='')$line=str_replace($code, '', $line);
		$line=str_replace(';', '', $line);
		//$line=str_replace(',', '', $line);
		return $line;
	}
	/**
	 * strips all types of comments from the given line
	 */
	private function stripComments($line){
		$result=trim($line);
		//if(strpos($result, '//')!==false)$result=substr($result, 0, strpos($result, '//'));
		$comment_start=strpos($result, '//');
		$comment_end=false;
		if($comment_start===false){
			$comment_start=strpos($result, '/*');
			//this should be now
			if($comment_start!==false)$this->setInComment(true);
			if(strpos($result, '*')===0)$comment_start=0;
			$comment_end=strpos($result, '*/');
			//this should be now
			if($comment_end!==false)$this->setInComment(false);
		}else $this->lines_from_comment=0;
		if($comment_start!==false){
			$comment=substr($result, $comment_start, $comment_end===false?strlen($result):$comment_end);
			$result=str_replace($comment, '', $result);
			$comment=substr($comment, strpos($comment, '//')!==false?2:0);
			$comment=substr($comment, strpos($comment, '/**')!==false?3:0);
			$comment=substr($comment, strpos($comment, '/*')!==false?2:0);
			$comment=substr($comment, strpos($comment, '*')===0?1:0);
			$this->comment.=$comment."\n";
		}elseif($comment_end!==false){
			//we have a comment closure. returning string part after it
			$result=substr($result, $comment_end + 2);
		}
		return trim($result);
	}
	private function getVisibility($str){
		switch(substr($str, 0, strpos($str, ' '))){
			case 'private':return 'private';
			case 'protected':return 'protected';
			default:return 'public';
		}
	}
	private function dbAddClass(&$item){
		$this->api->db->query("insert into class values (0, $this->project_id, " .
				"'{$item['name']}', null, '{$item['declaration']}', '{$item['description']}', ".
				$this->getVersionId().")");
		$item['id']=mysql_insert_id();
		return $item['id'];
	}
	private function dbUpdateMember($item){
		$this->formatItem($item);
		$this->api->db->query("update member set " .
			" description='".$item['description']."'".
			" where id = ".$item['id']
		);
	}
	private function dbAddMember(&$item, $class_id=null){
		if(is_null($class_id))$class_id='null';
		if($item['name']=='')throw new BaseException("Member name is empty! Details: " .
				"<br>file: ".$item['file'].
				"<br>line: ".$item['line']."<br>declaration: ".$item['declaration']);
		$this->formatItem($item);
		$this->api->db->query("insert into member values (0, $this->project_id, $class_id, " .
				"'{$item['name']}', '{$item['extends']}', '{$item['declaration']}', '{$item['description']}', ".
				$this->getVersionId().", '{$item['type']}', '{$item['visibility']}', " .
				"'".addslashes($item['file'])."', {$item['line']})");
		$item['id']=mysql_insert_id();
		return $item['id'];
	}
	private function formatItem(&$item){
		foreach($item as $key=>$value){
			$item[$key]=str_replace("'", "\"", $value);
		}
	}
	private function getVersionId(){
		if(is_null($this->version_id)){
			$this->version_id=$this->api->getProjectVersion($this->project_id);
			if($this->version_id==0){
				$this->api->db->query("insert into version values (0, 'auto add', SYSDATE(), $this->project_id)");
				$this->version_id=mysql_insert_id();
			}else{
				//cleaning data
				$this->api->db->query("delete from member where version_id=$this->version_id");
			}
		}
		return $this->version_id;
	}
}
Return current item: AModules3