/** * Saves a site * * @param {string} $id the ID for the user * @return {Site} */ public function save() { $dir = app()->basePath() . '/resources/sites/' . $this->id . '/'; $json = json_encode($this, JSON_PRETTY_PRINT); // save site.json Utilities::saveContent($dir, 'site.json', $json); }
/** * Creates a token to reset the password for the user * * @return Response */ public function forgot(Request $request) { $email = $request->json()->get('email'); $id = $request->json()->get('id'); // get site $site = Site::getById($id); if ($site != NULL) { // get user $user = User::getByEmail($email, $site->id); if ($user != NULL) { $user->token = uniqid(); // save user $user->save($site->id); // send email $to = $user->email; $from = env('EMAILS_FROM'); $fromName = env('EMAILS_FROM_NAME'); $subject = env('BRAND') . ': Reset Password'; $file = app()->basePath() . '/resources/emails/reset-password.html'; // create strings to replace $resetUrl = env('APP_URL') . '/reset/' . $site->id . '/' . $user->token; $replace = array('{{brand}}' => env('BRAND'), '{{reply-to}}' => env('EMAILS_FROM'), '{{reset-url}}' => $resetUrl); // send email from file Utilities::sendEmailFromFile($to, $from, $fromName, $subject, $replace, $file); return response('OK', 200); } } return response('Unauthorized', 401); }
/** * Handles a standard form submit * * @return Response */ public function submit(Request $request) { // get referer $referer = $request->header('referer'); // get the site $siteId = $request->input('siteid'); $url = $referer; $formId = $request->input('formid'); $timestamp = gmdate('D M d Y H:i:s O', time()); // get all fields $all_fields = $request->all(); $fields = array(); // walk through form fields foreach ($all_fields as $key => $value) { if ($key != 'siteid' && $key != 'url' && $key != 'formid') { // push field array_push($fields, array('id' => $key, 'value' => $value)); } } // get name of $name = 'New Submission'; if (sizeof($fields) > 0) { $name = $fields[0]['value']; } $arr = array('id' => Utilities::getGUID(), 'name' => $name, 'url' => $url, 'formId' => $formId, 'date' => $timestamp, 'fields' => $fields); // create a submission from the json file $submission = new Submission($arr); // save the submission $submission->save($siteId); return redirect($referer . '#success'); }
/** * Lists files for the site * * @param {string} $id */ public static function ListFiles($id) { $dir = app()->basePath() . '/public/sites/' . $id . '/files'; // list allowed types $exts = explode(',', env('ALLOWED_FILETYPES')); // list files $arr = Utilities::ListFiles($dir, $id, $exts, array('files/thumb/', 'files/thumbs/')); return $arr; }
/** * Lists the routes for given site * * @return Response */ public function listRoutes(Request $request) { // get request data $email = $request->input('auth-email'); $id = $request->input('auth-id'); // get base path for the site $dir = $file = app()->basePath() . '/public/sites/' . $id; $arr = array_merge(array('/'), Utilities::listRoutes($dir, $id)); return response()->json($arr); }
/** * Lists the themes available for the app * * @return Response */ public function listThemes(Request $request) { // list pages in the site $dir = app()->basePath() . '/' . env('THEMES_LOCATION'); // list files $arr = Utilities::listSpecificFiles($dir, 'theme.json'); $result = array(); foreach ($arr as $item) { // get contents of file $json = json_decode(file_get_contents($item)); // get location of theme $temp = explode('public/themes/', $item); $location = substr($temp[1], 0, strpos($temp[1], '/theme.json')); $json->location = $location; array_push($result, $json); } return response()->json($result); }
/** * lists all files * * @param {files} $data * @return {array} */ public static function listAll($siteId) { $dir = app()->basePath() . '/public/sites/' . $siteId . '/data/menus/'; $exts = array('json'); $files = Utilities::listFiles($dir, $siteId, $exts); $arr = array(); foreach ($files as $file) { $path = app()->basePath() . '/public/sites/' . $siteId . '/' . $file; if (file_exists($path)) { $json = json_decode(file_get_contents($path), true); $id = str_replace('.json', '', $file); $id = str_replace('data/menus/', '', $id); $id = str_replace('/', '', $id); array_push($arr, array('id' => $id, 'name' => $json['name'])); } } return $arr; }
/** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $auth = $request->header('X-AUTH'); $token = NULL; if ($auth != NULL) { $token = Utilities::ValidateJWTToken($auth); if ($token != NULL) { // merge the userId, siteId and friendlyId into the request $request->merge(array('auth-email' => $token->email, 'auth-id' => $token->id)); } else { return response('Unauthorized.', 401); } } else { return response('Unauthorized.', 401); } // continue return $next($request); }
/** * Publishes plugins for the site * * @param {Site} $site */ public static function publishPlugins($user, $site) { // get plugins for the site $dir = app()->basePath() . '/public/sites/' . $site->id . '/plugins/'; $exts = array('html'); $files = Utilities::listFiles($dir, $site->id, $exts); $plugins = array(); foreach ($files as $file) { $path = app()->basePath() . '/public/sites/' . $site->id . '/' . $file; if (file_exists($path)) { $html = file_get_contents($path); $id = basename($path); $id = str_replace('.html', '', $id); // push plugin to array array_push($plugins, $id); } } // location where twig should look for templates (local to site, then global) $template_dirs = array(app()->basePath() . '/public/sites/' . $site->id . '/plugins'); $global_plugin_dir = app()->basePath() . '/resources/plugins'; if (file_exists($global_plugin_dir)) { array_push($template_dirs, $global_plugin_dir); } // setup twig $loader = new \Twig_Loader_Filesystem($template_dirs); $twig = new \Twig_Environment($loader); $twig->addExtension(new BetterSortTwigExtension()); // get all pages $pages = Page::listAll($user, $site); // list all forms, menus, galleries $forms = Form::listExtended($site->id); $menus = Menu::listExtended($site->id); $galleries = Gallery::listExtended($site->id); $i = 0; // get html of pages foreach ($pages as $page) { // stript html $url = $page['url']; $url = preg_replace('/\\.[^.\\s]{3,4}$/', '', $url); // get html of page $file = app()->basePath() . '/public/sites/' . $site->id . '/' . $url . '.html'; if (file_exists($file)) { $html = file_get_contents($file); // set parser $dom = HtmlDomParser::str_get_html($html, $lowercase = true, $forceTagsClosed = false, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN = false, $defaultBRText = DEFAULT_BR_TEXT, $defaultSpanText = DEFAULT_SPAN_TEXT); // find main content $el = $dom->find('[role=main]'); $main_content = ''; // get the fragment content if (isset($el[0])) { $main_content = $el[0]->innertext; } // set html $pages[$i]['html'] = $main_content; } $i++; } $i = 0; // public plugin for pages foreach ($pages as $item) { // get page $page = new Page($item); // setup current page $current_page = array('url' => $page->url, 'title' => $page->title, 'description' => $page->description, 'keywords' => $page->keywords, 'callout' => $page->callout, 'photo' => $page->photo, 'thumb' => $page->thumb, 'language' => $page->language, 'direction' => $page->direction, 'firstName' => $page->firstName, 'lastName' => $page->lastName, 'lastModifiedBy' => $page->lastModifiedBy, 'lastModifiedDate' => $page->lastModifiedDate); // setup whether the site is using friendly urls $useFriendlyURLs = false; if (env('FRIENDLY_URLS') === true || env('FRIENDLY_URLS') === 'true') { $useFriendlyURLs = true; } // setup current site $current_site = array('id' => $site->id, 'name' => $site->name, 'email' => $site->email, 'api' => Utilities::retrieveAppUrl() . '/api', 'useFriendlyURLs' => $useFriendlyURLs); // set url $url = $page->url; $url = preg_replace('/\\.[^.\\s]{3,4}$/', '', $url); $location = app()->basePath() . '/public/sites/' . $site->id . '/' . $url . '.html'; // check for valid location if (file_exists($location)) { // get html from page $html = file_get_contents($location); // walk through plugins foreach ($plugins as $plugin) { // insert into respond-plugin comments $start = '<!-- respond-plugin:' . $plugin . ' -->'; $end = '<!-- /respond-plugin:' . $plugin . ' -->'; // check for start and end if (strpos($html, $start) !== FALSE && strpos($html, $end) !== FALSE) { // load the template $template = $twig->loadTemplate($plugin . '.html'); // render the template $plugin_html = $template->render(array('pages' => $pages)); // replace content $html = Utilities::replaceBetween($html, $start, $end, $plugin_html); } } // make sure the html is not empty if (!empty($html)) { // load the parser $dom = HtmlDomParser::str_get_html($html, $lowercase = true, $forceTagsClosed = false, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN = false, $defaultBRText = DEFAULT_BR_TEXT, $defaultSpanText = DEFAULT_SPAN_TEXT); // insert into [respond-plugin] elements foreach ($dom->find('[respond-plugin]') as $el) { if (isset($el->type)) { if (array_search($el->type, $plugins) !== FALSE) { // load the template $template = $twig->loadTemplate($el->type . '.html'); $render_arr = array('page' => $current_page, 'site' => $current_site, 'pages' => $pages, 'forms' => $forms, 'galleries' => $galleries, 'menus' => $menus, 'attributes' => $el->attr); // render the template $plugin_html = $template->render($render_arr); // set the inner text $el->innertext = $plugin_html; } } } } // find main content $el = $dom->find('[role=main]'); $main_content = ''; // get the fragment content if (isset($el[0])) { $main_content = $el[0]->innertext; } // put html back file_put_contents($location, $dom); // update html in the array $pages[$i]['html'] = $main_content; // increment $i++; } } }
/** * Edits a page provided by the querystring, in format ?q=site-name/dir/page.html * * @return Response */ public function edit(Request $request) { $q = $request->input('q'); if ($q != NULL) { $arr = explode('/', $q); if (sizeof($arr) > 0) { $siteId = $arr[0]; // set html if hiddne $url = $q; // strip any trailing .html from url $url = preg_replace('/\\.[^.\\s]{3,4}$/', '', $url); // add .html for non-friendly URLs if (env('FRIENDLY_URLS') === false) { $url .= '.html'; } // load page $path = rtrim(app()->basePath('public/sites/' . $url), '/'); if (file_exists($path)) { $html = file_get_contents($path); // set dom $dom = HtmlDomParser::str_get_html($html, $lowercase = true, $forceTagsClosed = false, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN = false, $defaultBRText = DEFAULT_BR_TEXT, $defaultSpanText = DEFAULT_SPAN_TEXT); // find base element $el = $dom->find('base', 0); $el->setAttribute('href', '/sites/' . $siteId . '/'); // get settings $sortable = Setting::getById('sortable', $siteId); $editable = Setting::getById('editable', $siteId); $blocks = Setting::getById('blocks', $siteId); $grid = Setting::getById('grid', $siteId); $framework = Setting::getById('framework', $siteId); // framework if ($framework === NULL) { $framework = 'bootstrap'; } // defaults if ($grid === NULL) { $grid = '[{"name": "1 Column","desc": "100%","html": "<div class=\\"block row\\" hashedit-block><div class=\\"col col-md-12\\" hashedit-sortable></div></div>"}]'; } else { $grid = json_encode($grid); } // defaults if ($sortable === NULL) { $sortable = '.col, .column'; } // get editable array if ($editable === NULL) { $editable = ['[role=main]']; } else { $editable = explode(',', $editable); // trim elements in the array $editable = array_map('trim', $editable); } if ($blocks === NULL) { $blocks = '.row'; } // find body element $el = $dom->find('body', 0); $el->setAttribute('hashedit-active', ''); // setup editable areas foreach ($editable as $value) { // find body element $els = $dom->find($value); foreach ($els as $el) { $el->setAttribute('hashedit', ''); $el->setAttribute('hashedit-selector', $value); } } // init $plugins_script = ''; // get custom plugins $js_file = app()->basePath() . '/resources/sites/' . $siteId . '/plugins.js'; if (file_exists($js_file)) { if (file_exists($js_file)) { $plugins_script .= file_get_contents($js_file); } } // inject forms into script if (strpos($plugins_script, 'respond.forms') !== false) { $arr = Form::listAll($siteId); $options = array(); // get id foreach ($arr as $item) { array_push($options, array('text' => $item['name'], 'value' => $item['id'])); } // inject forms into script $plugins_script = str_replace("['respond.forms']", json_encode($options), $plugins_script); } // inject galleries into script if (strpos($plugins_script, 'respond.galleries') !== false) { $arr = Gallery::listAll($siteId); $options = array(); // get id foreach ($arr as $item) { array_push($options, array('text' => $item['name'], 'value' => $item['id'])); } // inject galleries into script $plugins_script = str_replace("['respond.galleries']", json_encode($options), $plugins_script); } // inject routes into script if (strpos($plugins_script, 'respond.routes') !== false) { $dir = $file = app()->basePath() . '/public/sites/' . $siteId; $arr = array_merge(array('/'), Utilities::listRoutes($dir, $siteId)); $options = array(); // get id foreach ($arr as $item) { array_push($options, array('text' => $item, 'value' => $item)); } // inject galleries into script $plugins_script = str_replace("['respond.routes']", json_encode($options), $plugins_script); } // inject pages into script if (strpos($plugins_script, 'respond.pages') !== false) { $arr = Pages::listAllBySite($siteId); $options = array(); // get id foreach ($arr as $item) { array_push($options, array('text' => $item['title'], 'value' => $item['url'])); } // inject galleries into script $plugins_script = str_replace("['respond.galleries']", json_encode($options), $plugins_script); } // remove elements from that have been excluded $els = $dom->find('[hashedit-exclude]'); // add references to each element foreach ($els as $el) { $el->outertext = ''; } // setup references $parent = $dom->find('[role=main]', 0); if (env('APP_ENV') == 'development') { // hashedit development stack $hashedit = <<<EOD <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet" type="text/css"> <script src="/dev/hashedit/js/fetch.min.js"></script> <script src="/dev/hashedit/js/i18next.js"></script> <script src="/dev/hashedit/node_modules/sortablejs/Sortable.js"></script> <script src="/dev/hashedit/node_modules/dropzone/dist/dropzone.js"></script> <script src="/dev/hashedit/js/hashedit.js"></script> <script>{$plugins_script}</script> <script> hashedit.setup({ dev: true, url: '{$url}', sortable: '{$sortable}', blocks: '{$blocks}', grid: {$grid}, framework: '{$framework}', login: '******', translate: true, languagePath: '/i18n/{{language}}.json', auth: 'token', authHeader: 'X-AUTH' }); </script> EOD; } else { // hashedit production stack $hashedit = <<<EOD <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700" rel="stylesheet" type="text/css"> <script src="/app/libs/hashedit/dist/hashedit-min.js"></script> <script>{$plugins_script}</script> <script> hashedit.setup({ url: '{$url}', sortable: '{$sortable}', blocks: '{$blocks}', grid: {$grid}, framework: '{$framework}', login: '******', path: '/app/libs/hashedit/', stylesheet: ['/app/libs/hashedit/dist/hashedit-min.css'], translate: true, languagePath: '/i18n/{{language}}.json', auth: 'token', authHeader: 'X-AUTH' }); </script> EOD; } $hashedit .= '<style type="text/css">' . '.respond-plugin {' . ' position: relative;' . ' padding: 10px 0;' . ' margin: 1px 0 20px 0;' . ' background-color: #f8f8f8;' . ' border: 1px solid #f0f0f0;' . ' text-align: center;' . ' color: #aaa;' . '}' . '[hashedit-element]:hover .respond-plugin { border: 1px solid #00ADE3 !important; }' . '.respond-plugin span {' . ' display: block;' . ' margin: 0; padding: 0;' . ' color: #aaa;' . ' text-align: center;' . ' text-transform: uppercase;' . ' font-size: 11px;' . ' font-family: "Open Sans", sans-serif;' . '}' . '.respond-plugin svg{' . ' fill: currentColor;' . ' width: 35px;' . ' height: 35px;' . '}'; // find body element $el = $dom->find('body', 0); // append $el->outertext = $el->makeup() . $el->innertext . $hashedit . '</body>'; // return updated html return $dom; } } } }
/** * Creates a thumbnail * * @param {Site} $site * @param {string} $filename * @param {string} $image path to the image * @return {array} */ public static function createThumb($site, $image, $filename) { $dir = app()->basePath() . '/public/sites/' . $site->id . '/files/thumbs'; // set thumb size $target_w = env('THUMB_MAX_WIDTH'); $target_h = env('THUMB_MAX_HEIGHT'); list($curr_w, $curr_h, $type, $attr) = Utilities::getImageInfo($image); $ext = 'jpg'; switch ($type) { // create image case IMAGETYPE_JPEG: $ext = 'jpg'; break; case IMAGETYPE_PNG: $ext = 'png'; break; case IMAGETYPE_GIF: $ext = 'gif'; break; case 'image/svg+xml': $ext = 'svg'; break; default: return false; } $scale_h = $target_h / $curr_h; $scale_w = $target_w / $curr_w; $factor_x = $curr_w / $target_w; $factor_y = $curr_h / $target_h; if ($factor_x > $factor_y) { $factor = $factor_y; } else { $factor = $factor_x; } $up_w = ceil($target_w * $factor); $up_h = ceil($target_h * $factor); $x_start = ceil(($curr_w - $up_w) / 2); $y_start = ceil(($curr_h - $up_h) / 2); switch ($type) { // create image case IMAGETYPE_JPEG: $n_img = imagecreatefromjpeg($image); break; case IMAGETYPE_PNG: $n_img = imagecreatefrompng($image); break; case IMAGETYPE_GIF: $n_img = imagecreatefromgif($image); break; case 'image/svg+xml': break; default: return false; } $dst_img = ImageCreateTrueColor($target_w, $target_h); switch ($type) { // fix for transparency issues case IMAGETYPE_PNG: imagealphablending($dst_img, true); imagesavealpha($dst_img, true); $transparent_color = imagecolorallocatealpha($dst_img, 0, 0, 0, 127); imagefill($dst_img, 0, 0, $transparent_color); break; case IMAGETYPE_GIF: $transparency_index = imagecolortransparent($dst_img); if ($transparency_index >= 0) { $transparent_color = imagecolorsforindex($dst_img, $transparency_index); $transparency_index = imagecolorallocate($dst_img, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']); imagefill($dst_img, 0, 0, $transparency_index); imagecolortransparent($dst_img, $transparency_index); } break; default: break; } if ($type != 'image/svg+xml') { imagecopyresampled($dst_img, $n_img, 0, 0, $x_start, $y_start, $target_w, $target_h, $up_w, $up_h); } //return $dst_img; $full = $dir . '/' . $filename; if (!file_exists($dir)) { mkdir($dir, 0777, true); } switch ($ext) { case 'jpg': imagejpeg($dst_img, $full, 100); break; case 'png': // save file locally imagepng($dst_img, $full); break; case 'gif': // save file locally imagegif($dst_img, $full); break; case 'svg': // save file locally copy($image, $full); break; default: return false; return true; } }
/** * Refreshes the page JSON * * @param {User} $user * @param {string} $id friendly id of site (e.g. site-name) * @return Response */ public static function refreshJSON($user, $site) { // get base path for the site $json_file = app()->basePath() . '/public/sites/' . $site->id . '/data/pages.json'; // set dir $dir = app()->basePath() . '/public/sites/' . $site->id; // list files $files = Utilities::ListFiles($dir, $site->id, array('html'), array('plugins/', 'components/', 'css/', 'data/', 'files/', 'js/', 'locales/', 'fragments/', 'themes/')); // setup arrays to hold data $arr = array(); foreach ($files as $file) { // defaults $title = ''; $description = ''; $keywords = ''; $callout = ''; $url = $file; $text = ''; $html = ''; $language = 'en'; $direction = 'ltr'; $photo = ''; $thumb = ''; $lastModifiedDate = date('Y-m-d\\TH:i:s.Z\\Z', time()); // set full file path $file = app()->basePath() . '/public/sites/' . $site->id . '/' . $file; $file_modified_time = filemtime($file); // setup timestamp as JS date $timestamp = date('Y-m-d\\TH:i:s.Z\\Z', $file_modified_time); // set parser $dom = HtmlDomParser::str_get_html(file_get_contents($file), $lowercase = true, $forceTagsClosed = false, $target_charset = DEFAULT_TARGET_CHARSET, $stripRN = false, $defaultBRText = DEFAULT_BR_TEXT, $defaultSpanText = DEFAULT_SPAN_TEXT); // get title $els = $dom->find('title'); if (isset($els[0])) { $title = $els[0]->innertext; } // get els $els = $dom->find('body'); // set timestamp in head if (isset($els[0])) { $lastModifiedDate = $els[0]->getAttribute('data-lastmodified'); } // get description $els = $dom->find('meta[name=description]'); if (isset($els[0])) { $description = $els[0]->content; } // get keywords $els = $dom->find('meta[name=keywords]'); if (isset($els[0])) { $keywords = $els[0]->content; } // get text $els = $dom->find('[role=main]'); if (isset($els[0])) { $main_content = $els[0]->innertext; } // get the text from the content $text = strip_tags($main_content); $text = preg_replace("/\\s+/", " ", $text); $text = trim($text); $text = preg_replace('/[[:^print:]]/', '', $text); // get photo $photos = $dom->find('[role=main] img'); if (isset($photos[0])) { $photo = $photos[0]->src; } $thumb = ''; if ($photo === NULL || $photo === '') { $photo = ''; } else { if (substr($photo, 0, 4) === "http") { $thumb = $photo; } else { $thumb = str_replace('files/', 'files/thumbs/', $photo); $thumb = str_replace('thumbs/thumbs', 'thumbs/', $thumb); } } // get language and direction $els = $dom->find('html'); if (isset($els[0])) { $language = $els[0]->lang; $direction = $els[0]->dir; } // cleanup url $url = ltrim($url, '/'); // strip any trailing .html from url $url = preg_replace('/\\.[^.\\s]{3,4}$/', '', $url); // setup data $data = array('title' => $title, 'description' => $description, 'text' => $text, 'keywords' => $keywords, 'callout' => $callout, 'url' => $url, 'photo' => $photo, 'thumb' => $thumb, 'language' => $language, 'direction' => $direction, 'firstName' => $user->firstName, 'lastName' => $user->lastName, 'lastModifiedBy' => $user->email, 'lastModifiedDate' => $timestamp); // push to array if (substr($url, 0, strlen('.default')) !== '.default') { array_push($arr, $data); } } // encode arr $content = json_encode($arr, JSON_PRETTY_PRINT); // update content file_put_contents($json_file, $content); return $arr; }
/** * Uploads a file * * @return Response */ public function upload(Request $request) { // get request data $email = $request->input('auth-email'); $id = $request->input('auth-id'); // get site $site = Site::getById($id); // get file $file = $request->file('file'); // get file info $filename = $file->getClientOriginalName(); $contentType = $file->getMimeType(); $size = intval($file->getClientSize() / 1024); // get the extension $ext = $file->getClientOriginalExtension(); // allowed filetypes $allowed = explode(',', env('ALLOWED_FILETYPES')); // trim and lowercase all items in the aray $allowed = array_map('trim', $allowed); $allowed = array_map('strtolower', $allowed); // directory to save $directory = app()->basePath() . '/public/sites/' . $site->id . '/files'; // save image if ($ext == 'png' || $ext == 'jpg' || $ext == 'gif' || $ext == 'svg') { // upload image // move the file $file->move($directory, $filename); // set path $path = $directory . '/' . $filename; $arr = Utilities::createThumb($site, $path, $filename); // create array $arr = array('filename' => $filename, 'fullUrl' => '/files/' . $filename, 'thumbUrl' => '/files/thumbs/' . $filename, 'extension' => $ext, 'isImage' => true, 'width' => $arr['width'], 'height' => $arr['height']); } else { if (in_array($ext, $allowed)) { // save file if it is allowed // move the file $file->move($directory, $filename); // set url $url = $site->domain; $arr = array('filename' => $filename, 'fullUrl' => $url . '/files/' . $filename, 'thumbUrl' => NULL, 'extension' => $ext, 'isImage' => false, 'width' => -1, 'height' => -1); } else { return response('Unauthorized', 401); } } // return OK return response()->json($arr); }
/** * Saves a submission * * @param {string} $id the ID of the site * @return {Site} */ public function save($siteId) { // defaults $dir = app()->basePath() . '/resources/sites/' . $siteId . '/'; $is_match = false; $submissions = Submission::listAll($siteId); // push user array_push($submissions, (array) $this); // save users $json = json_encode($submissions, JSON_PRETTY_PRINT); // save site.json Utilities::saveContent($dir, 'submissions.json', $json); return; }