<?php

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use \Mtc\Cms\Models\Page as PageModel;
use \Mtc\Cms\Models\PageCache;
use \Illuminate\Support\Facades\Event;
use Mtc\Cms\Models\PageListItem;
use Mtc\Cms\Models\PageListItemData;
use MtcPharmacy\Multisite\Classes\MultisiteManager;
use Mtc\Cms\Models\Page as CmsPage;

/**
 * PAGE CLASS
 * @author Rihards Silins
 * @copyright MTC media Ltd
 * @version 9 14/07/2017
 * @access public
 *
 */
class Page
{
    public $id;
    public $sub_id = 0;                // This acutally the id of the parent page. To fix in cms3.
    public $order = 0;                // Default order in places like navigation, site trees. This is taken care of during save.
    public string $name;
    public $created;
    public $updated;

    public $language = null;             // Set via $page->setLangauge(), then do $page->get() to get this page and its data in
    //      desired language.

    public $type = "default";        // default - generic page.
    // button  - will trigger logic if clicked in admin area. Usually used for generating
    //      other pages.
    // temp    - page that will delete itself after a time unless opened in admin area, filled
    //      out & saved.
    // dynamic - TODO: Virtually generates other pages via controller.
    public $title = "";               // Cannot be empty. Is a json if multilangauge is on.
    public $slug = "";               // Url slug of page.
    public $search_title = "";               // Stemmed page title for searching (see search class)
    public $search_content = "";               // Stemmed Data from the page object and page data object for searching (see search class)

    public $seo_title = "";               // Seo title set via the cms admin panels. Used depending on layout.
    public $seo_keywords = "";               // Seo keywords set via the cms admin panels. Used depending on layout.
    public $seo_description = "";               // Seo description set via the cms admin panels. Used depending on layout.

    public $template = "content.twig";   // Filepath to template in site page templates (sites/www/templates/pages).
    public $layout = "default.twig";   // Filepath to layout in site page templates (sites/www/templates/layouts).
    public $logic = "";               // For advanced pages like buttons holding information about what for example should
    //      happen when you click them or for listing containers - holding information about
    //      how to dispaly the listingin the admin area.
    public $innav = 1;                // If this page should appear by default in frontend top navigation.
    public $searchable = 1;                // If this page should be searchable by cms2+ inbuilt search class.
    public $hide_in_cms = 0;                // Whether to hide this page from the client in the admin area cms page tree.
    //      Usefull for when your creating ajax pages for other functional pages or when
    //      you want to hide a locked page (see Page::$lock) that the client hasn't paid for.
    //      Carefull - subpages will also the not be visible in the admin area cms page tree.
    public $redchild = 0;                // Set to 1 to have this page automatically redirect you to first child page.
    public $frontpage = 0;                // Set to 1 to have this page as frontpage.
    public $contact_page = 0;
    public $lock = 0;                // Set to 1 to lock client access to this page in cms admin panels. (Still displays the
    //      page in the admin panel site tree)
    public $listing_container = 0;                // Set to 1 to mark this as a listing (blog/news/events) container. This makes the cms
    /**
     * Indicates whether or not pages parent page is a listing_container
     * @var int
     */
    public $listing_item = 0;
    //      display and handle it and subpages differently.
    //      - unlocks Listing customizable tab in the admin area.
    //      - unlocks Listing caching for subpages if LISTING_CACHING is set to true.
    //      - Handles subpages differently in cms admin area page tree.
    //      - and other
    public $draft_for_page_id = 0;                // TODO: This is for refering to which page this page is a draft of.
    public $allow_delete = 0;                // Set to 1 to not allow client to delete page.
    public $allow_update = 1;                // set to 1 to not allow client to update page.
    public $published = 0;                // 0 = Draft 1 = Published

    public $history_lock = 0;                // Locked history version. So as not to delete a history
    public $review_status = 0;                // Page review status
    public $noindex = 0;                // Whether or not page is to be indexed (currently not functional)
    public $ajax = 0;                // Whether or not page is to be for ajax request purposes
    public $directly_viewable = 1;                // Whether or not page is to be publicly directly viewable (currently not functional)
    public $versions = "";               // String of version histories if page versioning is on
    public $author_name = "";               // String of the author of the page, page changes
    public $author_admin_user_id = 0;               // Author Id of the page, page changes
    public $comment = "";               // Comment about the page, page changes

    public $data1 = "";               // varchar(1024) field in db for some DESPERATE custom situations
    public $data2 = "";               // varchar(1024) field in db for some DESPERATE custom situations
    public $data3 = "";               // varchar(1024) field in db for some DESPERATE custom situations

    // The following aren't in database but are generated either automatically or on request.

    public $trail = array();                  // Trail of page ids from this page to 0.
    public $subs = array();                  // Array of sub pages. Use Page::get_subs().
    public $url = "";                       // Full slug url to this page.
    public $pagedata = array();                  // Page pagedata.

    public $path = null;
    public $page_lists = null;

    public $link = null;
    public $active = null;

    /**
     * Page::Page()
     *
     * @param [int id = 0]
     * @return bool - If fetching of page is a success. Listen to this only if you are fetching a page
     *
     * Construct function.
     * Will init page.
     */
    public function __construct($id = 0)
    {

        if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true && CMS_MULTI_LANG_OTHER_DEFAULT_TO_P_LANGUAGE === true) {
            global $p;
            if (isset($p->language)) {
                $this->language = $p->language;
            } else {
                $this->language = "";
            }
        }

        if (!empty($id)) {
            return $this->Get($id);
        }
        return false;
    }

    protected function _getCache($page_id)
    {

        // RS 12/10/2013: Im doing cache logic here for the sake of keeping everything simple
        // and to have cache work on any Page object initializations
        // Page cache had a class but I did away with it. Should it be or not?
        if (PAGE_OBJECT_CACHE) {
            if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true) {
                $cache_data = PageCache::where("page_id", $page_id)
                    ->where("language", $this->language)
                    ->first();
            } else {
                $cache_data = PageCache::where("page_id", $page_id)
                    ->first();
            }

            $get_cache = false;
            if ($cache_data !== null) {
                if (PAGE_OBJECT_CACHE_SECONDS === 0) {
                    $get_cache = true;
                } else {
                    $cache_age_in_seconds = time() - strtotime($cache_data->timestamp);
                    if ($cache_age_in_seconds < PAGE_OBJECT_CACHE_SECONDS) {
                        $get_cache = true;
                    }
                }
            }

            if ($get_cache) {

                $pageObject = unserialize($cache_data->object);

                if ($pageObject === false) {
                    return false;
                }

                $this->id = $pageObject->id;
                $this->sub_id = $pageObject->sub_id;
                $this->order = $pageObject->order;
                $this->type = $pageObject->type;
                $this->slug = $pageObject->slug;
                $this->innav = $pageObject->innav;
                $this->searchable = $pageObject->searchable;
                $this->lock = $pageObject->lock;
                $this->hide_in_cms = $pageObject->hide_in_cms;
                $this->allow_delete = $pageObject->allow_delete;
                $this->allow_update = $pageObject->allow_update;
                $this->redchild = $pageObject->redchild;
                $this->listing_container = $pageObject->listing_container;
                $this->listing_item = $pageObject->listing_item;
                $this->draft_for_page_id = $pageObject->draft_for_page_id;
                $this->history_lock = $pageObject->history_lock;
                $this->review_status = $pageObject->review_status;
                $this->noindex = $pageObject->noindex;
                $this->search_title = $pageObject->search_title;
                $this->search_content = $pageObject->search_content;
                $this->created = $pageObject->created;
                $this->updated = $pageObject->updated;
                $this->published = $pageObject->published;
                $this->template = $pageObject->template;
                $this->layout = $pageObject->layout;
                $this->logic = $pageObject->logic;
                $this->frontpage = $pageObject->frontpage;
                $this->contact_page = $pageObject->contact_page;
                $this->ajax = $pageObject->ajax;
                $this->directly_viewable = $pageObject->directly_viewable;
                $this->versions = $pageObject->versions;
                $this->author_name = $pageObject->author_name;
                $this->author_admin_user_id = $pageObject->author_admin_user_id;
                $this->comment = $pageObject->comment;
                $this->data1 = $pageObject->data1;
                $this->data2 = $pageObject->data2;
                $this->data3 = $pageObject->data3;

                $this->title = $pageObject->title;
                if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true && CMS_MULTI_LANG_PAGE_TITLE_IN_JSON === true && !empty($pageObject->title_json)) {
                    $this->title_json = $pageObject->title_json;
                }

                $this->seo_title = $pageObject->seo_title;
                $this->seo_keywords = $pageObject->seo_keywords;
                $this->seo_description = $pageObject->seo_description;
                if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true && CMS_MULTI_LANG_SEO_DETAILS_IN_JSON === true) {
                    if (!empty($pageObject->seo_title_json)) {
                        $this->seo_title_json = $pageObject->seo_title_json;
                    }
                    if (!empty($pageObject->seo_keywords_json)) {
                        $this->seo_keywords_json = $pageObject->seo_keywords_json;
                    }
                    if (!empty($pageObject->seo_description_json)) {
                        $this->seo_description_json = $pageObject->seo_description_json;
                    }
                }

                $this->path = $pageObject->path;
                $this->url = $pageObject->url;
                $this->pagedata = $pageObject->pagedata;
                $this->page_lists = $pageObject->page_lists;
                $this->trail = $pageObject->trail;

                //

                return true;

            }
        }

        return false;

    }

    protected function _storeCache($url)
    {
        $serialized_page = serialize($this);

        $query_builder = PageCache::where("page_id", $this->id);
        if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true) {
            $query_builder = $query_builder->where("language", $this->language);
        }
        $page_cache_list = $query_builder->get();
        foreach ($page_cache_list as $page_cache) {
            $page_cache->delete();
        }

        $page_cache = new PageCache();
        $page_cache->page_id = $this->id;
        $page_cache->object = $serialized_page;
        $page_cache->url = $url;
        if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true) {
            $page_cache->language = $this->language;
        }
        return $page_cache->save();
    }


    /**
     * Page::Get()
     *
     * @param [int id = 0]
     * @return bool
     *
     *  Fetch from db, handle cache, setup additional data function
     *  This can be called if you ever wanted to 'refresh' the data
     */
    public function Get($id)
    {

        if (is_numeric($id)) {

            if ($this->_getCache($id)) {
                return true;
            }

            $page_data = PageModel::find($id);
            if ($page_data !== null) {

                $this->id = $page_data->id;
                $this->sub_id = $page_data->sub_id;
                $this->order = $page_data->order;
                $this->type = $page_data->type;
                $this->slug = $page_data->slug;
                $this->innav = $page_data->innav;
                $this->searchable = $page_data->searchable;
                $this->lock = $page_data->lock;
                $this->hide_in_cms = $page_data->hide_in_cms;
                $this->allow_delete = $page_data->allow_delete;
                $this->allow_update = $page_data->allow_update;
                $this->redchild = $page_data->redchild;
                $this->listing_container = $page_data->listing_container;
                $this->listing_item = $page_data->listing_item;
                $this->draft_for_page_id = $page_data->draft_for_page_id;
                $this->history_lock = $page_data->history_lock;
                $this->review_status = $page_data->review_status;
                $this->noindex = $page_data->noindex;
                $this->search_title = $page_data->search_title;
                $this->search_content = $page_data->search_content;
                $this->created = $page_data->created;
                $this->updated = $page_data->updated;
                $this->published = $page_data->published;
                $this->template = $page_data->template;
                $this->layout = $page_data->layout;
                $this->logic = $page_data->logic;
                $this->frontpage = $page_data->frontpage;
                $this->contact_page = $page_data->contact_page;
                $this->ajax = $page_data->ajax;
                $this->directly_viewable = $page_data->directly_viewable;
                $this->versions = $page_data->versions;
                $this->author_name = $page_data->author_name;
                $this->author_admin_user_id = $page_data->author_admin_user_id;
                $this->comment = $page_data->comment;
                $this->data1 = $page_data->data1;
                $this->data2 = $page_data->data2;
                $this->data3 = $page_data->data3;

                if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true && CMS_MULTI_LANG_PAGE_TITLE_IN_JSON === true) {
                    $this->title_json = $page_data->title;
                    $this->title = $this->_getValueFromLanguageJSON($this->title_json);
                } else {
                    $this->title = $page_data->title;
                }

                if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true && CMS_MULTI_LANG_SEO_DETAILS_IN_JSON === true) {
                    $this->seo_title_json = $page_data->seo_title;
                    $this->seo_title = $this->_getValueFromLanguageJSON($this->seo_title_json);
                    $this->seo_keywords_json = $page_data->seo_keywords;
                    $this->seo_keywords = $this->_getValueFromLanguageJSON($this->seo_keywords_json);
                    $this->seo_description_json = $page_data->seo_description;
                    $this->seo_description = $this->_getValueFromLanguageJSON($this->seo_description_json);
                } else {
                    $this->seo_title = $page_data->seo_title;
                    $this->seo_keywords = $page_data->seo_keywords;
                    $this->seo_description = $page_data->seo_description;
                }

                // Do aditional data fetch calls for things that need to be generated
                $this->url = $this->get_url(); // fetch the url
                $this->path = $this->get_url(false); // fetch the path

                // Pagedata
                $pagedata = new Pagedata();
                $pagedata->setLanguage($this->language);
                $pagedata->Get($this->id);
                $this->page_lists = $pagedata->pagedata; // raw pagedata
                $this->pagedata = Pagedata::ConvertToPagedataTags($this->page_lists); // formated for pulling through to view

                if (PAGE_OBJECT_CACHE === true && $this->published == 1 && $this->type == 'default') {
                    $cache_url = $this->get_url(false);
                    // $this->get_trail(); still not sure if this should be cached?
                    $this->_storeCache($cache_url);
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Attempts to parse a langauge json - {"en":"Title","lv":"Tituls","de":"Titel"}
     * @param string $potential_json
     * @return string $value
     */
    private function _getValueFromLanguageJSON($potential_json)
    {
        return Page::getValueFromLanguageJSON($potential_json, $this->language);
    }

    private function _setValueToLanguageJSON($value, $potential_json = false)
    {
        $language_code = $this->language;
        if (empty($language_code)) {
            $language_code = "en";
        }
        $array = array($language_code => $value);
        $json = json_decode($potential_json, true);
        if (!empty($json)) {
            $json[$language_code] = $value;
            return json_encode($json);
        }
        return json_encode($array);
    }

    public static function getValueFromLanguageJSON($potential_json, $language = "")
    {
        $language_code = $language;
        if (empty($language_code)) {
            $language_code = "en";
        }
        $value = $potential_json;
        $json = json_decode($potential_json, true);
        if (!empty($json)) {
            if (isset($json[$language_code])) {
                return $json[$language_code];
            } else if (isset($json["en"])) {
                return $json["en"];
            }
        }
        return $value;
    }

    /**
     * Page::get_url()
     *
     * @return string url
     *
     * Call this when you want to get the url of a page you already have an object of
     * URL will returned and also stored in object so later you don't have to do Sql
     * quieries in order to get url. You'll be able to just do $page->url
     */
    public function get_url($include_site_url = CMS_INCLUDE_DOMAIN_IN_PAGE_URL)
    {
        // Generates and sets url
        //  if url is empty or req options doesn't match with default
        if (empty($this->url) || $include_site_url != CMS_INCLUDE_DOMAIN_IN_PAGE_URL) {
            $this->url = $this->generate_url($this->id, $include_site_url, $this->language); // a costly fion
        }
        return $this->url;
    }

    /**
     * Page::generate_url()
     *
     * @param int page_id
     * @return string url
     *
     * Generates the URL for a page.
     *
     * TODO: do we need to store full urls in table?
     */
    public static function generate_url($page_id, $include_site_url = CMS_INCLUDE_DOMAIN_IN_PAGE_URL, $language = null, $only_public = false)
    {
        $result = "";
        $i = 0;
        while (intval($page_id) != 0) {
            $query_builder = PageModel::where('id', $page_id);
            if ($i == 0) {
                $query_builder = $query_builder->where(function ($query) {
                    $query->where('type', 'default')
                        ->orWhere('type', 'history');
                });
            }
            $page_data = $query_builder->first(['frontpage', 'type', 'slug', 'sub_id', 'published']);

            if ($page_data === null) {
                $page_id = 0;
                continue;
            }

            if ($page_data->slug == CMS_ROOT_PAGE_SLUG) {
                break;
            }

            if ($only_public == true && $page_data->published == 0) {
                return false;
            }

            if ($page_data->frontpage != 1) {
                $result = $page_data->slug . ($result == "" ? "" : "/") . $result;
            }

            $page_id = $page_data->sub_id;
            ++$i;
        }
        $result = "/" . $result;

        if (!isset($language) && defined('CMS_MULTI_LANG_GEN_URL_FUNCTION_TO_DEFAULT_TO_P_LANGUAGE') && CMS_MULTI_LANG_GEN_URL_FUNCTION_TO_DEFAULT_TO_P_LANGUAGE === true) {
            global $p;
            if (isset($p->language)) {
                $language = $p->language;
            }
        }

        if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true && CMS_MULTI_LANG_SWITCH_MODE === 'URL_SUBDIR' && !empty($language)) {
            $result = "/" . $language . $result;
        }

        if ($include_site_url == true) {
            $result = SITE_URL . $result;
        }

        $result = str_replace('//', '/', $result);

        return $result;
    }

    /**
     * Page::setLanguage()
     * Call before Page::Get() call to get a page in desired language
     * @param string $language
     * @return string $language
     */
    public function setLanguage($language)
    {
        if ($language === "" || defined('CMS_MULTI_LANG_CODE_' . $language)) {
            $this->language = $language;
        }
        return $this->language;
    }


    /**
     * Page::get_trail()
     *
     * @return Page Object[]
     *
     * Call this when you want to get the trail of a page you already have an object of
     * Trail will returned and also stored in object so later you don't have to do Sql
     * quieries in order to get the trail. You'll be able to just do $page->trail
     */
    public function get_trail()
    {
        // Generates and sets trail
        if (empty($this->trail)) {
            $this->trail = $this->generate_trail($this->id); // a costly fion
        }
        return $this->trail;
    }


    /**
     * Page::generate_trail()
     *
     * @param int page_id
     * @return Page Object []
     *
     * Generates the trail for this page.
     * Trail contains this page object and all of the parent pages
     * objects. All in one array. First being the `top parent`
     */
    public static function generate_trail($page_id)
    {
        $trail = array();

        $pagecrumb = new Page($page_id);
        $pID = $pagecrumb->sub_id;
        $trail[] = $pagecrumb;

        while (!empty($pID)) {
            $pagecrumb = new Page($pID);
            if (empty($pagecrumb->sub_id)) {
                break;
            }
            $pID = $pagecrumb->sub_id;
            $trail[] = $pagecrumb;
        }

        $trail = array_reverse($trail);
        return $trail;
    }

    /**
     * Page::get_subs()
     *
     * @param int limit = PHP_INT_MAX
     * @param string orderBy = "id"
     * @param string order = "ASC"
     * @param bool public = true
     * @param bool only_pages = false
     * @param string filter = ""
     * @return Page[] page_subs
     *
     */
    public function get_subs($limit = PHP_INT_MAX, $orderBy = "order", $order = "ASC", $public = true, $only_pages = false, $filter = "")
    {
        // Generates and sets subs
        $this->subs = $this->retrieve_subs($this->id, $limit, $orderBy, $order, $public, $only_pages, $filter);

        return $this->subs;
    }

    /**
     * Page::retrieve_subs()
     *
     * @param int page_id
     * @param int limit = PHP_INT_MAX
     * @param string orderBy = "id"
     * @param string order = "ASC"
     * @param bool public = true
     * @param bool only_pages = false
     * @param string filter = ""
     * @return Page[] page_subs
     *
     * Retrievs all or limited number of sub pages objects of provided page
     * order can be customized. (Default ORDER BY `id` ASC)
     */
    public static function retrieve_subs($page_id, $limit = PHP_INT_MAX, $orderBy = "order", $order = "ASC", $public = true, $only_pages = false, $filter = "")
    {
        $query_builder = PageModel::where("sub_id", $page_id)
            ->orderBy($orderBy, $order)
            ->take($limit);

        if ($public) {
            $query_builder = $query_builder->where("published", 1)
                ->where("type", "default");
        }
        if ($only_pages) {
            $query_builder = $query_builder->where("type", "default");
        }

        if ($filter != "") {
            $query_builder = $query_builder->whereRaw($filter);
        }

        $page_data_collection = $query_builder->get(["id"]);

        $subs = array();
        foreach ($page_data_collection as $page_data) {
            $page = new Page($page_data->id);

            $include_page = true;

            if ($include_page) {

                $subs[] = $page;

            }

        }
        return $subs;
    }

    /**
     * Page::dropCorruptPages()
     * Drop variously corrupt cms pages.
     * @return bool
     */
    public static function dropCorruptPages()
    {
        $result = true;
        $drop_corrupt_pages_after_x_min = 360;
        if (defined("CMS_DROP_CORRUPT_PAGES_AFTER_X_MIN") && is_numeric(CMS_DROP_CORRUPT_PAGES_AFTER_X_MIN)) {
            $drop_corrupt_pages_after_x_min = CMS_DROP_CORRUPT_PAGES_AFTER_X_MIN;
        }
        $page_data_collection = PageModel::where("type", "default")
            ->where("title", "")
            ->whereRaw("`updated` < DATE_SUB(NOW(), INTERVAL " . $drop_corrupt_pages_after_x_min . " MINUTE)")
            ->get(["id"]);
        foreach ($page_data_collection as $page_data) {
            $result = Page::Delete(
                $page_data->id, // page id
                false,          // delete sub pages
                true            // delete relations
            );
        }

        return $result;
    }

    /**
     * Page::dropTempPages()
     * Drops temporary pages. Would have put this in triggers but caused stored procedure/trigger conflicts
     * @return bool
     */
    public static function dropTempPages()
    {
        $result = true;
        $drop_temp_pages_after_x_min = 120;
        if (defined("CMS_DROP_TEMP_PAGES_AFTER_X_MIN") && is_numeric(CMS_DROP_TEMP_PAGES_AFTER_X_MIN)) {
            $drop_temp_pages_after_x_min = CMS_DROP_TEMP_PAGES_AFTER_X_MIN;
        }
        $page_data_collection = PageModel::where("type", "temp")
            ->whereRaw("`updated` < DATE_SUB(NOW(), INTERVAL " . $drop_temp_pages_after_x_min . " MINUTE)")
            ->get(['id']);
        foreach ($page_data_collection as $page_data) {
            $result = Page::Delete(
                $page_data->id,// temp page id
                false,      // delete sub pages
                true        // delete relations
            );
        }

        return $result;
    }

    /**
     * Page::dropHistoryPagesCountLimit()
     * Limit on how many history versions a page can have
     * @param int $page_id
     * @param int $cms_page_versioning_history_limit_count
     * @return bool
     */
    public static function dropHistoryPagesCountLimit($page_id, $cms_page_versioning_history_limit_count = 200)
    {
        $result = true;
        $page_data_collection = PageModel::where("type", "history")
            ->where("history_lock", 0)
            ->where("draft_for_page_id", $page_id)
            ->orderBy("updated", "DESC")
            ->take(PHP_INT_MAX)
            ->skip($cms_page_versioning_history_limit_count)
            ->get(["id"]);
        foreach ($page_data_collection as $page_data) {
            $result = Page::Delete(
                $page_data->id,// history page id
                false,      // delete sub pages
                true        // delete relations
            );
        }
        return $result;
    }

    /**
     * Page::dropHistoryPagesAgeLimit()
     * Limit on how old history versions a page can have
     * @param int $page_id
     * @param int $cms_page_versioning_history_limit_count
     * @return bool success
     */
    public static function dropHistoryPagesAgeLimit($page_id, $cms_page_versioning_history_limit_age = 712)
    {
        if (!is_numeric($cms_page_versioning_history_limit_age)) {
            $cms_page_versioning_history_limit_age = 712;
        }
        $result = true;
        $page_data_collection = PageModel::where("type", "history")
            ->where("history_lock", 0)
            ->where("draft_for_page_id", $page_id)
            ->whereRaw("`updated` < DATE_SUB(NOW(), INTERVAL " . $cms_page_versioning_history_limit_age . " DAY)")
            ->get(['id']);
        foreach ($page_data_collection as $page_data) {
            $result = Page::Delete(
                $page_data->id,// history page id
                false,      // delete sub pages
                true        // delete relations
            );
        }
        return $result;
    }

    /**
     * Page::dropParentlessHistoryPages()
     * Drop parentless history pages
     * @return bool success
     */
    public static function dropParentlessHistoryPages()
    {
        $pages = CmsPage::query()
            ->where('type', 'history')
            ->whereNotExists(function (Builder $query) {
                $query->select('id')
                    ->from('pages as b')
                    ->where('b.draft_for_page_id', 'pages.id')
                    ->where('b.type', 'default');
            });
        foreach ($pages as $page) {
            Page::delete($page->id);
        }
        return true;
    }

    /**
     * Page::dropHistoryPages()
     * Drops history pages. Would have put this in triggers but caused stored procedure/trigger conflicts
     * @param int $page_id
     * @return bool success
     */
    public static function dropHistoryPages($page_id)
    {

        $result = true;

        if (!defined("CMS_PAGE_VERSIONING_HISTORY_LIMITS") || empty(CMS_PAGE_VERSIONING_HISTORY_LIMITS)) {
            return $result;
        }

        // Limit on how many history versions a page can have
        if (strpos(CMS_PAGE_VERSIONING_HISTORY_LIMITS, 'COUNT') !== false) {
            $result = Page::dropHistoryPagesCountLimit($page_id, CMS_PAGE_VERSIONING_HISTORY_LIMIT_COUNT);
        }

        // Limit on how old history versions a page can have
        if (strpos(CMS_PAGE_VERSIONING_HISTORY_LIMITS, 'AGE') !== false) {
            $result = Page::dropHistoryPagesAgeLimit($page_id, CMS_PAGE_VERSIONING_HISTORY_LIMIT_AGE);
        }

        $result = Page::dropParentlessHistoryPages();

        return $result;
    }

    /**
     * Page::Save()
     */
    public function Save()
    {
        if ($this->type == "default") {
            Page::dropTempPages();
        }

        // no `url` field - not in use
        $params = array(
            'sub_id' => (int)$this->sub_id,
            'order' => (int)$this->order,
            'type' => $this->type,
            'title' => $this->title,
            'slug' => $this->slug,
            'innav' => (int)$this->innav,
            'searchable' => (int)$this->searchable,
            'lock' => (int)$this->lock,
            'hide_in_cms' => (int)$this->hide_in_cms,
            'allow_delete' => (int)$this->allow_delete,
            'allow_update' => (int)$this->allow_update,
            'redchild' => (int)$this->redchild,
            'listing_container' => (int)$this->listing_container,
            'draft_for_page_id' => (int)$this->draft_for_page_id,
            'history_lock' => (int)$this->history_lock,
            'review_status' => (int)$this->review_status,
            'noindex' => (int)$this->noindex,
            'search_content' => $this->search_content,
            'search_title' => $this->search_title,
            'ajax' => (int)$this->ajax,
            'directly_viewable' => (int)$this->directly_viewable,
            'versions' => $this->versions,
            'author_name' => $this->author_name,
            'author_admin_user_id' => (int)$this->author_admin_user_id,
            'comment' => $this->comment,
            'frontpage' => (int)$this->frontpage,
            'contact_page' => (int)$this->contact_page,
            'published' => (int)$this->published,
            'template' => $this->template,
            'layout' => $this->layout,
            'logic' => $this->logic,
            'seo_title' => $this->seo_title,
            'seo_keywords' => $this->seo_keywords,
            'seo_description' => $this->seo_description,
            'data1' => $this->data1,
            'data2' => $this->data2,
            'data3' => $this->data3
        );

        if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true && CMS_MULTI_LANG_PAGE_TITLE_IN_JSON === true) {
            // this can go wrong if *_json is old (two pages open)
            $params['title'] = $this->_setValueToLanguageJSON(
                $this->title,
                empty($this->title_json) ? false : $this->title_json
            );
        }

        if (defined('CMS_MULTI_LANG') && CMS_MULTI_LANG === true && CMS_MULTI_LANG_SEO_DETAILS_IN_JSON === true) {
            // this can go wrong if *_json is old (two pages open)
            $params['seo_title'] = $this->_setValueToLanguageJSON(
                $this->seo_title,
                empty($this->seo_title_json) ? false : $this->seo_title_json
            );
            $params['seo_keywords'] = $this->_setValueToLanguageJSON(
                $this->seo_keywords,
                empty($this->seo_keywords_json) ? false : $this->seo_keywords_json
            );
            $params['seo_description'] = $this->_setValueToLanguageJSON(
                $this->seo_description,
                empty($this->seo_description_json) ? false : $this->seo_description_json
            );
        }

        if (empty($this->id)) {
            // Adding order fix
            Page::orderFix($this->sub_id);
            $params = array(':sub_id' => $this->sub_id);
            if (defined('CMS_PAGE_ORDER_GENERATION_FROM_TOP') && CMS_PAGE_ORDER_GENERATION_FROM_TOP) {
                $page = CmsPage::query()
                    ->where('sub_id', $this->sub_id)
                    ->orderByDesc('order')
                    ->first();
                // Find the last button as new page will be added after it
                $params['order'] = !empty($page) ?
                    $page->order + 1 :
                    1;
                CmsPage::query()
                    ->where('sub_id', $this->sub_id)
                    ->where('type', '!=', 'button')
                    ->update(['order' => DB::raw('`order` + 1')]);
            } else {
                // Get Biggest Order Value Of Those Children
                $page = CmsPage::query()
                    ->where('sub_id', $this->sub_id)
                    ->orderByDesc('order')
                    ->first();
                $params['order'] = !empty($page) ?
                    $page->order + 1 :
                    1;
            }
            $page = CmsPage::query()
                ->create($params);
            $this->id = $page->id;

        } else {
            CmsPage::query()
                ->where('id', $this->id)
                ->update($params);
        }

        $requested_page = CmsPage::query()
            ->find($this->id);
        if ($requested_page) {
            $mm = new MultisiteManager($requested_page);
            $mm->getBaseEntity();
        }

        return true;
    }

    /**
     * Page::setReviewStatus()
     * Set page review status
     * @param int $status
     * @return bool $success
     */
    public function setReviewStatus($status, $comment = null)
    {
        if (empty($this->id)) {
            return false;
        }
        $page = CmsPage::query()
            ->find($this->id);
        $page->review_status = $status;
        if (isset($comment)) {
            $page->comment = $comment;
        }
        $page->save();
        return true;
    }

    /**
     * Page::toggleVersionLock()
     * toggle version lock
     * @param void
     * @return bool $success
     */
    public function toggleVersionLock()
    {
        if (empty($this->id)) {
            return false;
        }
        CmsPage::query()
            ->where('id', $this->id)
            ->update([
                'history_lock' => (int)!$this->history_lock
            ]);

        return (int)!$this->history_lock;
    }

    /**
     * Page::id_in_trail()
     *
     * @param int $id , Page Object trail[]
     * @return bool intrail
     *
     * Checks to see if an id is in the given page (object) trail
     */
    public static function id_in_trail($id, $trail)
    {
        $intrail = false;
        foreach ($trail as $page) {
            if ($page->id == $id) {
                $intrail = true;
                break;
            }
        }
        return $intrail;
    }

    /**
     * Page::get_top_parent_id()
     *
     * @param int page_id
     * @return int top_parent_id
     *
     * Finds the very top parent id (i.e. the page with sub_id = 0).
     */
    public static function get_top_parent_id($page_id)
    {
        $page = CmsPage::query()
            ->find($page_id);
        if (empty($page)) {
            return 0;
        }
        return $page->sub_id === 0 ?
            $page->id :
            self::get_top_parent_id($page->sub_id);
    }

    /**
     * Page::has_subs()
     *
     * @param int page_id
     * @return bool
     *
     * Checks to see if the current page has sub pages.
     */
    public static function has_subs($page_id, $public = true)
    {
        return CmsPage::query()
            ->where('sub_id', $page_id)
            ->when($public, function ($query) {
                $query->where('published', 1)
                    ->where('type', 'default');
            })
            ->exists();
    }

    /**
     * Page::num_of_subs()
     *
     * @param int page_id
     * @return int number_of_subs
     *
     * Checks how many subs page has.
     *
     */
    public static function num_of_subs($sub_id)
    {
        return CmsPage::query()
            ->where('sub_id', $sub_id)
            ->where('type', 'default')
            ->where('published', 1)
            ->count();
    }


    /**
     * Page::exists()
     *
     * @param int page_id
     * @return bool
     *
     * Checks if a page exists with given id
     */
    public static function exists($page_id)
    {
        return CmsPage::query()
            ->where('id', $page_id)
            ->exists();
    }

    /**
     * Returns a list of pages
     *
     * @return array
     */
    public static function getPageList(): array
    {
        $pages = CmsPage::query()
            ->whereNotIn('type', [
                'temp',
                'history',
            ])
            ->orderBy('order')
            ->get();
        $list = [];
        foreach ($pages as $page) {
            $list[] = new Page($page->id);
        }
        return $list;
    }

    /**
     * Returns a list of pages
     *
     * @return array
     */
    public static function getButtonList($subID): array
    {
        $pages = CmsPage::query()
            ->where('type', 'button')
            ->where('sub_id', $subID)
            ->orderBy('order')
            ->get();
        $list = [];
        foreach ($pages as $page) {
            $list[] = new Page($page->id);
        }
        return $list;
    }

    /**
     * Page::Delete()
     *
     * @param int $page_id ,
     * @param bool $delete_subpages default: false
     * @param bool $delete_relations default: true
     * @param bool $delete_linked_drafts default: true
     *
     * @return bool success
     */
    public static function delete($page_id, $delete_subpages = false, $delete_relations = true, $delete_linked_drafts = true)
    {
        // Execute events listening for 'Page/delete:before_delete'
        Event::dispatch(
            __CLASS__ . '/' . __FUNCTION__ . ':before_delete',
            [
                $page_id,
                $delete_subpages,
                $delete_relations,
                $delete_linked_drafts
            ]
        );

        if ($delete_subpages) {
            $subpages = CmsPage::query()
                ->where('sub_id', $page_id)
                ->get();
            foreach ($subpages as $subpage) {
                self::delete($subpage->id, $delete_subpages, $delete_relations);
            }
        }

        PageModel::destroy($page_id);
        if ($delete_relations) {
            \Mtc\Cms\Models\PageList::query()
                ->where('page_id', $page_id)
                ->delete();
            PageListItem::query()
                ->where('page_id', $page_id)
                ->delete();
            PageListItemData::query()
                ->where('page_id', $page_id)
                ->delete();
            if ($delete_linked_drafts) {
                $pages = CmsPage::query()
                    ->where('draft_for_page_id', $page_id)
                    ->get();
                foreach ($pages as $page) {
                    self::Delete($page->id, $delete_subpages, $delete_relations);
                }
            }
        }

        HooksAdapter::do_action(__CLASS__ . '/' . __FUNCTION__, ['page_id' => $page_id, 'delete_subpages' => $delete_subpages, 'delete_relations' => $delete_relations, 'delete_linked_drafts' => $delete_linked_drafts]);
        return true;
    }

    /**
     * Page::replacePageId()
     *
     * @param int old_id
     * @param int new_id
     * @return mixed result
     *
     * Replaces a page id. Returns false on failure
     *
     */
    public static function replacePageId($old_id, $new_id)
    {
        CmsPage::query()
            ->where('id', $old_id)
            ->update([
                'id' => $new_id,
            ]);
        return true;
    }

    /**
     * Page::getHighestPageId()
     * Return the highest `pages`.`id`
     * @return int highest_page_id
     */
    public static function getHighestPageId()
    {
        return CmsPage::query()
            ->max('id');
    }

    /**
     * Page::orderFix()
     *
     * Assign new order values to get rid of duplicate order values next to each other
     *  These can pop up when deleting pages, pasting pages and other reasons
     *
     * @param int sub_id
     * @return bool
     */
    public static function orderFix($sub_id)
    {
        $pages = CmsPage::query()
            ->where('sub_id', $sub_id)
            ->where('type', 'default')
            ->orderBy('order')
            ->get()
            ->toArray();

        $buttonPagesCount = CmsPage::query()
            ->where('sub_id', $sub_id)
            ->where('type', 'button')
            ->count();
        $order = $buttonPagesCount + 1;

        for ($i = 0; $i < count($pages); $i++) {
            CmsPage::query()
                ->where('id', $pages[$i]['id'])
                ->whereNotIn('type', [
                    'temp',
                    'history',
                ])
                ->update([
                    'order' =>  $order,
                ]);

            $order++;
        }

        return true;
    }

    /**
     * Page::getPageIdByName()
     *
     * Get a page ID using its title
     *
     * @param string $page_name Page Name to search for
     * @return int | bool
     */
    public static function getPageIdByName($page_name)
    {
        $page_data = PageModel::where("title", $page_name)
            ->where("type", "default")
            ->where("published", 1)
            ->first(['id']);
        if ($page_data === null) {
            return false;
        } else {
            return $page_data->id;
        }
    }

    /**
     * Remove default data from a page structure
     * @param mixed[][] $page_as_array
     * @return mixed[][] $page_as_array
     */
    public static function removeDefaultData($page_as_array)
    {
        // get default structure from newly created models
        $fresh_page = new Page();
        $fresh_page->save();

        foreach ($page_as_array as $attribute => $value) {
            // loop through attributes compare if they are "default"
            if (
                $attribute === "id" && $value == 0 ||
                $attribute === "sub_pages" && $value == [] ||
                $attribute === "order" && $value == null ||
                isset($fresh_page->{$attribute}) && $fresh_page->{$attribute} == $value && $attribute !== "order"
            ) {
                // if they are - unset
                unset($page_as_array[$attribute]);
            }
        }

        // clean up and delete newly created models
        Page::delete($fresh_page->id);

        return $page_as_array;
    }

    /**
     * Drop pages that refrence a page in sub_id that doesn't exist.
     * @return Page[] $orphaned_pages
     */
    public static function dropOrphanedPages()
    {
        $orphaned_pages = PageModel::where("sub_id", "!=", 0)
            ->whereRaw("
                NOT EXISTS (
                    SELECT `id`
                    FROM `pages` as `pages_temp`
                    WHERE `id` = `pages`.`sub_id`
                )
            ")
            ->get();

        foreach ($orphaned_pages as $orphaned_page) {
            $dropped_page_ids[] = $orphaned_page->id;
            self::delete(
                $orphaned_page->id,// page id
                false,             // delete sub pages
                false              // delete relations
            );
        }
        return $orphaned_pages;
    }

    /**
     * Retrieves value from an attribute in page or pagedata without knowing which one it is
     * @param mixed[][] location of value
     * @return mixed[] result containing value, type, name
     */
    public function getValue($attribute_location)
    {
        $value = null;
        // if its not an array, it's a page attribute
        if (!is_array($attribute_location)) {
            $attribute_name = str_replace("`", "", $attribute_location);
            if (isset($this->{$attribute_name})) {
                $value = clean_page($this->{$attribute_name});
            }
            $type = "page attribute";
        } elseif (isset($attribute_location[0]) && is_array($attribute_location[0])) {
            $attribute_name = $attribute_location[0][2];
            // check if such pagedata exists
            // $this->pagedata[ PAGEDATA_LIST_NAME ][ INDEX ][ PAGEDATA_DATA_NAME ]['value']
            // PAGEDATA_LIST_NAME = $attribute_location[0][0]
            // INDEX = $attribute_location[0][1]
            // PAGEDATA_DATA_NAME = $attribute_location[0][2]
            if (isset($this->pagedata[$attribute_location[0][0]][$attribute_location[0][1]][$attribute_location[0][2]]['value'])) {
                $value = $this->pagedata[$attribute_location[0][0]][$attribute_location[0][1]][$attribute_location[0][2]]['value'];
            }
            $type = "pagedata";
        } else {
            $attribute_name = $attribute_location[0];
            // check if such pagedata exists
            // $this->pagedata[ PAGEDATA_DATA_NAME ][0]['value']
            // PAGEDATA_DATA_NAME = $attribute_location[0]
            if (isset($this->pagedata[$attribute_location[0]][0]['value'])) {
                $value = clean_page($this->pagedata[$attribute_location[0]][0]['value']);
            }
            $type = "pagedata";
        }
        return [
            'value' => $value,
            'type' => $type,
            'name' => $attribute_name
        ];
    }

}
