1<?php
2/*
3    +-----------------------------------------------------------------------------+
4    | ILIAS open source                                                           |
5    +-----------------------------------------------------------------------------+
6    | Copyright (c) 1998-2006 ILIAS open source, University of Cologne            |
7    |                                                                             |
8    | This program is free software; you can redistribute it and/or               |
9    | modify it under the terms of the GNU General Public License                 |
10    | as published by the Free Software Foundation; either version 2              |
11    | of the License, or (at your option) any later version.                      |
12    |                                                                             |
13    | This program is distributed in the hope that it will be useful,             |
14    | but WITHOUT ANY WARRANTY; without even the implied warranty of              |
15    | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               |
16    | GNU General Public License for more details.                                |
17    |                                                                             |
18    | You should have received a copy of the GNU General Public License           |
19    | along with this program; if not, write to the Free Software                 |
20    | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. |
21    +-----------------------------------------------------------------------------+
22*/
23
24include_once './Services/Container/classes/class.ilContainer.php';
25include_once('Services/Container/classes/class.ilContainerSortingSettings.php');
26
27/**
28*
29* @author Stefan Meyer <meyer@leifos.com>
30* @version $Id$
31*
32*
33* @ingroup ServicesContainer
34*/
35class ilContainerSorting
36{
37    /**
38     * @var Logger
39     */
40    protected $log;
41
42    /**
43     * @var ilTree
44     */
45    protected $tree;
46
47    protected static $instances = array();
48
49    protected $obj_id;
50    protected $db;
51
52    protected $sorting_settings = null;
53    const ORDER_DEFAULT = 999999;
54
55    /**
56     * Constructor
57     *
58     * @access private
59     * @param int obj_id
60     *
61     */
62    private function __construct($a_obj_id)
63    {
64        global $DIC;
65
66        $this->log = $DIC["ilLog"];
67        $this->tree = $DIC->repositoryTree();
68        $ilDB = $DIC->database();
69
70        $this->db = $ilDB;
71        $this->obj_id = $a_obj_id;
72
73        $this->read();
74    }
75
76    /**
77     * Get sorting settings
78     * @return ilContainerSortingSettings
79     */
80    public function getSortingSettings()
81    {
82        return $this->sorting_settings;
83    }
84
85    /**
86     * get instance by obj_id
87     *
88     * @access public
89     * @param int obj_id
90     * @return object ilContainerSorting
91     * @static
92     */
93    public static function _getInstance($a_obj_id)
94    {
95        if (isset(self::$instances[$a_obj_id])) {
96            return self::$instances[$a_obj_id];
97        }
98        return self::$instances[$a_obj_id] = new ilContainerSorting($a_obj_id);
99    }
100
101    /**
102     * Get positions of subitems
103     * @param int $a_obj_id
104     * @return
105     */
106    public static function lookupPositions($a_obj_id)
107    {
108        global $DIC;
109
110        $ilDB = $DIC->database();
111
112        $query = "SELECT * FROM container_sorting WHERE " .
113            "obj_id = " . $ilDB->quote($a_obj_id, 'integer');
114        $res = $ilDB->query($query);
115        while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
116            $sorted[$row->child_id] = $row->position;
117        }
118        return $sorted ? $sorted : array();
119    }
120
121    /**
122     * clone sorting
123     *
124     * @return
125     * @static
126     */
127    public function cloneSorting($a_target_id, $a_copy_id)
128    {
129        $ilDB = $this->db;
130
131        $ilLog = ilLoggerFactory::getLogger("cont");
132        $ilLog->debug("Cloning container sorting.");
133
134        $target_obj_id = ilObject::_lookupObjId($a_target_id);
135
136        include_once('./Services/CopyWizard/classes/class.ilCopyWizardOptions.php');
137        $mappings = ilCopyWizardOptions::_getInstance($a_copy_id)->getMappings();
138
139
140        // copy blocks sorting
141        $set = $ilDB->queryF(
142            "SELECT * FROM container_sorting_bl " .
143            " WHERE obj_id = %s ",
144            array("integer"),
145            array($this->obj_id)
146            );
147        if ($rec = $ilDB->fetchAssoc($set)) {
148            if ($rec["block_ids"] != "") {
149                $ilLog->debug("Got block sorting for obj_id = " . $this->obj_id . ": " . $rec["block_ids"]);
150                $new_ids = implode(";", array_map(function ($block_id) use ($mappings) {
151                    if (is_numeric($block_id)) {
152                        $block_id = $mappings[$block_id];
153                    }
154                    return $block_id;
155                }, explode(";", $rec["block_ids"])));
156
157                $ilDB->replace("container_sorting_bl",
158                    array("obj_id" => array("integer", $target_obj_id)),
159                    array("block_ids" => array("text", $new_ids))
160                );
161
162                $ilLog->debug("Write block sorting for obj_id = " . $target_obj_id . ": " . $new_ids);
163            }
164        }
165
166
167        $ilLog->debug("Read container_sorting for obj_id = " . $this->obj_id);
168
169        $query = "SELECT * FROM container_sorting " .
170            "WHERE obj_id = " . $ilDB->quote($this->obj_id, 'integer');
171
172        $res = $ilDB->query($query);
173
174        while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
175            if (!isset($mappings[$row->child_id]) or !$mappings[$row->child_id]) {
176                $ilLog->debug("No mapping found for child id:" . $row->child_id);
177                continue;
178            }
179
180
181            $new_parent_id = 0;
182            if ($row->parent_id) {
183                // see bug #20347
184                // at least in the case of sessions and item groups parent_ids in container sorting are object IDs but $mappings store references
185                if (in_array($row->parent_type, array("sess", "itgr"))) {
186                    $par_refs = ilObject::_getAllReferences($row->parent_id);
187                    $par_ref_id = current($par_refs);			// should be only one
188                    $ilLog->debug("Got ref id: " . $par_ref_id . " for obj_id " . $row->parent_id . " map ref id: " . $mappings[$par_ref_id] . ".");
189                    if (isset($mappings[$par_ref_id])) {
190                        $new_parent_ref_id = $mappings[$par_ref_id];
191                        $new_parent_id = ilObject::_lookupObjectId($new_parent_ref_id);
192                    }
193                } else {		// not sure if this is still used for other cases that expect ref ids
194                    $new_parent_id = $mappings[$row->parent_id];
195                }
196                if ((int) $new_parent_id == 0) {
197                    $ilLog->debug("No mapping found for parent id:" . $row->parent_id . ", child_id: " . $row->child_id);
198                    continue;
199                }
200            }
201
202            $query = "DELETE FROM container_sorting " .
203                "WHERE obj_id = " . $ilDB->quote($target_obj_id, 'integer') . " " .
204                "AND child_id = " . $ilDB->quote($mappings[$row->child_id], 'integer') . " " .
205                "AND parent_type = " . $ilDB->quote($row->parent_type, 'text') . ' ' .
206                "AND parent_id = " . $ilDB->quote((int) $new_parent_id, 'integer');
207            $ilLog->debug($query);
208            $ilDB->manipulate($query);
209
210            // Add new value
211            $query = "INSERT INTO container_sorting (obj_id,child_id,position,parent_type,parent_id) " .
212                "VALUES( " .
213                $ilDB->quote($target_obj_id, 'integer') . ", " .
214                $ilDB->quote($mappings[$row->child_id], 'integer') . ", " .
215                $ilDB->quote($row->position, 'integer') . ", " .
216                $ilDB->quote($row->parent_type, 'text') . ", " .
217                $ilDB->quote((int) $new_parent_id, 'integer') .
218                ")";
219            $ilLog->debug($query);
220            $ilDB->manipulate($query);
221        }
222        return true;
223    }
224
225
226
227    /**
228     * sort subitems
229     *
230     * @access public
231     * @param array item data
232     * @return array sorted item data
233     */
234    public function sortItems($a_items)
235    {
236        if (!is_array($a_items)) {
237            return [];
238        }
239
240        $sorted = array();
241        if ($this->getSortingSettings()->getSortMode() != ilContainer::SORT_MANUAL) {
242            switch ($this->getSortingSettings()->getSortMode()) {
243                case ilContainer::SORT_TITLE:
244                    foreach ((array) $a_items as $type => $data) {
245                        // #16311 - sorting will remove keys (prev/next)
246                        if ($type == 'sess_link') {
247                            $sorted[$type] = $data;
248                            continue;
249                        }
250
251                        // this line used until #4389 has been fixed (3.10.6)
252                        // reanimated with 4.4.0
253                        $sorted[$type] = ilUtil::sortArray(
254                            (array) $data,
255                            'title',
256                            ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
257                            false
258                        );
259
260                        // the next line tried to use db sorting and has replaced sortArray due to bug #4389
261                        // but leads to bug #12165. PHP should be able to do a proper sorting, if the locale
262                        // is set correctly, so we witch back to sortArray (with 4.4.0) and see what
263                        // feedback we get
264                        // (next line has been used from 3.10.6 to 4.3.x)
265//						$sorted[$type] = $data;
266                    }
267                    return $sorted ? $sorted : array();
268
269                case ilContainer::SORT_ACTIVATION:
270                    foreach ((array) $a_items as $type => $data) {
271                        // #16311 - sorting will remove keys (prev/next)
272                        if ($type == 'sess_link') {
273                            $sorted[$type] = $data;
274                            continue;
275                        }
276
277                        $sorted[$type] = ilUtil::sortArray(
278                            (array) $data,
279                            'start',
280                            ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
281                            true
282                        );
283                    }
284                    return $sorted ? $sorted : array();
285
286
287                case ilContainer::SORT_CREATION:
288                    foreach ((array) $a_items as $type => $data) {
289                        // #16311 - sorting will remove keys (prev/next)
290                        if ($type == 'sess_link') {
291                            $sorted[$type] = $data;
292                            continue;
293                        }
294
295                        $sorted[$type] = ilUtil::sortArray(
296                            (array) $data,
297                            'create_date',
298                            ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
299                            true
300                        );
301                    }
302                    return $sorted ? $sorted : array();
303            }
304            return $a_items;
305        }
306        if (!is_array($a_items) || !count($a_items)) {
307            return $a_items;
308        }
309        $sorted = array();
310        foreach ((array) $a_items as $type => $data) {
311            if ($type == 'sess_link') {
312                $sorted[$type] = $data;
313                continue;
314            }
315
316            // Add position
317            $items = array();
318            foreach ((array) $data as $key => $item) {
319                $items[$key] = $item;
320                if (is_array($this->sorting['all']) and isset($this->sorting['all'][$item['child']])) {
321                    $items[$key]['position'] = $this->sorting['all'][$item['child']];
322                } else {
323                    $items[$key]['position'] = self::ORDER_DEFAULT;
324                }
325            }
326
327            $items = $this->sortOrderDefault($items);
328
329            switch ($type) {
330                case '_all':
331                    $sorted[$type] = ilUtil::sortArray((array) $items, 'position', 'asc', true);
332                    break;
333
334                case '_non_sess':
335                    $sorted[$type] = ilUtil::sortArray((array) $items, 'position', 'asc', true);
336                    break;
337
338                default:
339                    $sorted[$type] = ilUtil::sortArray((array) $items, 'position', 'asc', true);
340                    break;
341            }
342        }
343        return $sorted ? $sorted : array();
344    }
345
346    /**
347     * sort subitems (items of sessions or learning objectives)
348     *
349     * @access public
350     * @param
351     * @return
352     */
353    public function sortSubItems($a_parent_type, $a_parent_id, $a_items)
354    {
355        switch ($this->getSortingSettings()->getSortMode()) {
356            case ilContainer::SORT_MANUAL:
357                $items = array();
358                foreach ($a_items as $key => $item) {
359                    $items[$key] = $item;
360                    $items[$key]['position'] = isset($this->sorting[$a_parent_type][$a_parent_id][$item['child']]) ?
361                                                    $this->sorting[$a_parent_type][$a_parent_id][$item['child']] : self::ORDER_DEFAULT;
362                }
363
364                $items = $this->sortOrderDefault($items);
365                return ilUtil::sortArray((array) $items, 'position', 'asc', true);
366
367
368            case ilContainer::SORT_ACTIVATION:
369                return ilUtil::sortArray(
370                    (array) $a_items,
371                    'start',
372                    ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
373                    true
374                );
375
376            case ilContainer::SORT_CREATION:
377                return ilUtil::sortArray(
378                    (array) $a_items,
379                    'create_date',
380                    ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
381                    true
382                );
383
384            default:
385            case ilContainer::SORT_TITLE:
386                return ilUtil::sortArray(
387                    (array) $a_items,
388                    'title',
389                    ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
390                    false
391                );
392        }
393    }
394
395    /**
396     * Save post
397     *
398     * @access public
399     * @param array of positions e.g array(crs => array(1,2,3),'lres' => array(3,5,6))
400     *
401     */
402    public function savePost($a_type_positions)
403    {
404        if (!is_array($a_type_positions)) {
405            return false;
406        }
407        $items = [];
408        foreach ($a_type_positions as $key => $position) {
409            if ($key == "blocks") {
410                $this->saveBlockPositions($position);
411            } elseif (!is_array($position)) {
412                $items[$key] = ((float) $position) * 100;
413            } else {
414                foreach ($position as $parent_id => $sub_items) {
415                    $this->saveSubItems($key, $parent_id, $sub_items ? $sub_items : array());
416                }
417            }
418        }
419
420        if (!count($items)) {
421            return $this->saveItems(array());
422        }
423
424        asort($items);
425        $new_indexed = [];
426        $position = 0;
427        foreach ($items as $key => $null) {
428            $new_indexed[$key] = ++$position;
429        }
430
431        $this->saveItems($new_indexed);
432    }
433
434
435    /**
436     * save items
437     *
438     * @access protected
439     * @param string parent_type only used for sessions and objectives in the moment. Otherwise empty
440     * @param int parent id
441     * @param array array of items
442     * @return
443     */
444    protected function saveItems($a_items)
445    {
446        $ilDB = $this->db;
447
448        foreach ($a_items as $child_id => $position) {
449            $ilDB->replace(
450                'container_sorting',
451                array(
452                    'obj_id' => array('integer',$this->obj_id),
453                    'child_id' => array('integer',$child_id),
454                    'parent_id' => array('integer',0)
455                ),
456                array(
457                    'parent_type' => array('text',''),
458                    'position' => array('integer',$position)
459                )
460            );
461        }
462        return true;
463    }
464
465    /**
466     * Save subitem ordering (sessions, learning objectives)
467     * @param string $a_parent_type
468     * @param integer $a_parent_id
469     * @param array $a_items
470     * @return
471     */
472    protected function saveSubItems($a_parent_type, $a_parent_id, $a_items)
473    {
474        $ilDB = $this->db;
475
476        foreach ($a_items as $child_id => $position) {
477            $ilDB->replace(
478                'container_sorting',
479                array(
480                    'obj_id' => array('integer',$this->obj_id),
481                    'child_id' => array('integer',$child_id),
482                    'parent_id' => array('integer',$a_parent_id)
483                ),
484                array(
485                    'parent_type' => array('text',$a_parent_type),
486                    'position' => array('integer',$position)
487                )
488            );
489        }
490        return true;
491    }
492
493    /**
494     * Save block custom positions (for current object id)
495     *
496     * @param array $a_values
497     */
498    protected function saveBlockPositions(array $a_values)
499    {
500        $ilDB = $this->db;
501        asort($a_values);
502        $ilDB->replace(
503            'container_sorting_bl',
504            array(
505                'obj_id' => array('integer',$this->obj_id)
506            ),
507            array(
508                'block_ids' => array('text', implode(";", array_keys($a_values)))
509            )
510        );
511    }
512
513    /**
514     * Read block custom positions (for current object id)
515     *
516     * @return array
517     */
518    public function getBlockPositions()
519    {
520        $ilDB = $this->db;
521
522        $set = $ilDB->query("SELECT block_ids" .
523            " FROM container_sorting_bl" .
524            " WHERE obj_id = " . $ilDB->quote($this->obj_id, "integer"));
525        $row = $ilDB->fetchAssoc($set);
526        if ($row["block_ids"]) {
527            return explode(";", $row["block_ids"]);
528        }
529    }
530
531
532    /**
533     * Read
534     *
535     * @access private
536     *
537     */
538    private function read()
539    {
540        $tree = $this->tree;
541
542        if (!$this->obj_id) {
543            $this->sorting_settings = new ilContainerSortingSettings();
544            return true;
545        }
546
547        $sorting_settings = ilContainerSortingSettings::getInstanceByObjId($this->obj_id);
548        $this->sorting_settings = $sorting_settings->loadEffectiveSettings();
549        $query = "SELECT * FROM container_sorting " .
550            "WHERE obj_id = " . $this->db->quote($this->obj_id, 'integer') . " ORDER BY position";
551        $res = $this->db->query($query);
552        while ($row = $res->fetchRow(ilDBConstants::FETCHMODE_OBJECT)) {
553            if ($row->parent_id) {
554                $this->sorting[$row->parent_type][$row->parent_id][$row->child_id] = $row->position;
555            } else {
556                $this->sorting['all'][$row->child_id] = $row->position;
557            }
558        }
559        return true;
560    }
561
562    /**
563     * Position and order sort order for new object without position in manual sorting type
564     *
565     * @param $items
566     * @return array
567     */
568    private function sortOrderDefault($items)
569    {
570        $no_position = array();
571
572        foreach ($items as $key => $item) {
573            if ($item["position"] == self::ORDER_DEFAULT) {
574                $no_position[] = array("key" => $key, "title" => $item["title"], "create_date" => $item["create_date"],
575                    "start" => $item["start"]);
576            }
577        }
578
579        if (!count($no_position)) {
580            return $items;
581        }
582
583        switch ($this->getSortingSettings()->getSortNewItemsOrder()) {
584            case ilContainer::SORT_NEW_ITEMS_ORDER_TITLE:
585                $no_position = ilUtil::sortArray(
586                    (array) $no_position,
587                    'title',
588                    ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
589                    true
590                );
591                break;
592            case ilContainer::SORT_NEW_ITEMS_ORDER_CREATION:
593                $no_position = ilUtil::sortArray(
594                    (array) $no_position,
595                    'create_date',
596                    ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
597                    true
598                );
599                break;
600            case ilContainer::SORT_NEW_ITEMS_ORDER_ACTIVATION:
601                $no_position = ilUtil::sortArray(
602                    (array) $no_position,
603                    'start',
604                    ($this->getSortingSettings()->getSortDirection() == ilContainer::SORT_DIRECTION_ASC) ? 'asc' : 'desc',
605                    true
606                );
607
608        }
609        $count = $this->getSortingSettings()->getSortNewItemsPosition()
610            == ilContainer::SORT_NEW_ITEMS_POSITION_TOP ? -900000 : 900000;
611
612        foreach ($no_position as $values) {
613            $items[$values["key"]]["position"] = $count;
614            $count++;
615        }
616        return $items;
617    }
618}
619