ResourceSorter.php

<?php

namespace Lia\Addon;


class ResourceSorter extends \Lia\Addon {

    public string $fqn = 'lia:server.resourcesorter';

    protected $didSet=false;

    protected $orders=['css'=>[], 'js'=>[]];


    public function init_lia(){
        $this->lia->methods['setResourceOrder'] = [$this,'setResourceOrder'];
    }

    /**
     * Give an array of file names in the order they should be sent to the browser.
     *
     * @param $ext the file extension js or css
     * @param $names an array of relative file paths, in the order you want them. There MUST NOT be leading `/`. There MAY be as many `/` as you like
     * @param $prepend TRUE to put these $names BEFORE any previous names set this way. FALSE to put these names at the end. WILL replace any identical names for sorting
     */
    public function setResourceOrder($ext,$names, $prepend=true){
        $ext = strtolower($ext);
        if (!$this->didSet){
            $this->lia->setResourceSorter($ext, [$this, 'getSortedFiles']);
        }

        $resources = [];
        foreach ($names as $name){
            if (isset($this->orders[$ext][$name])){
                unset ($this->orders[$ext][$name]);
            }
            $resources[$ext][$name] = $name;
        }

        if ($prepend){
            $this->orders = array_merge_recursive($resources, $this->orders);
        } else {
            $this->orders = array_merge_recursive($this->orders, $resources);
        }
        
    }


    public function getSortedFiles($from_unsorted){
        //@TODO add support for file paths using backslash separators like C:\Whatever\Folder\File.js
        if (count($from_unsorted)==0)return;
        // get file extension
        $f = array_values($from_unsorted)[0];
        $ext = pathinfo($f, PATHINFO_EXTENSION);
        $ext = strtolower($ext);
        

        // generate all match positions
        $remaining_unsorted = [];
        $sort_values = [];
        $sort_orders = array_values($this->orders[$ext]);
        foreach ($from_unsorted as $index => $absolute_file){
            $absolute_file = str_replace(['///','//'], '/', $absolute_file);
            $sort_result = [];
            foreach ($sort_orders as $index=>$relative_file){
                $len = strlen($relative_file);
                if (substr($absolute_file, -$len)==$relative_file){
                    $sort_result[] = [
                        'match_order'=>$index,
                        'match_length'=>$len,
                    ];
                }
            }

            if (count($sort_result)==0){
                $remaining_unsorted[] = $absolute_file;
            } else {
                $sort_values[$absolute_file] = $sort_result;
            }
        }

        // choose match position that has the longest string match between the relative file and absolute file
        $best_values = [];
        foreach ($sort_values as $absolute_file => $values_list){
            if (count($values_list)==1)$best_values[$absolute_file] = $values_list[0];
            else {
                $best_value = null;
                $best_match_length = -1;
                foreach ($values_list as $value){
                    if ($value['match_length'] > $best_match_length)$best_value = $value;
                }
                $best_values[$absolute_file] = $value;
            }
            $best_values[$absolute_file]['absolute_file']=$absolute_file;
        }

        // put the matches in order
        $ordered_values = $best_values;
        usort($ordered_values,
            function ($a, $b){
                // return 1 if a before b; -1 if b before a; 0 if same position / don't care
                if ($a['match_order'] > $b['match_order']) return 1;
                else if ($a['match_order'] < $b['match_order']) return -1;
                else if ($a['match_length'] > $b['match_length']) return 1;
                else if ($a['match_length'] < $b['match_length']) return 1;
                else return 0;
            }
        );

        // finally build the sorted files array
        $sorted_files = [];
        foreach ($ordered_values as $value){
            $sorted_files[] = $value['absolute_file'];
        }
        $sorted_files = array_merge($sorted_files, $remaining_unsorted);

        return $sorted_files;
    }

}