| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122 | <?phpnamespace Grav\Plugin\Admin;use Grav\Common\Config\Config;use Grav\Common\Data\Data;use Grav\Common\Filesystem\Folder;use Grav\Common\Grav;use Grav\Common\Media\Interfaces\MediaInterface;use Grav\Common\Page\Interfaces\PageInterface;use Grav\Common\Page\Media;use Grav\Common\Uri;use Grav\Common\User\Interfaces\UserInterface;use Grav\Common\Utils;use Grav\Common\Plugin;use Grav\Common\Theme;use RocketTheme\Toolbox\Event\Event;use RocketTheme\Toolbox\File\File;/** * Class AdminController * * @package Grav\Plugin */class AdminBaseController{    /**     * @var Grav     */    public $grav;    /**     * @var string     */    public $view;    /**     * @var string     */    public $task;    /**     * @var string     */    public $route;    /**     * @var array     */    public $post;    /**     * @var array|null     */    public $data;    /**     * @var \Grav\Common\Uri     */    protected $uri;    /**     * @var Admin     */    protected $admin;    /**     * @var string     */    protected $redirect;    /**     * @var int     */    protected $redirectCode;    protected $upload_errors = [        0 => 'There is no error, the file uploaded with success',        1 => 'The uploaded file exceeds the max upload size',        2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML',        3 => 'The uploaded file was only partially uploaded',        4 => 'No file was uploaded',        6 => 'Missing a temporary folder',        7 => 'Failed to write file to disk',        8 => 'A PHP extension stopped the file upload'    ];    /** @var array */    public $blacklist_views = [];    /**     * Performs a task.     *     * @return bool True if the action was performed successfully.     */    public function execute()    {        if (in_array($this->view, $this->blacklist_views, true)) {            return false;        }//        if (!$this->validateNonce()) {//            return false;//        }        $method = 'task' . ucfirst($this->task);        if (method_exists($this, $method)) {            try {                $success = $this->{$method}();            } catch (\RuntimeException $e) {                $success = true;                $this->admin->setMessage($e->getMessage(), 'error');            }        } else {            $success = $this->grav->fireEvent('onAdminTaskExecute',                new Event(['controller' => $this, 'method' => $method]));        }        // Grab redirect parameter.        $redirect = $this->post['_redirect'] ?? null;        unset($this->post['_redirect']);        // Redirect if requested.        if ($redirect) {            $this->setRedirect($redirect);        }        return $success;    }    protected function validateNonce()    {        if (strtolower($_SERVER['REQUEST_METHOD']) === 'post') {            if (isset($this->post['admin-nonce'])) {                $nonce = $this->post['admin-nonce'];            } else {                $nonce = $this->grav['uri']->param('admin-nonce');            }            if (!$nonce || !Utils::verifyNonce($nonce, 'admin-form')) {                if ($this->task === 'addmedia') {                    $message = sprintf($this->admin::translate('PLUGIN_ADMIN.FILE_TOO_LARGE', null),                        ini_get('post_max_size'));                    //In this case it's more likely that the image is too big than POST can handle. Show message                    $this->admin->json_response = [                        'status'  => 'error',                        'message' => $message                    ];                    return false;                }                $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'), 'error');                $this->admin->json_response = [                    'status'  => 'error',                    'message' => $this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')                ];                return false;            }            unset($this->post['admin-nonce']);        } else {            if ($this->task === 'logout') {                $nonce = $this->grav['uri']->param('logout-nonce');                if (null === $nonce || !Utils::verifyNonce($nonce, 'logout-form')) {                    $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),                        'error');                    $this->admin->json_response = [                        'status'  => 'error',                        'message' => $this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')                    ];                    return false;                }            } else {                $nonce = $this->grav['uri']->param('admin-nonce');                if (null === $nonce || !Utils::verifyNonce($nonce, 'admin-form')) {                    $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN'),                        'error');                    $this->admin->json_response = [                        'status'  => 'error',                        'message' => $this->admin::translate('PLUGIN_ADMIN.INVALID_SECURITY_TOKEN')                    ];                    return false;                }            }        }        return true;    }    /**     * Sets the page redirect.     *     * @param string $path The path to redirect to     * @param int    $code The HTTP redirect code     */    public function setRedirect($path, $code = 303)    {        $this->redirect     = $path;        $this->redirectCode = $code;    }    /**     * Sends JSON response and terminates the call.     *     * @param array $response     * @param int $code     * @return bool     */    protected function sendJsonResponse(array $response, $code = 200)    {        // Make sure nothing extra gets written to the response.        while (ob_get_level()) {            ob_end_clean();        }        // JSON response.        http_response_code($code);        header('Content-Type: application/json');        header('Cache-Control: no-cache, no-store, must-revalidate');        echo json_encode($response);        exit();    }    /**     * Handles ajax upload for files.     * Stores in a flash object the temporary file and deals with potential file errors.     *     * @return bool True if the action was performed.     */    public function taskFilesUpload()    {        if (null === $_FILES || !$this->authorizeTask('save', $this->dataPermissions())) {            return false;        }        /** @var Config $config */        $config   = $this->grav['config'];        $data     = $this->view === 'pages' ? $this->admin->page(true) : $this->prepareData([]);        $settings = $data->blueprints()->schema()->getProperty($this->post['name']);        $settings = (object)array_merge([            'avoid_overwriting' => false,            'random_name'       => false,            'accept'            => ['image/*'],            'limit'             => 10,            'filesize'          => Utils::getUploadLimit()        ], (array)$settings, ['name' => $this->post['name']]);        $upload = $this->normalizeFiles($_FILES['data'], $settings->name);        $filename = $upload->file->name;        // Handle bad filenames.        if (!Utils::checkFilename($filename)) {            $this->admin->json_response = [                'status'  => 'error',                'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),                    $filename, 'Bad filename')            ];            return false;        }        if (!isset($settings->destination)) {            $this->admin->json_response = [                'status'  => 'error',                'message' => $this->admin::translate('PLUGIN_ADMIN.DESTINATION_NOT_SPECIFIED', null)            ];            return false;        }        // Do not use self@ outside of pages        if ($this->view !== 'pages' && in_array($settings->destination, ['@self', 'self@', '@self@'])) {            $this->admin->json_response = [                'status'  => 'error',                'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null),                    $settings->destination)            ];            return false;        }        // Handle errors and breaks without proceeding further        if ($upload->file->error !== UPLOAD_ERR_OK) {            $this->admin->json_response = [                'status'  => 'error',                'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_UPLOAD', null),                    $filename, $this->upload_errors[$upload->file->error])            ];            return false;        }        // Handle file size limits        $settings->filesize *= 1048576; // 2^20 [MB in Bytes]        if ($settings->filesize > 0 && $upload->file->size > $settings->filesize) {            $this->admin->json_response = [                'status'  => 'error',                'message' => $this->admin::translate('PLUGIN_ADMIN.EXCEEDED_GRAV_FILESIZE_LIMIT')            ];            return false;        }        // Handle Accepted file types        // Accept can only be mime types (image/png | image/*) or file extensions (.pdf|.jpg)        $accepted = false;        $errors   = [];        // Do not trust mimetype sent by the browser        $mime = Utils::getMimeByFilename($filename);        foreach ((array)$settings->accept as $type) {            // Force acceptance of any file when star notation            if ($type === '*') {                $accepted = true;                break;            }            $isMime = strstr($type, '/');            $find   = str_replace(['.', '*', '+'], ['\.', '.*', '\+'], $type);            if ($isMime) {                $match = preg_match('#' . $find . '$#', $mime);                if (!$match) {                    $errors[] = 'The MIME type "' . $mime . '" for the file "' . $filename . '" is not an accepted.';                } else {                    $accepted = true;                    break;                }            } else {                $match = preg_match('#' . $find . '$#', $filename);                if (!$match) {                    $errors[] = 'The File Extension for the file "' . $filename . '" is not an accepted.';                } else {                    $accepted = true;                    break;                }            }        }        if (!$accepted) {            $this->admin->json_response = [                'status'  => 'error',                'message' => implode('<br />', $errors)            ];            return false;        }        // Remove the error object to avoid storing it        unset($upload->file->error);        // we need to move the file at this stage or else        // it won't be available upon save later on        // since php removes it from the upload location        $tmp_dir  = Admin::getTempDir();        $tmp_file = $upload->file->tmp_name;        $tmp      = $tmp_dir . '/uploaded-files/' . basename($tmp_file);        Folder::create(dirname($tmp));        if (!move_uploaded_file($tmp_file, $tmp)) {            $this->admin->json_response = [                'status'  => 'error',                'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_MOVE', null), '',                    $tmp)            ];            return false;        }        $upload->file->tmp_name = $tmp;        // Retrieve the current session of the uploaded files for the field        // and initialize it if it doesn't exist        $sessionField = base64_encode($this->grav['uri']->url());        $flash        = $this->admin->session()->getFlashObject('files-upload');        if (!$flash) {            $flash = [];        }        if (!isset($flash[$sessionField])) {            $flash[$sessionField] = [];        }        if (!isset($flash[$sessionField][$upload->field])) {            $flash[$sessionField][$upload->field] = [];        }        // Set destination        if ($this->grav['locator']->isStream($settings->destination)) {            $destination = $this->grav['locator']->findResource($settings->destination, false, true);        } else {            $destination = Folder::getRelativePath(rtrim($settings->destination, '/'));            $destination = $this->admin->getPagePathFromToken($destination);        }        // Create destination if needed        if (!is_dir($destination)) {            Folder::mkdir($destination);        }        // Generate random name if required        if ($settings->random_name) { // TODO: document            $extension          = pathinfo($upload->file->name, PATHINFO_EXTENSION);            $upload->file->name = Utils::generateRandomString(15) . '.' . $extension;        }        // Handle conflicting name if needed        if ($settings->avoid_overwriting) { // TODO: document            if (file_exists($destination . '/' . $upload->file->name)) {                $upload->file->name = date('YmdHis') . '-' . $upload->file->name;            }        }        // Prepare object for later save        $path               = $destination . '/' . $upload->file->name;        $upload->file->path = $path;        // $upload->file->route = $page ? $path : null;        // Prepare data to be saved later        $flash[$sessionField][$upload->field][$path] = (array)$upload->file;        // Finally store the new uploaded file in the field session        $this->admin->session()->setFlashObject('files-upload', $flash);        $this->admin->json_response = [            'status'  => 'success',            'session' => \json_encode([                'sessionField' => base64_encode($this->grav['uri']->url()),                'path'         => $upload->file->path,                'field'        => $settings->name            ])        ];        return true;    }    /**     * Checks if the user is allowed to perform the given task with its associated permissions     *     * @param string $task        The task to execute     * @param array  $permissions The permissions given     *     * @return bool True if authorized. False if not.     */    public function authorizeTask($task = '', $permissions = [])    {        if (!$this->admin->authorize($permissions)) {            if ($this->grav['uri']->extension() === 'json') {                $this->admin->json_response = [                    'status'  => 'unauthorized',                    'message' => $this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' ' . $task . '.'                ];            } else {                $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.INSUFFICIENT_PERMISSIONS_FOR_TASK') . ' ' . $task . '.',                    'error');            }            return false;        }        return true;    }    /**     * Gets the permissions needed to access a given view     *     * @return array An array of permissions     */    protected function dataPermissions()    {        $type        = $this->view;        $permissions = ['admin.super'];        switch ($type) {            case 'configuration':            case 'config':            case 'system':                $permissions[] = 'admin.configuration';                break;            case 'settings':            case 'site':                $permissions[] = 'admin.settings';                break;            case 'plugins':                $permissions[] = 'admin.plugins';                break;            case 'themes':                $permissions[] = 'admin.themes';                break;            case 'users':                $permissions[] = 'admin.users';                break;            case 'user':                $permissions[] = 'admin.login';                $permissions[] = 'admin.users';                break;            case 'pages':                $permissions[] = 'admin.pages';                break;        }        return $permissions;    }    /**     * Gets the configuration data for a given view & post     *     * @param array $data     *     * @return array     */    protected function prepareData(array $data)    {        return $data;    }    /**     * Internal method to normalize the $_FILES array     *     * @param array  $data $_FILES starting point data     * @param string $key     *     * @return object a new Object with a normalized list of files     */    protected function normalizeFiles($data, $key = '')    {        $files        = new \stdClass();        $files->field = $key;        $files->file  = new \stdClass();        foreach ($data as $fieldName => $fieldValue) {            // Since Files Upload are always happening via Ajax            // we are not interested in handling `multiple="true"`            // because they are always handled one at a time.            // For this reason we normalize the value to string,            // in case it is arriving as an array.            $value                     = (array)Utils::getDotNotation($fieldValue, $key);            $files->file->{$fieldName} = array_shift($value);        }        return $files;    }    /**     * Removes a file from the flash object session, before it gets saved     *     * @return bool True if the action was performed.     */    public function taskFilesSessionRemove()    {        if (!$this->authorizeTask('save', $this->dataPermissions())) {            return false;        }        // Retrieve the current session of the uploaded files for the field        // and initialize it if it doesn't exist        $sessionField = base64_encode($this->grav['uri']->url());        $request      = \json_decode($this->post['session']);        // Ensure the URI requested matches the current one, otherwise fail        if ($request->sessionField !== $sessionField) {            return false;        }        // Retrieve the flash object and remove the requested file from it        $flash    = $this->admin->session()->getFlashObject('files-upload');        $endpoint = $flash[$request->sessionField][$request->field][$request->path];        if (isset($endpoint)) {            if (file_exists($endpoint['tmp_name'])) {                unlink($endpoint['tmp_name']);            }            unset($endpoint);        }        // Walk backward to cleanup any empty field that's left        // Field        if (isset($flash[$request->sessionField][$request->field][$request->path])) {            unset($flash[$request->sessionField][$request->field][$request->path]);        }        // Field        if (isset($flash[$request->sessionField][$request->field]) && empty($flash[$request->sessionField][$request->field])) {            unset($flash[$request->sessionField][$request->field]);        }        // Session Field        if (isset($flash[$request->sessionField]) && empty($flash[$request->sessionField])) {            unset($flash[$request->sessionField]);        }        // If there's anything left to restore in the flash object, do so        if (count($flash)) {            $this->admin->session()->setFlashObject('files-upload', $flash);        }        $this->admin->json_response = ['status' => 'success'];        return true;    }    /**     * Redirect to the route stored in $this->redirect     */    public function redirect()    {        if (!$this->redirect) {            return;        }        $base           = $this->admin->base;        $this->redirect = '/' . ltrim($this->redirect, '/');        $multilang      = $this->isMultilang();        $redirect = '';        if ($multilang) {            // if base path does not already contain the lang code, add it            $langPrefix = '/' . $this->grav['session']->admin_lang;            if (!Utils::startsWith($base, $langPrefix . '/')) {                $base = $langPrefix . $base;            }            // now the first 4 chars of base contain the lang code.            // if redirect path already contains the lang code, and is != than the base lang code, then use redirect path as-is            if (Utils::pathPrefixedByLangCode($base) && Utils::pathPrefixedByLangCode($this->redirect)                && !Utils::startsWith($this->redirect, $base)            ) {                $redirect = $this->redirect;            } else {                if (!Utils::startsWith($this->redirect, $base)) {                    $this->redirect = $base . $this->redirect;                }            }        } else {            if (!Utils::startsWith($this->redirect, $base)) {                $this->redirect = $base . $this->redirect;            }        }        if (!$redirect) {            $redirect = $this->redirect;        }        $this->grav->redirect($redirect, $this->redirectCode);    }    /**     * Prepare and return POST data.     *     * @param array $post     *     * @return array     */    protected function getPost($post)    {        if (!is_array($post)) {            return [];        }        unset($post['task']);        // Decode JSON encoded fields and merge them to data.        if (isset($post['_json'])) {            $post = array_replace_recursive($post, $this->jsonDecode($post['_json']));            unset($post['_json']);        }        $post = $this->cleanDataKeys($post);        return $post;    }    /**     * Recursively JSON decode data.     *     * @param  array $data     *     * @return array     */    protected function jsonDecode(array $data)    {        foreach ($data as &$value) {            if (is_array($value)) {                $value = $this->jsonDecode($value);            } else {                $value = json_decode($value, true);            }        }        return $data;    }    protected function cleanDataKeys($source = [])    {        $out = [];        if (is_array($source)) {            foreach ($source as $key => $value) {                $key = str_replace(['%5B', '%5D'], ['[', ']'], $key);                if (is_array($value)) {                    $out[$key] = $this->cleanDataKeys($value);                } else {                    $out[$key] = $value;                }            }        }        return $out;    }    /**     * Return true if multilang is active     *     * @return bool True if multilang is active     */    protected function isMultilang()    {        return count($this->grav['config']->get('system.languages.supported', [])) > 1;    }    /**     * @param PageInterface|UserInterface|Data $obj     *     * @return PageInterface|UserInterface|Data     */    protected function storeFiles($obj)    {        // Process previously uploaded files for the current URI        // and finally store them. Everything else will get discarded        $queue = $this->admin->session()->getFlashObject('files-upload');        if (is_array($queue)) {            $queue = $queue[base64_encode($this->grav['uri']->url())];            foreach ($queue as $key => $files) {                foreach ($files as $destination => $file) {                    if (!rename($file['tmp_name'], $destination)) {                        throw new \RuntimeException(sprintf($this->admin->translate('PLUGIN_ADMIN.FILEUPLOAD_UNABLE_TO_MOVE',                            null), '"' . $file['tmp_name'] . '"', $destination));                    }                    unset($files[$destination]['tmp_name']);                }                if ($this->view === 'pages') {                    $keys     = explode('.', preg_replace('/^header./', '', $key));                    $init_key = array_shift($keys);                    if (count($keys) > 0) {                        $new_data = $obj->header()->{$init_key} ?? [];                        Utils::setDotNotation($new_data, implode('.', $keys), $files, true);                    } else {                        $new_data = $files;                    }                    if (isset($obj->header()->{$init_key})) {                        $obj->modifyHeader($init_key,                            array_replace_recursive([], $obj->header()->{$init_key}, $new_data));                    } else {                        $obj->modifyHeader($init_key, $new_data);                    }                } elseif ($obj instanceof UserInterface and $key === 'avatar') {                    $obj->set($key, $files);                } else {                    // TODO: [this is JS handled] if it's single file, remove existing and use set, if it's multiple, use join                    $obj->join($key, $files); // stores                }            }        }        return $obj;    }    /**     * Used by the filepicker field to get a list of files in a folder.     */    protected function taskGetFilesInFolder()    {        if (!$this->authorizeTask('save', $this->dataPermissions())) {            return false;        }        $data = $this->view === 'pages' ? $this->admin->page(true) : $this->prepareData([]);        if (null === $data) {            return false;        }        if (method_exists($data, 'blueprints')) {            $settings = $data->blueprints()->schema()->getProperty($this->post['name']);        } elseif (method_exists($data, 'getBlueprint')) {            $settings = $data->getBlueprint()->schema()->getProperty($this->post['name']);        }        if (isset($settings['folder'])) {            $folder = $settings['folder'];        } else {            $folder = 'self@';        }        // Do not use self@ outside of pages        if ($this->view !== 'pages' && in_array($folder, ['@self', 'self@', '@self@'])) {            if (!$data instanceof MediaInterface) {                $this->admin->json_response = [                    'status'  => 'error',                    'message' => sprintf($this->admin::translate('PLUGIN_ADMIN.FILEUPLOAD_PREVENT_SELF', null), $folder)                ];                return false;            }            $media = $data->getMedia();        } else {            // Set destination            $folder = Folder::getRelativePath(rtrim($folder, '/'));            $folder = $this->admin->getPagePathFromToken($folder);            $media = new Media($folder);        }        $available_files = [];        $metadata = [];        $thumbs = [];        foreach ($media->all() as $name => $medium) {           $available_files[] = $name;            if (isset($settings['include_metadata'])) {                $img_metadata = $medium->metadata();                if ($img_metadata) {                    $metadata[$name] = $img_metadata;                }            }        }        // Peak in the flashObject for optimistic filepicker updates        $pending_files = [];        $sessionField  = base64_encode($this->grav['uri']->url());        $flash         = $this->admin->session()->getFlashObject('files-upload');        if ($flash && isset($flash[$sessionField])) {            foreach ($flash[$sessionField] as $field => $data) {                foreach ($data as $file) {                    if (dirname($file['path']) === $folder) {                        $pending_files[] = $file['name'];                    }                }            }        }        $this->admin->session()->setFlashObject('files-upload', $flash);        // Handle Accepted file types        // Accept can only be file extensions (.pdf|.jpg)        if (isset($settings['accept'])) {            $available_files = array_filter($available_files, function ($file) use ($settings) {                return $this->filterAcceptedFiles($file, $settings);            });            $pending_files = array_filter($pending_files, function ($file) use ($settings) {                return $this->filterAcceptedFiles($file, $settings);            });        }        // Generate thumbs if needed        if (isset($settings['preview_images']) && $settings['preview_images'] === true) {            foreach ($available_files as $filename) {                $thumbs[$filename] = $media[$filename]->zoomCrop(100,100)->url();            }        }        $this->admin->json_response = [            'status'  => 'success',            'files'   => array_values($available_files),            'pending' => array_values($pending_files),            'folder'  => $folder,            'metadata' => $metadata,            'thumbs' => $thumbs        ];        return true;    }    protected function filterAcceptedFiles($file, $settings)    {        $valid = false;        foreach ((array)$settings['accept'] as $type) {            $find = str_replace('*', '.*', $type);            $valid |= preg_match('#' . $find . '$#', $file);        }        return $valid;    }    /**     * Handle deleting a file from a blueprint     *     * @return bool True if the action was performed.     */    protected function taskRemoveFileFromBlueprint()    {        /** @var Uri $uri */        $uri       = $this->grav['uri'];        $blueprint = base64_decode($uri->param('blueprint'));        $path      = base64_decode($uri->param('path'));        $filename  = basename($this->post['filename'] ?? '');        $proute    = base64_decode($uri->param('proute'));        $type      = $uri->param('type');        $field     = $uri->param('field');        if ($filename === '') {           $this->admin->json_response = [                'status'  => 'error',                'message' => 'Filename is empty'            ];            return false;        }        // Get Blueprint        if ($type === 'pages' || strpos($blueprint, 'pages/') === 0) {            $page = $this->admin->page(true, $proute);            if (!$page) {                $this->admin->json_response = [                    'status'  => 'error',                    'message' => 'Page not found'                ];                return false;            }            $blueprints = $page->blueprints();            $path = Folder::getRelativePath($page->path());            $settings = (object)$blueprints->schema()->getProperty($field);        } else {            $page = null;            if ($type === 'themes' || $type === 'plugins') {                $obj = $this->grav[$type]->get(Utils::substrToString($blueprint, '/')); //here                $settings = (object) $obj->blueprints()->schema()->getProperty($field);            } else {                $settings = (object)$this->admin->blueprints($blueprint)->schema()->getProperty($field);            }        }        // Get destination        if ($this->grav['locator']->isStream($settings->destination)) {            $destination = $this->grav['locator']->findResource($settings->destination, false, true);        } else {            $destination = Folder::getRelativePath(rtrim($settings->destination, '/'));            $destination = $this->admin->getPagePathFromToken($destination, $page);        }        // Not in path        if (!Utils::startsWith($path, $destination)) {            $this->admin->json_response = [                'status'  => 'error',                'message' => 'Path not valid for this data type'            ];            return false;        }        // Only remove files from correct destination...        $this->taskRemoveMedia($destination . '/' . $filename);        if ($page) {            $keys      = explode('.', preg_replace('/^header./', '', $field));            $header    = (array)$page->header();            $data_path = implode('.', $keys);            $data      = Utils::getDotNotation($header, $data_path);            if (isset($data[$path])) {                unset($data[$path]);                Utils::setDotNotation($header, $data_path, $data);                $page->header($header);            }            $page->save();        } else {            $blueprint_prefix = $type === 'config' ? '' : $type . '.';            $blueprint_name   = str_replace(['config/', '/blueprints'], '', $blueprint);            $blueprint_field  = $blueprint_prefix . $blueprint_name . '.' . $field;            $files            = $this->grav['config']->get($blueprint_field);            if ($files) {                foreach ($files as $key => $value) {                    if ($key == $path) {                        unset($files[$key]);                    }                }            }            $this->grav['config']->set($blueprint_field, $files);            switch ($type) {                case 'config':                    $data   = $this->grav['config']->get($blueprint_name);                    $config = $this->admin->data($blueprint, $data);                    $config->save();                    break;                case 'themes':                    Theme::saveConfig($blueprint_name);                    break;                case 'plugins':                    Plugin::saveConfig($blueprint_name);                    break;            }        }        $this->admin->json_response = [            'status'  => 'success',            'message' => $this->admin::translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')        ];        return true;    }    /**     * Handles removing a media file     *     * @return bool True if the action was performed     */    public function taskRemoveMedia($filename = null)    {        if (!$this->canEditMedia()) {            return false;        }        if (null === $filename) {            $filename = base64_decode($this->grav['uri']->param('route'));            if (!$filename) {                $filename = base64_decode($this->route);            }        }        $file                  = File::instance($filename);        $resultRemoveMedia     = false;        if ($file->exists()) {            $resultRemoveMedia = $file->delete();            $fileParts = pathinfo($filename);            foreach (scandir($fileParts['dirname']) as $file) {                $regex_pattern = '/' . preg_quote($fileParts['filename'], '/') . "@\d+x\." . $fileParts['extension'] . "(?:\.meta\.yaml)?$|" . preg_quote($fileParts['basename'], '/') . "\.meta\.yaml$/";                if (preg_match($regex_pattern, $file)) {                    $path = $fileParts['dirname'] . '/' . $file;                    @unlink($path);                }            }        }        if ($resultRemoveMedia) {            if ($this->grav['uri']->extension() === 'json') {                $this->admin->json_response = [                    'status'  => 'success',                    'message' => $this->admin::translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL')                ];            } else {                $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.REMOVE_SUCCESSFUL'), 'info');                $this->clearMediaCache();                $this->setRedirect('/media-manager');            }            return true;        }        if ($this->grav['uri']->extension() === 'json') {            $this->admin->json_response = [                'status'  => 'success',                'message' => $this->admin::translate('PLUGIN_ADMIN.REMOVE_FAILED')            ];        } else {            $this->admin->setMessage($this->admin::translate('PLUGIN_ADMIN.REMOVE_FAILED'), 'error');        }        return false;    }    /**     * Handles clearing the media cache     *     * @return bool True if the action was performed     */    protected function clearMediaCache()    {        $key   = 'media-manager-files';        $cache = $this->grav['cache'];        $cache->delete(md5($key));        return true;    }    /**     * Determine if the user can edit media     *     * @param string $type     *     * @return bool True if the media action is allowed     */    protected function canEditMedia($type = 'media')    {        if (!$this->authorizeTask('edit media', ['admin.' . $type, 'admin.super'])) {            return false;        }        return true;    }}
 |