PageRenderTime 26ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/actions/ProjectAction.php

http://github.com/jquery/testswarm
PHP | 267 lines | 196 code | 39 blank | 32 comment | 29 complexity | 6d64bf7307e6c36175bf9416cdc7fdec MD5 | raw file
  1. <?php
  2. /**
  3. * Get details about a project and list its jobs.
  4. *
  5. * @author Timo Tijhof
  6. * @since 1.0.0
  7. * @package TestSwarm
  8. */
  9. class ProjectAction extends Action {
  10. private $defaultLimit = 10;
  11. /**
  12. * @actionParam string item: Project ID.
  13. */
  14. public function doAction() {
  15. $conf = $this->getContext()->getConf();
  16. $db = $this->getContext()->getDB();
  17. $request = $this->getContext()->getRequest();
  18. $projectID = $request->getVal( 'item' );
  19. if ( !$projectID ) {
  20. $this->setError( 'missing-parameters' );
  21. return;
  22. }
  23. // Note: The job list is reverse chronologic (descending).
  24. // To query the "next" page, you get the jobs with an id
  25. // that is lower than the last entry on the current page.
  26. // Parameters for navigation of job list in this project
  27. $dir = $request->getVal( 'dir', '' );
  28. $offset = $request->getInt( 'offset' );
  29. $limit = $request->getInt( 'limit', $this->defaultLimit );
  30. if ( !in_array( $dir, array( '', 'back' ) )
  31. || $limit < 1
  32. || $limit > 100
  33. ) {
  34. $this->setError( 'invalid-input' );
  35. return;
  36. }
  37. // Get project info
  38. $projectRow = $db->getRow(str_queryf(
  39. 'SELECT
  40. id,
  41. display_title,
  42. site_url,
  43. updated,
  44. created
  45. FROM projects
  46. WHERE id = %s;',
  47. $projectID
  48. ));
  49. if ( !$projectRow ) {
  50. $this->setError( 'invalid-input', 'Project does not exist' );
  51. return;
  52. }
  53. $conds = '';
  54. if ( $offset ) {
  55. if ( $dir === 'back' ) {
  56. $conds = 'AND id > ' . intval( $offset );
  57. } else {
  58. $conds = 'AND id < ' . intval( $offset );
  59. }
  60. }
  61. // Get list of jobs
  62. $jobRows = $db->getRows(str_queryf(
  63. 'SELECT
  64. id,
  65. name
  66. FROM
  67. jobs
  68. WHERE project_id = %s
  69. ' . $conds . '
  70. ORDER BY id ' . ( $dir === 'back' ? 'ASC' : 'DESC' ) . '
  71. LIMIT %u;',
  72. $projectID,
  73. // Get one more so we know whether to display navigation
  74. $limit + 1
  75. ));
  76. $jobs = array();
  77. // List of all user agents used in recent jobs
  78. // This is as helper to allow easy creation of placeholder gaps in a UI
  79. // when iterating over jobs, because not all jobs have the same user agents.
  80. $userAgents = array();
  81. if ( !$jobRows ) {
  82. $pagination = array();
  83. } else {
  84. $pagination = $this->getPaginationData( $dir, $offset, $limit, $jobRows, $projectID );
  85. if ( $dir === 'back' ) {
  86. $jobRows = array_reverse( $jobRows );
  87. }
  88. foreach ( $jobRows as $jobRow ) {
  89. $jobID = intval( $jobRow->id );
  90. $jobAction = JobAction::newFromContext( $this->getContext()->createDerivedRequestContext(
  91. array(
  92. 'item' => $jobID,
  93. )
  94. ) );
  95. $jobAction->doAction();
  96. if ( $jobAction->getError() ) {
  97. $this->setError( $jobAction->getError() );
  98. return;
  99. }
  100. $jobActionData = $jobAction->getData();
  101. // Add user agents array of this job to the overal user agents list.
  102. // php array+ automatically fixes clashing keys. The values are always the same
  103. // so it doesn't matter whether or not it overwrites.
  104. $userAgents += $jobActionData['userAgents'];
  105. $jobs[] = array(
  106. 'info' => $jobActionData['info'],
  107. 'name' => $jobRow->name,
  108. 'summaries' => $jobActionData['uaSummaries'],
  109. );
  110. }
  111. }
  112. uasort( $userAgents, 'BrowserInfo::sortUaData' );
  113. $projectInfo = (array)$projectRow;
  114. unset( $projectInfo['updated'], $projectInfo['created'] );
  115. self::addTimestampsTo( $projectInfo, $projectRow->updated, 'updated' );
  116. self::addTimestampsTo( $projectInfo, $projectRow->created, 'created' );
  117. $this->setData( array(
  118. 'info' => $projectInfo,
  119. 'jobs' => $jobs,
  120. 'pagination' => $pagination,
  121. 'userAgents' => $userAgents,
  122. ));
  123. }
  124. private function getPaginationData( $dir, $offset, $limit, &$jobRows, $projectID ) {
  125. $limitUrl = '';
  126. if ( $limit !== $this->defaultLimit ) {
  127. $limitUrl = '&limit=' . $limit;
  128. }
  129. $numRows = count( $jobRows );
  130. if ( $numRows ) {
  131. $row = reset( $jobRows );
  132. $firstRowID = $row->id;
  133. if ( $numRows > $limit ) {
  134. array_pop( $jobRows );
  135. }
  136. $row = end( $jobRows );
  137. $lastRowID = $row->id;
  138. } else {
  139. $firstRowID = '';
  140. $lastRowID = '';
  141. }
  142. if ( $dir === 'back' ) {
  143. $isFirst = $numRows <= $limit;
  144. $isLast = !$offset;
  145. $firstID = $lastRowID;
  146. $lastID = $firstRowID;
  147. } else {
  148. $isFirst = !$offset;
  149. $isLast = $numRows <= $limit;
  150. $firstID = $firstRowID;
  151. $lastID = $lastRowID;
  152. }
  153. if ( $isFirst ) {
  154. $prev = false;
  155. } else {
  156. $prev = array(
  157. 'dir' => 'back',
  158. 'offset' => $firstID,
  159. 'viewUrl' => swarmpath( "/project/$projectID?offset={$firstID}&dir=back" . $limitUrl, 'fullurl' ),
  160. );
  161. }
  162. if ( $isLast ) {
  163. $next = false;
  164. } else {
  165. $next = array(
  166. 'offset' => $lastID,
  167. 'viewUrl' => swarmpath( "/project/$projectID?offset={$lastID}" . $limitUrl, 'fullurl' ),
  168. );
  169. }
  170. return array(
  171. 'prev' => $prev,
  172. 'next' => $next,
  173. );
  174. }
  175. /**
  176. * @param string $id
  177. * @param array $options
  178. * @return array Exposes the new auth token
  179. */
  180. public function create( $id, Array $options = null ) {
  181. $db = $this->getContext()->getDB();
  182. $password = isset( $options['password'] ) ? $options['password'] : null;
  183. $displayTitle = isset( $options['displayTitle'] ) ? $options['displayTitle'] : null;
  184. $siteUrl = isset( $options['siteUrl'] ) ? $options['siteUrl'] : '';
  185. if ( !$id || !$displayTitle || !$password ) {
  186. $this->setError( 'missing-parameters' );
  187. return;
  188. }
  189. // Check if a project by this id doesn't exist already
  190. $row = $db->getOne( str_queryf( 'SELECT id FROM projects WHERE id = %s;', $id ) );
  191. if ( $row ) {
  192. $this->setError( 'invalid-input', 'Unable to create project, a project by that name exists already.' );
  193. return;
  194. }
  195. // Validate project id
  196. if ( !LoginAction::isValidName( $id ) ) {
  197. $this->setError( 'invalid-input', 'Project ids must be in format: "' . LoginAction::getNameValidationRegex() . '".' );
  198. return;
  199. }
  200. // maxlength (otherwise MySQL will crop it)
  201. if ( strlen( $displayTitle ) > 255 ) {
  202. $this->setError( 'Display title has to be no longer than 255 characters.' );
  203. return;
  204. }
  205. // Create the project
  206. $authToken = LoginAction::generateRandomHash( 40 );
  207. $authTokenHash = sha1( $authToken );
  208. $isInserted = $db->query(str_queryf(
  209. 'INSERT INTO projects
  210. (id, display_title, site_url, password, auth_token, updated, created)
  211. VALUES(%s, %s, %s, %s, %s, %s, %s);',
  212. $id,
  213. $displayTitle,
  214. $siteUrl,
  215. LoginAction::generatePasswordHash( $password ),
  216. $authTokenHash,
  217. swarmdb_dateformat( SWARM_NOW ),
  218. swarmdb_dateformat( SWARM_NOW )
  219. ));
  220. if ( !$isInserted ) {
  221. $this->setError( 'internal-error', 'Insertion of row into database failed.' );
  222. return;
  223. }
  224. return array(
  225. 'authToken' => $authToken,
  226. );
  227. }
  228. }