Compare commits
	
		
			77 Commits
		
	
	
		
			feature.do
			...
			feature.is
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					813184f06c | ||
| 
						 | 
					d860f299fd | ||
| 
						 | 
					33882d4fa7 | ||
| 
						 | 
					13fad756ab | ||
| 
						 | 
					1f0791df0e | ||
| 
						 | 
					a2c832a130 | ||
| 
						 | 
					b17de014ec | ||
| 
						 | 
					b1b72190e1 | ||
| 
						 | 
					2ff2f888bc | ||
| 
						 | 
					57c2389aae | ||
| 
						 | 
					d54c86f813 | ||
| 
						 | 
					05a9697007 | ||
| 
						 | 
					945429abf0 | ||
| 
						 | 
					a016bcb51b | ||
| 
						 | 
					f2b1ce795c | ||
| 
						 | 
					3a8c56acc4 | ||
| 
						 | 
					7b2552f940 | ||
| 
						 | 
					324b202215 | ||
| 
						 | 
					2c2da6082a | ||
| 
						 | 
					dd3fbbd7e4 | ||
| 
						 | 
					9bbcd571ec | ||
| 
						 | 
					bbc185bf3b | ||
| 
						 | 
					d1bcdcda20 | ||
| 
						 | 
					6d55602ef3 | ||
| 
						 | 
					6e7c9f7c4b | ||
| 
						 | 
					5427aab456 | ||
| 
						 | 
					4879d64989 | ||
| 
						 | 
					dab8ea63fc | ||
| 
						 | 
					b03d7a04a0 | ||
| 
						 | 
					ef5b93e3f7 | ||
| 
						 | 
					69ae1c08ef | ||
| 
						 | 
					8e4f828cc6 | ||
| 
						 | 
					c4f92f4569 | ||
| 
						 | 
					c4d2b99656 | ||
| 
						 | 
					d4fe88adab | ||
| 
						 | 
					69d0e8313a | ||
| 
						 | 
					11a234e135 | ||
| 
						 | 
					2f30e4e2f6 | ||
| 
						 | 
					118ca9f11f | ||
| 
						 | 
					24fc41ee0d | ||
| 
						 | 
					c00dbac5e0 | ||
| 
						 | 
					ac6be0d3c0 | ||
| 
						 | 
					7ff6f09f67 | ||
| 
						 | 
					00b576c5a3 | ||
| 
						 | 
					2a33510c96 | ||
| 
						 | 
					d1f79d906d | ||
| 
						 | 
					aa043f14ac | ||
| 
						 | 
					638b28399e | ||
| 
						 | 
					1d86f036a3 | ||
| 
						 | 
					2f6e0f0a22 | ||
| 
						 | 
					1b1b00a10c | ||
| 
						 | 
					8693418d39 | ||
| 
						 | 
					8ff15368ce | ||
| 
						 | 
					32cde534bd | ||
| 
						 | 
					c0e26133bd | ||
| 
						 | 
					592c2ff9ff | ||
| 
						 | 
					30efd0a2db | ||
| 
						 | 
					20c3f14cc8 | ||
| 
						 | 
					80313c88c8 | ||
| 
						 | 
					cab1c09ffc | ||
| 
						 | 
					c421919092 | ||
| 
						 | 
					d350876bc1 | ||
| 
						 | 
					de09c8af56 | ||
| 
						 | 
					998f4576fe | ||
| 
						 | 
					2aab4eea3b | ||
| 
						 | 
					5bbff9f5a6 | ||
| 
						 | 
					b5fcf1636a | ||
| 
						 | 
					13012be5d7 | ||
| 
						 | 
					ca2ef814fb | ||
| 
						 | 
					9bcb5f9456 | ||
| 
						 | 
					f412099f69 | ||
| 
						 | 
					0aa5999bb3 | ||
| 
						 | 
					16dda0743c | ||
| 
						 | 
					bcba64b2a1 | ||
| 
						 | 
					e40d922eef | ||
| 
						 | 
					7e226b43d3 | ||
| 
						 | 
					9171bfd1ab | 
							
								
								
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								AUTHORS
									
									
									
									
									
								
							@@ -12,6 +12,7 @@ Much appreciated contributors (in alphabetical order):
 | 
			
		||||
    Brian Armstrong <brianar>
 | 
			
		||||
    Charles Melbye <charlie@yourwiki.net>
 | 
			
		||||
    Ciaran Gultnieks <ciaran@ciarang.com>
 | 
			
		||||
    Daniel Steudler <steudlerdaniel@gmail.com>
 | 
			
		||||
    David Feeney <davidf>
 | 
			
		||||
    Denis Kot <denis.kot@gmail.com>                 - Russian translation
 | 
			
		||||
    Dmitry Dulepov <dmitryd>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								Makefile
									
									
									
									
									
								
							@@ -37,6 +37,10 @@ help:
 | 
			
		||||
	@printf "\tpo-push    - Send the all PO files to the transifex server.\n"
 | 
			
		||||
	@printf "\tpo-pull    - Get all PO files from the transifex server.\n"
 | 
			
		||||
	@printf "\tpo-stats   - Show translation statistics of all PO files.\n"
 | 
			
		||||
	@printf "\nMisc Rules :\n";
 | 
			
		||||
	@printf "\tdb-install - Install the database schema.\n"
 | 
			
		||||
	@printf "\tdb-update  - Update the database schema.\n"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
#   Internationalization rule, POT & PO file manipulation
 | 
			
		||||
@@ -139,3 +143,8 @@ po-stats:
 | 
			
		||||
		> indefero-$(@:-zipfile=)-`git log $(@:-zipfile=) -n 1 \
 | 
			
		||||
		--pretty=format:%h`.zip
 | 
			
		||||
 | 
			
		||||
db-install:
 | 
			
		||||
	@cd src && php $(PLUF_PATH)/migrate.php --conf=IDF/conf/idf.php -a -d -i
 | 
			
		||||
 | 
			
		||||
db-update:
 | 
			
		||||
	@cd src && php $(PLUF_PATH)/migrate.php --conf=IDF/conf/idf.php -a -d
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								NEWS.mdtext
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								NEWS.mdtext
									
									
									
									
									
								
							@@ -1,20 +1,50 @@
 | 
			
		||||
# InDefero 1.2 - xxx xxx xx xx:xx 2011 UTC
 | 
			
		||||
 | 
			
		||||
ATTENTION: You need Pluf [324ae60b](http://projects.ceondo.com/p/pluf/source/commit/324ae60b)
 | 
			
		||||
or newer to properly run this version of Indefero!
 | 
			
		||||
 | 
			
		||||
## New Features
 | 
			
		||||
 | 
			
		||||
- Indefero's issue tracker can now bi-directionally link issues with variable, configurable
 | 
			
		||||
  terms, such as "is related to", "is blocked by" or "is duplicated by" (issue 638)
 | 
			
		||||
- Mercurial source views now show parent revisions (if any) and detailed change information
 | 
			
		||||
- File download URLs now contain the file name rather than the upload id; old links still work though (issue 686)
 | 
			
		||||
- Subversion source views now show detailed change information (issue 622)
 | 
			
		||||
- File download URLs now contain the file name rather than the upload id; old links still work though (issues 559 and 686)
 | 
			
		||||
- Display monotone file and directory attributes in the tree and file view
 | 
			
		||||
  (needs a monotone with an interface version of 13.1 or newer)
 | 
			
		||||
- The context area is now kept in view when a page scrolls down several pages
 | 
			
		||||
 | 
			
		||||
## Bugfixes 
 | 
			
		||||
## Bugfixes
 | 
			
		||||
 | 
			
		||||
- The SVN interface acts more robust if an underlying repository has been restructured (issues 364 and 721)
 | 
			
		||||
- monotone zip archive entries now all carry the revision date as mtime (issue 645)
 | 
			
		||||
- Timeline only displays filter options for items a user has actually access to (issue 655)
 | 
			
		||||
- The log, tags and branches parsers for Mercurial are more robust now (issue 663) 
 | 
			
		||||
- The log, tags and branches parsers for Mercurial are more robust now (issue 663)
 | 
			
		||||
- Fix SSH public key parsing issues and improve the check for existing, uploaded keys (issue 679)
 | 
			
		||||
- Diff views now show empty context lines for git and hg again (issue 688)
 | 
			
		||||
- Let the SVN command line client not store the login credentials we give him as arguments
 | 
			
		||||
- The usher section in the forge administration no longer displays a bogus
 | 
			
		||||
  server enty in case no monotone server is configured in the connected
 | 
			
		||||
  usher instance
 | 
			
		||||
- Prevent a timeout from popping up when Usher is restarted (issue 695)
 | 
			
		||||
- The SyncMonotone plugin now cleans up partial artifacts it created during the addition of
 | 
			
		||||
  a new project or monotone key, in case an error popped up in the middle (issue 697)
 | 
			
		||||
- Indefero now sends the MD5 checksum as HTTP header when downloading a file from the
 | 
			
		||||
  download area. Additionally, a unneeded redirect has been removed. (issue 716)
 | 
			
		||||
- Source links without a specific revision did not work due to a wrong regex (issue 730)
 | 
			
		||||
- Better error detection and reporting in the SyncMonotone plugin
 | 
			
		||||
  ATTENTION: This needs Pluf 46b7f251 or newer!
 | 
			
		||||
- Fix the branch links users of the Subversion frontend get when they enter a wrong revision
 | 
			
		||||
  and only display this list if there are any branches available for all SCMs
 | 
			
		||||
- If git's author name is not encoded in an UTF-8 compatible encoding, skip the author lookup,
 | 
			
		||||
  as we have no information what the author string is actually encoded in
 | 
			
		||||
- Indefero no longer displays an empty parents paragraph in the commit view for root revisions of
 | 
			
		||||
  a git repository
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
- The documentation on the setup of the monotone plugin has been improved.
 | 
			
		||||
 | 
			
		||||
## Translations
 | 
			
		||||
 | 
			
		||||
# InDefero 1.1.2 - Thu May 26 07:42:25 2011 UTC
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ If you install monotone from source (<http://monotone.ca/downloads.php>),
 | 
			
		||||
please follow the `INSTALL` document which comes with the software.
 | 
			
		||||
It contains detailed instructions, including all needed dependencies.
 | 
			
		||||
 | 
			
		||||
## Choose your indefero setup
 | 
			
		||||
## Choose your indefero (IDF) setup
 | 
			
		||||
 | 
			
		||||
The monotone plugin can be used in several different ways:
 | 
			
		||||
 | 
			
		||||
@@ -112,14 +112,40 @@ The monotone plugin can be used in several different ways:
 | 
			
		||||
            ^D
 | 
			
		||||
            $ chmod 600 usher.conf
 | 
			
		||||
 | 
			
		||||
    **ATTENTION:** Do _not_ configure a default monotone server key in `usher.conf`,
 | 
			
		||||
    otherwise the individual server key that IDF creates for each project is
 | 
			
		||||
    not used and this could cause problems.
 | 
			
		||||
 | 
			
		||||
    Your indefero www user needs later write access to `usher.conf` and
 | 
			
		||||
    `projects/`. There are two ways of setting this up:
 | 
			
		||||
 | 
			
		||||
    * Make the usher user the web user, for example via Apache's `suexec`
 | 
			
		||||
    * Use acls, like this:
 | 
			
		||||
    * Make the usher user the web user, for example via Apache's `suexec`.
 | 
			
		||||
      This is however a bit clumsy.
 | 
			
		||||
    * Preferred: Use Access Control Lists (ACLs), like this:
 | 
			
		||||
 | 
			
		||||
            #
 | 
			
		||||
            # Linux
 | 
			
		||||
            #
 | 
			
		||||
            $ setfacl -m u:www:rw usher.conf
 | 
			
		||||
            $ setfacl -m d:u:www:rwx projects/
 | 
			
		||||
            $ setfacl -m d:u:usher:rwx projects/
 | 
			
		||||
            #
 | 
			
		||||
            # FreeBSD
 | 
			
		||||
            #
 | 
			
		||||
            $ setfacl -m user:www:rw::allow usher.conf
 | 
			
		||||
            $ setfacl -m user:www:rwxp:fd:allow projects/
 | 
			
		||||
            $ setfacl -m user:usher:rwxp:fd:allow projects/
 | 
			
		||||
            #
 | 
			
		||||
            # Mac OS X
 | 
			
		||||
            #
 | 
			
		||||
            chmod +a '_www allow read,write' usher.conf
 | 
			
		||||
            chmod +a '_www allow read,write,delete,add_file,add_subdirectory,file_inherit,directory_inherit' projects/
 | 
			
		||||
            chmod +a 'usher allow read,write,delete,add_file,add_subdirectory,file_inherit,directory_inherit' projects/
 | 
			
		||||
 | 
			
		||||
      In each example's last line, `usher` is the user which is executing
 | 
			
		||||
      the usher instance. **It is very important to add this line, otherwise
 | 
			
		||||
      usher won't be able to read and write into the initial file system
 | 
			
		||||
      setup IDF creates!**
 | 
			
		||||
 | 
			
		||||
5. Wrap a daemonizer around usher, for example supervise from daemontools
 | 
			
		||||
   (<http://cr.yp.to/damontools.html>):
 | 
			
		||||
 
 | 
			
		||||
@@ -101,7 +101,7 @@ class IDF_Diff
 | 
			
		||||
                $files[$current_file]['chunks'][] = array();
 | 
			
		||||
 | 
			
		||||
                while ($i < $diffsize && ($addlines >= 0 || $dellines >= 0)) {
 | 
			
		||||
                    $linetype = $this->lines[$i] != '' ? $this->lines[$i][0] : ' ';
 | 
			
		||||
                    $linetype = $this->lines[$i] != '' ? $this->lines[$i][0] : false;
 | 
			
		||||
                    switch ($linetype) {
 | 
			
		||||
                        case ' ':
 | 
			
		||||
                            $files[$current_file]['chunks'][$current_chunk][] =
 | 
			
		||||
 
 | 
			
		||||
@@ -319,6 +319,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form
 | 
			
		||||
                           'labels_issue_closed' => IDF_Form_IssueTrackingConf::init_closed,
 | 
			
		||||
                           'labels_issue_predefined' =>  IDF_Form_IssueTrackingConf::init_predefined,
 | 
			
		||||
                           'labels_issue_one_max' => IDF_Form_IssueTrackingConf::init_one_max,
 | 
			
		||||
                           'issue_relations' => IDF_Form_IssueTrackingConf::init_relations,
 | 
			
		||||
                           'webhook_url' => '',
 | 
			
		||||
                           'downloads_access_rights' => 'all',
 | 
			
		||||
                           'review_access_rights' => 'all',
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
    public $user = null;
 | 
			
		||||
    public $project = null;
 | 
			
		||||
    public $show_full = false;
 | 
			
		||||
    public $relation_types = null;
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
@@ -45,9 +46,12 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
            or $this->user->hasPerm('IDF.project-member', $this->project)) {
 | 
			
		||||
            $this->show_full = true;
 | 
			
		||||
        }
 | 
			
		||||
        $this->relation_types = $this->project->getRelationsFromConfig();
 | 
			
		||||
 | 
			
		||||
        $contentTemplate = $this->project->getConf()->getVal(
 | 
			
		||||
            'labels_issue_template', IDF_Form_IssueTrackingConf::init_template
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $this->fields['summary'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Summary'),
 | 
			
		||||
@@ -76,11 +80,11 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
        // case of someone allowing the upload path to be accessible
 | 
			
		||||
        // to everybody.
 | 
			
		||||
        for ($i=1;$i<4;$i++) {
 | 
			
		||||
            $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy'; 
 | 
			
		||||
            $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
 | 
			
		||||
            $this->fields['attachment'.$i] = new Pluf_Form_Field_File(
 | 
			
		||||
                array('required' => false,
 | 
			
		||||
                      'label' => __('Attach a file'),
 | 
			
		||||
                      'move_function_params' => 
 | 
			
		||||
                      'move_function_params' =>
 | 
			
		||||
                      array('upload_path' => $upload_path,
 | 
			
		||||
                            'upload_path_create' => true,
 | 
			
		||||
                            'file_name' => $filename,
 | 
			
		||||
@@ -109,6 +113,20 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
            $this->fields['relation_type0'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                          array('required' => false,
 | 
			
		||||
                                'label' => __('This issue'),
 | 
			
		||||
                                'initial' => current($this->relation_types),
 | 
			
		||||
                                'widget_attrs' => array('size' => 15),
 | 
			
		||||
                                ));
 | 
			
		||||
 | 
			
		||||
            $this->fields['relation_issue0'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                          array('required' => false,
 | 
			
		||||
                                'label' => null,
 | 
			
		||||
                                'initial' => '',
 | 
			
		||||
                                'widget_attrs' => array('size' => 10),
 | 
			
		||||
                                ));
 | 
			
		||||
 | 
			
		||||
            /*
 | 
			
		||||
             * get predefined tags for issues from current project
 | 
			
		||||
             *
 | 
			
		||||
@@ -181,7 +199,7 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
            $this->cleaned_data['label'.$i] = trim($this->cleaned_data['label'.$i]);
 | 
			
		||||
            if (strpos($this->cleaned_data['label'.$i], ':') !== false) {
 | 
			
		||||
                list($class, $name) = explode(':', $this->cleaned_data['label'.$i], 2);
 | 
			
		||||
                list($class, $name) = array(mb_strtolower(trim($class)), 
 | 
			
		||||
                list($class, $name) = array(mb_strtolower(trim($class)),
 | 
			
		||||
                                            trim($name));
 | 
			
		||||
            } else {
 | 
			
		||||
                $class = 'other';
 | 
			
		||||
@@ -215,10 +233,10 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
    function clean_status()
 | 
			
		||||
    {
 | 
			
		||||
        // Check that the status is in the list of official status
 | 
			
		||||
        $tags = $this->project->getTagsFromConfig('labels_issue_open', 
 | 
			
		||||
        $tags = $this->project->getTagsFromConfig('labels_issue_open',
 | 
			
		||||
                                          IDF_Form_IssueTrackingConf::init_open,
 | 
			
		||||
                                          'Status');
 | 
			
		||||
        $tags = array_merge($this->project->getTagsFromConfig('labels_issue_closed', 
 | 
			
		||||
        $tags = array_merge($this->project->getTagsFromConfig('labels_issue_closed',
 | 
			
		||||
                                          IDF_Form_IssueTrackingConf::init_closed,
 | 
			
		||||
                                          'Status')
 | 
			
		||||
                            , $tags);
 | 
			
		||||
@@ -235,6 +253,63 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
        return $this->cleaned_data['status'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // this method is not called from Pluf_Form directly, but shared for
 | 
			
		||||
    // among all similar fields
 | 
			
		||||
    function clean_relation_type($value)
 | 
			
		||||
    {
 | 
			
		||||
        $relation_type = trim($value);
 | 
			
		||||
        if (empty($relation_type))
 | 
			
		||||
            return '';
 | 
			
		||||
 | 
			
		||||
        $found = false;
 | 
			
		||||
        foreach ($this->relation_types as $type) {
 | 
			
		||||
            if ($type == $relation_type) {
 | 
			
		||||
                $found = true;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (!$found) {
 | 
			
		||||
            throw new Pluf_Form_Invalid(__('You provided an invalid relation type.'));
 | 
			
		||||
        }
 | 
			
		||||
        return $relation_type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_relation_type0()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->clean_relation_type($this->cleaned_data['relation_type0']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // this method is not called from Pluf_Form directly, but shared for
 | 
			
		||||
    // among all similar fields
 | 
			
		||||
    function clean_relation_issue($value)
 | 
			
		||||
    {
 | 
			
		||||
        $issues = trim($value);
 | 
			
		||||
        if (empty($issues))
 | 
			
		||||
            return '';
 | 
			
		||||
 | 
			
		||||
        $issue_ids = preg_split('/\s*,\s*/', $issues, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
        foreach ($issue_ids as $issue_id) {
 | 
			
		||||
            if (!ctype_digit($issue_id) || (int)$issue_id < 1) {
 | 
			
		||||
                throw new Pluf_Form_Invalid(sprintf(
 | 
			
		||||
                    __('The value "%s" is not a valid issue id.'), $issue_id
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            $issue = new IDF_Issue($issue_id);
 | 
			
		||||
            if ($issue->id != $issue_id || $issue->project != $this->project->id) {
 | 
			
		||||
                throw new Pluf_Form_Invalid(sprintf(
 | 
			
		||||
                    __('The issue "%s" does not exist.'), $issue_id
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return implode(', ', $issue_ids);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function clean_relation_issue0()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->clean_relation_issue($this->cleaned_data['relation_issue0']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clean the attachments post failure.
 | 
			
		||||
     */
 | 
			
		||||
@@ -298,6 +373,30 @@ class IDF_Form_IssueCreate extends Pluf_Form
 | 
			
		||||
        foreach ($tags as $tag) {
 | 
			
		||||
            $issue->setAssoc($tag);
 | 
			
		||||
        }
 | 
			
		||||
        // add relations (if any)
 | 
			
		||||
        if (!empty($this->cleaned_data['relation_type0'])) {
 | 
			
		||||
            $verb = $this->cleaned_data['relation_type0'];
 | 
			
		||||
            $other_verb = $this->relation_types[$verb];
 | 
			
		||||
            $related_issues = preg_split('/\s*,\s*/', $this->cleaned_data['relation_issue0'], -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
 | 
			
		||||
            foreach ($related_issues as $related_issue_id) {
 | 
			
		||||
                $related_issue = new IDF_Issue($related_issue_id);
 | 
			
		||||
                $rel = new IDF_IssueRelation();
 | 
			
		||||
                $rel->issue = $issue;
 | 
			
		||||
                $rel->verb = $verb;
 | 
			
		||||
                $rel->other_issue = $related_issue;
 | 
			
		||||
                $rel->submitter = $this->user;
 | 
			
		||||
                $rel->create();
 | 
			
		||||
 | 
			
		||||
                $other_rel = new IDF_IssueRelation();
 | 
			
		||||
                $other_rel->issue = $related_issue;
 | 
			
		||||
                $other_rel->verb = $other_verb;
 | 
			
		||||
                $other_rel->other_issue = $issue;
 | 
			
		||||
                $other_rel->submitter = $this->user;
 | 
			
		||||
                $other_rel->create();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // add the first comment
 | 
			
		||||
        $comment = new IDF_IssueComment();
 | 
			
		||||
        $comment->issue = $issue;
 | 
			
		||||
 
 | 
			
		||||
@@ -72,12 +72,29 @@ Performance          = Performance issue
 | 
			
		||||
Usability            = Affects program usability
 | 
			
		||||
Maintainability      = Hinders future changes';
 | 
			
		||||
    const init_one_max = 'Type, Priority, Milestone';
 | 
			
		||||
    // ATTENTION: if you change something here, change the values below as well!
 | 
			
		||||
    const init_relations = 'is related to
 | 
			
		||||
blocks, is blocked by
 | 
			
		||||
duplicates, is duplicated by';
 | 
			
		||||
 | 
			
		||||
    // These are actually all noop's, but we have no other chance to
 | 
			
		||||
    // tell IDF's translation mechanism to mark the strings as translatable
 | 
			
		||||
    // FIXME: IDF should get a internal translation system for strings like
 | 
			
		||||
    // that, that can also be easily expanded by users
 | 
			
		||||
    private function noop()
 | 
			
		||||
    {
 | 
			
		||||
        __('is related to');
 | 
			
		||||
        __('blocks');
 | 
			
		||||
        __('is blocked by');
 | 
			
		||||
        __('duplicates');
 | 
			
		||||
        __('is duplicated by');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function initFields($extra=array())
 | 
			
		||||
    {
 | 
			
		||||
        $this->fields['labels_issue_template'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Define an issue template to hint to the reporter to provide certain information'),
 | 
			
		||||
                                            'label' => __('Define an issue template to hint the reporter to provide certain information'),
 | 
			
		||||
                                            'initial' => self::init_template,
 | 
			
		||||
                                            'widget_attrs' => array('rows' => 7,
 | 
			
		||||
                                                                    'cols' => 75),
 | 
			
		||||
@@ -114,10 +131,19 @@ Maintainability      = Hinders future changes';
 | 
			
		||||
        $this->fields['labels_issue_one_max'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => false,
 | 
			
		||||
                                            'label' => __('Each issue may have at most one label with each of these classes'),
 | 
			
		||||
                                            'initial' => self::init_one_max, 
 | 
			
		||||
                                            'initial' => self::init_one_max,
 | 
			
		||||
                                            'widget_attrs' => array('size' => 60),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
        $this->fields['issue_relations'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
                                            'label' => __('Issue relations'),
 | 
			
		||||
                                            'initial' => self::init_relations,
 | 
			
		||||
                                            'help_text' => __('You can define bidirectional relations like "is related to" or "blocks, is blocked by".'),
 | 
			
		||||
                                            'widget_attrs' => array('rows' => 7,
 | 
			
		||||
                                                                    'cols' => 75),
 | 
			
		||||
                                            'widget' => 'Pluf_Form_Widget_TextareaInput',
 | 
			
		||||
                                            ));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,7 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
            or $this->user->hasPerm('IDF.project-member', $this->project)) {
 | 
			
		||||
            $this->show_full = true;
 | 
			
		||||
        }
 | 
			
		||||
        $this->relation_types = $this->project->getRelationsFromConfig();
 | 
			
		||||
        if ($this->show_full) {
 | 
			
		||||
            $this->fields['summary'] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                                      array('required' => true,
 | 
			
		||||
@@ -69,11 +70,11 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
        // case of someone allowing the upload path to be accessible
 | 
			
		||||
        // to everybody.
 | 
			
		||||
        for ($i=1;$i<4;$i++) {
 | 
			
		||||
            $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy'; 
 | 
			
		||||
            $filename = substr($md5, 0, 2).'/'.substr($md5, 2, 2).'/'.substr($md5, 4).'/%s.dummy';
 | 
			
		||||
            $this->fields['attachment'.$i] = new Pluf_Form_Field_File(
 | 
			
		||||
                array('required' => false,
 | 
			
		||||
                      'label' => __('Attach a file'),
 | 
			
		||||
                      'move_function_params' => 
 | 
			
		||||
                      'move_function_params' =>
 | 
			
		||||
                      array('upload_path' => $upload_path,
 | 
			
		||||
                            'upload_path_create' => true,
 | 
			
		||||
                            'file_name' => $filename,
 | 
			
		||||
@@ -102,6 +103,52 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
                                                       'size' => 15,
 | 
			
		||||
                                                                    ),
 | 
			
		||||
                                            ));
 | 
			
		||||
 | 
			
		||||
            $idx = 0;
 | 
			
		||||
            // note: clean_relation_type0 and clean_relation_issue0 already
 | 
			
		||||
            //       exist in the base class
 | 
			
		||||
            $this->fields['relation_type'.$idx] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                          array('required' => false,
 | 
			
		||||
                                'label' => __('This issue'),
 | 
			
		||||
                                'initial' => current($this->relation_types),
 | 
			
		||||
                                'widget_attrs' => array('size' => 15),
 | 
			
		||||
                                ));
 | 
			
		||||
 | 
			
		||||
            $this->fields['relation_issue'.$idx] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                          array('required' => false,
 | 
			
		||||
                                'label' => null,
 | 
			
		||||
                                'initial' => '',
 | 
			
		||||
                                'widget_attrs' => array('size' => 10),
 | 
			
		||||
                                ));
 | 
			
		||||
 | 
			
		||||
            ++$idx;
 | 
			
		||||
            $relatedIssues = $this->issue->getGroupedRelatedIssues(array(), true);
 | 
			
		||||
            foreach ($relatedIssues as $verb => $ids) {
 | 
			
		||||
                $this->fields['relation_type'.$idx] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                          array('required' => false,
 | 
			
		||||
                                'label' => __('This issue'),
 | 
			
		||||
                                'initial' => $verb,
 | 
			
		||||
                                'widget_attrs' => array('size' => 15),
 | 
			
		||||
                                ));
 | 
			
		||||
                $m = 'clean_relation_type'.$idx;
 | 
			
		||||
                $this->$m = create_function('$form', '
 | 
			
		||||
                    return $form->clean_relation_type($form->cleaned_data["relation_type'.$idx.'"]);
 | 
			
		||||
                ');
 | 
			
		||||
 | 
			
		||||
                $this->fields['relation_issue'.$idx] = new Pluf_Form_Field_Varchar(
 | 
			
		||||
                              array('required' => false,
 | 
			
		||||
                                    'label' => null,
 | 
			
		||||
                                    'initial' => implode(', ', $ids),
 | 
			
		||||
                                    'widget_attrs' => array('size' => 10),
 | 
			
		||||
                                    ));
 | 
			
		||||
                $m = 'clean_relation_issue'.$idx;
 | 
			
		||||
                $this->$m = create_function('$form', '
 | 
			
		||||
                    return $form->clean_relation_issue($form->cleaned_data["relation_issue'.$idx.'"]);
 | 
			
		||||
                ');
 | 
			
		||||
 | 
			
		||||
                ++$idx;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $tags = $this->issue->get_tags_list();
 | 
			
		||||
            for ($i=1;$i<7;$i++) {
 | 
			
		||||
                $initial = '';
 | 
			
		||||
@@ -155,6 +202,51 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
    public function clean()
 | 
			
		||||
    {
 | 
			
		||||
        $this->cleaned_data = parent::clean();
 | 
			
		||||
 | 
			
		||||
        // normalize the user's input by removing dublettes and by combining
 | 
			
		||||
        // ids from identical verbs in different input fields into one array
 | 
			
		||||
        $normRelatedIssues = array();
 | 
			
		||||
        for ($idx = 0; isset($this->cleaned_data['relation_type'.$idx]); ++$idx) {
 | 
			
		||||
            $verb = $this->cleaned_data['relation_type'.$idx];
 | 
			
		||||
            if (empty($verb))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $ids = preg_split('/\s*,\s*/', $this->cleaned_data['relation_issue'.$idx],
 | 
			
		||||
                              -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
            if (count($ids) == 0)
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            if (!array_key_exists($verb, $normRelatedIssues))
 | 
			
		||||
                $normRelatedIssues[$verb] = array();
 | 
			
		||||
            foreach ($ids as $id) {
 | 
			
		||||
                if (!in_array($id, $normRelatedIssues[$verb]))
 | 
			
		||||
                    $normRelatedIssues[$verb][] = $id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // now look at any added / removed ids
 | 
			
		||||
        $added = $removed = array();
 | 
			
		||||
        $relatedIssues = $this->issue->getGroupedRelatedIssues(array(), true);
 | 
			
		||||
        $added = array_diff_key($normRelatedIssues, $relatedIssues);
 | 
			
		||||
        $removed = array_diff_key($relatedIssues, $normRelatedIssues);
 | 
			
		||||
 | 
			
		||||
        $keysToLookAt = array_keys(
 | 
			
		||||
            array_intersect_key($relatedIssues, $normRelatedIssues)
 | 
			
		||||
        );
 | 
			
		||||
        foreach ($keysToLookAt as $key) {
 | 
			
		||||
            $a = array_diff($normRelatedIssues[$key], $relatedIssues[$key]);
 | 
			
		||||
            if (count($a) > 0)
 | 
			
		||||
                $added[$key] = $a;
 | 
			
		||||
            $r = array_diff($relatedIssues[$key], $normRelatedIssues[$key]);
 | 
			
		||||
            if (count($r) > 0)
 | 
			
		||||
                $removed[$key] = $r;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // cache the added / removed data, so we do not have to
 | 
			
		||||
        // calculate that again
 | 
			
		||||
        $this->cleaned_data['_added_issue_relations'] = $added;
 | 
			
		||||
        $this->cleaned_data['_removed_issue_relations'] = $removed;
 | 
			
		||||
 | 
			
		||||
        // As soon as we know that at least one change was done, we
 | 
			
		||||
        // return the cleaned data and do not go further.
 | 
			
		||||
        if (strlen(trim($this->cleaned_data['content']))) {
 | 
			
		||||
@@ -214,6 +306,11 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
                    return $this->cleaned_data;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (count($this->cleaned_data['_added_issue_relations']) != 0 ||
 | 
			
		||||
                count($this->cleaned_data['_removed_issue_relations']) != 0) {
 | 
			
		||||
                return $this->cleaned_data;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // no changes!
 | 
			
		||||
        throw new Pluf_Form_Invalid(__('No changes were entered.'));
 | 
			
		||||
@@ -255,20 +352,22 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
            foreach ($tags as $tag) {
 | 
			
		||||
                if (!Pluf_Model_InArray($tag, $oldtags)) {
 | 
			
		||||
                    if (!isset($changes['lb'])) $changes['lb'] = array();
 | 
			
		||||
                    if (!isset($changes['lb']['add'])) $changes['lb']['add'] = array();
 | 
			
		||||
                    if ($tag->class != 'Other') {
 | 
			
		||||
                        $changes['lb'][] = (string) $tag; //new tag
 | 
			
		||||
                        $changes['lb']['add'][] = (string) $tag; //new tag
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $changes['lb'][] = (string) $tag->name;
 | 
			
		||||
                        $changes['lb']['add'][] = (string) $tag->name;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            foreach ($oldtags as $tag) {
 | 
			
		||||
                if (!Pluf_Model_InArray($tag, $tags)) {
 | 
			
		||||
                    if (!isset($changes['lb'])) $changes['lb'] = array();
 | 
			
		||||
                    if (!isset($changes['lb']['rem'])) $changes['lb']['rem'] = array();
 | 
			
		||||
                    if ($tag->class != 'Other') {
 | 
			
		||||
                        $changes['lb'][] = '-'.(string) $tag; //new tag
 | 
			
		||||
                        $changes['lb']['rem'][] = (string) $tag; //new tag
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $changes['lb'][] = '-'.(string) $tag->name;
 | 
			
		||||
                        $changes['lb']['rem'][] = (string) $tag->name;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -286,6 +385,47 @@ class IDF_Form_IssueUpdate  extends IDF_Form_IssueCreate
 | 
			
		||||
                or ((!is_null($owner) and !is_null($this->issue->get_owner())) and $owner->id != $this->issue->get_owner()->id)) {
 | 
			
		||||
                $changes['ow'] = (is_null($owner)) ? '---' : $owner->login;
 | 
			
		||||
            }
 | 
			
		||||
            // Issue relations - additions
 | 
			
		||||
            foreach ($this->cleaned_data['_added_issue_relations'] as $verb => $ids) {
 | 
			
		||||
                $other_verb = $this->relation_types[$verb];
 | 
			
		||||
                foreach ($ids as $id) {
 | 
			
		||||
                    $related_issue = new IDF_Issue($id);
 | 
			
		||||
                    $rel = new IDF_IssueRelation();
 | 
			
		||||
                    $rel->issue = $this->issue;
 | 
			
		||||
                    $rel->verb = $verb;
 | 
			
		||||
                    $rel->other_issue = $related_issue;
 | 
			
		||||
                    $rel->submitter = $this->user;
 | 
			
		||||
                    $rel->create();
 | 
			
		||||
 | 
			
		||||
                    $other_rel = new IDF_IssueRelation();
 | 
			
		||||
                    $other_rel->issue = $related_issue;
 | 
			
		||||
                    $other_rel->verb = $other_verb;
 | 
			
		||||
                    $other_rel->other_issue = $this->issue;
 | 
			
		||||
                    $other_rel->submitter = $this->user;
 | 
			
		||||
                    $other_rel->create();
 | 
			
		||||
                }
 | 
			
		||||
                if (!isset($changes['rel'])) $changes['rel'] = array();
 | 
			
		||||
                if (!isset($changes['rel']['add'])) $changes['rel']['add'] = array();
 | 
			
		||||
                $changes['rel']['add'][] = $verb.' '.implode(', ', $ids);
 | 
			
		||||
            }
 | 
			
		||||
            // Issue relations - removals
 | 
			
		||||
            foreach ($this->cleaned_data['_removed_issue_relations'] as $verb => $ids) {
 | 
			
		||||
                foreach ($ids as $id) {
 | 
			
		||||
                    $db = &Pluf::db();
 | 
			
		||||
                    $table = Pluf::factory('IDF_IssueRelation')->getSqlTable();
 | 
			
		||||
                    $sql = new Pluf_SQL('verb=%s AND (
 | 
			
		||||
                                        (issue=%s AND other_issue=%s) OR
 | 
			
		||||
                                        (other_issue=%s AND issue=%s))',
 | 
			
		||||
                                        array($verb,
 | 
			
		||||
                                              $this->issue->id, $id,
 | 
			
		||||
                                              $this->issue->id, $id));
 | 
			
		||||
                    $db->execute('DELETE FROM '.$table.' WHERE '.$sql->gen());
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!isset($changes['rel'])) $changes['rel'] = array();
 | 
			
		||||
                if (!isset($changes['rel']['rem'])) $changes['rel']['rem'] = array();
 | 
			
		||||
                $changes['rel']['rem'][] = $verb.' '.implode(', ', $ids);
 | 
			
		||||
            }
 | 
			
		||||
            // Update the issue
 | 
			
		||||
            $this->issue->batchAssoc('IDF_Tag', $tagids);
 | 
			
		||||
            $this->issue->summary = trim($this->cleaned_data['summary']);
 | 
			
		||||
 
 | 
			
		||||
@@ -169,6 +169,24 @@ class IDF_Issue extends Pluf_Model
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getGroupedRelatedIssues($opts = array(), $idsOnly = false)
 | 
			
		||||
    {
 | 
			
		||||
        $rels = $this->get_related_issues_list(array_merge($opts, array(
 | 
			
		||||
               'view' => 'with_other_issue',
 | 
			
		||||
        )));
 | 
			
		||||
 | 
			
		||||
        $res = array();
 | 
			
		||||
        foreach ($rels as $rel) {
 | 
			
		||||
            $verb = $rel->verb;
 | 
			
		||||
            if (!array_key_exists($verb, $res)) {
 | 
			
		||||
                $res[$verb] = array();
 | 
			
		||||
            }
 | 
			
		||||
            $res[$verb][] = $idsOnly ? $rel->other_issue : $rel;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns an HTML fragment used to display this issue in the
 | 
			
		||||
     * timeline.
 | 
			
		||||
 
 | 
			
		||||
@@ -155,10 +155,19 @@ class IDF_IssueComment extends Pluf_Model
 | 
			
		||||
                    $out .= __('Owner:'); break;
 | 
			
		||||
                case 'lb':
 | 
			
		||||
                    $out .= __('Labels:'); break;
 | 
			
		||||
                case 'rel':
 | 
			
		||||
                    $out .= __('Relations:'); break;
 | 
			
		||||
                }
 | 
			
		||||
                $out .= '</strong> ';
 | 
			
		||||
                if ($w == 'lb') {
 | 
			
		||||
                    $out .= Pluf_esc(implode(', ', $v));
 | 
			
		||||
                if ($w == 'lb' || $w == 'rel') {
 | 
			
		||||
                    foreach ($v as $t => $ls) {
 | 
			
		||||
                        foreach ($ls as $l) {
 | 
			
		||||
                            if ($t == 'rem') $out .= '<s>';
 | 
			
		||||
                            $out .= Pluf_esc($l);
 | 
			
		||||
                            if ($t == 'rem') $out .= '</s>';
 | 
			
		||||
                            $out .= ' ';
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    $out .= Pluf_esc($v);
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								src/IDF/IssueRelation.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/IDF/IssueRelation.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero 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 General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A relation of one issue to another
 | 
			
		||||
 */
 | 
			
		||||
class IDF_IssueRelation extends Pluf_Model
 | 
			
		||||
{
 | 
			
		||||
    public $_model = __CLASS__;
 | 
			
		||||
 | 
			
		||||
    function init()
 | 
			
		||||
    {
 | 
			
		||||
        $this->_a['table'] = 'idf_issuerelations';
 | 
			
		||||
        $this->_a['model'] = __CLASS__;
 | 
			
		||||
        $this->_a['cols'] = array(
 | 
			
		||||
                             // It is mandatory to have an "id" column.
 | 
			
		||||
                            'id' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Sequence',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'issue' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'IDF_Issue',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('issue'),
 | 
			
		||||
                                  'relate_name' => 'related_issues',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'verb' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Text',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('verb'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'other_issue' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'IDF_Issue',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('other issue'),
 | 
			
		||||
                                  'relate_name' => 'related_other_issues',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            'submitter' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Foreignkey',
 | 
			
		||||
                                  'model' => 'Pluf_User',
 | 
			
		||||
                                  'blank' => false,
 | 
			
		||||
                                  'verbose' => __('submitter'),
 | 
			
		||||
                                   ),
 | 
			
		||||
                            'creation_dtime' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'type' => 'Pluf_DB_Field_Datetime',
 | 
			
		||||
                                  'blank' => true,
 | 
			
		||||
                                  'verbose' => __('creation date'),
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
        $this->_a['idx'] = array(
 | 
			
		||||
                            'creation_dtime_idx' =>
 | 
			
		||||
                            array(
 | 
			
		||||
                                  'col' => 'creation_dtime',
 | 
			
		||||
                                  'type' => 'normal',
 | 
			
		||||
                                  ),
 | 
			
		||||
                            );
 | 
			
		||||
        $issuetbl = $this->_con->pfx.'idf_issues';
 | 
			
		||||
        $this->_a['views'] = array(
 | 
			
		||||
            'with_other_issue' => array(
 | 
			
		||||
                'join' => 'INNER JOIN '.$issuetbl.' ON other_issue='.$issuetbl.'.id',
 | 
			
		||||
                'select' => $this->getSelect().', summary',
 | 
			
		||||
                'props' => array('summary' => 'other_summary'),
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function preSave($create=false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->id == '') {
 | 
			
		||||
            $this->creation_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -88,6 +88,7 @@ class IDF_Middleware
 | 
			
		||||
                              'showuser' => 'IDF_Template_ShowUser',
 | 
			
		||||
                              'ashowuser' => 'IDF_Template_AssignShowUser',
 | 
			
		||||
                              'appversion' => 'IDF_Template_AppVersion',
 | 
			
		||||
                              'upload' => 'IDF_Template_Tag_UploadUrl',
 | 
			
		||||
                                            ));
 | 
			
		||||
        $params['modifiers'] = array_merge($params['modifiers'],
 | 
			
		||||
                                           array(
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										90
									
								
								src/IDF/Migrations/17AddIssueRelations.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/IDF/Migrations/17AddIssueRelations.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero 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 General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add the new IDF_IssueRelation model.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_17AddIssueRelations_up($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    $schema->model = new IDF_IssueRelation();
 | 
			
		||||
    $schema->createTables();
 | 
			
		||||
 | 
			
		||||
    // change the serialization format for added / removed labels in IDF_IssueComment
 | 
			
		||||
    $comments = Pluf::factory('IDF_IssueComment')->getList();
 | 
			
		||||
    foreach ($comments as $comment) {
 | 
			
		||||
        if (!isset($comment->changes['lb'])) continue;
 | 
			
		||||
        $changes = $comment->changes;
 | 
			
		||||
        $adds = $removals = array();
 | 
			
		||||
        foreach ($comment->changes['lb'] as $lb) {
 | 
			
		||||
            if (substr($lb, 0, 1) == '-')
 | 
			
		||||
                $removals[] = substr($lb, 1);
 | 
			
		||||
            else
 | 
			
		||||
                $adds[] = $lb;
 | 
			
		||||
        }
 | 
			
		||||
        $changes['lb'] = array();
 | 
			
		||||
        if (count($adds) > 0)
 | 
			
		||||
            $changes['lb']['add'] = $adds;
 | 
			
		||||
        if (count($removals) > 0)
 | 
			
		||||
            $changes['lb']['rem'] = $removals;
 | 
			
		||||
        $comment->changes = $changes;
 | 
			
		||||
        $comment->update();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function IDF_Migrations_17AddIssueRelations_down($params=null)
 | 
			
		||||
{
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
    $schema->model = new IDF_IssueRelation();
 | 
			
		||||
    $schema->dropTables();
 | 
			
		||||
 | 
			
		||||
    // change the serialization format for added / removed labels in IDF_IssueComment
 | 
			
		||||
    $comments = Pluf::factory('IDF_IssueComment')->getList();
 | 
			
		||||
    foreach ($comments as $comment) {
 | 
			
		||||
        $changes = $comment->changes;
 | 
			
		||||
        if (empty($changes))
 | 
			
		||||
            continue;
 | 
			
		||||
        if (isset($changes['lb'])) {
 | 
			
		||||
            $labels = array();
 | 
			
		||||
            foreach ($changes['lb'] as $type => $lbs) {
 | 
			
		||||
                if (!is_array($lbs)) {
 | 
			
		||||
                    $labels[] = $lbs;
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                foreach ($lbs as $lb) {
 | 
			
		||||
                    $labels[] = ($type == 'rem' ? '-' : '') . $lb;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $changes['lb'] = $labels;
 | 
			
		||||
        }
 | 
			
		||||
        // while we're at it, remove any 'rel' changes
 | 
			
		||||
        unset($changes['rel']);
 | 
			
		||||
        $comment->changes = $changes;
 | 
			
		||||
        $comment->update();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -54,6 +54,7 @@ function IDF_Migrations_Backup_run($folder, $name=null)
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    'IDF_EmailAddress',
 | 
			
		||||
                    'IDF_IssueRelation',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    // Now, for each table, we dump the content in json, this is a
 | 
			
		||||
@@ -100,6 +101,7 @@ function IDF_Migrations_Backup_restore($folder, $name)
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    'IDF_EmailAddress',
 | 
			
		||||
                    'IDF_IssueRelation',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,7 @@ function IDF_Migrations_Install_setup($params=null)
 | 
			
		||||
                    'IDF_Queue',
 | 
			
		||||
                    'IDF_Gconf',
 | 
			
		||||
                    'IDF_EmailAddress',
 | 
			
		||||
                    'IDF_IssueRelation',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
@@ -109,6 +110,7 @@ function IDF_Migrations_Install_teardown($params=null)
 | 
			
		||||
                    'IDF_Commit',
 | 
			
		||||
                    'IDF_Project',
 | 
			
		||||
                    'IDF_EmailAddress',
 | 
			
		||||
                    'IDF_IssueRelation',
 | 
			
		||||
                    );
 | 
			
		||||
    $db = Pluf::db();
 | 
			
		||||
    $schema = new Pluf_DB_Schema($db);
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,16 @@ class IDF_Plugin_SyncGit_Cron
 | 
			
		||||
                $out .= sprintf($template, $cmd, $key->login, $content)."\n";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $out = "# indefero start" . PHP_EOL . $out . "# indefero end" . PHP_EOL;
 | 
			
		||||
        
 | 
			
		||||
        // We update only the part of the file between IDF_START / IDF_END comment
 | 
			
		||||
        $original_keys = file_get_contents($authorized_keys);
 | 
			
		||||
        if (strstr($original_keys, "# indefero start") && strstr($original_keys, "# indefero end")) {
 | 
			
		||||
            $out = preg_replace('/(#\sindefero\sstart).+(#\sindefero\send\s\s?)/isU', 
 | 
			
		||||
                                $out, $original_keys);
 | 
			
		||||
        } else {
 | 
			
		||||
             $out .= $original_keys;   
 | 
			
		||||
        }
 | 
			
		||||
        file_put_contents($authorized_keys, $out, LOCK_EX);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,18 @@
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Plugin_SyncMonotone
 | 
			
		||||
{
 | 
			
		||||
    private $old_err_rep = 0;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->old_err_rep = error_reporting(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __destruct()
 | 
			
		||||
    {
 | 
			
		||||
        error_reporting($this->old_err_rep);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Entry point of the plugin.
 | 
			
		||||
     */
 | 
			
		||||
@@ -80,24 +92,33 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // This guard cleans up on any kind of error, and here is how it works:
 | 
			
		||||
        // As long as the guard is not committed, it keeps a reference to
 | 
			
		||||
        // the given project. When the guard is destroyed and the reference
 | 
			
		||||
        // is still present, it deletes the object. The deletion indirectly
 | 
			
		||||
        // also calls into this plugin again, as the project delete hook
 | 
			
		||||
        // will be called, that removes any changes we've made during the
 | 
			
		||||
        // process.
 | 
			
		||||
        $projectGuard = new IDF_Plugin_SyncMonotone_ModelGuard($project);
 | 
			
		||||
 | 
			
		||||
        $projecttempl = Pluf::f('mtn_repositories', false);
 | 
			
		||||
        if ($projecttempl === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file.')
 | 
			
		||||
            $this->_diagnoseProblem(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $usher_config = Pluf::f('mtn_usher_conf', false);
 | 
			
		||||
        if (!$usher_config || !is_writable($usher_config)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_usher_conf" does not exist or is not writable.')
 | 
			
		||||
            $this->_diagnoseProblem(
 | 
			
		||||
                 __('"mtn_usher_conf" does not exist or is not writable')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $mtnpostpush = realpath(dirname(__FILE__) . '/../../../scripts/mtn-post-push');
 | 
			
		||||
        if (!file_exists($mtnpostpush)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not find mtn-post-push script "%s".'), $mtnpostpush
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('Could not find mtn-post-push script "%s"'), $mtnpostpush
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -110,13 +131,12 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
             'monotonerc.in',
 | 
			
		||||
             'remote-automate-permissions.in',
 | 
			
		||||
             'hooks.d/',
 | 
			
		||||
             // this is linked and not copied to be able to update
 | 
			
		||||
             // the list of read-only commands on upgrades
 | 
			
		||||
             'hooks.d/indefero_authorize_remote_automate.conf',
 | 
			
		||||
             'hooks.d/indefero_authorize_remote_automate.lua',
 | 
			
		||||
             'hooks.d/indefero_post_push.conf.in',
 | 
			
		||||
             'hooks.d/indefero_post_push.lua',
 | 
			
		||||
        );
 | 
			
		||||
        // enable remote command execution of read-only commands
 | 
			
		||||
        // only for public projects
 | 
			
		||||
        if (!$project->private) {
 | 
			
		||||
            // this is linked and not copied to be able to update
 | 
			
		||||
            // the list of read-only commands on upgrades
 | 
			
		||||
@@ -131,8 +151,8 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($confdir_contents as $content) {
 | 
			
		||||
            if (!file_exists($confdir.$content)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('The configuration file %s is missing.'), $content
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('The configuration file "%s" is missing'), $content
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -140,14 +160,15 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        $shortname = $project->shortname;
 | 
			
		||||
        $projectpath = sprintf($projecttempl, $shortname);
 | 
			
		||||
        if (file_exists($projectpath)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('The project path %s already exists.'), $projectpath
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('The project path "%s" already exists'), $projectpath
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!mkdir($projectpath)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('The project path %s could not be created.'), $projectpath
 | 
			
		||||
        if (!@mkdir($projectpath)) {
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('The project path "%s" could not be created'),
 | 
			
		||||
                $projectpath
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -156,7 +177,7 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        //
 | 
			
		||||
        $dbfile = $projectpath.'/database.mtn';
 | 
			
		||||
        $cmd = sprintf('db init -d %s', escapeshellarg($dbfile));
 | 
			
		||||
        self::_mtn_exec($cmd);
 | 
			
		||||
        $this->_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 2) create a server key
 | 
			
		||||
@@ -175,16 +196,17 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            escapeshellarg($projectpath),
 | 
			
		||||
            escapeshellarg($serverkey)
 | 
			
		||||
        );
 | 
			
		||||
        self::_mtn_exec($cmd);
 | 
			
		||||
        $this->_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 3) create a client key, and save it in IDF
 | 
			
		||||
        //
 | 
			
		||||
        $keydir = Pluf::f('tmp_folder').'/mtn-client-keys';
 | 
			
		||||
        if (!file_exists($keydir)) {
 | 
			
		||||
            if (!mkdir($keydir)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('The key directory %s could not be created.'), $keydir
 | 
			
		||||
            if (!@mkdir($keydir)) {
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('The key directory "%s" could not be created'),
 | 
			
		||||
                    $keydir
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -194,14 +216,14 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            escapeshellarg($keydir),
 | 
			
		||||
            escapeshellarg($clientkey_name)
 | 
			
		||||
        );
 | 
			
		||||
        $keyinfo = self::_mtn_exec($cmd);
 | 
			
		||||
        $keyinfo = $this->_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        $parsed_keyinfo = array();
 | 
			
		||||
        try {
 | 
			
		||||
            $parsed_keyinfo = IDF_Scm_Monotone_BasicIO::parse($keyinfo);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception $e) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('Could not parse key information: %s'), $e->getMessage()
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
@@ -219,13 +241,13 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            escapeshellarg($keydir),
 | 
			
		||||
            escapeshellarg($clientkey_hash)
 | 
			
		||||
        );
 | 
			
		||||
        $clientkey_pubdata = self::_mtn_exec($cmd);
 | 
			
		||||
        $clientkey_pubdata = $this->_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        $cmd = sprintf('au put_public_key --db=%s %s',
 | 
			
		||||
            escapeshellarg($dbfile),
 | 
			
		||||
            escapeshellarg($clientkey_pubdata)
 | 
			
		||||
        );
 | 
			
		||||
        self::_mtn_exec($cmd);
 | 
			
		||||
        $this->_mtn_exec($cmd);
 | 
			
		||||
 | 
			
		||||
        //
 | 
			
		||||
        // step 4) setup the configuration
 | 
			
		||||
@@ -238,18 +260,20 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        foreach ($confdir_contents as $content) {
 | 
			
		||||
            $filepath = $projectpath.'/'.$content;
 | 
			
		||||
            if (substr($content, -1) == '/') {
 | 
			
		||||
                if (!mkdir($filepath)) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not create configuration directory "%s"'), $filepath
 | 
			
		||||
                if (!@mkdir($filepath)) {
 | 
			
		||||
                    $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                        __('Could not create configuration directory "%s"'),
 | 
			
		||||
                        $filepath
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (substr($content, -3) != '.in') {
 | 
			
		||||
                if (!symlink($confdir.$content, $filepath)) {
 | 
			
		||||
                    IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not create symlink "%s"'), $filepath
 | 
			
		||||
                if (!@symlink($confdir.$content, $filepath)) {
 | 
			
		||||
                    $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                        __('Could not create symlink for configuration file "%s"'),
 | 
			
		||||
                        $filepath
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
                continue;
 | 
			
		||||
@@ -264,9 +288,10 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
            // remove the .in
 | 
			
		||||
            $filepath = substr($filepath, 0, -3);
 | 
			
		||||
            if (file_put_contents($filepath, $filecontents, LOCK_EX) === false) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('Could not write configuration file "%s"'), $filepath
 | 
			
		||||
            if (@file_put_contents($filepath, $filecontents, LOCK_EX) === false) {
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('Could not write configuration file "%s"'),
 | 
			
		||||
                    $filepath
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -280,7 +305,7 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            $parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception $e) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('Could not parse usher configuration in "%s": %s'),
 | 
			
		||||
                $usher_config, $e->getMessage()
 | 
			
		||||
            ));
 | 
			
		||||
@@ -291,7 +316,7 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            foreach ($stanzas as $stanza_line) {
 | 
			
		||||
                if ($stanza_line['key'] == 'server' &&
 | 
			
		||||
                    $stanza_line['values'][0] == $shortname) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                        __('usher configuration already contains a server '.
 | 
			
		||||
                           'entry named "%s"'),
 | 
			
		||||
                        $shortname
 | 
			
		||||
@@ -315,9 +340,10 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
        // FIXME: more sanity - what happens on failing writes? we do not
 | 
			
		||||
        // have a backup copy of usher.conf around...
 | 
			
		||||
        if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not write usher configuration file "%s"'), $usher_config
 | 
			
		||||
        if (@file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('Could not write usher configuration file "%s"'),
 | 
			
		||||
                $usher_config
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -325,6 +351,9 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        // step 6) reload usher to pick up the new configuration
 | 
			
		||||
        //
 | 
			
		||||
        IDF_Scm_Monotone_Usher::reload();
 | 
			
		||||
 | 
			
		||||
        // commit the guard, so the newly created project is not deleted
 | 
			
		||||
        $projectGuard->commit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -345,8 +374,8 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        $mtn = IDF_Scm_Monotone::factory($project);
 | 
			
		||||
        $stdio = $mtn->getStdio();
 | 
			
		||||
 | 
			
		||||
        $projectpath = self::_get_project_path($project);
 | 
			
		||||
        $auth_ids    = self::_get_authorized_user_ids($project);
 | 
			
		||||
        $projectpath = $this->_get_project_path($project);
 | 
			
		||||
        $auth_ids    = $this->_get_authorized_user_ids($project);
 | 
			
		||||
        $key_ids     = array();
 | 
			
		||||
        foreach ($auth_ids as $auth_id) {
 | 
			
		||||
            $sql = new Pluf_SQL('user=%s', array($auth_id));
 | 
			
		||||
@@ -361,9 +390,10 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
        $write_permissions = implode("\n", $key_ids);
 | 
			
		||||
        $rcfile = $projectpath.'/write-permissions';
 | 
			
		||||
        if (file_put_contents($rcfile, $write_permissions, LOCK_EX) === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not write write-permissions file "%s"'), $rcfile
 | 
			
		||||
        if (@file_put_contents($rcfile, $write_permissions, LOCK_EX) === false) {
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('Could not write write-permissions file "%s"'),
 | 
			
		||||
                $rcfile
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -382,11 +412,13 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
                array('key' => 'allow', 'values' => array('*')),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $read_permissions = IDF_Scm_Monotone_BasicIO::compile(array($stanza));
 | 
			
		||||
        $rcfile = $projectpath.'/read-permissions';
 | 
			
		||||
        if (file_put_contents($rcfile, $read_permissions, LOCK_EX) === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not write read-permissions file "%s"'), $rcfile
 | 
			
		||||
        if (@file_put_contents($rcfile, $read_permissions, LOCK_EX) === false) {
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('Could not write read-permissions file "%s"'),
 | 
			
		||||
                $rcfile
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -401,16 +433,16 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
        $serverRestartRequired = false;
 | 
			
		||||
        if ($project->private && file_exists($projectfile) && is_link($projectfile)) {
 | 
			
		||||
            if (!unlink($projectfile)) {
 | 
			
		||||
                IDF_Scm_Exception(sprintf(
 | 
			
		||||
            if (!@unlink($projectfile)) {
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('Could not remove symlink "%s"'), $projectfile
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
            $serverRestartRequired = true;
 | 
			
		||||
        } else
 | 
			
		||||
        if (!$project->private && !file_exists($projectfile)) {
 | 
			
		||||
            if (!symlink($templatefile, $projectfile)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
            if (!@symlink($templatefile, $projectfile)) {
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('Could not create symlink "%s"'), $projectfile
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
@@ -422,6 +454,9 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            // seems to be ignored when the server should be started
 | 
			
		||||
            // again immediately afterwards
 | 
			
		||||
            IDF_Scm_Monotone_Usher::killServer($project->shortname);
 | 
			
		||||
            // give usher some time to cool down, otherwise it might hang
 | 
			
		||||
            // (see https://code.monotone.ca/p/contrib/issues/175/)
 | 
			
		||||
            sleep(2);
 | 
			
		||||
            IDF_Scm_Monotone_Usher::startServer($project->shortname);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -443,8 +478,8 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
        $usher_config = Pluf::f('mtn_usher_conf', false);
 | 
			
		||||
        if (!$usher_config || !is_writable($usher_config)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_usher_conf" does not exist or is not writable.')
 | 
			
		||||
            $this->_diagnoseProblem(
 | 
			
		||||
                 __('"mtn_usher_conf" does not exist or is not writable')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -453,16 +488,16 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
        $projecttempl = Pluf::f('mtn_repositories', false);
 | 
			
		||||
        if ($projecttempl === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file.')
 | 
			
		||||
            $this->_diagnoseProblem(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $projectpath = sprintf($projecttempl, $shortname);
 | 
			
		||||
        if (file_exists($projectpath)) {
 | 
			
		||||
            if (!self::_delete_recursive($projectpath)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('One or more paths underknees %s could not be deleted.'), $projectpath
 | 
			
		||||
            if (!$this->_delete_recursive($projectpath)) {
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('One or more paths underneath %s could not be deleted'), $projectpath
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -473,8 +508,9 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        if ($keyname && $keyhash &&
 | 
			
		||||
            file_exists($keydir .'/'. $keyname . '.' . $keyhash)) {
 | 
			
		||||
            if (!@unlink($keydir .'/'. $keyname . '.' . $keyhash)) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    __('Could not delete client private key %s'), $keyname
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('Could not delete client private key "%s"'),
 | 
			
		||||
                    $keyname
 | 
			
		||||
                ));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -485,7 +521,7 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            $parsed_config = IDF_Scm_Monotone_BasicIO::parse($usher_rc);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception $e) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('Could not parse usher configuration in "%s": %s'),
 | 
			
		||||
                $usher_config, $e->getMessage()
 | 
			
		||||
            ));
 | 
			
		||||
@@ -505,9 +541,10 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
        // FIXME: more sanity - what happens on failing writes? we do not
 | 
			
		||||
        // have a backup copy of usher.conf around...
 | 
			
		||||
        if (file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('Could not write usher configuration file "%s"'), $usher_config
 | 
			
		||||
        if (@file_put_contents($usher_config, $usher_rc, LOCK_EX) === false) {
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('Could not write usher configuration file "%s"'),
 | 
			
		||||
                $usher_config
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -528,6 +565,8 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $keyGuard = new IDF_Plugin_SyncMonotone_ModelGuard($key);
 | 
			
		||||
 | 
			
		||||
        foreach (Pluf::factory('IDF_Project')->getList() as $project) {
 | 
			
		||||
            $conf = new IDF_Conf();
 | 
			
		||||
            $conf->setProject($project);
 | 
			
		||||
@@ -535,8 +574,8 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            if ($scm != 'mtn')
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $projectpath = self::_get_project_path($project);
 | 
			
		||||
            $auth_ids    = self::_get_authorized_user_ids($project);
 | 
			
		||||
            $projectpath = $this->_get_project_path($project);
 | 
			
		||||
            $auth_ids    = $this->_get_authorized_user_ids($project);
 | 
			
		||||
            if (!in_array($key->user, $auth_ids))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
@@ -556,7 +595,7 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
                    $parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception $e) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                        __('Could not parse read-permissions for project "%s": %s'),
 | 
			
		||||
                        $shortname, $e->getMessage()
 | 
			
		||||
                    ));
 | 
			
		||||
@@ -598,10 +637,11 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
                $read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);
 | 
			
		||||
 | 
			
		||||
                if (file_put_contents($projectpath.'/read-permissions',
 | 
			
		||||
                if (@file_put_contents($projectpath.'/read-permissions',
 | 
			
		||||
                                      $read_perms, LOCK_EX) === false) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not write read-permissions for project "%s"'), $shortname
 | 
			
		||||
                    $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                        __('Could not write read-permissions for project "%s"'),
 | 
			
		||||
                        $shortname
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -611,9 +651,9 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            if (!in_array('*', $lines) && !in_array($mtn_key_id, $lines)) {
 | 
			
		||||
                $lines[] = $mtn_key_id;
 | 
			
		||||
            }
 | 
			
		||||
            if (file_put_contents($projectpath.'/write-permissions',
 | 
			
		||||
            if (@file_put_contents($projectpath.'/write-permissions',
 | 
			
		||||
                                  implode("\n", $lines) . "\n", LOCK_EX) === false) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('Could not write write-permissions file for project "%s"'),
 | 
			
		||||
                    $shortname
 | 
			
		||||
                ));
 | 
			
		||||
@@ -623,6 +663,8 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            $stdio = $mtn->getStdio();
 | 
			
		||||
            $stdio->exec(array('put_public_key', $key->content));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $keyGuard->commit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -651,8 +693,8 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            if ($scm != 'mtn')
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $projectpath = self::_get_project_path($project);
 | 
			
		||||
            $auth_ids    = self::_get_authorized_user_ids($project);
 | 
			
		||||
            $projectpath = $this->_get_project_path($project);
 | 
			
		||||
            $auth_ids    = $this->_get_authorized_user_ids($project);
 | 
			
		||||
            if (!in_array($key->user, $auth_ids))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
@@ -672,7 +714,7 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
                    $parsed_read_perms = IDF_Scm_Monotone_BasicIO::parse($read_perms);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception $e) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                    $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                        __('Could not parse read-permissions for project "%s": %s'),
 | 
			
		||||
                        $shortname, $e->getMessage()
 | 
			
		||||
                    ));
 | 
			
		||||
@@ -693,10 +735,11 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
 | 
			
		||||
                $read_perms = IDF_Scm_Monotone_BasicIO::compile($parsed_read_perms);
 | 
			
		||||
 | 
			
		||||
                if (file_put_contents($projectpath.'/read-permissions',
 | 
			
		||||
                if (@file_put_contents($projectpath.'/read-permissions',
 | 
			
		||||
                                      $read_perms, LOCK_EX) === false) {
 | 
			
		||||
                    throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                        __('Could not write read-permissions for project "%s"'), $shortname
 | 
			
		||||
                    $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                        __('Could not write read-permissions for project "%s"'),
 | 
			
		||||
                        $shortname
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -711,9 +754,9 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (file_put_contents($projectpath.'/write-permissions',
 | 
			
		||||
                                  implode("\n", $lines) . "\n", LOCK_EX) === false) {
 | 
			
		||||
                throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
            if (@file_put_contents($projectpath.'/write-permissions',
 | 
			
		||||
                                   implode("\n", $lines) . "\n", LOCK_EX) === false) {
 | 
			
		||||
                $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                    __('Could not write write-permissions file for project "%s"'),
 | 
			
		||||
                    $shortname
 | 
			
		||||
                ));
 | 
			
		||||
@@ -762,7 +805,43 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function _get_authorized_user_ids($project)
 | 
			
		||||
    private function _get_project_path($project)
 | 
			
		||||
    {
 | 
			
		||||
        $projecttempl = Pluf::f('mtn_repositories', false);
 | 
			
		||||
        if ($projecttempl === false) {
 | 
			
		||||
            $this->_diagnoseProblem(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file.')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $projectpath = sprintf($projecttempl, $project->shortname);
 | 
			
		||||
        if (!file_exists($projectpath)) {
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('The project path %s does not exists.'), $projectpath
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        return $projectpath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function _mtn_exec($cmd)
 | 
			
		||||
    {
 | 
			
		||||
        $fullcmd = sprintf('%s %s %s',
 | 
			
		||||
            Pluf::f('idf_exec_cmd_prefix', ''),
 | 
			
		||||
            Pluf::f('mtn_path', 'mtn'),
 | 
			
		||||
            $cmd
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $output = $return = null;
 | 
			
		||||
        exec($fullcmd, $output, $return);
 | 
			
		||||
        if ($return != 0) {
 | 
			
		||||
            $this->_diagnoseProblem(sprintf(
 | 
			
		||||
                __('The command "%s" could not be executed.'), $cmd
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        return implode("\n", $output);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function _get_authorized_user_ids($project)
 | 
			
		||||
    {
 | 
			
		||||
        $mem = $project->getMembershipData();
 | 
			
		||||
        $members = array_merge((array)$mem['members'],
 | 
			
		||||
@@ -775,43 +854,7 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
        return $userids;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function _get_project_path($project)
 | 
			
		||||
    {
 | 
			
		||||
        $projecttempl = Pluf::f('mtn_repositories', false);
 | 
			
		||||
        if ($projecttempl === false) {
 | 
			
		||||
            throw new IDF_Scm_Exception(
 | 
			
		||||
                 __('"mtn_repositories" must be defined in your configuration file.')
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $projectpath = sprintf($projecttempl, $project->shortname);
 | 
			
		||||
        if (!file_exists($projectpath)) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('The project path %s does not exists.'), $projectpath
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        return $projectpath;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function _mtn_exec($cmd)
 | 
			
		||||
    {
 | 
			
		||||
        $fullcmd = sprintf('%s %s %s',
 | 
			
		||||
            Pluf::f('idf_exec_cmd_prefix', ''),
 | 
			
		||||
            Pluf::f('mtn_path', 'mtn'),
 | 
			
		||||
            $cmd
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $output = $return = null;
 | 
			
		||||
        exec($fullcmd, $output, $return);
 | 
			
		||||
        if ($return != 0) {
 | 
			
		||||
            throw new IDF_Scm_Exception(sprintf(
 | 
			
		||||
                __('The command "%s" could not be executed.'), $cmd
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        return implode("\n", $output);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static function _delete_recursive($path)
 | 
			
		||||
    private function _delete_recursive($path)
 | 
			
		||||
    {
 | 
			
		||||
        if (is_file($path) || is_link($path)) {
 | 
			
		||||
            return @unlink($path);
 | 
			
		||||
@@ -821,10 +864,48 @@ class IDF_Plugin_SyncMonotone
 | 
			
		||||
            $scan = glob(rtrim($path, '/') . '/*');
 | 
			
		||||
            $status = 0;
 | 
			
		||||
            foreach ($scan as $subpath) {
 | 
			
		||||
                $status |= self::_delete_recursive($subpath);
 | 
			
		||||
                $status |= $this->_delete_recursive($subpath);
 | 
			
		||||
            }
 | 
			
		||||
            $status |= rmdir($path);
 | 
			
		||||
            $status |= @rmdir($path);
 | 
			
		||||
            return $status;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function _diagnoseProblem($msg)
 | 
			
		||||
    {
 | 
			
		||||
        $system_err = error_get_last();
 | 
			
		||||
        if (!empty($system_err)) {
 | 
			
		||||
            $msg .= ': '.$system_err['message'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        error_reporting($this->old_err_rep);
 | 
			
		||||
        throw new IDF_Scm_Exception($msg);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A simple helper class that deletes the model instance if
 | 
			
		||||
 * it is not committed
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Plugin_SyncMonotone_ModelGuard
 | 
			
		||||
{
 | 
			
		||||
    private $model;
 | 
			
		||||
 | 
			
		||||
    public function __construct(Pluf_Model $m)
 | 
			
		||||
    {
 | 
			
		||||
        $this->model = $m;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __destruct()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->model == null)
 | 
			
		||||
            return;
 | 
			
		||||
        $this->model->delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function commit()
 | 
			
		||||
    {
 | 
			
		||||
        $this->model = null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -132,6 +132,46 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
        }
 | 
			
		||||
        return $projects[0];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the number of open/closed issues.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string Status ('open'), 'closed'
 | 
			
		||||
     * @param IDF_Tag Subfilter with a label (null)
 | 
			
		||||
     * @return int Count
 | 
			
		||||
     */
 | 
			
		||||
    public function getIssueCountByOwner($status='open')
 | 
			
		||||
    {
 | 
			
		||||
        switch ($status) {
 | 
			
		||||
        case 'open':
 | 
			
		||||
            $tags = implode(',', $this->getTagIdsByStatus('open'));
 | 
			
		||||
            break;
 | 
			
		||||
        case 'closed':
 | 
			
		||||
        default:
 | 
			
		||||
            $tags = implode(',', $this->getTagIdsByStatus('closed'));
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        $sqlIssueTable = Pluf::factory('IDF_Issue')->getSqlTable();
 | 
			
		||||
        $query = "SELECT uid AS id,COUNT(uid) AS nb
 | 
			
		||||
FROM (
 | 
			
		||||
    SELECT COALESCE(owner, -1) AS uid
 | 
			
		||||
    FROM $sqlIssueTable
 | 
			
		||||
    WHERE status IN ($tags)
 | 
			
		||||
    ) AS ff
 | 
			
		||||
GROUP BY uid";
 | 
			
		||||
 | 
			
		||||
        $db = Pluf::db();
 | 
			
		||||
        $dbData = $db->select($query);
 | 
			
		||||
        $ownerStatistics = array();
 | 
			
		||||
        foreach ($dbData as $k => $v) {
 | 
			
		||||
            $key = ($v['id'] === '-1') ? null : $v['id'];
 | 
			
		||||
            $ownerStatistics[$key] = (int)$v['nb'];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        arsort($ownerStatistics);
 | 
			
		||||
 | 
			
		||||
        return $ownerStatistics;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the number of open/closed issues.
 | 
			
		||||
@@ -233,6 +273,29 @@ class IDF_Project extends Pluf_Model
 | 
			
		||||
        return $tags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a list of relations which are available in this project as
 | 
			
		||||
     * associative array. Each key-value pair marks a set of orthogonal
 | 
			
		||||
     * relations. To ease processing, each of these pairs is included twice
 | 
			
		||||
     * in the array, once as key1 => key2 and once as key2 => key1.
 | 
			
		||||
     *
 | 
			
		||||
     * @return array List of relation names
 | 
			
		||||
     */
 | 
			
		||||
    public function getRelationsFromConfig()
 | 
			
		||||
    {
 | 
			
		||||
        $conf = $this->getConf();
 | 
			
		||||
        $rel = $conf->getVal('issue_relations', IDF_Form_IssueTrackingConf::init_relations);
 | 
			
		||||
        $relations = array();
 | 
			
		||||
        foreach (preg_split("/\015\012|\015|\012/", $rel, -1, PREG_SPLIT_NO_EMPTY) as $s) {
 | 
			
		||||
            $verbs = preg_split("/\s*,\s*/", $s, 2);
 | 
			
		||||
            if (count($verbs) == 1)
 | 
			
		||||
                $relations += array($verbs[0] => $verbs[0]);
 | 
			
		||||
            else
 | 
			
		||||
                $relations += array($verbs[0] => $verbs[1], $verbs[1] => $verbs[0]);
 | 
			
		||||
        }
 | 
			
		||||
        return $relations;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return membership data.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -88,22 +88,36 @@ class IDF_Scm
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Run exec and log some information.
 | 
			
		||||
     * Runs the given command and log some information.
 | 
			
		||||
     *
 | 
			
		||||
     * A previous version used plain exec(), but this should not be used
 | 
			
		||||
     * for various reasons, one being that this command does not preserve
 | 
			
		||||
     * trailing whitespace, which is essential for proper diff parsing.
 | 
			
		||||
     *
 | 
			
		||||
     * @param $caller Calling method
 | 
			
		||||
     * @param $cmd Command to run
 | 
			
		||||
     * @param &$out Array of output
 | 
			
		||||
     * @param &$return Return value
 | 
			
		||||
     * @return string Last line of the command
 | 
			
		||||
     */
 | 
			
		||||
    public static function exec($caller, $cmd, &$out=null, &$return=null)
 | 
			
		||||
    {
 | 
			
		||||
        $return = -1;
 | 
			
		||||
        Pluf_Log::stime('timer');
 | 
			
		||||
        $ret = exec($cmd, $out, $return);
 | 
			
		||||
        $fp = popen($cmd, 'r');
 | 
			
		||||
        $buf = '';
 | 
			
		||||
        if ($fp !== false) {
 | 
			
		||||
            while (!feof($fp)) {
 | 
			
		||||
                $buf .= fread($fp, 1024);
 | 
			
		||||
            }
 | 
			
		||||
            $return = pclose($fp);
 | 
			
		||||
        }
 | 
			
		||||
        $out = preg_split('/\r\n|\r|\n/', $buf);
 | 
			
		||||
        $elem = count($out);
 | 
			
		||||
        if ($elem > 0 && $out[$elem-1] === '')
 | 
			
		||||
            unset($out[$elem-1]);
 | 
			
		||||
        Pluf_Log::perf(array($caller, $cmd, Pluf_Log::etime('timer', 'total_exec')));
 | 
			
		||||
        Pluf_Log::debug(array($caller, $cmd, $out));
 | 
			
		||||
        Pluf_Log::inc('exec_calls');
 | 
			
		||||
        return $ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -325,7 +339,8 @@ class IDF_Scm
 | 
			
		||||
     * stdClass object {
 | 
			
		||||
     *  'additions' => array('path/to/file', 'path/to/directory', ...),
 | 
			
		||||
     *  'deletions' => array('path/to/file', 'path/to/directory', ...),
 | 
			
		||||
     *  'renames' => array('old/path/to/file' => 'new/path/to/file', ...)
 | 
			
		||||
     *  'renames' => array('old/path/to/file' => 'new/path/to/file', ...),
 | 
			
		||||
     *  'copies' => array('path/to/source' => 'path/to/target', ...),
 | 
			
		||||
     *  'patches' => array('path/to/file', ...),
 | 
			
		||||
     *  'properties' => array('path/to/file' => array(
 | 
			
		||||
     *              'propname' => 'propvalue', 'deletedprop' => null, ...)
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,7 @@ class IDF_Scm_Git extends IDF_Scm
 | 
			
		||||
            'additions'  => array(),
 | 
			
		||||
            'deletions'  => array(),
 | 
			
		||||
            'renames'    => array(),
 | 
			
		||||
            'copies'     => array(),
 | 
			
		||||
            'patches'    => array(),
 | 
			
		||||
            'properties' => array(),
 | 
			
		||||
        );
 | 
			
		||||
@@ -218,15 +219,15 @@ class IDF_Scm_Git extends IDF_Scm
 | 
			
		||||
                                                $cmd, $return,
 | 
			
		||||
                                                implode("\n", $out)));
 | 
			
		||||
        }
 | 
			
		||||
        rsort($out);
 | 
			
		||||
        $res = array();
 | 
			
		||||
        foreach ($out as $b) {
 | 
			
		||||
            $elts = explode(' ', $b, 2);
 | 
			
		||||
            $tag = substr(trim($elts[1]), 10);  // Remove refs/tags/ prefix
 | 
			
		||||
            $res[$tag] = '';
 | 
			
		||||
        }
 | 
			
		||||
        krsort($res);
 | 
			
		||||
        $this->cache['tags'] = $res;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -345,6 +346,14 @@ class IDF_Scm_Git extends IDF_Scm
 | 
			
		||||
        if (!preg_match('/<(.*)>/', $author, $match)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        // FIXME: newer git versions know a i18n.commitencoding setting which
 | 
			
		||||
        // leads to another header, "encoding", with which we _could_ try to
 | 
			
		||||
        // decode the string into utf8. Unfortunately this does not always
 | 
			
		||||
        // work, especially not in older repos, so we would then still have
 | 
			
		||||
        // to supply some fallback.
 | 
			
		||||
        if (!mb_check_encoding($match[1], 'UTF-8')) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $sql = new Pluf_SQL('login=%s', array($match[1]));
 | 
			
		||||
        $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen()));
 | 
			
		||||
        if ($users->count() > 0) {
 | 
			
		||||
@@ -637,7 +646,11 @@ class IDF_Scm_Git extends IDF_Scm
 | 
			
		||||
        $c['full_message'] = IDF_Commit::toUTF8($c['full_message']);
 | 
			
		||||
        $c['title'] = IDF_Commit::toUTF8($c['title']);
 | 
			
		||||
        if (isset($c['parents'])) {
 | 
			
		||||
            $c['parents'] = explode(' ', trim($c['parents']));
 | 
			
		||||
            $c['parents'] = preg_split('/ /', trim($c['parents']), -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
        } else {
 | 
			
		||||
            // this is actually an error state because we should _always_
 | 
			
		||||
            // be able to parse the parents line with every git version
 | 
			
		||||
            $c['parents'] = null;
 | 
			
		||||
        }
 | 
			
		||||
        $res[] = (object) $c;
 | 
			
		||||
        return $res;
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ class IDF_Scm_Mercurial_LogStyle
 | 
			
		||||
                . "\n"
 | 
			
		||||
                . 'file_del = "{file_del}\0"'
 | 
			
		||||
                . "\n"
 | 
			
		||||
                . 'file_copy = "{name}\0{source}\0"'
 | 
			
		||||
                . 'file_copy = "{source}\0{name}\0"'
 | 
			
		||||
                . "\n";
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new IDF_Scm_Exception('invalid type ' . $type);
 | 
			
		||||
@@ -457,24 +457,32 @@ class IDF_Scm_Mercurial extends IDF_Scm
 | 
			
		||||
            'patches'    => preg_split('/\0/', $log->file_mods, -1, PREG_SPLIT_NO_EMPTY),
 | 
			
		||||
            // hg has no support for built-in attributes, so this keeps empty
 | 
			
		||||
            'properties' => array(),
 | 
			
		||||
            // this is filled below
 | 
			
		||||
            // these two are filled below
 | 
			
		||||
            'copies'     => array(),
 | 
			
		||||
            'renames'    => array(),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $file_copies = preg_split('/\0/', $log->file_copies, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
 | 
			
		||||
        // FIXME: copies are only treated as renames if they have an add _and_
 | 
			
		||||
        // an drop, otherwise they're just treated as adds
 | 
			
		||||
        // copies are treated as renames if they have an add _and_ a drop;
 | 
			
		||||
        // only if they only have an add, but no drop, they're treated as copies
 | 
			
		||||
        for ($i=0; $i<count($file_copies); $i+=2) {
 | 
			
		||||
            $new = $file_copies[$i];
 | 
			
		||||
            $old = $file_copies[$i+1];
 | 
			
		||||
            $newidx = array_search($new, $return->additions);
 | 
			
		||||
            $oldidx = array_search($old, $return->deletions);
 | 
			
		||||
            if ($newidx !== false && $oldidx !== false) {
 | 
			
		||||
                $return->renames[$old] = $new;
 | 
			
		||||
                unset($return->additions[$newidx]);
 | 
			
		||||
                unset($return->deletions[$oldidx]);
 | 
			
		||||
            $src = $file_copies[$i];
 | 
			
		||||
            $trg = $file_copies[$i+1];
 | 
			
		||||
            $srcidx = array_search($src, $return->deletions);
 | 
			
		||||
            $trgidx = array_search($trg, $return->additions);
 | 
			
		||||
            if ($srcidx !== false && $trgidx !== false) {
 | 
			
		||||
                $return->renames[$src] = $trg;
 | 
			
		||||
                unset($return->deletions[$srcidx]);
 | 
			
		||||
                unset($return->additions[$trgidx]);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if ($srcidx === false && $trgidx !== false) {
 | 
			
		||||
                $return->copies[$src] = $trg;
 | 
			
		||||
                unset($return->additions[$trgidx]);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // file sutures (counter-operation to copy) not supported
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
@@ -531,7 +539,8 @@ class IDF_Scm_Mercurial extends IDF_Scm
 | 
			
		||||
            if ($line == "\0") {
 | 
			
		||||
                $headers_processed = false;
 | 
			
		||||
                if (count($c) > 0) {
 | 
			
		||||
                    $c['full_message'] = trim($c['full_message']);
 | 
			
		||||
                    if (array_key_exists('full_message', $c))
 | 
			
		||||
                        $c['full_message'] = trim($c['full_message']);
 | 
			
		||||
                    $res[] = (object) $c;
 | 
			
		||||
                }
 | 
			
		||||
                continue;
 | 
			
		||||
@@ -547,10 +556,8 @@ class IDF_Scm_Mercurial extends IDF_Scm
 | 
			
		||||
                    $c['commit'] = $match[2];
 | 
			
		||||
                    $c['tree'] = $c['commit'];
 | 
			
		||||
                    $c['full_message'] = '';
 | 
			
		||||
                } elseif ($match[1] == 'user') {
 | 
			
		||||
                } elseif ($match[1] == 'author') {
 | 
			
		||||
                    $c['author'] = $match[2];
 | 
			
		||||
                } elseif ($match[1] == 'summary') {
 | 
			
		||||
                    $c['title'] = $match[2];
 | 
			
		||||
                } elseif ($match[1] == 'branch') {
 | 
			
		||||
                    $c['branch'] = empty($match[2]) ? 'default' : $match[2];
 | 
			
		||||
                } elseif ($match[1] == 'parents') {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,10 +31,10 @@ class IDF_Scm_Monotone extends IDF_Scm
 | 
			
		||||
    /** the minimum supported interface version */
 | 
			
		||||
    public static $MIN_INTERFACE_VERSION = 13.0;
 | 
			
		||||
 | 
			
		||||
    private $stdio;
 | 
			
		||||
 | 
			
		||||
    private static $instances = array();
 | 
			
		||||
 | 
			
		||||
    private $stdio;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor
 | 
			
		||||
     */
 | 
			
		||||
@@ -609,6 +609,7 @@ class IDF_Scm_Monotone extends IDF_Scm
 | 
			
		||||
            'additions'  => array(),
 | 
			
		||||
            'deletions'  => array(),
 | 
			
		||||
            'renames'    => array(),
 | 
			
		||||
            'copies'     => array(),
 | 
			
		||||
            'patches'    => array(),
 | 
			
		||||
            'properties' => array(),
 | 
			
		||||
        );
 | 
			
		||||
@@ -698,6 +699,29 @@ class IDF_Scm_Monotone extends IDF_Scm
 | 
			
		||||
        return (object) $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see IDF_Scm::getProperties()
 | 
			
		||||
     */
 | 
			
		||||
    public function getProperties($rev, $path='')
 | 
			
		||||
    {
 | 
			
		||||
        $out = $this->stdio->exec(array('interface_version'));
 | 
			
		||||
        // support for querying file attributes of committed revisions
 | 
			
		||||
        // was added for mtn 1.1 (interface version 13.1)
 | 
			
		||||
        if (floatval($out) < 13.1)
 | 
			
		||||
            return array();
 | 
			
		||||
 | 
			
		||||
        $out = $this->stdio->exec(array('get_attributes', $path), array('r' => $rev));
 | 
			
		||||
        $stanzas = IDF_Scm_Monotone_BasicIO::parse($out);
 | 
			
		||||
        $res = array();
 | 
			
		||||
 | 
			
		||||
        foreach ($stanzas as $stanza) {
 | 
			
		||||
            $line = $stanza[0];
 | 
			
		||||
            $res[$line['values'][0]] = $line['values'][1];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see IDF_Scm::getExtraProperties
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ class IDF_Scm_Monotone_Usher
 | 
			
		||||
        if ($conn == 'none')
 | 
			
		||||
            return array();
 | 
			
		||||
 | 
			
		||||
        return preg_split('/[ ]/', $conn);
 | 
			
		||||
        return preg_split('/[ ]/', $conn, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -73,10 +73,10 @@ class IDF_Scm_Monotone_Usher
 | 
			
		||||
        if ($conn == 'none')
 | 
			
		||||
            return array();
 | 
			
		||||
 | 
			
		||||
        $single_conns = preg_split('/[ ]/', $conn);
 | 
			
		||||
        $single_conns = preg_split('/[ ]/', $conn, -1, PREG_SPLIT_NO_EMPTY);
 | 
			
		||||
        $ret = array();
 | 
			
		||||
        foreach ($single_conns as $conn) {
 | 
			
		||||
            preg_match('/\((\w+)\)([^:]+):(\d+)/', $conn, $matches);
 | 
			
		||||
            preg_match('/\(([^)]+)\)([^:]+):(\d+)/', $conn, $matches);
 | 
			
		||||
            $ret[$matches[1]][] = (object)array(
 | 
			
		||||
                'server'    => $matches[1],
 | 
			
		||||
                'address'   => $matches[2],
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@
 | 
			
		||||
 */
 | 
			
		||||
class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public $username = '';
 | 
			
		||||
    public $password = '';
 | 
			
		||||
    private $assoc = array('dir' => 'tree',
 | 
			
		||||
@@ -48,11 +47,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
 | 
			
		||||
    public function isAvailable()
 | 
			
		||||
    {
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --xml --username=%s --password=%s %s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info', '--xml'), $this->repo);
 | 
			
		||||
        $xmlInfo = self::shell_exec('IDF_Scm_Svn::isAvailable', $cmd);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
@@ -163,12 +158,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            return IDF_Scm::REVISION_VALID;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo),
 | 
			
		||||
                       escapeshellarg($rev));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info'), $this->repo, $rev);
 | 
			
		||||
        self::exec('IDF_Scm_Svn::validateRevision', $cmd, $out, $ret);
 | 
			
		||||
 | 
			
		||||
        if ($ret == 0)
 | 
			
		||||
@@ -176,7 +166,6 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        return IDF_Scm::REVISION_INVALID;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Test a given object hash.
 | 
			
		||||
     *
 | 
			
		||||
@@ -191,12 +180,9 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Else, test the path on revision
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --xml --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($path)),
 | 
			
		||||
                       escapeshellarg($rev));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info', '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($path),
 | 
			
		||||
                             $rev);
 | 
			
		||||
        $xmlInfo = self::shell_exec('IDF_Scm_Svn::testHash', $cmd);
 | 
			
		||||
 | 
			
		||||
        // If exception is thrown, return false
 | 
			
		||||
@@ -218,12 +204,9 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
 | 
			
		||||
    public function getTree($commit, $folder='/', $branch=null)
 | 
			
		||||
    {
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --no-auth-cache --xml --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($folder)),
 | 
			
		||||
                       escapeshellarg($commit));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('ls', '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($folder),
 | 
			
		||||
                             $commit);
 | 
			
		||||
        $xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getTree', $cmd));
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $folder = (strlen($folder) and ($folder != '/')) ? $folder.'/' : '';
 | 
			
		||||
@@ -248,7 +231,6 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the commit message of a revision revision.
 | 
			
		||||
     *
 | 
			
		||||
@@ -260,12 +242,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        if (isset($this->cache['commitmess'][$rev])) {
 | 
			
		||||
            return $this->cache['commitmess'][$rev];
 | 
			
		||||
        }
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' log --no-auth-cache --xml --limit 1 --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo),
 | 
			
		||||
                       escapeshellarg($rev));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('log', '--xml', '--limit', '1'), $this->repo, $rev);
 | 
			
		||||
        try {
 | 
			
		||||
            $xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getCommitMessage', $cmd));
 | 
			
		||||
            $this->cache['commitmess'][$rev] = (string) $xml->logentry->msg;
 | 
			
		||||
@@ -281,12 +258,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        if ($rev == null) {
 | 
			
		||||
            $rev = 'HEAD';
 | 
			
		||||
        }
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --xml --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($filename)),
 | 
			
		||||
                       escapeshellarg($rev));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info', '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($filename), $rev);
 | 
			
		||||
        $xml = simplexml_load_string(self::shell_exec('IDF_Scm_Svn::getPathInfo', $cmd));
 | 
			
		||||
        if (!isset($xml->entry)) {
 | 
			
		||||
            return false;
 | 
			
		||||
@@ -308,12 +281,9 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
 | 
			
		||||
    public function getFile($def, $cmd_only=false)
 | 
			
		||||
    {
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' cat --no-auth-cache --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($def->fullpath)),
 | 
			
		||||
                       escapeshellarg($def->rev));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('cat'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($def->fullpath),
 | 
			
		||||
                             $def->rev);
 | 
			
		||||
        return ($cmd_only) ?
 | 
			
		||||
            $cmd : self::shell_exec('IDF_Scm_Svn::getFile', $cmd);
 | 
			
		||||
    }
 | 
			
		||||
@@ -329,11 +299,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            return $this->cache['branches'];
 | 
			
		||||
        }
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --no-auth-cache --username=%s --password=%s %s@HEAD',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/branches'));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('ls'), $this->repo.'/branches', 'HEAD');
 | 
			
		||||
        self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
 | 
			
		||||
        if ($ret == 0) {
 | 
			
		||||
            foreach ($out as $entry) {
 | 
			
		||||
@@ -344,11 +310,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        ksort($res);
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --username=%s --password=%s %s@HEAD',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/trunk'));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
 | 
			
		||||
        $cmd = $this->svnCmd(array('info'), $this->repo.'/trunk', 'HEAD');
 | 
			
		||||
        self::exec('IDF_Scm_Svn::getBranches', $cmd, $out, $ret);
 | 
			
		||||
        if ($ret == 0) {
 | 
			
		||||
            $res = array('trunk' => 'trunk') + $res;
 | 
			
		||||
@@ -368,11 +331,7 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            return $this->cache['tags'];
 | 
			
		||||
        }
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' ls --no-auth-cache --username=%s --password=%s %s@HEAD',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/tags'));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('ls'), $this->repo.'/tags', 'HEAD');
 | 
			
		||||
        self::exec('IDF_Scm_Svn::getTags', $cmd, $out, $ret);
 | 
			
		||||
        if ($ret == 0) {
 | 
			
		||||
            foreach ($out as $entry) {
 | 
			
		||||
@@ -412,7 +371,6 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        return array();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get commit details.
 | 
			
		||||
     *
 | 
			
		||||
@@ -426,12 +384,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' log --no-auth-cache --xml --limit 1 -v --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo),
 | 
			
		||||
                       escapeshellarg($commit));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('log', '--xml', '--limit', '1', '-v'),
 | 
			
		||||
                             $this->repo, $commit);
 | 
			
		||||
        $xmlRes = self::shell_exec('IDF_Scm_Svn::getCommit', $cmd);
 | 
			
		||||
        $xml = simplexml_load_string($xmlRes);
 | 
			
		||||
        $res['author'] = (string) $xml->logentry->author;
 | 
			
		||||
@@ -473,15 +427,87 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
    private function getDiff($rev='HEAD')
 | 
			
		||||
    {
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' diff --no-auth-cache -c %s --username=%s --password=%s %s',
 | 
			
		||||
                       escapeshellarg($rev),
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('diff', '-c', $rev), $this->repo);
 | 
			
		||||
        return self::shell_exec('IDF_Scm_Svn::getDiff', $cmd);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see IDF_Scm::getChanges()
 | 
			
		||||
     */
 | 
			
		||||
    public function getChanges($commit)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $cmd = $this->svnCmd(array('log', '--xml', '-v'), $this->repo, $commit);
 | 
			
		||||
        $out = array();
 | 
			
		||||
        $out = self::shell_exec('IDF_Scm_Svn::getChanges', $cmd);
 | 
			
		||||
        $xml = simplexml_load_string($out);
 | 
			
		||||
        if (count($xml) == 0) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        $entry = current($xml);
 | 
			
		||||
 | 
			
		||||
        $return = (object) array(
 | 
			
		||||
            'additions'  => array(),
 | 
			
		||||
            'deletions'  => array(),
 | 
			
		||||
            'patches'    => array(),
 | 
			
		||||
            // while SVN has support for attributes, we cannot see their changes
 | 
			
		||||
            // in the log's XML unfortunately
 | 
			
		||||
            'properties' => array(),
 | 
			
		||||
            'copies'     => array(),
 | 
			
		||||
            'renames'    => array(),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        foreach ($entry->paths->path as $p) {
 | 
			
		||||
            $path = (string) $p;
 | 
			
		||||
            foreach ($p->attributes() as $k => $v) {
 | 
			
		||||
                $key = (string) $k;
 | 
			
		||||
                $val = (string) $v;
 | 
			
		||||
                if ($key != 'action')
 | 
			
		||||
                    continue;
 | 
			
		||||
                if ($val == 'M')
 | 
			
		||||
                    $return->patches[] = $path;
 | 
			
		||||
                else if ($val == 'A')
 | 
			
		||||
                    $return->additions[] = $path;
 | 
			
		||||
                else if ($val == 'D')
 | 
			
		||||
                    $return->deletions[] = $path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // copies are treated as renames if they have an add _and_ a drop;
 | 
			
		||||
        // only if they only have an add, but no drop, they're treated as copies
 | 
			
		||||
        foreach ($entry->paths->path as $p) {
 | 
			
		||||
            $trg = (string) $p;
 | 
			
		||||
            $src = null;
 | 
			
		||||
            foreach ($p->attributes() as $k => $v) {
 | 
			
		||||
                if ((string) $k == 'copyfrom-path') {
 | 
			
		||||
                    $src = (string) $v;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($src == null)
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            $srcidx = array_search($src, $return->deletions);
 | 
			
		||||
            $trgidx = array_search($trg, $return->additions);
 | 
			
		||||
            if ($srcidx !== false && $trgidx !== false) {
 | 
			
		||||
                $return->renames[$src] = $trg;
 | 
			
		||||
                unset($return->deletions[$srcidx]);
 | 
			
		||||
                unset($return->additions[$trgidx]);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if ($srcidx === false && $trgidx !== false) {
 | 
			
		||||
                $return->copies[$src] = $trg;
 | 
			
		||||
                unset($return->additions[$trgidx]);
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // file sutures (counter-operation to copy) not supported
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get latest changes.
 | 
			
		||||
@@ -491,20 +517,15 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
     *
 | 
			
		||||
     * @return array Changes.
 | 
			
		||||
     */
 | 
			
		||||
    public function getChangeLog($branch=null, $n=10)
 | 
			
		||||
    public function getChangeLog($rev=null, $n=10)
 | 
			
		||||
    {
 | 
			
		||||
        if ($branch != 'HEAD' and !preg_match('/^\d+$/', $branch)) {
 | 
			
		||||
        if ($rev != 'HEAD' and !preg_match('/^\d+$/', $rev)) {
 | 
			
		||||
            // we accept only revisions or HEAD
 | 
			
		||||
            $branch = 'HEAD';
 | 
			
		||||
            $rev = 'HEAD';
 | 
			
		||||
        }
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' log --no-auth-cache --xml -v --limit %s --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($n),
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo),
 | 
			
		||||
                       escapeshellarg($branch));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('log', '--xml', '-v', '--limit', $n),
 | 
			
		||||
                             $this->repo.'@'.$rev);
 | 
			
		||||
        $xmlRes = self::shell_exec('IDF_Scm_Svn::getChangeLog', $cmd);
 | 
			
		||||
        $xml = simplexml_load_string($xmlRes);
 | 
			
		||||
        foreach ($xml->logentry as $entry) {
 | 
			
		||||
@@ -520,7 +541,6 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get additionnals properties on path and revision
 | 
			
		||||
     *
 | 
			
		||||
@@ -531,12 +551,8 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
    public function getProperties($rev, $path='')
 | 
			
		||||
    {
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' proplist --no-auth-cache --xml --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($path)),
 | 
			
		||||
                       escapeshellarg($rev));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('proplist', '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($path), $rev);
 | 
			
		||||
        $xmlProps = self::shell_exec('IDF_Scm_Svn::getProperties', $cmd);
 | 
			
		||||
        $props = simplexml_load_string($xmlProps);
 | 
			
		||||
 | 
			
		||||
@@ -554,7 +570,6 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
        return $res;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a specific additionnal property on path and revision
 | 
			
		||||
     *
 | 
			
		||||
@@ -566,20 +581,14 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
    private function getProperty($property, $rev, $path='')
 | 
			
		||||
    {
 | 
			
		||||
        $res = array();
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' propget --no-auth-cache --xml %s --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($property),
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo.'/'.self::smartEncode($path)),
 | 
			
		||||
                       escapeshellarg($rev));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('propget', $property, '--xml'),
 | 
			
		||||
                             $this->repo.'/'.self::smartEncode($path), $rev);
 | 
			
		||||
        $xmlProp = self::shell_exec('IDF_Scm_Svn::getProperty', $cmd);
 | 
			
		||||
        $prop = simplexml_load_string($xmlProp);
 | 
			
		||||
 | 
			
		||||
        return (string) $prop->target->property;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the number of the last commit in the repository.
 | 
			
		||||
     *
 | 
			
		||||
@@ -590,16 +599,38 @@ class IDF_Scm_Svn extends IDF_Scm
 | 
			
		||||
    public function getLastCommit($rev='HEAD')
 | 
			
		||||
    {
 | 
			
		||||
        $xmlInfo = '';
 | 
			
		||||
        $cmd = sprintf(Pluf::f('svn_path', 'svn').' info --no-auth-cache --xml --username=%s --password=%s %s@%s',
 | 
			
		||||
                       escapeshellarg($this->username),
 | 
			
		||||
                       escapeshellarg($this->password),
 | 
			
		||||
                       escapeshellarg($this->repo),
 | 
			
		||||
                       escapeshellarg($rev));
 | 
			
		||||
        $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;
 | 
			
		||||
        $cmd = $this->svnCmd(array('info', '--xml'), $this->repo, $rev);
 | 
			
		||||
        $xmlInfo = self::shell_exec('IDF_Scm_Svn::getLastCommit', $cmd);
 | 
			
		||||
 | 
			
		||||
        $xml = simplexml_load_string($xmlInfo);
 | 
			
		||||
        return (string) $xml->entry->commit['revision'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function svnCmd($args = array(), $repoarg = null, $revarg = null)
 | 
			
		||||
    {
 | 
			
		||||
        $cmdline = array();
 | 
			
		||||
        $cmdline[] = Pluf::f('idf_exec_cmd_prefix', '');
 | 
			
		||||
        $cmdline[] = Pluf::f('svn_path', 'svn');
 | 
			
		||||
        $cmdline[] = '--no-auth-cache';
 | 
			
		||||
        $cmdline[] = '--username='.escapeshellarg($this->username);
 | 
			
		||||
        $cmdline[] = '--password='.escapeshellarg($this->password);
 | 
			
		||||
 | 
			
		||||
        foreach ($args as $arg) {
 | 
			
		||||
            $cmdline[] = escapeshellarg($arg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($repoarg != null) {
 | 
			
		||||
            if ($revarg != null) {
 | 
			
		||||
                $repoarg .= '@'.$revarg;
 | 
			
		||||
            }
 | 
			
		||||
            $cmdline[] = escapeshellarg($repoarg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($revarg != null) {
 | 
			
		||||
            $cmdline[] = '--revision='.escapeshellarg($revarg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return implode(' ', $cmdline);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ class IDF_Template_IssueComment extends Pluf_Template_Tag
 | 
			
		||||
                      implode('|', $nouns);
 | 
			
		||||
            $text = IDF_Template_safePregReplace('#((?:'.$prefix.')(?:\s+r?))([0-9a-f]{1,40}((?:\s+and|\s+or|,)\s+r?[0-9a-f]{1,40})*)\b#i',
 | 
			
		||||
                                                 array($this, 'callbackCommits'), $text);
 | 
			
		||||
            $text = IDF_Template_safePregReplace('=(src:)([^\s@#,\(\)\\\\]+(?:(\\\\)[\s@#][^\s@#,\(\)\\\\]+){0,})+(?:\@([^\s#,]+))(?:#(\d+))?=im',
 | 
			
		||||
            $text = IDF_Template_safePregReplace('=(src:)([^\s@#,\(\)\\\\]+(?:(\\\\)[\s@#][^\s@#,\(\)\\\\]+){0,})+(?:\@([^\s#,]+))?(?:#(\d+))?=im',
 | 
			
		||||
                                                 array($this, 'callbackSource'), $text);
 | 
			
		||||
        }
 | 
			
		||||
        if ($wordwrap) $text = Pluf_Text::wrapHtml($text, 69, "\n");
 | 
			
		||||
 
 | 
			
		||||
@@ -90,40 +90,69 @@ class IDF_Template_MarkdownPrefilter extends Pluf_Text_HTML_Filter
 | 
			
		||||
                                     );
 | 
			
		||||
 | 
			
		||||
    public $allowed = array(
 | 
			
		||||
                            'a' => array('href', 'title', 'rel'),
 | 
			
		||||
                            'abbr' => array('title'),
 | 
			
		||||
                            'address' => array(),
 | 
			
		||||
                            'b' => array(),
 | 
			
		||||
                            'blockquote' => array(),
 | 
			
		||||
                            'br' => array(),
 | 
			
		||||
                            'caption' => array(),
 | 
			
		||||
                            'code' => array(),
 | 
			
		||||
                            'dd' => array(),
 | 
			
		||||
                            'del' => array('cite', 'class', 'datetime', 'dir', 'id', 'title'),
 | 
			
		||||
                            'div' => array('align', 'class'),
 | 
			
		||||
                            'dl' => array(),
 | 
			
		||||
                            'dt' => array(),
 | 
			
		||||
                            'em' => array(),
 | 
			
		||||
                            'h1' => array('id'),
 | 
			
		||||
                            'h2' => array('id'),
 | 
			
		||||
                            'h3' => array('id'),
 | 
			
		||||
                            'h4' => array('id'),
 | 
			
		||||
                            'h5' => array('id'),
 | 
			
		||||
                            'h6' => array('id'),
 | 
			
		||||
                            'hr' => array(),
 | 
			
		||||
                            'i' => array(),
 | 
			
		||||
                            'img' => array('src', 'class', 'alt', 'height', 'width', 'style'),
 | 
			
		||||
                            'ins' => array('cite', 'class', 'datetime', 'dir', 'id', 'title'),
 | 
			
		||||
                            'li' => array(),
 | 
			
		||||
                            'ol' => array(),
 | 
			
		||||
                            'p' => array('align', 'class'),
 | 
			
		||||
                            'pre' => array(),
 | 
			
		||||
                            'strong' => array(),
 | 
			
		||||
                            'table' => array('summary'),
 | 
			
		||||
                            'td' => array('style'),
 | 
			
		||||
                            'th' => array(),
 | 
			
		||||
                            'tr' => array(),
 | 
			
		||||
                            'ul' => array(),
 | 
			
		||||
                            'a' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                         'href', 'hreflang', 'rel'),
 | 
			
		||||
                            'abbr' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'address' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'b' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'blockquote' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                                  'cite'),
 | 
			
		||||
                            'br' => array('class', 'id', 'style', 'title'),
 | 
			
		||||
                            'caption' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                               'align'), // deprecated attribute),
 | 
			
		||||
                            'code' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'dd' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'del' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                           'cite', 'datetime'),
 | 
			
		||||
                            'div' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                           'align'), // deprecated attribute
 | 
			
		||||
                            'dl' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'dt' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'em' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'font' => array('class', 'dir', 'id', 'style', 'title', // deprecated element
 | 
			
		||||
                                            'color', 'face', 'size'), // deprecated attribute
 | 
			
		||||
                            'h1' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align'), // deprecated attribute
 | 
			
		||||
                            'h2' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align'), // deprecated attribute
 | 
			
		||||
                            'h3' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align'), // deprecated attribute
 | 
			
		||||
                            'h4' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align'), // deprecated attribute
 | 
			
		||||
                            'h5' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align'), // deprecated attribute
 | 
			
		||||
                            'h6' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align'), // deprecated attribute
 | 
			
		||||
                            'hr' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align', 'noshade', 'size', 'width'), // deprecated attribute
 | 
			
		||||
                            'i' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'img' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                           'src', 'alt', 'height', 'width'),
 | 
			
		||||
                            'ins' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                           'cite', 'datetime'),
 | 
			
		||||
                            'li' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'type'), // deprecated attribute
 | 
			
		||||
                            'ol' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'type'), // deprecated attribute
 | 
			
		||||
                            'p' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                         'align'), // deprecated attribute
 | 
			
		||||
                            'pre' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                           'width'), // deprecated attribute
 | 
			
		||||
                            'strong' => array('class', 'dir', 'id', 'style', 'title'),
 | 
			
		||||
                            'table' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                             'border', 'cellpadding', 'cellspacing', 'frame', 'rules', 'summary', 'width',
 | 
			
		||||
                                             'align', 'bgcolor'), // deprecated attribute
 | 
			
		||||
                            'td' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align', 'colspan', 'headers', 'rowspan', 'scope', 'valign',
 | 
			
		||||
                                          'bgcolor', 'height', 'nowrap', 'width'), // deprecated attribute
 | 
			
		||||
                            'th' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align', 'colspan', 'rowspan', 'scope', 'valign',
 | 
			
		||||
                                          'bgcolor', 'height', 'nowrap', 'width'), // deprecated attribute
 | 
			
		||||
                            'tr' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'align', 'valign',
 | 
			
		||||
                                          'bgcolor'), // deprecated attribute
 | 
			
		||||
                            'ul' => array('class', 'dir', 'id', 'style', 'title',
 | 
			
		||||
                                          'type'), // deprecated attribute
 | 
			
		||||
                            );
 | 
			
		||||
    // tags which should always be self-closing (e.g. "<img />")
 | 
			
		||||
    public $no_close = array(
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								src/IDF/Template/Tag/UploadUrl.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/IDF/Template/Tag/UploadUrl.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2011 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero 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 General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
class IDF_Template_Tag_UploadUrl extends Pluf_Template_Tag
 | 
			
		||||
{
 | 
			
		||||
    function start($file='')
 | 
			
		||||
    {
 | 
			
		||||
        echo IDF_Template_Tag_UploadUrl::url($file);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function url($file='')
 | 
			
		||||
    {
 | 
			
		||||
        return Pluf::f('url_upload', Pluf_Template_Tag_MediaUrl::url() . '/upload') . $file;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -150,7 +150,7 @@ class IDF_Upload extends Pluf_Model
 | 
			
		||||
        if ($this->id == '') {
 | 
			
		||||
            $this->creation_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
            $this->modif_dtime = gmdate('Y-m-d H:i:s');
 | 
			
		||||
            $this->md5 = md5_file (Pluf::f('upload_path') . '/' . $this->get_project()->shortname . '/files/' . $this->file);
 | 
			
		||||
            $this->md5 = md5_file ($this->getFullPath());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -167,6 +167,11 @@ class IDF_Upload extends Pluf_Model
 | 
			
		||||
        return Pluf::f('url_upload').'/'.$project->shortname.'/files/'.$this->file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function getFullPath()
 | 
			
		||||
    {
 | 
			
		||||
        return(Pluf::f('upload_path').'/'.$this->get_project()->shortname.'/files/'.$this->file);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * We drop the information from the timeline.
 | 
			
		||||
     */
 | 
			
		||||
@@ -256,4 +261,4 @@ class IDF_Upload extends Pluf_Model
 | 
			
		||||
        }
 | 
			
		||||
        Pluf_Translation::loadSetLocale($current_locale);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -202,7 +202,11 @@ class IDF_Views_Download
 | 
			
		||||
        $prj->inOr404($upload);
 | 
			
		||||
        $upload->downloads += 1;
 | 
			
		||||
        $upload->update();
 | 
			
		||||
        return new Pluf_HTTP_Response_Redirect($upload->getAbsoluteUrl($prj));
 | 
			
		||||
        $path = $upload->getFullPath();
 | 
			
		||||
        $mime = IDF_FileUtil::getMimeType($path);
 | 
			
		||||
        $render = new Pluf_HTTP_Response_File($path, $mime[0]);
 | 
			
		||||
        $render->headers["Content-MD5"] = $upload->md5;
 | 
			
		||||
        return $render;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,81 @@ class IDF_Views_Issue
 | 
			
		||||
                                               $params, $request);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * View the issue summary.
 | 
			
		||||
     * TODO Add thoses data in cache, and process it only after an issue update
 | 
			
		||||
     */
 | 
			
		||||
    public $summary_precond = array('IDF_Precondition::accessIssues');
 | 
			
		||||
    public function summary($request, $match)
 | 
			
		||||
    {
 | 
			
		||||
        $tagStatistics = array();
 | 
			
		||||
        $ownerStatistics = array();
 | 
			
		||||
        $status = array();
 | 
			
		||||
        $isTrackerEmpty = false;
 | 
			
		||||
        
 | 
			
		||||
        $prj = $request->project;
 | 
			
		||||
        $opened = $prj->getIssueCountByStatus('open');
 | 
			
		||||
        $closed = $prj->getIssueCountByStatus('closed');
 | 
			
		||||
 | 
			
		||||
        // Check if the tracker is empty
 | 
			
		||||
        if ($opened === 0 && $closed === 0) {
 | 
			
		||||
            $isTrackerEmpty = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            if ($opened > 0 || $closed > 0) {
 | 
			
		||||
                // Issue status statistics
 | 
			
		||||
                $status['Open'] = array($opened, (int)(100 * $opened / ($opened + $closed)));
 | 
			
		||||
                $status['Closed'] = array($closed, (int)(100 * $closed / ($opened + $closed)));
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if ($opened > 0) {
 | 
			
		||||
                // Issue owner statistics
 | 
			
		||||
                $owners = $prj->getIssueCountByOwner('open');
 | 
			
		||||
                foreach ($owners as $user => $nb) {
 | 
			
		||||
                    if ($user === '') {
 | 
			
		||||
                        $key = __('Not assigned');
 | 
			
		||||
                        $login = null;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $obj = Pluf::factory('Pluf_User')->getOne(array('filter'=>'id='.$user));
 | 
			
		||||
                        $key = $obj->first_name . ' ' . $obj->last_name;
 | 
			
		||||
                        $login = $obj->login;
 | 
			
		||||
                    }
 | 
			
		||||
                    $ownerStatistics[$key] = array($nb, (int)(100 * $nb / $opened), $login);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Issue class tag statistics
 | 
			
		||||
                $tags = $prj->getTagCloud();
 | 
			
		||||
                foreach ($tags as $t) {
 | 
			
		||||
                    $tagStatistics[$t->class][$t->name] = array($t->nb_use, $t->id);
 | 
			
		||||
                }
 | 
			
		||||
                foreach($tagStatistics as $k => $v) {
 | 
			
		||||
                    $nbIssueInClass = 0;
 | 
			
		||||
                    foreach ($v as $val) {
 | 
			
		||||
                        $nbIssueInClass += $val[0];
 | 
			
		||||
                    }
 | 
			
		||||
                    foreach ($v as $kk => $vv) {
 | 
			
		||||
                        $tagStatistics[$k][$kk] = array($vv[0], (int)(100 * $vv[0] / $nbIssueInClass), $vv[1]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // Sort
 | 
			
		||||
                krsort($tagStatistics);
 | 
			
		||||
                arsort($ownerStatistics);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $title = sprintf(__('Summary of tracked issues in %s.'), (string) $prj);
 | 
			
		||||
        
 | 
			
		||||
        return Pluf_Shortcuts_RenderToResponse('idf/issues/summary.html',
 | 
			
		||||
                                               array('page_title' => $title,
 | 
			
		||||
                                                     'trackerEmpty' => $isTrackerEmpty,
 | 
			
		||||
                                                     'project' => $prj,
 | 
			
		||||
                                                     'tagStatistics' => $tagStatistics,
 | 
			
		||||
                                                     'ownerStatistics' => $ownerStatistics,
 | 
			
		||||
                                                     'status' => $status,
 | 
			
		||||
                                                     ),
 | 
			
		||||
                                               $request);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * View the issues watch list of a given user.
 | 
			
		||||
     * Limited to a specified project
 | 
			
		||||
@@ -90,37 +165,37 @@ class IDF_Views_Issue
 | 
			
		||||
        $ctags = $prj->getTagIdsByStatus('closed');
 | 
			
		||||
        if (count($otags) == 0) $otags[] = 0;
 | 
			
		||||
        if (count($ctags) == 0) $ctags[] = 0;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
         // Get the id list of issue in the user watch list (for all projects !)
 | 
			
		||||
        $db =& Pluf::db();
 | 
			
		||||
        $sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id);
 | 
			
		||||
        $issue_ids = array(0);
 | 
			
		||||
        foreach ($sql_results as $id) {
 | 
			
		||||
           $issue_ids[] = $id['id'];
 | 
			
		||||
        } 
 | 
			
		||||
        }
 | 
			
		||||
        $issue_ids = implode (',', $issue_ids);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Count open and close issues
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array($prj->id));
 | 
			
		||||
        $nb_open = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id));
 | 
			
		||||
        $nb_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Generate a filter for the paginator
 | 
			
		||||
        switch ($match[2]) {
 | 
			
		||||
        case 'closed':
 | 
			
		||||
            $title = sprintf(__('Watch List: Closed Issues for %s'), (string) $prj);
 | 
			
		||||
            $summary = __('This table shows the closed issues in your watch list for %s project.', (string) $prj);
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id));   
 | 
			
		||||
            break; 
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array($prj->id));
 | 
			
		||||
            break;
 | 
			
		||||
        case 'open':
 | 
			
		||||
        default:
 | 
			
		||||
            $title = sprintf(__('Watch List: Open Issues for %s'), (string) $prj);
 | 
			
		||||
            $summary = __('This table shows the open issues in your watch list for %s project.', (string) $prj);
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array($prj->id));
 | 
			
		||||
            break;  
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Paginator to paginate the issues
 | 
			
		||||
        $pag = new Pluf_Paginator(new IDF_Issue());
 | 
			
		||||
        $pag->class = 'recent-issues';
 | 
			
		||||
@@ -170,17 +245,17 @@ class IDF_Views_Issue
 | 
			
		||||
        }
 | 
			
		||||
        foreach (IDF_Views::getProjects($request->user) as $project) {
 | 
			
		||||
            $ctags = array_merge($ctags, $project->getTagIdsByStatus('closed'));
 | 
			
		||||
        }       
 | 
			
		||||
        }
 | 
			
		||||
        if (count($otags) == 0) $otags[] = 0;
 | 
			
		||||
        if (count($ctags) == 0) $ctags[] = 0;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
         // Get the id list of issue in the user watch list (for all projects !)
 | 
			
		||||
        $db =& Pluf::db();
 | 
			
		||||
        $sql_results = $db->select('SELECT idf_issue_id as id FROM '.Pluf::f('db_table_prefix', '').'idf_issue_pluf_user_assoc WHERE pluf_user_id='.$request->user->id);
 | 
			
		||||
        $issue_ids = array(0);
 | 
			
		||||
        foreach ($sql_results as $id) {
 | 
			
		||||
           $issue_ids[] = $id['id'];
 | 
			
		||||
        } 
 | 
			
		||||
        }
 | 
			
		||||
        $issue_ids = implode (',', $issue_ids);
 | 
			
		||||
 | 
			
		||||
        // Count open and close issues
 | 
			
		||||
@@ -194,16 +269,16 @@ class IDF_Views_Issue
 | 
			
		||||
        case 'closed':
 | 
			
		||||
            $title = sprintf(__('Watch List: Closed Issues'));
 | 
			
		||||
            $summary = __('This table shows the closed issues in your watch list.');
 | 
			
		||||
            $f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array());   
 | 
			
		||||
            break; 
 | 
			
		||||
            $f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $ctags).')', array());
 | 
			
		||||
            break;
 | 
			
		||||
        case 'open':
 | 
			
		||||
        default:
 | 
			
		||||
            $title = sprintf(__('Watch List: Open Issues'));
 | 
			
		||||
            $summary = __('This table shows the open issues in your watch list.');
 | 
			
		||||
            $f_sql = new Pluf_SQL('id IN ('.$issue_ids.') AND status IN ('.implode(', ', $otags).')', array());
 | 
			
		||||
            break;  
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // Paginator to paginate the issues
 | 
			
		||||
        $pag = new Pluf_Paginator(new IDF_Issue());
 | 
			
		||||
        $pag->class = 'recent-issues';
 | 
			
		||||
@@ -240,42 +315,55 @@ class IDF_Views_Issue
 | 
			
		||||
     *
 | 
			
		||||
     * Only open issues are shown.
 | 
			
		||||
     */
 | 
			
		||||
    public $myIssues_precond = array('IDF_Precondition::accessIssues',
 | 
			
		||||
                                     'Pluf_Precondition::loginRequired');
 | 
			
		||||
    public function myIssues($request, $match)
 | 
			
		||||
    public $userIssues_precond = array('IDF_Precondition::accessIssues');
 | 
			
		||||
    public function userIssues($request, $match)
 | 
			
		||||
    {
 | 
			
		||||
        $prj = $request->project;
 | 
			
		||||
        
 | 
			
		||||
        $sql = new Pluf_SQL('login=%s', array($match[2]));
 | 
			
		||||
        $user = Pluf::factory('Pluf_User')->getOne(array('filter' => $sql->gen()));
 | 
			
		||||
        if ($user === null) {
 | 
			
		||||
            $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::index',
 | 
			
		||||
                                            array($prj->shortname));
 | 
			
		||||
            return new Pluf_HTTP_Response_Redirect($url);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $otags = $prj->getTagIdsByStatus('open');
 | 
			
		||||
        $ctags = $prj->getTagIdsByStatus('closed');
 | 
			
		||||
        if (count($otags) == 0) $otags[] = 0;
 | 
			
		||||
        if (count($ctags) == 0) $ctags[] = 0;
 | 
			
		||||
        switch ($match[2]) {
 | 
			
		||||
        switch ($match[3]) {
 | 
			
		||||
        case 'submit':
 | 
			
		||||
            $title = sprintf(__('My Submitted %s Issues'), (string) $prj);
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
 | 
			
		||||
            $titleFormat = __('%s %s Submitted %s Issues');
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
 | 
			
		||||
            break;
 | 
			
		||||
        case 'submitclosed':
 | 
			
		||||
            $title = sprintf(__('My Closed Submitted %s Issues'), (string) $prj);
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $request->user->id));
 | 
			
		||||
            $titleFormat = __('%s %s Closed Submitted %s Issues');
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
 | 
			
		||||
            break;
 | 
			
		||||
        case 'ownerclosed':
 | 
			
		||||
            $title = sprintf(__('My Closed Working %s Issues'), (string) $prj);
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $request->user->id));
 | 
			
		||||
            $titleFormat = __('%s %s Closed Working %s Issues');
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            $title = sprintf(__('My Working %s Issues'), (string) $prj);
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
 | 
			
		||||
            $titleFormat = __('%s %s Working %s Issues');
 | 
			
		||||
            $f_sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        $title = sprintf($titleFormat,
 | 
			
		||||
                         $user->first_name,
 | 
			
		||||
                         $user->last_name,
 | 
			
		||||
                         (string) $prj);
 | 
			
		||||
        
 | 
			
		||||
        // Get stats about the issues
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
 | 
			
		||||
        $nb_submit = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $request->user->id));
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $otags).')', array($prj->id, $user->id));
 | 
			
		||||
        $nb_owner = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
 | 
			
		||||
        // Closed issues
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $request->user->id));
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND submitter=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
 | 
			
		||||
        $nb_submit_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $request->user->id));
 | 
			
		||||
        $sql = new Pluf_SQL('project=%s AND owner=%s AND status IN ('.implode(', ', $ctags).')', array($prj->id, $user->id));
 | 
			
		||||
        $nb_owner_closed = Pluf::factory('IDF_Issue')->getCount(array('filter'=>$sql->gen()));
 | 
			
		||||
 | 
			
		||||
        // Paginator to paginate the issues
 | 
			
		||||
@@ -286,7 +374,7 @@ class IDF_Views_Issue
 | 
			
		||||
                                       'current_user' => $request->user);
 | 
			
		||||
        $pag->summary = __('This table shows the open issues.');
 | 
			
		||||
        $pag->forced_where = $f_sql;
 | 
			
		||||
        $pag->action = array('IDF_Views_Issue::myIssues', array($prj->shortname, $match[2]));
 | 
			
		||||
        $pag->action = array('IDF_Views_Issue::userIssues', array($prj->shortname, $match[2]));
 | 
			
		||||
        $pag->sort_order = array('modif_dtime', 'ASC'); // will be reverted
 | 
			
		||||
        $pag->sort_reverse_order = array('modif_dtime');
 | 
			
		||||
        $pag->sort_link_title = true;
 | 
			
		||||
@@ -301,9 +389,10 @@ class IDF_Views_Issue
 | 
			
		||||
        $pag->items_per_page = 10;
 | 
			
		||||
        $pag->no_results_text = __('No issues were found.');
 | 
			
		||||
        $pag->setFromRequest($request);
 | 
			
		||||
        return Pluf_Shortcuts_RenderToResponse('idf/issues/my-issues.html',
 | 
			
		||||
        return Pluf_Shortcuts_RenderToResponse('idf/issues/userIssues.html',
 | 
			
		||||
                                               array('project' => $prj,
 | 
			
		||||
                                                     'page_title' => $title,
 | 
			
		||||
                                                     'login' => $user->login,
 | 
			
		||||
                                                     'nb_submit' => $nb_submit,
 | 
			
		||||
                                                     'nb_owner' => $nb_owner,
 | 
			
		||||
                                                     'nb_submit_closed' => $nb_submit_closed,
 | 
			
		||||
@@ -345,6 +434,7 @@ class IDF_Views_Issue
 | 
			
		||||
                                    'form' => $form,
 | 
			
		||||
                                    'page_title' => $title,
 | 
			
		||||
                                    'preview' => $preview,
 | 
			
		||||
                                    'issue' => new IDF_Issue(),
 | 
			
		||||
                                    ),
 | 
			
		||||
                              self::autoCompleteArrays($prj)
 | 
			
		||||
                              );
 | 
			
		||||
@@ -403,6 +493,8 @@ class IDF_Views_Issue
 | 
			
		||||
        $issue = Pluf_Shortcuts_GetObjectOr404('IDF_Issue', $match[2]);
 | 
			
		||||
        $prj->inOr404($issue);
 | 
			
		||||
        $comments = $issue->get_comments_list(array('order' => 'id ASC'));
 | 
			
		||||
        $related_issues = $issue->getGroupedRelatedIssues(array('order' => 'other_issue ASC'));
 | 
			
		||||
 | 
			
		||||
        $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view',
 | 
			
		||||
                                        array($prj->shortname, $issue->id));
 | 
			
		||||
        $title = Pluf_Template::markSafe(sprintf(__('Issue <a href="%s">%d</a>: %s'), $url, $issue->id, $issue->summary));
 | 
			
		||||
@@ -453,7 +545,7 @@ class IDF_Views_Issue
 | 
			
		||||
        $next_issue = Pluf::factory('IDF_Issue')->getList(array('filter' => $sql_next->gen(),
 | 
			
		||||
                                                                'order' => 'id ASC',
 | 
			
		||||
                                                                'nb' => 1
 | 
			
		||||
                                                               ));                                 
 | 
			
		||||
                                                               ));
 | 
			
		||||
        $previous_issue_id = (isset($previous_issue[0])) ? $previous_issue[0]->id : 0;
 | 
			
		||||
        $next_issue_id = (isset($next_issue[0])) ? $next_issue[0]->id : 0;
 | 
			
		||||
 | 
			
		||||
@@ -470,7 +562,8 @@ class IDF_Views_Issue
 | 
			
		||||
                                                     'preview' => $preview,
 | 
			
		||||
                                                     'interested' => $interested->count(),
 | 
			
		||||
                                                     'previous_issue_id' => $previous_issue_id,
 | 
			
		||||
                                                     'next_issue_id' => $next_issue_id
 | 
			
		||||
                                                     'next_issue_id' => $next_issue_id,
 | 
			
		||||
                                                     'related_issues' => $related_issues,
 | 
			
		||||
                                                     ),
 | 
			
		||||
                                               $arrays),
 | 
			
		||||
                                               $request);
 | 
			
		||||
@@ -643,6 +736,79 @@ class IDF_Views_Issue
 | 
			
		||||
                                               $request);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders a JSON string containing completed issue information
 | 
			
		||||
     * based on the queried / partial string
 | 
			
		||||
     */
 | 
			
		||||
    public $autoCompleteIssueList_precond = array('IDF_Precondition::accessIssues');
 | 
			
		||||
    public function autoCompleteIssueList($request, $match)
 | 
			
		||||
    {
 | 
			
		||||
        $prj = $request->project;
 | 
			
		||||
        $issue_id = !empty($match[2]) ? intval($match[2]) : 0;
 | 
			
		||||
        $query = trim($request->REQUEST['q']);
 | 
			
		||||
        $limit = !empty($request->REQUEST['limit']) ? intval($request->REQUEST['limit']) : 0;
 | 
			
		||||
        $limit = max(10, $limit);
 | 
			
		||||
 | 
			
		||||
        $issues = array();
 | 
			
		||||
 | 
			
		||||
        // empty search, return the most recently updated issues
 | 
			
		||||
        if (empty($query)) {
 | 
			
		||||
                $sql = new Pluf_SQL('project=%s', array($prj->id));
 | 
			
		||||
                $tmp = Pluf::factory('IDF_Issue')->getList(array(
 | 
			
		||||
                    'filter' => $sql->gen(),
 | 
			
		||||
                    'order' => 'modif_dtime DESC'
 | 
			
		||||
                ));
 | 
			
		||||
                $issues += $tmp->getArrayCopy();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            // ID-based search
 | 
			
		||||
            if (is_numeric($query)) {
 | 
			
		||||
                $sql = 'project=%s AND CAST(id AS VARCHAR) LIKE %s';
 | 
			
		||||
                // MySQL can't cast to VARCHAR and a CAST to CHAR converts
 | 
			
		||||
                // the whole number, not just the first digit
 | 
			
		||||
                if (strtolower(Pluf::f('db_engine')) == 'mysql') {
 | 
			
		||||
                    $sql = 'project=%s AND CAST(id AS CHAR) LIKE %s';
 | 
			
		||||
                }
 | 
			
		||||
                $sql = new Pluf_SQL($sql, array($prj->id, $query.'%'));
 | 
			
		||||
                $tmp = Pluf::factory('IDF_Issue')->getList(array(
 | 
			
		||||
                    'filter' => $sql->gen(),
 | 
			
		||||
                    'order' => 'id ASC'
 | 
			
		||||
                ));
 | 
			
		||||
                $issues += $tmp->getArrayCopy();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // text-based search
 | 
			
		||||
            $res = new Pluf_Search_ResultSet(
 | 
			
		||||
                IDF_Search::mySearch($query, $prj, 'IDF_Issue')
 | 
			
		||||
            );
 | 
			
		||||
            foreach ($res as $issue)
 | 
			
		||||
                $issues[] = $issue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Autocomplete from jQuery UI works with JSON, this old one still
 | 
			
		||||
        // expects a parsable string; since we'd need to bump jQuery beyond
 | 
			
		||||
        // 1.2.6 for this to use as well, we're trying to cope with the old format.
 | 
			
		||||
        // see http://www.learningjquery.com/2010/06/autocomplete-migration-guide
 | 
			
		||||
        $out = '';
 | 
			
		||||
        $ids = array();
 | 
			
		||||
        foreach ($issues as $issue)
 | 
			
		||||
        {
 | 
			
		||||
            if ($issue->id == $issue_id)
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            if (in_array($issue->id, $ids))
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            if (--$limit < 0)
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            $out .= str_replace('|', '|', $issue->summary) .'|'.$issue->id."\n";
 | 
			
		||||
            $ids[] = $issue->id;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Pluf_HTTP_Response($out);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Star/Unstar an issue.
 | 
			
		||||
     */
 | 
			
		||||
@@ -715,6 +881,15 @@ class IDF_Views_Issue
 | 
			
		||||
        }
 | 
			
		||||
        $auto['auto_owner'] = substr($auto['auto_owner'], 0, -2);
 | 
			
		||||
        unset($auto['_auto_owner']);
 | 
			
		||||
        // Get issue relations
 | 
			
		||||
        $r = $project->getRelationsFromConfig();
 | 
			
		||||
        $auto['auto_relation_types'] = '';
 | 
			
		||||
        foreach ($r as $rt) {
 | 
			
		||||
            $esc = Pluf_esc($rt);
 | 
			
		||||
            $auto['auto_relation_types'] .= sprintf('{ name: "%s", to: "%s" }, ',
 | 
			
		||||
                                                    $esc, $esc);
 | 
			
		||||
        }
 | 
			
		||||
        $auto['auto_relation_types'] = substr($auto['auto_relation_types'], 0, -2);
 | 
			
		||||
        return $auto;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -38,18 +38,18 @@ class IDF_Views_Project
 | 
			
		||||
    public function logo($request, $match)
 | 
			
		||||
    {
 | 
			
		||||
        $prj = $request->project;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $logo = $prj->getConf()->getVal('logo');
 | 
			
		||||
        if (empty($logo)) {
 | 
			
		||||
            $url = Pluf::f('url_media') . '/idf/img/no_logo.png';
 | 
			
		||||
            return new Pluf_HTTP_Response_Redirect($url);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $info = IDF_FileUtil::getMimeType($logo);
 | 
			
		||||
        return new Pluf_HTTP_Response_File(Pluf::f('upload_path') . '/' . $prj->shortname . $logo,
 | 
			
		||||
                                           $info[0]);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Home page of a project.
 | 
			
		||||
     */
 | 
			
		||||
@@ -291,12 +291,12 @@ class IDF_Views_Project
 | 
			
		||||
    public function admin($request, $match)
 | 
			
		||||
    {
 | 
			
		||||
        $prj = $request->project;
 | 
			
		||||
        $title = sprintf(__('%s Project Summary'), (string) $prj);    
 | 
			
		||||
        $extra = array('project' => $prj);  
 | 
			
		||||
        $title = sprintf(__('%s Project Summary'), (string) $prj);
 | 
			
		||||
        $extra = array('project' => $prj);
 | 
			
		||||
        if ($request->method == 'POST') {
 | 
			
		||||
            $form = new IDF_Form_ProjectConf(array_merge($request->POST,
 | 
			
		||||
                                                         $request->FILES),
 | 
			
		||||
                                             $extra);      
 | 
			
		||||
                                             $extra);
 | 
			
		||||
            if ($form->isValid()) {
 | 
			
		||||
                $form->save();
 | 
			
		||||
                $request->user->setMessage(__('The project has been updated.'));
 | 
			
		||||
@@ -305,9 +305,9 @@ class IDF_Views_Project
 | 
			
		||||
                return new Pluf_HTTP_Response_Redirect($url);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $form = new IDF_Form_ProjectConf($prj->getData(), $extra);    
 | 
			
		||||
            $form = new IDF_Form_ProjectConf($prj->getData(), $extra);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $logo = $prj->getConf()->getVal('logo');
 | 
			
		||||
        return Pluf_Shortcuts_RenderToResponse('idf/admin/summary.html',
 | 
			
		||||
                                               array(
 | 
			
		||||
@@ -316,7 +316,7 @@ class IDF_Views_Project
 | 
			
		||||
                                                     'project' => $prj,
 | 
			
		||||
                                                     'logo' => $logo,
 | 
			
		||||
                                                     ),
 | 
			
		||||
                                               $request);        
 | 
			
		||||
                                               $request);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -344,7 +344,8 @@ class IDF_Views_Project
 | 
			
		||||
            $params = array();
 | 
			
		||||
            $keys = array('labels_issue_template',
 | 
			
		||||
                          'labels_issue_open', 'labels_issue_closed',
 | 
			
		||||
                          'labels_issue_predefined', 'labels_issue_one_max');
 | 
			
		||||
                          'labels_issue_predefined', 'labels_issue_one_max',
 | 
			
		||||
                          'issue_relations');
 | 
			
		||||
            foreach ($keys as $key) {
 | 
			
		||||
                $_val = $conf->getVal($key, false);
 | 
			
		||||
                if ($_val !== false) {
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ class IDF_Views_Source
 | 
			
		||||
        $title = sprintf(__('%s Invalid Revision'), (string) $request->project);
 | 
			
		||||
        $scm = IDF_Scm::get($request->project);
 | 
			
		||||
        $branches = $scm->getBranches();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $commit = $match[2];
 | 
			
		||||
        $params = array(
 | 
			
		||||
                        'page_title' => $title,
 | 
			
		||||
@@ -66,7 +66,8 @@ class IDF_Views_Source
 | 
			
		||||
                        'commit' => $commit,
 | 
			
		||||
                        'branches' => $branches,
 | 
			
		||||
                        );
 | 
			
		||||
        return Pluf_Shortcuts_RenderToResponse('idf/source/invalid_revision.html',
 | 
			
		||||
        $scmConf = $request->conf->getVal('scm', 'git');
 | 
			
		||||
        return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/invalid_revision.html',
 | 
			
		||||
                                               $params, $request);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -307,12 +308,7 @@ class IDF_Views_Source
 | 
			
		||||
        $cobject->diff = null;
 | 
			
		||||
        $diff->parse();
 | 
			
		||||
        $scmConf = $request->conf->getVal('scm', 'git');
 | 
			
		||||
        try {
 | 
			
		||||
            $changes = $scm->getChanges($commit);
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
            // getChanges is not yes supported by this backend.
 | 
			
		||||
            $changes = array();
 | 
			
		||||
        }
 | 
			
		||||
        $changes = $scm->getChanges($commit);
 | 
			
		||||
        $branches = $scm->getBranches();
 | 
			
		||||
        $in_branches = $scm->inBranches($cobject->commit, '');
 | 
			
		||||
        $tags = $scm->getTags();
 | 
			
		||||
@@ -477,7 +473,7 @@ class IDF_Views_Source
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // compare two nodes of different types, directories ("tree")
 | 
			
		||||
        // should come before files ("blob") 
 | 
			
		||||
        // should come before files ("blob")
 | 
			
		||||
        if ($a->type > $b->type) {
 | 
			
		||||
            return -1;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -117,6 +117,11 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/$#',
 | 
			
		||||
               'base' => $base,
 | 
			
		||||
               'model' => 'IDF_Views_Issue',
 | 
			
		||||
               'method' => 'index');
 | 
			
		||||
               
 | 
			
		||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/summary/$#',
 | 
			
		||||
               'base' => $base,
 | 
			
		||||
               'model' => 'IDF_Views_Issue',
 | 
			
		||||
               'method' => 'summary');
 | 
			
		||||
 | 
			
		||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/search/$#',
 | 
			
		||||
               'base' => $base,
 | 
			
		||||
@@ -148,10 +153,10 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/create/$#',
 | 
			
		||||
               'model' => 'IDF_Views_Issue',
 | 
			
		||||
               'method' => 'create');
 | 
			
		||||
 | 
			
		||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/my/(\w+)/$#',
 | 
			
		||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/(.*)/(\w+)/$#',
 | 
			
		||||
               'base' => $base,
 | 
			
		||||
               'model' => 'IDF_Views_Issue',
 | 
			
		||||
               'method' => 'myIssues');
 | 
			
		||||
               'method' => 'userIssues');
 | 
			
		||||
 | 
			
		||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/attachment/(\d+)/(.*)$#',
 | 
			
		||||
               'base' => $base,
 | 
			
		||||
@@ -173,6 +178,11 @@ $ctl[] = array('regex' => '#^/watchlist/(\w+)$#',
 | 
			
		||||
               'model' => 'IDF_Views_Issue',
 | 
			
		||||
               'method' => 'forgeWatchList');
 | 
			
		||||
 | 
			
		||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/issues/autocomplete/(\d*)$#',
 | 
			
		||||
               'base' => $base,
 | 
			
		||||
               'model' => 'IDF_Views_Issue',
 | 
			
		||||
               'method' => 'autoCompleteIssueList');
 | 
			
		||||
 | 
			
		||||
// ---------- SCM ----------------------------------------
 | 
			
		||||
 | 
			
		||||
$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/help/$#',
 | 
			
		||||
 
 | 
			
		||||
@@ -130,7 +130,7 @@ msgstr "Entferntes Subversion-Depot"
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/ProjectCreate.php:92 IDF/Form/SourceConf.php:40
 | 
			
		||||
msgid "Repository username"
 | 
			
		||||
msgstr "Depot-Nutzername"
 | 
			
		||||
msgstr "Depot-Benutzername"
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/ProjectCreate.php:99 IDF/Form/SourceConf.php:47
 | 
			
		||||
msgid "Repository password"
 | 
			
		||||
@@ -156,11 +156,11 @@ msgstr "Projekt-Eigentümer"
 | 
			
		||||
#: IDF/Form/Admin/ProjectCreate.php:123 IDF/Form/Admin/ProjectUpdate.php:76
 | 
			
		||||
#: IDF/Form/MembersConf.php:54 IDF/Form/TabsConf.php:52
 | 
			
		||||
msgid "Project members"
 | 
			
		||||
msgstr "Projekt-Mitglieder"
 | 
			
		||||
msgstr "Projektmitglieder"
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/ProjectCreate.php:136
 | 
			
		||||
msgid "Project template"
 | 
			
		||||
msgstr "Projekt-Vorlage"
 | 
			
		||||
msgstr "Projektvorlage"
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/ProjectCreate.php:138
 | 
			
		||||
msgid ""
 | 
			
		||||
@@ -168,7 +168,7 @@ msgid ""
 | 
			
		||||
"general configuration will be taken from the template project."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Benutze das vorgegebene Projekt, um ein neues zu initialisieren. "
 | 
			
		||||
"Zugriffsrechte und allgemeine Konfiguration werden von der Projekt-Vorlage "
 | 
			
		||||
"Zugriffsrechte und allgemeine Konfiguration werden von der Projektvorlage "
 | 
			
		||||
"übernommen."
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/ProjectCreate.php:185
 | 
			
		||||
@@ -350,7 +350,7 @@ msgid ""
 | 
			
		||||
"The password must be hard for other people to guess, but easy for the user "
 | 
			
		||||
"to remember."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Das Passwort sollte für andere Leute schwer zu erraten, aber für den Nutzer "
 | 
			
		||||
"Das Passwort sollte für andere Leute schwer zu erraten, aber für den Benutzer "
 | 
			
		||||
"einfach zu erinnern sein."
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/UserUpdate.php:89
 | 
			
		||||
@@ -366,7 +366,7 @@ msgstr "Beschreibung"
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/UserUpdate.php:109 IDF/Form/UserAccount.php:110
 | 
			
		||||
msgid "Twitter username"
 | 
			
		||||
msgstr "Twitter-Nutzername"
 | 
			
		||||
msgstr "Twitter-Benutzername"
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/UserUpdate.php:119 IDF/Form/UserAccount.php:120
 | 
			
		||||
msgid "Public email address"
 | 
			
		||||
@@ -403,7 +403,7 @@ msgstr "Stab"
 | 
			
		||||
#: IDF/Form/Admin/UserUpdate.php:164
 | 
			
		||||
msgid "If you give staff rights to a user, you really need to trust them."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn Du einen Nutzer zum Stab hinzufügst, solltest Du ihm wirklich trauen."
 | 
			
		||||
"Wenn Du einen Benutzer zum Stab hinzufügst, solltest Du ihm wirklich trauen."
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/UserUpdate.php:172 IDF/Views/Admin.php:213
 | 
			
		||||
msgid "Active"
 | 
			
		||||
@@ -414,8 +414,8 @@ msgid ""
 | 
			
		||||
"If the user is not getting the confirmation email or is abusing the system, "
 | 
			
		||||
"you can directly enable or disable their account here."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn der Nutzer keine Bestätigungs-E-Mail erhält oder das System "
 | 
			
		||||
"missbraucht, kannst Du sein Konto hier direkt aktivieren oder deaktivieren."
 | 
			
		||||
"Wenn der Benutzer keine Bestätigungs-E-Mail erhält oder das System "
 | 
			
		||||
"missbraucht, kannst Du sein Benutzerkonto hier direkt aktivieren oder deaktivieren."
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/UserUpdate.php:274
 | 
			
		||||
msgid "--- is not a valid first name."
 | 
			
		||||
@@ -425,7 +425,7 @@ msgstr "--- ist kein gültiger Vorname"
 | 
			
		||||
msgid ""
 | 
			
		||||
"A user with this email already exists, please provide another email address."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Ein Nutzer mit dieser E-Mail-Adresse existiert bereits, bitte gib eine "
 | 
			
		||||
"Ein Benutzer mit dieser E-Mail-Adresse existiert bereits, bitte gib eine "
 | 
			
		||||
"andere E-Mail-Adresse an."
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Admin/UserUpdate.php:301 IDF/Form/UserAccount.php:389
 | 
			
		||||
@@ -678,7 +678,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/Register.php:148
 | 
			
		||||
msgid "Confirm the creation of your account."
 | 
			
		||||
msgstr "Bestätige die Erstellung deines Accounts."
 | 
			
		||||
msgstr "Bestätige die Erstellung Deines Benutzerkontos."
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/RegisterConfirmation.php:40 IDF/Form/RegisterInputKey.php:36
 | 
			
		||||
msgid "Your confirmation key"
 | 
			
		||||
@@ -705,7 +705,7 @@ msgid ""
 | 
			
		||||
"This account has already been confirmed. Maybe should you try to recover "
 | 
			
		||||
"your password using the help link."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Dieses Konto wurde bereits bestätigt. Vielleicht möchtest Du versuchen, Dein "
 | 
			
		||||
"Dieses Benutzerkonto wurde bereits bestätigt. Vielleicht möchtest Du versuchen, Dein "
 | 
			
		||||
"Passwort über den Hilfe-Link wiederherzustellen?"
 | 
			
		||||
 | 
			
		||||
#: IDF/Form/ReviewCreate.php:74
 | 
			
		||||
@@ -1204,7 +1204,7 @@ msgstr ""
 | 
			
		||||
"Quellcode.<br />\n"
 | 
			
		||||
"Wenn Du den Zugriff auf den Quellcode beschränkst, wird kein anonymer "
 | 
			
		||||
"Zugriff<br />\n"
 | 
			
		||||
"angeboten und die Nutzer müssen sich mit ihrem Passwort oder "
 | 
			
		||||
"angeboten und die Benutzer müssen sich mit ihrem Passwort oder "
 | 
			
		||||
"öffentlichen<br />\n"
 | 
			
		||||
"Schlüssel authentifizieren."
 | 
			
		||||
 | 
			
		||||
@@ -1241,7 +1241,7 @@ msgid ""
 | 
			
		||||
"\" will default to authorized users only."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn Du ein Projekt als privat markierst, haben nur Projektmitglieder und "
 | 
			
		||||
"Administratoren zusammen mit den von Dir extra authorisierten Nutzern "
 | 
			
		||||
"Administratoren zusammen mit den von Dir extra authorisierten Benutzern "
 | 
			
		||||
"Zugriff darauf. Du kannst weiterhin zusätzliche Zugriffsrechte für bestimmte "
 | 
			
		||||
"Projektfunktionen vergeben, aber die Einstellungen \"Für alle offen\" und "
 | 
			
		||||
"\"Angemeldete Benutzer\" werden standardmäßig nur authorisierte Benutzer "
 | 
			
		||||
@@ -1287,7 +1287,7 @@ msgid ""
 | 
			
		||||
"<a href=\"%%url%%\">Sign in or create your account</a> to create issues or "
 | 
			
		||||
"add comments"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"<a href=\"%%url%%\">Melde Dich an oder lege ein Konto an</a>, um Tickets "
 | 
			
		||||
"<a href=\"%%url%%\">Melde Dich an oder lege ein Benutzerkonto an</a>, um Tickets "
 | 
			
		||||
"oder Kommentare hinzuzufügen"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/base-full.html.php:4
 | 
			
		||||
@@ -1587,7 +1587,7 @@ msgid ""
 | 
			
		||||
"You need to create an account on <a href=\"http://en.gravatar.com/"
 | 
			
		||||
"\">Gravatar</a>, this takes about 5 minutes and is free."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Du musst Dir einen Account auf <a href=\"http://de.gravatar.com/\">Gravatar</"
 | 
			
		||||
"Du musst Dir ein Benutzerkonto auf <a href=\"http://de.gravatar.com/\">Gravatar.com</"
 | 
			
		||||
"a> erstellen, es dauert nur 5 Minuten und ist kostenfrei."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/faq.html.php:10
 | 
			
		||||
@@ -1632,7 +1632,7 @@ msgstr "<kbd>Umschalt+h</kbd>: Diese Hilfeseite."
 | 
			
		||||
#: IDF/gettexttemplates/idf/faq.html.php:18
 | 
			
		||||
msgid "If you are in a project, you have the following shortcuts:"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Befindest Du Dich in einem Projekt, kannst du die folgenden Tastaturkürzel "
 | 
			
		||||
"Befindest Du Dich in einem Projekt, kannst Du die folgenden Tastaturkürzel "
 | 
			
		||||
"benutzen:"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/faq.html.php:19
 | 
			
		||||
@@ -1874,28 +1874,28 @@ msgstr "Es wird eine Bestätigung verlangt."
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/base.html.php:3
 | 
			
		||||
#: IDF/Views/Admin.php:201
 | 
			
		||||
msgid "User List"
 | 
			
		||||
msgstr "Nutzerliste"
 | 
			
		||||
msgstr "Benutzerliste"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/base.html.php:4
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/update.html.php:13
 | 
			
		||||
msgid "Update User"
 | 
			
		||||
msgstr "Nutzer aktualisieren"
 | 
			
		||||
msgstr "Benutzer aktualisieren"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/base.html.php:5
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/create.html.php:4
 | 
			
		||||
msgid "Create User"
 | 
			
		||||
msgstr "Nutzer anlegen"
 | 
			
		||||
msgstr "Benutzer anlegen"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/create.html.php:3
 | 
			
		||||
msgid "The form contains some errors. Please correct them to create the user."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Die Eingabemaske enthält einige Fehler. Bitte korrigiere diese, um den "
 | 
			
		||||
"Nutzer zu erstellen."
 | 
			
		||||
"Benutzer zu erstellen."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/create.html.php:6
 | 
			
		||||
msgid "The user password will be sent by email to the user."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Das Passwort des Nutzers wird an die E-Mail-Adresse des Nutzers versandt."
 | 
			
		||||
"Das Passwort des Benutzers wird an die E-Mail-Adresse des Benutzers versandt."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/createuser-email.txt.php:3
 | 
			
		||||
#, php-format
 | 
			
		||||
@@ -1916,10 +1916,11 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Hallo %%user%%,\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Der Administrator %%admin%% hat ein neues Konto für Dich\n"
 | 
			
		||||
"auf der Forge angelegt.\n"
 | 
			
		||||
"Der Administrator %%admin%% hat ein neues Benutzerkonto\n"
 | 
			
		||||
"für Dich auf der Forge angelegt.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Hier sind die Zugangsdaten, mit denen Du auf Dein Konto zugreifen kannst:\n"
 | 
			
		||||
"Hier sind die Zugangsdaten, mit denen Du auf Dein Benutzerkonto\n"
 | 
			
		||||
"zugreifen kannst:\n"
 | 
			
		||||
"\n"
 | 
			
		||||
" Adresse: %%url%%\n"
 | 
			
		||||
" Anmeldename: %%user.login%%\n"
 | 
			
		||||
@@ -1931,17 +1932,17 @@ msgstr ""
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/index.html.php:3
 | 
			
		||||
#, php-format
 | 
			
		||||
msgid "See <a href=\"%%url%%\">not validated users</a>."
 | 
			
		||||
msgstr "Zeige <a href=\"%%url%%\">nicht validierte Nutzer</a>."
 | 
			
		||||
msgstr "Zeige <a href=\"%%url%%\">nicht validierte Benutzer</a>."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/index.html.php:4
 | 
			
		||||
msgid "<p>You have here an overview of the users registered in the forge.</p>"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"<p>Hier siehst Du eine Übersicht der Nutzer, die sich in der Forge "
 | 
			
		||||
"<p>Hier siehst Du eine Übersicht der Benutzer, die sich in der Forge "
 | 
			
		||||
"registriert haben."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/index.html.php:5
 | 
			
		||||
msgid "Number of users:"
 | 
			
		||||
msgstr "Anzahl der Nutzer:"
 | 
			
		||||
msgstr "Anzahl der Benutzer:"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/update.html.php:3
 | 
			
		||||
msgid ""
 | 
			
		||||
@@ -1949,7 +1950,7 @@ msgid ""
 | 
			
		||||
"need to ensure that you are providing a valid email\n"
 | 
			
		||||
"address"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn Du die E-Mail-Adresse eines Nutzers änderst,\n"
 | 
			
		||||
"Wenn Du die E-Mail-Adresse eines Benutzers änderst,\n"
 | 
			
		||||
"musst Du sicherstellen, eine gültige, neue E-Mail-Adresse\n"
 | 
			
		||||
"als Ersatz bereitzustellen."
 | 
			
		||||
 | 
			
		||||
@@ -1959,14 +1960,14 @@ msgid ""
 | 
			
		||||
"able to create new projects and update other non staff users.\n"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn Du dem Benutzer Stab-Rechte erteilst, kann er\n"
 | 
			
		||||
"neue Projekte anlegen und andere Nutzer, die nicht zum Stab\n"
 | 
			
		||||
"neue Projekte anlegen und andere Benutzer, die nicht zum Stab\n"
 | 
			
		||||
"gehören, aktualisieren.\n"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/update.html.php:9
 | 
			
		||||
msgid "The form contains some errors. Please correct them to update the user."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Die Eingabemaske enthält einige Fehler. Bitte korrigiere diese, um den "
 | 
			
		||||
"Nutzer zu aktualisieren."
 | 
			
		||||
"Benutzer zu aktualisieren."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/gadmin/users/update.html.php:10
 | 
			
		||||
#: IDF/gettexttemplates/idf/register/confirmation.html.php:4
 | 
			
		||||
@@ -2449,7 +2450,7 @@ msgid ""
 | 
			
		||||
"is still valid and more work is needed to fully fix it."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Dieses Ticket wurde als geschlossen markiert. Füge nur dann einen Kommentar "
 | 
			
		||||
"hinzu, wenn du denkst, dass das geschilderte Problem noch nicht ganz "
 | 
			
		||||
"hinzu, wenn Du denkst, dass das geschilderte Problem noch nicht ganz "
 | 
			
		||||
"beseitigt wurde."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/issues/view.html.orig.php:8
 | 
			
		||||
@@ -2531,7 +2532,7 @@ msgid ""
 | 
			
		||||
"If you don't have an account yet, you can create one <a href=\"%%url%%"
 | 
			
		||||
"\">here</a>."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn Du noch kein Konto hast, kannst Du <a href=\"%%url%%\">hier</a> ein "
 | 
			
		||||
"Wenn Du noch kein Benutzerkonto hast, kannst Du <a href=\"%%url%%\">hier</a> ein "
 | 
			
		||||
"neues erstellen."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/login_form.html.php:4
 | 
			
		||||
@@ -2560,7 +2561,7 @@ msgstr "Willkommen."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/login_form.html.php:10
 | 
			
		||||
msgid "It takes less than a minute to create your account."
 | 
			
		||||
msgstr "Es dauert weniger als eine Minute, um Dein Konto zu erstellen."
 | 
			
		||||
msgstr "Es dauert weniger als eine Minute, um Dein Benutzerkonto zu erstellen."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/main-menu.html.php:3
 | 
			
		||||
#, php-format
 | 
			
		||||
@@ -2576,7 +2577,7 @@ msgstr "Abmelden"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/main-menu.html.php:5
 | 
			
		||||
msgid "Sign in or create your account"
 | 
			
		||||
msgstr "Anmelden oder Konto erstellen"
 | 
			
		||||
msgstr "Anmelden oder Benutzerkonto erstellen"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/main-menu.html.php:8 IDF/Views/Admin.php:42
 | 
			
		||||
msgid "Forge Management"
 | 
			
		||||
@@ -2671,7 +2672,7 @@ msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Hallo,\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Du hast die Anlage eines Kontos angeforder, um an dem Leben\n"
 | 
			
		||||
"Du hast die Anlage eines Benutzerkontos angefordert, um an dem Leben\n"
 | 
			
		||||
"eines Softwareprojektes teilhaben zu können.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Um Dein Konto zu bestätigen, folge bitte dem folgenden Link:\n"
 | 
			
		||||
@@ -2688,7 +2689,7 @@ msgstr ""
 | 
			
		||||
"\n"
 | 
			
		||||
"Wenn Du nicht mehr länger an diesem Software-Projekt\n"
 | 
			
		||||
"interessiert bist oder wenn Du Dich nicht daran erinnern\n"
 | 
			
		||||
"kannst, dieses Konto erstellen zu wollen, entschuldige\n"
 | 
			
		||||
"kannst, dieses Benutzerkonto erstellen zu wollen, entschuldige\n"
 | 
			
		||||
"und ignoriere einfach diese E-Mail.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Mit freundlichen Grüßen,\n"
 | 
			
		||||
@@ -2716,7 +2717,7 @@ msgid ""
 | 
			
		||||
"strong> to log in afterwards."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Dies ist der letzte Schritt, aber bitte <strong>stelle sicher, dass Du "
 | 
			
		||||
"Cookies aktiviert hast</strong>, um Dich im folgenden anzumelden."
 | 
			
		||||
"Cookies aktiviert hast</strong>, um Dich im Folgenden anzumelden."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/register/index.html.php:3
 | 
			
		||||
#: IDF/gettexttemplates/idf/register/index.html~.php:3
 | 
			
		||||
@@ -2736,7 +2737,7 @@ msgid ""
 | 
			
		||||
"login name and password."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn Du einfach nur Deine Anmeldedaten vergessen hast, gibt es keinen Grund, "
 | 
			
		||||
"ein neues Konto zu erstellen. Gehe einfach <a href=\"%%url%%\">hier her</a>, "
 | 
			
		||||
"ein neues Benutzerkonto zu erstellen. Gehe einfach <a href=\"%%url%%\">hier her</a>, "
 | 
			
		||||
"um Deinen Anmeldenamen und Dein Passwort wiederherzustellen."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/register/index.html.php:5
 | 
			
		||||
@@ -2748,7 +2749,7 @@ msgid ""
 | 
			
		||||
"you have troubles, you can <a href=\"%%url%%\">let us know about your issues "
 | 
			
		||||
"at anytime</a>!"
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Durch Dein Konto bist Du in der Lage, an dem Leben aller Softwareprojekte, "
 | 
			
		||||
"Durch Dein Benutzerkonto bist Du in der Lage, an dem Leben aller Softwareprojekte, "
 | 
			
		||||
"die hierüber verwaltet werden, teilzuhaben. Das Teilnehmen an einem "
 | 
			
		||||
"Softwareprojekt muss Spass machen, deshalb <a href=\"%%url%%\">lass uns "
 | 
			
		||||
"jederzeit von Deinen Problemen wissen</a>, solltest Du welche haben."
 | 
			
		||||
@@ -3209,13 +3210,13 @@ msgstr "Zweige filtern"
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/mercurial/branch_tag_list.html.php:5
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/mtn/branch_tag_list.html.php:5
 | 
			
		||||
msgid "Tags"
 | 
			
		||||
msgstr "Marken"
 | 
			
		||||
msgstr "Tags"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/git/branch_tag_list.html.php:6
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/mercurial/branch_tag_list.html.php:6
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/mtn/branch_tag_list.html.php:6
 | 
			
		||||
msgid "filter tags"
 | 
			
		||||
msgstr "Marken filtern"
 | 
			
		||||
msgstr "Tags filtern"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/git/file.html.php:3
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/git/tree.html.php:3
 | 
			
		||||
@@ -3231,7 +3232,7 @@ msgid ""
 | 
			
		||||
"%%cobject.date%%."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Quellcode bei Revision <a class=\"mono\" href=\"%%url%%\">%%commit%%</a> "
 | 
			
		||||
"erzeugt am %%cobject.date%%."
 | 
			
		||||
"erzeugt %%cobject.date%%."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/git/file.html.php:4
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/git/tree.html.php:4
 | 
			
		||||
@@ -3409,8 +3410,8 @@ msgstr "Revision:"
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/svn/commit.html.php:4
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/svn/file.html.php:11
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/svn/tree.html.php:16
 | 
			
		||||
msgid "Go to revision"
 | 
			
		||||
msgstr "Zu Revision gehen:"
 | 
			
		||||
msgid "Switch"
 | 
			
		||||
msgstr "Wechseln"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/svn/file.html.php:6
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/svn/tree.html.php:11
 | 
			
		||||
@@ -3442,7 +3443,7 @@ msgstr "Zweige:"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/source/svn/tree.html.php:18
 | 
			
		||||
msgid "Tags:"
 | 
			
		||||
msgstr "Marken:"
 | 
			
		||||
msgstr "Tags:"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/user/changeemail-email.txt.php:3
 | 
			
		||||
#, php-format
 | 
			
		||||
@@ -3491,7 +3492,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/user/changeemail.html.php:4
 | 
			
		||||
msgid "Confirm Your New Email Address"
 | 
			
		||||
msgstr "Bestätige deine neue Email-Adresse"
 | 
			
		||||
msgstr "Bestätige Deine neue Email-Adresse"
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/user/changeemail.html.php:7
 | 
			
		||||
#: IDF/gettexttemplates/idf/user/passrecovery-inputkey.html.php:7
 | 
			
		||||
@@ -3508,7 +3509,7 @@ msgstr ""
 | 
			
		||||
#: IDF/gettexttemplates/idf/user/dashboard.html.php:3
 | 
			
		||||
#, php-format
 | 
			
		||||
msgid "<a href=\"%%url%%\">Update your account</a>."
 | 
			
		||||
msgstr "<a href=\"%%url%%\">Aktualisiere Dein Konto</a>."
 | 
			
		||||
msgstr "<a href=\"%%url%%\">Aktualisiere Dein Benutzerkonto</a>."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/user/dashboard.html.php:4
 | 
			
		||||
#, php-format
 | 
			
		||||
@@ -3578,7 +3579,7 @@ msgid ""
 | 
			
		||||
"If possible, use your real name. By using your real name, people will have "
 | 
			
		||||
"more trust in your comments and remarks."
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Wenn möglich, nutze Deinen realen Namen. Dadurch wird deinen Kommentaren und "
 | 
			
		||||
"Wenn möglich, nutze Deinen realen Namen. Dadurch wird Deinen Kommentaren und "
 | 
			
		||||
"Bemerkungen mehr Vertrauen geschenkt."
 | 
			
		||||
 | 
			
		||||
#: IDF/gettexttemplates/idf/user/myaccount.html.php:19
 | 
			
		||||
@@ -3646,7 +3647,7 @@ msgstr ""
 | 
			
		||||
"Hallo %%user%%,\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"Du hast Dein Passwort verloren und möchtest es wiederherstellen.\n"
 | 
			
		||||
"Um ein neues Passwort für Dein Konto anzugeben, musst Du lediglich\n"
 | 
			
		||||
"Um ein neues Passwort für Dein Benutzerkonto anzugeben, musst Du lediglich\n"
 | 
			
		||||
"dem folgenden Link folgen. Sie führt zu einer einfachen Eingabemaske,\n"
 | 
			
		||||
"mit der Du ein neues Passwort setzen kannst.\n"
 | 
			
		||||
"\n"
 | 
			
		||||
@@ -3801,7 +3802,7 @@ msgstr ""
 | 
			
		||||
"a> mit der <a href=\"%%eurl%%\"><em>Extra</em>-Erweiterung</a> verwendet "
 | 
			
		||||
"werden.</p>\n"
 | 
			
		||||
"<p>Website-Adresses werden automatisch verlinkt und Du kannst außerdem zu "
 | 
			
		||||
"anderen Dokumentations-Seiten durch die Benutzung von doppelten, eckige "
 | 
			
		||||
"anderen Dokumentations-Seiten durch die Benutzung von doppelten, eckigen "
 | 
			
		||||
"Klammern verlinken, etwa so: [[AndereSeite]].</p>\n"
 | 
			
		||||
"<p>Um direkt die Inhalte einer Datei aus dem Depot einzubinden, umklammere "
 | 
			
		||||
"den Pfad zur Datei mit dreifachen, eckigen Klammern: [[[Pfad/zu/Datei.txt]]]."
 | 
			
		||||
@@ -4388,7 +4389,7 @@ msgstr "Administrator"
 | 
			
		||||
 | 
			
		||||
#: IDF/Views/Admin.php:214
 | 
			
		||||
msgid "Last Login"
 | 
			
		||||
msgstr "Letzter Login"
 | 
			
		||||
msgstr "Letztes Login"
 | 
			
		||||
 | 
			
		||||
#: IDF/Views/Admin.php:221
 | 
			
		||||
msgid "No users were found."
 | 
			
		||||
@@ -4954,7 +4955,7 @@ msgstr ""
 | 
			
		||||
 | 
			
		||||
#: IDF/Views.php:284
 | 
			
		||||
msgid "Here to Help You!"
 | 
			
		||||
msgstr "Hier, um dir zu helfen!"
 | 
			
		||||
msgstr "Hier, um Dir zu helfen!"
 | 
			
		||||
 | 
			
		||||
#: IDF/Views.php:300
 | 
			
		||||
msgid "InDefero API (Application Programming Interface)"
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,7 @@ $m['IDF_Scm_Cache_Git'] = array('relate_to' => array('IDF_Project'));
 | 
			
		||||
 | 
			
		||||
$m['IDF_UserData'] = array('relate_to' => array('Pluf_User'));
 | 
			
		||||
$m['IDF_EmailAddress'] = array('relate_to' => array('Pluf_User'));
 | 
			
		||||
$m['IDF_IssueRelation'] = array('relate_to' => array('IDF_Issue', 'Pluf_User'));
 | 
			
		||||
 | 
			
		||||
Pluf_Signal::connect('Pluf_Template_Compiler::construct_template_tags_modifiers',
 | 
			
		||||
                     array('IDF_Middleware', 'updateTemplateTagsModifiers'));
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,15 @@
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
<tr>
 | 
			
		||||
<td colspan="2"><strong>{$form.f.issue_relations.labelTag}:</strong><br />
 | 
			
		||||
{if $form.f.issue_relations.errors}{$form.f.issue_relations.fieldErrors}{/if}
 | 
			
		||||
{$form.f.issue_relations|unsafe}<br />
 | 
			
		||||
<span class="helptext">{$form.f.issue_relations.help_text}</span>
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
<tr>
 | 
			
		||||
<td colspan="2">
 | 
			
		||||
<input type="submit" value="{trans 'Save Changes'}" name="submit" /> 
 | 
			
		||||
<input type="submit" value="{trans 'Save Changes'}" name="submit" />
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
</table>
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,6 @@
 | 
			
		||||
  <div id="ft">{block foot}{/block}</div>
 | 
			
		||||
</div>
 | 
			
		||||
{include 'idf/js-hotkeys.html'}
 | 
			
		||||
{include 'idf/list-filter.html'}
 | 
			
		||||
{block javascript}{/block}
 | 
			
		||||
{if $project}
 | 
			
		||||
<script type="text/javascript" charset="utf-8">{literal}
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="yui-b context">{block context}{/block}</div>
 | 
			
		||||
    <div class="yui-b context" id="context">{block context}{/block}</div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div id="ft">{block foot}{/block}</div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="yui-b context">{block context}{/block}</div>
 | 
			
		||||
    <div class="yui-b context" id="context">{block context}{/block}</div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div id="ft">{block foot}{/block}</div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -84,6 +84,21 @@ $(document).ready(function(){
 | 
			
		||||
    else if (frag.length > 3 && frag.substring(0, 3) == '#ic') {
 | 
			
		||||
        $(frag).addClass("issue-comment-focus");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var contextTop = $('div#context').position().top;
 | 
			
		||||
    var contextFixEnabled = true;
 | 
			
		||||
    $(window).scroll(function() {
 | 
			
		||||
         if (!contextFixEnabled || $(window).scrollTop() < contextTop)
 | 
			
		||||
             $('div#context').css('position', 'relative');
 | 
			
		||||
         else
 | 
			
		||||
             $('div#context').css('position', 'fixed');
 | 
			
		||||
    });
 | 
			
		||||
    $(window).resize(function() {
 | 
			
		||||
         contextFixEnabled =
 | 
			
		||||
             $('div#context').offset().top + $('div#context').height() <
 | 
			
		||||
             $(window).height();
 | 
			
		||||
    });
 | 
			
		||||
    $(window).resize();
 | 
			
		||||
});
 | 
			
		||||
//]]>{/literal}
 | 
			
		||||
</script>{/if}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,18 @@
 | 
			
		||||
    <th>{trans "address"}</th>
 | 
			
		||||
    <th>{trans "port"}</th>
 | 
			
		||||
</tr>
 | 
			
		||||
{if count($connections) == 0}
 | 
			
		||||
<tr>
 | 
			
		||||
    <td colspan="2" align="center">{trans 'No connections found.'}</td>
 | 
			
		||||
</tr>
 | 
			
		||||
{else}
 | 
			
		||||
{foreach $connections as $connection}
 | 
			
		||||
<tr>
 | 
			
		||||
    <td>{$connection.address}</td>
 | 
			
		||||
    <td>{$connection.port}</td>
 | 
			
		||||
</tr>
 | 
			
		||||
{/foreach}
 | 
			
		||||
{/if}
 | 
			
		||||
</table>
 | 
			
		||||
{/block}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,11 @@
 | 
			
		||||
    <th>{trans "status"}</th>
 | 
			
		||||
    <th>{trans "action"}</th>
 | 
			
		||||
</tr>
 | 
			
		||||
{if count($servers) == 0}
 | 
			
		||||
<tr>
 | 
			
		||||
    <td colspan="3" align="center">{trans 'No monotone servers configured.'}</td>
 | 
			
		||||
</tr>
 | 
			
		||||
{else}
 | 
			
		||||
{foreach $servers as $server}
 | 
			
		||||
<tr>
 | 
			
		||||
    <td>{$server.name}</td>
 | 
			
		||||
@@ -31,6 +36,7 @@
 | 
			
		||||
        {/if}
 | 
			
		||||
</tr>
 | 
			
		||||
{/foreach}
 | 
			
		||||
{/if}
 | 
			
		||||
</table>
 | 
			
		||||
{/block}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,9 @@
 | 
			
		||||
{block tabissues} class="active"{/block}
 | 
			
		||||
{block subtabs}
 | 
			
		||||
<div id="sub-tabs">
 | 
			
		||||
<a {if $inOpenIssues}class="active" {/if}href="{url 'IDF_Views_Issue::index', array($project.shortname)}">{trans 'Open Issues'}</a>
 | 
			
		||||
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>
 | 
			
		||||
<a {if $inSummaryIssues}class="active" {/if}href="{url 'IDF_Views_Issue::summary', array($project.shortname)}">{trans 'Summary'}</a>
 | 
			
		||||
| <a {if $inOpenIssues}class="active" {/if}href="{url 'IDF_Views_Issue::index', array($project.shortname)}">{trans 'Open Issues'}</a>
 | 
			
		||||
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::userIssues', array($project.shortname, $user.login, 'submit')}">{trans 'My Issues'}</a>
 | 
			
		||||
| <a {if $inWatchList}class="active" {/if}href="{url 'IDF_Views_Issue::watchList', array($project.shortname, 'open')}">{trans 'My watch list'}</a>{/if} |
 | 
			
		||||
<form class="star" action="{url 'IDF_Views_Issue::search', array($project.shortname)}" method="get">
 | 
			
		||||
<input accesskey="4" type="text" value="{$q}" name="q" size="20" />
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,15 @@
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
<tr>
 | 
			
		||||
<th>{$form.f.relation_type0.labelTag}:</th>
 | 
			
		||||
<td>
 | 
			
		||||
{if $form.f.relation_type0.errors}{$form.f.relation_type0.fieldErrors}{/if}
 | 
			
		||||
{if $form.f.relation_issue0.errors}{$form.f.relation_issue0.fieldErrors}{/if}
 | 
			
		||||
{$form.f.relation_type0|unsafe}
 | 
			
		||||
{$form.f.relation_issue0|unsafe}
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
<tr>
 | 
			
		||||
<th>{$form.f.label1.labelTag}:</th>
 | 
			
		||||
<td>
 | 
			
		||||
{if $form.f.label1.errors}{$form.f.label1.fieldErrors}{/if}{$form.f.label1|unsafe}
 | 
			
		||||
@@ -76,8 +85,8 @@
 | 
			
		||||
<tr>
 | 
			
		||||
<td> </td>
 | 
			
		||||
<td>
 | 
			
		||||
<input type="submit" value="{trans 'Submit Issue'}" name="submit" /> 
 | 
			
		||||
<input type="submit" value="{trans 'Preview'}" name="preview" /> | 
 | 
			
		||||
<input type="submit" value="{trans 'Submit Issue'}" name="submit" />
 | 
			
		||||
<input type="submit" value="{trans 'Preview'}" name="preview" /> |
 | 
			
		||||
<a href="{url 'IDF_Views_Issue::index', array($project.shortname)}">{trans 'Cancel'}</a>
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
@@ -123,11 +132,11 @@ $(document).ready(function(){
 | 
			
		||||
        });
 | 
			
		||||
    var j=0;
 | 
			
		||||
    for (j=1;j<4;j=j+1) {
 | 
			
		||||
	if($("tr#form-attachment-"+j+" > td > ul.errorlist").length == 0){
 | 
			
		||||
        	$("#form-attachment-"+j).hide();
 | 
			
		||||
	}else{
 | 
			
		||||
		$("#form-block-"+(j-1)).remove();
 | 
			
		||||
	}
 | 
			
		||||
    if($("tr#form-attachment-"+j+" > td > ul.errorlist").length == 0){
 | 
			
		||||
            $("#form-attachment-"+j).hide();
 | 
			
		||||
    }else{
 | 
			
		||||
        $("#form-block-"+(j-1)).remove();
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
    });
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,12 +10,22 @@
 | 
			
		||||
{if $attachments.count() > 0}
 | 
			
		||||
<hr align="left" class="attach" />
 | 
			
		||||
<ul>
 | 
			
		||||
{foreach $attachments as $a}<li><a href="{url 'IDF_Views_Issue::viewAttachment', array($project.shortname, $a.id, $a.filename)}">{$a.filename}</a> - {$a.filesize|ssize}</li>{/foreach} 
 | 
			
		||||
{foreach $attachments as $a}<li><a href="{url 'IDF_Views_Issue::viewAttachment', array($project.shortname, $a.id, $a.filename)}">{$a.filename}</a> - {$a.filesize|ssize}</li>{/foreach}
 | 
			
		||||
</ul>{/if}
 | 
			
		||||
{if $c.changes}
 | 
			
		||||
{foreach $c.changes as $w => $v}
 | 
			
		||||
<strong>{if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if}</strong> {if $w == 'lb'}{assign $l = implode(', ', $v)}{$l}{else}{$v}{/if}<br />
 | 
			
		||||
<strong>{if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if}{if $w == 'rel'}{trans 'Relations:'}{/if}</strong>
 | 
			
		||||
{if $w == 'lb' or $w == 'rel'}
 | 
			
		||||
  {foreach $v as $t => $ls}
 | 
			
		||||
    {foreach $ls as $l}
 | 
			
		||||
      {if $t == 'rem'}<s>{/if}{$l}{if $t == 'rem'}</s>{/if}
 | 
			
		||||
    {/foreach}
 | 
			
		||||
  {/foreach}
 | 
			
		||||
{else}
 | 
			
		||||
  {$v}
 | 
			
		||||
{/if}<br />
 | 
			
		||||
{/foreach}
 | 
			
		||||
{/if}
 | 
			
		||||
</div></content>
 | 
			
		||||
</entry>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
 | 
			
		||||
{if strlen($c.content) > 0}{$c.content|safe}{/if}{if $c.changedIssue()}
 | 
			
		||||
{foreach $c.changes as $w => $v}
 | 
			
		||||
 {if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if} {if $w == 'lb'}{assign $l = implode(', ', $v)}{$l}{else}{$v}{/if}{/foreach}{/if}{assign $attachments = $c.get_attachment_list()}{if $attachments.count() > 0}
 | 
			
		||||
 {if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if}{if $w == 'rel'}{trans 'Relations:'}{/if} {if $w == 'lb' or $w == 'rel'}{foreach $v as $t => $ls}{foreach $ls as $l}{if $t == 'rem'}-{/if}{$l} {/foreach}{/foreach}{else}{$v}{/if}{/foreach}{/if}{assign $attachments = $c.get_attachment_list()}{if $attachments.count() > 0}
 | 
			
		||||
 | 
			
		||||
{trans 'Attachments:'}{foreach $attachments as $a}
 | 
			
		||||
- {$a.filename|safe} - {$a.filesize|ssize}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,54 +2,88 @@
 | 
			
		||||
<script type="text/javascript" src="{media '/idf/js/jquery.bgiframe.min.js'}"></script>
 | 
			
		||||
<script type="text/javascript" src="{media '/idf/js/jquery.autocomplete.min.js'}"></script>
 | 
			
		||||
<script type="text/javascript" charset="utf-8">
 | 
			
		||||
// <!-- {literal} 
 | 
			
		||||
// <!-- {literal}
 | 
			
		||||
  $(document).ready(function(){
 | 
			
		||||
    var auto_labels = [{/literal}{$auto_labels|safe}{literal}];
 | 
			
		||||
    var auto_status = [{/literal}{$auto_status|safe}{literal}]; 
 | 
			
		||||
    var auto_owner = [{/literal}{$auto_owner|safe}{literal}]; 
 | 
			
		||||
    var auto_status = [{/literal}{$auto_status|safe}{literal}];
 | 
			
		||||
    var auto_owner = [{/literal}{$auto_owner|safe}{literal}];
 | 
			
		||||
    var auto_relation_types = [{/literal}{$auto_relation_types|safe}{literal}];
 | 
			
		||||
 | 
			
		||||
    var j=0;
 | 
			
		||||
    for (j=1;j<7;j=j+1) {
 | 
			
		||||
  $("#id_label"+j).autocomplete(auto_labels, {
 | 
			
		||||
		minChars: 0,
 | 
			
		||||
		width: 310,
 | 
			
		||||
		matchContains: true,
 | 
			
		||||
		max: 50,
 | 
			
		||||
		highlightItem: false,
 | 
			
		||||
		formatItem: function(row, i, max, term) {
 | 
			
		||||
			return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + "  <span style='font-size: 80%;'>" + row.name + "</span>";
 | 
			
		||||
		},
 | 
			
		||||
		formatResult: function(row) {
 | 
			
		||||
			return row.to;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
        minChars: 0,
 | 
			
		||||
        width: 310,
 | 
			
		||||
        matchContains: true,
 | 
			
		||||
        max: 50,
 | 
			
		||||
        highlightItem: false,
 | 
			
		||||
        formatItem: function(row, i, max, term) {
 | 
			
		||||
            return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + "  <span style='font-size: 80%;'>" + row.name + "</span>";
 | 
			
		||||
        },
 | 
			
		||||
        formatResult: function(row) {
 | 
			
		||||
            return row.to;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
  $("#id_status").autocomplete(auto_status, {
 | 
			
		||||
		minChars: 0,
 | 
			
		||||
		width: 310,
 | 
			
		||||
		matchContains: true,
 | 
			
		||||
		max: 50,
 | 
			
		||||
		highlightItem: false,
 | 
			
		||||
		formatItem: function(row, i, max, term) {
 | 
			
		||||
			return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + "  <span style='font-size: 80%;'>" + row.name + "</span>";
 | 
			
		||||
		},
 | 
			
		||||
		formatResult: function(row) {
 | 
			
		||||
			return row.to;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
        minChars: 0,
 | 
			
		||||
        width: 310,
 | 
			
		||||
        matchContains: true,
 | 
			
		||||
        max: 50,
 | 
			
		||||
        highlightItem: false,
 | 
			
		||||
        formatItem: function(row, i, max, term) {
 | 
			
		||||
            return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + "  <span style='font-size: 80%;'>" + row.name + "</span>";
 | 
			
		||||
        },
 | 
			
		||||
        formatResult: function(row) {
 | 
			
		||||
            return row.to;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
  $("#id_owner").autocomplete(auto_owner, {
 | 
			
		||||
		minChars: 0,
 | 
			
		||||
		width: 310,
 | 
			
		||||
		matchContains: true,
 | 
			
		||||
		max: 50,
 | 
			
		||||
		highlightItem: false,
 | 
			
		||||
		formatItem: function(row, i, max, term) {
 | 
			
		||||
			return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + "  <span style='font-size: 80%;'>" + row.name + "</span>";
 | 
			
		||||
		},
 | 
			
		||||
		formatResult: function(row) {
 | 
			
		||||
			return row.to;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
        minChars: 0,
 | 
			
		||||
        width: 310,
 | 
			
		||||
        matchContains: true,
 | 
			
		||||
        max: 50,
 | 
			
		||||
        highlightItem: false,
 | 
			
		||||
        formatItem: function(row, i, max, term) {
 | 
			
		||||
            return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + "  <span style='font-size: 80%;'>" + row.name + "</span>";
 | 
			
		||||
        },
 | 
			
		||||
        formatResult: function(row) {
 | 
			
		||||
            return row.to;
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
  for (var idx = 0; ; ++idx) {
 | 
			
		||||
      if ($("#id_relation_type" + idx).length == 0)
 | 
			
		||||
          break;
 | 
			
		||||
 | 
			
		||||
      $("#id_relation_type" + idx).autocomplete(auto_relation_types, {
 | 
			
		||||
            minChars: 0,
 | 
			
		||||
            width: 310,
 | 
			
		||||
            matchContains: true,
 | 
			
		||||
            max: 50,
 | 
			
		||||
            highlightItem: false,
 | 
			
		||||
            formatItem: function(row, i, max, term) {
 | 
			
		||||
                return row.to.replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + "  <span style='font-size: 80%;'>" + row.name + "</span>";
 | 
			
		||||
            },
 | 
			
		||||
            formatResult: function(row) {
 | 
			
		||||
                return row.to;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
      $("#id_relation_issue" + idx).autocomplete("{/literal}{url 'IDF_Views_Issue::autoCompleteIssueList', array($project.shortname, $issue.id)}{literal}", {
 | 
			
		||||
            minChars: 0,
 | 
			
		||||
            width: 310,
 | 
			
		||||
            matchContains: true,
 | 
			
		||||
            max: 10,
 | 
			
		||||
            multiple: true,
 | 
			
		||||
            delay: 500,
 | 
			
		||||
            highlightItem: false,
 | 
			
		||||
            formatItem: function(row, i, max, term) {
 | 
			
		||||
                return row[1].replace(new RegExp("(" + term + ")", "gi"), "<strong>$1</strong>") + "  <span style='font-size: 80%;'>" + row[0] + "</span>";
 | 
			
		||||
            },
 | 
			
		||||
            formatResult: function(row) {
 | 
			
		||||
                return row[1];
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
{/literal} //-->
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										100
									
								
								src/IDF/templates/idf/issues/summary.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/IDF/templates/idf/issues/summary.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
{extends "idf/issues/base.html"}
 | 
			
		||||
 | 
			
		||||
{block docclass}yui-t2{assign $inSummaryIssues=true}{/block}
 | 
			
		||||
 | 
			
		||||
{block body}
 | 
			
		||||
{if $trackerEmpty}
 | 
			
		||||
    {aurl 'create_url', 'IDF_Views_Issue::create', array($project.shortname)}
 | 
			
		||||
    <p>{blocktrans}The issue tracker is empty.<br />You can create your first issue <a href="{$create_url}">here</a>.{/blocktrans}</p>
 | 
			
		||||
{else}
 | 
			
		||||
<div class='issue-summary'>
 | 
			
		||||
{foreach $tagStatistics as $key => $class}
 | 
			
		||||
    <div>
 | 
			
		||||
    <h2>{blocktrans}Unresolved: By {$key}{/blocktrans}</h2>
 | 
			
		||||
    <table class='issue-summary'>
 | 
			
		||||
    <tbody>
 | 
			
		||||
    {foreach $class as $key => $value}
 | 
			
		||||
        <tr>
 | 
			
		||||
        <td class="name"><a href="{url 'IDF_Views_Issue::listLabel', array($project.shortname, $value[2], 'open')}">{$key}</a></td>
 | 
			
		||||
        <td class="count">{$value[0]}</td>
 | 
			
		||||
        <td class="graph">
 | 
			
		||||
            <table class='graph'>
 | 
			
		||||
            <tbody><tr>
 | 
			
		||||
            <td style="width:{$value[1] * 0.8 + 1}%" class="graph-color" valign="center">
 | 
			
		||||
            <div class="colour-bar"></div>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="graph-percent">{$value[1]}%</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
        </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    {/foreach}
 | 
			
		||||
    </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    </div>
 | 
			
		||||
{/foreach}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class='issue-summary'>
 | 
			
		||||
{if $status}
 | 
			
		||||
    <div>
 | 
			
		||||
    <h2>{blocktrans}Status Summary{/blocktrans}</h2>
 | 
			
		||||
    <table class='issue-summary'>
 | 
			
		||||
    <tbody>
 | 
			
		||||
    {foreach $status as $key => $value}
 | 
			
		||||
        <tr>
 | 
			
		||||
        <td class="name"><a href="{url 'IDF_Views_Issue::listStatus', array($project.shortname, $key)}">{$key}</a></td>
 | 
			
		||||
        <td class="count">{$value[0]}</td>
 | 
			
		||||
        <td class="graph">
 | 
			
		||||
            <table class='graph'>
 | 
			
		||||
            <tbody><tr>
 | 
			
		||||
            <td style="width:{$value[1] * 0.8 + 1}%" class="graph-color" valign="center">
 | 
			
		||||
            <div class="colour-bar"></div>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="graph-percent">{$value[1]}%</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
        </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    {/foreach}
 | 
			
		||||
    </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    </div>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
{if $ownerStatistics}
 | 
			
		||||
    <div>
 | 
			
		||||
    <h2>{blocktrans}Unresolved: By Assignee{/blocktrans}</h2>
 | 
			
		||||
    <table class='issue-summary'>
 | 
			
		||||
    <tbody>
 | 
			
		||||
    {foreach $ownerStatistics as $key => $value}
 | 
			
		||||
        <tr>
 | 
			
		||||
        <td class="name">
 | 
			
		||||
        {if !empty($value[2])}
 | 
			
		||||
        {aurl 'url', 'IDF_Views_Issue::userIssues', array($project.shortname, $value[2], 'owner')}
 | 
			
		||||
        <a href="{$url}">{$key}</a>
 | 
			
		||||
        {else}{$key}{/if}
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="count">{$value[0]}</td>
 | 
			
		||||
        <td class="graph">
 | 
			
		||||
            <table class='graph'>
 | 
			
		||||
            <tbody><tr>
 | 
			
		||||
            <td style="width:{$value[1] * 0.8 + 1}%" class="graph-color" valign="center">
 | 
			
		||||
            <div class="colour-bar"></div>
 | 
			
		||||
            </td>
 | 
			
		||||
            <td class="graph-percent">{$value[1]}%</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
        </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
    {/foreach}
 | 
			
		||||
    </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    </div>
 | 
			
		||||
{/if}
 | 
			
		||||
</div>
 | 
			
		||||
{/if}
 | 
			
		||||
{/block}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
{extends "idf/issues/base.html"}
 | 
			
		||||
{block docclass}yui-t2{assign $inMyIssues = true}{/block}
 | 
			
		||||
{block docclass}yui-t2{if $user.login == $login}{assign $inMyIssues = true}{/if}{/block}
 | 
			
		||||
{block body}
 | 
			
		||||
{$issues.render}
 | 
			
		||||
{if !$user.isAnonymous()}
 | 
			
		||||
@@ -8,10 +8,10 @@
 | 
			
		||||
 | 
			
		||||
{/block}
 | 
			
		||||
{block context}
 | 
			
		||||
{aurl 'owner_url', 'IDF_Views_Issue::myIssues', array($project.shortname, 'owner')}
 | 
			
		||||
{aurl 'submit_url', 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}
 | 
			
		||||
{aurl 'owner_closed_url', 'IDF_Views_Issue::myIssues', array($project.shortname, 'ownerclosed')}
 | 
			
		||||
{aurl 'submit_closed_url', 'IDF_Views_Issue::myIssues', array($project.shortname, 'submitclosed')}
 | 
			
		||||
{aurl 'owner_url', 'IDF_Views_Issue::userIssues', array($project.shortname, $login, 'owner')}
 | 
			
		||||
{aurl 'submit_url', 'IDF_Views_Issue::userIssues', array($project.shortname, $login, 'submit')}
 | 
			
		||||
{aurl 'owner_closed_url', 'IDF_Views_Issue::userIssues', array($project.shortname, $login, 'ownerclosed')}
 | 
			
		||||
{aurl 'submit_closed_url', 'IDF_Views_Issue::userIssues', array($project.shortname, $login, 'submitclosed')}
 | 
			
		||||
<p><strong>{trans 'Submitted issues:'}</strong> <a href="{$submit_url}">{$nb_submit}</a> 
 | 
			
		||||
{if $nb_submit_closed}<br /><span class="helptext">{blocktrans $nb_submit_closed}See the <a href="{$submit_closed_url}">{$nb_submit_closed} closed</a>.{plural}See the <a href="{$submit_closed_url}">{$nb_submit_closed} closed</a>.{/blocktrans}</span>{/if}</p>
 | 
			
		||||
{if $nb_owner > 0}
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
{assign $submitter_data = $c.get_submitter_data()}
 | 
			
		||||
<div class="issue-comment{if $i == 0} issue-comment-first{/if}{if $i == ($nc-1)} issue-comment-last{/if}" id="ic{$c.id}">
 | 
			
		||||
{if $submitter_data.avatar != ''}
 | 
			
		||||
<img style="float:right; position: relative; max-height: 60px; max-width: 60px;" src="{media}/upload/avatars/{$submitter_data.avatar}" alt=" " />
 | 
			
		||||
<img style="float:right; position: relative; max-height: 60px; max-width: 60px;" src="{upload}/avatars/{$submitter_data.avatar}" alt=" " />
 | 
			
		||||
{else}
 | 
			
		||||
<img style="float:right; position: relative; max-height: 60px; max-width: 60px;" src="http://www.gravatar.com/avatar/{$submitter.email|md5}.jpg?s=60&d={media}/idf/img/spacer.gif" alt=" " />
 | 
			
		||||
{/if}
 | 
			
		||||
@@ -40,7 +40,16 @@
 | 
			
		||||
{if $i> 0 and $c.changedIssue()}
 | 
			
		||||
<div class="issue-changes">
 | 
			
		||||
{foreach $c.changes as $w => $v}
 | 
			
		||||
<strong>{if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if}</strong> {if $w == 'lb'}{assign $l = implode(', ', $v)}{$l}{else}{$v}{/if}<br />
 | 
			
		||||
<strong>{if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if}{if $w == 'ow'}{trans 'Owner:'}{/if}{if $w == 'lb'}{trans 'Labels:'}{/if}{if $w == 'rel'}{trans 'Relations:'}{/if}</strong>
 | 
			
		||||
{if $w == 'lb' or $w == 'rel'}
 | 
			
		||||
  {foreach $v as $t => $ls}
 | 
			
		||||
    {foreach $ls as $l}
 | 
			
		||||
      {if $t == 'rem'}<s>{/if}{$l}{if $t == 'rem'}</s>{/if}
 | 
			
		||||
    {/foreach}
 | 
			
		||||
  {/foreach}
 | 
			
		||||
{else}
 | 
			
		||||
  {$v}
 | 
			
		||||
{/if}<br />
 | 
			
		||||
{/foreach}
 | 
			
		||||
</div>
 | 
			
		||||
{/if}
 | 
			
		||||
@@ -120,6 +129,23 @@
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
<tr>
 | 
			
		||||
<th>{$form.f.relation_type0.labelTag}:</th>
 | 
			
		||||
<td>
 | 
			
		||||
{assign $prevField}
 | 
			
		||||
{foreach $form as $field}
 | 
			
		||||
   {if strpos($field.name, 'relation_type') === 0}
 | 
			
		||||
        {$field|unsafe}
 | 
			
		||||
        {assign $prevField = $field}
 | 
			
		||||
   {/if}
 | 
			
		||||
   {if strpos($field.name, 'relation_issue') === 0}
 | 
			
		||||
        {$field|unsafe}<br />
 | 
			
		||||
        {if $prevField.errors}{$prevField.fieldErrors}{/if}
 | 
			
		||||
        {if $field.errors}{$field.fieldErrors}{/if}
 | 
			
		||||
   {/if}
 | 
			
		||||
{/foreach}
 | 
			
		||||
</td>
 | 
			
		||||
</tr>
 | 
			
		||||
<tr>
 | 
			
		||||
<th>{$form.f.label1.labelTag}:</th>
 | 
			
		||||
<td>
 | 
			
		||||
{if $form.f.label1.errors}{$form.f.label1.fieldErrors}{/if}{$form.f.label1|unsafe}
 | 
			
		||||
@@ -161,6 +187,21 @@
 | 
			
		||||
<span class="label"><a href="{$url}" class="label"><strong>{$tag.class}:</strong>{$tag.name}</a></span><br />
 | 
			
		||||
{/foreach}
 | 
			
		||||
</p>{/if}
 | 
			
		||||
{if count($related_issues) > 0}
 | 
			
		||||
{foreach $related_issues as $verb => $rel_issues}
 | 
			
		||||
<p>
 | 
			
		||||
<strong>{blocktrans}This issue {$verb}{/blocktrans}</strong><br />
 | 
			
		||||
  {foreach $rel_issues as $rel_issue}
 | 
			
		||||
     <span class="label">
 | 
			
		||||
        <a href="{url 'IDF_Views_Issue::view', array($project.shortname, $rel_issue.other_issue)}"
 | 
			
		||||
           class="label" title="{$rel_issue.other_summary}">
 | 
			
		||||
           <strong>{$rel_issue.other_issue}</strong> - {$rel_issue.other_summary|shorten:30}
 | 
			
		||||
        </a>
 | 
			
		||||
     </span><br />
 | 
			
		||||
  {/foreach}
 | 
			
		||||
</p>
 | 
			
		||||
{/foreach}
 | 
			
		||||
{/if}
 | 
			
		||||
</div>
 | 
			
		||||
{/block}
 | 
			
		||||
{block javascript}{if $form}{include 'idf/issues/js-autocomplete.html'}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,8 @@
 | 
			
		||||
{if $hasWikiAccess}{hotkey 'Shift+o', 'IDF_Views_Wiki::index', array($project.shortname)}{/if}
 | 
			
		||||
{if $hasSourceAccess}{hotkey 'Shift+s', 'IDF_Views_Source::treeBase', array($project.shortname, $project.getScmRoot())}{/if}
 | 
			
		||||
{if $hasIssuesAccess and !$user.isAnonymous()}
 | 
			
		||||
{hotkey 'Shift+m', 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}
 | 
			
		||||
{hotkey 'Shift+w', 'IDF_Views_Issue::myIssues', array($project.shortname, 'owner')}
 | 
			
		||||
{hotkey 'Shift+m', 'IDF_Views_Issue::userIssues', array($project.shortname, $user.login, 'submit')}
 | 
			
		||||
{hotkey 'Shift+w', 'IDF_Views_Issue::userIssues', array($project.shortname, $user.login, 'owner')}
 | 
			
		||||
{/if}{/if} //-->
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
<div id="sub-tabs">
 | 
			
		||||
<a {if $inOpenReviews}class="active" {/if}href="{url 'IDF_Views_Review::index', array($project.shortname)}">{trans 'Open Reviews'}</a> {*
 | 
			
		||||
 | 
			
		||||
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::myIssues', array($project.shortname, 'submit')}">{trans 'My Issues'}</a>{/if} |
 | 
			
		||||
{if !$user.isAnonymous()} | <a {if $inCreate}class="active" {/if}href="{url 'IDF_Views_Issue::create', array($project.shortname)}">{trans 'New Issue'}</a> | <a {if $inMyIssues}class="active" {/if}href="{url 'IDF_Views_Issue::userIssues', array($project.shortname, $user.login, 'submit')}">{trans 'My Issues'}</a>{/if} |
 | 
			
		||||
<form class="star" action="{url 'IDF_Views_Issue::search', array($project.shortname)}" method="get">
 | 
			
		||||
<input accesskey="4" type="text" value="{$q}" name="q" size="20" />
 | 
			
		||||
<input type="submit" name="s" value="{trans 'Search'}" />
 | 
			
		||||
 
 | 
			
		||||
@@ -109,7 +109,7 @@ to propose more contributions</strong>.
 | 
			
		||||
{foreach $comments as $c}{ashowuser 'submitter', $c.get_submitter(), $request}{assign $submitter = $c.get_submitter()}{assign $submitter_data = $c.get_submitter_data()}
 | 
			
		||||
<div class="issue-comment{if $i == 1} issue-comment-first{/if}{if $i == ($nc)} issue-comment-last{/if}" id="ic{$c.id}">
 | 
			
		||||
{if $submitter_data.avatar != ''}
 | 
			
		||||
    <img style="float:right; position: relative; max-height: 60px; max-width: 60px;" src="{media}/upload/avatars/{$submitter_data.avatar}" alt=" " />
 | 
			
		||||
    <img style="float:right; position: relative; max-height: 60px; max-width: 60px;" src="{upload}/avatars/{$submitter_data.avatar}" alt=" " />
 | 
			
		||||
{else}
 | 
			
		||||
    <img style="float:right; position: relative;" src="http://www.gravatar.com/avatar/{$submitter.email|md5}.jpg?s=60&d={media}/idf/img/spacer.gif" alt=" " />
 | 
			
		||||
{/if}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,9 @@
 | 
			
		||||
{foreach $changes.renames as $oldname => $newname}
 | 
			
		||||
<tr><td><span class="scm-action renamed" title="{trans 'renamed'}">R</span></td><td><a href="{url 'IDF_Views_Source::tree', array($project.shortname, $commit, $newname)}">{$oldname} → {$newname}</a></td></tr>
 | 
			
		||||
{/foreach}
 | 
			
		||||
{foreach $changes.copies as $srcname => $destname}
 | 
			
		||||
<tr><td><span class="scm-action copied" title="{trans 'copied'}">C</span></td><td><a href="{url 'IDF_Views_Source::tree', array($project.shortname, $commit, $destname)}">{$srcname} → {$destname}</a></td></tr>
 | 
			
		||||
{/foreach}
 | 
			
		||||
{foreach $changes.additions as $filename}
 | 
			
		||||
<tr><td><span class="scm-action added" title="{trans 'added'}">A</span></td><td><a href="{url 'IDF_Views_Source::tree', array($project.shortname, $commit, $filename)}">{$filename}</a>{if !empty($diff.files[$filename])} (<a href="#diff-{$filename|md5}">{trans 'full'}</a>){/if}</td></tr>
 | 
			
		||||
{/foreach}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								src/IDF/templates/idf/source/git/invalid_revision.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/IDF/templates/idf/source/git/invalid_revision.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
{extends "idf/source/invalid_revision.html"}
 | 
			
		||||
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
<p>{blocktrans}The branch or revision <b>{$commit}</b> is not valid or does not exist
 | 
			
		||||
in this repository.{/blocktrans}</p>
 | 
			
		||||
 | 
			
		||||
{if count($branches) > 0}
 | 
			
		||||
<p>{blocktrans}The following list shows all available branches:{/blocktrans}</p>
 | 
			
		||||
<ul>
 | 
			
		||||
{foreach $branches as $branch => $path}
 | 
			
		||||
@@ -14,6 +15,7 @@ in this repository.{/blocktrans}</p>
 | 
			
		||||
</li>
 | 
			
		||||
{/foreach}
 | 
			
		||||
</ul>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
{if $isOwner or $isMember}
 | 
			
		||||
{aurl 'url', 'IDF_Views_Source::help', array($project.shortname)}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
{extends "idf/source/invalid_revision.html"}
 | 
			
		||||
 | 
			
		||||
@@ -5,12 +5,25 @@
 | 
			
		||||
<h2 class="top"><a href="{url 'IDF_Views_Source::treeBase', array($project.shortname, $commit)}">{trans 'Root'}</a><span class="sep">/</span>{if $breadcrumb}{$breadcrumb|safe}{/if}</h2>
 | 
			
		||||
 | 
			
		||||
<table class="code" summary=" ">
 | 
			
		||||
{if !$tree_in and !$tags_in}
 | 
			
		||||
{if (!$tree_in and !$tags_in) or $props}
 | 
			
		||||
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
 | 
			
		||||
<tfoot>
 | 
			
		||||
<tr><th colspan="2">{blocktrans}Source at commit <a class="mono" href="{$url}">{$commit}</a> created {$cobject.date|dateago}.{/blocktrans}<br/>
 | 
			
		||||
<span class="smaller">{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans}</span>
 | 
			
		||||
</th></tr>
 | 
			
		||||
    {if $props}
 | 
			
		||||
    <tr><th colspan="2">
 | 
			
		||||
        <ul>
 | 
			
		||||
          {foreach $props as $prop => $val}
 | 
			
		||||
          <li>{blocktrans}Property <strong>{$prop}</strong> set to <em>{$val}</em>{/blocktrans}</li>
 | 
			
		||||
          {/foreach}
 | 
			
		||||
        </ul>
 | 
			
		||||
    </th></tr>
 | 
			
		||||
    {/if}
 | 
			
		||||
    {if !$tree_in and !$tags_in}
 | 
			
		||||
    <tr>
 | 
			
		||||
        <th colspan="2">{blocktrans}Source at commit <a class="mono" href="{$url}">{$commit}</a> created {$cobject.date|dateago}.{/blocktrans}<br/>
 | 
			
		||||
          <span class="smaller">{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans}</span>
 | 
			
		||||
        </th>
 | 
			
		||||
    </tr>
 | 
			
		||||
    {/if}
 | 
			
		||||
</tfoot>
 | 
			
		||||
{/if}
 | 
			
		||||
<tbody>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								src/IDF/templates/idf/source/mtn/invalid_revision.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/IDF/templates/idf/source/mtn/invalid_revision.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
{extends "idf/source/invalid_revision.html"}
 | 
			
		||||
 | 
			
		||||
@@ -11,14 +11,27 @@
 | 
			
		||||
<th>{trans 'Message'}</th>
 | 
			
		||||
<th>{trans 'Size'}</th>
 | 
			
		||||
</tr>
 | 
			
		||||
</thead>{if !$tree_in and !$tags_in}
 | 
			
		||||
</thead>
 | 
			
		||||
{if (!$tree_in and !$tags_in) or $props}
 | 
			
		||||
{aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
 | 
			
		||||
<tfoot>
 | 
			
		||||
<tr><th colspan="5">{blocktrans}Source at commit <a class="mono" href="{$url}">{$commit}</a> created {$cobject.date|dateago}.{/blocktrans}<br/>
 | 
			
		||||
<span class="smaller">{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans}</span>
 | 
			
		||||
</th></tr>
 | 
			
		||||
    {if $props}
 | 
			
		||||
    <tr><th colspan="5">
 | 
			
		||||
        <ul>
 | 
			
		||||
          {foreach $props as $prop => $val}
 | 
			
		||||
          <li>{blocktrans}Property <strong>{$prop}</strong> set to <em>{$val}</em>{/blocktrans}</li>
 | 
			
		||||
          {/foreach}
 | 
			
		||||
        </ul>
 | 
			
		||||
    </th></tr>
 | 
			
		||||
    {/if}
 | 
			
		||||
    {if !$tree_in and !$tags_in}
 | 
			
		||||
    <tr><th colspan="5">{blocktrans}Source at commit <a class="mono" href="{$url}">{$commit}</a> created {$cobject.date|dateago}.{/blocktrans}<br/>
 | 
			
		||||
    <span class="smaller">{blocktrans}By {$cobject.author|strip_tags|trim}, {$cobject.title}{/blocktrans}</span>
 | 
			
		||||
    </th></tr>
 | 
			
		||||
    {/if}
 | 
			
		||||
{/if}
 | 
			
		||||
</tfoot>
 | 
			
		||||
{/if}<tbody>
 | 
			
		||||
<tbody>
 | 
			
		||||
{if $base}
 | 
			
		||||
<tr>
 | 
			
		||||
<td> </td>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
<p><strong>{trans 'Revision:'}</strong> {$commit}</p>
 | 
			
		||||
<p>
 | 
			
		||||
    <input accesskey="4" type="text" value="{$commit}" name="rev" size="5" />
 | 
			
		||||
    <input type="submit" name="s" value="{trans 'Go to revision'}" />
 | 
			
		||||
    <input type="submit" name="s" value="{trans 'Switch'}" />
 | 
			
		||||
</p>
 | 
			
		||||
</form>
 | 
			
		||||
{/block}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
<p><strong>{trans 'Revision:'}</strong> {$commit}</p>
 | 
			
		||||
<p>
 | 
			
		||||
    <input accesskey="4" type="text" value="{$commit}" name="rev" size="5"/>
 | 
			
		||||
    <input type="submit" name="s" value="{trans 'Go to revision'}"/>
 | 
			
		||||
    <input type="submit" name="s" value="{trans 'Switch'}"/>
 | 
			
		||||
</p>
 | 
			
		||||
</form>
 | 
			
		||||
{/block}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,13 +6,13 @@
 | 
			
		||||
 | 
			
		||||
<table class="code" summary=" ">
 | 
			
		||||
{if !$tree_in || $props}
 | 
			
		||||
  {aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)} 
 | 
			
		||||
  {aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
 | 
			
		||||
  <tfoot>
 | 
			
		||||
    {if $props}
 | 
			
		||||
    <tr><th colspan="2">
 | 
			
		||||
        <ul>
 | 
			
		||||
          {foreach $props as $prop => $val}
 | 
			
		||||
          <li>{trans 'Property'} <strong>{$prop}</strong> {trans 'set to:'} <em>{$val}</em></li>
 | 
			
		||||
          <li>{blocktrans}Property <strong>{$prop}</strong> set to <em>{$val}</em>{/blocktrans}</li>
 | 
			
		||||
          {/foreach}
 | 
			
		||||
        </ul>
 | 
			
		||||
    </th></tr>
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
<p>
 | 
			
		||||
    <input accesskey="4" type="text" value="{$commit}" name="rev" size="5" />
 | 
			
		||||
    <input type="hidden" name="sourcefile" value="{$base}"/>
 | 
			
		||||
    <input type="submit" name="s" value="{trans 'Go to revision'}" /></p>
 | 
			
		||||
    <input type="submit" name="s" value="{trans 'Switch'}" /></p>
 | 
			
		||||
  </form>
 | 
			
		||||
{/block}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								src/IDF/templates/idf/source/svn/invalid_revision.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/IDF/templates/idf/source/svn/invalid_revision.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
{extends "idf/source/base.html"}
 | 
			
		||||
{block docclass}yui-t2{assign $inError=true}{/block}
 | 
			
		||||
{block body}
 | 
			
		||||
 | 
			
		||||
<p>{blocktrans}The revision <b>{$commit}</b> is not valid or does not exist
 | 
			
		||||
in this repository.{/blocktrans}</p>
 | 
			
		||||
 | 
			
		||||
{if count($branches) > 0}
 | 
			
		||||
<p>{blocktrans}The following list shows all available branches:{/blocktrans}</p>
 | 
			
		||||
<ul>
 | 
			
		||||
{foreach $branches as $branch => $path}
 | 
			
		||||
{if $path}{aurl 'url', 'IDF_Views_Source::tree', array($project.shortname, 'HEAD', $path)}
 | 
			
		||||
{else}{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, 'HEAD')}{/if}
 | 
			
		||||
<li class="label">
 | 
			
		||||
  <a href="{$url}" class="label">{$branch}</a>
 | 
			
		||||
</li>
 | 
			
		||||
{/foreach}
 | 
			
		||||
</ul>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
{if $isOwner or $isMember}
 | 
			
		||||
{aurl 'url', 'IDF_Views_Source::help', array($project.shortname)}
 | 
			
		||||
<p>{blocktrans}If this is a new repository, the reason for this error
 | 
			
		||||
could be that you have not committed and / or pushed any change so far.
 | 
			
		||||
In this case please take a look at the <a href="{$url}">Help</a> page
 | 
			
		||||
how to access your repository.{/blocktrans}</p>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
{/block}
 | 
			
		||||
 | 
			
		||||
@@ -13,13 +13,13 @@
 | 
			
		||||
      <th>{trans 'Size'}</th>
 | 
			
		||||
    </tr>
 | 
			
		||||
  </thead>{if (!$tree_in and !$tags_in and $commit != 'HEAD') || $props}
 | 
			
		||||
  {aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)} 
 | 
			
		||||
  {aurl 'url', 'IDF_Views_Source::commit', array($project.shortname, $commit)}
 | 
			
		||||
  <tfoot>
 | 
			
		||||
    {if $props}
 | 
			
		||||
    <tr><th colspan="6">
 | 
			
		||||
        <ul>
 | 
			
		||||
          {foreach $props as $prop => $val}
 | 
			
		||||
          <li>{trans 'Property'} <strong>{$prop}</strong> {trans 'set to:'} <em>{$val}</em></li>
 | 
			
		||||
          <li>{blocktrans}Property <strong>{$prop}</strong> set to <em>{$val}</em>{/blocktrans}</li>
 | 
			
		||||
          {/foreach}
 | 
			
		||||
        </ul>
 | 
			
		||||
    </th></tr>
 | 
			
		||||
@@ -45,7 +45,7 @@
 | 
			
		||||
      <td class="fileicon"><img src="{media '/idf/img/'~$file.type~'.png'}" alt="{$file.type}" /></td>
 | 
			
		||||
 | 
			
		||||
      <td><a href="{$url}">{$file.file}</a></td>
 | 
			
		||||
      <td><span class="smaller">{$file.date|dateago:"without"}</span></td> 
 | 
			
		||||
      <td><span class="smaller">{$file.date|dateago:"without"}</span></td>
 | 
			
		||||
      <td>{$file.rev}</td>
 | 
			
		||||
      <td{if $file.type != 'blob'} colspan="2"{/if}><span class="smaller">{$file.author|strip_tags|trim}{trans ':'} {issuetext $file.log, $request, true, false}</span></td>
 | 
			
		||||
      {if $file.type == 'blob'}
 | 
			
		||||
@@ -66,7 +66,7 @@
 | 
			
		||||
<p>
 | 
			
		||||
    <input accesskey="4" type="text" value="{$commit}" name="rev" size="5" />
 | 
			
		||||
    <input type="hidden" name="sourcefile" value="{$base}"/>
 | 
			
		||||
    <input type="submit" name="s" value="{trans 'Go to revision'}" /></p>
 | 
			
		||||
    <input type="submit" name="s" value="{trans 'Switch'}" /></p>
 | 
			
		||||
  </form>
 | 
			
		||||
<p><strong>{trans 'Branches:'}</strong><br />
 | 
			
		||||
{foreach $branches as $branch => $path}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
<table class="form" summary="">
 | 
			
		||||
<tr>
 | 
			
		||||
<th style="text-align: right">{if $user_data.avatar != ''}
 | 
			
		||||
    <img style="max-height: 60px; max-width: 60px;" src="{media}/upload/avatars/{$user_data.avatar}" alt=" " />
 | 
			
		||||
    <img style="max-height: 60px; max-width: 60px;" src="{upload}/avatars/{$user_data.avatar}" alt=" " />
 | 
			
		||||
{else}
 | 
			
		||||
    <img src="http://www.gravatar.com/avatar/{$member.email|md5}.jpg?s=60&d={media}/idf/img/spacer.gif" alt=" " />
 | 
			
		||||
{/if}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										90
									
								
								test/IDF/ProjectTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								test/IDF/ProjectTest.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2008-2011 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero 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 General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
class IDF_ProjectTest extends PHPUnit_Framework_TestCase
 | 
			
		||||
{
 | 
			
		||||
    public function testGetIssueCountByOwner()
 | 
			
		||||
    {
 | 
			
		||||
        // Add users
 | 
			
		||||
        $user1 = new Pluf_User();
 | 
			
		||||
        $user1->login = 'user1';
 | 
			
		||||
        $user1->create();
 | 
			
		||||
        $user2 = new Pluf_User();
 | 
			
		||||
        $user2->login = 'user2';
 | 
			
		||||
        $user2->create();
 | 
			
		||||
        
 | 
			
		||||
        // Add a project
 | 
			
		||||
        $prj = new IDF_Project();
 | 
			
		||||
        $prj->create();
 | 
			
		||||
        $tag = $prj->getTagIdsByStatus('open');
 | 
			
		||||
  
 | 
			
		||||
        // First test with no issue
 | 
			
		||||
        $stats = $prj->getIssueCountByOwner();
 | 
			
		||||
        $this->assertEquals($stats, array());
 | 
			
		||||
        
 | 
			
		||||
        // Add some issues
 | 
			
		||||
        $issue1 = new IDF_Issue();
 | 
			
		||||
        $issue1->project = $prj;
 | 
			
		||||
        $issue1->submitter = $user1;
 | 
			
		||||
        $issue1->owner = $user1;
 | 
			
		||||
        $issue1->status = new IDF_Tag($tag[0]);
 | 
			
		||||
        $issue1->create();
 | 
			
		||||
        
 | 
			
		||||
        $issue2 = new IDF_Issue();
 | 
			
		||||
        $issue2->project = $prj;
 | 
			
		||||
        $issue2->submitter = $user2;
 | 
			
		||||
        $issue2->owner = $user1;
 | 
			
		||||
        $issue2->status = new IDF_Tag($tag[0]);
 | 
			
		||||
        $issue2->create();        
 | 
			
		||||
 | 
			
		||||
        $issue3 = new IDF_Issue();
 | 
			
		||||
        $issue3->project = $prj;
 | 
			
		||||
        $issue3->submitter = $user2;
 | 
			
		||||
        $issue3->status = new IDF_Tag($tag[0]);
 | 
			
		||||
        $issue3->create();          
 | 
			
		||||
 | 
			
		||||
        $issue4 = new IDF_Issue();
 | 
			
		||||
        $issue4->project = $prj;
 | 
			
		||||
        $issue4->submitter = $user2;
 | 
			
		||||
        $issue4->owner = $user2;
 | 
			
		||||
        $issue4->status = new IDF_Tag($tag[0]);
 | 
			
		||||
        $issue4->create();    
 | 
			
		||||
                
 | 
			
		||||
        // 2nd test
 | 
			
		||||
        $stats = $prj->getIssueCountByOwner();
 | 
			
		||||
        $expected = array(0          => 1,
 | 
			
		||||
                          $user2->id => 1,
 | 
			
		||||
                          $user1->id => 2);
 | 
			
		||||
        $this->assertEquals($stats, $expected);
 | 
			
		||||
 | 
			
		||||
        // Clean DB
 | 
			
		||||
        $issue4->delete();
 | 
			
		||||
        $issue3->delete();
 | 
			
		||||
        $issue2->delete();
 | 
			
		||||
        $issue1->delete();
 | 
			
		||||
        $prj->delete();
 | 
			
		||||
        $user2->delete();
 | 
			
		||||
        $user1->delete();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -625,6 +625,7 @@ END;
 | 
			
		||||
            'additions'  => array('new_dir', 'new_dir/new_file'),
 | 
			
		||||
            'deletions'  => array('old_dir', 'old_dir/old_file'),
 | 
			
		||||
            'renames'    => array('dir_with_old_name' => 'new_dir/dir_with_new_name'),
 | 
			
		||||
            'copies'     => array(), // this is always empty
 | 
			
		||||
            'patches'    => array('existing_file'),
 | 
			
		||||
            'properties' => array(
 | 
			
		||||
                'new_dir/dir_with_new_name' => array(
 | 
			
		||||
@@ -714,6 +715,31 @@ END;
 | 
			
		||||
        $this->assertEquals('', $commit->diff);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testGetProperties()
 | 
			
		||||
    {
 | 
			
		||||
        $rev = "2345678901234567890123456789012345678901";
 | 
			
		||||
 | 
			
		||||
        $instance = $this->createMock();
 | 
			
		||||
        $instance->getStdio()->setExpectedOutput(array('interface_version'), array(), '13.1');
 | 
			
		||||
 | 
			
		||||
        $stdio =<<<END
 | 
			
		||||
attr "foo" "bar"
 | 
			
		||||
state "unchanged"
 | 
			
		||||
 | 
			
		||||
attr "some new
 | 
			
		||||
line" "and more <weird>-
 | 
			
		||||
nesses"
 | 
			
		||||
END;
 | 
			
		||||
        $instance->getStdio()->setExpectedOutput(array('get_attributes', 'foo'), array('r' => $rev), $stdio);
 | 
			
		||||
        $res = $instance->getProperties($rev, 'foo');
 | 
			
		||||
 | 
			
		||||
        $this->assertEquals(2, count($res));
 | 
			
		||||
        $this->assertEquals(array(
 | 
			
		||||
          'foo' => 'bar',
 | 
			
		||||
          "some new\nline" => "and more <weird>-\nnesses"
 | 
			
		||||
        ), $res);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testGetExtraProperties()
 | 
			
		||||
    {
 | 
			
		||||
        $instance = $this->createMock();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								test/IDF/Scm/SvnTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								test/IDF/Scm/SvnTest.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
<?php
 | 
			
		||||
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 | 
			
		||||
/*
 | 
			
		||||
# ***** BEGIN LICENSE BLOCK *****
 | 
			
		||||
# This file is part of InDefero, an open source project management application.
 | 
			
		||||
# Copyright (C) 2011 Céondo Ltd and contributors.
 | 
			
		||||
#
 | 
			
		||||
# InDefero is free software; you can redistribute it and/or modify
 | 
			
		||||
# it under the terms of the GNU General Public License as published by
 | 
			
		||||
# the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
# (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
# InDefero 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 General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program; if not, write to the Free Software
 | 
			
		||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | 
			
		||||
#
 | 
			
		||||
# ***** END LICENSE BLOCK ***** */
 | 
			
		||||
 | 
			
		||||
class IDF_Scm_SvnTest extends PHPUnit_Framework_TestCase
 | 
			
		||||
{
 | 
			
		||||
    private $proj = null;
 | 
			
		||||
 | 
			
		||||
    public function setUp()
 | 
			
		||||
    {
 | 
			
		||||
        $this->proj = new IDF_Project();
 | 
			
		||||
        $this->proj->id = 1;
 | 
			
		||||
        $this->proj->name = $this->proj->shortname = 'test';
 | 
			
		||||
        $this->proj->create();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function tearDown()
 | 
			
		||||
    {
 | 
			
		||||
        $this->proj->delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createMock($reponame)
 | 
			
		||||
    {
 | 
			
		||||
        $repourl = 'file://'.DATADIR.'/'.__CLASS__.'/'.$reponame;
 | 
			
		||||
        $instance = new IDF_Scm_Svn($repourl, $this->proj);
 | 
			
		||||
        return $instance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testAccessHistoryOfRenamedAndDeletedFiles()
 | 
			
		||||
    {
 | 
			
		||||
        $instance = $this->createMock(__FUNCTION__);
 | 
			
		||||
        $this->assertEquals('new-file', $instance->getPathInfo('new-file', 1)->fullpath);
 | 
			
		||||
        $this->assertEquals('alternate-name', $instance->getPathInfo('alternate-name', 2)->fullpath);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
3
 | 
			
		||||
@@ -0,0 +1,2 @@
 | 
			
		||||
4
 | 
			
		||||
layout sharded 1000
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
fsfs
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
0
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
K 8
 | 
			
		||||
svn:date
 | 
			
		||||
V 27
 | 
			
		||||
2011-08-12T17:06:15.429110Z
 | 
			
		||||
END
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
K 10
 | 
			
		||||
svn:author
 | 
			
		||||
V 7
 | 
			
		||||
patrick
 | 
			
		||||
K 8
 | 
			
		||||
svn:date
 | 
			
		||||
V 27
 | 
			
		||||
2011-08-12T17:07:33.111035Z
 | 
			
		||||
K 7
 | 
			
		||||
svn:log
 | 
			
		||||
V 12
 | 
			
		||||
added a file
 | 
			
		||||
END
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
K 10
 | 
			
		||||
svn:author
 | 
			
		||||
V 7
 | 
			
		||||
patrick
 | 
			
		||||
K 8
 | 
			
		||||
svn:date
 | 
			
		||||
V 27
 | 
			
		||||
2011-08-12T17:07:55.894416Z
 | 
			
		||||
K 7
 | 
			
		||||
svn:log
 | 
			
		||||
V 14
 | 
			
		||||
renamed a file
 | 
			
		||||
END
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
K 10
 | 
			
		||||
svn:author
 | 
			
		||||
V 7
 | 
			
		||||
patrick
 | 
			
		||||
K 8
 | 
			
		||||
svn:date
 | 
			
		||||
V 27
 | 
			
		||||
2011-08-12T17:08:12.646051Z
 | 
			
		||||
K 7
 | 
			
		||||
svn:log
 | 
			
		||||
V 14
 | 
			
		||||
deleted a file
 | 
			
		||||
END
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
PLAIN
 | 
			
		||||
END
 | 
			
		||||
ENDREP
 | 
			
		||||
id: 0.0.r0/17
 | 
			
		||||
type: dir
 | 
			
		||||
count: 0
 | 
			
		||||
text: 0 0 4 4 2d2977d1c96f487abe4a1e202dd03b4e
 | 
			
		||||
cpath: /
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
17 107
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
DELTA
 | 
			
		||||
SVNENDREP
 | 
			
		||||
id: 0-1.0.r1/17
 | 
			
		||||
type: file
 | 
			
		||||
count: 0
 | 
			
		||||
text: 1 0 4 0 d41d8cd98f00b204e9800998ecf8427e da39a3ee5e6b4b0d3255bfef95601890afd80709 0-0/_2
 | 
			
		||||
cpath: /new-file
 | 
			
		||||
copyroot: 0 /
 | 
			
		||||
 | 
			
		||||
PLAIN
 | 
			
		||||
K 8
 | 
			
		||||
new-file
 | 
			
		||||
V 16
 | 
			
		||||
file 0-1.0.r1/17
 | 
			
		||||
END
 | 
			
		||||
ENDREP
 | 
			
		||||
id: 0.0.r1/232
 | 
			
		||||
type: dir
 | 
			
		||||
pred: 0.0.r0/17
 | 
			
		||||
count: 1
 | 
			
		||||
text: 1 180 39 39 4a0c4e617d69f8a0bf11161d1e1b09a6
 | 
			
		||||
cpath: /
 | 
			
		||||
copyroot: 0 /
 | 
			
		||||
 | 
			
		||||
_0.0.t0-0 add-file true false /new-file
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
232 357
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
id: 0-1.0-2.r2/0
 | 
			
		||||
type: file
 | 
			
		||||
pred: 0-1.0.r1/17
 | 
			
		||||
count: 1
 | 
			
		||||
text: 1 0 4 0 d41d8cd98f00b204e9800998ecf8427e da39a3ee5e6b4b0d3255bfef95601890afd80709 0-0/_2
 | 
			
		||||
cpath: /alternate-name
 | 
			
		||||
copyfrom: 1 /new-file
 | 
			
		||||
 | 
			
		||||
PLAIN
 | 
			
		||||
K 14
 | 
			
		||||
alternate-name
 | 
			
		||||
V 17
 | 
			
		||||
file 0-1.0-2.r2/0
 | 
			
		||||
END
 | 
			
		||||
ENDREP
 | 
			
		||||
id: 0.0.r2/256
 | 
			
		||||
type: dir
 | 
			
		||||
pred: 0.0.r1/232
 | 
			
		||||
count: 2
 | 
			
		||||
text: 2 196 47 47 518568bcb7e6fe3165235a9f9993f5e1
 | 
			
		||||
cpath: /
 | 
			
		||||
copyroot: 0 /
 | 
			
		||||
 | 
			
		||||
0-1.0.r1/17 delete-file false false /new-file
 | 
			
		||||
 | 
			
		||||
0-1._0.t1-1 add-file false false /alternate-name
 | 
			
		||||
1 /new-file
 | 
			
		||||
 | 
			
		||||
256 382
 | 
			
		||||
@@ -0,0 +1,15 @@
 | 
			
		||||
PLAIN
 | 
			
		||||
END
 | 
			
		||||
ENDREP
 | 
			
		||||
id: 0.0.r3/17
 | 
			
		||||
type: dir
 | 
			
		||||
pred: 0.0.r2/256
 | 
			
		||||
count: 3
 | 
			
		||||
text: 3 0 4 4 2d2977d1c96f487abe4a1e202dd03b4e
 | 
			
		||||
cpath: /
 | 
			
		||||
copyroot: 0 /
 | 
			
		||||
 | 
			
		||||
0-1.0-2.r2/0 delete-file false false /alternate-name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
17 138
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
3
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
5d5eaf70-0fe3-4d07-ada0-45c336a5bfad
 | 
			
		||||
@@ -0,0 +1 @@
 | 
			
		||||
5
 | 
			
		||||
@@ -86,6 +86,14 @@ a.soft:visited {
 | 
			
		||||
 | 
			
		||||
div.context {
 | 
			
		||||
  padding-left: 1em;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  top: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.context h3 {
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  margin: 10px 0 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -127,9 +135,9 @@ ul.errorlist {
 | 
			
		||||
div.user-messages {
 | 
			
		||||
  border: 1px solid rgb(229, 225, 169);
 | 
			
		||||
  background-color: #fffde3;
 | 
			
		||||
  margin-bottom: 1em;
 | 
			
		||||
  margin-bottom: 2em;
 | 
			
		||||
  margin-left: -1px;
 | 
			
		||||
  width: 90%;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.theterms {
 | 
			
		||||
@@ -275,7 +283,7 @@ div.issue-changes-timeline {
 | 
			
		||||
 | 
			
		||||
div.issue-prev-next {
 | 
			
		||||
  float: right;
 | 
			
		||||
  margin-top: -25px;
 | 
			
		||||
  margin-top: -1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.issue-submit-info {
 | 
			
		||||
@@ -928,14 +936,6 @@ ol > li {
 | 
			
		||||
/**
 | 
			
		||||
 * List expander for tag and branch view
 | 
			
		||||
 */
 | 
			
		||||
.context {}
 | 
			
		||||
 | 
			
		||||
.context h3 {
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  margin: 10px 0 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.context > .expander {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
@@ -1058,6 +1058,10 @@ span.scm-action.renamed {
 | 
			
		||||
  background-color: purple;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
span.scm-action.copied {
 | 
			
		||||
  background-color: orchid;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
span.scm-action.property-changed {
 | 
			
		||||
  background-color: blue;
 | 
			
		||||
}
 | 
			
		||||
@@ -1100,3 +1104,53 @@ div.p-list-private {
 | 
			
		||||
    right: -3px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Issue summary
 | 
			
		||||
 */
 | 
			
		||||
div.issue-summary {
 | 
			
		||||
    float: left;
 | 
			
		||||
    width: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.issue-summary > div {
 | 
			
		||||
    margin-right: 3em;
 | 
			
		||||
    padding-top: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.issue-summary h2 {
 | 
			
		||||
    border-bottom: 1px solid #A5E26A;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.issue-summary {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.issue-summary tr td {
 | 
			
		||||
    border: 0;
 | 
			
		||||
    padding: .1em .005em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.issue-summary td.graph {
 | 
			
		||||
    width: 60%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.issue-summary td.count {
 | 
			
		||||
    text-align: right;
 | 
			
		||||
    padding-right: .5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.graph {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.issue-summary td.graph-color {
 | 
			
		||||
    background: #3C78B5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
table.issue-summary td.graph-percent {
 | 
			
		||||
    padding-left: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user