Jump to content

Pagetree: moving pages in very long branches


horst
 Share

Recommended Posts

On a site we have a branch with 1200+ pages. The sort order is manual drag and drop. Sometimes we need to move new created pages 300 or 400 places in the pagetree. This is a real pain. Is there any way (besides moving them manually) how we can sort in a page at a desired place?

Link to comment
Share on other sites

I don't know but manually sorting 400 pages uff..?

Some kind of sort integer field to enter position in a field. Build an custom admin page for that? It's not easy anyway. And experiment with maybe grid instead of a 1 column?

  • Like 1
Link to comment
Share on other sites

I don't know but manually sorting 400 pages uff..?

Some kind of sort integer field to enter position in a field. Build an custom admin page for that? It's not easy anyway. And experiment with maybe grid instead of a 1 column?

It isn't about sorting 400 pages. The pages have kind of a chronological order. And mostly there will be added new projects to the tree what only need to be sorted within the top 20 pages. That's just fine. But there are also some older projects that should find the way into the online archive. And this pages needs to be pulled down 400 pages or more. (sigh')

I think the first thing I will experiment with, is to display the pages sort (range) number in the pagetree. So we can see them and easily pick one we want a new page moved to. Add a "desired sort range" inputfield and maybe a button to those pages template, where we can add in a sort range number where the page gets moved to on / after saving or when an own button was pressed.

Link to comment
Share on other sites

Had something like this once (not in PW) where I updated the integer sort field to be 100, 200, 300... Then to move item X to be just after item Y you simply give it Y's sort value plus an offset. Use offset of 1 the first time and increment it each time. A negative offset can be used for inserting before. You need a function to normalize the sort values when you are done or after a large number of moves but that's easy enough to do.

  • Like 1
Link to comment
Share on other sites

Had something like this once (not in PW) where I updated the integer sort field to be 100, 200, 300... Then to move item X to be just after item Y you simply give it Y's sort value plus an offset. Use offset of 1 the first time and increment it each time. A negative offset can be used for inserting before. You need a function to normalize the sort values when you are done or after a large number of moves but that's easy enough to do.

I believe this isn't doable in PW because on every manually move in the pagetree all integer sort fields gets rebuild in a linear row, without those niches.

Otherwise this would be the best or simplest solution, I agree.

Link to comment
Share on other sites

Many thanks for all the suggestions!
 
My current working solution is using a Pagefield with PageTreeSelect, and as a sidenote: wow! I never have used this fieldtype combination before. I always used ASM-Select. Just fallen in love again with PW. :)
 
The second element is an injected button, what I have learned recently from a nice and usefull snippet of @Lostkobrakai.
 
The working code is located in the ready.php and uses the PW API.
 
Our pages in the example here uses the PageTreeAddNewChildReverse module, so the sort numbers stored in the DB are very high. (If you wonder why they have such high numbers in the screenshots.)
 
The Pagefield where we select the new neighbour page is named "albumselect".

/* moving albumpages programatically in the PageTree */
$wire->addHookAfter("ProcessPageEdit::buildForm", function(HookEvent $event) {

    $DEBUG = true;
    $FIELD = 'albumselect';  // change to the name of your Pagefield !

    $page = $event->object->getPage();
    if (!(bool)$page->fields->get($FIELD)) return;  // check if the page includes the submitbutton for our function
    if (!$page->sortable()) {                       // check if the page is sortable and the user has the right to sort it
        $this->warning($this->_("You have not the right to sort this page."));
        return;
    }
    if ('sort' != $page->parent->template->sortfield && '' != $page->parent->template->sortfield) {
        $this->warning($this->_("The sortsetting of the parent page does not allow drag and drop. It is set to: {$page->parent->template->sortfield}"));
        return;
    }

    $form = $event->return;
    $input = wire('input');
    $pages = wire('pages');
    $i = $num = 0;

    if ($input->post('hook_move_page_in_pagetree') && $input->post($FIELD)) {

        $page->of(false);
        $selectedNeighbour = $pages->get($input->post($FIELD));
        #$sortLow = $page->parent->child('include=all')->sort;                // the sortnumber of the first page in the tree, top page
        $sortCur = $page->get('sort');                                        // the current number of the page we want to move
        $sortEnd = $selectedNeighbour->getUnformatted('sort');                // the number of the page where we want to sort in the current page
        if ($DEBUG) my_var_dump(array('sortCur' => $sortCur, 'sortEnd' => $sortEnd), 1);

        if (0 < $selectedNeighbour->id) {

            if ($sortCur != $sortEnd) {                                       // has the user selected a real or valid change of position?
                if ($sortCur < $sortEnd && $sortCur != $sortEnd - 1) {

                    // we want to move this page down in the tree
                    foreach($page->parent->children('include=all') as $p) {
                        $num++;
                        $p->of(false);
                        if ($i++ > 50) { set_time_limit(30); $i = 0; }        // just to play save, reset timelimit
                        if ($p->sort == $sortEnd) {                           // we reached the page on which top we want to place the current one
                            if ($DEBUG) {
                                my_var_dump(array('num' => $num, 'page->sort' => $page->sort, 'result'  => $sortEnd - 1, 'sortCur' => $sortCur, 'sortEnd' => $sortEnd, 'break' => true), 1);
                            } else {
                                #$page->setAndSave('sort', $sortEnd - 1, array('quiet' => true));
                                databaseUpdateSortInPages($page->id, $sortEnd - 1);
                            }
                            break;                                            // we are finished now
                        }
                        if ($sortCur < $p->sort) {                            // only change sort for pages that are between $sortCur and $sortEnd
                            if ($DEBUG) {
                                my_var_dump(array('num' => $num, 'p->sort' => $p->sort, 'result'  => $p->sort - 1, 'sortCur' => $sortCur, 'sortEnd' => $sortEnd), 1);
                            } else {
                                #$p->setAndSave('sort', $p->sort - 1, array('quiet' => true));
                                databaseUpdateSortInPages($p->id, $p->sort - 1);
                            }
                        }
                        $pages->uncache($p);                                  // free memory
                    }

                } elseif ($sortCur > $sortEnd || $sortCur == $sortEnd - 1) {

                    // we want to move this page up in the tree
                    foreach($page->parent->children('include=all') as $p) {
                        $num++;
                        $p->of(false);
                        if ($i++ > 50) { set_time_limit(30); $i = 0; }
                        if ($p->sort >= $sortEnd && $p->sort <= $sortCur) {
                            if ($p->sort == $sortCur) {
                                if ($DEBUG) {
                                    my_var_dump(array('num' => $num, 'p->sort' => $p->sort, 'result'  => $sortEnd, 'sortCur' => $sortCur, 'sortEnd' => $sortEnd, 'break' => true), 1);
                                } else {
                                    #$page->setAndSave('sort', $sortEnd, array('quiet' => true));
                                    databaseUpdateSortInPages($page->id, $sortEnd);
                                }
                                break;
                            } else {
                                if ($DEBUG) {
                                    my_var_dump(array('num' => $num, 'p->sort' => $p->sort, 'result'  => $p->sort + 1, 'sortCur' => $sortCur, 'sortEnd' => $sortEnd), 1);
                                } else {
                                    #$p->setAndSave('sort', $p->sort + 1, array('quiet' => true));
                                    databaseUpdateSortInPages($p->id, $p->sort + 1);
                                }
                            }
                        }
                        $pages->uncache($p);
                    }
                }
            }
        }
        if ($DEBUG) die();
    }

    $button = wire('modules')->get('InputfieldSubmit');
    $button->attr('id+name', 'hook_move_page_in_pagetree');
    $button->value = __('jetzt einsortieren!');
    $button->addClass('ui-priority-secondary');
    $form->insertAfter($button, $form->get($FIELD));
});

function databaseUpdateSortInPages($id, $sort) {
    $sql = "UPDATE pages SET sort = $sort WHERE id = $id";
    $query = wire('database')->prepare($sql);
    $query->execute();
}

.

.

EDIT:

Updated the above code. It supports up and down moving now. Also it has a debug function now. (The used function for debug output is in the spoiler beneath)

What could be done better:

  • [x] first check if the pages are set to use manually drag/drop sortorder or not.
  • [x] test with direct manipulating DB-tables to improve speed.

Currently, on my server, it took up to 35 seconds for iterating over 1250 pages and saving a new sortvalue for each of them. But this only happens if we move it from one end to the other (first place to last place). If we move a page 150 places, there will be only changed 150 pages in DB. This tooks around 4 seconds here. :)

EDIT 2:

Updated the code and included @Lostkobrakais idea to check for the PagetreeField on every page, and not on templatenames etc. This way we can add/remove the functionality to every templates we like, just by adding/removing the Pagefield to them, without altering the hook-function. (In our case the pagefield is called 'albumselect'). ^-^

EDIT 3:

Updated the code. Now it doesn't alter the modified time and modified user-id anymore. Before the change, it altered it of each page that changes the sort value! Also the execution time is much faster now. It only takes 3 seconds on my server to move it 1200 pages down or up. (Before using direct DB-access, it took 35 seconds)

screen_215.jpg
screen_216.jpg


 

if (!function_exists('my_var_dump')) {
 function my_var_dump($v, $outputMode=2, $filename='') {
    ob_start();
    var_dump($v);
    $content = ob_get_contents();
    ob_end_clean();
    $m = 0;
    preg_match_all('#^(.*)=>#mU', $content, $stack);
    $lines = $stack[1];
    $indents = array_map('strlen', $lines);
    if ($indents) $m = max($indents) + 1;
    $content = preg_replace('#^(.*)=>\\n\s+(\S)#eUm', '"\\1" . str_repeat(" ", $m - strlen("\\1") > 1 ? $m - strlen("\\1") : 1) . "\\2"', $content);
    $content = preg_replace('#^((\s*).*){$#m', "\\1\n\\2{", $content);
    $content = str_replace(array('<pre>','</pre>'), '', $content);
    switch($outputMode) {
        case 1:
            // Output to Browser-Window
            echo "<pre style=\"margin:10px 10px 10px; padding:10px 10px 10px 10px; background-color:#F2F2F2; color:#000; border:1px solid #333; font-family:'Hack', 'Source Code Pro', 'Lucida Console', 'Courier', monospace; font-size:12px; line-height:15px; overflow:visible;\">{$content}</pre>";
            break;
        case 2:
            // Output to Commandline-Window or to Browser as hidden comment
            echo isset($_SERVER['HTTP_HOST']) ? "\n<!--\n".$content."\n-->\n" : $content."\n";
            break;
        case 3:
            // Output into a StringVar
            return "<pre style=\"margin: 10px 10px 10px; padding: 10px 10px 10px 10px; background-color:#F2F2F2; color:#000; border: 1px solid #333; font-family:'Hack', 'Source Code Pro', 'Lucida Console', 'Courier', monospace; font-size:12px; line-height:15px; overflow: visible;\">{$content}</pre>";
            break;
        case 4:
            // Output into a file, if a valid filename is given and we have write access to it
            @touch($filename);
            if (is_writable($filename)) {
                $content = str_replace(array('>','"','
'), array('>','"',''), strip_tags($content));
                $res = file_put_contents($filename, $content, FILE_APPEND);
                wireChmod($filename);
                return $res === strlen($content);
            }
            return false;
            break;
    }
 }
}
Edited by horst
EDIT 3: Now it doesn't alter the modified time ..
  • Like 4
Link to comment
Share on other sites

I was about to say, that you could add the page field via the hook as well, but I think it's actually quite nice to just look if the page field is part of a page and enhance it automatically. So one doesn't have to touch the hooks code after setting the fields name once. 

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...