Returns HTML code for a graph showing the dispersion of ancestors across grand-parents
@return string HTML code
185552
private function htmlAncestorDispersionG3()
    {
        $ancestorsDispGen2 = $this->sosa_provider->getAncestorDispersionForGen(3);
        
        $size = '700x300';
        
        $color_motmot = 'ffd1dc';
        $color_motfat = 'b998a0';
        $color_fatfat = '577292';
        $color_fatmot = '84beff';
        $color_shared = '777777';
    
        $total_fatfat = array_key_exists(1, $ancestorsDispGen2) ? $ancestorsDispGen2[1] : 0;
        $total_fatmot = array_key_exists(2, $ancestorsDispGen2) ? $ancestorsDispGen2[2] : 0;
        $total_motfat = array_key_exists(4, $ancestorsDispGen2) ? $ancestorsDispGen2[4] : 0;
        $total_motmot = array_key_exists(8, $ancestorsDispGen2) ? $ancestorsDispGen2[8] : 0;
        $total_sha = array_key_exists(-1, $ancestorsDispGen2) ? $ancestorsDispGen2[-1] : 0;
        $total = $total_fatfat + $total_fatmot + $total_motfat+ $total_motmot + $total_sha;
    
        $chd = $this->arrayToExtendedEncoding(array(
	    4095 * Functions::safeDivision($total_fatfat, $total), 
            4095 * Functions::safeDivision($total_fatmot, $total),
            4095 * Functions::safeDivision($total_sha, $total), 
            4095 * Functions::safeDivision($total_motfat, $total),
            4095 * Functions::safeDivision($total_motmot, $total)          
        ));
        $chart_title = I18N::translate('Known Sosa ancestors\' dispersion - G3');
        $chl =
            \Fisharebest\Webtrees\Functions\Functions::getRelationshipNameFromPath('fatfat') . ' - ' . I18N::percentage(Functions::safeDivision($total_fatfat, $total), 1) . '|' .
            \Fisharebest\Webtrees\Functions\Functions::getRelationshipNameFromPath('fatmot') . ' - ' . I18N::percentage(Functions::safeDivision($total_fatmot, $total), 1) . '|' .
            I18N::translate('Shared') . ' - ' . I18N::percentage(Functions::safeDivision($total_sha, $total), 1) . '|' .
            \Fisharebest\Webtrees\Functions\Functions::getRelationshipNameFromPath('motfat') . ' - ' . I18N::percentage(Functions::safeDivision($total_motfat, $total), 1) . '|' .
            \Fisharebest\Webtrees\Functions\Functions::getRelationshipNameFromPath('motmot') . ' - ' . I18N::percentage(Functions::safeDivision($total_motmot, $total), 1);
         return "<img src=\"https://chart.googleapis.com/chart?cht=p&chp=1.5708&amp;chd=e:{$chd}&amp;chs={$size}&amp;chco={$color_fatfat},{$color_fatmot},{$color_shared},{$color_motfat},{$color_motmot}&amp;chf=bg,s,ffffff00&amp;chl={$chl}\" alt=\"" . $chart_title . "\" title=\"" . $chart_title . "\" />";
    }
108743
public function lastToken() {
    $tail = $this->tail;
    while ($tail instanceof ParentNode) {
      $tail = $tail->tail;
    }
    return $tail;
  }
709452
private function getLinksDeclaration()
    {

        // we got a max instances allowed in normality tags
        $links = $this->entity->pgClass->getLinks();
        foreach( $links as &$link ) {
            $link = $link->toArray();
        }
        $links = json_encode( $links );
        $links = Php::varExport( $links, true );

        $this->class->classComponents[] = new VariableDeclaration(
            'links',
            new SFormatf( <<<'PHP'
                /**
                 * Link information for this relation
                 * @var array
                 */
                protected $links = %s;
PHP
                , $links
            )
        );

    }
1162459
public function getBranchAsTree()
    {
        $lastParents = array();
        
        foreach ($this->getBranch() as $page)
        {
            if (!$page->isRoot())
            {
                $lastParents[$page->getLevel() - 1]->addNestedSetChild($page);
            }
            
            $lastParents[$page->getLevel()] = $page;
        }
        
        return $this;
    }
1360113
private function getInitialLettersList() { 
        $list = array();
        /** @var \Fisharebest\Webtrees\Tree $tree */
        $tree = $this->data->get('tree');
		$script_base_url = WT_SCRIPT_NAME . '?mod=' . \MyArtJaub\Webtrees\Constants::MODULE_MAJ_PATROLIN_NAME . '&mod_action=Lineage';
		
        foreach (QueryName::surnameAlpha($tree, false, false) as $letter => $count) {
            switch ($letter) {
                case '@':
                    $html = I18N::translateContext('Unknown surname', '…');
                    break;
                case ',':
                    $html = I18N::translate('None');
                    break;
                default:
                    $html = Filter::escapeHtml($letter);
                    break;
            }
            if ($count) {
                if ($letter == $this->data->get('alpha')) {
                    $list[] = '<a href="' . $script_base_url . '&alpha=' . rawurlencode($letter) . '&amp;ged=' . $tree->getNameUrl() . '" class="warning" title="' . I18N::number($count) . '">' . $html . '</a>';
                } else {
                    $list[] = '<a href="' . $script_base_url . '&alpha=' . rawurlencode($letter) . '&amp;ged=' . $tree->getNameUrl() . '" title="' . I18N::number($count) . '">' . $html . '</a>';
                }
            } else {
                $list[] = $html;
            }
        }
    
        // Search spiders don't get the "show all" option as the other links give them everything.
        if (!Auth::isSearchEngine()) {
            if ($this->data->get('show_all') === 'yes') {
                $list[] = '<span class="warning">' . I18N::translate('All') . '</span>';
            } else {
                $list[] = '<a href="' . $script_base_url . '&show_all=yes' . '&amp;ged=' . $tree->getNameUrl() . '">' . I18N::translate('All') . '</a>';
            }
        }
    
        return $list;
    }
173554
public static function getHierarchyParents($code)
    {
        $parents = [''];

        $node_length = strlen($code);
        $current_length = 0;

        foreach (static::getHierarchyLengths() as $length) {
            if ($current_length + $length === $node_length) {
                return $parents;
            } elseif ($current_length + $length > $node_length) {
                return false;
            } else {
                $current_length += $length;
                $parents[] = substr($code, 0, $current_length);
            }
        }

        return false;
    }
528577
protected function getPlacesHierarchyFromData() {
        $nb_levels = 0;
        
        //Select all '2 PLAC ' tags in the file and create array
        $places_list=array();
        $ged_data = Database::prepare(
            'SELECT i_gedcom AS gedcom'.
            ' FROM `##individuals`'.
            ' WHERE i_gedcom LIKE :gedcom AND i_file = :gedcom_id'.
            ' UNION ALL'.
            'SELECT f_gedcom AS gedcom'.
            ' FROM `##families`'.
            ' WHERE f_gedcom LIKE :gedcom AND f_file = :gedcom_id'
        )->execute(array(
            'gedcom' => '%\n2 PLAC %',
            'gedcom_id' => $this->tree->getTreeId()            
        ))->fetchOneColumn();
        foreach ($ged_data as $ged_datum) {
            $matches = null;
            preg_match_all('/\n2 PLAC (.+)/', $ged_datum, $matches);
            foreach ($matches[1] as $match) {
                $places_list[$match]=true;
            }
        }
        
        // Unique list of places
        $places_list=array_keys($places_list);
        
        //sort the array, limit to unique values, and count them
        usort($places_list, array('I18N', 'strcasecmp'));
        
        //calculate maximum no. of levels to display
        $has_found_good_example = false;
        foreach($places_list as $place){
            $levels = explode(",", $place);
            $parts = count($levels);
            if ($parts >= $nb_levels){
                $nb_levels = $parts;
                if(!$has_found_good_example){
                    $random_place = $place;
                    if(min(array_map('strlen', $levels)) > 0){
                        $has_found_good_example = true;
                    }
                }
            }
        }
        
        return array_reverse(array_map('trim',explode(',', $random_place)));
    }
1078638
private function FindParentGroup()
    {
        $parentGroup = null;
        if ($this->parent)
        {
            $parentGroup = GroupFinder::FindPageGroup($this->parent);
        }
        if (!$parentGroup)
        {
            $parentGroup = $this->site->GetUserGroup();
        }
        return $parentGroup;
    }
645212
public function summary(): string
    {
        $attributes = [];
        $target     = $this->target();
        if ($target instanceof GedcomRecord) {
            $attributes[] = $target->fullName();
        } else {
            // Fact value
            $value = $this->value();
            if ($value !== '' && $value !== 'Y') {
                $attributes[] = '<span dir="auto">' . e($value) . '</span>';
            }
            // Fact date
            $date = $this->date();
            if ($date->isOK()) {
                if ($this->record() instanceof Individual && in_array($this->getTag(), Gedcom::BIRTH_EVENTS, true) && $this->record()->tree()->getPreference('SHOW_PARENTS_AGE')) {
                    $attributes[] = $date->display() . FunctionsPrint::formatParentsAges($this->record(), $date);
                } else {
                    $attributes[] = $date->display();
                }
            }
            // Fact place
            if ($this->place()->gedcomName() !== '') {
                $attributes[] = $this->place()->shortName();
            }
        }

        $class = 'fact_' . $this->getTag();
        if ($this->isPendingAddition()) {
            $class .= ' new';
        } elseif ($this->isPendingDeletion()) {
            $class .= ' old';
        }

        return
            '<div class="' . $class . '">' .
            /* I18N: a label/value pair, such as “Occupation: Farmer”. Some languages may need to change the punctuation. */
            I18N::translate('<span class="label">%1$s:</span> <span class="field" dir="auto">%2$s</span>', $this->label(), implode(' — ', $attributes)) .
            '</div>';
    }
449820
public function getRootParagraph()
    {
        if ( $this->proxyBase->rootId && (
                null === $this->rootParagraph ||
                $this->rootParagraph->id != $this->proxyBase->rootId
            ) )
        {
            $this->rootParagraph = $this->getMapper()
                                        ->find( $this->proxyBase->rootId );
        }

        return $this->rootParagraph;
    }
571093
public function hashCode()
    {
        $validPk = null !== $this->getStructureNodeId() &&
            null !== $this->getParentId();

        $validPrimaryKeyFKs = 2;
        $primaryKeyFKs = [];

        //relation structure_node_parent_fk_node to table kk_trixionary_structure_node
        if ($this->aStructureNodeRelatedByStructureNodeId && $hash = spl_object_hash($this->aStructureNodeRelatedByStructureNodeId)) {
            $primaryKeyFKs[] = $hash;
        } else {
            $validPrimaryKeyFKs = false;
        }

        //relation structure_node_parent_fk_parent to table kk_trixionary_structure_node
        if ($this->aStructureNodeRelatedByParentId && $hash = spl_object_hash($this->aStructureNodeRelatedByParentId)) {
            $primaryKeyFKs[] = $hash;
        } else {
            $validPrimaryKeyFKs = false;
        }

        if ($validPk) {
            return crc32(json_encode($this->getPrimaryKey(), JSON_UNESCAPED_UNICODE));
        } elseif ($validPrimaryKeyFKs) {
            return crc32(json_encode($primaryKeyFKs, JSON_UNESCAPED_UNICODE));
        }

        return spl_object_hash($this);
    }
661954
public function getHtml()
    {
        $html = array();
        
        if (count($this->m_trendlines) > 0)
        {
            $this->m_options['trendlines'] = json_decode(json_encode($this->m_trendlines, JSON_FORCE_OBJECT)); 
        }
        
        # debug hacks
        $this->m_options['seriesType'] = 'lines';
        $chartType = "ScatterChart"; # LineChart
        
        $html[] = "var data = google.visualization.arrayToDataTable(" . json_encode($this->m_data, JSON_PRETTY_PRINT) . ");";
        $html[] = "var options = " . json_encode($this->m_options) . ";";
        $html[] = "var chart = new google.visualization." . $chartType . "(document.getElementById('" . $this->m_anchorId . "'));";
        
        if ($this->m_clickHandler != null)
        {
            $html[] = "google.visualization.events.addListener(chart, 'click', " . $this->m_clickHandler . ");";
        }
        
        if ($this->m_selectHandler != null)
        {
            $html[] = "google.visualization.events.addListener(chart, 'select', " . $this->m_selectHandler . ");";
        }
        
        $html[] = "chart.draw(data, options);";
        
        return implode(PHP_EOL, $html);
    }
415249
private function _renderLegend() {
		$legend = $this->_chart->getLegend();
		if (!is_null($legend)) {
			$legendPosition = $legend->getPosition();
			$legendOverlay = $legend->getOverlay();
			switch ($legendPosition) {
				case 'r'	:
					$this->_graph->legend->SetPos(0.01,0.5,'right','center');	//	right
					$this->_graph->legend->SetColumns(1);
					break;
				case 'l'	:
					$this->_graph->legend->SetPos(0.01,0.5,'left','center');	//	left
					$this->_graph->legend->SetColumns(1);
					break;
				case 't'	:
					$this->_graph->legend->SetPos(0.5,0.01,'center','top');	//	top
					break;
				case 'b'	:
					$this->_graph->legend->SetPos(0.5,0.99,'center','bottom');	//	bottom
					break;
				default		:
					$this->_graph->legend->SetPos(0.01,0.01,'right','top');	//	top-right
					$this->_graph->legend->SetColumns(1);
					break;
			}
		} else {
			$this->_graph->legend->Hide();
		}
	}
1096204
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;
    }
1340301
private function getMarriageChartData(): array
    {
        // Count how many families got marriage in each country
        $surn_countries = [];
        $m_countries    = $this->placeRepository->statsPlaces('FAM');

        // webtrees uses 3 letter country codes and localised country names, but google uses 2 letter codes.
        foreach ($m_countries as $place) {
            // Consolidate places (Germany, DEU => DE)
            if (array_key_exists($place->country, $this->country_to_iso3166)) {
                $country_code = $this->country_to_iso3166[$place->country];

                if (array_key_exists($country_code, $surn_countries)) {
                    $surn_countries[$country_code] += $place->tot;
                } else {
                    $surn_countries[$country_code] = $place->tot;
                }
            }
        }

        return $this->createChartData($surn_countries);
    }
754553
public function getParent()
    {
        $parentID = $this->getField("ParentID");
        if ($parentID) {
            return SiteTree::get_by_id(self::class, $parentID);
        }
        return null;
    }
464764
public function SiteTreeAsUL()
    {
        $treeClass = $this->config()->get('tree_class');
        $filter = $this->getSearchFilter();

        DataObject::singleton($treeClass)->prepopulateTreeDataCache(null, [
            'childrenMethod' => $filter ? $filter->getChildrenMethod() : 'AllChildrenIncludingDeleted',
            'numChildrenMethod' => $filter ? $filter->getNumChildrenMethod() : 'numChildren',
        ]);

        $html = $this->getSiteTreeFor($treeClass);

        $this->extend('updateSiteTreeAsUL', $html);

        return $html;
    }