diff options
Diffstat (limited to 'site/app/controllers/developers_controller.php')
-rw-r--r-- | site/app/controllers/developers_controller.php | 1643 |
1 files changed, 1486 insertions, 157 deletions
diff --git a/site/app/controllers/developers_controller.php b/site/app/controllers/developers_controller.php index f089e63..a77946e 100644 --- a/site/app/controllers/developers_controller.php +++ b/site/app/controllers/developers_controller.php @@ -37,31 +37,11 @@ * ***** END LICENSE BLOCK ***** */ require_once('Archive/Zip.php'); -/** - * Returns $object[$name], or $default if that's not set. - * - * If $name is a string of dot-separated names like 'foo.bar.baz', - * $object['foo']['bar']['baz'] will be returned. If any name - * along the way is not set, $default will be returned. - * - * If you want to fetch a name with embedded dots, look elsewhere. - */ -function getitem($object, $name, $default=null) { - $split = explode('.', $name, 2); - if (count($split) == 2) { - list($a, $b) = $split; - return isset($object[$a]) ? getitem($object[$a], $b, $default) - : $default; - } else { - return isset($object[$name]) ? $object[$name] : $default; - } -} - class DevelopersController extends AppController { var $name = 'Developers'; var $uses = array('Addon', 'Addontype', 'Application', 'Approval', 'Appversion', - 'EditorSubscription', 'Eventlog', 'File', 'License', 'Platform', 'Preview', 'Review', + 'EditorSubscription', 'Eventlog', 'File', 'Platform', 'Preview', 'Review', 'Tag', 'Translation', 'User', 'Version'); var $components = array('Amo', 'Developers', 'Editors', 'Email', 'Error', 'Image', 'Opensearch', 'Rdf', 'Src', 'Versioncompare'); @@ -110,9 +90,6 @@ class DevelopersController extends AppController // Default "My Add-ons" sidebar data $session = $this->Session->read('User'); $this->publish('all_addons', $this->Addon->getAddonsByUser($session['id'])); - - // Include the dev_agreement column on developer pages. - array_push($this->Addon->default_fields, 'dev_agreement'); } /** @@ -235,7 +212,6 @@ class DevelopersController extends AppController */ function _submitAddon() { $this->publish('type', 'new'); - $this->publish('hasAgreement', false); $this->render('uploader'); } @@ -266,17 +242,9 @@ class DevelopersController extends AppController $session = $this->Session->read('User'); $this->Addon->saveAuthor($data['Addon']['id'], $session['id']); - // Save License - $license_id = $this->Developers->saveLicense( - $this->data['License'], - getitem($this->data, 'License.text'), - getitem($this->params, 'form.data.License')); - $this->Addon->saveField('dev_agreement', 1); - // Add Version $this->Version->id = 0; $data['Version']['addon_id'] = $data['Addon']['id']; - $data['Version']['license_id'] = $license_id; $this->Version->save($data['Version']); $data['Version']['id'] = $this->Version->getLastInsertId(); @@ -351,30 +319,13 @@ class DevelopersController extends AppController return $this->Error->getJSONforError(sprintf(___('devcp_update_addon_version_exists_error'), $data['Version']['version'], $this->url('/developers/versions/addfile/'.$vcheck['Version']['id']))); } - // Save License - if ($addon['Addon']['dev_agreement'] == true) { - // If we already have an agreement, we didn't show the license - // picker, so use the previously selected license. - global $valid_status; - $old_id = $this->Version->getVersionByAddonId($addon_id, $valid_status); - $oldVersion = $this->Version->findById($old_id); - $license_id = $oldVersion['Version']['license_id']; - } else { - $license_id = $this->Developers->saveLicense( - $this->data['License'], - getitem($this->data, 'License.text'), - getitem($this->params, 'form.data.License')); - } - $this->Addon->save(array('Addon' => array('id' => $addon_id, - 'dev_agreement' => 1))); - + // Add Version $this->Version->id = 0; $data['Version']['addon_id'] = $addon_id; - $data['Version']['license_id'] = $license_id; $this->Version->save($data['Version']); $version_id = $this->Version->getLastInsertId(); - + // If add-on is public, cancel any pending files if ($addon['Addon']['status'] == STATUS_PUBLIC) { $this->Addon->execute("UPDATE files SET status = ".STATUS_SANDBOX." WHERE files.version_id IN (SELECT id FROM versions WHERE versions.addon_id={$addon_id}) AND files.status = ".STATUS_PENDING); @@ -456,6 +407,7 @@ class DevelopersController extends AppController ); } + function _rmtree($dir) { $dir = "$dir"; @@ -543,71 +495,65 @@ class DevelopersController extends AppController ); } - // we are sugar - if (true) { - $bundle = $addon['File']['details']['path']; - $pathinfo = pathinfo($bundle); - - if ($pathinfo['extension'] == '.xo') { - $ini = $this->_unbundle($bundle, 'activity/activity.info'); - if (isset($ini['error'])) - return $this->Error->getJSONforError($ini['error']); - $ini_file = $ini['manifest']; - - if (!is_array($ini_file)) - return $this->Error->getJSONforError(_('devcp_error_activity_info_not_found')); - if (!isset($ini_file['name'])) - return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_name')); - if (!isset($ini_file['activity_version'])) - return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_activity_version')); - - if (isset($ini_file['bundle_id'])) - $addon['Addon']['guid'] = $ini_file['bundle_id']; - else if (isset($ini_file['service_name'])) - $addon['Addon']['guid'] = $ini_file['service_name']; - else - return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_bundle_id')); + if ($addon['Addon']['addontype_id'] == ADDON_ACTIVITY) { + $ini = $this->_unbundle($addon['File']['details']['path'], 'activity/activity.info'); + if (isset($ini['error'])) + return $this->Error->getJSONforError($ini['error']); + $ini_file = $ini['manifest']; - $addon['Addon']['name'] = $ini_file['name']; - $addon['Addon']['summary'] = $ini_file['name']; - $addon['Version']['version'] = $ini_file['activity_version']; + if (!is_array($ini_file)) + return $this->Error->getJSONforError(_('devcp_error_activity_info_not_found')); + if (!isset($ini_file['name'])) + return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_name')); + if (!isset($ini_file['activity_version'])) + return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_activity_version')); + if (isset($ini_file['bundle_id'])) + $addon['Addon']['guid'] = $ini_file['bundle_id']; + else if (isset($ini_file['service_name'])) + $addon['Addon']['guid'] = $ini_file['service_name']; + else + return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_bundle_id')); + + $addon['Addon']['name'] = $ini_file['name']; + $addon['Addon']['summary'] = $ini_file['name']; + $addon['Version']['version'] = $ini_file['activity_version']; + + } else if ($addon['Addon']['addontype_id'] == ADDON_CONTENT) { + $ini = $this->_unbundle($addon['File']['details']['path'], 'library/library.info'); + if (isset($ini['error'])) + return $this->Error->getJSONforError($ini['error']); + $ini_file = $ini['manifest']; + + if (!is_array($ini_file)) + return $this->Error->getJSONforError(_('devcp_error_activity_info_not_found')); + if (!isset($ini_file['name'])) + return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_name')); + if (!isset($ini_file['long_name'])) + return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_summary')); + if (!isset($ini_file['global_name'])) + return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_bundle_id')); + + $addon['Addon']['name'] = $ini_file['name']; + $addon['Addon']['summary'] = $ini_file['long_name']; + $addon['Addon']['guid'] = $ini_file['global_name']; + + if (!isset($this->data['Addon']['id'])) { + $addon['Version']['version'] = 1; } else { - $ini = $this->_unbundle($bundle, 'library/library.info'); - if (isset($ini['error'])) - return $this->Error->getJSONforError($ini['error']); - $ini_file = $ini['manifest']; - - if (!is_array($ini_file)) - return $this->Error->getJSONforError(_('devcp_error_activity_info_not_found')); - if (!isset($ini_file['name'])) - return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_name')); - if (!isset($ini_file['long_name'])) - return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_summary')); - if (!isset($ini_file['global_name'])) - return $this->Error->getJSONforError(_('devcp_error_activity_info_missing_bundle_id')); - - $addon['Addon']['name'] = $ini_file['name']; - $addon['Addon']['summary'] = $ini_file['long_name']; - $addon['Addon']['guid'] = $ini_file['global_name']; - - if (!isset($this->data['Addon']['id'])) { - $addon['Version']['version'] = 1; - } else { - $addon_id = $this->data['Addon']['id']; - $_addon = $this->Addon->findById($addon_id); - $version = 0; - - if (!empty($_addon)) { - foreach ($_addon['Version'] as $i) { - if ($i['version'] > $version) { - $version = $i['version']; - } + $addon_id = $this->data['Addon']['id']; + $_addon = $this->Addon->findById($addon_id); + $version = 0; + + if (!empty($_addon)) { + foreach ($_addon['Version'] as $i) { + if ($i['version'] > $version) { + $version = $i['version']; } } - - $addon['Version']['version'] = $version + 1; } + + $addon['Version']['version'] = $version + 1; } } else // Parse install.rdf file if not a search plugin @@ -665,11 +611,6 @@ class DevelopersController extends AppController elseif ($addon['Addon']['addontype_id'] == ADDON_SEARCH) { // Get search engine properties $search = $this->Opensearch->parse($addon['File']['details']['path']); - - // There was a parse error, the name was empty, etc. Bad things. - if ($search == null) { - return $this->Error->getJSONforError(___('devcp_verify_search_engine_error','Either the XML is invalid or required fields are missing. Please <a href="https://developer.mozilla.org/en/Creating_OpenSearch_plugins_for_Firefox">read the documentation</a>, verify your add-on, and try again.')); - } $addon['Addon']['name'] = $search->name; $addon['Addon']['summary'] = $search->description; @@ -863,7 +804,7 @@ class DevelopersController extends AppController $supportedApps = array( 0 => array( 'Application' => array( - 'id' => APP_FIREFOX + 'id' => APP_SUGAR ) ) ); @@ -1030,6 +971,7 @@ class DevelopersController extends AppController $criteria['description'] = !empty($addon['Translation']['description']['string']); $criteria['category'] = !empty($addon['Tag']); $criteria['previews'] = !empty($previews); + $criteria['reviews'] = (!empty($reviews) && count($reviews) >= 3) ? true : false; $criteria['prerelease'] = !empty($addon['Addon']['prerelease']) ? false : true; $criteria['application'] = !empty($versions) ? true : false; @@ -1068,7 +1010,6 @@ class DevelopersController extends AppController $addonData = array('status' => STATUS_SANDBOX, 'higheststatus' => STATUS_SANDBOX); $this->Addon->save($addonData); $this->publish('success', true); - if (QUERY_CACHE) $this->Addon->Cache->markListForFlush("addon:{$addon['Addon']['id']}"); return true; } @@ -1111,7 +1052,7 @@ class DevelopersController extends AppController $this->render('addon_status_nominate'); return false; } - $addonData = array('status' => STATUS_NOMINATED, 'nominationmessage' => $this->params['form']['data']['Addon']['nominationmessage'], 'nominationdate' => date('Y-m-d H:i:s')); + $addonData = array('status' => STATUS_NOMINATED, 'nominationmessage' => $this->params['form']['data']['Addon']['nominationmessage']); $this->Addon->save($addonData); $this->publish('success', true); @@ -1226,9 +1167,6 @@ class DevelopersController extends AppController $this->publish('version', $version['Version']['version']); } - $addon = $this->Addon->findById($addon_id, array('Addon.dev_agreement')); - $this->publish('hasAgreement', $addon['Addon']['dev_agreement']); - $this->render('uploader'); } @@ -1351,13 +1289,6 @@ class DevelopersController extends AppController } } - // Save license. - $license_id = $this->Developers->saveLicense( - $this->data['License'], - getitem($this->data, 'Version.License.text'), - getitem($this->params, 'form.data.Version.License')); - $this->Version->saveField('license_id', $license_id); - // flush cached add-on objects if (QUERY_CACHE) $this->Addon->Cache->markListForFlush("addon:{$addon_id}"); @@ -1385,13 +1316,6 @@ class DevelopersController extends AppController // Get all translations $translations = $this->Version->getAllTranslations($version_id); - if (isset($version['Version']['license_id'])) { - $trans = $this->License->getAllTranslations($version['Version']['license_id']); - $translations['license_text'] = $trans['text']; - } else { - $translations['license_text'] = array(); - } - $this->set('translations', $translations); // Other info @@ -1486,9 +1410,7 @@ class DevelopersController extends AppController // flush cached add-on objects if (QUERY_CACHE) $this->Addon->Cache->markListForFlush("addon:{$addon_id}"); - // inform about cache lag, if any of the changes were successful - if (!empty($messages['success'])) $messages['success'][] = ___('devcp_several_hours'); - + $messages['success'][] = ___('devcp_several_hours'); $this->publish('messages', $messages); } @@ -1607,10 +1529,8 @@ class DevelopersController extends AppController return; } // Make sure user has some permissions to view this add-on - $session = $this->Session->read('User'); - $isEditor = $this->SimpleAcl->actionAllowed('Editors', '*', $session); $role = $this->Amo->getAuthorRole($inforequest['Approval']['addon_id']); - if (!$isEditor && empty($role)) $this->Amo->accessDenied(); + if (empty($role)) $this->Amo->accessDenied(); $this->publish('inforequest', $inforequest); @@ -1628,21 +1548,6 @@ class DevelopersController extends AppController if (!empty($this->data)) { $session = $this->Session->read('User'); - //Auto-detect addontype if necessary - if ($this->data['Addon']['addontype_id'] == 0) { - $this->data['Addon']['addontype_id'] = $this->Developers->detectAddontype($this->data['File']['file1']); - $this->publish('autoDetected', $this->Addontype->getName($this->data['Addon']['addontype_id'])); - } - - //Make sure addontype is allowed - $allowedAddonTypes = $this->Developers->getAllowedAddonTypes(false, $this->SimpleAcl->actionAllowed('*', '*', $this->Session->read('User'))); - if (!array_key_exists($this->data['Addon']['addontype_id'], $allowedAddonTypes)) { - $this->Error->addError(_('devcp_error_invalid_addontype')); - } - - //Validate files - $this->Developers->validateFiles(); - // reply submitted $approvalData = array( 'user_id' => $session['id'], @@ -1684,5 +1589,1429 @@ class DevelopersController extends AppController } $this->render(); } + + /** + * Index + */ + function index($addon_id = '') { + $this->Amo->clean($addon_id); + $session = $this->Session->read('User'); + $this->User->id = $session['id']; + + // If requesting a specific add-on, show details for it + if (!empty($addon_id)) { + $this->details($addon_id); + return; + } + + $addon_ids = $this->Addon->getAddonsByUser($session['id']); + $data = array(); + + if (!empty($addon_ids)) { + foreach ($addon_ids as $addon_id => $addon_name) { + $addon = $this->Addon->find("Addon.id={$addon_id}"); + + if (!empty($addon['Version'][0])) { + $files = $this->File->findAll("File.version_id={$addon['Version'][0]['id']}"); + + if (!empty($files)) { + foreach ($files as $file) { + $addon['Version'][0]['File'][] = $file; + } + } + } + + $data['Addon'][$addon_id] = array_merge($addon['Addon'], $addon); + $data['Addon'][$addon_id]['updatepings'] = $this->Addon->getMostRecentUpdatePingCount($addon_id); + + } + } + + $this->publish('data', $data); + $this->publish('approval', $this->Amo->getApprovalStatus()); + $this->publish('platforms', $this->Amo->getPlatformName()); + $this->publish('addontypes', $this->Addontype->getNames()); + } + + /** + * Shows the details of a specific add-on + * @param int $id The add-on id + */ + function details($id) { + $this->Amo->clean($id); + + if (!$this->Amo->checkOwnership($id)) { + $this->flash(_('devcp_error_addon_access_denied'), '/developers/index'); + return; + } + + //Bind necessary models + $this->User->bindFully(); + $this->Addontype->bindFully(); + $this->Version->bindFully(); + $this->Addon->bindFully(); + + $this->Addon->id = intval($id); + + if (!empty($this->data)) { + //Save translated fields (releasenotes) + $this->Developers->saveTranslations($this->data); + $this->flash(_('devcp_comments_updated'), '/developers/details/'.$id); + return; + } + + if(!$addon = $this->Addon->read()) { + $this->flash(_('error_addon_notfound'), '/developers/index'); + return; + } + + if (!empty($addon)) { + if (!empty($addon['Version'])) { + foreach ($addon['Version'] as $v => $version) { + $addon['Version'][$v]['File'] = $this->File->findAll("File.version_id='{$version['id']}'"); + } + } + + if (!empty($addon['Preview'])) { + foreach ($addon['Preview'] as $p => $preview) { + $this->Preview->id = $preview['id']; + $addon['Preview'][$p] = $this->Preview->read(); + } + } + + if (!empty($addon['Tag'])) { + foreach ($addon['Tag'] as $tag) { + $tags[] = $tag['id']; + } + $tags = $this->Tag->findAll("Tag.id IN (".implode(', ', $tags).")"); + if (!empty($tags)) { + foreach ($tags as $tag) { + $addon['Tags'][] = $tag['Translation']['name']['string']; + } + } + } + } + + $this->publish('addon', $addon); + $this->publish('approval', $this->Amo->getApprovalStatus()); + $this->publish('platforms', $this->Amo->getPlatformName()); + $this->publish('addontypes', $this->Addontype->getNames()); + + //Retrieve language arrays from bootstrap. + global $valid_languages, $native_languages; + foreach (array_keys($valid_languages) as $key) { + $languages[$key] = $native_languages[$key]['native']; + + $this->Addon->setLang($key, $this); + $addonL = $this->Addon->read(); + + foreach ($addonL['Translation'] as $field => $translation) { + if ($translation['locale'] == $key) { + $info[$key][$field] = $translation['string']; + } + else { + $info[$key][$field] = ''; + } + } + } + $this->Addon->setLang(LANG, $this); + + //Set up localebox info + $this->set('localebox', array('info' => $info, + 'defaultLocale' => $addon['Addon']['defaultlocale'], + 'languages' => $languages)); + $this->render('details', 'mozilla'); + } + + /** + * Add new version to a new or existing add-on + * @param int $id + */ + function add($id = '') { + $this->Amo->clean($id); + $this->publish('subpagetitle', _('devcp_addon_submit_pagetitle')); + $this->breadcrumbs[_('devcp_addon_submit_pagetitle')] = '/developers/add/'.$id; + $this->publish('breadcrumbs', $this->breadcrumbs); + + //If submissions are disabled, show appropriate error + if ($this->Config->getValue('submissions_disabled') == 1 && !$this->SimpleAcl->actionAllowed('*', '*', $this->Session->read('User'))) { + $this->flash(_('devcp_submissions_disabled'), '/', 3); + return; + } + + //Bind necessary models + $this->User->bindFully(); + $this->Addontype->bindFully(); + $this->Version->bindFully(); + $this->Addon->bindFully(); + + //Get a list of add-on types + $this->publish('addonTypes', $this->Addontype->getNames()); + + //Determine if adding a new addon or new version + if (!empty($this->data['Addon']['newAddon'])) { + if ($this->data['Addon']['newAddon'] == 'false') { + $this->addVars['newAddon'] = false; + } + else { + $this->addVars['newAddon'] = true; + } + $this->Addon->id = $id; + $this->addVars['existing'] = $this->Addon->read(); + } + else { + //This is step 1, so check if the id was passed + if (!empty($id) && $this->Amo->checkOwnership($id)) { + $this->Addon->id = $id; + $this->addVars['existing'] = $this->Addon->read(); + $this->addVars['newAddon'] = false; + } + else { + $this->addVars['newAddon'] = true; + $this->addVars['existing'] = array(); + $id = ''; + } + } + $this->publish('existing', $this->addVars['existing']); + + //Set some view data used in all steps + $this->publish('id', $id); + $this->publish('newAddon', $this->addVars['newAddon']); + if (!empty($this->data['Version']['id'])) { + $this->publish('version', $this->data['Version']['id']); + $this->Version->id = $this->data['Version']['id']; + } + + //Set default locale + if (!empty($this->data['Addon']['defaultlocale'])) { + $this->addVars['defaultLocale'] = $this->data['Addon']['defaultlocale']; + } + elseif (!empty($this->addVars['existing']['Addon']['defaultlocale'])) { + $this->addVars['defaultLocale'] = $this->addVars['existing']['Addon']['defaultlocale']; + } + else { + $this->addVars['defaultLocale'] = LANG; + } + + //Set models to read and save using default locale, which may not be the current locale + $this->Addon->setLang($this->addVars['defaultLocale'], $this); + $this->Version->setLang($this->addVars['defaultLocale'], $this); + + $this->set('defaultLocale', $this->addVars['defaultLocale']); + + //Go to appropriate step + if (isset($_POST['cancel'])) { + $this->_cancelAdd(); + } + elseif (isset($this->data['Addon']['add_step1'])) { + $this->_addStep1(); + } + elseif (isset($this->data['Addon']['add_step2'])) { + $this->_addStep2(); + } + elseif (isset($this->data['Addon']['add_step3'])) { + $this->_addStep3(); + } + elseif (isset($this->data['Addon']['add_step4'])) { + $this->_addStep4(); + } + else { + if ($this->addVars['newAddon'] == true) { + $this->_addStep0(); + } + else { + $this->_addStep1(); + } + } + } + + /** + * Step 0: Agreement + */ + function _addStep0() { + if (isset($this->data['Addon']['add_step0'])) { + if (!empty($_POST['accept'])) { + $this->data = ''; + $this->_addStep1(); + return; + } + } + $this->publish('step', 0); + $this->render('add_step0'); + } + + /** + * Step 1: Upload files/select add-on type + */ + function _addStep1() { + //Check if we are processing Step 1 or just displaying it + if (isset($this->data['Addon']['add_step1'])) { + //Check for model validation first + if (!$this->Addon->validates($this->data)) { + $this->Error->addError(_('error_formerrors')); + $this->validateErrors(); + } + + //Auto-detect addontype if necessary + if ($this->data['Addon']['addontype_id'] == 0) { + $this->data['Addon']['addontype_id'] = $this->Developers->detectAddontype($this->data['File']['file1']); + $this->publish('autoDetected', $this->Addontype->getName($this->data['Addon']['addontype_id'])); + } + + //Make sure addontype is allowed + $allowedAddonTypes = $this->Developers->getAllowedAddonTypes(false, $this->SimpleAcl->actionAllowed('*', '*', $this->Session->read('User'))); + if (!array_key_exists($this->data['Addon']['addontype_id'], $allowedAddonTypes)) { + $this->Error->addError(_('devcp_error_invalid_addontype')); + } + + //Validate files + $this->Developers->validateFiles(); + + //Only proceed if no errors + if ($this->Error->noErrors()) { + //Search plugins do not have install.rdf to parse + if ($this->data['Addon']['addontype_id'] != ADDON_SEARCH) { + + //Iterate through files to make sure they're all valid + //Reverse order so that file 1's info will be used after + for ($f = 3; $f >= 1; $f--) { + if (!empty($this->addVars['file'.$f])) { + //Extract install.rdf + $zip = new Archive_Zip($this->addVars['file'.$f]['path']); + $extraction = $zip->extract(array('extract_as_string' => true, 'by_name' => array('install.rdf'))); + + //Make sure install.rdf is present + if (!empty($extraction)) { + $fileContents = $extraction[0]['content']; + + //Use Rdf Component to parse install.rdf + $manifestData = $this->Rdf->parseInstallManifest($fileContents); + + //Clean manifest data - take THAT, evil-doers! + $this->Amo->clean($manifestData); + + //Validate manifest data + $validate = $this->Developers->validateManifestData($manifestData); + // Anything other than boolean true is an error + if ($validate === true) { + + //Validate target applications + $this->addVars['appversions'] = $this->Developers->validateTargetApplications($manifestData['targetApplication']); + + // If result is a string, it's an error + if (is_string($this->addVars['appversions'])) { + $this->Error->addError($this->addVars['appversions']); + } + + //Make sure GUIDs match in all manifests + if (empty($guid)) { + $guid = $manifestData['id']; + } + elseif ($manifestData['id'] != $guid) { + $this->Error->addError(_('devcp_error_file_guids_dont_match')); + } + } + else { + // There was an error validating manifest data + $this->Error->addError($validate); + } + } + else { + $validAppReference = sprintf(_('devcp_valid_app_reference'), '<a href="'.$this->url('/pages/appversions').'">'._('devcp_valid_app_reference_linktext').'</a>'); + $this->Error->addError(_('devcp_error_index_rdf_notfound').'<br>'.$validAppReference); + } + } + } + + if ($this->Error->noErrors()) { + + //These are arrays by locale + $addonNames = $manifestData['name']; + $addonDesc = $manifestData['description']; + + //In case user said it was a new add-on when it is actually an update + if ($existing = $this->Addon->findAll("Addon.guid='{$manifestData['id']}'")) { + if ($existing[0]['Addon']['status'] != STATUS_NULL && $this->addVars['newAddon'] == true) { + $this->addVars['newAddon'] = false; + $this->publish('newAddon', $this->addVars['newAddon']); + } + + /** + * If user went back to step 1, we know because newAddon is true + * and status is NULL. If that's the case, we make a note so that + * we don't insert things that are already inserted. + */ + if ($existing[0]['Addon']['status'] == STATUS_NULL) { + $this->addVars['skipInserts'] = true; + } + + $this->addVars['existing'] = $existing[0]; + $this->publish('existing', $this->addVars['existing']); + $this->publish('id', $this->addVars['existing']['Addon']['id']); + $this->Addon->id = $this->addVars['existing']['Addon']['id']; + if (!$this->Amo->checkOwnership($this->addVars['existing']['Addon']['id'])) { + $this->flash(_('devcp_error_update_access_denied')); + return; + } + } + elseif ($this->addVars['newAddon'] == false) { + $this->addVars['newAddon'] = true; + $this->publish('newAddon', $this->addVars['newAddon']); + $this->publish('existing', ''); + $this->publish('id', ''); + $this->Addon->id = 0; + } + + //Check for identical versions + if (!empty($this->Addon->id)) { + + for ($f = 1; $f <= 4; $f++) { + if (!empty($this->data['File']['platform_id'.$f])) { + $platformIds[] = $this->data['File']['platform_id'.$f]; + } + } + + //If updating the addon, get any identical version numbers + //Must have WHERE statement this way, otherwise cake doesn't quote properly + if ($versions = $this->Version->findAll("addon_id={$this->Addon->id} AND version='{$manifestData['version']}'")) { + foreach ($versions as $version) { + foreach($version['File'] as $file) { + if (in_array($file['platform_id'], $platformIds) && $file['status'] != STATUS_NULL) { + $this->Error->addError(sprintf(_('devcp_error_identical_version_exists'), $manifestData['version'])); + } + } + } + } + } + } + } + //If it is a search plugin, read the .src file + else { + //Parse the .src file + $src = $this->Src->parse(REPO_PATH.'/temp/'.$this->addVars['file1']['filename']); + if ($src !== false) { + $manifestData['name']['en-US'] = $src; + } + else { + $manifestData = array(); + } + + $this->data['Addon']['platform_id'] = 1; + } + + //If no errors, save + if ($this->Error->noErrors()) { + + //Create the addon entry if new + if ($this->addVars['newAddon'] == true) { + if ($this->data['Addon']['addontype_id'] != ADDON_SEARCH) { + $this->data['Addon']['guid'] = $manifestData['id']; + } + + //If creating new, blacklist status and id. + $addonData = array_merge($this->data['Addon'], (array)$this->addVars['file4']); + $addonData = $this->Amo->filterFields($addonData, array(), + array('status', 'id', 'trusted', 'averagerating', 'weeklydownloads', 'totaldownloads')); + $this->Addon->save($addonData); + + if (empty($this->addVars['skipInserts'])) { + $this->Addon->id = $this->Addon->getLastInsertId(); + + //Insert current user as author so no permissions issues occur + $session = $this->Session->read('User'); + $this->Addon->execute("INSERT INTO addons_users (addon_id, user_id) VALUES({$this->Addon->id}, {$session['id']})"); + } + + $this->addVars['existing'] = $this->data; + $this->publish('id', $this->Addon->id); + } + else { + //If updating, whitelist defaultlocale and icon fields + if (!empty($this->addVars['file4'])) { + $addonData = array_merge($this->data['Addon'], $this->addVars['file4']); + } + else { + $addonData = $this->data['Addon']; + } + $addonData = $this->Amo->filterFields($addonData, + array('addontype_id', 'defaultlocale', 'icondata', 'icontype')); + $this->Addon->save($addonData); + } + + //Create the version entry + $this->data['Version']['addon_id'] = $this->Addon->id; + $this->data['Version']['version'] = (!empty($manifestData['version'])) ? $manifestData['version'] : date('Ymd'); + + //Blacklist id. addon_id should be watched too, but it's already overwritten above + $versionData = $this->Amo->filterFields($this->data['Version'], array(), + array('id')); + $this->Version->save($versionData); + $this->Version->id = $this->Version->getLastInsertId(); + $this->publish('version', $this->Version->id); + + if(!empty($this->addVars['appversions'])) { + //Insert target apps + foreach ($this->addVars['appversions'] as $appversion) { + //Cake doesn't deal with extra fields in HABTM tables, so have to insert manually + $this->Version->execute("INSERT INTO applications_versions (version_id, application_id, min, max) VALUES('{$this->Version->id}', '{$appversion['application_id']}', '{$appversion['min']}', '{$appversion['max']}')"); + } + } + + //Insert files + for ($f = 1; $f <= 3; $f++) { + if (!empty($this->addVars['file'.$f]['filename'])) { + $this->File->id = 0; + $this->addVars['file'.$f]['version_id'] = $this->Version->id; + + //These fields are parsed through the code and shouldn't need filtering + $this->File->save($this->addVars['file'.$f]); + $this->addVars['files'][] = $this->File->getLastInsertId(); + } + } + $this->publish('files', $this->addVars['files']); + + //Show next step + $this->addVars['manifestData'] = $manifestData; + + //If user opted to not review add-on info and all required info is present, + //skip to step 3 + if (!empty($this->data['Addon']['Review']) && $this->Developers->noReviewRequired()) { + $this->_addStep3(); + } + else { + $this->_addStep2(); + } + + return; + } + } + } + + //Prep for view + //Get Platforms list + $this->set('platforms', $this->Amo->getPlatformName()); + + //Default to reviewing add-on information + $this->publish('ReviewInfo', ((isset($this->data['Addon']['Review']) && $this->data['Addon']['Review'] == 1) ? array('value' => '1', 'checked' => 'checked') : array())); + + //Set allowed addontypes. + $allowedAddonTypes = $this->Developers->getAllowedAddonTypes(true, $this->SimpleAcl->actionAllowed('*', '*', $this->Session->read('User'))); + $this->set('allowedAddonTypes', $allowedAddonTypes); + + //Retrieve language arrays from bootstrap. + global $valid_languages, $native_languages; + foreach (array_keys($valid_languages) as $key) { + $languages[$key] = $native_languages[$key]['native']; + } + + if (empty($this->data)) + $this->data = array(); + $this->publish('data', $this->data); + $this->set('errors', $this->Error->errors); + $this->publish('step', 1); + $this->set('languages', $languages); + $this->render('add_step1'); + } + + /** + * Step 2: Add-on wide information + */ + function _addStep2() { + + //Check if we are processing step 2 or just displaying the view for it + if (isset($this->data['Addon']['add_step2'])) { + + //Check for model validation first + if (!$this->Addon->validates($this->data)) { + $this->Error->addError(_('error_formerrors')); + $this->validateErrors(); + } + + //Validate tags/categories if not correcting addontype id + if (!empty($this->data['Addon']['addontype_id']) && $this->data['Addon']['addontype_id'] != $this->addVars['existing']['Addon']['addontype_id']) { + unset($this->data['Tag']); + $this->addVars['showTagsStep3'] = $this->data['Addon']['addontype_id']; + } + elseif ($this->addVars['existing']['Addon']['addontype_id'] != ADDON_SEARCH && empty($this->data['Tag']['noTags'])) { + //Search engines don't have tags + @$this->Developers->validateTags($this->data['Tag']['Tag']); + } + + //Validate users + @$this->Developers->validateUsers($this->data['User']['User']); + + if ($this->Error->noErrors()) { + //Updated Addon + if ($this->addVars['newAddon'] == false) { + $tagData = $this->data['Tag']['Tag']; + unset($this->data['Tag']); + unset($this->Addon->data['Tag']); // Cake-- + $this->Tag->LEGACY_saveCategories($this->Addon->id, $tagData); + + //Update addon - usual addon blacklist + $addonData = $this->Amo->filterFields($this->data, array(), + array('id', 'guid', 'status', 'trusted', 'averagerating', 'weeklydownloads', 'totaldownloads')); + + if (!$this->Addon->save($addonData, false)) { + $this->Error->addError(_('devcp_error_saving')); + } + } + //New Addon + else { + //Check for duplicate names + //@TODO this doesn't really do well with the new translation stuff. + if ($this->Addon->findAllByName($this->data['Addon']['name'])) { + $this->Error->addError(_('devcp_error_addonname_not_unique')); + } + else { + //Insert addon + $addonData = $this->Amo->filterFields($this->data, array(), + array('id', 'guid', 'status', 'trusted', 'averagerating', 'weeklydownloads', 'totaldownloads')); + if (!$this->Addon->save($addonData, false)) { + $this->Error->addError(_('devcp_error_saving')); + } + } + } + } + + if ($this->Error->noErrors()) { + //Getting to this point means there were no errors, so show next step + $this->_addStep3(); + return; + } + + //Prepare for view - use post data (unclean data before publishing to avoid escaped quotes in html) + $info = $this->Amo->unclean($this->data); + + //Checkboxes + $checked = array('value' => '1', 'checked' => 'checked'); + $notChecked = array(); + $info['Addon']['viewsource'] = (!empty($this->data['Addon']['viewsource'])) ? $checked : $notChecked; + $info['Addon']['prerelease'] = (!empty($this->data['Addon']['prerelease'])) ? $checked : $notChecked; + $info['Addon']['sitespecific'] = (!empty($this->data['Addon']['sitespecific'])) ? $checked : $notChecked; + $info['Addon']['externalsoftware'] = (!empty($this->data['Addon']['externalsoftware'])) ? $checked : $notChecked; + } + else { + //Prepare for view - use existing data if !empty, else use parsed manifest + $manifestData = $this->Amo->unclean($this->addVars['manifestData']); + $info = $this->Amo->unclean($this->data); + + $info['Addon']['name'] = (!empty($this->addVars['existing']['Translation']['name']['string']) ? $this->addVars['existing']['Translation']['name']['string'] : (!empty($manifestData['name']['en-US']) ? $manifestData['name']['en-US'] : '')); + $info['Addon']['description'] = (!empty($this->addVars['existing']['Translation']['description']['string']) ? $this->addVars['existing']['Translation']['description']['string'] : (!empty($manifestData['description']['en-US']) ? $manifestData['description']['en-US'] : '')); + $info['Addon']['homepage'] = (!empty($this->addVars['existing']['Translation']['homepage']['string']) ? $this->addVars['existing']['Translation']['homepage']['string'] : (!empty($manifestData['homepageURL']) ? $manifestData['homepageURL'] : '')); + + $info['Addon']['addontype_id'] = !empty($this->addVars['existing']['Addon']['addontype_id']) ? $this->addVars['existing']['Addon']['addontype_id'] : $this->data['Addon']['addontype_id']; + $info['Version']['version'] = !empty($manifestData['version']) ? $manifestData['version'] : ''; + $info['Addon']['summary'] = !empty($this->addVars['existing']['Translation']['summary']['string']) ? $this->addVars['existing']['Translation']['summary']['string'] : ''; + + $info['Addon']['supportemail'] = (!empty($this->addVars['existing']['Translation']['supportemail']['string']) ? $this->addVars['existing']['Translation']['supportemail']['string'] : ''); + $info['Addon']['supporturl'] = (!empty($this->addVars['existing']['Translation']['supporturl']['string']) ? $this->addVars['existing']['Translation']['supporturl']['string'] : (!empty($manifestData['homepageURL']) ? $manifestData['homepageURL'] : '')); + + $info['Addon']['eula'] = !empty($this->addVars['existing']['Translation']['eula']['string']) ? $this->addVars['existing']['Translation']['eula']['string'] : ''; + $info['Addon']['privacypolicy'] = !empty($this->addVars['existing']['Translation']['privacypolicy']['string']) ? $this->addVars['existing']['Translation']['privacypolicy']['string'] : ''; + $info['Addon']['guid'] = (!empty($this->addVars['existing']['Addon']['guid']) ? $this->addVars['existing']['Addon']['guid'] : (!empty($manifestData['id']) ? $manifestData['id'] : '')); + $info['File']['id'] = $this->addVars['files']; + + //Checkboxes + $checked = array('value' => '1', 'checked' => 'checked'); + $notChecked = array(); + $info['Addon']['viewsource'] = (!empty($this->addVars['existing']['Addon']['viewsource'])) ? $checked : $notChecked; + $info['Addon']['prerelease'] = (!empty($this->addVars['existing']['Addon']['prerelease'])) ? $checked : $notChecked; + $info['Addon']['sitespecific'] = (!empty($this->addVars['existing']['Addon']['sitespecific'])) ? $checked : $notChecked; + $info['Addon']['externalsoftware'] = (!empty($this->addVars['existing']['Addon']['externalsoftware'])) ? $checked : $notChecked; + } + + //Get authors in order of post data, existing data, default data + $info['authors'] = @$this->Developers->getAuthors($this->addVars['existing']['User']); + + //Get application_ids + $applicationIds = array(); + if (!empty($this->addVars['appversions'])) { + foreach ($this->addVars['appversions'] as $appversion) { + $applicationIds[] = $appversion['application_id']; + } + } + else { + $version = $this->Version->read(); + foreach ($version['Application'] as $appversion) { + $applicationIds[] = $appversion['id']; + } + } + + $tags = $this->Developers->getTags($this->addVars['existing']['Addon']['addontype_id'], $applicationIds); + $this->set('tags', $tags); + + //Get selected tags in order of post data, existing data, default data + $info['selectedTags'] = @$this->Developers->getSelectedTags($this->addVars['existing']['Tag']); + + //Set allowed addontypes. + $allowedAddonTypes = $this->Developers->getAllowedAddonTypes(false, $this->SimpleAcl->actionAllowed('*', '*', $this->Session->read('User'))); + $this->set('allowedAddonTypes', $allowedAddonTypes); + + $this->publish('info', $info); + $this->set('errors', $this->Error->errors); + $this->publish('step', 2); + $this->render('add_step2'); + } + + /** + * Step 3: Version Information + */ + function _addStep3() { + //Check whether we are processing Step 3 or just displaying it + if (isset($this->data['Addon']['add_step3'])) { + //Check for model validation first + if (!$this->Version->validates($this->data)) { + $this->Error->addError(_('error_formerrors')); + $this->validateErrors(); + } + + if ($this->addVars['newAddon'] == false && empty($this->data['Version']['releasenotes'])) { + $this->Error->addError(_('devcp_error_describe_changes'), 'Version/releasenotes'); + //For some reason, using Version model to invalidate doesn't work... + $this->Addon->invalidate('releasenotes'); + $this->Error->addError(_('error_formerrors')); + } + + if ($this->Error->noErrors()) { + $version = $this->Version->read(); + + $fileUpdates = $this->Developers->moveFiles($version, $this->addVars['existing']['Addon']['addontype_id']); + + if ($this->Error->noErrors()) { + + //Update version + $versionData = $this->Amo->filterFields($this->data['Version'], array(), + array('id', 'addon_id')); + $this->Version->data = ''; + $this->Version->save($versionData); + + //Determine file status + $fileStatus = $this->Developers->determineFileStatus($this->addVars['existing']['Addon']); + + //Update file locations + if (!empty($fileUpdates)) { + foreach ($fileUpdates as $file => $filename) { + $this->File->id = $file; + $filename['status'] = $fileStatus; + $this->File->save($filename); + + // Copy file to rsync area if public + if ($fileStatus == STATUS_PUBLIC) { + $fileInfo = $this->File->read(); + $this->Amo->copyFileToPublic($this->Addon->id, $fileInfo['File']['filename']); + $this->File->save(array('datestatuschanged' => $this->Amo->getNOW())); + } + } + } + + //If add-on is public, cancel any pending files + if ($this->addVars['existing']['Addon']['status'] == STATUS_PUBLIC) { + if (!empty($this->addVars['existing']['Version'])) { + foreach($this->addVars['existing']['Version'] as $version) { + $version = $this->Version->find("Version.id='{$version['id']}'"); + if (!empty($version['File'])) { + foreach ($version['File'] as $file) { + if ($file['status'] == STATUS_PENDING) { + $this->File->id = $file['id']; + $this->File->save(array('status' => STATUS_SANDBOX)); + } + } + } + } + } + } + + //Update addon status if new or incomplete + if ($this->addVars['newAddon'] == true || $this->addVars['existing']['Addon']['status'] == 0) { + $addonData = array('Addon' => array('status' => STATUS_SANDBOX)); + if (!empty($this->data['Tag'])) { + $addonData['Tag'] = $this->data['Tag']; + } + $this->Addon->save($addonData); + } + + //Getting this far means there were no errors, display step 5 + $this->_addStep4(); + return; + } + } + } + + //Prep view + //Pull version info + $info = $this->Version->read(); + + if (!empty($this->addVars['showTagsStep3'])) { + foreach ($info['Application'] as $appversion) { + $applicationIds[] = $appversion['id']; + } + $tags = $this->Developers->getTags($this->addVars['showTagsStep3'], $applicationIds); + $this->publish('tags', $tags); + } + + $info['files'] = $this->File->findAllByVersion_id($this->Version->id); + $info['targetapps'] = $this->Amo->getMinMaxVersions($this->Version->id); + + // Determine compatibility with latest Firefox + $noticeVersion = $this->Config->getValue('firefox_notice_version'); + if (!empty($noticeVersion)) { + if (!empty($info['targetapps'])) { + foreach ($info['targetapps'] as $targetApp) { + if ($targetApp['max']['application_id'] == APP_FIREFOX && + $this->Versioncompare->compareVersions($targetApp['max']['version'], $noticeVersion) == -1) { + $this->publish('showFirefoxVersionNotice', true); + } + } + } + } + + //Get Platforms list + $this->publish('platforms', $this->Amo->getPlatformName()); + + $this->publish('info', $info); + $this->set('errors', $this->Error->errors); + $this->publish('step', 3); + $this->render('add_step3'); + } + + /** + * Step 4: Localization + */ + function _addStep4() { + //Check whether we are processing Step 4 or just displaying it + if (isset($this->data['Addon']['add_step4'])) { + + $this->Developers->saveTranslations($this->data); + + $this->_addStep5(); + return; + } + + //Prep view + + //Retrieve language arrays from bootstrap. + global $valid_languages, $native_languages; + foreach (array_keys($valid_languages) as $key) { + $languages[$key] = $native_languages[$key]['native']; + + $this->Addon->setLang($key, $this); + $addon = $this->Addon->read(); + + foreach ($addon['Translation'] as $field => $translation) { + if ($translation['locale'] == $key) { + $info[$key][$field] = $translation['string']; + } + else { + $info[$key][$field] = ''; + } + } + + $this->Version->useLang = $key; + $version = $this->Version->read(); + + foreach ($version['Translation'] as $field => $translation) { + if ($translation['locale'] == $key) { + $info[$key][$field] = $translation['string']; + } + else { + $info[$key][$field] = ''; + } + } + } + + $localizedFields = array( + 'name' => array( + 'type' => 'input', + 'display' => _('devcp_addon_field_name_displaytitle'), + 'model' => 'Addon', + 'field' => 'name', + 'attributes' => array() + ), + 'homepage' => array( + 'type' => 'input', + 'display' => _('devcp_addon_field_homepage_displaytitle'), + 'model' => 'Addon', + 'field' => 'homepage', + 'attributes' => array( + 'size' => 40 + ) + ), + 'summary' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_summary_displaytitle'), + 'model' => 'Addon', + 'field' => 'summary', + 'attributes' => array( + 'rows' => 3, + 'cols' => 55, + 'onBlur' => 'checkSummary(this, \''._('devcp_error_addon_field_summary_toolong').'\');' + ) + ), + 'description' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_description_displaytitle'), + 'model' => 'Addon', + 'field' => 'description', + 'attributes' => array( + 'rows' => 4, + 'cols' => 55 + ) + ), + 'eula' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_eula_displaytitle'), + 'model' => 'Addon', + 'field' => 'eula', + 'attributes' => array( + 'rows' => 6, + 'cols' => 55 + ) + ), + 'privacypolicy' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_privacy_displaytitle'), + 'model' => 'Addon', + 'field' => 'privacypolicy', + 'attributes' => array( + 'rows' => 6, + 'cols' => 55 + ) + ), + 'releasenotes' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_versionnotes_displaytitle'), + 'model' => 'Version', + 'field' => 'releasenotes', + 'attributes' => array( + 'rows' => 3, + 'cols' => 55 + ) + ) + ); + + //Set up localebox info + $this->set('localebox', array('info' => $info, + 'defaultLocale' => $this->addVars['defaultLocale'], + 'languages' => $languages, + 'localizedFields' => $localizedFields)); + $this->publish('step', 4); + $this->render('add_step4'); + } + + /** + * Step 5: Success + */ + function _addStep5() { + //Determine file status + $fileStatus = $this->Developers->determineFileStatus($this->addVars['existing']['Addon']); + + $this->publish('fileStatus', $fileStatus); + $this->publish('step', 5); + $this->render('add_step5'); + } + + /** + * Cancel additem process + */ + function _cancelAdd() { + if (!$this->Amo->checkOwnership($this->Addon->id)) { + $this->flash(_('devcp_error_addon_access_denied'), '/developers/index'); + return; + } + + //Delete version, files, and translations + $this->Developers->deleteVersion($this->Version->id); + + //If this was a new add-on, delete it and translations + if ($this->addVars['newAddon'] == true) { + $this->Developers->deleteAddon($this->Addon->id); + } + + $this->redirect('/developers'); + } + + /** + * Edit add-on + * @param int $id + */ + function edit($id) { + $this->Amo->clean($id); + $this->publish('subpagetitle', _('devcp_addon_edit_pagetitle')); + $this->breadcrumbs[_('devcp_addon_edit_pagetitle')] = '/developers/edit/'.$id; + $this->publish('breadcrumbs', $this->breadcrumbs); + + if (!$this->Amo->checkOwnership($id)) { + $this->flash(_('devcp_error_addon_access_denied'), '/developers/index'); + return; + } + + //Bind necessary models + $this->User->bindFully(); + $this->Addontype->bindFully(); + $this->Version->bindFully(); + $this->Addon->bindFully(); + + $this->Addon->id = $id; + + $this->Amo->detailedLog('Editing add-on'); + + if (!empty($this->data)) { + $this->Amo->detailedLog('Non-empty data'); + $this->Amo->detailedLog('Editing add-on details'); + //Check for model validation first + if (!$this->Addon->validates($this->data)) { + $this->Error->addError(_('error_formerrors')); + $this->validateErrors(); + $this->Amo->detailedLog('Validation errors'); + } + + $addon = $this->Addon->read(); + + //Validate tags/categories + if ($addon['Addon']['addontype_id'] != ADDON_SEARCH && empty($this->data['Tag']['noTags'])) { + @$this->Developers->validateTags($this->data['Tag']['Tag']); + } + + //Validate users + @$this->Developers->validateUsers($this->data['User']['User']); + + $this->Amo->detailedLog('Validated tags and users'); + + //Delete icon if requested + if (!empty($this->data['Addon']['DeleteIcon'])) { + $this->data['Addon']['icontype'] = ''; + $this->data['Addon']['icondata'] = ''; + } + + //Update/insert icon + if (!empty($this->data['Addon']['icon']['name'])) { + $fileErrors = array('1' => ___('devcp_edit_file_error_size'), + '2' => ___('devcp_edit_file_error_size'), + '3' => ___('devcp_edit_file_error_incomplete'), + '4' => ___('devcp_edit_file_error_no') + ); + $allowedImage = array('.png', '.jpg', '.gif'); + + $iconData = $this->Developers->validateIcon($this->data['Addon']['icon'], $fileErrors, $allowedImage); + + if (is_string($iconData)) { + $this->Error->addError($iconData, 'Addon/icon'); + } + else { + $this->data['Addon'] = array_merge($this->data['Addon'], $iconData); + } + } + + if ($this->Error->noErrors()) { + $this->Amo->detailedLog('No errors - performing update'); + //Update addon + $this->Developers->saveUsers($this->data['User']['User']); + $this->Amo->detailedLog('Saved users'); + + //Strip localized fields + $addonData = $this->Developers->stripLocalized($this->data); + $this->Amo->detailedLog('Stripped localized fields: '.print_r($addonData, true), false); + + //usual addon blacklist + if ($this->SimpleAcl->actionAllowed('*', '*', $this->Session->read('User'))) { + $blacklist = array('id', 'guid', 'status'); + } + else { + $blacklist = array('id', 'guid', 'status', 'trusted', 'averagerating', 'weeklydownloads', 'totaldownloads'); + } + $addonData = $this->Amo->filterFields($addonData, array(), $blacklist); + $this->Amo->detailedLog('Fields filtered: '.print_r($addonData, true), false); + $this->Addon->data = array(); + $tagData = $addonData['Tag']['Tag']; + unset($addonData['Tag']['Tag']); + + if ($this->Addon->save($addonData) && $this->Developers->saveTranslations($this->data)) { + $this->Tag->LEGACY_saveCategories($this->Addon->id, $tagData); + $this->Amo->detailedLog('Add-on and translations saved: '.print_r($addonData, true)); + $this->flash(_('devcp_addon_updated_successfully'), '/developers/edit/'.$this->Addon->id); + return; + } + else { + $this->Error->addError(_('devcp_error_saving')); + } + } + } + + if (!$addon = $this->Addon->read()) { + $this->flash(_('error_addon_notfound'), '/developers/index'); + return; + } + + //Get tags based on addontype + $this->Version->id = $addon['Version'][0]['id']; + $applicationIds = array(); + $version = $this->Version->read(); + if (!empty($version)) { + foreach ($version['Application'] as $appversion) { + $applicationIds[] = $appversion['id']; + } + } + $this->set('tags', $this->Developers->getTags($addon['Addon']['addontype_id'], $applicationIds)); + + //Get selected tags + $this->set('selectedTags', $this->Developers->getSelectedTags($addon['Tag'])); + + //Get author info + $addon['authors'] = $this->Developers->getAuthors($addon['User'], false); + + //Retrieve language arrays from bootstrap. + global $valid_languages, $native_languages; + foreach (array_keys($valid_languages) as $key) { + $languages[$key] = $native_languages[$key]['native']; + + $this->Addon->setLang($key, $this); + $addonL = $this->Addon->read(); + + foreach ($addonL['Translation'] as $field => $translation) { + if ($translation['locale'] == $key) { + $info[$key][$field] = $translation['string']; + } + else { + $info[$key][$field] = ''; + } + } + } + $this->Addon->setLang(LANG, $this); + + //Checkboxes + $checked = array('value' => '1', 'checked' => 'checked'); + $notChecked = array(); + $addon['Addon']['viewsource'] = (!empty($addon['Addon']['viewsource'])) ? $checked : $notChecked; + $addon['Addon']['prerelease'] = (!empty($addon['Addon']['prerelease'])) ? $checked : $notChecked; + $addon['Addon']['sitespecific'] = (!empty($addon['Addon']['sitespecific'])) ? $checked : $notChecked; + $addon['Addon']['externalsoftware'] = (!empty($addon['Addon']['externalsoftware'])) ? $checked : $notChecked; + $addon['Addon']['trusted'] = (!empty($addon['Addon']['trusted'])) ? $checked : $notChecked; + + $this->publish('addon', $addon); + $this->set('errors', $this->Error->errors); + $localizedFields = array( + 'name' => array( + 'type' => 'input', + 'display' => _('devcp_addon_field_name_displaytitle'), + 'model' => 'Addon', + 'field' => 'name', + 'attributes' => array() + ), + 'homepage' => array( + 'type' => 'input', + 'display' => _('devcp_addon_field_homepage_displaytitle'), + 'model' => 'Addon', + 'field' => 'homepage', + 'attributes' => array( + 'size' => 40 + ) + ), + 'supportemail' => array( + 'type' => 'input', + 'display' => _('devcp_addon_field_supportemail_displaytitle'), + 'model' => 'Addon', + 'field' => 'supportemail', + 'attributes' => array( + 'size' => 40 + ) + ), + 'supporturl' => array( + 'type' => 'input', + 'display' => _('devcp_addon_field_supporturl_displaytitle'), + 'model' => 'Addon', + 'field' => 'supporturl', + 'attributes' => array( + 'size' => 40 + ) + ), + 'summary' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_summary_displaytitle'), + 'model' => 'Addon', + 'field' => 'summary', + 'attributes' => array( + 'rows' => 3, + 'cols' => 55, + 'onBlur' => 'checkSummary(this, \''._('devcp_error_addon_field_summary_toolong').'\');' + ) + ), + 'description' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_description_displaytitle'), + 'model' => 'Addon', + 'field' => 'description', + 'attributes' => array( + 'rows' => 4, + 'cols' => 55 + ) + ), + 'eula' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_eula_displaytitle'), + 'model' => 'Addon', + 'field' => 'eula', + 'attributes' => array( + 'rows' => 6, + 'cols' => 55 + ) + ), + 'privacypolicy' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_privacy_displaytitle'), + 'model' => 'Addon', + 'field' => 'privacypolicy', + 'attributes' => array( + 'rows' => 6, + 'cols' => 55 + ) + ) + ); + + //Set up localebox info + $this->set('localebox', array('info' => $info, + 'defaultLocale' => $addon['Addon']['defaultlocale'], + 'languages' => $languages, + 'localizedFields' => $localizedFields)); + } + + /** + * Edit a version + * @param int $id + */ + function editversion($id) { + $this->Amo->clean($id); + $this->set ('subpagetitle', _('devcp_version_edit_pagetitle')); + $this->breadcrumbs[_('devcp_version_edit_pagetitle')] = '/developers/editversion/'.$id; + $this->publish('breadcrumbs', $this->breadcrumbs); + + //Bind necessary models + $this->User->bindFully(); + $this->Addontype->bindFully(); + $this->Version->bindFully(); + $this->Addon->bindFully(); + + $this->Version->id = $id; + if (!$version = $this->Version->read()) { + $this->flash(_('error_version_notfound'), '/developers/index'); + return; + } + + if (!$this->Amo->checkOwnership($version['Version']['addon_id'])) { + $this->flash(_('devcp_error_addon_access_denied'), '/developers/index'); + return; + } + + if (!empty($this->data)) { + foreach ($this->data['File']['id'] as $id => $file_id) { + $this->File->id = $file_id; + + //Update or delete file + if ($this->data['File']['Delete'][$id] != 1) { + $fileData = array('platform_id' => $this->data['File']['platform_id'][$id]); + $this->File->save($fileData); + } + else { + $this->Developers->deleteFile($this->File->id, $version['Version']['addon_id']); + } + } + + //Save translated fields (releasenotes) + $this->Developers->saveTranslations($this->data); + + //Save min/max versions + $this->Amo->saveMinMaxVersions($this->Version->id, $this->data['targetApps']); + + //Save other version fields by whitelist + $versionData = $this->Amo->filterFields($this->data['Version'], array('approvalnotes')); + $this->Version->save($versionData); + + $this->flash(_('devcp_version_updated_successfully'), '/developers/editversion/'.$this->Version->id); + return; + } + + $version['targetapps'] = $this->Amo->getMinMaxVersions($this->Version->id); + + // Get all versions + foreach ($version['targetapps'] as $k => $app) { + $_versionlist = $this->Appversion->findAllByApplication_id($app['applications']['id'], '', '', '', '', -1); + $this->Versioncompare->sortAppversionArray($_versionlist); + $version['targetapps'][$k]['appversions'] = $_versionlist; + } + + //Retrieve language arrays from bootstrap. + global $valid_languages, $native_languages; + foreach (array_keys($valid_languages) as $key) { + $languages[$key] = $native_languages[$key]['native']; + + $this->Version->setLang($key, $this); + $versionL = $this->Version->read(); + + foreach ($versionL['Translation'] as $field => $translation) { + if ($translation['locale'] == $key) { + $info[$key][$field] = $translation['string']; + } + else { + $info[$key][$field] = ''; + } + } + } + $this->Version->setLang(LANG, $this); + + //Pull add-on info for translations + $this->Addon->id = $version['Version']['addon_id']; + $addon = $this->Addon->read(); + + $localizedFields = array( + 'releasenotes' => array( + 'type' => 'textarea', + 'display' => _('devcp_addon_field_versionnotes_displaytitle'), + 'model' => 'Version', + 'field' => 'releasenotes', + 'attributes' => array( + 'rows' => 3, + 'cols' => 55 + ) + ) + ); + + $this->publish('version', $version); + $this->publish('addon', $addon); + $this->publish('platforms', $this->Amo->getPlatformName()); + + //Set up localebox info + $this->set('localebox', array('info' => $info, + 'defaultLocale' => $addon['Addon']['defaultlocale'], + 'languages' => $languages, + 'localizedFields' => $localizedFields)); + //Javascript localization + $this->publish('jsLocalization', array( + 'deleteMessage' => _('devcp_question_delete_file') + )); + } + + /** + * Disable or enable add-on + */ + function disable($addon_id) { + $this->Amo->clean($addon_id); + $session = $this->Session->read('User'); + $this->User->id = $session['id']; + + $this->Addon->id = $addon_id; + + // Make sure user has permission + if (!$this->Amo->checkOwnership($addon_id)) { + $this->flash(_('devcp_error_addon_access_denied'), '/developers/index'); + return; + } + + if (!empty($_POST)) { + if (!empty($_POST['enable'])) { + $addonData = array('inactive' => 0); + $this->Addon->save($addonData); + $this->flash(_('devcp_addon_enabled_successfully'), '/developers/details/'.$this->Addon->id); + } + else { + $addonData = array('inactive' => 1); + $this->Addon->save($addonData); + $this->flash(_('devcp_addon_disabled_successfully'), '/developers/details/'.$this->Addon->id); + } + + return; + } + + $addon = $this->Addon->read(); + + $this->publish('addon', $addon); + + $this->render('disable'); + } + + /** + * Nominate an add-on to be public + * @param int $id the add-on id + */ + function nominate($id) { + $this->Amo->clean($id); + $session = $this->Session->read('User'); + $this->User->id = $session['id']; + + $this->publish('subpagetitle', _('devcp_addon_nominate_pagetitle')); + $this->breadcrumbs[_('devcp_addon_nominate_pagetitle')] = '/developers/nominate/'.$id; + $this->publish('breadcrumbs', $this->breadcrumbs); + + $this->User->bindFully(); + $data = $this->User->read(); + + $this->Addon->id = $id; + if (!$addon = $this->Addon->read()) { + $this->flash(_('error_addon_notfound'), '/developers/index'); + return; + } + + //Make sure has ownership or is an editor to nominate + if (!$this->Amo->checkOwnership($id) && !$this->SimpleAcl->actionAllowed('Editors', '*', $this->Session->read('User'))) { + $this->flash(_('devcp_error_addon_access_denied'), '/developers/index'); + return; + } + + //Make sure add-on is in sandbox + if ($addon['Addon']['status'] != STATUS_SANDBOX) { + $this->flash(_('devcp_error_nominate_sandbox_only'), '/developers/details/'.$id, 3); + return; + } + + //Nominate + if (!empty($_POST['nominate'])) { + //Make sure not a pre-release + if ($addon['Addon']['prerelease'] == 1) { + $this->flash(_('devcp_error_nominate_no_prerelease'), '/developers/details/'.$id, 3); + return; + } + + //Make sure nomination message not empty + if (empty($this->data['Addon']['nominationmessage'])) { + $this->flash(_('devcp_error_nominate_message'), '/developers/nominate/'.$id, 3); + return; + } + + //Amo->clean is being stupid. Yes, that's right. I said STUPID + $nominationMessage = str_replace('\n', "\n", $this->data['Addon']['nominationmessage']); + $nominationMessage = str_replace('\r', "\r", $nominationMessage); + + $addonData = array('status' => STATUS_NOMINATED, 'nominationmessage' => $nominationMessage); + + $this->Addon->save($addonData); + $this->flash(_('devcp_addon_nominated_successfully'), '/developers/details/'.$id, 3); + return; + } + + $this->publish('addon', $addon); + } + + /** + * AJAX action for looking up an author by email + * @param string $email + */ + function authorLookup() { + // Rather than change our cake parameter regex, use a normal get var + $email = $_GET['q']; + $this->Amo->clean($email); + if ($authors = $this->User->findAllByEmail($email)) { + $author = $authors[0]['User']['firstname'].' '.$authors[0]['User']['lastname']; + $author .= ' ['.$authors[0]['User']['email'].']'; + $this->publish('id', $authors[0]['User']['id']); + } + else { + $author = false; + } + + $this->publish('author', $author); + $this->publish('email', $email); + $this->render('author_lookup', 'ajax'); + } + } + ?> |