Browse Source

new dependency model, tab view, and db files

rightbit 4 years ago
parent
commit
06b574b6f3

+ 59 - 0
app/controller/issues.php

@@ -713,6 +713,45 @@ class Issues extends \Controller {
 					if($f3->get("AJAX"))
 						return;
 					break;
+
+
+
+				case "add_dependency":
+					$dependencies = new \Model\Issue\Dependency;
+					// Loads just in case the task  is already a dependency
+					$dependencies->load(array("issue_id = ? AND dependency_id = ?", $issue->id, $post["id"]));
+					$dependencies->issue_id = $issue->id;
+					$dependencies->dependency_id = $post["id"];
+					$dependencies->dependency_type = $post["type_id"];
+					$dependencies->save();
+
+					if($f3->get("AJAX"))
+						return;
+					break;
+
+				case "add_dependent":
+					$dependencies = new \Model\Issue\Dependency;
+					// Loads just in case the task  is already a dependency
+					$dependencies->load(array("issue_id = ? AND dependency_id = ?",  $post["id"],  $issue->id));
+					$dependencies->dependency_id = $issue->id;
+					$dependencies->issue_id = $post["id"];
+					$dependencies->dependency_type = $post["type_id"];
+					$dependencies->save();
+
+					if($f3->get("AJAX"))
+						return;
+					break;
+
+
+				case "remove_dependency":
+					$dependencies = new \Model\Issue\Dependency;
+					$dependencies->load($post["id"]);
+					$dependencies->delete();
+
+					if($f3->get("AJAX"))
+						return;
+					break;
+
 			}
 		}
 
@@ -840,6 +879,26 @@ class Issues extends \Controller {
 		));
 	}
 
+	public function single_dependencies($f3, $params) {
+		$issue = new \Model\Issue;
+		$issue->load($params["id"]);
+
+		if($issue->id) {
+			$dependencies = new \Model\Issue\Dependency;
+			$f3->set("dependencies", $dependencies->findby_issue($issue->id));
+			$f3->set("dependents", $dependencies->findby_dependent($issue->id));
+
+			$this->_printJson(array(
+				"total" => count($f3->get("dependencies")) + count($f3->get("dependents")),
+				"html" => $this->_cleanJson(\Helper\View::instance()->render("issues/single/dependencies.html"))
+			));
+		} else {
+			$f3->error(404);
+		}
+
+
+	}
+
 	public function single_delete($f3, $params) {
 		$issue = new \Model\Issue;
 		$issue->load($params["id"]);

+ 14 - 0
app/dict/en.ini

@@ -53,6 +53,7 @@ dict.close=Close
 dict.error.loading_issue_history=Error loading issue history.
 dict.error.loading_issue_watchers=Error loading issue watchers.
 dict.error.loading_related_issues=Error loading related issues.
+dict.error.loading_dependencies=Error loading dependencies.
 
 ; Dashboard
 dict.taskboard=Taskboard
@@ -108,9 +109,11 @@ dict.cols.parent_id=Parent ID
 dict.cols.parent=Parent
 dict.cols.sprint=Sprint
 dict.cols.created=Created
+dict.cols.start=Start
 dict.cols.due=Due
 dict.cols.closed=Closed
 dict.cols.hours_spent=Hours Spent
+dict.cols.depends_on=Depends On
 
 ; Issue editing
 dict.not_assigned=Not Assigned
@@ -156,6 +159,17 @@ dict.watchers=Watchers
 dict.child_tasks=Child Tasks
 dict.related_tasks=Related Tasks
 
+dict.dependencies=Dependencies
+dict.dependency=Dependency
+dict.dependent=Dependent
+dict.task_depends=This task depends on:
+dict.task_dependency=This task  is a dependency for:
+;Dependency Types (Finish-Start, Finish-Finish, Start-Start, Start-Finish)
+dic.fs=FS
+dic.ff=FF
+dic.ss=SS
+dic.sf=SF
+
 dict.write_a_comment=Write a comment…
 dict.save_comment=Save Comment
 

+ 0 - 84
app/helper/diff/renderer/base.php

@@ -1,84 +0,0 @@
-<?php
-/**
- * Abstract class for diff renderers in PHP DiffLib.
- *
- * PHP version 5
- *
- * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *  - Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- *  - Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *  - Neither the name of the Chris Boulton nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @package DiffLib
- * @author Chris Boulton <chris.boulton@interspire.com>
- * @copyright (c) 2009 Chris Boulton
- * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
- * @version 1.1
- * @link http://github.com/chrisboulton/php-diff
- */
-
-namespace Helper\Diff\Renderer;
-
-abstract class Base
-{
-	/**
-	 * @var object Instance of the diff class that this renderer is generating the rendered diff for.
-	 */
-	public $diff;
-
-	/**
-	 * @var array Array of the default options that apply to this renderer.
-	 */
-	protected $defaultOptions = array();
-
-	/**
-	 * @var array Array containing the user applied and merged default options for the renderer.
-	 */
-	protected $options = array();
-
-	/**
-	 * The constructor. Instantiates the rendering engine and if options are passed,
-	 * sets the options for the renderer.
-	 *
-	 * @param array $options Optionally, an array of the options for the renderer.
-	 */
-	public function __construct(array $options = array())
-	{
-		$this->setOptions($options);
-	}
-
-	/**
-	 * Set the options of the renderer to those supplied in the passed in array.
-	 * Options are merged with the default to ensure that there aren't any missing
-	 * options.
-	 *
-	 * @param array $options Array of options to set.
-	 */
-	public function setOptions(array $options)
-	{
-		$this->options = array_merge($this->defaultOptions, $options);
-	}
-}

+ 0 - 227
app/helper/diff/renderer/html/base.php

@@ -1,227 +0,0 @@
-<?php
-/**
- * Base renderer for rendering HTML based diffs for PHP DiffLib.
- *
- * PHP version 5
- *
- * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *  - Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- *  - Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *  - Neither the name of the Chris Boulton nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @package DiffLib
- * @author Chris Boulton <chris.boulton@interspire.com>
- * @copyright (c) 2009 Chris Boulton
- * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
- * @version 1.1
- * @link http://github.com/chrisboulton/php-diff
- */
-
-namespace Helper\Diff\Renderer\Html;
-
-class Base extends \Helper\Diff\Renderer\Base
-{
-	/**
-	 * @var array Array of the default options that apply to this renderer.
-	 */
-	protected $defaultOptions = array(
-		'tabSize' => 4
-	);
-
-	/**
-	 * Render and return an array structure suitable for generating HTML
-	 * based differences. Generally called by subclasses that generate a
-	 * HTML based diff and return an array of the changes to show in the diff.
-	 *
-	 * @return array An array of the generated chances, suitable for presentation in HTML.
-	 */
-	public function render()
-	{
-		// As we'll be modifying a & b to include our change markers,
-		// we need to get the contents and store them here. That way
-		// we're not going to destroy the original data
-		$a = $this->diff->getA();
-		$b = $this->diff->getB();
-
-		$changes = array();
-		$opCodes = $this->diff->getGroupedOpcodes();
-		foreach($opCodes as $group) {
-			$blocks = array();
-			$lastTag = null;
-			$lastBlock = 0;
-			foreach($group as $code) {
-				list($tag, $i1, $i2, $j1, $j2) = $code;
-
-				if($tag == 'replace' && $i2 - $i1 == $j2 - $j1) {
-					for($i = 0; $i < ($i2 - $i1); ++$i) {
-						$fromLine = $a[$i1 + $i];
-						$toLine = $b[$j1 + $i];
-
-						list($start, $end) = $this->getChangeExtent($fromLine, $toLine);
-						if($start != 0 || $end != 0) {
-							$last = $end + strlen($fromLine);
-							$fromLine = substr_replace($fromLine, "\0", $start, 0);
-							$fromLine = substr_replace($fromLine, "\1", $last + 1, 0);
-							$last = $end + strlen($toLine);
-							$toLine = substr_replace($toLine, "\0", $start, 0);
-							$toLine = substr_replace($toLine, "\1", $last + 1, 0);
-							$a[$i1 + $i] = $fromLine;
-							$b[$j1 + $i] = $toLine;
-						}
-					}
-				}
-
-				if($tag != $lastTag) {
-					$blocks[] = array(
-						'tag' => $tag,
-						'base' => array(
-							'offset' => $i1,
-							'lines' => array()
-						),
-						'changed' => array(
-							'offset' => $j1,
-							'lines' => array()
-						)
-					);
-					$lastBlock = count($blocks)-1;
-				}
-
-				$lastTag = $tag;
-
-				if($tag == 'equal') {
-					$lines = array_slice($a, $i1, ($i2 - $i1));
-					$blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
-					$lines = array_slice($b, $j1, ($j2 - $j1));
-					$blocks[$lastBlock]['changed']['lines'] +=  $this->formatLines($lines);
-				}
-				else {
-					if($tag == 'replace' || $tag == 'delete') {
-						$lines = array_slice($a, $i1, ($i2 - $i1));
-						$lines = $this->formatLines($lines);
-						$lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines);
-						$blocks[$lastBlock]['base']['lines'] += $lines;
-					}
-
-					if($tag == 'replace' || $tag == 'insert') {
-						$lines = array_slice($b, $j1, ($j2 - $j1));
-						$lines =  $this->formatLines($lines);
-						$lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines);
-						$blocks[$lastBlock]['changed']['lines'] += $lines;
-					}
-				}
-			}
-			$changes[] = $blocks;
-		}
-		return $changes;
-	}
-
-	/**
-	 * Given two strings, determine where the changes in the two strings
-	 * begin, and where the changes in the two strings end.
-	 *
-	 * @param string $fromLine The first string.
-	 * @param string $toLine The second string.
-	 * @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
-	 */
-	private function getChangeExtent($fromLine, $toLine)
-	{
-		$start = 0;
-		$limit = min(strlen($fromLine), strlen($toLine));
-		while($start < $limit && $fromLine{$start} == $toLine{$start}) {
-			++$start;
-		}
-		$end = -1;
-		$limit = $limit - $start;
-		while(-$end <= $limit && substr($fromLine, $end, 1) == substr($toLine, $end, 1)) {
-			--$end;
-		}
-		return array(
-			$start,
-			$end + 1
-		);
-	}
-
-	/**
-	 * Format a series of lines suitable for output in a HTML rendered diff.
-	 * This involves replacing tab characters with spaces, making the HTML safe
-	 * for output, ensuring that double spaces are replaced with &nbsp; etc.
-	 *
-	 * @param array $lines Array of lines to format.
-	 * @return array Array of the formatted lines.
-	 */
-	private function formatLines($lines)
-	{
-		$lines = array_map(array($this, 'ExpandTabs'), $lines);
-		$lines = array_map(array($this, 'HtmlSafe'), $lines);
-		foreach($lines as &$line) {
-			$line = preg_replace_callback('# ( +)|^ #', array($this, 'fixSpaces'), $line);
-		}
-		return $lines;
-	}
-
-	/**
-	 * Replace a string containing spaces with a HTML representation using &nbsp;.
-	 *
-	 * @param string $spaces The string of spaces.
-	 * @return string The HTML representation of the string.
-	 */
-	function fixSpaces($spaces='')
-	{
-		$count = strlen($spaces);
-		if($count == 0) {
-			return '';
-		}
-
-		$div = floor($count / 2);
-		$mod = $count % 2;
-		return str_repeat('&nbsp; ', $div).str_repeat('&nbsp;', $mod);
-	}
-
-	/**
-	 * Replace tabs in a single line with a number of spaces as defined by the tabSize option.
-	 *
-	 * @param string $line The containing tabs to convert.
-	 * @return string The line with the tabs converted to spaces.
-	 */
-	private function expandTabs($line)
-	{
-		return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
-	}
-
-	/**
-	 * Make a string containing HTML safe for output on a page.
-	 *
-	 * @param string $string The string.
-	 * @return string The string with the HTML characters replaced by entities.
-	 */
-	private function htmlSafe($string)
-	{
-		if(function_exists('mb_convert_encoding')) {
-			$string = mb_convert_encoding($string, 'UTF-8');
-		}
-		return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
-	}
-}

+ 0 - 143
app/helper/diff/renderer/html/inline.php

@@ -1,143 +0,0 @@
-<?php
-/**
- * Inline HTML diff generator for PHP DiffLib.
- *
- * PHP version 5
- *
- * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *  - Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- *  - Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *  - Neither the name of the Chris Boulton nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @package DiffLib
- * @author Chris Boulton <chris.boulton@interspire.com>
- * @copyright (c) 2009 Chris Boulton
- * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
- * @version 1.1
- * @link http://github.com/chrisboulton/php-diff
- */
-
-namespace Helper\Diff\Renderer\Html;
-
-class Inline extends \Helper\Diff\Renderer\Html\Base
-{
-	/**
-	 * Render a and return diff with changes between the two sequences
-	 * displayed inline (under each other)
-	 *
-	 * @return string The generated inline diff.
-	 */
-	public function render()
-	{
-		$changes = parent::render();
-		$html = '';
-		if(empty($changes)) {
-			return $html;
-		}
-
-		$html .= '<table class="Differences DifferencesInline">';
-		$html .= '<thead>';
-		$html .= '<tr>';
-		$html .= '<th>Old</th>';
-		$html .= '<th>New</th>';
-		$html .= '<th>Differences</th>';
-		$html .= '</tr>';
-		$html .= '</thead>';
-		foreach($changes as $i => $blocks) {
-			// If this is a separate block, we're condensing code so output ...,
-			// indicating a significant portion of the code has been collapsed as
-			// it is the same
-			if($i > 0) {
-				$html .= '<tbody class="Skipped">';
-				$html .= '<th>&hellip;</th>';
-				$html .= '<th>&hellip;</th>';
-				$html .= '<td>&nbsp;</td>';
-				$html .= '</tbody>';
-			}
-
-			foreach($blocks as $change) {
-				$html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
-				// Equal changes should be shown on both sides of the diff
-				if($change['tag'] == 'equal') {
-					foreach($change['base']['lines'] as $no => $line) {
-						$fromLine = $change['base']['offset'] + $no + 1;
-						$toLine = $change['changed']['offset'] + $no + 1;
-						$html .= '<tr>';
-						$html .= '<th>'.$fromLine.'</th>';
-						$html .= '<th>'.$toLine.'</th>';
-						$html .= '<td class="Left">'.$line.'</td>';
-						$html .= '</tr>';
-					}
-				}
-				// Added lines only on the right side
-				else if($change['tag'] == 'insert') {
-					foreach($change['changed']['lines'] as $no => $line) {
-						$toLine = $change['changed']['offset'] + $no + 1;
-						$html .= '<tr>';
-						$html .= '<th>&nbsp;</th>';
-						$html .= '<th>'.$toLine.'</th>';
-						$html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</td>';
-						$html .= '</tr>';
-					}
-				}
-				// Show deleted lines only on the left side
-				else if($change['tag'] == 'delete') {
-					foreach($change['base']['lines'] as $no => $line) {
-						$fromLine = $change['base']['offset'] + $no + 1;
-						$html .= '<tr>';
-						$html .= '<th>'.$fromLine.'</th>';
-						$html .= '<th>&nbsp;</th>';
-						$html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</td>';
-						$html .= '</tr>';
-					}
-				}
-				// Show modified lines on both sides
-				else if($change['tag'] == 'replace') {
-					foreach($change['base']['lines'] as $no => $line) {
-						$fromLine = $change['base']['offset'] + $no + 1;
-						$html .= '<tr>';
-						$html .= '<th>'.$fromLine.'</th>';
-						$html .= '<th>&nbsp;</th>';
-						$html .= '<td class="Left"><span>'.$line.'</span></td>';
-						$html .= '</tr>';
-					}
-
-					foreach($change['changed']['lines'] as $no => $line) {
-						$toLine = $change['changed']['offset'] + $no + 1;
-						$html .= '<tr>';
-						$html .= '<th>'.$toLine.'</th>';
-						$html .= '<th>&nbsp;</th>';
-						$html .= '<td class="Right"><span>'.$line.'</span></td>';
-						$html .= '</tr>';
-					}
-				}
-				$html .= '</tbody>';
-			}
-		}
-		$html .= '</table>';
-		return $html;
-	}
-}

+ 0 - 163
app/helper/diff/renderer/html/sidebyside.php

@@ -1,163 +0,0 @@
-<?php
-/**
- * Side by Side HTML diff generator for PHP DiffLib.
- *
- * PHP version 5
- *
- * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *  - Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- *  - Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *  - Neither the name of the Chris Boulton nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @package DiffLib
- * @author Chris Boulton <chris.boulton@interspire.com>
- * @copyright (c) 2009 Chris Boulton
- * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
- * @version 1.1
- * @link http://github.com/chrisboulton/php-diff
- */
-
-namespace Helper\Diff\Renderer\Html;
-
-class Sidebyside extends \Helper\Diff\Renderer\Html\Base
-{
-	/**
-	 * Render a and return diff with changes between the two sequences
-	 * displayed side by side.
-	 *
-	 * @return string The generated side by side diff.
-	 */
-	public function render()
-	{
-		$changes = parent::render();
-
-		$html = '';
-		if(empty($changes)) {
-			return $html;
-		}
-
-		$html .= '<table class="Differences DifferencesSideBySide">';
-		$html .= '<thead>';
-		$html .= '<tr>';
-		$html .= '<th colspan="2">Old Version</th>';
-		$html .= '<th colspan="2">New Version</th>';
-		$html .= '</tr>';
-		$html .= '</thead>';
-		foreach($changes as $i => $blocks) {
-			if($i > 0) {
-				$html .= '<tbody class="Skipped">';
-				$html .= '<th>&hellip;</th><td>&nbsp;</td>';
-				$html .= '<th>&hellip;</th><td>&nbsp;</td>';
-				$html .= '</tbody>';
-			}
-
-			foreach($blocks as $change) {
-				$html .= '<tbody class="Change'.ucfirst($change['tag']).'">';
-				// Equal changes should be shown on both sides of the diff
-				if($change['tag'] == 'equal') {
-					foreach($change['base']['lines'] as $no => $line) {
-						$fromLine = $change['base']['offset'] + $no + 1;
-						$toLine = $change['changed']['offset'] + $no + 1;
-						$html .= '<tr>';
-						$html .= '<th>'.$fromLine.'</th>';
-						$html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</span></td>';
-						$html .= '<th>'.$toLine.'</th>';
-						$html .= '<td class="Right"><span>'.$line.'</span>&nbsp;</span></td>';
-						$html .= '</tr>';
-					}
-				}
-				// Added lines only on the right side
-				else if($change['tag'] == 'insert') {
-					foreach($change['changed']['lines'] as $no => $line) {
-						$toLine = $change['changed']['offset'] + $no + 1;
-						$html .= '<tr>';
-						$html .= '<th>&nbsp;</th>';
-						$html .= '<td class="Left">&nbsp;</td>';
-						$html .= '<th>'.$toLine.'</th>';
-						$html .= '<td class="Right"><ins>'.$line.'</ins>&nbsp;</td>';
-						$html .= '</tr>';
-					}
-				}
-				// Show deleted lines only on the left side
-				else if($change['tag'] == 'delete') {
-					foreach($change['base']['lines'] as $no => $line) {
-						$fromLine = $change['base']['offset'] + $no + 1;
-						$html .= '<tr>';
-						$html .= '<th>'.$fromLine.'</th>';
-						$html .= '<td class="Left"><del>'.$line.'</del>&nbsp;</td>';
-						$html .= '<th>&nbsp;</th>';
-						$html .= '<td class="Right">&nbsp;</td>';
-						$html .= '</tr>';
-					}
-				}
-				// Show modified lines on both sides
-				else if($change['tag'] == 'replace') {
-					if(count($change['base']['lines']) >= count($change['changed']['lines'])) {
-						foreach($change['base']['lines'] as $no => $line) {
-							$fromLine = $change['base']['offset'] + $no + 1;
-							$html .= '<tr>';
-							$html .= '<th>'.$fromLine.'</th>';
-							$html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</td>';
-							if(!isset($change['changed']['lines'][$no])) {
-								$toLine = '&nbsp;';
-								$changedLine = '&nbsp;';
-							}
-							else {
-								$toLine = $change['base']['offset'] + $no + 1;
-								$changedLine = '<span>'.$change['changed']['lines'][$no].'</span>';
-							}
-							$html .= '<th>'.$toLine.'</th>';
-							$html .= '<td class="Right">'.$changedLine.'</td>';
-							$html .= '</tr>';
-						}
-					}
-					else {
-						foreach($change['changed']['lines'] as $no => $changedLine) {
-							if(!isset($change['base']['lines'][$no])) {
-								$fromLine = '&nbsp;';
-								$line = '&nbsp;';
-							}
-							else {
-								$fromLine = $change['base']['offset'] + $no + 1;
-								$line = '<span>'.$change['base']['lines'][$no].'</span>';
-							}
-							$html .= '<tr>';
-							$html .= '<th>'.$fromLine.'</th>';
-							$html .= '<td class="Left"><span>'.$line.'</span>&nbsp;</td>';
-							$toLine = $change['changed']['offset'] + $no + 1;
-							$html .= '<th>'.$toLine.'</th>';
-							$html .= '<td class="Right">'.$changedLine.'</td>';
-							$html .= '</tr>';
-						}
-					}
-				}
-				$html .= '</tbody>';
-			}
-		}
-		$html .= '</table>';
-		return $html;
-	}
-}

+ 0 - 128
app/helper/diff/renderer/text/context.php

@@ -1,128 +0,0 @@
-<?php
-/**
- * Context diff generator for PHP DiffLib.
- *
- * PHP version 5
- *
- * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *  - Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- *  - Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *  - Neither the name of the Chris Boulton nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @package DiffLib
- * @author Chris Boulton <chris.boulton@interspire.com>
- * @copyright (c) 2009 Chris Boulton
- * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
- * @version 1.1
- * @link http://github.com/chrisboulton/php-diff
- */
-
-namespace Helper\Diff\Renderer\Text;
-
-class context extends \Helper\Diff\Renderer\Base
-{
-	/**
-	 * @var array Array of the different opcode tags and how they map to the context diff equivalent.
-	 */
-	private $tagMap = array(
-		'insert' => '+',
-		'delete' => '-',
-		'replace' => '!',
-		'equal' => ' '
-	);
-
-	/**
-	 * Render and return a context formatted (old school!) diff file.
-	 *
-	 * @return string The generated context diff.
-	 */
-	public function render()
-	{
-		$diff = '';
-		$opCodes = $this->diff->getGroupedOpcodes();
-		foreach($opCodes as $group) {
-			$diff .= "***************\n";
-			$lastItem = count($group)-1;
-			$i1 = $group[0][1];
-			$i2 = $group[$lastItem][2];
-			$j1 = $group[0][3];
-			$j2 = $group[$lastItem][4];
-
-			if($i2 - $i1 >= 2) {
-				$diff .= '*** '.($group[0][1] + 1).','.$i2." ****\n";
-			}
-			else {
-				$diff .= '*** '.$i2." ****\n";
-			}
-
-			if($j2 - $j1 >= 2) {
-				$separator = '--- '.($j1 + 1).','.$j2." ----\n";
-			}
-			else {
-				$separator = '--- '.$j2." ----\n";
-			}
-
-			$hasVisible = false;
-			foreach($group as $code) {
-				if($code[0] == 'replace' || $code[0] == 'delete') {
-					$hasVisible = true;
-					break;
-				}
-			}
-
-			if($hasVisible) {
-				foreach($group as $code) {
-					list($tag, $i1, $i2, $j1, $j2) = $code;
-					if($tag == 'insert') {
-						continue;
-					}
-					$diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetA($i1, $i2))."\n";
-				}
-			}
-
-			$hasVisible = false;
-			foreach($group as $code) {
-				if($code[0] == 'replace' || $code[0] == 'insert') {
-					$hasVisible = true;
-					break;
-				}
-			}
-
-			$diff .= $separator;
-
-			if($hasVisible) {
-				foreach($group as $code) {
-					list($tag, $i1, $i2, $j1, $j2) = $code;
-					if($tag == 'delete') {
-						continue;
-					}
-					$diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetB($j1, $j2))."\n";
-				}
-			}
-		}
-		return $diff;
-	}
-}

+ 0 - 87
app/helper/diff/renderer/text/unified.php

@@ -1,87 +0,0 @@
-<?php
-/**
- * Unified diff generator for PHP DiffLib.
- *
- * PHP version 5
- *
- * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *  - Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- *  - Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *  - Neither the name of the Chris Boulton nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @package DiffLib
- * @author Chris Boulton <chris.boulton@interspire.com>
- * @copyright (c) 2009 Chris Boulton
- * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
- * @version 1.1
- * @link http://github.com/chrisboulton/php-diff
- */
-
-namespace Helper\Diff\Renderer\Text;
-
-class Unified extends \Helper\Diff\Renderer\Base
-{
-	/**
-	 * Render and return a unified diff.
-	 *
-	 * @return string The unified diff.
-	 */
-	public function render()
-	{
-		$diff = '';
-		$opCodes = $this->diff->getGroupedOpcodes();
-		foreach($opCodes as $group) {
-			$lastItem = count($group)-1;
-			$i1 = $group[0][1];
-			$i2 = $group[$lastItem][2];
-			$j1 = $group[0][3];
-			$j2 = $group[$lastItem][4];
-
-			if($i1 == 0 && $i2 == 0) {
-				$i1 = -1;
-				$i2 = -1;
-			}
-
-			$diff .= '@@ -'.($i1 + 1).','.($i2 - $i1).' +'.($j1 + 1).','.($j2 - $j1)." @@\n";
-			foreach($group as $code) {
-				list($tag, $i1, $i2, $j1, $j2) = $code;
-				if($tag == 'equal') {
-					$diff .= ' '.implode("\n ", $this->diff->GetA($i1, $i2))."\n";
-				}
-				else {
-					if($tag == 'replace' || $tag == 'delete') {
-						$diff .= '-'.implode("\n-", $this->diff->GetA($i1, $i2))."\n";
-					}
-
-					if($tag == 'replace' || $tag == 'insert') {
-						$diff .= '+'.implode("\n+", $this->diff->GetB($j1, $j2))."\n";
-					}
-				}
-			}
-		}
-		return $diff;
-	}
-}

+ 0 - 744
app/helper/diff/sequencematcher.php

@@ -1,744 +0,0 @@
-<?php
-/**
- * Sequence matcher for Diff
- *
- * PHP version 5
- *
- * Copyright (c) 2009 Chris Boulton <chris.boulton@interspire.com>
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- *  - Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- *  - Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- *  - Neither the name of the Chris Boulton nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * @package Diff
- * @author Chris Boulton <chris.boulton@interspire.com>
- * @copyright (c) 2009 Chris Boulton
- * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
- * @version 1.1
- * @link http://github.com/chrisboulton/php-diff
- */
-
-namespace Helper\Diff;
-
-class Sequencematcher
-{
-	/**
-	 * @var string|array Either a string or an array containing a callback function to determine if a line is "junk" or not.
-	 */
-	private $junkCallback = null;
-
-	/**
-	 * @var array The first sequence to compare against.
-	 */
-	private $a = null;
-
-	/**
-	 * @var array The second sequence.
-	 */
-	private $b = null;
-
-	/**
-	 * @var array Array of characters that are considered junk from the second sequence. Characters are the array key.
-	 */
-	private $junkDict = array();
-
-	/**
-	 * @var array Array of indices that do not contain junk elements.
-	 */
-	private $b2j = array();
-
-	private $options = array();
-
-	private $defaultOptions = array(
-		'ignoreNewLines' => false,
-		'ignoreWhitespace' => false,
-		'ignoreCase' => false
-	);
-
-	/**
-	 * The constructor. With the sequences being passed, they'll be set for the
-	 * sequence matcher and it will perform a basic cleanup & calculate junk
-	 * elements.
-	 *
-	 * @param string|array $a A string or array containing the lines to compare against.
-	 * @param string|array $b A string or array containing the lines to compare.
-	 * @param string|array $junkCallback Either an array or string that references a callback function (if there is one) to determine 'junk' characters.
-	 */
-	public function __construct($a, $b, $junkCallback=null, $options)
-	{
-		$this->a = null;
-		$this->b = null;
-		$this->junkCallback = $junkCallback;
-		$this->setOptions($options);
-		$this->setSequences($a, $b);
-	}
-
-	public function setOptions($options)
-	{
-		$this->options = array_merge($this->defaultOptions, $options);
-	}
-
-	/**
-	 * Set the first and second sequences to use with the sequence matcher.
-	 *
-	 * @param string|array $a A string or array containing the lines to compare against.
-	 * @param string|array $b A string or array containing the lines to compare.
-	 */
-	public function setSequences($a, $b)
-	{
-		$this->setSeq1($a);
-		$this->setSeq2($b);
-	}
-
-	/**
-	 * Set the first sequence ($a) and reset any internal caches to indicate that
-	 * when calling the calculation methods, we need to recalculate them.
-	 *
-	 * @param string|array $a The sequence to set as the first sequence.
-	 */
-	public function setSeq1($a)
-	{
-		if(!is_array($a)) {
-			$a = str_split($a);
-		}
-		if($a == $this->a) {
-			return;
-		}
-
-		$this->a= $a;
-		$this->matchingBlocks = null;
-		$this->opCodes = null;
-	}
-
-	/**
-	 * Set the second sequence ($b) and reset any internal caches to indicate that
-	 * when calling the calculation methods, we need to recalculate them.
-	 *
-	 * @param string|array $b The sequence to set as the second sequence.
-	 */
-	public function setSeq2($b)
-	{
-		if(!is_array($b)) {
-			$b = str_split($b);
-		}
-		if($b == $this->b) {
-			return;
-		}
-
-		$this->b = $b;
-		$this->matchingBlocks = null;
-		$this->opCodes = null;
-		$this->fullBCount = null;
-		$this->chainB();
-	}
-
-	/**
-	 * Generate the internal arrays containing the list of junk and non-junk
-	 * characters for the second ($b) sequence.
-	 */
-	private function chainB()
-	{
-		$length = count ($this->b);
-		$this->b2j = array();
-		$popularDict = array();
-
-		for($i = 0; $i < $length; ++$i) {
-			$char = $this->b[$i];
-			if(isset($this->b2j[$char])) {
-				if($length >= 200 && count($this->b2j[$char]) * 100 > $length) {
-					$popularDict[$char] = 1;
-					unset($this->b2j[$char]);
-				}
-				else {
-					$this->b2j[$char][] = $i;
-				}
-			}
-			else {
-				$this->b2j[$char] = array(
-					$i
-				);
-			}
-		}
-
-		// Remove leftovers
-		foreach(array_keys($popularDict) as $char) {
-			unset($this->b2j[$char]);
-		}
-
-		$this->junkDict = array();
-		if(is_callable($this->junkCallback)) {
-			foreach(array_keys($popularDict) as $char) {
-				if(call_user_func($this->junkCallback, $char)) {
-					$this->junkDict[$char] = 1;
-					unset($popularDict[$char]);
-				}
-			}
-
-			foreach(array_keys($this->b2j) as $char) {
-				if(call_user_func($this->junkCallback, $char)) {
-					$this->junkDict[$char] = 1;
-					unset($this->b2j[$char]);
-				}
-			}
-		}
-	}
-
-	/**
-	 * Checks if a particular character is in the junk dictionary
-	 * for the list of junk characters.
-	 *
-	 * @return boolean $b True if the character is considered junk. False if not.
-	 */
-	private function isBJunk($b)
-	{
-		if(isset($this->juncDict[$b])) {
-			return true;
-		}
-
-		return false;
-	}
-
-	/**
-	 * Find the longest matching block in the two sequences, as defined by the
-	 * lower and upper constraints for each sequence. (for the first sequence,
-	 * $alo - $ahi and for the second sequence, $blo - $bhi)
-	 *
-	 * Essentially, of all of the maximal matching blocks, return the one that
-	 * startest earliest in $a, and all of those maximal matching blocks that
-	 * start earliest in $a, return the one that starts earliest in $b.
-	 *
-	 * If the junk callback is defined, do the above but with the restriction
-	 * that the junk element appears in the block. Extend it as far as possible
-	 * by matching only junk elements in both $a and $b.
-	 *
-	 * @param int $alo The lower constraint for the first sequence.
-	 * @param int $ahi The upper constraint for the first sequence.
-	 * @param int $blo The lower constraint for the second sequence.
-	 * @param int $bhi The upper constraint for the second sequence.
-	 * @return array Array containing the longest match that includes the starting position in $a, start in $b and the length/size.
-	 */
-	public function findLongestMatch($alo, $ahi, $blo, $bhi)
-	{
-		$a = $this->a;
-		$b = $this->b;
-
-		$bestI = $alo;
-		$bestJ = $blo;
-		$bestSize = 0;
-
-		$j2Len = array();
-		$nothing = array();
-
-		for($i = $alo; $i < $ahi; ++$i) {
-			$newJ2Len = array();
-			$jDict = $this->arrayGetDefault($this->b2j, $a[$i], $nothing);
-			foreach($jDict as $jKey => $j) {
-				if($j < $blo) {
-					continue;
-				}
-				else if($j >= $bhi) {
-					break;
-				}
-
-				$k = $this->arrayGetDefault($j2Len, $j -1, 0) + 1;
-				$newJ2Len[$j] = $k;
-				if($k > $bestSize) {
-					$bestI = $i - $k + 1;
-					$bestJ = $j - $k + 1;
-					$bestSize = $k;
-				}
-			}
-
-			$j2Len = $newJ2Len;
-		}
-
-		while($bestI > $alo && $bestJ > $blo && !$this->isBJunk($b[$bestJ - 1]) &&
-			!$this->linesAreDifferent($bestI - 1, $bestJ - 1)) {
-				--$bestI;
-				--$bestJ;
-				++$bestSize;
-		}
-
-		while($bestI + $bestSize < $ahi && ($bestJ + $bestSize) < $bhi &&
-			!$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
-				++$bestSize;
-		}
-
-		while($bestI > $alo && $bestJ > $blo && $this->isBJunk($b[$bestJ - 1]) &&
-			!$this->isLineDifferent($bestI - 1, $bestJ - 1)) {
-				--$bestI;
-				--$bestJ;
-				++$bestSize;
-		}
-
-		while($bestI + $bestSize < $ahi && $bestJ + $bestSize < $bhi &&
-			$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
-					++$bestSize;
-		}
-
-		return array(
-			$bestI,
-			$bestJ,
-			$bestSize
-		);
-	}
-
-	/**
-	 * Check if the two lines at the given indexes are different or not.
-	 *
-	 * @param int $aIndex Line number to check against in a.
-	 * @param int $bIndex Line number to check against in b.
-	 * @return boolean True if the lines are different and false if not.
-	 */
-	public function linesAreDifferent($aIndex, $bIndex)
-	{
-		$lineA = $this->a[$aIndex];
-		$lineB = $this->b[$bIndex];
-
-		if($this->options['ignoreWhitespace']) {
-			$replace = array("\t", ' ');
-			$lineA = str_replace($replace, '', $lineA);
-			$lineB = str_replace($replace, '', $lineB);
-		}
-
-		if($this->options['ignoreCase']) {
-			$lineA = strtolower($lineA);
-			$lineB = strtolower($lineB);
-		}
-
-		if($lineA != $lineB) {
-			return true;
-		}
-
-		return false;
-	}
-
-	/**
-	 * Return a nested set of arrays for all of the matching sub-sequences
-	 * in the strings $a and $b.
-	 *
-	 * Each block contains the lower constraint of the block in $a, the lower
-	 * constraint of the block in $b and finally the number of lines that the
-	 * block continues for.
-	 *
-	 * @return array Nested array of the matching blocks, as described by the function.
-	 */
-	public function getMatchingBlocks()
-	{
-		if(!empty($this->matchingBlocks)) {
-			return $this->matchingBlocks;
-		}
-
-		$aLength = count($this->a);
-		$bLength = count($this->b);
-
-		$queue = array(
-			array(
-				0,
-				$aLength,
-				0,
-				$bLength
-			)
-		);
-
-		$matchingBlocks = array();
-		while(!empty($queue)) {
-			list($alo, $ahi, $blo, $bhi) = array_pop($queue);
-			$x = $this->findLongestMatch($alo, $ahi, $blo, $bhi);
-			list($i, $j, $k) = $x;
-			if($k) {
-				$matchingBlocks[] = $x;
-				if($alo < $i && $blo < $j) {
-					$queue[] = array(
-						$alo,
-						$i,
-						$blo,
-						$j
-					);
-				}
-
-				if($i + $k < $ahi && $j + $k < $bhi) {
-					$queue[] = array(
-						$i + $k,
-						$ahi,
-						$j + $k,
-						$bhi
-					);
-				}
-			}
-		}
-
-		usort($matchingBlocks, array($this, 'tupleSort'));
-
-		$i1 = 0;
-		$j1 = 0;
-		$k1 = 0;
-		$nonAdjacent = array();
-		foreach($matchingBlocks as $block) {
-			list($i2, $j2, $k2) = $block;
-			if($i1 + $k1 == $i2 && $j1 + $k1 == $j2) {
-				$k1 += $k2;
-			}
-			else {
-				if($k1) {
-					$nonAdjacent[] = array(
-						$i1,
-						$j1,
-						$k1
-					);
-				}
-
-				$i1 = $i2;
-				$j1 = $j2;
-				$k1 = $k2;
-			}
-		}
-
-		if($k1) {
-			$nonAdjacent[] = array(
-				$i1,
-				$j1,
-				$k1
-			);
-		}
-
-		$nonAdjacent[] = array(
-			$aLength,
-			$bLength,
-			0
-		);
-
-		$this->matchingBlocks = $nonAdjacent;
-		return $this->matchingBlocks;
-	}
-
-	/**
-	 * Return a list of all of the opcodes for the differences between the
-	 * two strings.
-	 *
-	 * The nested array returned contains an array describing the opcode
-	 * which includes:
-	 * 0 - The type of tag (as described below) for the opcode.
-	 * 1 - The beginning line in the first sequence.
-	 * 2 - The end line in the first sequence.
-	 * 3 - The beginning line in the second sequence.
-	 * 4 - The end line in the second sequence.
-	 *
-	 * The different types of tags include:
-	 * replace - The string from $i1 to $i2 in $a should be replaced by
-	 *           the string in $b from $j1 to $j2.
-	 * delete -  The string in $a from $i1 to $j2 should be deleted.
-	 * insert -  The string in $b from $j1 to $j2 should be inserted at
-	 *           $i1 in $a.
-	 * equal  -  The two strings with the specified ranges are equal.
-	 *
-	 * @return array Array of the opcodes describing the differences between the strings.
-	 */
-	public function getOpCodes()
-	{
-		if(!empty($this->opCodes)) {
-			return $this->opCodes;
-		}
-
-		$i = 0;
-		$j = 0;
-		$this->opCodes = array();
-
-		$blocks = $this->getMatchingBlocks();
-		foreach($blocks as $block) {
-			list($ai, $bj, $size) = $block;
-			$tag = '';
-			if($i < $ai && $j < $bj) {
-				$tag = 'replace';
-			}
-			else if($i < $ai) {
-				$tag = 'delete';
-			}
-			else if($j < $bj) {
-				$tag = 'insert';
-			}
-
-			if($tag) {
-				$this->opCodes[] = array(
-					$tag,
-					$i,
-					$ai,
-					$j,
-					$bj
-				);
-			}
-
-			$i = $ai + $size;
-			$j = $bj + $size;
-
-			if($size) {
-				$this->opCodes[] = array(
-					'equal',
-					$ai,
-					$i,
-					$bj,
-					$j
-				);
-			}
-		}
-		return $this->opCodes;
-	}
-
-	/**
-	 * Return a series of nested arrays containing different groups of generated
-	 * opcodes for the differences between the strings with up to $context lines
-	 * of surrounding content.
-	 *
-	 * Essentially what happens here is any big equal blocks of strings are stripped
-	 * out, the smaller subsets of changes are then arranged in to their groups.
-	 * This means that the sequence matcher and diffs do not need to include the full
-	 * content of the different files but can still provide context as to where the
-	 * changes are.
-	 *
-	 * @param int $context The number of lines of context to provide around the groups.
-	 * @return array Nested array of all of the grouped opcodes.
-	 */
-	public function getGroupedOpcodes($context=3)
-	{
-		$opCodes = $this->getOpCodes();
-		if(empty($opCodes)) {
-			$opCodes = array(
-				array(
-					'equal',
-					0,
-					1,
-					0,
-					1
-				)
-			);
-		}
-
-		if($opCodes[0][0] == 'equal') {
-			$opCodes[0] = array(
-				$opCodes[0][0],
-				max($opCodes[0][1], $opCodes[0][2] - $context),
-				$opCodes[0][2],
-				max($opCodes[0][3], $opCodes[0][4] - $context),
-				$opCodes[0][4]
-			);
-		}
-
-		$lastItem = count($opCodes) - 1;
-		if($opCodes[$lastItem][0] == 'equal') {
-			list($tag, $i1, $i2, $j1, $j2) = $opCodes[$lastItem];
-			$opCodes[$lastItem] = array(
-				$tag,
-				$i1,
-				min($i2, $i1 + $context),
-				$j1,
-				min($j2, $j1 + $context)
-			);
-		}
-
-		$maxRange = $context * 2;
-		$groups = array();
-		$group = array();
-		foreach($opCodes as $code) {
-			list($tag, $i1, $i2, $j1, $j2) = $code;
-			if($tag == 'equal' && $i2 - $i1 > $maxRange) {
-				$group[] = array(
-					$tag,
-					$i1,
-					min($i2, $i1 + $context),
-					$j1,
-					min($j2, $j1 + $context)
-				);
-				$groups[] = $group;
-				$group = array();
-				$i1 = max($i1, $i2 - $context);
-				$j1 = max($j1, $j2 - $context);
-			}
-			$group[] = array(
-				$tag,
-				$i1,
-				$i2,
-				$j1,
-				$j2
-			);
-		}
-
-		if(!empty($group) && !(count($group) == 1 && $group[0][0] == 'equal')) {
-			$groups[] = $group;
-		}
-
-		return $groups;
-	}
-
-	/**
-	 * Return a measure of the similarity between the two sequences.
-	 * This will be a float value between 0 and 1.
-	 *
-	 * Out of all of the ratio calculation functions, this is the most
-	 * expensive to call if getMatchingBlocks or getOpCodes is yet to be
-	 * called. The other calculation methods (quickRatio and realquickRatio)
-	 * can be used to perform quicker calculations but may be less accurate.
-	 *
-	 * The ratio is calculated as (2 * number of matches) / total number of
-	 * elements in both sequences.
-	 *
-	 * @return float The calculated ratio.
-	 */
-	public function Ratio()
-	{
-		$matches = array_reduce($this->getMatchingBlocks(), array($this, 'ratioReduce'), 0);
-		return $this->calculateRatio($matches, count ($this->a) + count ($this->b));
-	}
-
-	/**
-	 * Helper function to calculate the number of matches for Ratio().
-	 *
-	 * @param int $sum The running total for the number of matches.
-	 * @param array $triple Array containing the matching block triple to add to the running total.
-	 * @return int The new running total for the number of matches.
-	 */
-	private function ratioReduce($sum, $triple)
-	{
-		return $sum + ($triple[count($triple) - 1]);
-	}
-
-	/**
-	 * Quickly return an upper bound ratio for the similarity of the strings.
-	 * This is quicker to compute than Ratio().
-	 *
-	 * @return float The calculated ratio.
-	 */
-	private function quickRatio()
-	{
-		if($this->fullBCount === null) {
-			$this->fullBCount = array();
-			$bLength = count ($b);
-			for($i = 0; $i < $bLength; ++$i) {
-				$char = $this->b[$i];
-				$this->fullBCount[$char] = $this->arrayGetDefault($this->fullBCount, $char, 0) + 1;
-			}
-		}
-
-		$avail = array();
-		$matches = 0;
-		$aLength = count ($this->a);
-		for($i = 0; $i < $aLength; ++$i) {
-			$char = $this->a[$i];
-			if(isset($avail[$char])) {
-				$numb = $avail[$char];
-			}
-			else {
-				$numb = $this->arrayGetDefault($this->fullBCount, $char, 0);
-			}
-			$avail[$char] = $numb - 1;
-			if($numb > 0) {
-				++$matches;
-			}
-		}
-
-		$this->calculateRatio($matches, count ($this->a) + count ($this->b));
-	}
-
-	/**
-	 * Return an upper bound ratio really quickly for the similarity of the strings.
-	 * This is quicker to compute than Ratio() and quickRatio().
-	 *
-	 * @return float The calculated ratio.
-	 */
-	private function realquickRatio()
-	{
-		$aLength = count ($this->a);
-		$bLength = count ($this->b);
-
-		return $this->calculateRatio(min($aLength, $bLength), $aLength + $bLength);
-	}
-
-	/**
-	 * Helper function for calculating the ratio to measure similarity for the strings.
-	 * The ratio is defined as being 2 * (number of matches / total length)
-	 *
-	 * @param int $matches The number of matches in the two strings.
-	 * @param int $length The length of the two strings.
-	 * @return float The calculated ratio.
-	 */
-	private function calculateRatio($matches, $length=0)
-	{
-		if($length) {
-			return 2 * ($matches / $length);
-		}
-		else {
-			return 1;
-		}
-	}
-
-	/**
-	 * Helper function that provides the ability to return the value for a key
-	 * in an array of it exists, or if it doesn't then return a default value.
-	 * Essentially cleaner than doing a series of if(isset()) {} else {} calls.
-	 *
-	 * @param array $array The array to search.
-	 * @param string $key The key to check that exists.
-	 * @param mixed $default The value to return as the default value if the key doesn't exist.
-	 * @return mixed The value from the array if the key exists or otherwise the default.
-	 */
-	private function arrayGetDefault($array, $key, $default)
-	{
-		if(isset($array[$key])) {
-			return $array[$key];
-		}
-		else {
-			return $default;
-		}
-	}
-
-	/**
-	 * Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks
-	 *
-	 * @param array $a First array to compare.
-	 * @param array $b Second array to compare.
-	 * @return int -1, 0 or 1, as expected by the usort function.
-	 */
-	private function tupleSort($a, $b)
-	{
-		$max = max(count($a), count($b));
-		for($i = 0; $i < $max; ++$i) {
-			if($a[$i] < $b[$i]) {
-				return -1;
-			}
-			else if($a[$i] > $b[$i]) {
-				return 1;
-			}
-		}
-
-		if(count($a) == $count($b)) {
-			return 0;
-		}
-		else if(count($a) < count($b)) {
-			return -1;
-		}
-		else {
-			return 1;
-		}
-	}
-}

+ 43 - 0
app/model/issue/dependency.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Model\Issue;
+
+class Dependency extends \Model {
+
+	protected $_table_name = "issue_dependency";
+
+	/**
+	 * Find dependency issues by issue_id
+	 * @param  int    $issue_id
+	 * @param  string $orderby
+	 * @return array
+	 */
+	public function findby_issue ($issue_id, $orderby = 'due_date') {
+		return $this->db->exec(
+			'SELECT d.id as d_id,i.id, i.name, i.start_date, i.due_date, i.status_closed, i.author_name, i.author_username, i.owner_name, i.owner_username, i.status_name, i.status, d.dependency_type '.
+			'FROM issue_detail i JOIN issue_dependency d on i.id = d.dependency_id  '.
+			'WHERE d.issue_id = :issue_id AND  i.deleted_date IS NULL  '.
+			'ORDER BY :orderby ',
+			array(':issue_id' => $issue_id,  ':orderby' => $orderby)
+		);
+	}
+
+	/**
+	 * Find dependent issues by issue_id
+	 * @param  int    $issue_id
+	 * @param  string $orderby
+	 * @return array
+	 */
+	public function findby_dependent ($issue_id, $orderby = 'due_date') {
+		return  $this->db->exec(
+			'SELECT  d.id as d_id, i.id, i.name, i.start_date, i.due_date, i.status_closed, i.author_name, i.author_username, i.owner_name, i.owner_username, i.status_name, i.status,  d.dependency_type '.
+			'FROM issue_detail i JOIN issue_dependency d on i.id = d.issue_id  '.
+			'WHERE d.dependency_id = :issue_id AND  i.deleted_date IS NULL  '.
+			'ORDER BY :orderby ',
+			array(':issue_id' => $issue_id,  ':orderby' => $orderby)
+		);
+	}
+
+
+
+}

+ 1 - 0
app/routes.ini

@@ -29,6 +29,7 @@ POST /issues/file/delete = Controller\Issues->file_delete
 POST /issues/file/undelete = Controller\Issues->file_undelete
 GET /issues/@id/history = Controller\Issues->single_history
 GET /issues/@id/related = Controller\Issues->single_related
+GET /issues/@id/dependencies = Controller\Issues->single_dependencies
 GET /issues/@id/watchers = Controller\Issues->single_watchers
 GET /issues/project/@id = Controller\Issues->project_overview
 GET /search = Controller\Issues->search

+ 53 - 0
app/view/issues/single.html

@@ -239,6 +239,8 @@
 						<li><a href="#related" data-toggle="tab" id="tab_related">{{ @dict.related_tasks }}</a></li>
 					</false>
 				</check>
+				<li><a href="#dependencies" data-toggle="tab" id="tab_dependencies">{{ @dict.dependencies }}</a></li>
+
 			</ul>
 			<div class="tab-content">
 				<div class="tab-pane fade in active" id="comments">
@@ -308,6 +310,13 @@
 						</div>
 					</div>
 				</div>
+				<div class="tab-pane fade" id="dependencies">
+					<div class="col-md-4 col-md-offset-4">
+						<div class="progress progress-striped active">
+							<div class="progress-bar" style="width: 100%"></div>
+						</div>
+					</div>
+				</div>
 			</div>
 
 		</div>
@@ -453,6 +462,50 @@ $(function() {
 		$('#related').empty().append($('<p />').addClass('text-center text-danger').text('{{ @dict.error.loading_related_issues }}'));
 	});
 
+	$(function loading_dependencies(){
+		$.get(site_url + 'issues/{{ @issue.id }}/dependencies', {}, function(data) {
+			$('#dependencies').empty().append(data.html);
+			$('#tab_dependencies').html($('#tab_dependencies').text() + '&ensp;<span class="badge" id="dependencies-badge">' + data.total + '</span>');
+			$('#dependencies table').stupidtable();
+
+			// Add/remove dependencies from tab
+			$('.dependency-list').on('click', 'a.delete', function(e) {
+				$tr = $(this).parents('tr');
+				$.post(site_url + 'issues/{{ @issue.id }}', {
+					action: 'remove_dependency',
+					id: $tr.data('id')
+				});
+
+				$tr.fadeOut(400, function(){
+				 	$tr.remove();
+				 });
+
+				$('#dependencies-badge').text(parseInt($('#dependencies-badge').text())  - 1);
+
+				e.preventDefault();
+			});
+
+			$('#dependency_form').submit(function(e) {
+				$this = $(this);
+				$.post(site_url + 'issues/{{ @issue.id }}', $this.serialize());
+
+				$loading_dependencies;
+				e.preventDefault();
+			});
+
+			$('#dependent_form').submit(function(e) {
+				$this = $(this);
+				$.post(site_url + 'issues/{{ @issue.id }}', $this.serialize());
+
+				$loading_dependencies;
+				e.preventDefault();
+			});
+
+		}, 'json').error(function() {
+			$('#dependencies').empty().append($('<p />').addClass('text-center text-danger').text('{{ @dict.error.loading_dependencies }}'));
+		});
+	});
+
 	// Handle inline edit form
 	$("#btn-edit, #form-edit .close, #form-edit .btn-cancel").click(function(e) {
 		$("#form-edit").slideToggle("fast");

+ 111 - 0
app/view/issues/single/dependencies.html

@@ -0,0 +1,111 @@
+<form id="dependency_form" role="form" class="form-horizontal">
+<input type="hidden" name="action" value="add_dependency">
+<div class="form-group">
+		<label class="col-md-3"><strong> {{ @dict.task_depends }} </strong></label>
+		<div class="col-md-2">
+			<input type="text" class="form-control input-sm" name="id" value="" required>
+		</div>
+		<div class="col-md-1">
+			<select class="form-control input-sm" name="type_id">
+				<option>FS</option>
+				<option>FF</option>
+				<option>SS</option>
+				<option>SF</option>
+			</select>
+		</div>
+		<div class="col-md-1">
+			<button type="submit" class="btn btn-primary btn-sm">Add  Dependency</button>
+		</div>
+	</div>
+
+</form>
+<check if="{{ !empty(@dependencies) }}">
+	<true>
+		<table class="table table-striped table-responsive table-condensed issue-list dependency-list">
+		<thead>
+			<tr>
+				<th data-sort="int">{{ @dict.cols.id }}</th>
+				<th data-sort="string">{{ @dict.cols.title }}</th>
+				<th data-sort="string">{{ @dict.cols.type}}</th>
+				<th data-sort="string">{{ @dict.cols.author }}</th>
+				<th data-sort="string">{{ @dict.cols.assignee }}</th>
+				<th data-sort="int">{{ @dict.cols.start }}</th>
+				<th data-sort="int">{{ @dict.cols.due }}</th>
+				<th data-sort="int">{{ @dict.cols.status }}</th>
+				<th data-sort="int"></th>
+			</tr>
+		</thead>
+		<tbody>
+			<repeat group="{{ @dependencies }}" value="{{ @item }}">
+				<tr data-id="{{ @item.d_id }}" class="{{ @item.status_closed ? 'closed' : '' }}">
+					<td><a href="{{ @site.url }}issues/{{ @item.id }}">{{ @item.id }}</a></td>
+					<td><a href="{{ @site.url }}issues/{{ @item.id }}">{{ @item.name | esc }}</a></td>
+					<td>{{ @item.dependency_type }}</td>
+					<td><a href="{{ @site.url }}user/{{ @item.author_username }}">{{ @item.author_name | esc }}</a></td>
+					<td><a href="{{ @site.url }}user/{{ @item.owner_username }}">{{ @item.owner_name | esc }}</a></td>
+					<td data-sort-value="{{ strtotime(@item.start_date) }}" title="{{ date('M j, Y \\a\\t g:ia', strtotime(@item.start_date)) }}">{{ date("n/j/y", strtotime(@item.start_date)) }}</td>
+					<td data-sort-value="{{ strtotime(@item.due_date) ?: 0 }}">{{ !empty(@item.due_date) ? date("n/j", strtotime(@item.due_date)) : "" }}</td>
+					<td data-sort-value="{{ @item.status }}">{{ @item.status_name }}</td>
+					<td><a href="#" class="delete"><span class="glyphicon glyphicon-remove"></span></a></td>
+				</tr>
+			</repeat>
+		</tbody>
+		</table>
+	</true>
+</check>
+<hr>
+<form id="dependent_form" role="form" class="form-horizontal">
+<input type="hidden" name="action" value="add_dependent">
+<div class="form-group">
+		<label class="col-md-3"><strong> {{ @dict.task_dependency }} </strong></label>
+		<div class="col-md-2">
+			<input type="text" class="form-control input-sm" name="id" value="" required>
+		</div>
+		<div class="col-md-1">
+			<select class="form-control input-sm" name="type_id">
+				<option>FS</option>
+				<option>FF</option>
+				<option>SS</option>
+				<option>SF</option>
+			</select>
+		</div>
+		<div class="col-md-1">
+			<button type="submit" class="btn btn-primary btn-sm">Add Dependent</button>
+		</div>
+	</div>
+
+</form>
+<check if="{{ !empty(@dependents) }}">
+	<true>
+		<table class="table table-striped table-responsive table-condensed issue-list dependency-list">
+		<thead>
+			<tr>
+				<th data-sort="int">{{ @dict.cols.id }}</th>
+				<th data-sort="string">{{ @dict.cols.title }}</th>
+				<th data-sort="string">{{ @dict.cols.type}}</th>
+				<th data-sort="string">{{ @dict.cols.author }}</th>
+				<th data-sort="string">{{ @dict.cols.assignee }}</th>
+				<th data-sort="int">{{ @dict.cols.start }}</th>
+				<th data-sort="int">{{ @dict.cols.due }}</th>
+				<th data-sort="int">{{ @dict.cols.status }}</th>
+				<th data-sort="int"></th>
+			</tr>
+		</thead>
+		<tbody>
+			<repeat group="{{ @dependents }}" value="{{ @item }}">
+				<tr data-id="{{ @item.d_id }}" class="{{ @item.status_closed ? 'closed' : '' }}">
+					<td><a href="{{ @site.url }}issues/{{ @item.id }}">{{ @item.id }}</a></td>
+					<td><a href="{{ @site.url }}issues/{{ @item.id }}">{{ @item.name | esc }}</a></td>
+					<td>{{ @item.dependency_type }}</td>
+					<td><a href="{{ @site.url }}user/{{ @item.author_username }}">{{ @item.author_name | esc }}</a></td>
+					<td><a href="{{ @site.url }}user/{{ @item.owner_username }}">{{ @item.owner_name | esc }}</a></td>
+					<td data-sort-value="{{ strtotime(@item.start_date) }}" title="{{ date('M j, Y \\a\\t g:ia', strtotime(@item.start_date)) }}">{{ date("n/j/y", strtotime(@item.start_date)) }}</td>
+					<td data-sort-value="{{ strtotime(@item.due_date) ?: 0 }}">{{ !empty(@item.due_date) ? date("n/j", strtotime(@item.due_date)) : "" }}</td>
+					<td data-sort-value="{{ @item.status }}">{{ @item.status_name }}</td>
+					<td><a href="#" class="delete"><span class="glyphicon glyphicon-remove"></span></a></td>
+				</tr>
+			</repeat>
+		</tbody>
+		</table>
+	</true>
+</check>

+ 16 - 0
db/15.04.17.sql

@@ -0,0 +1,16 @@
+# Add Issue dependency
+DROP TABLE IF EXISTS `issue_dependency`;
+CREATE TABLE `issue_dependency` (
+	  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+	  `issue_id` int(10) unsigned NOT NULL,
+	  `dependency_id` int(11) unsigned NOT NULL,
+	  `dependency_type` char(2) COLLATE utf8_unicode_ci NOT NULL,
+	  PRIMARY KEY (`id`),
+	  UNIQUE KEY `issue_id_dependency_id` (`issue_id`,`dependency_id`),
+	  KEY `dependency_id` (`dependency_id`),
+	  CONSTRAINT `issue_dependency_ibfk_2` FOREIGN KEY (`issue_id`) REFERENCES `issue` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+	  CONSTRAINT `issue_dependency_ibfk_3` FOREIGN KEY (`dependency_id`) REFERENCES `issue` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+# Update Version
+UPDATE `config` SET `value` = '15.04.17' WHERE `attribute` = 'version';

+ 14 - 1
db/database.sql

@@ -186,6 +186,19 @@ CREATE TABLE `issue_tag`(
 	CONSTRAINT `issue_tag_issue` FOREIGN KEY (`issue_id`) REFERENCES `issue`(`id`) ON UPDATE CASCADE ON DELETE CASCADE
 ) ENGINE=INNODB CHARSET=utf8;
 
+DROP TABLE IF EXISTS `issue_dependency`;
+CREATE TABLE `issue_dependency` (
+	  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+	  `issue_id` int(10) unsigned NOT NULL,
+	  `dependency_id` int(11) unsigned NOT NULL,
+	  `dependency_type` char(2) COLLATE utf8_unicode_ci NOT NULL,
+	  PRIMARY KEY (`id`),
+	  UNIQUE KEY `issue_id_dependency_id` (`issue_id`,`dependency_id`),
+	  KEY `dependency_id` (`dependency_id`),
+	  CONSTRAINT `issue_dependency_ibfk_2` FOREIGN KEY (`issue_id`) REFERENCES `issue` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+	  CONSTRAINT `issue_dependency_ibfk_3` FOREIGN KEY (`dependency_id`) REFERENCES `issue` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `sprint`;
 CREATE TABLE `sprint` (
 	`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@@ -273,4 +286,4 @@ CREATE TABLE `config` (
 	UNIQUE KEY `attribute` (`attribute`)
 ) ;
 
-INSERT INTO `config` (`attribute`, `value`) VALUES ('version', '15.02.26');
+INSERT INTO `config` (`attribute`, `value`) VALUES ('version', '15.03.05');