Classes

File phpboost/menu/MenuService.class.php

File phpboost/menu/MenuService.class.php

  1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 
<?php
/**
 * This service manage kernel menus by adding the persistance to menus objects.
 * It also provides all moving and disabling methods to change the website appearance.
 * @package     PHPBoost
 * @subpackage  Menu
 * @copyright   &copy; 2005-2019 PHPBoost
 * @license     https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL-3.0
 * @author      Loic ROUCHON <horn@phpboost.com>
 * @version     PHPBoost 5.2 - last update: 2018 02 02
 * @since       PHPBoost 2.0 - 2008 11 13
 * @contributor Julien BRISWALTER <j1.seth@phpboost.com>
 * @contributor Arnaud GENET <elenwii@phpboost.com>
 * @contributor mipel <mipel@phpboost.com>
*/

class MenuService
{
    const MOVE_UP = -1;
    const MOVE_DOWN = 1;

    /**
     * @var DBQuerier
     */
    private static $querier;

    /**
     * @var string[] the columns needed to instanciate a menu
     */
    private static $columns = array('id', 'object', 'class', 'block', 'position', 'enabled');

    public static function __static()
    {
        self::$querier = PersistenceContext::get_querier();
    }

    ## Menus ##
    /**
    *
    * @param $block
    * @param $enabled
    * @return Menu[]
    */
    public static function get_menu_list($class = Menu::MENU__CLASS, $block = Menu::BLOCK_POSITION__ALL, $enabled = Menu::MENU_ENABLE_OR_NOT)
    {
        $fragment = self::build_menu_list_query_conditions($class, $block, $enabled);
        $menus = array();
        $results = self::$querier->select_rows(DB_TABLE_MENUS, self::$columns, $fragment->get_query() . ' ORDER BY position ASC', $fragment->get_parameters());
        foreach ($results as $row)
        {
            $menus[$row['id']] = self::initialize($row);
        }
        $results->dispose();

        return $menus;
    }

    /**
     *
     * @return unknown_type
     */
    public static function get_menus_map()
    {
        $menus = self::initialize_menus_map();
        $results = self::$querier->select_rows(DB_TABLE_MENUS, self::$columns, 'ORDER BY position ASC');
        foreach ($results as $row)
        {
            if ($row['enabled'] != Menu::MENU_ENABLED)
            {
                $menus[Menu::BLOCK_POSITION__NOT_ENABLED][] = self::initialize($row);
            }
            else
            {
                $menus[$row['block']][] = self::initialize($row);
            }
        }
        $results->dispose();

        return $menus;
    }

    /**
     * Retrieve a Menu Object from the database by its id
     * @param int $id the id of the Menu to retrieve from the database
     * @return Menu the requested Menu if it exists else, null
     */
    public static function load($id)
    {
        try
        {
            $result = self::$querier->select_single_row(DB_TABLE_MENUS, self::$columns, 'WHERE id=:id', array('id' => $id));
            return self::initialize($result);
        } catch (RowNotFoundException $ex)
        {
            return null;
        }
    }

    /**
     * save a Menu in the database
     * @param Menu $menu The Menu to save
     * @return bool true if the save have been correctly done
     */
    public static function save(Menu $menu)
    {
        $block_position = $menu->get_block_position();

        if (($block = $menu->get_block()) != Menu::MENU_NOT_ENABLED && ($block_position = $menu->get_block_position()) == -1)
        {
            $block_position = self::get_next_position($block);
        }

        $id_menu = $menu->get_id();
        $columns = array(
            'title' => $menu->get_title(),
            'object' => TextHelper::serialize($menu),
            'class' => get_class($menu),
            'enabled' => (int) $menu->is_enabled(),
            'block' => $block,
            'position' => $menu->get_block_position()
        );

        if ($id_menu > 0)
        {
            self::$querier->update(DB_TABLE_MENUS, $columns, 'WHERE id=:id', array('id' => $id_menu));
        }
        else
        {
            $result = self::$querier->insert(DB_TABLE_MENUS, $columns);
            $menu->id($result->get_last_inserted_id());
        }

        return true;
    }

    /**
     * Delete a Menu from the database
     * @param mixed $menu The (Menu) Menu or its (int) id to delete from the database
     */
    public static function delete($menu)
    {
        if (!is_object($menu))
        {
            $menu = self::load($menu);
        }
        self::disable($menu);
        self::$querier->delete(DB_TABLE_MENUS, 'WHERE id=:id', array('id' => $menu->get_id()));
    }

    /**
     * Enable a menu
     * @param Menu $menu the menu to enable
     */
    public static function enable(Menu $menu)
    {
        // Commputes the new Menu position and save it
        self::move($menu, $menu->get_block());
    }

    /**
     * Disable a menu
     * @param Menu $menu the menu to disable
     */
    public static function disable(Menu $menu)
    {
        // Commputes menus positions of the previous block and save the current menu
        self::move($menu, Menu::BLOCK_POSITION__NOT_ENABLED);
    }

    /**
     * Move a menu into a block and save it. Enable or disable it according to the destination block
     * @param Menu $menu the menu to move
     * @param int $block the destination block
     * @param int $position the destination block position
     * @param bool $save if true, save also the menu
     */
    public static function move(Menu $menu, $block, $position = 0, $save = true)
    {
        if ($menu->get_id() > 0 && $menu->is_enabled())
        {   // Updates the previous block position counter
            // Only for already existing menu that are enabled, not for new ones
            $parameters = array('block' => $menu->get_block(), 'position' => $menu->get_block_position());
            self::$querier->inject('UPDATE ' . DB_TABLE_MENUS . ' SET position=position - 1
                WHERE block=:block AND position > :position', $parameters);
        }

        // Disables the menu if the destination block is the NOT_ENABLED block position
        $menu->enabled($block == Menu::BLOCK_POSITION__NOT_ENABLED ? Menu::MENU_NOT_ENABLED : Menu::MENU_ENABLED);

        // If not enabled, we do not move it so we can restore its position by reactivating it
        if ($menu->is_enabled())
        {   // Moves the menu into the destination block
            $menu->set_block($block);

            // Computes the new block position for the menu
            if (empty($position))
            {
                $position_query = self::get_next_position($menu->get_block());
                $menu->set_block_position($position_query);
            }
        }

        if ($save)
        {
            self::save($menu);
        }
    }

    /**
     * Set the menu position in a block
     * @param Menu $menu The menu
     * @param int $block_position the new position.
     */
    public static function set_position(Menu $menu, $block_position)
    {
        if ($block_position != $menu->get_block_position())
        {
            // Updating the current menu
            $menu->set_block_position($block_position);
            self::save($menu);
        }
    }

    /**
     * Change the menu position in a block
     * @param Menu $menu The menu to move
     * @param int $diff the direction to move it. positives integers move down, negatives, up.
    */
    public static function change_position($menu, $direction = self::MOVE_UP)
    {
        $block_position = $menu->get_block_position();
        $new_block_position = $block_position;
        $update_query = '';

        if ($direction > 0)
        {   // Moving the menu down
            $parameters = array('block' => $menu->get_block());
            $max_position = PersistenceContext::get_querier()->get_column_value(DB_TABLE_MENUS, 'MAX(position)', 'WHERE block=:block AND enabled=1', $parameters);

            // Getting the max diff
            if (($new_block_position = ($menu->get_block_position() + $direction)) > $max_position)
                $new_block_position = $max_position;

            $update_query = "
                UPDATE " . DB_TABLE_MENUS . " SET position=position - 1
                WHERE
                    block='" . $menu->get_block() . "' AND
                    position BETWEEN '" . ($block_position + 1) . "' AND '" . $new_block_position . "'
            ";
        }
        else if ($direction < 0)
        {   // Moving the menu up

            // Getting the max diff
            if (($new_block_position = ($menu->get_block_position() + $direction)) < 0)
                $new_block_position = 0;

            // Updating other menus
            $update_query = "
                UPDATE " . DB_TABLE_MENUS . " SET position=position + 1
                WHERE
                    block='" . $menu->get_block() . "' AND
                    position BETWEEN '" . $new_block_position . "' AND '" . ($block_position - 1) . "'
            ";
        }

        if ($block_position != $new_block_position)
        {   // Updating other menus
            PersistenceContext::get_querier()->inject($update_query);

            // Updating the current menu
            $menu->set_block_position($new_block_position);
            self::save($menu);
        }
    }

    /**
     * Enables or disables all menus
     * @param bool $enable if true enables all menus otherwise, disables them
     */
    public static function enable_all($enable = true)
    {
        $menus = self::get_menu_list();
        foreach($menus as $menu)
        {
            if ($enable === true)
            {
                self::enable($menu);
            }
            else
            {
                self::disable($menu);
            }
        }
    }

    ## Cache ##

    /**
     * Generate the cache
     */
    public static function generate_cache()
    {
        MenusCache::invalidate();
    }

    ## Mini Modules ##
    /**
    * Add the module named $module mini modules
    * @param string $module the module name
    * @return bool true if the module has been installed, else, false
    */
    public static function add_mini_module($module_id, $generate_cache = true)
    {
        self::update_mini_modules_list($generate_cache);
    }

    /**
     * delete the mini module $module
     * @param string $module the mini module name
     */
    public static function delete_mini_module($module)
    {
        $menus_class = array();

        if (MenusProvidersService::module_containing_extension_point($module))
        {
            foreach (MenusProvidersService::get_menus($module) as $menu)
            {
                if ($menu instanceof ModuleMiniMenu)
                {
                    $menus_class[] = get_class($menu);
                }
            }
        }

        if (!empty($menus_class))
        {
            $results = self::$querier->select_rows(DB_TABLE_MENUS, self::$columns, 'WHERE class IN :class', array('class' => $menus_class));
            foreach ($results as $row)
            {
                self::delete(self::initialize($row));
            }
            $results->dispose();
        }
    }

    /**
     * Update the mini modules list by adding new ones and delete old ones
     * @param bool $update_cache if true it will also regenerate the cache
     */
    public static function update_mini_modules_list($update_cache = true)
    {
        // Retrieves the mini modules already installed
        $installed_minimodules = array();
        $menus = array();
        foreach (MenusProvidersService::get_extension_point() as $module_id => $extension_point)
        {
            foreach ($extension_point->get_menus() as $menu)
            {
                $menus[get_class($menu)] = array(
                    'module_id' => $module_id,
                    'menu' => $menu
                );
            }
        }

        $results = self::$querier->select_rows(DB_TABLE_MENUS, array('id', 'title', 'class'));
        foreach ($results as $row)
        {
            if (array_key_exists($row['class'], $menus))
            {
                $installed_minimodules[$row['class']] = $row['id'];
            }
            else
            {
                if (!in_array($row['class'], array(ContentMenu::CONTENT_MENU__CLASS, FeedMenu::FEED_MENU__CLASS, LinksMenu::LINKS_MENU__CLASS)))
                    self::delete($row['id']);
            }
        }
        $results->dispose();

        $new_menus = array_diff_key($menus, $installed_minimodules);
        foreach ($new_menus as $class => $menu)
        {
            $mini_module = $menu['menu'];
            $title = $mini_module->get_title();
            $default_block = $mini_module->get_default_block();
            $mini_module->set_title($menu['module_id'] . '/' . $title);
            $mini_module->set_block($default_block);
            $mini_module->enabled($mini_module->default_is_enabled());
            self::save($mini_module);
        }

        if ($update_cache)
        {
            self::generate_cache();
        }
    }


    /**
     * Delete all the feeds menus with the this module id
     * @param string $module_id the module id
     */
    public static function delete_module_feeds_menus($module_id)
    {
        $feeds_menus = self::get_menu_list(FeedMenu::FEED_MENU__CLASS);
        foreach($feeds_menus as $feed_menu)
        {
            if ($module_id == $feed_menu->get_module_id())
            {
                self::delete($feed_menu);
            }
        }
    }

    /**
     * Return a menu with links to modules
     * @param int $menu_type the menu type
     * @return LinksMenu the menu with links to modules
     */
    public static function website_modules($menu_type = LinksMenu::AUTOMATIC_MENU)
    {
        $modules_menu = new LinksMenu('PHPBoost', '/', '', $menu_type);
        $modules = ModulesManager::get_activated_modules_map_sorted_by_localized_name();
        foreach ($modules as $module)
        {
            $configuration = $module->get_configuration();
            $start_page = $configuration->get_home_page();
            if (!empty($start_page))
            {
                $img = '';
                $img_url = PATH_TO_ROOT . '/' . $module->get_id() . '/' . $module->get_id();

                foreach (array('_mini.png', '_mini.gif', '_mini.jpg') as $extension)
                {
                    $file = new File($img_url . $extension);
                    if ($file->exists())
                    {
                        $img = '/' . $module->get_id() . '/' . $file->get_name();
                        break;
                    }
                }
                $modules_menu->add(new LinksMenuLink($configuration->get_name(), '/' . $module->get_id() . '/', $img));
            }
        }
        return $modules_menu;
    }


    /**
     * Assigns the positions conditions for different printing modes
     * @param Template $template the template to use
     * @param int $position the menu position
     */
    public static function assign_positions_conditions($template, $position)
    {
        $vertical_position = in_array($position, array(Menu::BLOCK_POSITION__LEFT, Menu::BLOCK_POSITION__RIGHT));
        $template->put_all(array(
            'C_HEADER' => $position == Menu::BLOCK_POSITION__HEADER,
            'C_SUBHEADER' => $position == Menu::BLOCK_POSITION__SUB_HEADER,
            'C_TOP_CENTRAL' => $position == Menu::BLOCK_POSITION__TOP_CENTRAL,
            'C_BOTTOM_CENTRAL' => $position == Menu::BLOCK_POSITION__BOTTOM_CENTRAL,
            'C_TOP_FOOTER' => $position == Menu::BLOCK_POSITION__TOP_FOOTER,
            'C_FOOTER' => $position == Menu::BLOCK_POSITION__FOOTER,
            'C_LEFT' => $position == Menu::BLOCK_POSITION__LEFT,
            'C_RIGHT' => $position == Menu::BLOCK_POSITION__RIGHT,
            'C_VERTICAL' => $vertical_position,
            'C_HORIZONTAL' => !$vertical_position
        ));
    }
    ## Tools ##

    /**
     * Convert the string location the int location
     * @param string $str_location the location
     * @return int the corresponding location
     */
    public static function str_to_location($str_location)
    {
        switch ($str_location)
        {
            case 'header':
                return Menu::BLOCK_POSITION__HEADER;
            case 'subheader':
                return Menu::BLOCK_POSITION__SUB_HEADER;
            case 'topcentral':
                return Menu::BLOCK_POSITION__TOP_CENTRAL;
            case 'left':
                return Menu::BLOCK_POSITION__LEFT;
            case 'right':
                return Menu::BLOCK_POSITION__RIGHT;
            case 'bottomcentral':
                return Menu::BLOCK_POSITION__BOTTOM_CENTRAL;
            case 'topfooter':
                return Menu::BLOCK_POSITION__TOP_FOOTER;
            case 'footer':
                return Menu::BLOCK_POSITION__FOOTER;
            default:
                return Menu::BLOCK_POSITION__NOT_ENABLED;
        }
    }

    /**
     *
     * @param int $class
     * @param int_type $block
     * @param boolean $enabled
     * @return SQLFragment
     */
    private static function build_menu_list_query_conditions($class, $block, $enabled)
    {
        $conditions = array();
        $parameters = array();
        if ($class != Menu::MENU__CLASS)
        {
            $conditions[] = 'class=:class';
            $parameters['class'] = TextHelper::strtolower($class);
        }
        if ($block != Menu::BLOCK_POSITION__ALL)
        {
            $conditions[] = 'block=:block';
            $parameters['block'] = $block;
        }
        if ($enabled !== Menu::MENU_ENABLE_OR_NOT)
        {
            $conditions[] = 'enabled=:enabled';
            $parameters['enabled'] = $enabled;
        }
        $condition = '';
        if (!empty($conditions))
        {
            $condition .= 'WHERE ' . implode(' AND ', $conditions);
        }
        return new SQLFragment($condition, $parameters);
    }

    private static function get_next_position($block)
    {
        $column = 'MAX(position) + 1 AS newPosition';
        $condition = 'WHERE block=:block AND enabled=1';
        $parameters = array('block' => $block);
        return (int) self::$querier->get_column_value(DB_TABLE_MENUS, $column, $condition, $parameters);
    }

    /**
     * @access private
     * @return array[] initialize the menus map structure
     */
    private static function initialize_menus_map()
    {
        return array(
        Menu::BLOCK_POSITION__HEADER => array(),
        Menu::BLOCK_POSITION__SUB_HEADER => array(),
        Menu::BLOCK_POSITION__TOP_CENTRAL => array(),
        Menu::BLOCK_POSITION__BOTTOM_CENTRAL => array(),
        Menu::BLOCK_POSITION__TOP_FOOTER => array(),
        Menu::BLOCK_POSITION__FOOTER => array(),
        Menu::BLOCK_POSITION__LEFT => array(),
        Menu::BLOCK_POSITION__RIGHT => array(),
        Menu::BLOCK_POSITION__NOT_ENABLED => array()
        );
    }

    /**
     * @access private
     * Build a Menu object from a database result
     * @param string[key] $db_result the map from the database with the Menu id and serialized object
     * @return Menu the menu object from the serialized one
     */
    private static function initialize($db_result)
    {
        if (!class_exists($db_result['class']))
        {
            $menu = new ContentMenu('Unable to load the menu');
            $menu->set_content('Unable to load the menu with the following class : ' . $db_result['class']);
        }
        else
        {
            $fixed_object = preg_replace_callback( '!s:(\d+):"(.*?)";!u', function($match) {
                return ($match[1] == TextHelper::strlen($match[2])) ? $match[0] : 's:' . TextHelper::strlen($match[2]) . ':"' . $match[2] . '";';
            }, $db_result['object']);

            $menu = TextHelper::unserialize($fixed_object);
        }

        // Synchronize the object and the database
        $menu->id($db_result['id']);
        $menu->enabled($db_result['enabled']);
        $menu->set_block($db_result['block']);
        $menu->set_block_position($db_result['position']);

        if (($menu instanceof LinksMenu) || ($menu instanceof LinksMenuLink))
        {
            $menu->update_uid();
        }

        return $menu;
    }
}
?>